Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
engines/bladerunner/bladerunner.cpp
engines/bladerunner/detection_tables.h
engines/bladerunner/metaengine.cpp

File diff suppressed because it is too large Load Diff

287
engines/bladerunner/actor.h Normal file
View File

@@ -0,0 +1,287 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ACTOR_H
#define BLADERUNNER_ACTOR_H
#include "bladerunner/boundingbox.h"
#include "bladerunner/vector.h"
#include "common/array.h"
#include "common/rect.h"
namespace BladeRunner {
class ActorClues;
class ActorCombat;
class ActorWalk;
class BladeRunnerEngine;
class BoundingBox;
class MovementTrack;
class SaveFileReadStream;
class SaveFileWriteStream;
class View;
class Actor {
BladeRunnerEngine *_vm;
static const int kActorTimers = 7;
public:
BoundingBox _bbox;
Common::Rect _screenRectangle;
MovementTrack *_movementTrack;
ActorWalk *_walkInfo;
ActorCombat *_combatInfo;
ActorClues *_clues;
private:
int _honesty;
int _intelligence;
int _stability;
int _combatAggressiveness;
int _goalNumber;
Common::Array<int> _friendlinessToOther;
int _currentHP;
int _maxHP;
int _id;
int _setId;
Vector3 _position;
int _facing; // [0, 1024)
int _targetFacing;
int _walkboxId;
int _cluesLimit;
uint32 _timer4RemainDefault;
// Flags
bool _isTarget;
bool _isInvisible;
bool _isImmuneToObstacles;
bool _mustReachWalkDestination;
bool _isRetired;
bool _inCombat;
bool _isMoving;
bool _damageAnimIfMoving;
// Movement
bool _movementTrackPaused;
int _movementTrackNextWaypointId;
int32 _movementTrackNextDelay; // probably not used
int _movementTrackNextAngle; // fixed: used for AI_Movement_Track_Append_With_Facing - original: probably not used
bool _movementTrackNextRunning;
int _movementTrackWalkingToWaypointId;
int32 _movementTrackDelayOnNextWaypoint;
// Animation
int _width;
int _height;
int _animationMode;
int _animationModeCombatIdle;
int _animationModeCombatWalk;
int _animationModeCombatRun;
int _fps;
int _frameMs;
int _animationId;
int _animationFrame;
int _retiredWidth;
int _retiredHeight;
int32 _timersLeft[kActorTimers]; // this keeps time difference, and it is stored during save() (saveInt actually saves a uint32)
uint32 _timersLast[kActorTimers]; // this keeps actual time, and is not stored during save(), so it can be a uint32
float _scale;
Vector3 _actorSpeed;
int _sitcomRatio;
public:
Actor(BladeRunnerEngine *_vm, int actorId);
~Actor();
void setup(int actorId);
void setAtXYZ(const Vector3 &pos, int facing, bool setFacing = true, bool moving = false, bool retired = false);
void setAtWaypoint(int waypointId, int angle, bool moving, bool retired);
int getId() const { return _id; };
float getX() const;
float getY() const;
float getZ() const;
Vector3 getXYZ() const;
int getFacing() const;
int getAnimationMode() const;
int getAnimationId() const;
Vector3 getPosition() const { return _position; }
void changeAnimationMode(int animationMode, bool force = false);
void changeAnimationState(int animationState, int animationFrame, int animationStateNext, int animationNext); // new for debugging purposes
void queryAnimationState(int *animationState, int *animationFrame, int *animationStateNext, int *animationNext); // new for debugging purposes
int getFPS() const;
void setFPS(int fps);
void increaseFPS();
void timerStart(int timerId, int32 intervalMillis);
void timerReset(int timerId);
int32 timerLeft(int timerId);
void timersUpdate();
void timerUpdate(int timerId);
void movementTrackNext(bool omitAiScript);
void movementTrackPause();
void movementTrackUnpause();
void movementTrackWaypointReached();
bool loopWalk(const Vector3 &destination, int proximity, bool interruptible, bool runFlag, const Vector3 &start, float targetWidth, float targetSize, bool mustReach, bool *isRunningFlag, bool async);
bool walkTo(bool runFlag, const Vector3 &destination, bool mustReach);
bool loopWalkToActor(int otherActorId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag);
bool loopWalkToItem(int itemId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag);
bool loopWalkToSceneObject(const Common::String &objectName, int proximity, bool interruptible, bool runFlag, bool mustReach, bool *isRunningFlag);
bool loopWalkToWaypoint(int waypointId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag);
bool loopWalkToXYZ(const Vector3 &destination, int proximity, bool interruptible, bool runFlag, bool mustReach, bool *isRunningFlag);
bool asyncWalkToWaypoint(int waypointId, int proximity, bool runFlag, bool mustReach);
void asyncWalkToXYZ(const Vector3 &destination, int proximity, bool runFlag, bool mustReach);
void run();
bool tick(bool forceUpdate, Common::Rect *screenRect);
void tickCombat();
bool draw(Common::Rect *screenRect);
void resetScreenRectangleAndBbox();
int getSetId() const;
void setSetId(int setId);
const BoundingBox &getBoundingBox() const { return _bbox; }
const Common::Rect &getScreenRectangle() { return _screenRectangle; }
int getWalkbox() const { return _walkboxId; }
bool isRetired() const { return _isRetired; }
bool isTarget() const { return _isTarget; }
void setTarget(bool targetable);
bool isImmuneToObstacles() const { return _isImmuneToObstacles; }
bool inCombat() const { return _inCombat; }
bool isMoving() const { return _isMoving; }
void setMoving(bool value) { _isMoving = value; }
bool mustReachWalkDestination() const { return _mustReachWalkDestination; }
bool isWalking() const;
bool isRunning() const;
void stopWalking(bool value);
void faceActor(int otherActorId, bool animate);
void faceObject(const Common::String &objectName, bool animate);
void faceItem(int itemId, bool animate);
void faceWaypoint(int waypointId, bool animate);
void faceXYZ(float x, float y, float z, bool animate);
void faceXYZ(const Vector3 &pos, bool animate);
void faceCurrentCamera(bool animate);
void faceHeading(int heading, bool animate);
void setFacing(int facing, bool halfOrSet = true);
int getCurrentHP() const { return _currentHP; }
int getMaxHP() const { return _maxHP; }
void setCurrentHP(int hp);
void setHealth(int hp, int maxHp);
void modifyCurrentHP(signed int change);
void modifyMaxHP(signed int change);
int getFriendlinessToOther(int otherActorId) const { return _friendlinessToOther[otherActorId]; }
void setFriendlinessToOther(int otherActorId, int friendliness);
void modifyFriendlinessToOther(int otherActorId, signed int change);
bool checkFriendlinessAndHonesty(int otherActorId);
int getHonesty() const { return _honesty; }
void setHonesty(int honesty);
void modifyHonesty(signed int change);
int getIntelligence() const { return _intelligence; }
void setIntelligence(int intelligence);
void modifyIntelligence(signed int change);
int getStability() const { return _stability; }
void setStability(int stability);
void modifyStability(signed int change);
int getCombatAggressiveness() const { return _combatAggressiveness; }
void setCombatAggressiveness(int combatAggressiveness);
void modifyCombatAggressiveness(signed int change);
void setInvisible(bool isInvisible);
void setImmunityToObstacles(bool isImmune);
void setFlagDamageAnimIfMoving(bool value);
bool getFlagDamageAnimIfMoving() const;
int getSitcomRatio() const;
void retire(bool isRetired, int width, int height, int retiredByActorId);
void combatModeOn(int initialState, bool rangedAttack, int enemyId, int waypointType, int animationModeCombatIdle, int animationModeCombatWalk, int animationModeCombatRun, int fleeRatio, int coverRatio, int attackRatio, int damage, int range, bool unstoppable);
void combatModeOff();
void setGoal(int goalNumber);
int getGoal() const;
float distanceFromActor(int otherActorId);
int angleTo(const Vector3 &target) const;
void speechPlay(int sentenceId, bool voiceOver);
void speechStop();
bool isSpeeching();
void addClueToDatabase(int clueId, int unknown, bool clueAcquired, bool unknownFlag, int fromActorId);
bool canAcquireClue(int clueId) const;
void acquireClue(int clueId, bool unknownFlag, int fromActorId);
void loseClue(int clueId);
bool hasClue(int clueId) const;
bool copyClues(int actorId);
void acquireCluesByRelations();
int soundVolume() const;
int soundPan(uint8 overrideRange = 35) const;
bool isObstacleBetween(const Vector3 &target);
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
static int findTargetUnderMouse(BladeRunnerEngine *vm, int mouseX, int mouseY);
private:
void setBoundingBox(const Vector3 &position, bool retired);
float distanceFromView(View *view) const;
bool findEmptyPositionAround(const Vector3 &startPosition, const Vector3 &targetPosition, float size, Vector3 *emptyPosition);
bool findNearestPosition(Vector3 *nearestPosition, float targetWidth, int proximity, float targetSize, const Vector3 &startPosition, const Vector3 &targetPosition);
bool stepAway(const Vector3 &destination, float distance);
//bool walkFindU3(int actorId, Vector3 from, int distance, Vector3 *out);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,442 @@
/* 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/actor_clues.h"
#include "bladerunner/actor.h"
#include "bladerunner/script/ai_script.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/game_info.h"
#include "bladerunner/crimes_database.h"
#include "bladerunner/savefile.h"
#include "common/debug.h"
namespace BladeRunner {
ActorClues::ActorClues(BladeRunnerEngine *vm, int cluesLimit) {
_vm = vm;
_count = 0;
_maxCount = 0;
switch (cluesLimit) {
case 4:
_maxCount = kClueCount;
break;
case 3:
_maxCount = 100;
break;
case 2:
_maxCount = 50;
break;
case 1:
_maxCount = 25;
break;
case 0:
_maxCount = 0;
break;
default:
return;
}
if (_maxCount > 0) {
_clues.resize(_maxCount);
}
removeAll();
}
void ActorClues::acquire(int clueId, bool flag2, int fromActorId) {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) { // prevent assertion fault
// debug("Actor could not acquire clue: \"%s\" from %d", _vm->_crimesDatabase->getClueText(clueId), fromActorId);
return;
} else {
_clues[clueIndex].flags |= 0x01;
_clues[clueIndex].flags = (_clues[clueIndex].flags & ~0x02) | (((flag2? 1:0) << 1) & 0x02);
_clues[clueIndex].fromActorId = fromActorId;
// debug("Actor acquired clue: \"%s\" from %d", _vm->_crimesDatabase->getClueText(clueId), fromActorId);
}
}
void ActorClues::lose(int clueId) {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) { // prevent assertion fault
// debug("Actor could not lose clue: \"%s\"", _vm->_crimesDatabase->getClueText(clueId));
return;
} else {
_clues[clueIndex].flags = 0;
}
}
bool ActorClues::isAcquired(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return false;
}
return _clues[clueIndex].flags & 0x01;
}
int ActorClues::getWeight(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return 0;
}
return _clues[clueIndex].weight;
}
int ActorClues::getModifier(int actorId, int otherActorId, int clueId) {
Actor *actor = _vm->_actors[actorId];
Actor *otherActor = _vm->_actors[otherActorId];
int modifier1, modifier2, modifier3, modifier4;
int friendliness = actor->getFriendlinessToOther(otherActorId);
int clueWeight = otherActor->_clues->getWeight(clueId);
if (actor->_clues->isFlag2(clueId)) {
modifier1 = 100 - actor->getHonesty() - friendliness;
} else {
modifier1 = 0;
}
modifier2 = 0;
for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); ++i) {
if (i != actorId && i != otherActorId) {
modifier2 += (friendliness - 50) * _vm->_aiScripts->callGetFriendlinessModifierIfGetsClue(i, otherActorId, clueId) / 100;
}
}
modifier3 = _vm->_aiScripts->callGetFriendlinessModifierIfGetsClue(otherActorId, actorId, clueId);
modifier4 = _vm->_rnd.getRandomNumberRng(0, (100 - actor->getIntelligence()) / 10);
if (_vm->_rnd.getRandomNumberRng(0, 1) == 1) {
modifier4 = -modifier4;
}
return modifier1 + modifier2 + modifier3 + modifier4 + clueWeight;
}
static int cluesCompare(const void *p1, const void *p2) {
const ActorClues::CluesUS *clue1 = (const ActorClues::CluesUS *)p1;
const ActorClues::CluesUS *clue2 = (const ActorClues::CluesUS *)p2;
if (clue1->modifier > clue2->modifier)
return -1;
return (clue1->modifier < clue2->modifier);
}
void ActorClues::acquireCluesByRelations(int actorId, int otherActorId) {
CluesUS clues1[kClueCount], clues2[kClueCount];
int count1 = findAcquirableCluesFromActor(actorId, otherActorId, clues1, kClueCount);
int count2 = findAcquirableCluesFromActor(otherActorId, actorId, clues2, kClueCount);
if (count1 || count2) {
for (int i = 0; i < count1; ++i) {
clues1[i].modifier = getModifier(actorId, otherActorId, clues1[i].clueId);
}
qsort(clues1, count1, sizeof(CluesUS), cluesCompare);
for (int i = 0; i < count2; ++i) {
clues2[i].modifier = getModifier(otherActorId, actorId, clues2[i].clueId);
}
qsort(clues2, count2, sizeof(CluesUS), cluesCompare);
Actor *actor = _vm->_actors[actorId];
Actor *otherActor = _vm->_actors[otherActorId];
uint avgParameters = (otherActor->getHonesty() + otherActor->getIntelligence() + actor->getFriendlinessToOther(otherActorId)) / 3;
int clue1count = avgParameters * count1 / 100;
if (avgParameters >= 50 && clue1count == 0 && count1 == 1) {
clue1count = 1;
}
avgParameters = (actor->getHonesty() + actor->getIntelligence() + otherActor->getFriendlinessToOther(actorId)) / 3;
int clue2count = avgParameters * count2 / 100;
if (avgParameters >= 50 && clue2count == 0 && count2 == 1) {
clue2count = 1;
}
for (int i = 0; i < clue2count; ++i) {
bool flag = false;
if (otherActor->_clues->isFlag2(clues2[i].clueId)) {
avgParameters = (2 * otherActor->getFriendlinessToOther(actorId) + otherActor->getHonesty()) / 3;
if (avgParameters > 70) {
avgParameters = 100;
} else if (avgParameters < 30) {
avgParameters = 0;
}
if (_vm->_rnd.getRandomNumberRng(1, 100) <= avgParameters) {
flag = true;
}
}
actor->_clues->acquire(clues2[i].clueId, flag, otherActorId);
}
for (int i = 0; i < clue1count; ++i) {
bool flag = false;
if (actor->_clues->isFlag2(clues1[i].clueId)) {
avgParameters = (2 * actor->getFriendlinessToOther(otherActorId) + actor->getHonesty()) / 3;
if (avgParameters > 70) {
avgParameters = 100;
} else if (avgParameters < 30) {
avgParameters = 0;
}
if (_vm->_rnd.getRandomNumberRng(1, 100) <= avgParameters) {
flag = true;
}
}
otherActor->_clues->acquire(clues1[i].clueId, flag, actorId);
}
}
}
int ActorClues::findAcquirableCluesFromActor(int actorId, int targetActorId, ActorClues::CluesUS *list, int size) {
Actor *actor = _vm->_actors[actorId];
Actor *otherActor = _vm->_actors[targetActorId];
int count = 0;
int cluesCount = actor->_clues->getCount();
for (int i = 0; i < cluesCount; ++i) {
int clueId = actor->_clues->getClueIdByIndex(i);
if (actor->_clues->isAcquired(clueId)
&& otherActor->_clues->getWeight(clueId) > 0
&& !otherActor->_clues->isAcquired(clueId)) {
list[count].clueId = clueId;
list[count].modifier = 0;
++count;
}
}
return count;
}
int ActorClues::getFromActorId(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return -1;
}
return _clues[clueIndex].fromActorId;
}
/**
* @brief returns if flag2 for specified clue is set.
*
* Bit flag "flag2" seems to affect one modifier when sharing / spreading clues
* (based on Honesty, friendliness and some seemingly *complicated* algorithm).
* It seems that it increases the overall modifier value for a clue, making it more likely to be shared with another actor.
*
* @param clueId
* @return true if this bit flag is set, false otherwise
*/
bool ActorClues::isFlag2(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return false;
}
return _clues[clueIndex].flags & 0x02;
}
bool ActorClues::isViewed(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return false;
}
return _clues[clueIndex].flags & 0x04;
}
void ActorClues::setViewed(int clueId, bool viewed) {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return;
}
if (viewed) {
_clues[clueIndex].flags |= 0x04;
} else {
_clues[clueIndex].flags &= ~0x04;
}
}
// Method for Restored Content
// Checks whether a clue, which McCoy has, was shared between McCoy and Mainframe
bool ActorClues::isSharedWithMainframe(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return false;
}
return _clues[clueIndex].field4 & 0x01;
}
// Method for Restored Content
// Marks a clue, which McCoy has, as shared between McCoy and Mainframe
void ActorClues::setSharedWithMainframe(int clueId, bool value) {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return;
}
if (value) {
_clues[clueIndex].field4 |= 0x01;
} else {
_clues[clueIndex].field4 &= ~0x01;
}
}
bool ActorClues::isPrivate(int clueId) const {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return false;
}
return _clues[clueIndex].flags & 0x08;
}
void ActorClues::setPrivate(int clueId, bool value) {
int clueIndex = findClueIndex(clueId);
if (clueIndex == -1) {
return;
}
if (value) {
_clues[clueIndex].flags |= 0x08;
} else {
_clues[clueIndex].flags &= ~0x08;
}
}
int ActorClues::getCount() const {
return _count;
}
int ActorClues::getClueIdByIndex(int index) const {
assert(index < _count);
if (index < 0 || index >= _count) {
return -1;
}
return _clues[index].clueId;
}
void ActorClues::removeAll() {
_count = 0;
for (int i = 0; i < _maxCount; ++i) {
remove(i);
}
}
int ActorClues::findClueIndex(int clueId) const {
for (int i = 0; i < _count; ++i) {
if (clueId == _clues[i].clueId) {
return i;
}
}
return -1;
}
void ActorClues::add(int actorId, int clueId, int weight, bool acquired, bool unknownFlag, int fromActorId) {
assert(_count < _maxCount);
_clues[_count].clueId = clueId;
_clues[_count].weight = weight;
_clues[_count].flags = 0;
_clues[_count].flags = (_clues[_count].flags & ~0x01) | ((acquired? 1:0) & 0x01);
_clues[_count].flags = (_clues[_count].flags & ~0x02) | (((unknownFlag? 1:0) << 1) & 0x02);
_clues[_count].fromActorId = fromActorId;
++_count;
}
bool ActorClues::exists(int clueId) const {
return findClueIndex(clueId) != -1;
}
void ActorClues::remove(int index) {
// if (_vm->_crimesDatabase) {
// debug("Actor removed clue: \"%s\"", _vm->_crimesDatabase->getClueText(_clues[index].clueId));
// }
_clues[index].clueId = -1;
_clues[index].weight = 0;
_clues[index].flags = 0;
_clues[index].fromActorId = -1;
_clues[index].field3 = -1; // unused (but stored/restored)
_clues[index].field4 = 0; // Restored Content: Use to mark if McCoy's clue was shared with Mainframe. original: unused (but stored/restored)
_clues[index].field5 = -1; // unused (but stored/restored)
_clues[index].field6 = 0; // unused (but stored/restored)
_clues[index].field7 = -1; // unused (but stored/restored)
_clues[index].field8 = 0; // unused (but stored/restored)
}
void ActorClues::save(SaveFileWriteStream &f) {
f.writeInt(_count);
f.writeInt(_maxCount);
for (int i = 0; i < _maxCount; ++i) {
Clue &c = _clues[i];
f.writeInt(c.clueId);
f.writeInt(c.weight);
f.writeInt(c.fromActorId);
f.writeInt(c.field3);
f.writeInt(c.field4);
f.writeInt(c.field5);
f.writeInt(c.field6);
f.writeInt(c.field7);
f.writeInt(c.field8);
f.writeByte(c.flags);
}
}
void ActorClues::load(SaveFileReadStream &f) {
_count = f.readInt();
_maxCount = f.readInt();
_clues.clear();
_clues.resize(_maxCount);
for (int i = 0; i < _maxCount; ++i) {
Clue &c = _clues[i];
c.clueId = f.readInt();
c.weight = f.readInt();
c.fromActorId = f.readInt();
c.field3 = f.readInt();
c.field4 = f.readInt();
c.field5 = f.readInt();
c.field6 = f.readInt();
c.field7 = f.readInt();
c.field8 = f.readInt();
c.flags = f.readByte();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,108 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ACTOR_CLUES_H
#define BLADERUNNER_ACTOR_CLUES_H
#include "common/array.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class ActorClues {
// _vm->_gameInfo->getClueCount()
static const int kClueCount = 288;
struct Clue {
int clueId;
int weight;
int fromActorId;
int field3; // unused (but stored/restored)
int field4; // Used in Restored Content. Original: unused (but stored/restored)
int field5; // unused (but stored/restored)
int field6; // unused (but stored/restored)
int field7; // unused (but stored/restored)
int field8; // unused (but stored/restored)
byte flags; // bit 0 (acquired), bit 1 (unknown), bit 2 (viewed), bit 3 (private)
};
BladeRunnerEngine *_vm;
int _count;
int _maxCount;
Common::Array<Clue> _clues;
public:
struct CluesUS {
int clueId;
int modifier;
};
public:
ActorClues(BladeRunnerEngine *_vm, int cluesLimit);
void add(int actorId, int clueId, int unknown, bool acquired, bool unknownFlag, int fromActorId);
bool exists(int clueId) const;
void acquire(int clueId, bool flag2, int fromActorId);
void lose(int clueId);
bool isAcquired(int clueId) const;
int getWeight(int clueId) const;
int getModifier(int actorId, int otherActorId, int clueId);
void acquireCluesByRelations(int actorId, int otherActorId);
int findAcquirableCluesFromActor(int actorId, int targetActorId, CluesUS *list, int size);
int getFromActorId(int clueId) const;
bool isFlag2(int clueId) const;
bool isViewed(int clueId) const;
void setViewed(int clueId, bool viewed);
bool isPrivate(int clueId) const;
void setPrivate(int clueId, bool value);
// Restored Content method - Checks whether a clue, that McCoy has, was shared with Mainframe
bool isSharedWithMainframe(int clueId) const;
// Restored Content method - Marks a clue, that McCoy has, as shared with Mainframe
void setSharedWithMainframe(int clueId, bool value);
int getCount() const;
int getClueIdByIndex(int index) const;
void removeAll();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
int findClueIndex(int clueId) const;
void remove(int clueIndex);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,700 @@
/* 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/actor_combat.h"
#include "bladerunner/actor.h"
#include "bladerunner/audio_speech.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/combat.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/game_info.h"
#include "bladerunner/movement_track.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/script/ai_script.h"
#include "bladerunner/set.h"
#include "bladerunner/settings.h"
namespace BladeRunner {
ActorCombat::ActorCombat(BladeRunnerEngine *vm) {
_vm = vm;
reset();
}
ActorCombat::~ActorCombat() {
}
void ActorCombat::setup() {
reset();
}
void ActorCombat::combatOn(int actorId, int initialState, bool rangedAttackFlag, int enemyId, int waypointType, int fleeRatio, int coverRatio, int attackRatio, int damage, int range, bool unstoppable) {
_actorId = actorId;
_state = initialState;
_rangedAttack = rangedAttackFlag;
_enemyId = enemyId;
_waypointType = waypointType;
_damage = damage;
_fleeRatioConst = fleeRatio;
_coverRatioConst = coverRatio;
_attackRatioConst = attackRatio;
_fleeRatio = fleeRatio;
_coverRatio = coverRatio;
_attackRatio = attackRatio;
_active = true;
if (_rangedAttack) {
_range = range;
} else {
_range = 300;
}
_unstoppable = unstoppable;
Actor *actor = _vm->_actors[_actorId];
_actorPosition = actor->getXYZ();
_enemyPosition = _vm->_actors[_enemyId]->getXYZ();
actor->_movementTrack->flush();
actor->stopWalking(false);
if (_enemyId == kActorMcCoy) {
actor->setTarget(true);
}
_actorHp = actor->getCurrentHP();
_coversWaypointCount = 0;
for (int i = 0; i < (int)_vm->_gameInfo->getCoverWaypointCount(); ++i) {
if (_vm->_combat->_coverWaypoints[i].type == waypointType && _vm->_combat->_coverWaypoints[i].setId == actor->getSetId()) {
++_coversWaypointCount;
}
}
if (_coversWaypointCount == 0) {
_coverRatioConst = 0;
_coverRatio = 0;
}
_fleeWaypointsCount = 0;
for (int i = 0; i < (int)_vm->_gameInfo->getFleeWaypointCount(); ++i) {
if (_vm->_combat->_fleeWaypoints[i].type == waypointType && _vm->_combat->_fleeWaypoints[i].setId == actor->getSetId()) {
++_fleeWaypointsCount;
}
}
if (_fleeWaypointsCount == 0) {
_fleeRatioConst = 0;
_fleeRatio = 0;
}
}
void ActorCombat::combatOff() {
_active = false;
reset();
}
void ActorCombat::tick() {
static int processingCounter = 0;
if (!_active || processingCounter > 0) {
return;
}
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (actor->getSetId() != enemy->getSetId()) {
actor->combatModeOff();
return;
}
++processingCounter;
_actorPosition = actor->getXYZ();
_enemyPosition = enemy->getXYZ();
if (_attackRatioConst >= 0) {
_attackRatio = _attackRatioConst;
} else {
_attackRatio = calculateAttackRatio();
}
if (_vm->_combat->findCoverWaypoint(_waypointType, _actorId, _enemyId) != -1) {
if (_coverRatioConst >= 0) {
_coverRatio = _coverRatioConst;
} else {
_coverRatio = calculateCoverRatio();
}
} else {
_coverRatio = 0;
}
if (_fleeRatioConst >= 0) {
_fleeRatio = _fleeRatioConst;
} else {
_fleeRatio = calculateFleeRatio();
}
float dist = actor->distanceFromActor(_enemyId);
int oldState = _state;
if (_attackRatio < _fleeRatio || _attackRatio < _coverRatio) {
if (_coverRatio >= _fleeRatio && _coverRatio >= _attackRatio) {
_state = kActorCombatStateCover;
} else {
_state = kActorCombatStateFlee;
}
} else {
if (_rangedAttack) {
if (dist > _range) {
_state = kActorCombatStateApproachRangedAttack;
} else {
if (actor->isObstacleBetween(_enemyPosition)) {
_state = kActorCombatStateUncover;
} else {
_state = kActorCombatStateRangedAttack;
}
}
} else {
if (dist > 36.0f) {
_state = kActorCombatStateApproachCloseAttack;
} else {
_state = kActorCombatStateCloseAttack;
}
}
}
if (enemy->isRetired()) {
_state = kActorCombatStateIdle;
}
if (actor->getAnimationMode() == kAnimationModeHit || actor->getAnimationMode() == kAnimationModeCombatHit) {
_state = kActorCombatStateIdle;
} else {
if (_state != oldState) {
actor->stopWalking(false);
}
}
switch (_state) {
case kActorCombatStateCover:
cover();
break;
case kActorCombatStateApproachCloseAttack:
approachToCloseAttack();
break;
case kActorCombatStateUncover:
uncover();
break;
case kActorCombatStateAim:
aim();
break;
case kActorCombatStateRangedAttack:
rangedAttack();
break;
case kActorCombatStateCloseAttack:
closeAttack();
break;
case kActorCombatStateFlee:
flee();
break;
case kActorCombatStateApproachRangedAttack:
approachToRangedAttack();
break;
default:
break;
}
--processingCounter;
}
void ActorCombat::hitAttempt() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (_enemyId == kActorMcCoy && !_vm->playerHasControl() && !_unstoppable) {
return;
}
if (actor->isRetired()) {
return;
}
int attackCoefficient = 0;
if (_rangedAttack) {
attackCoefficient = _rangedAttack ? getCoefficientRangedAttack() : 0;
} else {
attackCoefficient = getCoefficientCloseAttack();
}
if (attackCoefficient == 0) {
return;
}
int random = _vm->_rnd.getRandomNumberRng(1, 100);
if (random <= attackCoefficient) {
if (enemy->isWalking()) {
enemy->stopWalking(true);
}
int sentenceId = _vm->_rnd.getRandomNumberRng(0, 1) ? 9000 : 9005;
if (enemy->inCombat()) {
enemy->changeAnimationMode(kAnimationModeCombatHit, false);
} else {
enemy->changeAnimationMode(kAnimationModeHit, false);
}
int damage = 0;
if (_rangedAttack) {
damage = getDamageRangedAttack(random, attackCoefficient);
} else {
damage = getDamageCloseAttack(random, attackCoefficient);
}
int enemyHp = MAX(enemy->getCurrentHP() - damage, 0);
enemy->setCurrentHP(enemyHp);
if (enemyHp <= 0) {
if (!enemy->isRetired()) {
#if BLADERUNNER_ORIGINAL_BUGS
#else
// make sure the dead enemy won't pick a pending movement track and re-spawn
enemy->_movementTrack->flush();
#endif
if (enemy->inCombat()) {
enemy->changeAnimationMode(kAnimationModeCombatDie, false);
} else {
enemy->changeAnimationMode(kAnimationModeDie, false);
}
sentenceId = 9020;
}
enemy->retire(true, 6, 3, _actorId);
}
if (_enemyId == kActorMcCoy) {
sentenceId += 900;
}
_vm->_audioSpeech->playSpeechLine(_enemyId, sentenceId, 75, enemy->soundPan(), 99);
}
}
void ActorCombat::save(SaveFileWriteStream &f) {
f.writeInt(_actorId);
f.writeBool(_active);
f.writeInt(_state);
f.writeBool(_rangedAttack);
f.writeInt(_enemyId);
f.writeInt(_waypointType);
f.writeInt(_damage);
f.writeInt(_fleeRatio);
f.writeInt(_coverRatio);
f.writeInt(_attackRatio);
f.writeInt(_fleeRatioConst);
f.writeInt(_coverRatioConst);
f.writeInt(_attackRatioConst);
f.writeInt(_range);
f.writeInt(_unstoppable);
f.writeInt(_actorHp);
f.writeInt(_fleeingTowards);
f.writeVector3(_actorPosition);
f.writeVector3(_enemyPosition);
f.writeInt(_coversWaypointCount);
f.writeInt(_fleeWaypointsCount);
}
void ActorCombat::load(SaveFileReadStream &f) {
_actorId = f.readInt();
_active = f.readBool();
_state = f.readInt();
_rangedAttack = f.readBool();
_enemyId = f.readInt();
_waypointType = f.readInt();
_damage = f.readInt();
_fleeRatio = f.readInt();
_coverRatio = f.readInt();
_attackRatio = f.readInt();
_fleeRatioConst = f.readInt();
_coverRatioConst = f.readInt();
_attackRatioConst = f.readInt();
_range = f.readInt();
_unstoppable = f.readInt();
_actorHp = f.readInt();
_fleeingTowards = f.readInt();
_actorPosition = f.readVector3();
_enemyPosition = f.readVector3();
_coversWaypointCount = f.readInt();
_fleeWaypointsCount = f.readInt();
}
void ActorCombat::reset() {
_active = false;
_actorId = -1;
_state = -1;
_rangedAttack = false;
_enemyId = -1;
_waypointType = -1;
_damage = 0;
_fleeRatio = -1;
_coverRatio = -1;
_attackRatio = -1;
_fleeRatioConst = -1;
_coverRatioConst = -1;
_attackRatioConst = -1;
_actorHp = 0;
_range = 300;
_unstoppable = false;
_actorPosition = Vector3(0.0f, 0.0f, 0.0f);
_enemyPosition = Vector3(0.0f, 0.0f, 0.0f);
_coversWaypointCount = 0;
_fleeWaypointsCount = 0;
_fleeingTowards = -1;
}
void ActorCombat::cover() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isWalking()) {
return;
}
if (actor->isObstacleBetween(_enemyPosition)) {
faceEnemy();
return;
}
int coverWaypointId = _vm->_combat->findCoverWaypoint(_waypointType, _actorId, _enemyId);
if (coverWaypointId == -1) {
_state = kActorCombatStateIdle;
} else {
actor->asyncWalkToXYZ(_vm->_combat->_coverWaypoints[coverWaypointId].position, 0, true, 0);
}
}
void ActorCombat::approachToCloseAttack() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
float dist = actor->distanceFromActor(_enemyId);
if (dist > 36.0f) {
if (!actor->isWalking() || enemy->isWalking()) {
Vector3 target;
if (findClosestPositionToEnemy(target)) {
actor->asyncWalkToXYZ(target, 0, dist >= 240.0f, 0);
} else {
_state = kActorCombatStateCover;
}
}
} else {
if (actor->isWalking()) {
actor->stopWalking(false);
}
faceEnemy();
_state = kActorCombatStateCloseAttack;
}
}
void ActorCombat::approachToRangedAttack() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
float dist = actor->distanceFromActor(_enemyId);
if (dist > _range) {
if (!actor->isWalking() || enemy->isWalking()) {
Vector3 target;
if (findClosestPositionToEnemy(target)) {
actor->asyncWalkToXYZ(target, 0, dist >= 240.0f, 0);
} else {
_state = kActorCombatStateCover;
}
}
} else {
if (actor->isWalking()) {
actor->stopWalking(false);
}
faceEnemy();
_state = kActorCombatStateRangedAttack;
}
}
void ActorCombat::uncover() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (actor->isObstacleBetween(_enemyPosition)) {
actor->asyncWalkToXYZ(enemy->getXYZ(), 16, false, 0);
} else {
if (actor->isWalking()) {
actor->stopWalking(false);
}
faceEnemy();
}
}
void ActorCombat::aim() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isObstacleBetween(_enemyPosition)) {
if (actor->getAnimationMode() != kAnimationModeCombatIdle) {
actor->changeAnimationMode(kAnimationModeCombatIdle, false);
}
} else {
faceEnemy();
if (actor->getAnimationMode() != kAnimationModeCombatAim) {
actor->changeAnimationMode(kAnimationModeCombatAim, false);
}
}
}
void ActorCombat::rangedAttack() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isObstacleBetween(_enemyPosition) || (actor->distanceFromActor(_enemyId) > _range)) {
_state = kActorCombatStateApproachRangedAttack;
} else {
faceEnemy();
if (actor->getAnimationMode() != kAnimationModeCombatAttack) {
if (_enemyId != kActorMcCoy || _vm->playerHasControl() || _unstoppable) {
actor->changeAnimationMode(kAnimationModeCombatAttack, false);
}
}
}
}
void ActorCombat::closeAttack() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isObstacleBetween(_enemyPosition) || (actor->distanceFromActor(_enemyId) > 36.0f)) {
_state = kActorCombatStateApproachCloseAttack;
} else {
faceEnemy();
if (actor->getAnimationMode() != kAnimationModeCombatAttack) {
if (_enemyId != kActorMcCoy || _vm->playerHasControl() || _unstoppable) {
actor->changeAnimationMode(kAnimationModeCombatAttack, false);
}
}
}
}
void ActorCombat::flee() {
Actor *actor = _vm->_actors[_actorId];
if (_fleeingTowards != -1 && actor->isWalking()) {
Vector3 fleeWaypointPosition = _vm->_combat->_fleeWaypoints[_fleeingTowards].position;
if (distance(_actorPosition, fleeWaypointPosition) <= 12.0f) {
_vm->_aiScripts->fledCombat(_actorId/*, _enemyId*/);
actor->setSetId(kSetFreeSlotG);
actor->combatModeOff();
_fleeingTowards = -1;
}
} else {
int fleeWaypointId = _vm->_combat->findFleeWaypoint(actor->getSetId(), _enemyId, _actorPosition);
if (fleeWaypointId == -1) {
_state = kActorCombatStateIdle;
} else {
Vector3 fleeWaypointPosition = _vm->_combat->_fleeWaypoints[fleeWaypointId].position;
actor->asyncWalkToXYZ(fleeWaypointPosition, 0, true, 0);
_fleeingTowards = fleeWaypointId;
}
}
}
void ActorCombat::faceEnemy() {
_vm->_actors[_actorId]->setFacing(angle_1024(_actorPosition.x, _actorPosition.z, _enemyPosition.x, _enemyPosition.z), false);
}
int ActorCombat::getCoefficientCloseAttack() const{
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
float distance = actor->distanceFromActor(_enemyId);
if (distance > 36.0f) {
return 0;
}
int aggressiveness = 0;
if (enemy->isRunning()) {
aggressiveness = 11;
} else if (enemy->isMoving()) {
aggressiveness = 22;
} else {
aggressiveness = 33;
}
aggressiveness += actor->getCombatAggressiveness() / 3;
int angle = abs(actor->angleTo(_enemyPosition));
if (angle > 128) {
return false;
}
return aggressiveness + (abs(angle - 128) / 3.7f);
}
int ActorCombat::getCoefficientRangedAttack() const {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (actor->isObstacleBetween(_enemyPosition)) {
return 0;
}
int distance = MIN(actor->distanceFromActor(_enemyId), 900.0f);
int aggressiveness = 0;
if (enemy->isRunning()) {
aggressiveness = 10;
} else if (enemy->isMoving()) {
aggressiveness = 20;
} else {
aggressiveness = 30;
}
aggressiveness += actor->getCombatAggressiveness() / 5;
return aggressiveness + abs((distance / 30) - 30) + actor->getIntelligence() / 5;
}
int ActorCombat::getDamageCloseAttack(int min, int max) const {
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyEasy) {
return _damage / 2;
}
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyHard) {
return _damage;
}
return ((MIN(max - min, 30) * 100.0f / 60.0f) + 50) * _damage / 100;
}
int ActorCombat::getDamageRangedAttack(int min, int max) const {
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyEasy) {
return _damage / 2;
}
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyHard) {
return _damage;
}
return ((MIN(max - min, 30) * 100.0f / 60.0f) + 50) * _damage / 100;
}
int ActorCombat::calculateAttackRatio() const {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
int aggressivenessFactor = actor->getCombatAggressiveness();
int actorHpFactor = actor->getCurrentHP();
int enemyHpFactor = 100 - enemy->getCurrentHP();
int combatFactor = enemy->inCombat() ? 0 : 100;
int angleFactor = (100 * abs(enemy->angleTo(_actorPosition))) / 512;
int distanceFactor = 2 * (50 - MIN(actor->distanceFromActor(_enemyId) / 12.0f, 50.0f));
if (_rangedAttack) {
return
angleFactor * 0.25f +
combatFactor * 0.05f +
enemyHpFactor * 0.20f +
actorHpFactor * 0.10f +
aggressivenessFactor * 0.40f;
} else {
return
distanceFactor * 0.20f +
angleFactor * 0.10f +
combatFactor * 0.10f +
enemyHpFactor * 0.15f +
actorHpFactor * 0.15f +
aggressivenessFactor * 0.30f;
}
}
int ActorCombat::calculateCoverRatio() const {
if (_coversWaypointCount == 0) {
return 0;
}
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
int angleFactor = 100 - (100 * abs(enemy->angleTo(_actorPosition))) / 512;
int actorHpFactor = 100 - actor->getCurrentHP();
int enemyHpFactor = enemy->getCurrentHP();
int aggressivenessFactor = 100 - actor->getCombatAggressiveness();
int distanceFactor = 2 * MIN(actor->distanceFromActor(_enemyId) / 12.0f, 50.0f);
if (_rangedAttack) {
return
angleFactor * 0.40f +
enemyHpFactor * 0.05f +
actorHpFactor * 0.15f +
aggressivenessFactor * 0.50f;
} else {
return
distanceFactor * 0.25f +
angleFactor * 0.20f +
enemyHpFactor * 0.05f +
actorHpFactor * 0.10f +
aggressivenessFactor * 0.50f;
}
}
int ActorCombat::calculateFleeRatio() const {
if (_fleeWaypointsCount == 0) {
return 0;
}
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
int aggressivenessFactor = 100 - actor->getCombatAggressiveness();
int actorHpFactor = 100 - actor->getCurrentHP();
int combatFactor = enemy->inCombat() ? 100 : 0;
return
combatFactor * 0.2f +
actorHpFactor * 0.4f +
aggressivenessFactor * 0.4f;
}
bool ActorCombat::findClosestPositionToEnemy(Vector3 &output) const {
output = Vector3();
Vector3 offsets[] = {
Vector3( 0.0f, 0.0f, -28.0f),
Vector3( 28.0f, 0.0f, 0.0f),
Vector3( 0.0f, 0.0f, 28.0f),
Vector3(-28.0f, 0.0f, 0.0f)
};
float min = -1.0f;
for (int i = 0; i < 4; ++i) {
Vector3 test = _enemyPosition + offsets[i];
float dist = distance(_actorPosition, test);
if ( min == -1.0f || dist < min) {
if (!_vm->_sceneObjects->existsOnXZ(_actorId + kSceneObjectOffsetActors, test.x, test.z, true, true) && _vm->_scene->_set->findWalkbox(test.x, test.z) >= 0) {
output = test;
min = dist;
}
}
}
return min >= 0.0f;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,103 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ACTOR_COMBAT_H
#define BLADERUNNER_ACTOR_COMBAT_H
#include "bladerunner/vector.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class ActorCombat {
BladeRunnerEngine *_vm;
int _actorId;
bool _active;
int _state;
bool _rangedAttack;
int _enemyId;
int _waypointType;
int _damage;
int _fleeRatio;
int _coverRatio;
int _attackRatio;
int _fleeRatioConst;
int _coverRatioConst;
int _attackRatioConst;
int _actorHp;
int _range;
bool _unstoppable;
Vector3 _actorPosition;
Vector3 _enemyPosition;
int _coversWaypointCount;
int _fleeWaypointsCount;
int _fleeingTowards;
public:
ActorCombat(BladeRunnerEngine *vm);
~ActorCombat();
void setup();
void combatOn(int actorId, int initialState, bool rangedAttack, int enemyId, int waypointType, int fleeRatio, int coverRatio, int attackRatio, int damage, int range, bool unstoppable);
void combatOff();
void tick();
void hitAttempt();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
void reset();
void cover();
void approachToCloseAttack();
void approachToRangedAttack();
void uncover();
void aim();
void rangedAttack();
void closeAttack();
void flee();
void faceEnemy();
int getCoefficientCloseAttack() const;
int getCoefficientRangedAttack() const;
int getDamageCloseAttack(int min, int max) const;
int getDamageRangedAttack(int min, int max) const;
int calculateAttackRatio() const;
int calculateCoverRatio() const;
int calculateFleeRatio() const;
bool findClosestPositionToEnemy(Vector3 &output) const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,241 @@
/* 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/actor_dialogue_queue.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/actor.h"
#include "bladerunner/audio_speech.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene.h"
#include "bladerunner/time.h"
#include "bladerunner/script/scene_script.h"
namespace BladeRunner {
ActorDialogueQueue::Entry::Entry() {
isNotPause = false;
isPause = false;
actorId = -1;
delayMillis = -1;
sentenceId = -1;
animationMode = -1;
}
ActorDialogueQueue::ActorDialogueQueue(BladeRunnerEngine *vm) {
_vm = vm;
clear();
}
ActorDialogueQueue::~ActorDialogueQueue() {
}
void ActorDialogueQueue::add(int actorId, int sentenceId, int animationMode) {
if (actorId == kActorMcCoy || actorId == kActorVoiceOver) {
animationMode = -1;
}
if (_entries.size() < kMaxEntries) {
Entry entry;
entry.isNotPause = true;
entry.isPause = false;
entry.actorId = actorId;
entry.sentenceId = sentenceId;
entry.animationMode = animationMode;
entry.delayMillis = -1;
_entries.push_back(entry);
}
}
void ActorDialogueQueue::addPause(int32 delayMillis) {
if (_entries.size() < kMaxEntries) {
Entry entry;
entry.isNotPause = false;
entry.isPause = true;
entry.actorId = -1;
entry.sentenceId = -1;
entry.animationMode = -1;
entry.delayMillis = delayMillis;
_entries.push_back(entry);
}
}
void ActorDialogueQueue::flush(int a1, bool callScript) {
if (_isNotPause && _vm->_audioSpeech->isPlaying()) {
_vm->_audioSpeech->stopSpeech();
if (_animationModePrevious >= 0) {
_vm->_actors[_actorId]->changeAnimationMode(_animationModePrevious, false);
_animationModePrevious = -1;
}
_isNotPause = false;
_actorId = -1;
_sentenceId = -1;
_animationMode = -1;
}
if (_isPause) {
_isPause = false;
_delayMillis = 0;
_timeLast = 0u;
}
clear();
if (callScript) {
_vm->_sceneScript->dialogueQueueFlushed(a1);
}
}
/**
* return true when queue is empty and object is flushed
*/
bool ActorDialogueQueue::isEmpty() {
return _entries.empty() \
&& !_isNotPause \
&& !_isPause \
&& _actorId == -1 \
&& _sentenceId == -1 \
&& _animationMode == -1 \
&& _animationModePrevious == -1 \
&& _delayMillis == 0 \
&& _timeLast == 0u;
}
void ActorDialogueQueue::tick() {
if (!_vm->_audioSpeech->isPlaying()) {
if (_isPause) {
uint32 time = _vm->_time->current();
uint32 timeDiff = time - _timeLast; // unsigned difference is intentional
_timeLast = time;
_delayMillis = (_delayMillis < 0 || ((uint32)_delayMillis < timeDiff) ) ? 0 : ((uint32)_delayMillis - timeDiff);
if (_delayMillis > 0) {
return;
}
_isPause = false;
_delayMillis = 0;
_timeLast = 0u;
if (_entries.empty()) {
flush(0, true);
}
}
if (_isNotPause) {
if (_animationModePrevious >= 0) {
_vm->_actors[_actorId]->changeAnimationMode(_animationModePrevious, false);
_animationModePrevious = -1;
}
_isNotPause = false;
_actorId = -1;
_sentenceId = -1;
_animationMode = -1;
if (_entries.empty()) {
flush(0, true);
}
}
if (!_entries.empty()) {
Entry firstEntry = _entries.remove_at(0);
if (firstEntry.isNotPause) {
_animationMode = firstEntry.animationMode;
if (_vm->_actors[firstEntry.actorId]->getSetId() != _vm->_scene->getSetId()) {
_animationMode = -1;
}
_vm->_actors[firstEntry.actorId]->speechPlay(firstEntry.sentenceId, false);
_isNotPause = true;
_actorId = firstEntry.actorId;
_sentenceId = firstEntry.sentenceId;
if (_animationMode >= 0) {
_animationModePrevious = _vm->_actors[firstEntry.actorId]->getAnimationMode();
_vm->_actors[firstEntry.actorId]->changeAnimationMode(_animationMode, false);
} else {
_animationModePrevious = -1;
}
} else if (firstEntry.isPause) {
_isPause = true;
_delayMillis = firstEntry.delayMillis;
_timeLast = _vm->_time->current();
}
}
}
}
void ActorDialogueQueue::save(SaveFileWriteStream &f) {
int count = (int)_entries.size();
f.writeInt(count);
for (int i = 0; i < count; ++i) {
Entry &e = _entries[i];
f.writeBool(e.isNotPause);
f.writeBool(e.isPause);
f.writeInt(e.actorId);
f.writeInt(e.sentenceId);
f.writeInt(e.animationMode);
f.writeInt(e.delayMillis);
}
f.padBytes((kMaxEntries - count) * 24);
f.writeBool(_isNotPause);
f.writeInt(_actorId);
f.writeInt(_sentenceId);
f.writeInt(_animationMode);
f.writeInt(_animationModePrevious);
f.writeBool(_isPause);
f.writeInt(_delayMillis);
// f.write(_timeLast);
}
void ActorDialogueQueue::load(SaveFileReadStream &f) {
_entries.clear();
uint count = f.readInt();
assert(count <= kMaxEntries);
_entries.resize(count);
for (uint i = 0; i < count; ++i) {
Entry &e = _entries[i];
e.isNotPause = f.readBool();
e.isPause = f.readBool();
e.actorId = f.readInt();
e.sentenceId = f.readInt();
e.animationMode = f.readInt();
e.delayMillis = f.readInt();
}
f.skip((kMaxEntries - count) * 24);
_isNotPause = f.readBool();
_actorId = f.readInt();
_sentenceId = f.readInt();
_animationMode = f.readInt();
_animationModePrevious = f.readInt();
_isPause = f.readBool();
_delayMillis = f.readInt();
_timeLast = 0u;
}
void ActorDialogueQueue::clear() {
_entries.clear();
_isNotPause = false;
_actorId = -1;
_sentenceId = -1;
_animationMode = -1;
_animationModePrevious = -1;
_isPause = false;
_delayMillis = 0;
_timeLast = 0u;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,78 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ACTOR_DIALOGUE_QUEUE_H
#define BLADERUNNER_ACTOR_DIALOGUE_QUEUE_H
#include "common/array.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class ActorDialogueQueue {
static const uint kMaxEntries = 25;
struct Entry {
bool isNotPause;
bool isPause;
int actorId;
int sentenceId;
int animationMode;
int32 delayMillis; // in milliseconds, TODO: Info on special values 0 and -1?
Entry();
};
BladeRunnerEngine *_vm;
Common::Array<Entry> _entries;
bool _isNotPause;
int _actorId;
int _sentenceId;
int _animationMode;
int _animationModePrevious;
bool _isPause;
int32 _delayMillis; // in milliseconds, TODO: Info on special values 0 and -1?
uint32 _timeLast; // in milliseconds
public:
ActorDialogueQueue(BladeRunnerEngine *vm);
~ActorDialogueQueue();
void add(int actorId, int sentenceId, int animationMode);
void addPause(int32 delayMillis);
void flush(int a1, bool callScript);
bool isEmpty();
void tick();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
void clear();
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,465 @@
/* 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/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<int, bool>::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<int, bool>::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

View File

@@ -0,0 +1,82 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ACTOR_WALK_H
#define BLADERUNNER_ACTOR_WALK_H
#include "bladerunner/vector.h"
#include "common/hashmap.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class ActorWalk {
BladeRunnerEngine *_vm;
int _walking;
int _running;
Vector3 _destination;
Vector3 _originalDestination;
Vector3 _current;
Vector3 _next;
int _facing;
Common::HashMap<int, bool> _nearActors;
int _status;
public:
ActorWalk(BladeRunnerEngine *vm);
~ActorWalk();
void reset(); // added method for bug fix (bad new game state for player actor) and better management of object
bool setup(int actorId, bool runFlag, const Vector3 &from, const Vector3 &to, bool mustReach, bool *arrived);
void getCurrentPosition(int actorId, Vector3 *pos, int *facing) const;
bool tick(int actorId, float stepDistance, bool flag);
bool isWalking() const { return _walking; }
bool isRunning() const { return _running; }
bool isXYZOccupied(float x, float y, float z, int actorId) const;
bool findEmptyPositionAround(int actorId, const Vector3 &from, int distance, Vector3 &out) const;
void stop(int actorId, bool immediately, int combatAnimationMode, int animationMode);
void run(int actorId);
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
int nextOnPath(int actorId, const Vector3 &from, const Vector3 &to, Vector3 &next) const;
bool findEmptyPositionAroundToOriginalDestination(int actorId, Vector3 &out) const;
bool addNearActors(int skipActorId);
void obstaclesAddNearActors(int actorId) const;
void obstaclesRestore() const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,173 @@
/* 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/adpcm_decoder.h"
#include "common/endian.h"
#include "common/util.h"
namespace BladeRunner {
static const
int16 imaIndexTable[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
static const
uint16 imaStepTable[712] = {
0x0000,0x0001,0x0003,0x0004,0x0007,0x0008,0x000a,0x000b,
0x0001,0x0003,0x0005,0x0007,0x0009,0x000b,0x000d,0x000f,
0x0001,0x0003,0x0005,0x0007,0x000a,0x000c,0x000e,0x0010,
0x0001,0x0003,0x0006,0x0008,0x000b,0x000d,0x0010,0x0012,
0x0001,0x0003,0x0006,0x0008,0x000c,0x000e,0x0011,0x0013,
0x0001,0x0004,0x0007,0x000a,0x000d,0x0010,0x0013,0x0016,
0x0001,0x0004,0x0007,0x000a,0x000e,0x0011,0x0014,0x0017,
0x0001,0x0004,0x0008,0x000b,0x000f,0x0012,0x0016,0x0019,
0x0002,0x0006,0x000a,0x000e,0x0012,0x0016,0x001a,0x001e,
0x0002,0x0006,0x000a,0x000e,0x0013,0x0017,0x001b,0x001f,
0x0002,0x0006,0x000b,0x000f,0x0015,0x0019,0x001e,0x0022,
0x0002,0x0007,0x000c,0x0011,0x0017,0x001c,0x0021,0x0026,
0x0002,0x0007,0x000d,0x0012,0x0019,0x001e,0x0024,0x0029,
0x0003,0x0009,0x000f,0x0015,0x001c,0x0022,0x0028,0x002e,
0x0003,0x000a,0x0011,0x0018,0x001f,0x0026,0x002d,0x0034,
0x0003,0x000a,0x0012,0x0019,0x0022,0x0029,0x0031,0x0038,
0x0004,0x000c,0x0015,0x001d,0x0026,0x002e,0x0037,0x003f,
0x0004,0x000d,0x0016,0x001f,0x0029,0x0032,0x003b,0x0044,
0x0005,0x000f,0x0019,0x0023,0x002e,0x0038,0x0042,0x004c,
0x0005,0x0010,0x001b,0x0026,0x0032,0x003d,0x0048,0x0053,
0x0006,0x0012,0x001f,0x002b,0x0038,0x0044,0x0051,0x005d,
0x0006,0x0013,0x0021,0x002e,0x003d,0x004a,0x0058,0x0065,
0x0007,0x0016,0x0025,0x0034,0x0043,0x0052,0x0061,0x0070,
0x0008,0x0018,0x0029,0x0039,0x004a,0x005a,0x006b,0x007b,
0x0009,0x001b,0x002d,0x003f,0x0052,0x0064,0x0076,0x0088,
0x000a,0x001e,0x0032,0x0046,0x005a,0x006e,0x0082,0x0096,
0x000b,0x0021,0x0037,0x004d,0x0063,0x0079,0x008f,0x00a5,
0x000c,0x0024,0x003c,0x0054,0x006d,0x0085,0x009d,0x00b5,
0x000d,0x0027,0x0042,0x005c,0x0078,0x0092,0x00ad,0x00c7,
0x000e,0x002b,0x0049,0x0066,0x0084,0x00a1,0x00bf,0x00dc,
0x0010,0x0030,0x0051,0x0071,0x0092,0x00b2,0x00d3,0x00f3,
0x0011,0x0034,0x0058,0x007b,0x00a0,0x00c3,0x00e7,0x010a,
0x0013,0x003a,0x0061,0x0088,0x00b0,0x00d7,0x00fe,0x0125,
0x0015,0x0040,0x006b,0x0096,0x00c2,0x00ed,0x0118,0x0143,
0x0017,0x0046,0x0076,0x00a5,0x00d5,0x0104,0x0134,0x0163,
0x001a,0x004e,0x0082,0x00b6,0x00eb,0x011f,0x0153,0x0187,
0x001c,0x0055,0x008f,0x00c8,0x0102,0x013b,0x0175,0x01ae,
0x001f,0x005e,0x009d,0x00dc,0x011c,0x015b,0x019a,0x01d9,
0x0022,0x0067,0x00ad,0x00f2,0x0139,0x017e,0x01c4,0x0209,
0x0026,0x0072,0x00bf,0x010b,0x0159,0x01a5,0x01f2,0x023e,
0x002a,0x007e,0x00d2,0x0126,0x017b,0x01cf,0x0223,0x0277,
0x002e,0x008a,0x00e7,0x0143,0x01a1,0x01fd,0x025a,0x02b6,
0x0033,0x0099,0x00ff,0x0165,0x01cb,0x0231,0x0297,0x02fd,
0x0038,0x00a8,0x0118,0x0188,0x01f9,0x0269,0x02d9,0x0349,
0x003d,0x00b8,0x0134,0x01af,0x022b,0x02a6,0x0322,0x039d,
0x0044,0x00cc,0x0154,0x01dc,0x0264,0x02ec,0x0374,0x03fc,
0x004a,0x00df,0x0175,0x020a,0x02a0,0x0335,0x03cb,0x0460,
0x0052,0x00f6,0x019b,0x023f,0x02e4,0x0388,0x042d,0x04d1,
0x005a,0x010f,0x01c4,0x0279,0x032e,0x03e3,0x0498,0x054d,
0x0063,0x012a,0x01f1,0x02b8,0x037f,0x0446,0x050d,0x05d4,
0x006d,0x0148,0x0223,0x02fe,0x03d9,0x04b4,0x058f,0x066a,
0x0078,0x0168,0x0259,0x0349,0x043b,0x052b,0x061c,0x070c,
0x0084,0x018d,0x0296,0x039f,0x04a8,0x05b1,0x06ba,0x07c3,
0x0091,0x01b4,0x02d8,0x03fb,0x051f,0x0642,0x0766,0x0889,
0x00a0,0x01e0,0x0321,0x0461,0x05a2,0x06e2,0x0823,0x0963,
0x00b0,0x0210,0x0371,0x04d1,0x0633,0x0793,0x08f4,0x0a54,
0x00c2,0x0246,0x03ca,0x054e,0x06d2,0x0856,0x09da,0x0b5e,
0x00d5,0x027f,0x042a,0x05d4,0x0780,0x092a,0x0ad5,0x0c7f,
0x00ea,0x02bf,0x0495,0x066a,0x0840,0x0a15,0x0beb,0x0dc0,
0x0102,0x0306,0x050b,0x070f,0x0914,0x0b18,0x0d1d,0x0f21,
0x011c,0x0354,0x058c,0x07c4,0x09fc,0x0c34,0x0e6c,0x10a4,
0x0138,0x03a8,0x0619,0x0889,0x0afb,0x0d6b,0x0fdc,0x124c,
0x0157,0x0406,0x06b5,0x0964,0x0c14,0x0ec3,0x1172,0x1421,
0x017a,0x046e,0x0762,0x0a56,0x0d4a,0x103e,0x1332,0x1626,
0x019f,0x04de,0x081e,0x0b5d,0x0e9e,0x11dd,0x151d,0x185c,
0x01c9,0x055c,0x08ef,0x0c82,0x1015,0x13a8,0x173b,0x1ace,
0x01f7,0x05e5,0x09d4,0x0dc2,0x11b1,0x159f,0x198e,0x1d7c,
0x0229,0x067c,0x0acf,0x0f22,0x1375,0x17c8,0x1c1b,0x206e,
0x0260,0x0721,0x0be3,0x10a4,0x1567,0x1a28,0x1eea,0x23ab,
0x029d,0x07d8,0x0d14,0x124f,0x178b,0x1cc6,0x2202,0x273d,
0x02e0,0x08a1,0x0e63,0x1424,0x19e6,0x1fa7,0x2569,0x2b2a,
0x032a,0x097f,0x0fd4,0x1629,0x1c7e,0x22d3,0x2928,0x2f7d,
0x037b,0x0a72,0x1169,0x1860,0x1f57,0x264e,0x2d45,0x343c,
0x03d4,0x0b7d,0x1326,0x1acf,0x2279,0x2a22,0x31cb,0x3974,
0x0436,0x0ca3,0x1511,0x1d7e,0x25ec,0x2e59,0x36c7,0x3f34,
0x04a2,0x0de7,0x172c,0x2071,0x29b7,0x32fc,0x3c41,0x4586,
0x0519,0x0f4b,0x197e,0x23b0,0x2de3,0x3815,0x4248,0x4c7a,
0x059b,0x10d2,0x1c0a,0x2741,0x327a,0x3db1,0x48e9,0x5420,
0x062b,0x1281,0x1ed8,0x2b2e,0x3786,0x43dc,0x5033,0x5c89,
0x06c9,0x145b,0x21ee,0x2f80,0x3d14,0x4aa6,0x5839,0x65cb,
0x0777,0x1665,0x2553,0x3441,0x4330,0x521e,0x610c,0x6ffa,
0x0836,0x18a2,0x290f,0x397b,0x49e8,0x5a54,0x6ac1,0x7b2d,
0x0908,0x1b19,0x2d2a,0x3f3b,0x514c,0x635d,0x756e,0x877f,
0x09ef,0x1dce,0x31ae,0x458d,0x596d,0x6d4c,0x812c,0x950b,
0x0aee,0x20ca,0x36a6,0x4c82,0x625f,0x783b,0x8e17,0xa3f3,
0x0c05,0x2410,0x3c1c,0x5427,0x6c34,0x843f,0x9c4b,0xb456,
0x0d39,0x27ac,0x4220,0x5c93,0x7707,0x917a,0xabee,0xc661,
0x0e8c,0x2ba4,0x48bd,0x65d5,0x82ee,0xa006,0xbd1f,0xda37,
0x0fff,0x2ffe,0x4ffe,0x6ffd,0x8ffe,0xaffd,0xcffd,0xeffc
};
void ADPCMWestwoodDecoder::decode(uint8 *in, size_t size, int16 *out, bool forceLittleEndianOut) {
uint8 *end = in + size;
int16 stepIndex = _stepIndex;
int32 predictor = _predictor;
while (in != end) {
uint16 bl = *in++;
for (int n = 0; n != 2; ++n) {
uint8 nibble = (bl >> (4 * n)) & 0x0f;
uint8 code = nibble & 0x07;
uint8 sign = nibble & 0x08;
int diff = imaStepTable[(stepIndex << 3) | code];
// Westwood's IMA ADPCM differs from the below standard implementation
// in the LSB in a couple of places.
//int diff = imaStepTable_std[stepIndex] * code / 4 + imaStepTable_std[stepIndex] / 8;
if (sign)
predictor -= diff;
else
predictor += diff;
predictor = CLIP<int32>(predictor, -32768, 32767);
if (out) {
if (forceLittleEndianOut) {
// Bugfix:
// enforce "little-endian" type of output for VQA audio stream
// This is needed for Big Endian platforms to behave correctly in raw audio streams in VQA videos
// because in VQADecoder::VQAAudioTrack::decodeAudioFrame() a raw stream is created for the audio
// with the explicit flag: FLAG_LITTLE_ENDIAN
WRITE_LE_INT16(out++, (int16)predictor);
} else {
*out++ = (int16)predictor;
}
}
stepIndex = imaIndexTable[code] + stepIndex;
stepIndex = CLIP<int16>(stepIndex, 0, 88);
}
}
_stepIndex = stepIndex;
_predictor = predictor;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,48 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ADPCM_DECODER_H
#define BLADERUNNER_ADPCM_DECODER_H
#include "common/types.h"
namespace BladeRunner {
class ADPCMWestwoodDecoder {
int16 _stepIndex;
int32 _predictor;
public:
ADPCMWestwoodDecoder()
: _stepIndex(0), _predictor(0) {
}
void setParameters(int16 stepIndex, int32 predictor) {
_stepIndex = stepIndex;
_predictor = predictor;
}
void decode(uint8 *in, size_t size, int16 *out, bool forceLittleEndianOut);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,671 @@
/* 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/ambient_sounds.h"
#include "bladerunner/audio_player.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/game_info.h"
#include "bladerunner/savefile.h"
#include "bladerunner/time.h"
#include "bladerunner/game_constants.h"
#include "common/debug.h"
#include "common/system.h"
namespace BladeRunner {
AmbientSounds::AmbientSounds(BladeRunnerEngine *vm) {
_vm = vm;
_nonLoopingSounds = new NonLoopingSound[kNonLoopingSounds];
_loopingSounds = new LoopingSound[kLoopingSounds];
// The actual volume of ambient sounds is determined by the mixer (see BladeRunnerEngine::syncSoundSettings())
// In our BladeRunner engine ambient sounds do not have a distinct sound type of their own,
// so they are treated as kAmbientSoundType (default type, see: ambient_sounds.h).
//
// _ambientVolumeFactorOriginalEngine here sets a percentage to be applied on the ambient audio tracks' volume
// before sending them to the audio player.
// This is how the original engine set the volume via the in-game KIA volume slider controls.
// Setting _ambientVolumeFactorOriginalEngine to 100, for the purposes ScummVM engine, renders it indifferent,
// so sound volume can be controlled by ScummVM's Global Main Menu / ConfMan/ syncSoundSettings().
// Note however that _ambientVolumeFactorOriginalEngine is also changed when entering and exiting ESPER and possibly VK modes.
_ambientVolumeFactorOriginalEngine = BLADERUNNER_ORIGINAL_SETTINGS ? 65 : 100;
for (int i = 0; i != kNonLoopingSounds; ++i) {
NonLoopingSound &track = _nonLoopingSounds[i];
track.isActive = false;
#if !BLADERUNNER_ORIGINAL_BUGS
track.name.clear();
track.hash = 0;
track.audioPlayerTrack = -1;
track.delayMin = 0u;
track.delayMax = 0u;
track.nextPlayTimeStart = 0u;
track.nextPlayTimeDiff = 0u;
track.volumeMin = 0;
track.volumeMax = 0;
track.volume = 0;
track.panStartMin = 0;
track.panStartMax = 0;
track.panEndMin = 0;
track.panEndMax = 0;
track.priority = 0;
track.soundType = -1;
#endif // !BLADERUNNER_ORIGINAL_BUGS
}
for (int i = 0; i != kLoopingSounds; ++i) {
LoopingSound &track = _loopingSounds[i];
track.isActive = false;
#if !BLADERUNNER_ORIGINAL_BUGS
track.name.clear();
track.hash = 0;
track.audioPlayerTrack = -1;
track.volume = 0;
track.pan = 0;
track.soundType = -1;
#endif // !BLADERUNNER_ORIGINAL_BUGS
}
}
AmbientSounds::~AmbientSounds() {
delete[] _nonLoopingSounds;
delete[] _loopingSounds;
}
static inline void sort(int *a, int *b) {
if (*a > *b) {
int t = *a;
*a = *b;
*b = t;
}
}
static inline void sort(uint32 *a, uint32 *b) {
if (*a > *b) {
uint32 t = *a;
*a = *b;
*b = t;
}
}
//
// addSound() will add a track to the non-looping tracks array list
// it will use the kAmbientSoundType for Mixer's Sound Type.
// see AmbientSounds::tick()
// Used also by:
// Ambient_Sounds_Add_Sound()
// Spinner::chooseDestination()
// Calls:
// addSoundByName()
//
// volumeMin, volumeMax should be in [0, 100]
// panStartMin, panStartMax should be in [-100, 100]
// panEndMin, panEndMax should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
// priority should be in [0, 100]
void AmbientSounds::addSound(int sfxId,
uint32 delayMinSeconds, uint32 delayMaxSeconds,
int volumeMin, int volumeMax,
int panStartMin, int panStartMax,
int panEndMin, int panEndMax,
int priority, int unk) {
debugC(6, kDebugSound, "AmbientSounds::addSound id:%d dMin:%d,dMax:%d vMin:%d,vMax:%d pSMin:%d,pSMax:%d pEMin:%d,pEMax:%d pr:%d unk:%d",
sfxId,
delayMinSeconds, delayMaxSeconds,
volumeMin, volumeMax,
panStartMin,panStartMax,
panEndMin, panEndMax,
priority, unk);
addSoundByName(_vm->_gameInfo->getSfxTrack(sfxId),
delayMinSeconds, delayMaxSeconds,
volumeMin, volumeMax,
panStartMin, panStartMax,
panEndMin, panEndMax,
priority, unk);
}
void AmbientSounds::removeNonLoopingSound(int sfxId, bool stopPlaying) {
debugC(6, kDebugSound, "AmbientSounds::removeNonLoopingSound id:%d stop:%d", sfxId, stopPlaying? 1: 0);
int32 hash = MIXArchive::getHash(_vm->_gameInfo->getSfxTrack(sfxId));
int index = findNonLoopingTrackByHash(hash);
if (index >= 0) {
removeNonLoopingSoundByIndex(index, stopPlaying);
}
}
void AmbientSounds::removeAllNonLoopingSounds(bool stopPlaying) {
debugC(6, kDebugSound, "AmbientSounds::removeAllNonLoopingSounds stop:%d", stopPlaying? 1: 0);
for (int i = 0; i < kNonLoopingSounds; ++i) {
removeNonLoopingSoundByIndex(i, stopPlaying);
}
}
//
// addSpeech() will add a track to the non-looping tracks array list
// it will use the kAmbientSoundType for Mixer's Sound Type
// see AmbientSounds::tick()
// Mainly used for dispatch radio ambient sounds and blimp announcements.
// Called by:
// Ambient_Sounds_Add_Speech_Sound()
// Calls:
// addSoundByName()
//
// volumeMin, volumeMax should be in [0, 100]
// panStartMin, panStartMax should be in [-100, 100]
// panEndMin, panEndMax should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
// priority should be in [0, 100]
void AmbientSounds::addSpeech(int actorId, int sentenceId,
uint32 delayMinSeconds, uint32 delayMaxSeconds,
int volumeMin, int volumeMax,
int panStartMin, int panStartMax,
int panEndMin, int panEndMax,
int priority, int unk) {
debugC(6, kDebugSound, "AmbientSounds::addSpeech id:%d-%d dMin:%d,dMax:%d vMin:%d,vMax:%d pSMin:%d,pSMax:%d pEMin:%d,pEMax:%d pr:%d unk:%d",
actorId, sentenceId,
delayMinSeconds, delayMaxSeconds,
volumeMin, volumeMax,
panStartMin,panStartMax,
panEndMin, panEndMax,
priority, unk);
Common::String name = Common::String::format( "%02d-%04d%s.AUD", actorId, sentenceId, _vm->_languageCode.c_str());
addSoundByName(name,
delayMinSeconds, delayMaxSeconds,
volumeMin, volumeMax,
panStartMin, panStartMax,
panEndMin, panEndMax,
priority, unk);
}
// Explicitly plays a sound effect (sfx) track (specified by id)
// It does not add it as a track to the non-looping tracks array list
// It uses the parameter "type" as the mixer's sound type - which determines the volume setting in effect.
// By default sound type is kAmbientSoundType (see ambient_sounds.h).
//
// volume should be in [0, 100]
// panStart should be in [-100, 100]
// panEnd should be in [-100, 100]
// priority should be in [0, 100]
void AmbientSounds::playSound(int sfxId, int volume, int panStart, int panEnd, int priority, Audio::Mixer::SoundType type) {
debugC(6, kDebugSound, "AmbientSounds::playSound id:%d v:%d pS:%d pE:%d pr:%d typ:%d", sfxId, volume, panStart, panEnd, priority, (int32) type);
_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(sfxId), (volume * _ambientVolumeFactorOriginalEngine) / 100, panStart, panEnd, priority, kAudioPlayerOverrideVolume, type);
}
// Explicitly plays a speech cue
// It does not add it as a track to the non-looping tracks array list
// It uses mixer's sound type kSpeechSoundType - which determines the volume setting in effect (Speech)
//
// volume should be in [0, 100]
// panStart should be in [-100, 100]
// panEnd should be in [-100, 100]
// priority should be in [0, 100]
void AmbientSounds::playSpeech(int actorId, int sentenceId, int volume, int panStart, int panEnd, int priority) {
debugC(6, kDebugSound, "AmbientSounds::playSpeech id:%d-%d v:%d pS:%d pE:%d pr:%d", actorId, sentenceId, volume, panStart, panEnd, priority);
Common::String name = Common::String::format( "%02d-%04d%s.AUD", actorId, sentenceId, _vm->_languageCode.c_str());
// (volume * _ambientVolume) / 100 should result in a value in [0, 100]
_vm->_audioPlayer->playAud(name, (volume * _ambientVolumeFactorOriginalEngine) / 100, panStart, panEnd, priority, kAudioPlayerOverrideVolume, Audio::Mixer::kSpeechSoundType);
}
// Looping Sound will use paramerter "type" as the mixer's SoundType when playing this track.
// By default sound type is kAmbientSoundType (see ambient_sounds.h).
// This determines the volume setting that will be in effect for the audio.
//
// NOTE If restoring from a saved game, a looping track will always use the default SoundType (kAmbientSoundType)
// because sound type is not stored.
// TODO We could save the sound type re-using the space for field "track.audioPlayerTrack"
// which is skipped for *both* looping and non-looping tracks in save() and load() code
// However, the issue is negligible; the default SoundType for looping tracks is overridden
// only in one special case so far (restored content Outtake "FLYTRU_E.VQA", see: outtake.cpp)
//
// volume should be in [0, 100]
// pan should be in [-100, 100]
void AmbientSounds::addLoopingSound(int sfxId, int volume, int pan, uint32 delaySeconds, Audio::Mixer::SoundType type) {
debugC(6, kDebugSound, "AmbientSounds::addLoopingSound id:%d v:%d p:%d d:%u typ:%d", sfxId, volume, pan, delaySeconds, (int32) type);
const Common::String &name = _vm->_gameInfo->getSfxTrack(sfxId);
int32 hash = MIXArchive::getHash(name);
if (findLoopingTrackByHash(hash) >= 0) {
return;
}
int i = findAvailableLoopingTrack();
if (i == -1) {
return;
}
LoopingSound &track = _loopingSounds[i];
track.isActive = true;
track.name = name;
track.hash = hash;
track.pan = pan;
track.volume = volume;
track.soundType = (int32) type;
int actualVolumeStart = (volume * _ambientVolumeFactorOriginalEngine) / 100;
int actualVolumeEnd = actualVolumeStart;
if (delaySeconds > 0u) {
actualVolumeStart = 0;
}
// actualVolumeStart should be in [0, 100]
// pan should be in [-100, 100]
// priority should be in [0, 100]
track.audioPlayerTrack = _vm->_audioPlayer->playAud(name, actualVolumeStart, pan, pan, 99, kAudioPlayerLoop | kAudioPlayerOverrideVolume, type);
if (track.audioPlayerTrack == -1) {
removeLoopingSoundByIndex(i, 0u);
} else {
if (delaySeconds) {
_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, actualVolumeEnd, delaySeconds, false);
}
}
}
// volume should be in [0, 100], with "-1" being a special value for skipping volume adjustment
// pan should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
void AmbientSounds::adjustLoopingSound(int sfxId, int volume, int pan, uint32 delaySeconds) {
debugC(6, kDebugSound, "AmbientSounds::adjustLoopingSound id:%d v:%d p:%d d:%u", sfxId, volume, pan, delaySeconds);
int32 hash = MIXArchive::getHash(_vm->_gameInfo->getSfxTrack(sfxId));
int index = findLoopingTrackByHash(hash);
if (index >= 0 && _loopingSounds[index].audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(_loopingSounds[index].audioPlayerTrack)) {
if (volume != -1) {
_loopingSounds[index].volume = volume;
_vm->_audioPlayer->adjustVolume(_loopingSounds[index].audioPlayerTrack, (volume * _ambientVolumeFactorOriginalEngine) / 100, delaySeconds, false);
}
if (pan != -101) {
_loopingSounds[index].pan = pan;
_vm->_audioPlayer->adjustPan(_loopingSounds[index].audioPlayerTrack, pan, delaySeconds);
}
}
}
void AmbientSounds::removeLoopingSound(int sfxId, uint32 delaySeconds) {
debugC(6, kDebugSound, "AmbientSounds::removeLoopingSound id:%d d:%u", sfxId, delaySeconds);
int32 hash = MIXArchive::getHash(_vm->_gameInfo->getSfxTrack(sfxId));
int index = findLoopingTrackByHash(hash);
if (index >= 0) {
removeLoopingSoundByIndex(index, delaySeconds);
}
}
void AmbientSounds::removeAllLoopingSounds(uint32 delaySeconds) {
debugC(6, kDebugSound, "AmbientSounds::removeAllLoopingSounds d:%u", delaySeconds);
for (int i = 0; i < kLoopingSounds; ++i) {
removeLoopingSoundByIndex(i, delaySeconds);
}
}
// tick() only handles the non-looping added ambient sounds
void AmbientSounds::tick() {
uint32 now = _vm->_time->current();
for (int i = 0; i != kNonLoopingSounds; ++i) {
NonLoopingSound &track = _nonLoopingSounds[i];
// unsigned difference is intentional
if (!track.isActive || now - track.nextPlayTimeStart < track.nextPlayTimeDiff) {
continue;
}
int panEnd;
int panStart = _vm->_rnd.getRandomNumberRng(track.panStartMin, track.panStartMax);
// typically when using the -101 special value, both panEndMin and panEndMax have this value
// -101 here means "do not adjust the panning for the track (while it's playing)"
if (track.panEndMin != -101) {
panEnd = _vm->_rnd.getRandomNumberRng(track.panEndMin, track.panEndMax);
} else {
panEnd = panStart;
}
track.volume = _vm->_rnd.getRandomNumberRng(track.volumeMin, track.volumeMax);
Audio::Mixer::SoundType mixerAmbientSoundType = kAmbientSoundType;
if (track.soundType >= 0) {
mixerAmbientSoundType = (Audio::Mixer::SoundType) track.soundType;
}
track.audioPlayerTrack = _vm->_audioPlayer->playAud(track.name,
(track.volume * _ambientVolumeFactorOriginalEngine) / 100,
panStart,
panEnd,
track.priority,
kAudioPlayerOverrideVolume,
mixerAmbientSoundType);
track.nextPlayTimeStart = now;
track.nextPlayTimeDiff = _vm->_rnd.getRandomNumberRng(track.delayMin, track.delayMax);
}
}
// TODO Evaluate if for ScummVM we can avoid using and modifying _ambientVolumeFactorOriginalEngine altogether.
// While we no longer use the original engine's mechanism to set the ambient sounds volume
// with the AmbientSounds::setVolume() public method, when using the in-game KIA volume slider,
// we do use this method as did the original engine to temporarily set the volume levels
// in ESPER and VK modes.
// This affects only looping ambient sounds.
// volume should be in [0, 100]
void AmbientSounds::setVolume(int volume) {
debugC(6, kDebugSound, "AmbientSounds::setVolume v:%d", volume);
if (_loopingSounds) {
for (int i = 0; i < kLoopingSounds; ++i) {
if (_loopingSounds[i].isActive && _loopingSounds[i].audioPlayerTrack != -1) {
int newVolume = (_loopingSounds[i].volume * volume) / 100;
if (_vm->_audioPlayer->isActive(_loopingSounds[i].audioPlayerTrack)) {
_vm->_audioPlayer->adjustVolume(_loopingSounds[i].audioPlayerTrack, newVolume, 1u, false);
} else {
Audio::Mixer::SoundType mixerAmbientSoundType = kAmbientSoundType;
if (_loopingSounds[i].soundType >= 0) {
mixerAmbientSoundType = (Audio::Mixer::SoundType) _loopingSounds[i].soundType;
}
_loopingSounds[i].audioPlayerTrack = _vm->_audioPlayer->playAud(_loopingSounds[i].name, 1, _loopingSounds[i].pan, _loopingSounds[i].pan, 99, kAudioPlayerLoop | kAudioPlayerOverrideVolume, mixerAmbientSoundType);
if (_loopingSounds[i].audioPlayerTrack == -1) {
removeLoopingSound(i, 0u);
} else {
_vm->_audioPlayer->adjustVolume(_loopingSounds[i].audioPlayerTrack, newVolume, 1u, false);
}
}
}
}
}
_ambientVolumeFactorOriginalEngine = volume;
}
int AmbientSounds::getVolume() const {
return _ambientVolumeFactorOriginalEngine;
}
void AmbientSounds::playSample() {
// Original uses priority 0 here also
playSound(kSfxSPIN1A, 100, 0, 0, 0);
}
int AmbientSounds::findAvailableNonLoopingTrack() const {
for (int i = 0; i != kNonLoopingSounds; ++i) {
if (!_nonLoopingSounds[i].isActive) {
return i;
}
}
return -1;
}
int AmbientSounds::findNonLoopingTrackByHash(int32 hash) const {
for (int i = 0; i != kNonLoopingSounds; ++i) {
NonLoopingSound &track = _nonLoopingSounds[i];
if (track.isActive && track.hash == hash) {
return i;
}
}
return -1;
}
int AmbientSounds::findAvailableLoopingTrack() const {
for (int i = 0; i != kLoopingSounds; ++i) {
if (!_loopingSounds[i].isActive) {
return i;
}
}
return -1;
}
int AmbientSounds::findLoopingTrackByHash(int32 hash) const {
for (int i = 0; i != kLoopingSounds; ++i) {
LoopingSound &track = _loopingSounds[i];
if (track.isActive && track.hash == hash) {
return i;
}
}
return -1;
}
// volumeMin, volumeMax should be in [0, 100]
// panStartMin, panStartMax should be in [-100, 100]
// panEndMin, panEndMax should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
// priority should be in [0, 100]
void AmbientSounds::addSoundByName(const Common::String &name,
uint32 delayMinSeconds, uint32 delayMaxSeconds,
int volumeMin, int volumeMax,
int panStartMin, int panStartMax,
int panEndMin, int panEndMax,
int priority, int unk) {
int i = findAvailableNonLoopingTrack();
if (i < 0) {
return;
}
NonLoopingSound &track = _nonLoopingSounds[i];
uint32 now = _vm->_time->current();
#if !BLADERUNNER_ORIGINAL_BUGS
sort(&delayMinSeconds, &delayMaxSeconds);
#endif // BLADERUNNER_ORIGINAL_BUGS
sort(&volumeMin, &volumeMax);
sort(&panStartMin, &panStartMax);
sort(&panEndMin, &panEndMax);
track.isActive = true;
track.name = name;
track.hash = MIXArchive::getHash(name);
track.delayMin = 1000u * delayMinSeconds; // store as milliseconds
track.delayMax = 1000u * delayMaxSeconds; // store as milliseconds
track.nextPlayTimeStart = now;
track.nextPlayTimeDiff = _vm->_rnd.getRandomNumberRng(track.delayMin, track.delayMax);
track.volumeMin = volumeMin;
track.volumeMax = volumeMax;
track.volume = 0;
track.panStartMin = panStartMin;
track.panStartMax = panStartMax;
track.panEndMin = panEndMin;
track.panEndMax = panEndMax;
track.priority = priority;
track.soundType = -1;
}
void AmbientSounds::removeNonLoopingSoundByIndex(int index, bool stopPlaying) {
NonLoopingSound &track = _nonLoopingSounds[index];
if (stopPlaying) {
if (track.isActive && track.audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(track.audioPlayerTrack)) {
_vm->_audioPlayer->stop(track.audioPlayerTrack, stopPlaying);
}
}
track.isActive = false;
track.audioPlayerTrack = -1;
// track.field_45 = 0;
track.soundType = -1;
#if !BLADERUNNER_ORIGINAL_BUGS
track.name.clear();
track.hash = 0;
track.delayMin = 0u;
track.delayMax = 0u;
track.nextPlayTimeStart = 0u;
track.nextPlayTimeDiff = 0u;
track.volumeMin = 0;
track.volumeMax = 0;
track.volume = 0;
track.panStartMin = 0;
track.panStartMax = 0;
track.panEndMin = 0;
track.panEndMax = 0;
track.priority = 0;
#endif // !BLADERUNNER_ORIGINAL_BUGS
}
void AmbientSounds::removeLoopingSoundByIndex(int index, uint32 delaySeconds) {
LoopingSound &track = _loopingSounds[index];
if (track.isActive && track.audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(track.audioPlayerTrack)) {
if (delaySeconds > 0u) {
_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, 0, delaySeconds, false);
} else {
_vm->_audioPlayer->stop(track.audioPlayerTrack, false);
}
}
track.isActive = false;
track.name.clear();
track.hash = 0;
track.audioPlayerTrack = -1;
track.volume = 0;
track.pan = 0;
track.soundType = -1;
}
void AmbientSounds::save(SaveFileWriteStream &f) {
f.writeBool(false); // _isDisabled - not used
for (int i = 0; i != kNonLoopingSounds; ++i) {
// 73 bytes per non-looping sound
NonLoopingSound &track = _nonLoopingSounds[i];
f.writeBool(track.isActive);
f.writeStringSz(track.name, 13);
f.writeSint32LE(track.hash);
f.writeInt(-1); // track.audioPlayerTrack is not used after load
f.writeInt(track.delayMin);
f.writeInt(track.delayMax);
f.writeInt(0); // track.nextPlayTime is not used after load
f.writeInt(track.volumeMin);
f.writeInt(track.volumeMax);
f.writeInt(track.volume);
f.writeInt(track.panStartMin);
f.writeInt(track.panStartMax);
f.writeInt(track.panEndMin);
f.writeInt(track.panEndMax);
f.writeInt(track.priority);
f.padBytes(4); // field_45
}
for (int i = 0; i != kLoopingSounds; ++i) {
// 33 bytes per looping sound
LoopingSound &track = _loopingSounds[i];
f.writeBool(track.isActive);
f.writeStringSz(track.name, 13);
f.writeSint32LE(track.hash);
f.writeInt(-1); // track.audioPlayerTrack is not used after load
f.writeInt(track.volume);
f.writeInt(track.pan);
}
}
void AmbientSounds::load(SaveFileReadStream &f) {
removeAllLoopingSounds(0u);
removeAllNonLoopingSounds(true);
f.skip(4); // _isDisabled - not used
uint32 now = _vm->_time->getPauseStart();
for (int i = 0; i != kNonLoopingSounds; ++i) {
NonLoopingSound &track = _nonLoopingSounds[i];
track.isActive = f.readBool();
track.name = f.readStringSz(13);
track.hash = f.readSint32LE();
f.skip(4); // track.audioPlayerTrack is not used after load
track.audioPlayerTrack = -1;
track.delayMin = (uint32)f.readInt();
track.delayMax = (uint32)f.readInt();
f.skip(4); // track.nextPlayTime is not used after load
track.nextPlayTimeStart = now;
#if BLADERUNNER_ORIGINAL_BUGS
track.nextPlayTimeDiff = _vm->_rnd.getRandomNumberRng(track.delayMin, track.delayMax);
#endif // BLADERUNNER_ORIGINAL_BUGS
track.volumeMin = f.readInt();
track.volumeMax = f.readInt();
track.volume = f.readInt();
track.panStartMin = f.readInt();
track.panStartMax = f.readInt();
track.panEndMin = f.readInt();
track.panEndMax = f.readInt();
track.priority = f.readInt();
f.skip(4); // field_45
track.soundType = -1;
#if !BLADERUNNER_ORIGINAL_BUGS
// Since unused ambient sound track fields are unitialized
// don't keep garbage field values for non-active tracks
// This was basically an issue when calling _vm->_rnd.getRandomNumberRng()
// with uninitialized fields, but it's a good practice to sanitize the fields here anyway
if (!track.isActive) {
track.delayMin = 0u;
track.delayMax = 0u;
track.nextPlayTimeDiff = 0u;
track.volumeMin = 0;
track.volumeMax = 0;
track.volume = 0;
track.panStartMin = 0;
track.panStartMax = 0;
track.panEndMin = 0;
track.panEndMax = 0;
track.priority = 0;
} else {
sort(&(track.delayMin), &(track.delayMax));
track.nextPlayTimeDiff = _vm->_rnd.getRandomNumberRng(track.delayMin, track.delayMax);
sort(&(track.volumeMin), &(track.volumeMax));
sort(&(track.panStartMin), &(track.panStartMax));
sort(&(track.panEndMin), &(track.panEndMax));
}
#endif // !BLADERUNNER_ORIGINAL_BUGS
}
for (int i = 0; i != kLoopingSounds; ++i) {
LoopingSound &track = _loopingSounds[i];
track.isActive = f.readBool();
track.name = f.readStringSz(13);
track.hash = f.readSint32LE();
f.skip(4); // track.audioPlayerTrack is not used after load
track.audioPlayerTrack = -1;
track.volume = f.readInt();
track.pan = f.readInt();
track.soundType = -1;
#if !BLADERUNNER_ORIGINAL_BUGS
// Since unused ambient sound track fields are unitialized
// don't keep garbage field values for non-active tracks
if (!track.isActive) {
track.volume = 0;
track.pan = 0;
}
#endif // !BLADERUNNER_ORIGINAL_BUGS
}
for (int i = 0; i != kLoopingSounds; ++i) {
LoopingSound &track = _loopingSounds[i];
if (track.isActive) {
Audio::Mixer::SoundType mixerAmbientSoundType = kAmbientSoundType;
if (track.soundType >= 0) {
mixerAmbientSoundType = (Audio::Mixer::SoundType) track.soundType;
}
// Looping sound (loaded) gets high priority (99)
// Also started with volume at 1 (but adjusted below appropriately)
track.audioPlayerTrack = _vm->_audioPlayer->playAud(track.name, 1, track.pan, track.pan, 99, kAudioPlayerLoop | kAudioPlayerOverrideVolume, mixerAmbientSoundType);
if (track.audioPlayerTrack == -1) {
removeLoopingSoundByIndex(i, 0u);
} else {
_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, (track.volume * _ambientVolumeFactorOriginalEngine) / 100, 2u, false);
}
}
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,137 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_AMBIENT_SOUNDS_H
#define BLADERUNNER_AMBIENT_SOUNDS_H
#include "bladerunner/bladerunner.h" // For BLADERUNNER_ORIGINAL_SETTINGS symbol
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/str.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class AmbientSounds {
static const int kNonLoopingSounds = 25;
static const int kLoopingSounds = 3;
static const Audio::Mixer::SoundType kAmbientSoundType = Audio::Mixer::kPlainSoundType;
struct NonLoopingSound {
bool isActive;
Common::String name;
int32 hash;
int audioPlayerTrack;
uint32 delayMin; // milliseconds
uint32 delayMax; // milliseconds
uint32 nextPlayTimeStart; // milliseconds
uint32 nextPlayTimeDiff; // milliseconds
int volumeMin; // should be in [0, 100]
int volumeMax; // should be in [0, 100]
int volume; // should be in [0, 100] (calculated as a random value within [volumeMin, volumeMax]
int panStartMin; // should be in [-100, 100]
int panStartMax; // should be in [-100, 100]
int panEndMin; // should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
int panEndMax; // should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
int priority; // should be in [0, 100]
int32 soundType; // new - not stored in saved games
};
struct LoopingSound {
bool isActive;
Common::String name;
int32 hash;
int audioPlayerTrack;
int volume; // should be in [0, 100]
int pan; // should be in [-100, 100]
int32 soundType; // new - not stored in saved games
};
BladeRunnerEngine *_vm;
NonLoopingSound *_nonLoopingSounds;
LoopingSound *_loopingSounds;
int _ambientVolumeFactorOriginalEngine; // should be in [0, 100]
public:
AmbientSounds(BladeRunnerEngine *vm);
~AmbientSounds();
void addSound(int sfxId,
uint32 delayMinSeconds, uint32 delayMaxSeconds,
int volumeMin, int volumeMax,
int panStartMin, int panStartMax,
int panEndMin, int panEndMax,
int priority, int unk);
void removeNonLoopingSound(int sfxId, bool stopPlaying);
void removeAllNonLoopingSounds(bool stopPlaying);
void addSpeech(int actorId, int sentenceId,
uint32 delayMinSeconds, uint32 delayMaxSeconds,
int volumeMin, int volumeMax,
int panStartMin, int panStartMax,
int panEndMin, int panEndMax,
int priority, int unk);
void playSound(int sfxId, int volume, int panStart, int panEnd, int priority, Audio::Mixer::SoundType type = kAmbientSoundType);
void playSpeech(int actorId, int sentenceId, int volume, int panStart, int panEnd, int priority);
void addLoopingSound(int sfxId, int volume, int pan, uint32 delaySeconds, Audio::Mixer::SoundType type = kAmbientSoundType);
void adjustLoopingSound(int sfxId, int volume, int pan, uint32 delaySeconds);
// it seems there is little confusion in original code about delay parameter,
// sometimes it is used as boolean in same way as stopPlaying from non looping
void removeLoopingSound(int sfxId, uint32 delaySeconds);
void removeAllLoopingSounds(uint32 delaySeconds);
void tick();
void setVolume(int volume);
int getVolume() const;
void playSample();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
int findAvailableNonLoopingTrack() const;
int findNonLoopingTrackByHash(int32 hash) const;
int findAvailableLoopingTrack() const;
int findLoopingTrackByHash(int32 hash) const;
void addSoundByName(const Common::String &name,
uint32 delayMinSeconds, uint32 delayMaxSeconds,
int volumeMin, int volumeMax,
int panStartMin, int panStartMax,
int panEndMin, int panEndMax,
int priority, int unk);
void removeNonLoopingSoundByIndex(int index, bool stopPlaying);
void removeLoopingSoundByIndex(int index, uint32 delaySeconds);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,164 @@
/* 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/archive.h"
#include "common/debug.h"
namespace BladeRunner {
MIXArchive::MIXArchive() {
_isTLK = false;
_entryCount = 0;
_size = 0;
}
MIXArchive::~MIXArchive() {
if (_fd.isOpen()) {
warning("~MIXArchive: File not closed: %s", _fd.getName());
}
}
bool MIXArchive::exists(const Common::Path &filename) {
return Common::File::exists(filename);
}
bool MIXArchive::open(const Common::Path &filename) {
if (!_fd.open(filename)) {
error("MIXArchive::open(): Can not open %s", filename.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
_isTLK = filename.baseName().hasSuffix(".TLK");
_entryCount = _fd.readUint16LE();
_size = _fd.readUint32LE();
_entries.resize(_entryCount);
for (uint16 i = 0; i != _entryCount; ++i) {
_entries[i].hash = _fd.readUint32LE();
_entries[i].offset = _fd.readUint32LE();
_entries[i].length = _fd.readUint32LE();
// Verify that the entries are sorted by id. Note that id is signed.
if (i > 0) {
assert(_entries[i].hash > _entries[i - 1].hash);
}
}
if (_fd.err()) {
error("MIXArchive::open(): Error reading entries in %s", filename.toString(Common::Path::kNativeSeparator).c_str());
_fd.close();
return false;
}
// debug("MIXArchive::open: Opened archive %s", filename.c_str());
return true;
}
void MIXArchive::close() {
return _fd.close();
}
bool MIXArchive::isOpen() const {
return _fd.isOpen();
}
#define ROL(n) ((n << 1) | ((n >> 31) & 1))
int32 MIXArchive::getHash(const Common::String &name) {
char buffer[12] = { 0 };
for (uint i = 0; i != name.size() && i < 12u; ++i) {
buffer[i] = (char)toupper(name[i]);
}
uint32 id = 0;
for (int i = 0; i < 12 && buffer[i]; i += 4) {
uint32 t = (uint32)buffer[i + 3] << 24
| (uint32)buffer[i + 2] << 16
| (uint32)buffer[i + 1] << 8
| (uint32)buffer[i + 0];
id = ROL(id) + t;
}
return id;
}
static uint32 tlk_id(const Common::String &name) {
char buffer[12] = { 0 };
for (uint i = 0; i != name.size() && i < 12u; ++i)
buffer[i] = (char)toupper(name[i]);
int actor_id = 10 * (buffer[0] - '0') +
(buffer[1] - '0');
int speech_id = 1000 * (buffer[3] - '0') +
100 * (buffer[4] - '0') +
10 * (buffer[5] - '0') +
(buffer[6] - '0');
return 10000 * actor_id + speech_id;
}
uint32 MIXArchive::indexForHash(int32 hash) const {
uint32 lo = 0, hi = _entryCount;
while (lo < hi) {
uint32 mid = lo + (hi - lo) / 2;
if (hash > _entries[mid].hash) {
lo = mid + 1;
} else if (hash < _entries[mid].hash) {
hi = mid;
} else {
return mid;
}
}
return _entryCount;
}
Common::SeekableReadStream *MIXArchive::createReadStreamForMember(const Common::Path &name) {
int32 hash;
if (_isTLK) {
hash = tlk_id(name.baseName());
} else {
hash = MIXArchive::getHash(name.baseName());
}
uint32 i = indexForHash(hash);
if (i == _entryCount) {
return nullptr;
}
uint32 start = _entries[i].offset + 6 + 12 * _entryCount;
uint32 end = _entries[i].length + start;
return new Common::SafeSeekableSubReadStream(&_fd, start, end, DisposeAfterUse::NO);
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ARCHIVE_H
#define BLADERUNNER_ARCHIVE_H
#include "common/array.h"
#include "common/file.h"
#include "common/substream.h"
namespace BladeRunner {
class MIXArchive {
public:
MIXArchive();
~MIXArchive();
static int32 getHash(const Common::String &name);
static bool exists(const Common::Path &filename);
bool open(const Common::Path &filename);
void close();
bool isOpen() const;
Common::String getName() const { return _fd.getName(); }
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &name);
private:
Common::File _fd;
bool _isTLK;
uint16 _entryCount;
uint32 _size;
struct ArchiveEntry {
int32 hash;
uint32 offset;
uint32 length;
};
Common::Array<ArchiveEntry> _entries;
uint32 indexForHash(int32 hash) const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,172 @@
/* 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/aud_stream.h"
#include "bladerunner/audio_cache.h"
#include "common/util.h"
namespace BladeRunner {
AudStream::AudStream(byte *data, int overrideFrequency) {
_hash = 0;
_cache = nullptr;
_overrideFrequency = overrideFrequency;
init(data);
}
AudStream::AudStream(AudioCache *cache, int32 hash, int overrideFrequency) {
assert(cache != nullptr);
_cache = cache;
_hash = hash;
_overrideFrequency = overrideFrequency;
_cache->incRef(_hash);
init(_cache->findByHash(_hash));
}
void AudStream::init(byte *data) {
_data = data;
_frequency = READ_LE_UINT16(_data);
_size = READ_LE_UINT32(_data + 2);
_sizeDecompressed = READ_LE_UINT32(_data + 6);
_flags = *(_data + 10);
_compressionType = *(_data + 11);
_end = _data + _size + 12;
assert(_end - _data >= 12);
_deafBlockRemain = 0;
_p = _data + 12;
}
AudStream::~AudStream() {
if (_cache) {
_cache->decRef(_hash);
}
}
int AudStream::readBuffer(int16 *buffer, const int numSamples) {
int samplesRead = 0;
if (_compressionType == 99) {
assert(numSamples % 2 == 0);
while (samplesRead < numSamples) {
if (_deafBlockRemain == 0) {
if (_end - _p == 0)
break;
assert(_end - _p >= 6);
uint16 blockSize = READ_LE_UINT16(_p);
uint16 blockOutSize = READ_LE_UINT16(_p + 2);
uint32 sig = READ_LE_UINT32(_p + 4);
_p += 8;
assert(sig == 0xdeaf);
assert(_end - _p >= blockSize);
// TODO: Previously we asserted that
// blockOutSize == 4 * blockSize, but
// occasionally, at the end of an AUD,
// we see blockOutSize == 4 * blockSize + 2
// Investigate how BLADE.EXE handles this.
assert(blockOutSize / 4 == blockSize);
_deafBlockRemain = blockSize;
}
assert(_end - _p >= _deafBlockRemain);
int bytesConsumed = MIN<int>(_deafBlockRemain, (numSamples - samplesRead) / 2);
if (buffer) {
_decoder.decode(_p, bytesConsumed, buffer + samplesRead, false);
} else {
_decoder.decode(_p, bytesConsumed, nullptr, false);
}
_p += bytesConsumed;
_deafBlockRemain -= bytesConsumed;
samplesRead += 2 * bytesConsumed;
}
} else {
samplesRead = MIN(numSamples, (int)(_end - _p) / 2);
if (buffer) {
for (int i = 0; i < samplesRead; ++i, _p += 2) {
buffer[i] = READ_LE_UINT16(_p);
}
}
}
return samplesRead;
}
int AudStream::getBytesPerSecond() const {
int bytesPerSecond = _overrideFrequency > 0 ? _overrideFrequency : _frequency;
if (_flags & 1) { // 16 bit
bytesPerSecond *= 2;
}
if (_flags & 2) { // stereo
bytesPerSecond *= 2;
}
return bytesPerSecond;
}
bool AudStream::startAtSecond(uint32 startSecond) {
uint32 audStreamLengthMillis = getLength();
if (startSecond == 0 || startSecond * 1000 > audStreamLengthMillis || audStreamLengthMillis == 0) {
return false;
}
if (rewind()) {
int samplesPerSecond = _overrideFrequency > 0 ? _overrideFrequency : _frequency;
readBuffer(nullptr, startSecond * samplesPerSecond);
return true;
}
return false;
}
bool AudStream::rewind() {
_p = _data + 12;
_decoder.setParameters(0, 0);
return true;
}
/**
* Returns audio length in milliseconds
*/
uint32 AudStream::getLength() const {
// since everything is 44100, we easily get overflows with ints
// thus we must use doubles
int bytesPerSecond = getBytesPerSecond();
if (bytesPerSecond <= 0) {
return 0u;
}
double res = (double)_sizeDecompressed * 1000.0 / (double)bytesPerSecond;
return (uint32)res;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,70 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_AUD_STREAM_H
#define BLADERUNNER_AUD_STREAM_H
#include "bladerunner/adpcm_decoder.h"
#include "audio/audiostream.h"
#include "common/endian.h"
#include "common/types.h"
namespace BladeRunner {
class AudioCache;
class AudStream : public Audio::RewindableAudioStream {
byte *_data;
byte *_p;
byte *_end;
AudioCache *_cache;
int32 _hash;
uint16 _deafBlockRemain;
uint16 _frequency;
uint32 _size;
uint32 _sizeDecompressed;
byte _flags;
byte _compressionType;
int _overrideFrequency;
ADPCMWestwoodDecoder _decoder;
void init(byte *data);
public:
AudStream(byte *data, int overrideFrequency = -1);
AudStream(AudioCache *cache, int32 hash, int overrideFrequency = -1);
~AudStream() override;
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
int getRate() const override { return _overrideFrequency > 0 ? _overrideFrequency : _frequency; };
bool endOfData() const override { return _p == _end; }
bool rewind() override;
uint32 getLength() const;
bool startAtSecond(uint32 startSecond);
int getBytesPerSecond() const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,128 @@
/* 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/audio_cache.h"
#include "common/stream.h"
namespace BladeRunner {
AudioCache::AudioCache() :
_totalSize(0),
_maxSize(2457600),
_accessCounter(0) {}
AudioCache::~AudioCache() {
for (uint i = 0; i != _cacheItems.size(); ++i) {
free(_cacheItems[i].data);
}
}
bool AudioCache::canAllocate(uint32 size) const {
Common::StackLock lock(_mutex);
return _maxSize - _totalSize >= size;
}
bool AudioCache::dropOldest() {
Common::StackLock lock(_mutex);
if (_cacheItems.size() == 0)
return false;
int oldest = -1;
for (uint i = 1; i != _cacheItems.size(); ++i) {
if (_cacheItems[i].refs == 0) {
if (oldest == -1 || _cacheItems[i].lastAccess < _cacheItems[oldest].lastAccess) {
oldest = i;
}
}
}
if (oldest == -1) {
return false;
}
memset(_cacheItems[oldest].data, 0x00, _cacheItems[oldest].size);
free(_cacheItems[oldest].data);
_totalSize -= _cacheItems[oldest].size;
_cacheItems.remove_at(oldest);
return true;
}
byte *AudioCache::findByHash(int32 hash) {
Common::StackLock lock(_mutex);
for (uint i = 0; i != _cacheItems.size(); ++i) {
if (_cacheItems[i].hash == hash) {
_cacheItems[i].lastAccess = _accessCounter++;
return _cacheItems[i].data;
}
}
return nullptr;
}
void AudioCache::storeByHash(int32 hash, Common::SeekableReadStream *stream) {
Common::StackLock lock(_mutex);
uint32 size = stream->size();
byte *data = (byte *)malloc(size);
stream->read(data, size);
cacheItem item = {
hash,
0,
_accessCounter++,
data,
size
};
_cacheItems.push_back(item);
_totalSize += size;
}
void AudioCache::incRef(int32 hash) {
Common::StackLock lock(_mutex);
for (uint i = 0; i != _cacheItems.size(); ++i) {
if (_cacheItems[i].hash == hash) {
++(_cacheItems[i].refs);
return;
}
}
assert(false && "AudioCache::incRef: hash not found");
}
void AudioCache::decRef(int32 hash) {
Common::StackLock lock(_mutex);
for (uint i = 0; i != _cacheItems.size(); ++i) {
if (_cacheItems[i].hash == hash) {
assert(_cacheItems[i].refs > 0);
--(_cacheItems[i].refs);
return;
}
}
assert(false && "AudioCache::decRef: hash not found");
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,64 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_AUDIO_CACHE_H
#define BLADERUNNER_AUDIO_CACHE_H
#include "common/array.h"
#include "common/mutex.h"
namespace BladeRunner {
/*
* This is a poor imitation of Bladerunner's resource cache
*/
class AudioCache {
struct cacheItem {
int32 hash;
int refs;
uint lastAccess;
byte *data;
uint32 size;
};
Common::Mutex _mutex;
Common::Array<cacheItem> _cacheItems;
uint32 _totalSize;
uint32 _maxSize;
uint32 _accessCounter;
public:
AudioCache();
~AudioCache();
bool canAllocate(uint32 size) const;
bool dropOldest();
byte *findByHash(int32 hash);
void storeByHash(int32 hash, Common::SeekableReadStream *stream);
void incRef(int32 hash);
void decRef(int32 hash);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,301 @@
/* 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/audio_mixer.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/music.h"
#include "bladerunner/time.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/timer.h"
namespace BladeRunner {
AudioMixer::AudioMixer(BladeRunnerEngine *vm) {
_vm = vm;
for (int i = 0; i < kChannels; ++i) {
_channels[i].isPresent = false;
}
#if !BLADERUNNER_ORIGINAL_BUGS
for (int i = 0; i < kAudioMixerAppTimersNum; ++i) {
_audioMixerAppTimers[i].started = false;
_audioMixerAppTimers[i].lastFired = 0u;
_audioMixerAppTimers[i].intervalMillis = 0u;
}
#endif // BLADERUNNER_ORIGINAL_BUGS
_vm->getTimerManager()->installTimerProc(timerCallback, (1000 / kUpdatesPerSecond) * 1000, this, "BladeRunnerAudioMixerTimer");
}
AudioMixer::~AudioMixer() {
for (int i = 0; i < kChannels; ++i) {
stop(i, 0u);
}
_vm->getTimerManager()->removeTimerProc(timerCallback);
}
// volume should be in [0, 100]
// pan should be in [-100, 100]
// priority should be in [0, 100]
int AudioMixer::play(Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void (*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs) {
Common::StackLock lock(_mutex);
int channelToAssign = -1;
int lowestPriority = 1000000;
int lowestPriorityChannel = -1;
for (int i = 0; i < kUsableChannels; ++i) {
if (!_channels[i].isPresent) {
channelToAssign = i;
break;
}
if (_channels[i].priority < lowestPriority) {
lowestPriority = _channels[i].priority;
lowestPriorityChannel = i;
}
}
// If there's no available channel found
if (channelToAssign == -1) {
// Give up if the lowest priority channel still has greater priority (lowestPriority).
// NOTE If the new priority is *equal* to the existing lowestPriority,
// then the new audio will replace the old one.
if (priority < lowestPriority) {
//debug("No available audio channel found - giving up");
return -1;
}
// Otherwise, stop the lowest priority channel, and assign the slot to the new one.
//debug("Stopping lowest priority channel %d with lower prio %d!", lowestPriorityChannel, lowestPriority);
stop(lowestPriorityChannel, 0u);
channelToAssign = lowestPriorityChannel;
}
return playInChannel(channelToAssign, type, stream, priority, loop, volume, pan, endCallback, callbackData, trackDurationMs);
}
// volume should be in [0, 100]
int AudioMixer::playMusic(Audio::RewindableAudioStream *stream, int volume, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs) {
Common::StackLock lock(_mutex);
return playInChannel(kMusicChannel, Audio::Mixer::kMusicSoundType, stream, 100, false, volume, 0, endCallback, callbackData, trackDurationMs);
}
// Note: time tends to be the requested time in seconds multiplied by 60u
void AudioMixer::stop(int channel, uint32 time) {
Common::StackLock lock(_mutex);
if (_channels[channel].isPresent) {
if (time) {
adjustVolume(channel, 0, time);
} else {
_channels[channel].isPresent = false;
if (_channels[channel].sentToMixer) {
_vm->_mixer->stopHandle(_channels[channel].handle);
}
if (_channels[channel].endCallback != nullptr) {
_channels[channel].endCallback(channel, _channels[channel].callbackData);
}
}
}
}
// volume should be in [0, 100]
// pan should be in [-100, 100]
// priority should be in [0, 100]
int AudioMixer::playInChannel(int channel, Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs) {
_channels[channel].isPresent = true;
_channels[channel].stream = stream;
_channels[channel].priority = priority;
_channels[channel].loop = loop;
_channels[channel].volume = volume;
_channels[channel].volumeTarget = 0;
_channels[channel].volumeDelta = 0;
_channels[channel].pan = pan;
_channels[channel].panTarget = 0;
_channels[channel].panDelta = 0;
_channels[channel].endCallback = endCallback;
_channels[channel].callbackData = callbackData;
_channels[channel].timeStarted = _vm->_time->currentSystem();
_channels[channel].trackDurationMs = trackDurationMs;
if (!_vm->_mixer->isReady()) {
_channels[channel].sentToMixer = false;
return channel;
}
_channels[channel].sentToMixer = true;
Audio::AudioStream *audioStream = stream;
if (loop) {
audioStream = new Audio::LoopingAudioStream(stream, 0, DisposeAfterUse::YES);
}
// Note: 127 (multiplier for pan) is the max (abs) balance value (see common/mixer.cpp)
_vm->_mixer->playStream(type,
&_channels[channel].handle,
audioStream,
-1,
(volume * Audio::Mixer::kMaxChannelVolume) / 100, // the resulting value will be a percentage over kMaxChannelVolume
// playStream() will get the soundtype volume into consideration
// See: Channel::updateChannelVolumes() in audio/mixer.cpp
(pan * 127) / 100);
return channel;
}
bool AudioMixer::isActive(int channel) const {
Common::StackLock lock(_mutex);
return _channels[channel].isPresent
&& ((_channels[channel].sentToMixer && _vm->_mixer->isSoundHandleActive(_channels[channel].handle))
|| (!_channels[channel].sentToMixer && !_channels[channel].loop && (_vm->_time->currentSystem() - _channels[channel].timeStarted < _channels[channel].trackDurationMs)));
}
void AudioMixer::timerCallback(void *self) {
((AudioMixer *)self)->tick();
}
// This method sets the target volume (as a percent) and the delta increment or decrement
// to reach the target in the specified time.
// Note: time tends to be the requested time in seconds multiplied by 60u
// targetVolume should be in [0, 100]
void AudioMixer::adjustVolume(int channel, int targetVolume, uint32 time) {
Common::StackLock lock(_mutex);
if (_channels[channel].isPresent) {
_channels[channel].volumeTarget = targetVolume;
_channels[channel].volumeDelta = ((targetVolume - _channels[channel].volume) / (time / 60.0f)) / (float)kUpdatesPerSecond;
}
}
// This method sets the target pan (as a percent) and the delta increment or decrement
// to reach the target in the specified time.
// Note: time tends to be the requested time in seconds multiplied by 60u
// targetPan should be in [-100, 100]
void AudioMixer::adjustPan(int channel, int targetPan, uint32 time) {
Common::StackLock lock(_mutex);
if (_channels[channel].isPresent) {
targetPan = CLIP(targetPan, -100, 100);
_channels[channel].panTarget = targetPan;
_channels[channel].panDelta = ((targetPan - _channels[channel].pan) / (time / 60.0f)) / (float)kUpdatesPerSecond;
}
}
void AudioMixer::tick() {
Common::StackLock lock(_mutex);
for (int i = 0; i < kChannels; ++i) {
Channel *channel = &_channels[i];
if (!channel->isPresent) {
continue;
}
if (channel->volumeDelta != 0.0f) {
// apply volumeDelta to volume (common use for adjustVolume or stop playing - ie mainly for fadeIn, fadeOut)
channel->volume = CLIP(channel->volume + channel->volumeDelta, 0.0f, 100.0f);
if ((channel->volumeDelta < 0 && channel->volume <= channel->volumeTarget)
|| (channel->volumeDelta > 0 && channel->volume >= channel->volumeTarget)) {
channel->volumeDelta = 0.0f;
}
if (channel->sentToMixer) {
// map volume value from [0..100] to [0..kMaxChannelVolume]
_vm->_mixer->setChannelVolume(channel->handle, (channel->volume * Audio::Mixer::kMaxChannelVolume) / 100);
}
if (channel->volume <= 0.0f) {
stop(i, 0u);
}
}
if (channel->panDelta != 0.0) {
// apply panDelta to pan (common use for adjusting pan)
channel->pan = CLIP(channel->pan + channel->panDelta, -100.0f, 100.0f);
if ((channel->panDelta < 0 && channel->pan <= channel->panTarget) || (channel->panDelta > 0 && channel->pan >= channel->panTarget)) {
channel->panDelta = 0.0f;
}
if (channel->sentToMixer) {
// map balance value from [-100..100] to [-127..127]
_vm->_mixer->setChannelBalance(channel->handle, (channel->pan * 127) / 100);
}
}
if ((channel->sentToMixer && !_vm->_mixer->isSoundHandleActive(channel->handle))
|| channel->stream->endOfStream()
|| (!channel->sentToMixer && !channel->loop && _vm->_time->currentSystem() - channel->timeStarted >= channel->trackDurationMs)
) {
stop(i, 0u);
}
}
#if !BLADERUNNER_ORIGINAL_BUGS
// piggyback the realtime triggered tick() actions, with a check for the virtual timers (app timers)
for (int i = 0; i < kAudioMixerAppTimersNum; ++i) {
if (_audioMixerAppTimers[i].started
&& _vm->_time->currentSystem() - _audioMixerAppTimers[i].lastFired > _audioMixerAppTimers[i].intervalMillis) {
// We actually need to have the _vm->_time->currentSystem() check in the if clause
// and not use a var that stores the current time before we enter the loop
// because the functions for these timers may affect the lastFired, by setting it to the a current system time
// and then lastFired would have been greater than our stored system time here.
_audioMixerAppTimers[i].lastFired = _vm->_time->currentSystem();
switch (i) {
case kAudioMixerAppTimerMusicNext:
_vm->_music->next();
break;
case kAudioMixerAppTimerMusicFadeOut:
_vm->_music->fadeOut();
break;
default:
// error - but probably won't happen
error("Unknown Audio Mixer App Timer Id");
break;
}
}
}
#endif // !BLADERUNNER_ORIGINAL_BUGS
}
#if !BLADERUNNER_ORIGINAL_BUGS
void AudioMixer::startAppTimerProc(int audioMixAppTimerId, uint32 intervalMillis) {
// Attempt to lock the mutex, since we reach here from another thread (main thread)
Common::StackLock lock(_mutex);
if (audioMixAppTimerId < 0 || audioMixAppTimerId >= kAudioMixerAppTimersNum) {
return;
}
_audioMixerAppTimers[audioMixAppTimerId].started = true;
_audioMixerAppTimers[audioMixAppTimerId].intervalMillis = intervalMillis;
_audioMixerAppTimers[audioMixAppTimerId].lastFired = _vm->_time->currentSystem();
}
void AudioMixer::stopAppTimerProc(int audioMixAppTimerId) {
// Attempt to lock the mutex, since we reach here from another thread (main thread)
Common::StackLock lock(_mutex);
if (audioMixAppTimerId < 0 || audioMixAppTimerId >= kAudioMixerAppTimersNum) {
return;
}
_audioMixerAppTimers[audioMixAppTimerId].started = false;
}
#endif // !BLADERUNNER_ORIGINAL_BUGS
} // End of namespace BladeRunner

View File

@@ -0,0 +1,120 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_AUDIO_MIXER_H
#define BLADERUNNER_AUDIO_MIXER_H
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/mutex.h"
#include "bladerunner/bladerunner.h" // For BLADERUNNER_ORIGINAL_BUGS symbol
namespace BladeRunner {
class BladeRunnerEngine;
#if !BLADERUNNER_ORIGINAL_BUGS
enum audioMixerAppTimers {
kAudioMixerAppTimerMusicNext = 0,
kAudioMixerAppTimerMusicFadeOut = 1
};
#endif
class AudioMixer {
#if BLADERUNNER_ORIGINAL_BUGS
static const int kChannels = 9;
static const int kUsableChannels = 8;
static const int kMusicChannel = 8;
#else
static const int kChannels = 15;
static const int kUsableChannels = 14;
static const int kMusicChannel = 14;
static const int kAudioMixerAppTimersNum = 2;
#endif // BLADERUNNER_ORIGINAL_BUGS
static const int kUpdatesPerSecond = 40;
struct Channel {
bool isPresent;
int priority;
bool loop;
Audio::SoundHandle handle;
Audio::AudioStream *stream;
float volume; // should be in [0.0f, 100.0f]. It's percent for the Audio::Mixer::kMaxChannelVolume
float volumeDelta;
float volumeTarget; // should be in [0.0f, 100.0f], as for volume field.
float pan; // should be in [-100.0f, 100.0f]. It's percent for 127 (max absolute balance value)
float panDelta;
float panTarget; // should be in [-100.0f, 100.0f], as for pan field.
void (*endCallback)(int channel, void *data);
void *callbackData;
uint32 timeStarted;
uint32 trackDurationMs;
bool sentToMixer;
};
BladeRunnerEngine *_vm;
Channel _channels[kChannels];
Common::Mutex _mutex;
#if !BLADERUNNER_ORIGINAL_BUGS
struct audioMixerAppTimer {
bool started;
uint32 intervalMillis; // expiration interval in milliseconds
uint32 lastFired; // time of last time the timer expired in milliseconds
};
audioMixerAppTimer _audioMixerAppTimers[kAudioMixerAppTimersNum];
#endif // !BLADERUNNER_ORIGINAL_BUGS
public:
AudioMixer(BladeRunnerEngine *vm);
~AudioMixer();
int play(Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs);
int playMusic(Audio::RewindableAudioStream *stream, int volume, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs);
void stop(int channel, uint32 time);
void adjustVolume(int channel, int targetVolume, uint32 time);
void adjustPan(int channel, int targetPan, uint32 time);
#if !BLADERUNNER_ORIGINAL_BUGS
void startAppTimerProc(int audioMixAppTimerId, uint32 intervalMillis);
void stopAppTimerProc(int audioMixAppTimerId);
#endif // !BLADERUNNER_ORIGINAL_BUGS
// TODO Are these completely unused?
// void resume(int channel, uint32 delay);
// void pause(int channel, uint32 delay);
private:
int playInChannel(int channel, Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs);
bool isActive(int channel) const;
void tick();
static void timerCallback(void *refCon);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,300 @@
/* 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/audio_player.h"
#include "bladerunner/archive.h"
#include "bladerunner/aud_stream.h"
#include "bladerunner/audio_cache.h"
#include "bladerunner/audio_mixer.h"
#include "bladerunner/bladerunner.h"
#include "common/debug.h"
#include "common/stream.h"
#include "common/random.h"
namespace Common {
class MemoryReadStream;
}
namespace BladeRunner {
AudioPlayer::AudioPlayer(BladeRunnerEngine *vm) {
_vm = vm;
for (int i = 0; i != kTracks; ++i) {
_tracks[i].priority = 0;
_tracks[i].isActive = false;
_tracks[i].channel = -1;
_tracks[i].stream = nullptr;
}
// _sfxVolumeFactorOriginalEngine here sets a percentage to be applied on the audio tracks' volume
// before sending them to the audio player.
// This is how the original engine set the volume via the in-game KIA volume slider controls.
// Setting _sfxVolumeFactorOriginalEngine to 100, for the purposes ScummVM engine, renders it indifferent,
// so sound volume can be controlled by ScummVM's Global Main Menu / ConfMan/ syncSoundSettings().
_sfxVolumeFactorOriginalEngine = BLADERUNNER_ORIGINAL_SETTINGS ? 65 : 100;
}
AudioPlayer::~AudioPlayer() {
stopAll();
}
void AudioPlayer::stopAll() {
for (int i = 0; i != kTracks; ++i) {
stop(i, true);
}
for (int i = 0; i != kTracks; ++i) {
while (isActive(i)) {
// wait for all tracks to finish
}
}
}
// This method sets the target volume (as a percent) for the track
// and the time (delaySeconds) to reach that target in.
// volume (and actualVolume) should be in [0, 100]
void AudioPlayer::adjustVolume(int track, int volume, uint32 delaySeconds, bool explicitVolumeAdjustment) {
if (track < 0 || track >= kTracks || !_tracks[track].isActive || _tracks[track].channel == -1) {
return;
}
int actualVolume = volume;
if (explicitVolumeAdjustment) {
actualVolume = (actualVolume * _sfxVolumeFactorOriginalEngine) / 100;
}
_tracks[track].volume = actualVolume;
_vm->_audioMixer->adjustVolume(_tracks[track].channel, actualVolume, 60u * delaySeconds);
}
// This method sets the target pan (as a percent) for the track
// and the time (delaySeconds) to reach that target in.
// pan should be in [-100, 100]
void AudioPlayer::adjustPan(int track, int pan, uint32 delaySeconds) {
if (track < 0 || track >= kTracks || !_tracks[track].isActive || _tracks[track].channel == -1) {
return;
}
_tracks[track].pan = pan;
_vm->_audioMixer->adjustPan(_tracks[track].channel, pan, 60u * delaySeconds);
}
#if BLADERUNNER_ORIGINAL_SETTINGS
// We no longer set the _sfxVolumeFactorOriginalEngine via a public method.
// For the ScummVM Engine's purposes it is set in AudioPlayer::AudioPlayer() constructor and keeps its value constant.
void AudioPlayer::setVolume(int volume) {
_sfxVolumeFactorOriginalEngine = volume;
}
int AudioPlayer::getVolume() const {
return _sfxVolumeFactorOriginalEngine;
}
#endif // BLADERUNNER_ORIGINAL_SETTINGS
void AudioPlayer::playSample() {
Common::String name;
switch (_vm->_rnd.getRandomNumber(3)) {
case 0:
name = "gunmiss1.aud";
break;
case 1:
name = "gunmiss2.aud";
break;
case 2:
name = "gunmiss3.aud";
break;
default:
name = "gunmiss4.aud";
break;
}
// Sample plays with priority 100 (max)
playAud(name, 100, 0, 0, 100, 0);
}
void AudioPlayer::remove(int channel) {
Common::StackLock lock(_mutex);
for (int i = 0; i != kTracks; ++i) {
if (_tracks[i].channel == channel) {
_tracks[i].isActive = false;
_tracks[i].priority = 0;
_tracks[i].channel = -1;
_tracks[i].stream = nullptr;
break;
}
}
}
void AudioPlayer::mixerChannelEnded(int channel, void *data) {
AudioPlayer *audioPlayer = (AudioPlayer *)data;
audioPlayer->remove(channel);
}
// This method plays an audio file, at volume "volume" (as a percent, set immediately, no fade-in),
// with audio balance starting at panStart (as a signed percent) and ending at panEnd.
// The balance shifting happens throughout the duration of the audio track.
// volume (and actualVolume) should be in [0, 100]
// panStart and panEnd should be in [-100, 100]
// priority should be in [0, 100]
// The higher the priority, the more likely it is that this track will replace another active one
// (with the lowest priority), if no available track slots exists.
// Returns the index of the track slot assigned (for _tracks array) or -1 otherwise (gave up)
// The code here is very similar to AudioMixer::play()
// Note that this method calls AudioMixer::play() which also uses the priority value to assign a channel to the track.
// If that fails, the new track is dropped (gave up).
// TODO Maybe explain why we need this two step priority check (for track slot and then channel slot)
int AudioPlayer::playAud(const Common::String &name, int volume, int panStart, int panEnd, int priority, byte flags, Audio::Mixer::SoundType type) {
debugC(6, kDebugSound, "AudioPlayer::playAud name:%s v:%d pS:%d pE:%d pr:%d type:%d", name.c_str(), volume, panStart, panEnd, priority, (int)type);
// Find first available track or, alternatively, the lowest priority playing track
int trackSlotToAssign = -1;
int lowestPriority = 1000000; // TODO wouldn't a lower value work as well? eg. 1000? Original uses 100 but code is a bit different
int lowestPriorityTrackSlot = -1;
// Find an available track slot
for (int i = 0; i != kTracks; ++i) {
if (!isActive(i)) {
//debug("Assigned track %i to %s", i, name.c_str());
trackSlotToAssign = i;
break;
}
if (lowestPriorityTrackSlot == -1 || _tracks[i].priority < lowestPriority) {
lowestPriority = _tracks[i].priority;
lowestPriorityTrackSlot = i;
}
}
// If there's still no available track slot
if (trackSlotToAssign == -1) {
// Give up if the lowest priority track still has greater priority (lowestPriority).
#if BLADERUNNER_ORIGINAL_BUGS
// NOTE If the new priority is *equal* to the existing lowestPriority,
// then the new audio would still not replace the old one.
if (priority <= lowestPriority) {
return -1;
}
#else
// NOTE If the new priority is *equal* to the existing lowestPriority,
// then the new audio will replace the old one.
if (priority < lowestPriority) {
//debug("No available track for %s %d - giving up", name.c_str(), priority);
return -1;
}
#endif
// Otherwise, stop the lowest priority track, and assign the slot to the new one.
//debug("Stop lowest priority track (with lower prio: %d %d), for %s %d!", lowestPriorityTrackSlot, lowestPriority, name.c_str(), priority);
stop(lowestPriorityTrackSlot, true);
trackSlotToAssign = lowestPriorityTrackSlot;
}
// Load audio resource and store in cache. Playback will happen directly from there.
int32 hash = MIXArchive::getHash(name);
if (!_vm->_audioCache->findByHash(hash)) {
Common::SeekableReadStream *r = _vm->getResourceStream(_vm->_enhancedEdition ? ("audio/" + name) : name);
if (!r) {
//debug("Could not get stream for %s %d - giving up", name.c_str(), priority);
return -1;
}
int32 size = r->size();
while (!_vm->_audioCache->canAllocate(size)) {
if (!_vm->_audioCache->dropOldest()) {
delete r;
//debug("No available mem in cache for %s %d - giving up", name.c_str(), priority);
return -1;
}
}
_vm->_audioCache->storeByHash(hash, r);
delete r;
}
AudStream *audioStream = new AudStream(_vm->_audioCache, hash);
int actualVolume = volume;
if (!(flags & kAudioPlayerOverrideVolume)) {
actualVolume = (actualVolume * _sfxVolumeFactorOriginalEngine) / 100;
}
int channel = _vm->_audioMixer->play(type,
audioStream,
priority,
flags & kAudioPlayerLoop,
actualVolume,
panStart,
mixerChannelEnded,
this,
audioStream->getLength());
if (channel == -1) {
delete audioStream;
//debug("No available channel for %s %d - giving up", name.c_str(), priority);
return -1;
}
if (panStart != panEnd) {
_vm->_audioMixer->adjustPan(channel, panEnd, (60u * audioStream->getLength()) / 1000u);
}
_tracks[trackSlotToAssign].isActive = true;
_tracks[trackSlotToAssign].channel = channel;
_tracks[trackSlotToAssign].priority = priority;
_tracks[trackSlotToAssign].volume = actualVolume;
_tracks[trackSlotToAssign].stream = audioStream;
return trackSlotToAssign;
}
bool AudioPlayer::isActive(int track) const {
Common::StackLock lock(_mutex);
if (track < 0 || track >= kTracks) {
return false;
}
return _tracks[track].isActive;
}
/**
* Return the track's length in milliseconds
*/
uint32 AudioPlayer::getLength(int track) const {
Common::StackLock lock(_mutex);
if (track < 0 || track >= kTracks) {
return 0;
}
return _tracks[track].stream->getLength();
}
void AudioPlayer::stop(int track, bool immediately) {
if (isActive(track)) {
// If parameter "immediately" is not set,
// the delay for audio stop is 1 second (multiplied by 60u as expected by AudioMixer::stop())
_vm->_audioMixer->stop(_tracks[track].channel, immediately ? 0u : 60u);
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,95 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_AUDIO_PLAYER_H
#define BLADERUNNER_AUDIO_PLAYER_H
#include "common/array.h"
#include "common/mutex.h"
#include "common/str.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "bladerunner/bladerunner.h" // For BLADERUNNER_ORIGINAL_BUGS and BLADERUNNER_ORIGINAL_SETTINGS symbols
namespace BladeRunner {
class BladeRunnerEngine;
class AudioCache;
class AudStream;
enum AudioPlayerFlags {
kAudioPlayerLoop = 1,
kAudioPlayerOverrideVolume = 2
};
class AudioPlayer {
#if BLADERUNNER_ORIGINAL_BUGS
static const int kTracks = 6;
#else
// increase tracks, reduce probability of tracks being skipped
static const int kTracks = 12;
#endif // BLADERUNNER_ORIGINAL_BUGS
// Use SFX sound type if none is specified
static const Audio::Mixer::SoundType kAudioPlayerSoundType = Audio::Mixer::kSFXSoundType;
struct Track {
bool isActive;
int channel;
int priority;
int volume; // should be in [0, 100]
int pan; // should be in [-100, 100]
AudStream *stream;
};
BladeRunnerEngine *_vm;
Common::Mutex _mutex;
Track _tracks[kTracks];
int _sfxVolumeFactorOriginalEngine; // should be in [0, 100] - Unused in ScummVM Engine, used in original engine
public:
AudioPlayer(BladeRunnerEngine *vm);
~AudioPlayer();
int playAud(const Common::String &name, int volume, int panStart, int panEnd, int priority, byte flags = 0, Audio::Mixer::SoundType type = kAudioPlayerSoundType);
bool isActive(int track) const;
uint32 getLength(int track) const;
void stop(int track, bool immediately);
void stopAll();
void adjustVolume(int track, int volume, uint32 delaySeconds, bool explicitVolumeAdjustment);
void adjustPan(int track, int pan, uint32 delaySeconds);
#if BLADERUNNER_ORIGINAL_SETTINGS
void setVolume(int volume);
int getVolume() const;
#endif // BLADERUNNER_ORIGINAL_SETTINGS
void playSample();
private:
void remove(int channel);
static void mixerChannelEnded(int channel, void *data);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,169 @@
/* 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/audio_speech.h"
#include "bladerunner/actor.h"
#include "bladerunner/aud_stream.h"
#include "bladerunner/audio_mixer.h"
#include "bladerunner/audio_player.h"
#include "bladerunner/bladerunner.h"
#include "common/str.h"
namespace BladeRunner {
// Note: Speech samples here should be from A.TLK file
const int kSpeechSamplesNumber = 23;
const int AudioSpeech::kSpeechSamples[kSpeechSamplesNumber] = { 65, 355, 490, 465, 480, 485, 505, 760, 7655, 7770, 7740, 8170, 2705, 7200, 6460, 5560, 4870, 4555, 3880, 3525, 3595, 3250, 3070 };
void AudioSpeech::ended() {
//Common::StackLock lock(_mutex);
_isActive = false;
_channel = -1;
}
void AudioSpeech::mixerChannelEnded(int channel, void *data) {
AudioSpeech *audioSpeech = (AudioSpeech *)data;
audioSpeech->ended();
}
AudioSpeech::AudioSpeech(BladeRunnerEngine *vm) {
_vm = vm;
// _speechVolumeFactorOriginalEngine here sets a percentage to be applied on the voice cues' volume
// before sending them to the audio player.
// This is how the original engine set the volume via the in-game KIA volume slider controls.
// Setting _speechVolumeFactorOriginalEngine to 100, for the purposes ScummVM engine, renders it indifferent,
// so sound volume can be controlled by ScummVM's Global Main Menu / ConfMan/ syncSoundSettings().
_speechVolumeFactorOriginalEngine = BLADERUNNER_ORIGINAL_SETTINGS ? 50 : 100;
_isActive = false;
_data = new byte[kBufferSize];
_channel = -1;
}
AudioSpeech::~AudioSpeech() {
stopSpeech();
while (isPlaying()) {
// wait for the mixer to finish
}
delete[] _data;
}
// pan should be in [-100, 100]
bool AudioSpeech::playSpeech(const Common::String &name, int pan) {
if (isPlaying()) {
stopSpeech();
}
// Audio cache is not usable as hash function is producing collision for speech lines.
// It was not used in the original game either
Common::ScopedPtr<Common::SeekableReadStream> r(_vm->getResourceStream(_vm->_enhancedEdition ? ("audio/" + name) : name));
if (!r) {
warning("AudioSpeech::playSpeech: AUD resource \"%s\" not found", name.c_str());
return false;
}
if (r->size() > kBufferSize) {
warning("AudioSpeech::playSpeech: AUD larger than buffer size (%d > %d)", (int)r->size(), kBufferSize);
return false;
}
if (isPlaying()) {
stopSpeech();
}
r->read(_data, r->size());
if (r->err()) {
warning("AudioSpeech::playSpeech: Error reading resource \"%s\"", name.c_str());
return false;
}
AudStream *audioStream = new AudStream(_data, _vm->_shortyMode ? 33000 : -1);
// Speech plays here with priority 100 (max)
// Using directly _speechVolumeFactorOriginalEngine as the volume for audioMixer::play(),
// which for the ScummVM engine is 100, so speech volume will be only determined
// by the ScummVM volume for the speech sound type.
_channel = _vm->_audioMixer->play(Audio::Mixer::kSpeechSoundType,
audioStream,
100,
false,
_speechVolumeFactorOriginalEngine,
pan,
mixerChannelEnded,
this,
audioStream->getLength());
_isActive = true;
return true;
}
void AudioSpeech::stopSpeech() {
//Common::StackLock lock(_mutex);
if (_channel != -1) {
_vm->_audioMixer->stop(_channel, 0u);
}
}
bool AudioSpeech::isPlaying() const {
if (_channel == -1) {
return false;
}
return _isActive;
}
// volume should be in [0, 100]
// priority should be in [0, 100]
// pan is calculated based on actor's position with Actor::soundPan()
bool AudioSpeech::playSpeechLine(int actorId, int sentenceId, int volume, int a4, int priority) {
int pan = _vm->_actors[actorId]->soundPan();
Common::String name = Common::String::format("%02d-%04d%s.AUD", actorId, sentenceId, _vm->_languageCode.c_str());
return _vm->_audioPlayer->playAud(name, (volume * _speechVolumeFactorOriginalEngine) / 100, pan, pan, priority, kAudioPlayerOverrideVolume, Audio::Mixer::kSpeechSoundType);
}
#if BLADERUNNER_ORIGINAL_SETTINGS
// We no longer set the _speechVolumeFactorOriginalEngine via a public method.
// For the ScummVM Engine's purposes it is set in AudioSpeech::AudioSpeech() constructor and keeps its value constant.
void AudioSpeech::setVolume(int volume) {
_speechVolumeFactorOriginalEngine = volume;
}
int AudioSpeech::getVolume() const {
return _speechVolumeFactorOriginalEngine;
}
#endif // BLADERUNNER_ORIGINAL_SETTINGS
void AudioSpeech::playSample() {
#if BLADERUNNER_ORIGINAL_BUGS
_vm->_playerActor->speechPlay(kSpeechSamples[_vm->_rnd.getRandomNumber(kSpeechSamplesNumber-1)], true);
#else
if (_vm->openArchive("A.TLK")) {
// load sample speech even when in initial KIA screen (upon launch - but before loading a game)
_vm->_playerActor->speechPlay(kSpeechSamples[_vm->_rnd.getRandomNumber(kSpeechSamplesNumber-1)], true);
}
#endif // BLADERUNNER_ORIGINAL_BUGS
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_AUDIO_SPEECH_H
#define BLADERUNNER_AUDIO_SPEECH_H
#include "bladerunner/bladerunner.h" // For BLADERUNNER_ORIGINAL_SETTINGS symbol
#include "common/str.h"
#include "common/types.h"
namespace BladeRunner {
class BladeRunnerEngine;
class AudioSpeech {
static const int kBufferSize = 200000;
static const int kSpeechSamples[];
BladeRunnerEngine *_vm;
int _speechVolumeFactorOriginalEngine; // should be in [0, 100] - Unused in ScummVM Engine, used in original engine
bool _isActive;
int _channel;
byte *_data;
public:
AudioSpeech(BladeRunnerEngine *vm);
~AudioSpeech();
bool playSpeech(const Common::String &name, int pan = 0);
void stopSpeech();
bool isPlaying() const;
bool playSpeechLine(int actorId, int sentenceId, int volume, int a4, int priority);
#if BLADERUNNER_ORIGINAL_SETTINGS
void setVolume(int volume);
int getVolume() const;
#endif // BLADERUNNER_ORIGINAL_SETTINGS
void playSample();
private:
void ended();
static void mixerChannelEnded(int channel, void *data);
};
} // End of namespace BladeRunner
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,509 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_BLADERUNNER_H
#define BLADERUNNER_BLADERUNNER_H
#include "bladerunner/archive.h"
#include "common/array.h"
#include "common/random.h"
#include "common/stream.h"
#include "common/keyboard.h"
#include "common/events.h"
#include "engines/engine.h"
#include "graphics/surface.h"
#include "math/cosinetables.h"
#include "math/sinetables.h"
//TODO: change this to debugflag
#define BLADERUNNER_DEBUG_CONSOLE 0
#define BLADERUNNER_ORIGINAL_SETTINGS 0
#define BLADERUNNER_ORIGINAL_BUGS 0
namespace Common {
class Archive;
struct Event;
}
namespace GUI {
class Debugger;
}
struct ADGameDescription;
namespace BladeRunner {
enum DebugLevels {
kDebugScript = 1,
kDebugSound,
kDebugAnimation,
};
class Actor;
class ActorDialogueQueue;
class ScreenEffects;
class AIScripts;
class AmbientSounds;
class AudioCache;
class AudioMixer;
class AudioPlayer;
class AudioSpeech;
class Chapters;
class CrimesDatabase;
class Combat;
class Debugger;
class DialogueMenu;
class Elevator;
class EndCredits;
class ESPER;
class Framelimiter;
class Font;
class GameFlags;
class GameInfo;
class ItemPickup;
class Items;
class KIA;
class Lights;
class Mouse;
class Music;
class Obstacles;
class Overlays;
class PoliceMaze;
class Scene;
class SceneObjects;
class SceneScript;
class Scores;
class Settings;
class Shapes;
class SliceAnimations;
class SliceRenderer;
class Spinner;
class Subtitles;
class SuspectsDatabase;
class TextResource;
class Time;
class Vector3;
class View;
class VK;
class Waypoints;
class ZBuffer;
class BladeRunnerEngine : public Engine {
public:
static const int kArchiveCount = 12; // +2 to original value (10) to accommodate for SUBTITLES.MIX and one extra resource file, to allow for capability of loading all VQAx.MIX and the MODE.MIX file (debug purposes)
static const int kActorCount = 100;
static const int kActorVoiceOver = kActorCount - 1;
static const int kMaxCustomConcurrentRepeatableEvents = 20;
static const int16 kOriginalGameWidth = 640;
static const int16 kOriginalGameHeight = 480;
static const int16 kDemoGameWidth = 320;
static const int16 kDemoGameHeight = 200;
// Incremental number to keep track of significant revisions of the ScummVM bladerunner engine
// that could potentially introduce incompatibilities with old save files or require special actions to restore compatibility
// This is stored in game global variable "kVariableGameVersion"
// Original (classic) save game files will have version number of 0
// Values:
// 1: alpha testing (from May 15, 2019 to July 17, 2019)
// 2: all time code uses uint32 (since July 17 2019),
static const int kBladeRunnerScummVMVersion = 2;
static const char *kGameplayKeymapId;
static const char *kKiaKeymapId;
static const char *kCommonKeymapId;
bool _gameIsRunning;
bool _windowIsActive;
int _playerLosesControlCounter;
int _extraCPos;
uint8 _extraCNotify;
Common::String _languageCode;
Common::Language _language;
bool _russianCP1251;
bool _noMusicDriver; // If "Music Device" is set to "No Music" from Audio tab
ActorDialogueQueue *_actorDialogueQueue;
ScreenEffects *_screenEffects;
AIScripts *_aiScripts;
AmbientSounds *_ambientSounds;
AudioCache *_audioCache;
AudioMixer *_audioMixer;
AudioPlayer *_audioPlayer;
AudioSpeech *_audioSpeech;
Chapters *_chapters;
CrimesDatabase *_crimesDatabase;
Combat *_combat;
DialogueMenu *_dialogueMenu;
Elevator *_elevator;
EndCredits *_endCredits;
ESPER *_esper;
GameFlags *_gameFlags;
GameInfo *_gameInfo;
ItemPickup *_itemPickup;
Items *_items;
KIA *_kia;
Lights *_lights;
Font *_mainFont;
Subtitles *_subtitles;
Mouse *_mouse;
Music *_music;
Obstacles *_obstacles;
Overlays *_overlays;
PoliceMaze *_policeMaze;
Scene *_scene;
SceneObjects *_sceneObjects;
SceneScript *_sceneScript;
Scores *_scores;
Settings *_settings;
SliceAnimations *_sliceAnimations;
SliceRenderer *_sliceRenderer;
Spinner *_spinner;
SuspectsDatabase *_suspectsDatabase;
Time *_time;
View *_view;
Framelimiter *_framelimiter;
VK *_vk;
Waypoints *_waypoints;
int *_gameVars;
TextResource *_textActorNames;
TextResource *_textCrimes;
TextResource *_textClueTypes;
TextResource *_textKIA;
TextResource *_textSpinnerDestinations;
TextResource *_textVK;
TextResource *_textOptions;
Shapes *_shapes;
Actor *_actors[kActorCount];
Actor *_playerActor;
Graphics::PixelFormat _screenPixelFormat;
Graphics::Surface _surfaceFront;
Graphics::Surface _surfaceBack;
bool _surfaceFrontCreated;
bool _surfaceBackCreated;
ZBuffer *_zbuffer;
Common::RandomSource _rnd;
uint32 _newGameRandomSeed;
Debugger *_debugger;
Math::CosineTable *_cosTable1024;
Math::SineTable *_sinTable1024;
bool _isWalkingInterruptible;
bool _interruptWalking;
bool _playerActorIdle;
bool _playerDead;
bool _actorIsSpeaking;
bool _actorSpeakStopIsRequested;
bool _gameOver;
bool _gameJustLaunched;
int _gameAutoSaveTextId;
bool _gameIsAutoSaving;
bool _gameIsLoading;
bool _sceneIsLoading;
bool _vqaIsPlaying;
bool _vqaStopIsRequested;
bool _subtitlesEnabled; // tracks the state of whether subtitles are enabled or disabled from ScummVM GUI option or KIA checkbox (the states are synched)
bool _showSubtitlesForTextCrawl;
bool _sitcomMode;
bool _shortyMode;
bool _noDelayMillisFramelimiter;
bool _framesPerSecondMax;
bool _disableStaminaDrain;
bool _spanishCreditsCorrection;
bool _cutContent;
bool _enhancedEdition;
bool _validBootParam;
int _walkSoundId;
int _walkSoundVolume;
int _walkSoundPan;
int _runningActorId;
uint32 _mouseClickTimeLast;
uint32 _mouseClickTimeDiff;
int _walkingToExitId;
bool _isInsideScriptExit;
int _walkingToRegionId;
bool _isInsideScriptRegion;
int _walkingToObjectId;
bool _isInsideScriptObject;
int _walkingToItemId;
bool _isInsideScriptItem;
bool _walkingToEmpty;
int _walkingToEmptyX;
int _walkingToEmptyY;
bool _isInsideScriptEmpty;
int _walkingToActorId;
bool _isInsideScriptActor;
int _actorUpdateCounter;
uint32 _actorUpdateTimeLast;
uint32 _timeOfMainGameLoopTickPrevious;
bool _isNonInteractiveDemo;
// This addon is to emulate keeping a keyboard key pressed (continuous / repeated firing of the event)
// -- code is pretty much identical from our common\events.cpp (KeyboardRepeatEventSourceWrapper)
// for continuous events (keyDown)
enum {
kKeyRepeatInitialDelay = 400,
kKeyRepeatSustainDelay = 100
};
Common::KeyState _currentKeyDown;
uint32 _keyRepeatTimeLast;
uint32 _keyRepeatTimeDelay;
uint32 _customEventRepeatTimeLast;
uint32 _customEventRepeatTimeDelay;
typedef Common::Array<Common::Event> ActiveCustomEventsArray;
// We do allow keys mapped to the same event,
// so eg. a key (Enter) could cause 2 or more events to fire,
// However, we should probably restrict the active events
// (that can be repeated while holding the mapped keys down)
// to a maximum of kMaxCustomConcurrentRepeatableEvents
ActiveCustomEventsArray _activeCustomEvents;
// NOTE We still need keyboard functionality for naming saved games and also for the KIA Easter eggs.
// In KIA keyboard events should be accounted where possible - however some keymaps are still needed
// which is why we have the three separate common, gameplay-only and kia-only keymaps.
// If a valid keyboard key character eg. ("A") for text input (or Easter egg input)
// is also mapped to a common or KIA only custom event, then the custom event will be effected and not the key input.
// NOTE We don't use a custom action for left click -- we just use the standard left click action event (kStandardActionLeftClick)
// NOTE Dialogue Skip does not work for dialogue replayed when clicking on KIA clues (this is the original's behavior too)
// NOTE Toggle KIA options does not work when McCoy is walking towards a character when the player clicks on McCoy
// (this is the original's behavior too).
// "Esc" (by default) or the mapped key to this action still works though.
// NOTE A drawback of using customized keymapper for the game is that we can no longer replicate the original's behavior
// whereby holding down <SPACEBAR> would cause McCoy to keep switching quickly between combat mode and normal mode.
// This is because the original, when holding down right mouse button, would just toggle McCoy's mode once.
// We keep the behavior for "right mouse button".
// The continuous fast toggle behavior when holding down <SPACEBAR> feels more like a bug anyway.
// NOTE In the original, the KP_PERIOD key with NUMLOCK on, would work as a normal '.' character
// in the KIA Save Game screen. With NUMLOCK off, it would work as a delete request for the selected entry.
// However, NUMLOCK is currently not working as a modifier key for keymaps,
// so maybe we can implement the original behavior more accurately,
// when that is fixed in the keymapper or hardware-input code.
// For now, KP_PERIOD will work (by default) as a delete request.
enum BladeRunnerEngineMappableAction {
// kMpActionLeftClick, // default <left click> (select, walk-to, run-to, look-at, talk-to, use, shoot (combat mode), KIA (click on McCoy))
kMpActionToggleCombat, // default <right click> or <Spacebar>
kMpActionCutsceneSkip, // default <Return> or <KP_Enter> or <Esc> or <Spacebar>
kMpActionDialogueSkip, // default <Return> or <KP_Enter>
kMpActionToggleKiaOptions, // default <Esc> opens/closes KIA, in Options tab
kMpActionOpenKiaDatabase, // default <Tab> - only opens KIA (if closed), in one of the database tabs (the last active one, or else the first)
kMpActionOpenKIATabHelp, // default <F1>
kMpActionOpenKIATabSaveGame, // default <F2>
kMpActionOpenKIATabLoadGame, // default <F3>
kMpActionOpenKIATabCrimeSceneDatabase, // default <F4>
kMpActionOpenKIATabSuspectDatabase, // default <F5>
kMpActionOpenKIATabClueDatabase, // default <F6>
kMpActionOpenKIATabQuitGame, // default <F10>
kMpActionScrollUp, // ScummVM addition (scroll list up)
kMpActionScrollDown, // ScummVM addition (scroll list down)
kMpConfirmDlg, // default <Return> or <KP_Enter>
kMpDeleteSelectedSvdGame, // default <Delete> or <KP_Period>
kMpActionToggleCluePrivacy // default <right click>
};
private:
MIXArchive _archives[kArchiveCount];
Common::Archive *_archive;
public:
BladeRunnerEngine(OSystem *syst, const ADGameDescription *desc);
~BladeRunnerEngine() override;
bool hasFeature(EngineFeature f) const override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::Error loadGameState(int slot) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
/**
* NOTE: Disable support for external autosave (ScummVM's feature).
* Main reason is that it's not easy to properly label this autosave,
* since currently it would translate "Autosave" to the ScummVM GUI language
* which ends up showing as "?????? ??????" on non-latin languages (eg. Greek),
* and in addition is inconsistent with the game's own GUI language.
* Secondary reason is that the game already has an autosaving mechanism,
* albeit only at the start of a new Act.
* And final reason would be to prevent an autosave at an unforeseen moment,
* if we've failed to account for all cases that the game should not save by itself;
* currently those are listed in BladeRunnerEngine::canSaveGameStateCurrently().
*/
int getAutosaveSlot() const override { return -1; }
void pauseEngineIntern(bool pause) override;
Common::Error run() override;
bool checkFiles(Common::Array<Common::String> &missingFiles);
bool startup(bool hasSavegames = false);
void initChapterAndScene();
void shutdown();
bool loadSplash();
Common::Point getMousePos() const;
bool isMouseButtonDown() const;
void gameLoop();
void gameTick();
void actorsUpdate();
void walkingReset();
void handleEvents();
void handleKeyUp(Common::Event &event);
void handleKeyDown(Common::Event &event);
void handleMouseAction(int x, int y, bool mainButton, bool buttonDown, int scrollDirection = 0);
void handleMouseClickExit(int exitId, int x, int y, bool buttonDown);
void handleMouseClickRegion(int regionId, int x, int y, bool buttonDown);
void handleMouseClickItem(int itemId, bool buttonDown);
void handleMouseClickActor(int actorId, bool mainButton, bool buttonDown, Vector3 &scenePosition, int x, int y);
void handleMouseClick3DObject(int objectId, bool buttonDown, bool isClickable, bool isTarget);
void handleMouseClickEmpty(int x, int y, Vector3 &scenePosition, bool buttonDown);
bool isAllowedRepeatedKey(const Common::KeyState &currKeyState);
void handleCustomEventStart(Common::Event &event);
void handleCustomEventStop(Common::Event &event);
bool isAllowedRepeatedCustomEvent(const Common::Event &currEvent);
bool shouldDropRogueCustomEvent(const Common::Event &evt);
void cleanupPendingRepeatingEvents(const Common::String &keymapperId);
void gameWaitForActive();
void loopActorSpeaking();
void loopQueuedDialogueStillPlaying();
void outtakePlay(int id, bool no_localization, int container = -1);
void outtakePlay(const Common::String &basenameNoExt, bool no_localization, int container = -3);
bool openArchive(const Common::String &name);
bool closeArchive(const Common::String &name);
bool isArchiveOpen(const Common::String &name) const;
bool openArchiveEnhancedEdition();
void syncSoundSettings() override;
bool isSubtitlesEnabled();
void setSubtitlesEnabled(bool newVal);
Common::SeekableReadStream *getResourceStream(const Common::String &name);
bool playerHasControl();
void playerLosesControl();
void playerGainsControl(bool force = false);
void playerDied();
bool saveGame(Common::WriteStream &stream, Graphics::Surface *thumb = NULL, bool origformat = false);
bool loadGame(Common::SeekableReadStream &stream, int version);
void newGame(int difficulty);
void autoSaveGame(int textId, bool endgame);
void ISez(const Common::String &str);
void blitToScreen(const Graphics::Surface &src) const;
Graphics::Surface generateThumbnail() const;
Common::String getTargetName() const;
uint8 getExtraCNotify();
void setExtraCNotify(uint8 val);
};
static inline const Graphics::PixelFormat gameDataPixelFormat() {
return Graphics::PixelFormat(2, 5, 5, 5, 1, 10, 5, 0, 15);
}
static inline void getGameDataColor(uint16 color, uint8 &a, uint8 &r, uint8 &g, uint8 &b) {
// gameDataPixelFormat().colorToARGB(vqaColor, a, r, g, b);
// using pixel format functions is too slow on some ports because of runtime checks
uint8 r5 = (color >> 10) & 0x1F;
uint8 g5 = (color >> 5) & 0x1F;
uint8 b5 = (color ) & 0x1F;
a = color >> 15;
r = (r5 << 3) | (r5 >> 2);
g = (g5 << 3) | (g5 >> 2);
b = (b5 << 3) | (b5 >> 2);
}
static inline const Graphics::PixelFormat screenPixelFormat() {
return ((BladeRunnerEngine*)g_engine)->_screenPixelFormat;
}
static inline void drawPixel(Graphics::Surface &surface, void* dst, uint32 value) {
switch (surface.format.bytesPerPixel) {
case 1:
*(uint8*)dst = (uint8)value;
break;
case 2:
*(uint16*)dst = (uint16)value;
break;
case 4:
*(uint32*)dst = (uint32)value;
break;
default:
break;
}
}
static inline void getPixel(Graphics::Surface &surface, void* dst, uint32 &value) {
switch (surface.format.bytesPerPixel) {
case 1:
value = (uint8)(*(uint8*)dst);
break;
case 2:
value = (uint16)(*(uint16*)dst);
break;
case 4:
value = (uint32)(*(uint32*)dst);
break;
default:
break;
}
}
void blit(const Graphics::Surface &src, Graphics::Surface &dst);
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,91 @@
/* 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/boundingbox.h"
#include "bladerunner/savefile.h"
namespace BladeRunner {
// TO VERIFY
// First vertex is typically the bottom left point (x0, y0, z0)
// and second vertex is the top right (x1, y1, z1)
// Hence, we also assume that x0 < x1, y0 < y1 and z0 < z1 (ie. see how the inside() method makes its calculation)
// TODO Maybe add a check here to catch any exceptions?
BoundingBox::BoundingBox(float x0, float y0, float z0, float x1, float y1, float z1) {
_vertices[0].x = x0;
_vertices[0].y = y0;
_vertices[0].z = z0;
_vertices[1].x = x1;
_vertices[1].y = y1;
_vertices[1].z = z1;
}
void BoundingBox::expand(float x0, float y0, float z0, float x1, float y1, float z1) {
_vertices[0].x += x0;
_vertices[0].y += y0;
_vertices[0].z += z0;
_vertices[1].x += x1;
_vertices[1].y += y1;
_vertices[1].z += z1;
}
bool BoundingBox::inside(float x, float y, float z) const {
return x >= _vertices[0].x && x <= _vertices[1].x
&& y >= _vertices[0].y && y <= _vertices[1].y
&& z >= _vertices[0].z && z <= _vertices[1].z;
}
bool BoundingBox::inside(Vector3 &position) const {
return inside(position.x, position.y, position.z);
}
void BoundingBox::setXYZ(float x0, float y0, float z0, float x1, float y1, float z1) {
_vertices[0].x = x0;
_vertices[0].y = y0;
_vertices[0].z = z0;
_vertices[1].x = x1;
_vertices[1].y = y1;
_vertices[1].z = z1;
}
void BoundingBox::getXYZ(float *x0, float *y0, float *z0, float *x1, float *y1, float *z1) const {
*x0 = _vertices[0].x;
*y0 = _vertices[0].y;
*z0 = _vertices[0].z;
*x1 = _vertices[1].x;
*y1 = _vertices[1].y;
*z1 = _vertices[1].z;
}
float BoundingBox::getZ0() const {
return _vertices[0].z;
}
float BoundingBox::getZ1() const {
return _vertices[1].z;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_BOUNDING_BOX_H
#define BLADERUNNER_BOUNDING_BOX_H
#include "bladerunner/vector.h"
namespace BladeRunner {
class SaveFileWriteStream;
class BoundingBox {
Vector3 _vertices[2];
public:
BoundingBox() {}
BoundingBox(float x0, float y0, float z0, float x1, float y1, float z1);
void expand(float x0, float y0, float z0, float x1, float y1, float z1);
bool inside(float x, float y, float z) const;
bool inside(Vector3 &position) const;
void setXYZ(float x0, float y0, float z0, float x1, float y1, float z1);
void getXYZ(float *x0, float *y0, float *z0, float *x1, float *y1, float *z1) const;
float getZ0() const;
float getZ1() const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,94 @@
/* 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/chapters.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/slice_animations.h"
namespace BladeRunner {
bool Chapters::enterChapter(int chapter) {
// Chapter valid values are: 1 - 5 corresponding to the game Acts.
// Before set, _chapter is 0 (see chapters.h -- The mapped resource id is still 1)
// Resource id is (see _resourceIds in chapters.h):
// 1 for chapter 0, 1 - Act 1
// 2 for chapter 2, 3 - Act 2, 3
// 3 for chapter 4 - Act 4 (see note and code below, 3 can be used for Act 5 too)
// 4 for chapter 5 - Act 5
// Note: For resources that only go up to "3" (TLK, VQA), 3 is used for both Acts 4 and 5.
int id = _resourceIds[chapter];
if (!_vm->_sliceAnimations->openFrames(id))
return false;
if (!_vm->openArchive("A.TLK"))
return false;
if (!_vm->openArchive(Common::String::format("VQA%d.MIX", MIN(id, 3))))
return false;
if (_vm->_cutContent) {
for (int chi = 1; chi < 4; ++chi) {
if (!_vm->isArchiveOpen(Common::String::format("%d.TLK", chi))
&& !_vm->openArchive(Common::String::format("%d.TLK", chi))
) {
return false;
}
}
} else {
if (!_vm->openArchive(Common::String::format("%d.TLK", MIN(id, 3))))
return false;
}
if (!_vm->openArchive(Common::String::format("OUTTAKE%d.MIX", id)))
return false;
_chapter = chapter;
_hasOpenResources = true;
return true;
}
void Chapters::closeResources() {
int id = _resourceIds[_chapter];
#if BLADERUNNER_ORIGINAL_BUGS
_vm->closeArchive("A.TLK");
#else
if (_vm->isArchiveOpen("A.TLK")) {
_vm->closeArchive("A.TLK");
}
#endif // BLADERUNNER_ORIGINAL_BUGS
_vm->closeArchive(Common::String::format("VQA%d.MIX", MIN(id, 3)));
// It's better to try and close every TLK file here (if open), since
// when switching from Restored Content version to Original (due to a save game load)
// TLK files would still remain open -- and should still be closed here
for (int chi = 1; chi < 4; ++chi) {
if (_vm->isArchiveOpen(Common::String::format("%d.TLK", chi))) {
_vm->closeArchive(Common::String::format("%d.TLK", chi));
}
}
_vm->closeArchive(Common::String::format("OUTTAKE%d.MIX", id));
_hasOpenResources = false;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_CHAPTERS_H
#define BLADERUNNER_CHAPTERS_H
namespace BladeRunner {
class BladeRunnerEngine;
class Chapters {
BladeRunnerEngine *_vm;
int _chapter;
int _resourceIds[6];
bool _hasOpenResources;
public:
Chapters(BladeRunnerEngine *vm) {
_vm = vm;
_chapter = 0;
_resourceIds[0] = 1;
_resourceIds[1] = 1;
_resourceIds[2] = 2;
_resourceIds[3] = 2;
_resourceIds[4] = 3;
_resourceIds[5] = 4;
_hasOpenResources = false;
}
bool enterChapter(int chapter);
void closeResources();
bool hasOpenResources() { return _hasOpenResources; }
int currentResourceId() { return _chapter ? _resourceIds[_chapter] : -1; }
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,41 @@
/* 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/color.h"
namespace BladeRunner {
// This array essentially stores the conversion from unsigned 5bit values to 8bit
// ie. ((int)i * 255) / 31 (integer division), for i values 0 to 31
// Note that just using a multiplier 256/16 (= 8) will not properly
// map the color, since eg. value 31 would be mapped to 248 instead of 255.
const uint8 Color::map5BitsTo8Bits[] = {0, 8, 16, 24, 32, 41, 49, 57, 65, 74, 82, 90, 98, 106, 115, 123, 131, 139, 148, 156, 164, 172, 180, 189, 197, 205, 213, 222, 230, 238, 246, 255};
uint8 Color::get8BitColorFrom5Bit(uint8 col5b) {
if (col5b > 31) {
// A value larger than 31 is invalid (never going to happen for 5bits)
// but still catch the case, since the parameter is 8bits
return 255;
}
return map5BitsTo8Bits[col5b];
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,57 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_COLOR_H
#define BLADERUNNER_COLOR_H
#include "common/system.h"
namespace BladeRunner {
class Color {
static const uint8 map5BitsTo8Bits[32];
public:
float r;
float g;
float b;
Color() : r(0.0f), g(0.0f), b(0.0f) {}
Color(float r_, float g_, float b_) : r(r_), g(g_), b(b_) {}
static uint8 get8BitColorFrom5Bit(uint8 col5b);
};
#include "common/pack-start.h"
struct Color256 {
uint8 r;
uint8 g;
uint8 b;
} PACKED_STRUCT;
#include "common/pack-end.h"
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,236 @@
/* 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/combat.h"
#include "bladerunner/actor.h"
#include "bladerunner/audio_speech.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/game_info.h"
#include "bladerunner/movement_track.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/settings.h"
namespace BladeRunner {
Combat::Combat(BladeRunnerEngine *vm) {
_vm = vm;
_coverWaypoints.resize(_vm->_gameInfo->getCoverWaypointCount());
_fleeWaypoints.resize(_vm->_gameInfo->getFleeWaypointCount());
reset();
}
Combat::~Combat() {
}
void Combat::reset() {
_active = false;
_enabled = true;
_ammoDamage[0] = 10;
_ammoDamage[1] = 20;
_ammoDamage[2] = 30;
for (int i = 0; i < kSoundCount; ++i) {
_hitSoundId[i] = -1;
_missSoundId[i] = -1;
}
}
void Combat::activate() {
if (_enabled) {
_vm->_playerActor->combatModeOn(-1, true, -1, -1, kAnimationModeCombatIdle, kAnimationModeCombatWalk, kAnimationModeCombatRun, -1, -1, -1, _vm->_combat->_ammoDamage[_vm->_settings->getAmmoType()], 0, false);
_active = true;
}
}
void Combat::deactivate() {
if (_enabled) {
_vm->_playerActor->combatModeOff();
_active = false;
}
}
void Combat::change() {
if (!_vm->_playerActor->mustReachWalkDestination() && _enabled) {
if (_active) {
deactivate();
} else {
activate();
}
}
}
bool Combat::isActive() const{
return _active;
}
void Combat::enable() {
_enabled = true;
}
void Combat::disable() {
_enabled = false;
}
void Combat::setHitSound(int ammoType, int column, int soundId) {
_hitSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * ammoType + column] = soundId;
}
void Combat::setMissSound(int ammoType, int column, int soundId) {
_missSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * ammoType + column] = soundId;
}
int Combat::getHitSound() const {
return _hitSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * _vm->_settings->getAmmoType() + _vm->_rnd.getRandomNumber(2)];
}
int Combat::getMissSound() const {
return _missSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * _vm->_settings->getAmmoType() + _vm->_rnd.getRandomNumber(2)];
}
void Combat::shoot(int actorId, Vector3 &to, int screenX) {
Actor *actor = _vm->_actors[actorId];
if (actor->isRetired()) {
return;
}
int sentenceId = -1;
/*
Distance from center as a percentage:
screenX - abs(right + left) / 2
distanceFromCenter = 100 * -------------------------------
abs(right - left) / 2
*/
const Common::Rect &rect = actor->getScreenRectangle();
int distanceFromCenter = CLIP(100 * (screenX - abs((rect.right + rect.left) / 2)) / abs((rect.right - rect.left) / 2), 0, 100);
int damage = (100 - distanceFromCenter) * _ammoDamage[_vm->_settings->getAmmoType()] / 100;
int hp = MAX(actor->getCurrentHP() - damage, 0);
actor->setCurrentHP(hp);
bool setDamageAnimation = true;
if (actor->isWalking() == 1 && !actor->getFlagDamageAnimIfMoving()) {
setDamageAnimation = false;
}
if (actor->_movementTrack->hasNext() && !actor->_movementTrack->isPaused()) {
setDamageAnimation = false;
}
if (setDamageAnimation) {
if (actor->isWalking()) {
actor->stopWalking(false);
}
if (actor->getAnimationMode() != kAnimationModeHit && actor->getAnimationMode() != kAnimationModeCombatHit) {
actor->changeAnimationMode(kAnimationModeHit, false);
sentenceId = _vm->_rnd.getRandomNumberRng(0, 1) ? 9000 : 9005;
}
}
if (hp <= 0) {
actor->setTarget(false);
if (actor->inCombat()) {
actor->combatModeOff();
}
#if BLADERUNNER_ORIGINAL_BUGS
#else
// make sure the dead enemy won't pick a pending movement track and re-spawn
actor->_movementTrack->flush();
#endif
actor->stopWalking(false);
actor->changeAnimationMode(kAnimationModeDie, false);
actor->retire(true, 72, 36, kActorMcCoy);
actor->setAtXYZ(actor->getXYZ(), actor->getFacing(), true, false, true);
_vm->_sceneObjects->setRetired(actorId + kSceneObjectOffsetActors, true);
sentenceId = 9020; // Bug or intended? This sentence id (death rattle) won't be used in this case since combat mode is set to off above. Probably intended, in order to use the rattle in a case by case (?)
}
if (sentenceId >= 0 && actor->inCombat()) {
_vm->_audioSpeech->playSpeechLine(actorId, sentenceId, 75, 0, 99);
}
}
int Combat::findFleeWaypoint(int setId, int enemyId, const Vector3& position) const {
float min = -1.0f;
int result = -1;
for (int i = 0; i < (int)_fleeWaypoints.size(); ++i) {
if (setId == _fleeWaypoints[i].setId) {
float dist = distance(position, _fleeWaypoints[i].position);
if (result == -1 || dist < min) {
result = i;
min = dist;
}
}
}
return result;
}
int Combat::findCoverWaypoint(int waypointType, int actorId, int enemyId) const {
Actor *actor = _vm->_actors[actorId];
Actor *enemy = _vm->_actors[enemyId];
int result = -1;
float min = -1.0f;
for (int i = 0; i < (int)_coverWaypoints.size(); ++i) {
if (waypointType == _coverWaypoints[i].type && actor->getSetId() == _coverWaypoints[i].setId) {
if (_vm->_sceneObjects->isObstacleBetween(_coverWaypoints[i].position, enemy->getXYZ(), enemyId)) {
float dist = distance(_coverWaypoints[i].position, actor->getXYZ());
if (result == -1 || dist < min) {
result = i;
min = dist;
}
}
}
}
return result;
}
void Combat::save(SaveFileWriteStream &f) {
f.writeBool(_active);
f.writeBool(_enabled);
for (int i = 0; i != kSoundCount; ++i) {
f.writeInt(_hitSoundId[i]);
}
for (int i = 0; i != kSoundCount; ++i) {
f.writeInt(_missSoundId[i]);
}
}
void Combat::load(SaveFileReadStream &f) {
_active = f.readBool();
_enabled = f.readBool();
for (int i = 0; i != kSoundCount; ++i) {
_hitSoundId[i] = f.readInt();
}
for (int i = 0; i != kSoundCount; ++i) {
_missSoundId[i] = f.readInt();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,99 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_COMBAT_H
#define BLADERUNNER_COMBAT_H
#include "bladerunner/vector.h"
#include "common/array.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class Vector3;
class Combat {
static const int kSoundCount = 9;
BladeRunnerEngine *_vm;
bool _active;
bool _enabled;
int _hitSoundId[kSoundCount];
int _missSoundId[kSoundCount];
// int _random1;
// int _random2;
public:
int _ammoDamage[3];
struct CoverWaypoint {
int type;
int setId;
int sceneId;
Vector3 position;
};
struct FleeWaypoint {
int type;
int setId;
int sceneId;
Vector3 position;
int field7;
};
Common::Array<CoverWaypoint> _coverWaypoints;
Common::Array<FleeWaypoint> _fleeWaypoints;
public:
Combat(BladeRunnerEngine *vm);
~Combat();
void reset();
void activate();
void deactivate();
void change();
bool isActive() const;
void enable();
void disable();
void setHitSound(int ammoType, int column, int soundId);
void setMissSound(int ammoType, int column, int soundId);
int getHitSound() const;
int getMissSound() const;
void shoot(int actorId, Vector3 &to, int screenX);
int findFleeWaypoint(int setId, int enemyId, const Vector3& position) const;
int findCoverWaypoint(int waypointType, int actorId, int enemyId) const;
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine bladerunner "Blade Runner" yes "" "" "16bit highres"

View File

@@ -0,0 +1,6 @@
begin_section("Blade Runner");
add_person("Thanasis Antoniou", "Praetorian", "");
add_person("Thomas Fach-Pedersen", "madmoose", "");
add_person("Peter Kohaut", "peterkohaut", "");
add_person("Eugene Sandulenko", "sev", "");
end_section();

View File

@@ -0,0 +1,87 @@
/* 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/crimes_database.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/savefile.h"
#include "bladerunner/text_resource.h"
#include "bladerunner/game_constants.h"
namespace BladeRunner {
CrimesDatabase::CrimesDatabase(BladeRunnerEngine *vm, const Common::String &cluesResource, int crimeCount) {
_crimeCount = crimeCount;
_crimes.resize(_crimeCount);
_assetTypes.resize(_crimeCount);
_cluesText = new TextResource(vm);
if (!_cluesText->open(cluesResource)) {
delete _cluesText;
return;
}
for (int i = 0; i != _crimeCount; ++i) {
_crimes[i] = -1;
_assetTypes[i] = kClueTypeIntangible;
}
}
CrimesDatabase::~CrimesDatabase() {
delete _cluesText;
}
void CrimesDatabase::setCrime(int clueId, int crimeId) {
_crimes[clueId] = crimeId;
}
int CrimesDatabase::getCrime(int clueId) const {
return _crimes[clueId];
}
void CrimesDatabase::setAssetType(int clueId, int assetType) {
_assetTypes[clueId] = assetType;
}
int CrimesDatabase::getAssetType(int clueId) const {
return _assetTypes[clueId];
}
const char *CrimesDatabase::getClueText(int clueId) const {
return _cluesText->getText(clueId);
}
void CrimesDatabase::save(SaveFileWriteStream &f) {
for (int i = 0; i < _crimeCount; ++i) {
int8 c = _crimes[i];
f.writeSByte(c);
}
}
void CrimesDatabase::load(SaveFileReadStream &f) {
for (int i = 0; i < _crimeCount; ++i) {
_crimes[i] = f.readSByte();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,58 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_CRIMES_DATABASE_H
#define BLADERUNNER_CRIMES_DATABASE_H
#include "common/array.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class TextResource;
class CrimesDatabase {
int _crimeCount;
Common::Array<int> _crimes;
Common::Array<int> _assetTypes;
TextResource *_cluesText;
public:
CrimesDatabase(BladeRunnerEngine *vm, const Common::String &cluesResource, int crimeCount);
~CrimesDatabase();
void setCrime(int clueId, int crimeId);
int getCrime(int clueId) const;
void setAssetType(int clueId, int assetType);
int getAssetType(int clueId) const;
const char *getClueText(int clueId) const;
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
};
} // End of namespace BladeRunner
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_DEBUGGER_H
#define BLADERUNNER_DEBUGGER_H
#include "bladerunner/bladerunner.h" // For BLADERUNNER_ORIGINAL_BUGS symbol
#include "bladerunner/vector.h"
#include "gui/debugger.h"
namespace Graphics {
struct Surface;
}
namespace BladeRunner {
class BladeRunnerEngine;
class View;
enum DebuggerDrawnObjectType {
debuggerObjTypeUndefined = 99,
debuggerObjTypeActor = 0,
debuggerObjType3dObject = 1,
debuggerObjTypeItem = 2,
debuggerObjTypeRegionNormal = 3,
debuggerObjTypeRegionExit = 4,
debuggerObjTypeWaypointNorm = 5,
debuggerObjTypeWaypoingFlee = 6,
debuggerObjTypeWaypointCover = 7,
debuggerObjTypeWalkbox = 8,
debuggerObjTypeEffect = 9,
debuggerObjTypeLight = 10,
debuggerObjTypeFog = 11
};
class Debugger : public GUI::Debugger {
BladeRunnerEngine *_vm;
static const uint kMaxSpecificObjectsDrawnCount = 100;
struct DebuggerDrawnObject {
int sceneId;
int setId;
int objId;
DebuggerDrawnObjectType type;
DebuggerDrawnObject() : sceneId(0), setId(0), objId(0), type(debuggerObjTypeUndefined) {};
};
struct DebuggerPendingOuttake {
bool pending;
int outtakeId;
bool notLocalized;
int container;
Common::String externalFilename;
DebuggerPendingOuttake() : pending(false), outtakeId(-1), notLocalized(true), container(-1), externalFilename("") {};
};
public:
bool _isDebuggerOverlay;
bool _viewActorsToggle;
bool _view3dObjectsToggle;
bool _viewItemsToggle;
bool _viewFogs;
bool _viewLights;
bool _viewScreenEffects;
bool _viewObstacles;
bool _viewRegionsNormalToggle;
bool _viewRegionsExitsToggle;
bool _viewUI;
bool _viewWaypointsNormalToggle;
bool _viewWaypointsFleeToggle;
bool _viewWaypointsCoverToggle;
bool _viewWalkboxes;
bool _viewZBuffer;
bool _playFullVk;
bool _showStatsVk;
bool _showMazeScore;
bool _showMouseClickInfo;
bool _useBetaCrosshairsCursor;
bool _useAdditiveDrawModeForMouseCursorMode0;
bool _useAdditiveDrawModeForMouseCursorMode1;
DebuggerPendingOuttake _dbgPendingOuttake;
Debugger(BladeRunnerEngine *vm);
~Debugger() override;
bool cmdAnimation(int argc, const char **argv);
bool cmdHealth(int argc, const char **argv);
// bool cmdChapter(int argc, const char **argv);
bool cmdDraw(int argc, const char **argv);
bool cmdFlag(int argc, const char **argv);
bool cmdGoal(int argc, const char **argv);
bool cmdLoop(int argc, const char **argv);
bool cmdPosition(int argc, const char **argv);
bool cmdMusic(int argc, const char** argv);
bool cmdSoundFX(int argc, const char** argv);
bool cmdSay(int argc, const char **argv);
bool cmdScene(int argc, const char **argv);
bool cmdVariable(int argc, const char **argv);
bool cmdClue(int argc, const char **argv);
bool cmdTimer(int argc, const char **argv);
bool cmdFriend(int argc, const char **argv);
bool cmdLoad(int argc, const char **argv);
bool cmdSave(int argc, const char **argv);
bool cmdOverlay(int argc, const char **argv);
bool cmdSubtitle(int argc, const char **argv);
bool cmdMazeScore(int argc, const char **argv);
bool cmdObject(int argc, const char **argv);
bool cmdItem(int argc, const char **argv);
bool cmdRegion(int argc, const char **argv);
bool cmdMouse(int argc, const char **argv);
bool cmdDifficulty(int argc, const char **argv);
bool cmdOuttake(int argc, const char** argv);
bool cmdPlayVqa(int argc, const char** argv);
bool cmdAmmo(int argc, const char** argv);
bool cmdCheatReport(int argc, const char** argv);
#if BLADERUNNER_ORIGINAL_BUGS
#else
bool cmdEffect(int argc, const char **argv);
#endif // BLADERUNNER_ORIGINAL_BUGS
bool cmdList(int argc, const char **argv);
bool cmdVk(int argc, const char **argv);
Common::String getDifficultyDescription(int difficultyValue);
Common::String getAmmoTypeDescription(int ammoType);
void drawDebuggerOverlay();
void drawBBox(Vector3 start, Vector3 end, View *view, Graphics::Surface *surface, int color);
void drawSceneObjects();
void drawLights();
void drawFogs();
void drawRegions();
void drawWaypoints();
void drawWalkboxes();
void drawScreenEffects();
bool dbgAttemptToLoadChapterSetScene(int chapterId, int setId, int sceneId);
void resetPendingOuttake();
private:
Common::Array<DebuggerDrawnObject> _specificDrawnObjectsList;
bool _specificActorsDrawn;
bool _specific3dObjectsDrawn;
bool _specificItemsDrawn;
bool _specificEffectsDrawn;
bool _specificLightsDrawn;
bool _specificFogsDrawn;
bool _specificRegionNormalDrawn;
bool _specificRegionExitsDrawn;
bool _specificWaypointNormalDrawn;
bool _specificWaypointFleeDrawn;
bool _specificWaypointCoverDrawn;
bool _specificWalkboxesDrawn;
void toggleObjectInDbgDrawList(DebuggerDrawnObject &drObj);
int findInDbgDrawList(DebuggerDrawnObjectType objType, int objId, int setId, int sceneId);
void updateTogglesForDbgDrawListInCurrentSetAndScene();
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,133 @@
/* 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/decompress_lcw.h"
#include "common/util.h"
namespace BladeRunner {
uint32 decompress_lcw(uint8 *inBuf, uint32 inLen, uint8 *outBuf, uint32 outLen) {
int version = 1;
int count, i, color, pos, relpos;
uint8 *src = inBuf;
uint8 *dst = outBuf;
uint8 *outEnd = dst + outLen;
if (src[0] == 0) {
version = 2;
++src;
}
while (src < inBuf + inLen && dst < outEnd && src[0] != 0x80) {
int out_remain = (int)(outEnd - dst);
if (src[0] == 0xff) { // 0b11111111
count = src[1] | (src[2] << 8);
pos = src[3] | (src[4] << 8);
src += 5;
count = MIN(count, out_remain);
if (version == 1) {
for (i = 0; i < count; ++i)
dst[i] = outBuf[i + pos];
} else {
for (i = 0; i < count; ++i)
dst[i] = *(dst + i - pos);
}
} else if (src[0] == 0xfe) { // 0b11111110
count = src[1] | (src[2] << 8);
color = src[3];
src += 4;
count = MIN(count, out_remain);
memset(dst, color, count);
} else if (src[0] >= 0xc0) { // 0b11??????
count = (src[0] & 0x3f) + 3;
pos = src[1] | (src[2] << 8);
src += 3;
count = MIN(count, out_remain);
if (version == 1) {
for (i = 0; i < count; ++i)
dst[i] = outBuf[i + pos];
} else {
for (i = 0; i < count; ++i)
dst[i] = *(dst + i - pos);
}
} else if (src[0] >= 0x80) { // 0b10??????
count = src[0] & 0x3f;
++src;
count = MIN(count, out_remain);
memcpy(dst, src, count);
src += count;
} else { // 0b0???????
count = ((src[0] & 0x70) >> 4) + 3;
relpos = ((src[0] & 0x0f) << 8) | src[1];
src += 2;
count = MIN(count, out_remain);
for (i = 0; i < count; ++i) {
dst[i] = *(dst + i - relpos);
}
}
dst += count;
}
return uint32(dst - outBuf);
}
uint32 decompress_lcw_output_size(uint8 *inBuf, uint32 inLen) {
int count;
uint8 *src = inBuf;
uint32 outsize = 0;
if (src[0] == 0)
++src;
while (src[0] != 0x80 && src < inBuf + inLen) {
if (src[0] == 0xff) { // 0b11111111
count = src[1] | (src[2] << 8);
src += 5;
} else if (src[0] == 0xfe) { // 0b11111110
count = src[1] | (src[2] << 8);
src += 4;
} else if (src[0] >= 0xc0) { // 0b11??????
count = (src[0] & 0x3f) + 3;
src += 3;
} else if (src[0] & 0x80) { // 0b10??????
count = src[0] & 0x3f;
src += count + 1;
} else { // 0b0???????
count = ((src[0] & 0x70) >> 4) + 3;
src += 2;
}
outsize += count;
}
return outsize;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,34 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_DECOMPRESS_LCW_H
#define BLADERUNNER_DECOMPRESS_LCW_H
#include "common/types.h"
namespace BladeRunner {
uint32 decompress_lcw(uint8 *inBuf, uint32 inLen, uint8 *outBuf, uint32 outLen);
uint32 decompress_lcw_output_size(void *inBuf, uint32 inLen);
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,133 @@
/* 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/decompress_lzo.h"
namespace BladeRunner {
static inline uint32 decode_count(const uint8 **pp) {
uint32 v = 0;
for (; !**pp; ++(*pp))
v += 255;
v += **pp;
++(*pp);
return v;
}
static inline void copy(uint8 **dst, const uint8 **src, int count) {
assert(count > 0);
uint8 *d = *dst;
const uint8 *s = *src;
*dst += count;
*src += count;
do { *d++ = *s++; } while (--count);
}
int decompress_lzo1x(const uint8 *in, size_t inLen, uint8 *out, size_t *outLen) {
uint32 t;
uint8 *op;
const uint8 *ip, *m_pos;
const uint8 * const ip_end = in + inLen;
*outLen = 0;
op = out;
ip = in;
if (*ip > 17) {
t = *ip++ - 17;
if (t < 4)
goto match_next;
copy(&op, &ip, t);
goto first_literal_run;
}
for (;;) {
t = *ip++;
if (t >= 16)
goto match;
if (t == 0)
t = 15 + decode_count(&ip);
copy(&op, &ip, t + 3);
first_literal_run:
t = *ip++;
if (t >= 16)
goto match;
m_pos = op - 0x0801 - (t >> 2) - (*ip++ << 2);
copy(&op, &m_pos, 3);
goto match_done;
for (;;) {
match:
if (t >= 64) {
m_pos = op - 1 - ((t >> 2) & 7) - (*ip++ << 3);
t = (t >> 5) - 1;
goto copy_match;
} else if (t >= 32) {
t &= 31;
if (t == 0)
t = 31 + decode_count(&ip);
m_pos = op - 1 - (ip[0] >> 2) - (ip[1] << 6);
ip += 2;
} else if (t >= 16) {
m_pos = op - ((t & 8) << 11);
t &= 7;
if (t == 0)
t = 7 + decode_count(&ip);
m_pos -= (ip[0] >> 2) + (ip[1] << 6);
ip += 2;
if (m_pos == op)
goto eof_found;
m_pos -= 0x4000;
} else {
m_pos = op - 1 - (t >> 2) - (*ip++ << 2);
copy(&op, &m_pos, 2);
goto match_done;
}
copy_match:
copy(&op, &m_pos, t + 2);
match_done:
t = ip[-2] & 3;
if (t == 0)
break;
match_next:
assert(t > 0 && t <= 3);
copy(&op, &ip, t);
t = *ip++;
}
}
eof_found:
*outLen = op - out;
return ip != ip_end;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,33 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_DECOMPRESS_LZO_H
#define BLADERUNNER_DECOMPRESS_LZO_H
#include "common/types.h"
namespace BladeRunner {
int decompress_lzo1x(const uint8 *in, size_t inLen, uint8 *out, size_t *outLen);
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,99 @@
/* 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/detection_tables.h"
#include "bladerunner/savefile.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/serializer.h"
#include "engines/advancedDetector.h"
static const DebugChannelDef debugFlagList[] = {
{BladeRunner::kDebugScript, "Script", "Debug the scripts"},
{BladeRunner::kDebugSound, "Sound", "Debug the sound"},
{BladeRunner::kDebugAnimation, "Animation", "Debug the model animations"},
DEBUG_CHANNEL_END
};
namespace BladeRunner {
static const PlainGameDescriptor bladeRunnerGames[] = {
{"bladerunner", "Blade Runner"},
{"bladerunner-final", "Blade Runner with restored content"},
{"bladerunner-ee", "Blade Runner: Enhanced Edition"},
{nullptr, nullptr}
};
static const char *const directoryGlobs[] = {
"BASE",
nullptr
};
} // End of namespace BladeRunner
class BladeRunnerMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
public:
BladeRunnerMetaEngineDetection();
const char *getName() const override;
const char *getEngineName() const override;
const char *getOriginalCopyright() const override;
const DebugChannelDef *getDebugChannels() const override;
};
BladeRunnerMetaEngineDetection::BladeRunnerMetaEngineDetection()
: AdvancedMetaEngineDetection(
BladeRunner::gameDescriptions,
BladeRunner::bladeRunnerGames) {
// Setting this, allows the demo files to be copied in the BladeRunner
// game data folder and be detected and subsequently launched without
// any issues (eg. like ScummVM launching Blade Runner instead of the demo).
// Although the demo files are not part of the original game's installation
// or CD content, it's nice to support the use case whereby the user
// manually copies the demo files in the Blade Runner game data folder
// and expects ScummVM to detect both, offer a choice on which to add,
// and finally launch the proper one depending on which was added.
_flags = kADFlagUseExtraAsHint;
_maxScanDepth = 2;
_directoryGlobs = BladeRunner::directoryGlobs;
}
const char *BladeRunnerMetaEngineDetection::getName() const {
return "bladerunner";
}
const char *BladeRunnerMetaEngineDetection::getEngineName() const {
return "Blade Runner";
}
const char *BladeRunnerMetaEngineDetection::getOriginalCopyright() const {
return "Blade Runner (C) 1997 Westwood Studios";
}
const DebugChannelDef *BladeRunnerMetaEngineDetection::getDebugChannels() const {
return debugFlagList;
}
REGISTER_PLUGIN_STATIC(BLADERUNNER_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, BladeRunnerMetaEngineDetection);

View File

@@ -0,0 +1,33 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_DETECTION_H
#define BLADERUNNER_DETECTION_H
#define GAMEOPTION_SITCOM GUIO_GAMEOPTIONS1
#define GAMEOPTION_SHORTY GUIO_GAMEOPTIONS2
#define GAMEOPTION_FRAMELIMITER_NODELAYMILLIS GUIO_GAMEOPTIONS3
#define GAMEOPTION_FRAMELIMITER_FPS GUIO_GAMEOPTIONS4
#define GAMEOPTION_DISABLE_STAMINA_DRAIN GUIO_GAMEOPTIONS5
#define GAMEOPTION_SHOW_SUBS_IN_CRAWL GUIO_GAMEOPTIONS6
#define GAMEOPTION_FIX_SPANISH_CREDITS GUIO_GAMEOPTIONS7
#endif

View File

@@ -0,0 +1,253 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_DETECTION_TABLES_H
#define BLADERUNNER_DETECTION_TABLES_H
#include "common/translation.h"
#include "engines/advancedDetector.h"
#include "bladerunner/detection.h"
namespace BladeRunner {
static const ADGameDescription gameDescriptions[] = {
// BladeRunner (English) - ENG
{
"bladerunner",
"",
AD_ENTRY1s("STARTUP.MIX", "5643b53306ca7764cf1ec7b79c9630a3", 2312374),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (German) - DEU
{
"bladerunner",
"",
AD_ENTRY1s("STARTUP.MIX", "57d674ed860148a530b7f4957cbe65ec", 2314301),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (French) - FRA - Bug #9722
{
"bladerunner",
"",
AD_ENTRY1s("STARTUP.MIX", "39d1901df50935d58aee252707134952", 2314526),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Italian) - ITA
{
"bladerunner",
"",
AD_ENTRY1s("STARTUP.MIX", "c7ceb9c691223d25e78516aa519ff504", 2314461),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Russian - Fargus Multimedia + Home Systems, Inc.) - RUS
{
"bladerunner",
"",
AD_ENTRY1s("STARTUP.MIX", "bf42af841d9f4b643665013a348c81e0", 2483111),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Russian - Fargus Multimedia + Home Systems, Inc. + Siberian Studio, R3) - RUS
{
"bladerunner",
_s("The fan translator does not wish his translation to be incorporated into ScummVM."),
AD_ENTRY1s("STARTUP.MIX", "c198b54a5366b88b1734bbca21d3b192", 2678672),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Russian - Fargus Multimedia + Home Systems, Inc. + Siberian Studio, R4) - RUS
{
"bladerunner",
_s("The fan translator does not wish his translation to be incorporated into ScummVM."),
AD_ENTRY1s("STARTUP.MIX", "d62498a7415682bb3ff86a894303c836", 2810053),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Spanish) - ESP
{
"bladerunner",
"",
AD_ENTRY1s("STARTUP.MIX", "54cad53da9e4ae03a85648834ac6765d", 2312976),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO8(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GAMEOPTION_FIX_SPANISH_CREDITS, GUIO_NOMIDI)
},
// BladeRunner (Chinese)
{
"bladerunner",
MetaEngineDetection::GAME_NOT_IMPLEMENTED, // Reason for being unsupported
AD_ENTRY2s("STARTUP.MIX", "c198b54a5366b88b1734bbca21d3b192", 2678672,
"95blade.dll", "187f257c3183d6b0a0aee69e5cde4c76", 307200),
Common::ZH_CHN,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner - Enhanced Edition
{
"bladerunner-ee",
// I18N: Blade Runner Enhanced Edition is a trademark, so please keep the capitalization
// for Enhanced Edition as is.
_s("Enhanced Edition not supported. Please, use the original files which are part of the release."),
AD_ENTRY1s("BladeRunner.kpf", "c42766a9337c828f0d98383f72636fb3", 1163268364),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// Versions with restored content
// BladeRunner (English) - ENG
{
"bladerunner-final",
"",
AD_ENTRY1s("STARTUP.MIX", "5643b53306ca7764cf1ec7b79c9630a3", 2312374),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (German) - DEU
{
"bladerunner-final",
"",
AD_ENTRY1s("STARTUP.MIX", "57d674ed860148a530b7f4957cbe65ec", 2314301),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (French) - FRA
{
"bladerunner-final",
"",
AD_ENTRY1s("STARTUP.MIX", "39d1901df50935d58aee252707134952", 2314526),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Italian) - ITA
{
"bladerunner-final",
"",
AD_ENTRY1s("STARTUP.MIX", "c7ceb9c691223d25e78516aa519ff504", 2314461),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Russian - Fargus Multimedia + Home Systems, Inc.) - RUS
{
"bladerunner-final",
"",
AD_ENTRY1s("STARTUP.MIX", "bf42af841d9f4b643665013a348c81e0", 2483111),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Russian - Fargus Multimedia + Home Systems, Inc. + Siberian Studio, R3) - RUS
{
"bladerunner-final",
_s("The fan translator does not wish his translation to be incorporated into ScummVM."),
AD_ENTRY1s("STARTUP.MIX", "c198b54a5366b88b1734bbca21d3b192", 2678672),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Russian - Fargus Multimedia + Home Systems, Inc. + Siberian Studio, R4) - RUS
{
"bladerunner-final",
_s("The fan translator does not wish his translation to be incorporated into ScummVM."),
AD_ENTRY1s("STARTUP.MIX", "d62498a7415682bb3ff86a894303c836", 2810053),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUIO7(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GUIO_NOMIDI)
},
// BladeRunner (Spanish) - ESP
{
"bladerunner-final",
"",
AD_ENTRY1s("STARTUP.MIX", "54cad53da9e4ae03a85648834ac6765d", 2312976),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO8(GAMEOPTION_SITCOM, GAMEOPTION_SHORTY, GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GAMEOPTION_DISABLE_STAMINA_DRAIN, GAMEOPTION_SHOW_SUBS_IN_CRAWL, GAMEOPTION_FIX_SPANISH_CREDITS, GUIO_NOMIDI)
},
// Demo Version(s)
// BladeRunner (VQA/VQP Teaser) - Non-interactive demo
{
"bladerunner",
"Non-Interactive Demo",
AD_ENTRY1s("SIZZLE2.VQA", "2979892996f9f6fafb06c0cd72fc1025", 14386668),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO3(GAMEOPTION_FRAMELIMITER_NODELAYMILLIS, GAMEOPTION_FRAMELIMITER_FPS, GUIO_NOMIDI)
},
AD_TABLE_END_MARKER
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,571 @@
/* 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/dialogue_menu.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/font.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/mouse.h"
#include "bladerunner/savefile.h"
#include "bladerunner/settings.h"
#include "bladerunner/shape.h"
#include "bladerunner/text_resource.h"
#include "common/debug.h"
#include "common/rect.h"
#include "common/util.h"
namespace BladeRunner {
DialogueMenu::DialogueMenu(BladeRunnerEngine *vm) {
_vm = vm;
reset();
_textResource = new TextResource(_vm);
_shapes = new Shapes(_vm);
_screenX = 0;
_screenY = 0;
_maxItemWidth = 0;
_fadeInItemIndex = 0;
}
DialogueMenu::~DialogueMenu() {
delete _textResource;
delete _shapes;
}
bool DialogueMenu::loadResources() {
bool r = _textResource->open("DLGMENU");
if (!r) {
error("Failed to load dialogue menu text");
}
r = _shapes->load("DIALOG.SHP");
if (!r) {
error("Failed to load dialogue menu shapes");
}
return r;
}
bool DialogueMenu::show() {
int x, y;
_vm->_mouse->getXY(&x, &y);
return showAt(x, y);
}
bool DialogueMenu::showAt(int x, int y) {
if (_isVisible) {
return false;
}
_isVisible = true;
_selectedItemIndex = 0;
_centerX = x;
_centerY = y;
calculatePosition(x, y);
return true;
}
bool DialogueMenu::hide() {
_waitingForInput = false;
if (!_isVisible) {
return false;
}
_isVisible = false;
return true;
}
bool DialogueMenu::clearList() {
_selectedItemIndex = -1;
_listSize = 0;
return true;
}
bool DialogueMenu::addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly) {
if (_listSize >= kMaxItems) {
return false;
}
if (getAnswerIndex(answer) != -1) {
return false;
}
#if BLADERUNNER_ORIGINAL_BUGS
// Original uses incorrect spelling for entry id 1020: DRAGONFLY JEWERLY
const Common::String &text = _textResource->getText(answer);
#else
const char *answerTextCP = _textResource->getText(answer);
if (_vm->_language == Common::EN_ANY && answer == 1020 && strcmp(answerTextCP, "DRAGONFLY JEWERLY") == 0) {
// fix spelling or entry id 1020 to DRAGONFLY JEWELRY in English version
answerTextCP = "DRAGONFLY JEWELRY";
} else if (_vm->_language == Common::IT_ITA && answer == 180 && strcmp(answerTextCP, "AUTOMOBILI") == 0) {
// fix bad dialogue entry id 180 for Italian version.
// The entry is supposed to be Grigorian's organization name (which is CARS in English, but CCSR in Italian)
// However, the original localization has it as "AUTOMOBILI" which is literally "cars" and results in confusion.
// The other official localizations do not have this issue.
answerTextCP = "C.C.S.R.";
}
const Common::String &text = answerTextCP;
#endif // BLADERUNNER_ORIGINAL_BUGS
if (text.empty() || text.size() >= 50) {
return false;
}
int index = _listSize++;
_items[index].text = text;
_items[index].answerValue = answer;
_items[index].colorIntensity = 0;
_items[index].isDone = done;
_items[index].priorityPolite = priorityPolite;
_items[index].priorityNormal = priorityNormal;
_items[index].prioritySurly = prioritySurly;
// CHECK(madmoose): BLADE.EXE calls this needlessly
// calculatePosition();
return true;
}
/**
* Aux function - used in cut content mode to re-use some NeverRepeatOnceSelected dialogue options for different characters
*/
bool DialogueMenu::clearNeverRepeatWasSelectedFlag(int answer) {
int foundIndex = -1;
for (int i = 0; i != _neverRepeatListSize; ++i) {
if (answer == _neverRepeatValues[i]) {
foundIndex = i;
break;
}
}
if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) {
_neverRepeatWasSelected[foundIndex] = false;
return true;
}
return false;
}
bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly) {
int foundIndex = -1;
for (int i = 0; i != _neverRepeatListSize; ++i) {
if (answer == _neverRepeatValues[i]) {
foundIndex = i;
break;
}
}
if (foundIndex >= 0 && _neverRepeatWasSelected[foundIndex]) {
return true;
}
if (foundIndex == -1) {
_neverRepeatValues[_neverRepeatListSize] = answer;
_neverRepeatWasSelected[_neverRepeatListSize] = false;
++_neverRepeatListSize;
assert(_neverRepeatListSize <= 100);
}
return addToList(answer, false, priorityPolite, priorityNormal, prioritySurly);
}
bool DialogueMenu::removeFromList(int answer) {
int index = getAnswerIndex(answer);
if (index < 0) {
return false;
}
if (index < _listSize - 1) {
for (int i = index; i < _listSize; ++i) {
_items[index] = _items[index + 1];
}
}
--_listSize;
calculatePosition();
return true;
}
int DialogueMenu::queryInput() {
if (!_isVisible || _listSize == 0) {
return -1;
}
int answer = -1;
if (_listSize == 1) {
_selectedItemIndex = 0;
answer = _items[_selectedItemIndex].answerValue;
} else if (_listSize == 2) {
#if BLADERUNNER_ORIGINAL_BUGS
if (_items[0].isDone) {
_selectedItemIndex = 1;
answer = _items[_selectedItemIndex].answerValue;
} else if (_items[1].isDone) {
_selectedItemIndex = 0;
answer = _items[_selectedItemIndex].answerValue;
}
#else
// In User Choice mode, avoid auto-select of last option
// In this mode, player should still have agency to skip the last (non- "DONE")
// question instead of automatically asking it because the other remaining option is "DONE"
if (_vm->_settings->getPlayerAgenda() != kPlayerAgendaUserChoice) {
if (_items[0].isDone) {
_selectedItemIndex = 1;
answer = _items[_selectedItemIndex].answerValue;
} else if (_items[1].isDone) {
_selectedItemIndex = 0;
answer = _items[_selectedItemIndex].answerValue;
}
}
#endif // BLADERUNNER_ORIGINAL_BUGS
}
if (answer == -1) {
int agenda = _vm->_settings->getPlayerAgenda();
if (agenda == kPlayerAgendaUserChoice) {
_waitingForInput = true;
do {
while (!_vm->playerHasControl()) {
_vm->playerGainsControl();
}
while (_vm->_mouse->isDisabled()) {
_vm->_mouse->enable();
}
_vm->gameTick();
} while (_vm->_gameIsRunning && _waitingForInput);
} else if (agenda == kPlayerAgendaErratic) {
int tries = 0;
bool searching = true;
int i;
do {
i = _vm->_rnd.getRandomNumber(_listSize - 1);
if (!_items[i].isDone) {
searching = false;
} else if (++tries > 1000) {
searching = false;
i = 0;
}
} while (searching);
_selectedItemIndex = i;
} else {
int priority = -1;
for (int i = 0; i < _listSize; ++i) {
int priorityCompare = -1;
if (agenda == kPlayerAgendaPolite) {
priorityCompare = _items[i].priorityPolite;
} else if (agenda == kPlayerAgendaNormal) {
priorityCompare = _items[i].priorityNormal;
} else if (agenda == kPlayerAgendaSurly) {
priorityCompare = _items[i].prioritySurly;
}
if (priority < priorityCompare) {
priority = priorityCompare;
_selectedItemIndex = i;
}
}
}
}
answer = _items[_selectedItemIndex].answerValue;
for (int i = 0; i != _neverRepeatListSize; ++i) {
if (answer == _neverRepeatValues[i]) {
_neverRepeatWasSelected[i] = true;
break;
}
}
// debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text.c_str());
return answer;
}
int DialogueMenu::listSize() const {
return _listSize;
}
bool DialogueMenu::isVisible() const {
return _isVisible;
}
bool DialogueMenu::isOpen() const {
return _isVisible || _waitingForInput;
}
void DialogueMenu::tick(int x, int y) {
if (!_isVisible || _listSize == 0) {
return;
}
int line = (y - (_screenY + kBorderSize)) / kLineHeight;
line = CLIP(line, 0, _listSize - 1);
_selectedItemIndex = line;
}
void DialogueMenu::draw(Graphics::Surface &s) {
if (!_isVisible || _listSize == 0) {
return;
}
int fadeInItemIndex = _fadeInItemIndex;
if (fadeInItemIndex < listSize()) {
++_fadeInItemIndex;
}
for (int i = 0; i != _listSize; ++i) {
int targetColorIntensity = 0;
if (i == _selectedItemIndex) {
targetColorIntensity = 31;
} else {
targetColorIntensity = 16;
}
if (i > fadeInItemIndex) {
targetColorIntensity = 0;
}
if (_items[i].colorIntensity < targetColorIntensity) {
_items[i].colorIntensity += 4;
if (_items[i].colorIntensity > targetColorIntensity) {
_items[i].colorIntensity = targetColorIntensity;
}
} else if (_items[i].colorIntensity > targetColorIntensity) {
_items[i].colorIntensity -= 2;
if (_items[i].colorIntensity < targetColorIntensity) {
_items[i].colorIntensity = targetColorIntensity;
}
}
}
const int x1 = _screenX;
const int y1 = _screenY;
const int x2 = _screenX + kBorderSize + _maxItemWidth;
const int y2 = _screenY + kBorderSize + _listSize * kLineHeight;
darkenRect(s, x1 + 8, y1 + 8, x2 + 2, y2 + 2);
int x = x1 + kBorderSize;
int y = y1 + kBorderSize;
Common::Point mouse = _vm->getMousePos();
if (mouse.x >= x && mouse.x < x2) {
s.vLine(mouse.x, y1 + 8, y2 + 2, s.format.RGBToColor(64, 64, 64));
}
if (mouse.y >= y && mouse.y < y2) {
s.hLine(x1 + 8, mouse.y, x2 + 2, s.format.RGBToColor(64, 64, 64));
}
_shapes->get(0)->draw(s, x1, y1);
_shapes->get(3)->draw(s, x2, y1);
_shapes->get(2)->draw(s, x1, y2);
_shapes->get(5)->draw(s, x2, y2);
for (int i = 0; i != _listSize; ++i) {
_shapes->get(1)->draw(s, x1, y);
_shapes->get(4)->draw(s, x2, y);
uint32 color = s.format.RGBToColor((_items[i].colorIntensity / 2) * (256 / 32), (_items[i].colorIntensity / 2) * (256 / 32), _items[i].colorIntensity * (256 / 32));
_vm->_mainFont->drawString(&s, _items[i].text, x, y, s.w, color);
y += kLineHeight;
}
for (; x != x2; ++x) {
_shapes->get(6)->draw(s, x, y1);
_shapes->get(7)->draw(s, x, y2);
}
}
int DialogueMenu::getAnswerIndex(int answer) const {
for (int i = 0; i != _listSize; ++i) {
if (_items[i].answerValue == answer) {
return i;
}
}
return -1;
}
const char *DialogueMenu::getText(int id) const {
return _textResource->getText((uint32)id);
}
void DialogueMenu::calculatePosition(int unusedX, int unusedY) {
_maxItemWidth = 0;
for (int i = 0; i != _listSize; ++i) {
_maxItemWidth = MAX(_maxItemWidth, _vm->_mainFont->getStringWidth(_items[i].text));
}
_maxItemWidth += 2;
int w = kBorderSize + _shapes->get(4)->getWidth() + _maxItemWidth;
int h = kBorderSize + _shapes->get(7)->getHeight() + kLineHeight * _listSize;
_screenX = _centerX - w / 2;
_screenY = _centerY - h / 2;
_screenX = CLIP(_screenX, 0, BladeRunnerEngine::kOriginalGameWidth - w);
_screenY = CLIP(_screenY, 0, BladeRunnerEngine::kOriginalGameHeight - h);
_fadeInItemIndex = 0;
}
void DialogueMenu::mouseUp() {
_waitingForInput = false;
}
bool DialogueMenu::waitingForInput() const {
return _waitingForInput;
}
void DialogueMenu::save(SaveFileWriteStream &f) {
f.writeBool(_isVisible);
f.writeBool(_waitingForInput);
f.writeInt(_selectedItemIndex);
f.writeInt(_listSize);
f.writeInt(_neverRepeatListSize);
for (int i = 0; i < 100; ++i) {
f.writeInt(_neverRepeatValues[i]);
}
for (int i = 0; i < 100; ++i) {
f.writeBool(_neverRepeatWasSelected[i]);
}
for (int i = 0; i < 10; ++i) {
f.writeStringSz(_items[i].text, 50);
f.writeInt(_items[i].answerValue);
f.writeInt(_items[i].colorIntensity);
f.writeInt(_items[i].priorityPolite);
f.writeInt(_items[i].priorityNormal);
f.writeInt(_items[i].prioritySurly);
f.writeInt(_items[i].isDone);
}
}
void DialogueMenu::load(SaveFileReadStream &f) {
_isVisible = f.readBool();
_waitingForInput = f.readBool();
_selectedItemIndex = f.readInt();
_listSize = f.readInt();
#if 0
/* fix for duplicated non-repeated entries in the save game */
f.readInt();
_neverRepeatListSize = 0;
int answer[100];
bool selected[100];
for (int i = 0; i < 100; ++i) {
_neverRepeatValues[i] = -1;
answer[i] = f.readInt();
}
for (int i = 0; i < 100; ++i) {
_neverRepeatWasSelected[i] = false;
selected[i] = f.readBool();
}
for (int i = 0; i < 100; ++i) {
int found = false;
bool value = false;
for (int j = 0; j < 100; ++j) {
if (_neverRepeatValues[j] == answer[i]) {
found = true;
}
if (answer[j] == answer[i]) {
value |= selected[j];
}
}
if (!found) {
_neverRepeatValues[_neverRepeatListSize] = answer[i];
_neverRepeatWasSelected[_neverRepeatListSize] = value;
++_neverRepeatListSize;
}
}
#else
_neverRepeatListSize = f.readInt();
for (int i = 0; i < 100; ++i) {
_neverRepeatValues[i] = f.readInt();
}
for (int i = 0; i < 100; ++i) {
_neverRepeatWasSelected[i] = f.readBool();
}
#endif
for (int i = 0; i < 10; ++i) {
_items[i].text = f.readStringSz(50);
_items[i].answerValue = f.readInt();
_items[i].colorIntensity = f.readInt();
_items[i].priorityPolite = f.readInt();
_items[i].priorityNormal = f.readInt();
_items[i].prioritySurly = f.readInt();
_items[i].isDone = f.readInt();
}
}
void DialogueMenu::clear() {
_isVisible = false;
_waitingForInput = false;
_selectedItemIndex = 0;
_listSize = 0;
for (int i = 0; i != kMaxItems; ++i) {
_items[i].text.clear();
_items[i].answerValue = -1;
_items[i].isDone = 0;
_items[i].priorityPolite = -1;
_items[i].priorityNormal = -1;
_items[i].prioritySurly = -1;
_items[i].colorIntensity = 0;
}
_neverRepeatListSize = 0;
for (int i = 0; i != kMaxRepeatHistory; ++i) {
_neverRepeatValues[i] = -1;
_neverRepeatWasSelected[i] = false;
}
_centerX = 0;
_centerY = 0;
}
void DialogueMenu::reset() {
clear();
_textResource = nullptr;
}
void DialogueMenu::darkenRect(Graphics::Surface &s, int x1, int y1, int x2, int y2) {
x1 = MAX(x1, 0);
y1 = MAX(y1, 0);
x2 = MIN<int32>(x2, BladeRunnerEngine::kOriginalGameWidth);
y2 = MIN<int32>(y2, BladeRunnerEngine::kOriginalGameHeight);
if (x1 < x2 && y1 < y2) {
for (int y = y1; y != y2; ++y) {
for (int x = x1; x != x2; ++x) {
void *p = s.getBasePtr(CLIP(x, 0, s.w - 1), CLIP(y, 0, s.h - 1));
uint8 r, g, b;
s.format.colorToRGB(READ_UINT32(p), r, g, b);
r /= 4;
g /= 4;
b /= 4;
drawPixel(s, p, s.format.RGBToColor(r, g, b));
}
}
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,119 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_DIALOGUE_MENU_H
#define BLADERUNNER_DIALOGUE_MENU_H
#include "bladerunner/shape.h"
#include "common/array.h"
#include "common/str.h"
#include "graphics/surface.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class TextResource;
class DialogueMenu {
static const int kMaxItems = 10;
static const int kMaxRepeatHistory = 100;
static const int kLineHeight = 9;
static const int kBorderSize = 10;
struct DialogueItem {
Common::String text;
int answerValue;
int colorIntensity;
int priorityPolite;
int priorityNormal;
int prioritySurly;
int isDone;
};
BladeRunnerEngine *_vm;
TextResource *_textResource;
Shapes *_shapes;
bool _isVisible;
bool _waitingForInput;
int _selectedItemIndex;
int _listSize;
// These track whether a dialogue option
// has previously been selected
int _neverRepeatListSize;
int _neverRepeatValues[kMaxRepeatHistory];
bool _neverRepeatWasSelected[kMaxRepeatHistory];
int _centerX;
int _centerY;
int _screenX;
int _screenY;
int _maxItemWidth;
DialogueItem _items[kMaxItems];
int _fadeInItemIndex;
public:
DialogueMenu(BladeRunnerEngine *vm);
~DialogueMenu();
void clear();
bool loadResources();
bool show();
bool hide();
bool addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly);
bool clearNeverRepeatWasSelectedFlag(int answer); // aux function - used in cut content mode to re-use some dialogue options for different characters
bool addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly);
bool removeFromList(int answer);
bool clearList();
int queryInput();
int listSize() const;
bool isVisible() const;
bool isOpen() const;
void tick(int x, int y);
void draw(Graphics::Surface &s);
void mouseUp();
bool waitingForInput() const;
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
bool showAt(int x, int y);
int getAnswerIndex(int answer) const;
const char *getText(int id) const;
void calculatePosition(int unusedX = 0, int unusedY = 0);
void reset();
static void darkenRect(Graphics::Surface &s, int x1, int y1, int x2, int y2);
};
} // End of namespace BladeRunner
#endif

348
engines/bladerunner/fog.cpp Normal file
View File

@@ -0,0 +1,348 @@
/* 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/fog.h"
#include "common/stream.h"
namespace BladeRunner {
Fog::Fog() {
_frameCount = 0;
_animatedParameters = 0;
_fogDensity = 0.0f;
_animationData = nullptr;
_m11ptr = nullptr;
_m12ptr = nullptr;
_m13ptr = nullptr;
_m14ptr = nullptr;
_m21ptr = nullptr;
_m22ptr = nullptr;
_m23ptr = nullptr;
_m24ptr = nullptr;
_m31ptr = nullptr;
_m32ptr = nullptr;
_m33ptr = nullptr;
_m34ptr = nullptr;
_next = nullptr;
}
Fog::~Fog() {
if (_animationData != nullptr) {
delete[] _animationData;
}
}
int Fog::readCommon(Common::ReadStream *stream) {
int offset = stream->readUint32LE();
char buf[20];
stream->read(buf, sizeof(buf));
_name = buf;
_fogColor.r = stream->readFloatLE();
_fogColor.g = stream->readFloatLE();
_fogColor.b = stream->readFloatLE();
_fogDensity = stream->readFloatLE();
return offset;
}
void Fog::readAnimationData(Common::ReadStream *stream, int size) {
_animatedParameters = stream->readUint32LE();
if (_animationData != nullptr) {
delete[] _animationData;
}
int floatCount = size / 4;
_animationData = new float[floatCount];
for (int i = 0; i < floatCount; ++i) {
_animationData[i] = stream->readFloatLE();
}
_m11ptr = _animationData;
_m12ptr = _m11ptr + ((_animatedParameters & 0x1) ? _frameCount : 1);
_m13ptr = _m12ptr + ((_animatedParameters & 0x2) ? _frameCount : 1);
_m14ptr = _m13ptr + ((_animatedParameters & 0x4) ? _frameCount : 1);
_m21ptr = _m14ptr + ((_animatedParameters & 0x8) ? _frameCount : 1);
_m22ptr = _m21ptr + ((_animatedParameters & 0x10) ? _frameCount : 1);
_m23ptr = _m22ptr + ((_animatedParameters & 0x20) ? _frameCount : 1);
_m24ptr = _m23ptr + ((_animatedParameters & 0x40) ? _frameCount : 1);
_m31ptr = _m24ptr + ((_animatedParameters & 0x80) ? _frameCount : 1);
_m32ptr = _m31ptr + ((_animatedParameters & 0x100) ? _frameCount : 1);
_m33ptr = _m32ptr + ((_animatedParameters & 0x200) ? _frameCount : 1);
_m34ptr = _m33ptr + ((_animatedParameters & 0x400) ? _frameCount : 1);
setupFrame(0);
}
void Fog::reset() {
}
void Fog::setupFrame(int frame) {
int offset = frame % _frameCount;
_matrix._m[0][0] = ((_animatedParameters & 0x1) ? _m11ptr[offset] : *_m11ptr);
_matrix._m[0][1] = ((_animatedParameters & 0x2) ? _m12ptr[offset] : *_m12ptr);
_matrix._m[0][2] = ((_animatedParameters & 0x4) ? _m13ptr[offset] : *_m13ptr);
_matrix._m[0][3] = ((_animatedParameters & 0x8) ? _m14ptr[offset] : *_m14ptr);
_matrix._m[1][0] = ((_animatedParameters & 0x10) ? _m21ptr[offset] : *_m21ptr);
_matrix._m[1][1] = ((_animatedParameters & 0x20) ? _m22ptr[offset] : *_m22ptr);
_matrix._m[1][2] = ((_animatedParameters & 0x40) ? _m23ptr[offset] : *_m23ptr);
_matrix._m[1][3] = ((_animatedParameters & 0x80) ? _m24ptr[offset] : *_m24ptr);
_matrix._m[2][0] = ((_animatedParameters & 0x100) ? _m31ptr[offset] : *_m31ptr);
_matrix._m[2][1] = ((_animatedParameters & 0x200) ? _m32ptr[offset] : *_m32ptr);
_matrix._m[2][2] = ((_animatedParameters & 0x400) ? _m33ptr[offset] : *_m33ptr);
_matrix._m[2][3] = ((_animatedParameters & 0x800) ? _m34ptr[offset] : *_m34ptr);
_inverted = invertMatrix(_matrix);
}
void FogSphere::read(Common::ReadStream *stream, int frameCount) {
_frameCount = frameCount;
int size = readCommon(stream);
float radius = stream->readFloatLE();
_radius_sq = radius * radius;
readAnimationData(stream, size - 52);
}
void FogSphere::calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) {
*coeficient = 0.0f;
// Ray - sphere intersection, where sphere center is always at 0, 0, 0 as everything else transformed by the fog matrix.
// Quadratic formula can and was simplified because rayDirection is normalized and hence a = 1.
// Explained on wikipedia https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
// There is also alternative approach which will end-up with this formula where plane is created from ray origin, ray destination
// and sphere center, then there is only need to solve two right triangles.
// Explained in book Andrew S. Glassner (1995), Graphics Gems I (p. 388-389)
Vector3 rayOrigin = _matrix * position;
Vector3 rayDestination = _matrix * viewPosition;
Vector3 rayDirection = (rayDestination - rayOrigin).normalize();
float b = Vector3::dot(rayDirection, rayOrigin);
float c = Vector3::dot(rayOrigin, rayOrigin) - _radius_sq;
float d = b * b - c;
if (d >= 0.0f) { // there is an interstection between ray and the sphere
float sqrt_d = sqrt(d);
Vector3 intersection1 = rayOrigin + (-b - sqrt_d) * rayDirection;
Vector3 intersection2 = rayOrigin + (-b + sqrt_d) * rayDirection;
Vector3 intersection1World = _inverted * intersection1;
Vector3 intersection2World = _inverted * intersection2;
float intersection1Distance = (intersection1World - position).length();
float intersection2Distance = (intersection2World - position).length();
float distance = (viewPosition - position).length();
if (intersection1Distance < 0.0f) {
intersection1Distance = 0.0f;
}
if (intersection2Distance > distance) {
intersection2Distance = distance;
}
if (intersection2Distance >= intersection1Distance) {
*coeficient = intersection2Distance - intersection1Distance;
}
}
}
void FogCone::read(Common::ReadStream *stream, int frameCount) {
_frameCount = frameCount;
int size = readCommon(stream);
float coneAngle = stream->readFloatLE();
float tan_coneAngle = tan(coneAngle);
_cos_coneAngle = cos(coneAngle);
_tan_coneAngle_sq = tan_coneAngle * tan_coneAngle;
readAnimationData(stream, size - 52);
}
void FogCone::calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) {
*coeficient = 0.0f;
// ray - cone intersection, cone vertex V lies at (0,0,0) and direction v = (0,0,-1)
// The algorithm looks like from book Alan W. Paeth (1995), Graphics Gems V (p. 228-230)
Vector3 positionT = _matrix * position;
Vector3 viewPositionT = _matrix * viewPosition;
Vector3 v(0.0f, 0.0f, -1.0f);
Vector3 planeNormal = Vector3::cross(positionT, viewPositionT).normalize();
if (planeNormal.x != 0.0f || planeNormal.y != 0.0f || planeNormal.z != 0.0f) {
if (planeNormal.z < 0.0f) {
planeNormal = -1.0f * planeNormal;
}
float cosTheta = sqrt(1.0f - Vector3::dot(planeNormal, v) * Vector3::dot(planeNormal, v));
if (cosTheta > _cos_coneAngle) {
Vector3 u = Vector3::cross(v, planeNormal).normalize();
Vector3 w = Vector3::cross(u, v).normalize();
float tanTheta = sqrt(1.0f - cosTheta * cosTheta) / cosTheta;
Vector3 temp1 = tanTheta * w;
Vector3 temp2 = sqrt(_tan_coneAngle_sq - tanTheta * tanTheta) * u;
Vector3 delta1 = v + temp1 - temp2;
Vector3 delta2 = v + temp1 + temp2;
Vector3 d = viewPositionT - positionT;
Vector3 vecVD = -1.0f * positionT;
Vector3 crossddelta1 = Vector3::cross(d, delta1);
Vector3 crossddelta2 = Vector3::cross(d, delta2);
float r1 = Vector3::dot(Vector3::cross(vecVD, delta1), crossddelta1) / Vector3::dot(crossddelta1, crossddelta1);
float r2 = Vector3::dot(Vector3::cross(vecVD, delta2), crossddelta2) / Vector3::dot(crossddelta2, crossddelta2);
if (r2 < r1) {
float temp = r1;
r1 = r2;
r2 = temp;
}
if (r1 <= 1.0f && r2 >= 0.0f) {
if (r1 < 0.0f) {
r1 = 0.0;
}
if (r2 > 1.0f) {
r2 = 1.0;
}
Vector3 intersection1 = positionT + (r1 * d);
Vector3 intersection1World = _inverted * intersection1;
Vector3 intersection2 = positionT + (r2 * d);
Vector3 intersection2World = _inverted * intersection2;
*coeficient = (intersection2World - intersection1World).length();
}
}
}
}
void FogBox::read(Common::ReadStream *stream, int frameCount) {
_frameCount = frameCount;
int size = readCommon(stream);
_size.x = stream->readFloatLE();
_size.y = stream->readFloatLE();
_size.z = stream->readFloatLE();
readAnimationData(stream, size - 60);
}
void FogBox::calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) {
*coeficient = 0.0f;
// line - box intersection, where everything is rotated to box orientation by the fog matrix
Vector3 point1 = _matrix * position;
Vector3 point2 = _matrix * viewPosition;
Vector3 intersection1 = point1;
Vector3 intersection2 = point2;
Vector3 direction = point2 - point1;
// clip X
float minX = -(_size.x * 0.5f);
if (point1.x < minX) {
if (point2.x < minX) {
return;
}
float scale = (minX - point1.x) / direction.x;
intersection1 = point1 + scale * direction;
} else if (point2.x < minX) {
float scale = (minX - point2.x) / direction.x;
intersection2 = point2 + scale * direction;
}
float maxX = _size.x * 0.5f;
if (intersection1.x > maxX ) {
if (intersection2.x > maxX) {
return;
}
float scale = (maxX - intersection1.x) / direction.x;
intersection1 = intersection1 + scale * direction;
} else if (intersection2.x > maxX) {
float scale = (maxX - intersection2.x) / direction.x;
intersection2 = intersection2 + scale * direction;
}
// clip Y
float minY = -(_size.y * 0.5f);
if (intersection1.y < minY) {
if (intersection2.y < minY) {
return;
}
float scale = (minY - intersection1.y) / direction.y;
intersection1 = intersection1 + scale * direction;
} else if (intersection2.y < minY) {
float scale = (minY - intersection2.y) / direction.y;
intersection2 = intersection2 + scale * direction;
}
float maxY = _size.y * 0.5f;
if (intersection1.y > maxY) {
if (intersection2.y > maxY) {
return;
}
float scale = (maxY - intersection1.y) / direction.y;
intersection1 = intersection1 + scale * direction;
} else if (intersection2.y > maxY) {
float scale = (maxY - intersection2.y) / direction.y;
intersection2 = intersection2 + scale * direction;
}
// clip Z
if (intersection1.z < 0.0f) {
if (intersection2.z < 0.0f) {
return;
}
float scale = -intersection1.z / direction.z;
intersection1 = intersection1 + scale * direction;
} else if (intersection2.z < 0.0f) {
float scale = -intersection2.z / direction.z;
intersection2 = intersection2 + scale * direction;
}
if (intersection1.z > _size.z) {
if (intersection2.z > _size.z) {
return;
}
float scale = (_size.z - intersection1.z) / direction.z;
intersection1 = intersection1 + scale * direction;
} else if (intersection2.z > _size.z) {
float scale = (_size.z - intersection2.z) / direction.z;
intersection2 = intersection2 + scale * direction;
}
Vector3 intersection1World = _inverted * intersection1;
Vector3 intersection2World = _inverted * intersection2;
*coeficient = (intersection2World - intersection1World).length();
}
} // End of namespace BladeRunner

117
engines/bladerunner/fog.h Normal file
View File

@@ -0,0 +1,117 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_FOG_H
#define BLADERUNNER_FOG_H
#include "bladerunner/color.h"
#include "bladerunner/matrix.h"
namespace Common {
class ReadStream;
}
namespace BladeRunner {
class SetEffects;
class Fog {
friend class SetEffects;
friend class Debugger;
protected:
Common::String _name;
int _frameCount;
int _animatedParameters;
Matrix4x3 _matrix;
Matrix4x3 _inverted;
Color _fogColor;
float _fogDensity;
float *_animationData;
float *_m11ptr;
float *_m12ptr;
float *_m13ptr;
float *_m14ptr;
float *_m21ptr;
float *_m22ptr;
float *_m23ptr;
float *_m24ptr;
float *_m31ptr;
float *_m32ptr;
float *_m33ptr;
float *_m34ptr;
Fog *_next;
public:
Fog();
virtual ~Fog();
virtual void read(Common::ReadStream *stream, int frameCount) = 0;
virtual void calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) = 0;
void reset();
void setupFrame(int frame);
protected:
int readCommon(Common::ReadStream *stream);
void readAnimationData(Common::ReadStream *stream, int count);
};
class FogSphere : public Fog {
private:
float _radius_sq;
public:
FogSphere():_radius_sq(0.0f) {};
void read(Common::ReadStream *stream, int frameCount) override;
void calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) override;
};
class FogCone : public Fog {
private:
float _tan_coneAngle_sq;
float _cos_coneAngle;
public:
FogCone():_tan_coneAngle_sq(0.0f), _cos_coneAngle(1.0f) {};
void read(Common::ReadStream *stream, int frameCount) override;
void calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) override;
};
class FogBox : public Fog {
private:
Vector3 _size;
public:
FogBox():_size() {};
void read(Common::ReadStream *stream, int frameCount) override;
void calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) override;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,159 @@
/* 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/font.h"
#include "bladerunner/bladerunner.h"
#include "common/debug.h"
namespace BladeRunner {
Font::Font() {
reset();
}
Font::~Font() {
close();
}
Font* Font::load(BladeRunnerEngine *vm, const Common::String &fileName, int spacing, bool useFontColor) {
Font *font = new Font();
font->_spacing = spacing;
font->_useFontColor = useFontColor;
Common::ScopedPtr<Common::SeekableReadStream> stream(vm->getResourceStream(fileName));
if (!stream) {
warning("Font::open failed to open '%s'", fileName.c_str());
delete font;
return nullptr;
}
font->_characterCount = stream->readUint32LE();
font->_maxWidth = stream->readUint32LE();
font->_maxHeight = stream->readUint32LE();
font->_dataSize = stream->readUint32LE();
font->_data = new uint16[font->_dataSize];
if (!font->_data) {
warning("Font::open failed to allocate font buffer");
delete font;
return nullptr;
}
font->_characters.resize(font->_characterCount);
for (uint32 i = 0; i < font->_characterCount; ++i) {
font->_characters[i].x = stream->readUint32LE();
font->_characters[i].y = stream->readUint32LE();
font->_characters[i].width = stream->readUint32LE();
font->_characters[i].height = stream->readUint32LE();
font->_characters[i].dataOffset = stream->readUint32LE();
}
for (int i = 0; i < font->_dataSize; ++i) {
font->_data[i] = stream->readUint16LE();
}
return font;
}
void Font::close() {
if (_data != nullptr) {
delete[] _data;
}
reset();
}
void Font::reset() {
_maxWidth = 0;
_maxHeight = 0;
_characterCount = 0;
_data = nullptr;
_dataSize = 0;
_screenWidth = 0;
_screenHeight = 0;
_spacing = 0;
_useFontColor = false;
_characters.clear();
}
int Font::getFontHeight() const {
return _maxHeight;
}
int Font::getMaxCharWidth() const {
return _maxWidth;
}
int Font::getCharWidth(uint32 chr) const {
if (chr >= _characterCount) {
return 0;
}
return _characters[chr + 1].width + _spacing;
}
void Font::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const {
uint32 characterIndex = chr + 1;
if (x < 0 || x >= dst->w || y < 0 || y >= dst->h || !_data || characterIndex >= _characterCount) {
return;
}
uint16 *srcPtr = &_data[_characters[characterIndex].dataOffset];
int width = _characters[characterIndex].width;
int height = _characters[characterIndex].height;
int endY = height + y - 1;
int currentY = y;
// FIXME/TODO
// This width and height check were added as a temporary bug fix -- a sanity check which is only needed for the internal TAHOMA18.FON font.
// That font's glyph properties table is corrupted - the start of the file states that there are 0xF7 (=247) entries in the char properties table
// but that table get corrupted past the 176th entry. The image data glyph part of the FON file also only covers the 176 entries.
// So the following if clause-check will return here if the width and height values are unnaturally big.
// The bug only affects debug cases where all character glyph need to be displayed...
// ...or potential custom dialogue / translations that reference characters that are not within the range of ASCII values for the normal Latin characters.
if (width > 100 || height > 100) {
return;
}
while (currentY <= endY && currentY < dst->h) {
int currentX = x;
int endX = width + x - 1;
while (currentX <= endX && currentX < dst->w) {
uint8 a, r, g, b;
getGameDataColor(*srcPtr, a, r, g, b);
if (!a) { // Alpha is inversed
uint32 outColor = color;
if (_useFontColor) {
// Ignore the alpha in the output as it is inversed in the input
outColor = dst->format.RGBToColor(r, g, b);
}
void *dstPtr = dst->getBasePtr(CLIP(currentX + _characters[characterIndex].x, 0, dst->w - 1), CLIP(currentY + _characters[characterIndex].y, 0, dst->h - 1));
drawPixel(*dst, dstPtr, outColor);
}
++srcPtr;
++currentX;
}
++currentY;
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,77 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_FONT_H
#define BLADERUNNER_FONT_H
#include "common/array.h"
#include "common/str.h"
#include "graphics/font.h"
namespace Graphics {
struct Surface;
}
namespace BladeRunner {
class BladeRunnerEngine;
class Font : public Graphics::Font {
struct Character {
int x;
int y;
int width;
int height;
int dataOffset;
};
uint32 _characterCount;
int _maxWidth;
int _maxHeight;
Common::Array<Character> _characters;
int _dataSize;
uint16 *_data;
int _screenWidth;
int _screenHeight;
int _spacing;
bool _useFontColor;
public:
~Font() override;
static Font* load(BladeRunnerEngine *vm, const Common::String &fileName, int spacing, bool useFontColor);
int getFontHeight() const override;
int getMaxCharWidth() const override;
int getCharWidth(uint32 chr) const override;
void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const override;
private:
Font();
void reset();
void close();
// void drawCharacter(const uint8 character, Graphics::Surface &surface, int x, int y) const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,74 @@
/* 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/framelimiter.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/time.h"
#include "common/debug.h"
#include "common/system.h"
namespace BladeRunner {
Framelimiter::Framelimiter(BladeRunnerEngine *vm, uint fps) {
_vm = vm;
reset();
if (fps > 0) {
_enabled = true;
_speedLimitMs = 1000 / fps;
} else {
_enabled = false;
}
_timeFrameStart = _vm->_time->currentSystem();
}
void Framelimiter::wait() {
// TODO: when vsync will be supported, use it
if (!_enabled) {
return;
}
uint32 timeNow = _vm->_time->currentSystem();
uint32 frameDuration = timeNow - _timeFrameStart;
if (frameDuration < _speedLimitMs) {
uint32 waittime = _speedLimitMs - frameDuration;
if (_vm->_noDelayMillisFramelimiter) {
while (_vm->_time->currentSystem() - timeNow < waittime) { }
} else {
_vm->_system->delayMillis(waittime);
}
timeNow += waittime;
}
// debug("frametime %i ms", timeNow - _timeFrameStart);
// using _vm->_time->currentSystem() here is slower and causes some shutters
_timeFrameStart = timeNow;
}
void Framelimiter::reset() {
_timeFrameStart = 0u;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_FRAMELIMITER_H
#define BLADERUNNER_FRAMELIMITER_H
#include "bladerunner/bladerunner.h"
namespace BladeRunner {
class BladeRunnerEngine;
class Framelimiter {
friend class Debugger;
private:
BladeRunnerEngine *_vm;
bool _enabled;
uint32 _speedLimitMs;
uint32 _timeFrameStart;
public:
Framelimiter(BladeRunnerEngine *vm, uint fps = 60);
void wait();
private:
void reset();
};
} // End of namespace BladeRunner
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
/* 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/game_flags.h"
#include "bladerunner/savefile.h"
#include "common/debug.h"
namespace BladeRunner {
GameFlags::GameFlags()
: _flags(nullptr), _flagCount(0) {
}
GameFlags::~GameFlags() {
delete[] _flags;
}
void GameFlags::clear() {
for (int i = 0; i <= _flagCount; ++i) {
reset(i);
}
}
void GameFlags::setFlagCount(int count) {
assert(count > 0);
_flagCount = count;
_flags = new uint32[count / 32 + 1]();
clear();
}
void GameFlags::set(int flag) {
assert(flag >= 0 && flag <= _flagCount);
_flags[flag / 32] |= (1 << (flag % 32));
}
void GameFlags::reset(int flag) {
assert(flag >= 0 && flag <= _flagCount);
_flags[flag / 32] &= ~(1 << (flag % 32));
}
bool GameFlags::query(int flag) const {
//debug("GameFlags::query(%d): %d", flag, !!(flags[flag / 32] & (1 << (flag % 32))));
assert(flag >= 0 && flag <= _flagCount);
return !!(_flags[flag / 32] & (1 << (flag % 32)));
}
void GameFlags::save(SaveFileWriteStream &f) {
for (int i = 0; i != _flagCount / 32 + 1; ++i) {
f.writeUint32LE(_flags[i]);
}
}
void GameFlags::load(SaveFileReadStream &f) {
for (int i = 0; i != _flagCount / 32 + 1; ++i) {
_flags[i] = f.readUint32LE();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,53 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_GAME_FLAGS_H
#define BLADERUNNER_GAME_FLAGS_H
#include "common/scummsys.h"
namespace BladeRunner {
class SaveFileReadStream;
class SaveFileWriteStream;
class GameFlags {
uint32 *_flags;
int _flagCount;
public:
GameFlags();
~GameFlags();
void clear();
void setFlagCount(int count);
void set(int flag);
void reset(int flag);
bool query(int flag) const;
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,172 @@
/* 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/game_info.h"
#include "bladerunner/bladerunner.h"
#include "common/debug.h"
#include "common/substream.h"
namespace BladeRunner {
GameInfo::GameInfo(BladeRunnerEngine *vm) {
_vm = vm;
_actorCount = 0;
_playerId = 0;
_flagCount = 0;
_clueCount = 0;
_globalVarCount = 0;
_sceneNamesCount = 0;
_initialSceneId = 0;
_initialSetId = 0;
_waypointCount = 0;
_sfxTrackCount = 0;
_musicTrackCount = 0;
_outtakeCount = 0;
_crimeCount = 0;
_suspectCount = 0;
_coverWaypointCount = 0;
_fleeWaypointCount = 0;
}
bool GameInfo::open(const Common::String &name) {
Common::SeekableReadStream *s = _vm->getResourceStream(name);
if (!s) {
return false;
}
_actorCount = s->readUint32LE(); /* 00 */
_playerId = s->readUint32LE(); /* 01 */
_flagCount = s->readUint32LE(); /* 02 */
_clueCount = s->readUint32LE(); /* 03 */
_globalVarCount = s->readUint32LE(); /* 04 */
_sceneNamesCount = s->readUint32LE(); /* 05 */
_initialSceneId = s->readUint32LE(); /* 06 */
s->skip(4); /* 07 */
_initialSetId = s->readUint32LE(); /* 08 */
s->skip(4); /* 09 */
_waypointCount = s->readUint32LE(); /* 10 */
_sfxTrackCount = s->readUint32LE(); /* 11 */
_musicTrackCount = s->readUint32LE(); /* 12 */
_outtakeCount = s->readUint32LE(); /* 13 */
_crimeCount = s->readUint32LE(); /* 14 */
_suspectCount = s->readUint32LE(); /* 15 */
_coverWaypointCount = s->readUint32LE(); /* 16 */
_fleeWaypointCount = s->readUint32LE(); /* 17 */
char buf[9];
_sceneNames.resize(_sceneNamesCount);
for (uint32 i = 0; i != _sceneNamesCount; ++i) {
s->read(buf, 5);
_sceneNames[i] = buf;
}
_sfxTracks.resize(_sfxTrackCount);
for (uint32 i = 0; i != _sfxTrackCount; ++i) {
s->read(buf, 9);
_sfxTracks[i] = buf;
_sfxTracks[i] += ".AUD";
}
_musicTracks.resize(_musicTrackCount);
for (uint32 i = 0; i != _musicTrackCount; ++i) {
s->read(buf, 9);
_musicTracks[i] = buf;
_musicTracks[i] += ".AUD";
}
_outtakes.resize(_outtakeCount);
for (uint32 i = 0; i != _outtakeCount; ++i) {
s->read(buf, 9);
_outtakes[i] = buf;
}
#if BLADERUNNER_DEBUG_CONSOLE
debug("\nScene names\n----------------");
for (uint32 i = 0; i != _sceneNamesCount; ++i) {
debug("%3d: %s", i, _sceneNames[i].c_str());
}
debug("\nSfx tracks\n----------------");
for (uint32 i = 0; i != _sfxTrackCount; ++i) {
debug("%3d: %s", i, _sfxTracks[i].c_str());
}
debug("\nMusic tracks\n----------------");
for (uint32 i = 0; i != _musicTrackCount; ++i) {
debug("%3d: %s", i, _musicTracks[i].c_str());
}
debug("\nOuttakes\n----------------");
for (uint32 i = 0; i != _outtakeCount; ++i) {
debug("%2d: %s.VQA", i, _outtakes[i].c_str());
}
debug("Clue Count: %d ", _clueCount);
debug("Crime Count: %d ", _crimeCount);
debug("Suspect Count: %d ", _suspectCount);
#endif
bool err = s->err();
delete s;
return !err;
}
const Common::String &GameInfo::getSceneName(int i) const {
if (i < 0 || i >= (int)_sceneNamesCount) {
warning("GameInfo::getSceneName: unknown id \"%i\"", i);
static Common::String str("UNKNOWN_SCENE");
return str;
}
return _sceneNames[i];
}
const Common::String &GameInfo::getSfxTrack(int i) const {
if (i < 0 || i >= (int)_sfxTrackCount) {
warning("GameInfo::getSfxTrack: unknown id \"%i\"", i);
static Common::String str("UNKNOWN_SFX_TRACK");
return str;
}
return _sfxTracks[i];
}
const Common::String &GameInfo::getMusicTrack(int i) const {
if (i < 0 || i >= (int)_musicTrackCount) {
warning("GameInfo::getMusicTrack: unknown id \"%i\"", i);
static Common::String str("UNKNOWN_MUSIC_TRACK");
return str;
}
return _musicTracks[i];
}
const Common::String &GameInfo::getOuttake(int i) const {
if (i < 0 || i >= (int)_outtakeCount) {
warning("GameInfo::getOuttake: unknown id \"%i\"", i);
static Common::String str("UNKNOWN_OUTTAKE");
return str;
}
return _outtakes[i];
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,87 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_GAME_INFO_H
#define BLADERUNNER_GAME_INFO_H
#include "common/array.h"
#include "common/str.h"
namespace BladeRunner {
class BladeRunnerEngine;
class GameInfo {
BladeRunnerEngine *_vm;
uint32 _actorCount;
uint32 _playerId;
uint32 _flagCount;
uint32 _clueCount;
uint32 _globalVarCount;
uint32 _sceneNamesCount;
uint32 _initialSceneId;
uint32 _initialSetId;
uint32 _waypointCount;
uint32 _sfxTrackCount;
uint32 _musicTrackCount;
uint32 _outtakeCount;
uint32 _crimeCount;
uint32 _suspectCount;
uint32 _coverWaypointCount;
uint32 _fleeWaypointCount;
Common::Array<Common::String> _sceneNames;
Common::Array<Common::String> _sfxTracks;
Common::Array<Common::String> _musicTracks;
Common::Array<Common::String> _outtakes;
public:
GameInfo(BladeRunnerEngine *vm);
bool open(const Common::String &name);
uint32 getActorCount() const { return _actorCount; }
uint32 getPlayerId() const { return _playerId; }
uint32 getFlagCount() const { return _flagCount; }
uint32 getClueCount() const { return _clueCount; }
uint32 getGlobalVarCount() const { return _globalVarCount; }
uint32 getSceneNamesCount() const { return _sceneNamesCount; }
uint32 getInitialSceneId() const { return _initialSceneId; }
uint32 getInitialSetId() const { return _initialSetId; }
uint32 getWaypointCount() const { return _waypointCount; }
uint32 getSfxTrackCount() const { return _sfxTrackCount; }
uint32 getMusicTrackCount() const { return _musicTrackCount; }
uint32 getOuttakeCount() const { return _outtakeCount; }
uint32 getCrimeCount() const { return _crimeCount; }
uint32 getSuspectCount() const { return _suspectCount; }
uint32 getCoverWaypointCount() const { return _coverWaypointCount; }
uint32 getFleeWaypointCount() const { return _fleeWaypointCount; }
const Common::String &getSceneName(int i) const;
const Common::String &getSfxTrack(int i) const;
const Common::String &getMusicTrack(int i) const;
const Common::String &getOuttake(int i) const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,88 @@
/* 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/image.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/decompress_lcw.h"
#include "common/rect.h"
namespace BladeRunner {
Image::Image(BladeRunnerEngine *vm) {
_vm = vm;
}
Image::~Image() {
_surface.free();
}
bool Image::open(const Common::String &name) {
Common::SeekableReadStream *stream = _vm->getResourceStream(name);
if (!stream) {
warning("Image::open failed to open '%s'\n", name.c_str());
return false;
}
char tag[4] = { 0 };
stream->read(tag, 3);
uint32 width = stream->readUint32LE();
uint32 height = stream->readUint32LE();
// Enforce a reasonable limit
assert(width < 8000 && height < 8000);
uint32 bufSize = stream->size();
uint8 *buf = new uint8[bufSize];
stream->read(buf, bufSize);
uint32 dataSize = 2 * width * height;
void *data = malloc(dataSize);
assert(data);
if (strcmp(tag, "LZO") == 0) {
warning("LZO image decompression is not implemented");
} else if (strcmp(tag, "LCW") == 0) {
decompress_lcw(buf, bufSize, (uint8 *)data, dataSize);
#ifdef SCUMM_BIG_ENDIAN
// As the compression is working with 8-bit data, on big-endian architectures we have to switch order of bytes in uncompressed data
uint8 *rawData = (uint8 *)data;
for (size_t i = 0; i < dataSize - 1; i += 2) {
SWAP(rawData[i], rawData[i + 1]);
}
#endif
}
_surface.init(width, height, 2*width, data, gameDataPixelFormat());
_surface.convertToInPlace(screenPixelFormat());
delete[] buf;
delete stream;
return true;
}
void Image::copyToSurface(Graphics::Surface *dst) const {
dst->copyRectToSurface(_surface, 0, 0, Common::Rect(_surface.w, _surface.h));
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,49 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_IMAGE_H
#define BLADERUNNER_IMAGE_H
#include "graphics/surface.h"
namespace Common {
class String;
}
namespace BladeRunner {
class BladeRunnerEngine;
class Image {
BladeRunnerEngine *_vm;
Graphics::Surface _surface;
public:
Image(BladeRunnerEngine *vm);
~Image();
bool open(const Common::String &name);
void copyToSurface(Graphics::Surface *surface) const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,224 @@
/* 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/item.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/savefile.h"
#include "bladerunner/slice_renderer.h"
#include "bladerunner/zbuffer.h"
namespace BladeRunner {
Item::Item(BladeRunnerEngine *vm) {
_vm = vm;
_itemId = -1;
_setId = -1;
_animationId = -1;
_position.x = 0;
_position.y = 0;
_position.z = 0;
_facing = 0;
_angle = 0.0f;
_width = 0;
_height = 0;
_boundingBox.setXYZ(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
_screenX = 0;
_screenY = 0;
_depth = 0.0f;
_isTarget = false;
_isSpinning = false;
_facingChange = 0;
_isVisible = true;
_isPoliceMazeEnemy = false;
_screenRectangle.bottom = -1;
_screenRectangle.right = -1;
_screenRectangle.top = -1;
_screenRectangle.left = -1;
}
void Item::getXYZ(float *x, float *y, float *z) const {
*x = _position.x;
*y = _position.y;
*z = _position.z;
}
void Item::getWidthHeight(int *width, int *height) const {
*width = _width;
*height = _height;
}
void Item::getAnimationId(int *animationId) const {
*animationId = _animationId;
}
void Item::setFacing(int facing) {
_facing = facing;
_angle = _facing * (M_PI / 512.0f);
}
bool Item::tick(Common::Rect *screenRect, bool special) {
if (!_isVisible) {
*screenRect = Common::Rect();
return false;
}
bool isVisibleFlag = false;
Vector3 position(_position.x, -_position.z, _position.y);
int animationId = _animationId + (special ? 1 : 0);
_vm->_sliceRenderer->drawInWorld(animationId, 0, position, M_PI - _angle, 1.0f, _vm->_surfaceFront, _vm->_zbuffer->getData());
_vm->_sliceRenderer->getScreenRectangle(&_screenRectangle, animationId, 0, position, M_PI - _angle, 1.0f);
if (!_screenRectangle.isEmpty()) {
*screenRect = _screenRectangle;
isVisibleFlag = true;
} else {
*screenRect = Common::Rect();
}
if (_isSpinning) {
_facing += _facingChange;
if (_facing >= 1024) {
_facing -= 1024;
} else if (_facing < 0) {
_facing += 1024;
}
_angle = _facing * (M_PI / 512.0f);
if (_facingChange > 0) {
_facingChange = _facingChange - 20;
if (_facingChange < 0) {
_facingChange = 0;
_isSpinning = false;
}
} else if (_facingChange < 0) {
_facingChange = _facingChange + 20;
if (_facingChange > 0) {
_facingChange = 0;
_isSpinning = false;
}
} else {
_isSpinning = false;
}
}
return isVisibleFlag;
}
// setXYZ() recalculates the item's bounding box,
// but in addition to the item's (Vector3) position
// it takes into account the item's width and height
void Item::setXYZ(Vector3 position) {
_position = position;
int halfWidth = _width / 2;
_boundingBox.setXYZ(_position.x - halfWidth, _position.y, _position.z - halfWidth,
_position.x + halfWidth, _position.y + _height, _position.z + halfWidth);
Vector3 screenPosition = _vm->_view->calculateScreenPosition(_position);
_screenX = screenPosition.x;
_screenY = screenPosition.y;
_depth = screenPosition.z * 25.5f;
}
void Item::setup(int itemId, int setId, int animationId, Vector3 position, int facing, int height, int width, bool isTargetFlag, bool isVisibleFlag, bool isPoliceMazeEnemyFlag) {
_itemId = itemId;
_setId = setId;
_animationId = animationId;
_facing = facing;
_angle = facing * (M_PI / 512.0f);
_width = width;
_height = height;
_isTarget = isTargetFlag;
_isVisible = isVisibleFlag;
_isPoliceMazeEnemy = isPoliceMazeEnemyFlag;
setXYZ(position);
_screenRectangle.bottom = -1;
_screenRectangle.right = -1;
_screenRectangle.top = -1;
_screenRectangle.left = -1;
}
void Item::spinInWorld() {
_isSpinning = true;
if (_vm->_rnd.getRandomNumberRng(1, 2) == 1) {
_facingChange = -340;
} else {
_facingChange = 340;
}
}
bool Item::isUnderMouse(int mouseX, int mouseY) const {
return _isVisible
&& mouseX >= _screenRectangle.left - 10
&& mouseX <= _screenRectangle.right + 10
&& mouseY >= _screenRectangle.top - 10
&& mouseY <= _screenRectangle.bottom + 10;
}
void Item::save(SaveFileWriteStream &f) {
f.writeInt(_setId);
f.writeInt(_itemId);
f.writeBoundingBox(_boundingBox, false);
f.writeRect(_screenRectangle);
f.writeInt(_animationId);
f.writeVector3(_position);
f.writeInt(_facing);
f.writeFloat(_angle);
f.writeInt(_width);
f.writeInt(_height);
f.writeInt(_screenX);
f.writeInt(_screenY);
f.writeFloat(_depth);
f.writeBool(_isTarget);
f.writeBool(_isSpinning);
f.writeInt(_facingChange);
f.writeFloat(0.0f); // _viewAngle
f.writeBool(_isVisible);
f.writeBool(_isPoliceMazeEnemy);
}
void Item::load(SaveFileReadStream &f) {
_setId = f.readInt();
_itemId = f.readInt();
_boundingBox = f.readBoundingBox(false);
_screenRectangle = f.readRect();
_animationId = f.readInt();
_position = f.readVector3();
_facing = f.readInt();
_angle = f.readFloat();
_width = f.readInt();
_height = f.readInt();
_screenX = f.readInt();
_screenY = f.readInt();
_depth = f.readFloat();
_isTarget = f.readBool();
_isSpinning = f.readBool();
_facingChange = f.readInt();
f.skip(4);
_isVisible = f.readBool();
_isPoliceMazeEnemy = f.readBool();
}
} // End of namespace BladeRunner

100
engines/bladerunner/item.h Normal file
View File

@@ -0,0 +1,100 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ITEM_H
#define BLADERUNNER_ITEM_H
#include "bladerunner/boundingbox.h"
#include "bladerunner/vector.h"
#include "common/rect.h"
namespace BladeRunner {
class BladeRunnerEngine;
class Items;
class SaveFileReadStream;
class SaveFileWriteStream;
class Item {
friend class Items;
BladeRunnerEngine *_vm;
int _itemId;
int _setId;
BoundingBox _boundingBox;
Common::Rect _screenRectangle;
int _animationId;
Vector3 _position;
int _facing;
float _angle;
int _width;
int _height;
int _screenX;
int _screenY;
float _depth;
bool _isTarget;
bool _isSpinning;
int _facingChange;
bool _isVisible;
bool _isPoliceMazeEnemy;
public:
Item(BladeRunnerEngine *vm);
void getXYZ(float *x, float *y, float *z) const;
void setXYZ(Vector3 position);
void getWidthHeight(int *width, int *height) const;
void getAnimationId(int *animationId) const;
const BoundingBox &getBoundingBox() { return _boundingBox; }
const Common::Rect &getScreenRectangle() { return _screenRectangle; }
int getFacing() const { return _facing; }
void setFacing(int facing);
bool isTarget() const { return _isTarget; }
void setIsTarget(bool val) { _isTarget = val; }
bool isSpinning() const { return _isSpinning; }
void spinInWorld();
bool isVisible() const { return _isVisible; }
void setVisible(bool val) { _isVisible = val; }
bool isPoliceMazeEnemy() const { return _isPoliceMazeEnemy; }
void setPoliceMazeEnemy(bool val) { _isPoliceMazeEnemy = val; }
bool tick(Common::Rect *screenRect, bool special);
void setup(int itemId, int setId, int animationId, Vector3 position, int facing, int height, int width, bool isTargetFlag, bool isVisibleFlag, bool isPoliceMazeEnemyFlag);
bool isUnderMouse(int mouseX, int mouseY) const;
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
};
}
#endif

View File

@@ -0,0 +1,111 @@
/* 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/item_pickup.h"
#include "bladerunner/audio_player.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/game_info.h"
#include "bladerunner/slice_animations.h"
#include "bladerunner/slice_renderer.h"
#include "bladerunner/time.h"
#include "bladerunner/zbuffer.h"
#include "bladerunner/game_constants.h"
namespace BladeRunner {
ItemPickup::ItemPickup(BladeRunnerEngine *vm) {
_vm = vm;
_facingStep = float(2.0f / 3000.0f * (2.0f * M_PI));
reset();
}
ItemPickup::~ItemPickup() {
}
void ItemPickup::setup(int animationId, int screenX, int screenY) {
_animationId = animationId;
_animationFrame = 0;
_facing = 0.0;
_timeLeft = 3000u;
_scale = 0;
_screenX = CLIP(screenX, 40, BladeRunnerEngine::kOriginalGameWidth - 40);
_screenY = CLIP(screenY, 40, BladeRunnerEngine::kOriginalGameHeight - 40);
_screenRect.left = _screenX - 40;
_screenRect.right = _screenX + 40;
_screenRect.top = _screenY - 40;
_screenRect.bottom = _screenY + 40;
// map [0..640] to [-75..75]
int pan = (75 * (2 * _screenX - BladeRunnerEngine::kOriginalGameWidth)) / BladeRunnerEngine::kOriginalGameWidth;
_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(kSfxGETITEM1), 80, pan, pan, 50, 0);
_timeLast = _vm->_time->currentSystem();
}
void ItemPickup::reset() {
_animationId = -1;
_screenX = 0;
_screenY = 0;
_facing = 0.0f;
_scale = 1.0f;
_animationFrame = 0;
_timeLeft = 0u;
_timeLast = 0u;
}
void ItemPickup::tick() {
if (_timeLeft == 0u) {
return;
}
uint32 timeNow = _vm->_time->currentSystem();
// unsigned difference is intentional
uint32 timeDiff = timeNow - _timeLast;
_timeLast = timeNow;
timeDiff = MIN(MIN<uint32>(timeDiff, 67u), _timeLeft);
_timeLeft = (_timeLeft < timeDiff) ? 0 : (_timeLeft - timeDiff);
if (_timeLeft >= 2000u) {
_scale = 1.0f - (((2000.0f - _timeLeft) / 1000.0f) * ((2000.0f - _timeLeft) / 1000.0f));
} else if (_timeLeft < 1000u) {
_scale = 1.0f - (((1000.0f - _timeLeft) / 1000.0f) * ((1000.0f - _timeLeft) / 1000.0f));
} else {
_scale = 1.0f;
}
_scale *= 75.0f;
_facing += _facingStep * timeDiff;
if (_facing > float(2.0f * M_PI)) {
_facing -= float(2.0f * M_PI);
}
_animationFrame = (_animationFrame + 1) % _vm->_sliceAnimations->getFrameCount(_animationId);
}
void ItemPickup::draw() {
if (_timeLeft == 0u) {
return;
}
_vm->_sliceRenderer->drawOnScreen(_animationId, _animationFrame, _screenX, _screenY, _facing, _scale, _vm->_surfaceFront);
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,59 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ITEMPICKUP_H
#define BLADERUNNER_ITEMPICKUP_H
#include "common/rect.h"
namespace BladeRunner {
class BladeRunnerEngine;
class ItemPickup {
BladeRunnerEngine *_vm;
float _facingStep;
int _animationId;
int _screenX;
int _screenY;
float _facing;
float _scale;
int _animationFrame;
uint32 _timeLeft;
uint32 _timeLast;
Common::Rect _screenRect;
public:
ItemPickup(BladeRunnerEngine *vm);
~ItemPickup();
void setup(int animationId, int screenX, int screenY);
void reset();
void tick();
void draw();
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,308 @@
/* 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/items.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/zbuffer.h"
namespace BladeRunner {
Items::Items(BladeRunnerEngine *vm) {
_vm = vm;
}
Items::~Items() {
reset();
}
void Items::reset() {
for (int i = _items.size() - 1; i >= 0; --i) {
delete _items.remove_at(i);
}
}
void Items::getXYZ(int itemId, float *x, float *y, float *z) const {
int itemIndex = findItem(itemId);
assert(itemIndex != -1);
_items[itemIndex]->getXYZ(x, y, z);
}
void Items::setXYZ(int itemId, Vector3 position) {
int itemIndex = findItem(itemId);
assert(itemIndex != -1);
_items[itemIndex]->setXYZ(position);
}
void Items::getWidthHeight(int itemId, int *width, int *height) const {
int itemIndex = findItem(itemId);
assert(itemIndex != -1);
_items[itemIndex]->getWidthHeight(width, height);
}
void Items::getAnimationId(int itemId, int *animationId) const {
int itemIndex = findItem(itemId);
assert(itemIndex != -1);
_items[itemIndex]->getAnimationId(animationId);
}
void Items::tick() {
int setId = _vm->_scene->getSetId();
for (int i = 0; i < (int)_items.size(); ++i) {
if (_items[i]->_setId != setId) {
continue;
}
bool notPoliceMazeTarget = setId == kSetPS10_PS11_PS12_PS13 && !_items[i]->isTarget();
Common::Rect screenRect;
if (_items[i]->tick(&screenRect, notPoliceMazeTarget)) {
_vm->_zbuffer->mark(screenRect);
}
}
}
bool Items::addToWorld(int itemId, int animationId, int setId, Vector3 position, int facing, int height, int width, bool isTargetFlag, bool isVisibleFlag, bool isPoliceMazeEnemyFlag, bool addToSetFlag) {
if (_items.size() >= 100) {
return false;
}
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
itemIndex = _items.size();
_items.push_back(new Item(_vm));
}
Item *item = _items[itemIndex];
item->setup(itemId, setId, animationId, position, facing, height, width, isTargetFlag, isVisibleFlag, isPoliceMazeEnemyFlag);
if (addToSetFlag && setId == _vm->_scene->getSetId()) {
return _vm->_sceneObjects->addItem(itemId + kSceneObjectOffsetItems, item->_boundingBox, item->_screenRectangle, isTargetFlag, isVisibleFlag);
}
return true;
}
bool Items::addToSet(int setId) {
int itemCount = _items.size();
if (itemCount == 0) {
return true;
}
for (int i = 0; i < itemCount; ++i) {
Item *item = _items[i];
if (item->_setId == setId) {
_vm->_sceneObjects->addItem(item->_itemId + kSceneObjectOffsetItems, item->_boundingBox, item->_screenRectangle, item->isTarget(), item->_isVisible);
}
}
return true;
}
#if !BLADERUNNER_ORIGINAL_BUGS
bool Items::removeFromCurrentSceneOnly(int itemId) {
if (_items.size() == 0) {
return false;
}
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return false;
}
if (_items[itemIndex]->_setId == _vm->_scene->getSetId()) {
_vm->_sceneObjects->remove(itemId + kSceneObjectOffsetItems);
}
return true;
}
#endif // !BLADERUNNER_ORIGINAL_BUGS
bool Items::remove(int itemId) {
if (_items.size() == 0) {
return false;
}
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return false;
}
if (_items[itemIndex]->_setId == _vm->_scene->getSetId()) {
_vm->_sceneObjects->remove(itemId + kSceneObjectOffsetItems);
}
delete _items.remove_at(itemIndex);
return true;
}
void Items::setIsTarget(int itemId, bool val) {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return;
}
_items[itemIndex]->setIsTarget(val);
_vm->_sceneObjects->setIsTarget(itemId + kSceneObjectOffsetItems, val);
}
bool Items::isTarget(int itemId) const {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return false;
}
return _items[itemIndex]->isTarget();
}
bool Items::isSpinning(int itemId) const {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return false;
}
return _items[itemIndex]->isSpinning();
}
bool Items::isVisible(int itemId) const {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return false;
}
return _items[itemIndex]->isVisible();
}
bool Items::isPoliceMazeEnemy(int itemId) const {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return false;
}
return _items[itemIndex]->isPoliceMazeEnemy();
}
void Items::setPoliceMazeEnemy(int itemId, bool val) {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return;
}
_items[itemIndex]->setPoliceMazeEnemy(val);
}
void Items::setIsObstacle(int itemId, bool val) {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return;
}
_items[itemIndex]->setVisible(val);
_vm->_sceneObjects->setIsClickable(itemId + kSceneObjectOffsetItems, val);
}
const BoundingBox &Items::getBoundingBox(int itemId) {
int itemIndex = findItem(itemId);
// if (itemIndex == -1) {
// return nullptr;
// }
return _items[itemIndex]->getBoundingBox();
}
const Common::Rect &Items::getScreenRectangle(int itemId) {
int itemIndex = findItem(itemId);
// if (itemIndex == -1) {
// return nullptr;
// }
return _items[itemIndex]->getScreenRectangle();
}
int Items::getFacing(int itemId) const {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return 0;
}
return _items[itemIndex]->getFacing();
}
void Items::setFacing(int itemId, int facing) {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return;
}
_items[itemIndex]->setFacing(facing);
}
void Items::spinInWorld(int itemId) {
int itemIndex = findItem(itemId);
if (itemIndex == -1) {
return;
}
_items[itemIndex]->spinInWorld();
}
int Items::findTargetUnderMouse(int mouseX, int mouseY) const {
int setId = _vm->_scene->getSetId();
for (int i = 0 ; i < (int)_items.size(); ++i) {
if (_items[i]->_setId == setId && _items[i]->isTarget() && _items[i]->isUnderMouse(mouseX, mouseY)) {
return _items[i]->_itemId;
}
}
return -1;
}
int Items::findItem(int itemId) const {
for (int i = 0; i < (int)_items.size(); ++i) {
if (_items[i]->_itemId == itemId) {
return i;
}
}
return -1;
}
void Items::save(SaveFileWriteStream &f) {
int size = (int)_items.size();
f.writeInt(size);
int i;
for (i = 0; i != size; ++i) {
_items[i]->save(f);
}
// Always write out 100 items
for (; i != 100; ++i) {
f.padBytes(0x174); // bbox + rect + 18 float fields
}
}
void Items::load(SaveFileReadStream &f) {
for (int i = _items.size() - 1; i >= 0; --i) {
delete _items.remove_at(i);
}
_items.resize(f.readInt());
int size = (int)_items.size();
int i;
for (i = 0; i != size; ++i) {
_items[i] = new Item(_vm);
_items[i]->load(f);
}
// Always read out 100 items
for (; i != 100; ++i) {
f.skip(0x174); // bbox + rect + 18 float fields
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,84 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_ITEMS_H
#define BLADERUNNER_ITEMS_H
#include "bladerunner/bladerunner.h"
#include "bladerunner/item.h"
#include "common/array.h"
namespace BladeRunner {
class SaveFileReadStream;
class SaveFileWriteStream;
class Items {
BladeRunnerEngine *_vm;
Common::Array<Item *> _items;
public:
Items(BladeRunnerEngine *vm);
~Items();
void reset();
void getXYZ(int itemId, float *x, float *y, float *z) const;
void setXYZ(int itemId, Vector3 position);
void getWidthHeight(int itemId, int *width, int *height) const;
void getAnimationId(int itemId, int *animationId) const;
void tick();
bool addToWorld(int itemId, int animationId, int setId, Vector3 position, int facing, int height, int width, bool isTargetFlag, bool isVisibleFlag, bool isPoliceMazeEnemyFlag, bool addToSetFlag);
bool addToSet(int itemId);
#if !BLADERUNNER_ORIGINAL_BUGS
bool removeFromCurrentSceneOnly(int itemId);
#endif // !BLADERUNNER_ORIGINAL_BUGS
bool remove(int itemId);
void setIsTarget(int itemId, bool val);
bool isTarget(int itemId) const;
bool isSpinning(int itemId) const;
bool isPoliceMazeEnemy(int itemId) const;
void setPoliceMazeEnemy(int itemId, bool val);
void setIsObstacle(int itemId, bool val);
bool isVisible(int itemId) const;
int findTargetUnderMouse(int mouseX, int mouseY) const;
const BoundingBox &getBoundingBox(int itemId);
const Common::Rect &getScreenRectangle(int itemId);
int getFacing(int itemId) const;
void setFacing(int itemId, int facing);
void spinInWorld(int itemId);
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
int findItem(int itemId) const;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,354 @@
/* 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/light.h"
#include "common/util.h"
namespace BladeRunner {
Light::Light() {
_frameCount = 0;
_animated = 0;
_animationData = nullptr;
_animatedParameters = 0;
_falloffStart = 0.0f;
_falloffEnd = 0.0f;
_angleStart = 0.0f;
_angleEnd = 0.0f;
_m11ptr = nullptr;
_m12ptr = nullptr;
_m13ptr = nullptr;
_m14ptr = nullptr;
_m21ptr = nullptr;
_m22ptr = nullptr;
_m23ptr = nullptr;
_m24ptr = nullptr;
_m31ptr = nullptr;
_m32ptr = nullptr;
_m33ptr = nullptr;
_m34ptr = nullptr;
_colorRPtr = nullptr;
_colorGPtr = nullptr;
_colorBPtr = nullptr;
_falloffStartPtr = nullptr;
_falloffEndPtr = nullptr;
_angleStartPtr = nullptr;
_angleEndPtr = nullptr;
}
Light::~Light() {
if (_animationData != nullptr) {
delete[] _animationData;
}
}
void Light::read(Common::ReadStream *stream, int frameCount, int frame, int animated) {
_frameCount = frameCount;
_animated = animated;
int size = stream->readUint32LE();
size = size - 32;
char buf[20];
stream->read(buf, sizeof(buf));
_name = buf;
_animatedParameters = stream->readUint32LE();
if (_animationData != nullptr) {
delete[] _animationData;
}
int floatCount = size / 4;
_animationData = new float[floatCount];
for (int i = 0; i < floatCount; ++i) {
_animationData[i] = stream->readFloatLE();
}
_m11ptr = _animationData;
_m12ptr = _m11ptr + ((_animatedParameters & 0x1) ? frameCount : 1);
_m13ptr = _m12ptr + ((_animatedParameters & 0x2) ? frameCount : 1);
_m14ptr = _m13ptr + ((_animatedParameters & 0x4) ? frameCount : 1);
_m21ptr = _m14ptr + ((_animatedParameters & 0x8) ? frameCount : 1);
_m22ptr = _m21ptr + ((_animatedParameters & 0x10) ? frameCount : 1);
_m23ptr = _m22ptr + ((_animatedParameters & 0x20) ? frameCount : 1);
_m24ptr = _m23ptr + ((_animatedParameters & 0x40) ? frameCount : 1);
_m31ptr = _m24ptr + ((_animatedParameters & 0x80) ? frameCount : 1);
_m32ptr = _m31ptr + ((_animatedParameters & 0x100) ? frameCount : 1);
_m33ptr = _m32ptr + ((_animatedParameters & 0x200) ? frameCount : 1);
_m34ptr = _m33ptr + ((_animatedParameters & 0x400) ? frameCount : 1);
_colorRPtr = _m34ptr + ((_animatedParameters & 0x800) ? frameCount : 1);
_colorGPtr = _colorRPtr + ((_animatedParameters & 0x1000) ? frameCount : 1);
_colorBPtr = _colorGPtr + ((_animatedParameters & 0x2000) ? frameCount : 1);
_falloffStartPtr = _colorBPtr + ((_animatedParameters & 0x4000) ? frameCount : 1);
_falloffEndPtr = _falloffStartPtr + ((_animatedParameters & 0x8000) ? frameCount : 1);
_angleStartPtr = _falloffEndPtr + ((_animatedParameters & 0x10000) ? frameCount : 1);
_angleEndPtr = _angleStartPtr + ((_animatedParameters & 0x20000) ? frameCount : 1);
setupFrame(frame);
}
void Light::readVqa(Common::ReadStream *stream, int frameCount, int frame, int animated) {
_frameCount = frameCount;
_animated = animated;
_animatedParameters = stream->readUint32LE();
int size = stream->readUint32LE();
if (_animationData != nullptr) {
delete[] _animationData;
}
int floatCount = size / 4;
_animationData = new float[floatCount];
for (int i = 0; i < floatCount; ++i) {
_animationData[i] = stream->readFloatLE();
}
_m11ptr = _animationData;
_m12ptr = _m11ptr + ((_animatedParameters & 0x1) ? frameCount : 1);
_m13ptr = _m12ptr + ((_animatedParameters & 0x2) ? frameCount : 1);
_m14ptr = _m13ptr + ((_animatedParameters & 0x4) ? frameCount : 1);
_m21ptr = _m14ptr + ((_animatedParameters & 0x8) ? frameCount : 1);
_m22ptr = _m21ptr + ((_animatedParameters & 0x10) ? frameCount : 1);
_m23ptr = _m22ptr + ((_animatedParameters & 0x20) ? frameCount : 1);
_m24ptr = _m23ptr + ((_animatedParameters & 0x40) ? frameCount : 1);
_m31ptr = _m24ptr + ((_animatedParameters & 0x80) ? frameCount : 1);
_m32ptr = _m31ptr + ((_animatedParameters & 0x100) ? frameCount : 1);
_m33ptr = _m32ptr + ((_animatedParameters & 0x200) ? frameCount : 1);
_m34ptr = _m33ptr + ((_animatedParameters & 0x400) ? frameCount : 1);
_colorRPtr = _m34ptr + ((_animatedParameters & 0x800) ? frameCount : 1);
_colorGPtr = _colorRPtr + ((_animatedParameters & 0x1000) ? frameCount : 1);
_colorBPtr = _colorGPtr + ((_animatedParameters & 0x2000) ? frameCount : 1);
_falloffStartPtr = _colorBPtr + ((_animatedParameters & 0x4000) ? frameCount : 1);
_falloffEndPtr = _falloffStartPtr + ((_animatedParameters & 0x8000) ? frameCount : 1);
_angleStartPtr = _falloffEndPtr + ((_animatedParameters & 0x10000) ? frameCount : 1);
_angleEndPtr = _angleStartPtr + ((_animatedParameters & 0x20000) ? frameCount : 1);
setupFrame(frame);
}
void Light::setupFrame(int frame) {
int offset = frame % _frameCount;
_matrix._m[0][0] = ((_animatedParameters & 0x1) ? _m11ptr[offset] : *_m11ptr);
_matrix._m[0][1] = ((_animatedParameters & 0x2) ? _m12ptr[offset] : *_m12ptr);
_matrix._m[0][2] = ((_animatedParameters & 0x4) ? _m13ptr[offset] : *_m13ptr);
_matrix._m[0][3] = ((_animatedParameters & 0x8) ? _m14ptr[offset] : *_m14ptr);
_matrix._m[1][0] = ((_animatedParameters & 0x10) ? _m21ptr[offset] : *_m21ptr);
_matrix._m[1][1] = ((_animatedParameters & 0x20) ? _m22ptr[offset] : *_m22ptr);
_matrix._m[1][2] = ((_animatedParameters & 0x40) ? _m23ptr[offset] : *_m23ptr);
_matrix._m[1][3] = ((_animatedParameters & 0x80) ? _m24ptr[offset] : *_m24ptr);
_matrix._m[2][0] = ((_animatedParameters & 0x100) ? _m31ptr[offset] : *_m31ptr);
_matrix._m[2][1] = ((_animatedParameters & 0x200) ? _m32ptr[offset] : *_m32ptr);
_matrix._m[2][2] = ((_animatedParameters & 0x400) ? _m33ptr[offset] : *_m33ptr);
_matrix._m[2][3] = ((_animatedParameters & 0x800) ? _m34ptr[offset] : *_m34ptr);
_color.r = ((_animatedParameters & 0x1000) ? _colorRPtr[offset] : *_colorRPtr);
_color.g = ((_animatedParameters & 0x2000) ? _colorGPtr[offset] : *_colorGPtr);
_color.b = ((_animatedParameters & 0x4000) ? _colorBPtr[offset] : *_colorBPtr);
_falloffStart = ((_animatedParameters & 0x8000) ? _falloffStartPtr[offset] : *_falloffStartPtr);
_falloffEnd = ((_animatedParameters & 0x10000) ? _falloffEndPtr[offset] : *_falloffEndPtr);
_angleStart = ((_animatedParameters & 0x20000) ? _angleStartPtr[offset] : *_angleStartPtr);
_angleEnd = ((_animatedParameters & 0x40000) ? _angleEndPtr[offset] : *_angleEndPtr);
}
float Light::calculate(Vector3 start, Vector3 end) const {
return calculateFalloutCoefficient(_matrix * start, _matrix * end, _falloffStart, _falloffEnd);
}
void Light::calculateColor(Color *outColor, Vector3 position) const {
Vector3 positionT = _matrix * position;
float att = attenuation(_falloffStart, _falloffEnd, positionT.length());
outColor->r = _color.r * att;
outColor->g = _color.g * att;
outColor->b = _color.b * att;
}
float Light::calculateFalloutCoefficient(Vector3 start, Vector3 end, float falloffStart, float falloffEnd) const {
if (falloffEnd == 0.0f) {
return 1.0e30f;
}
if (falloffStart * falloffStart >= start.length() && falloffStart * falloffStart >= end.length()) {
return 1.0e30f;
}
float diff = (end - start).length();
float v31 = 0.0f;
if (diff != 0.0f) {
Vector3 v27 = Vector3::cross(start, (end - start));
v31 = v27.length() / diff;
}
if (v31 < falloffEnd) {
return 1.0f / (1.0f - (v31 / falloffEnd));
}
return 1.0e30f;
}
float Light::attenuation(float min, float max, float distance) const {
if (max == 0.0f) {
return 1.0f;
}
if (min < max) {
distance = CLIP(distance, min, max);
float x = (max - distance) / (max - min);
return x * x * (3.0f - 2.0f * x);
}
if (distance < min) {
return 1.0f;
}
return 0.0f;
}
float Light1::calculate(Vector3 start, Vector3 end) const {
start = _matrix * start;
end = _matrix * end;
float v40 = 0.0f;
if (_falloffEnd != 0.0f) {
v40 = calculateFalloutCoefficient(start, end, _falloffStart, _falloffEnd);
}
float v41 = atan2(sqrt(start.x * start.x + start.y * start.y), -start.z);
float v42 = atan2(sqrt(end.x * end.x + end.y * end.y), -end.z);
float v43;
if ((_angleStart >= v41 && _angleStart >= v42) || (_angleEnd <= v41 && _angleEnd <= v42)) {
v43 = 1.0e30f;
} else {
v43 = 2.0;
}
if (v43 < v40) {
return v40;
} else {
return v43;
}
}
void Light1::calculateColor(Color *outColor, Vector3 position) const {
Vector3 positionT = _matrix * position;
outColor->r = 0.0f;
outColor->g = 0.0f;
outColor->b = 0.0f;
if (positionT.z < 0.0f) {
float v12 = attenuation(_angleStart, _angleEnd, atan2(sqrt(positionT.x * positionT.x + positionT.y * positionT.y), -positionT.z));
float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());
outColor->r = v12 * v13 * _color.r;
outColor->g = v12 * v13 * _color.g;
outColor->b = v12 * v13 * _color.b;
}
}
float Light2::calculate(Vector3 start, Vector3 end) const {
start = _matrix * start;
end = _matrix * end;
float v54 = 0.0f;
if (_falloffEnd != 0.0f) {
v54 = calculateFalloutCoefficient(start, end, _falloffStart, _falloffEnd);
}
float v55 = atan2(fabs(start.x), -start.z);
float v58 = atan2(fabs(start.y), -start.z);
float v57 = atan2(fabs(end.x), -end.z);
float v56 = atan2(fabs(end.y), -end.z);
float v59;
if ((_angleStart >= v55 && _angleStart >= v57 && _angleStart >= v58 && _angleStart >= v56) || (_angleEnd <= v55 && _angleEnd <= v57 && _angleEnd <= v58 && _angleEnd <= v56)) {
v59 = 1.0e30f;
} else {
v59 = 2.0f;
}
if (v59 < v54) {
return v54;
} else {
return v59;
}
}
void Light2::calculateColor(Color *outColor, Vector3 position) const {
Vector3 positionT = _matrix * position;
outColor->r = 0.0f;
outColor->g = 0.0f;
outColor->b = 0.0f;
if (positionT.z < 0.0f) {
float v11 = attenuation(_angleStart, _angleEnd, atan2(fabs(positionT.y), -positionT.z));
float v12 = attenuation(_angleStart, _angleEnd, atan2(fabs(positionT.x), -positionT.z));
float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());
outColor->r = v11 * v12 * v13 * _color.r;
outColor->g = v11 * v12 * v13 * _color.g;
outColor->b = v11 * v12 * v13 * _color.b;
}
}
void Light3::calculateColor(Color *outColor, Vector3 position) const {
Vector3 positionT = _matrix * position;
outColor->r = 0.0f;
outColor->g = 0.0f;
outColor->b = 0.0f;
if (positionT.z < 0.0f) {
float v12 = attenuation(_angleStart, _angleEnd, sqrt(positionT.x * positionT.x + positionT.y * positionT.y));
float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());
outColor->r = v12 * v13 * _color.r;
outColor->g = v12 * v13 * _color.g;
outColor->b = v12 * v13 * _color.b;
}
}
void Light4::calculateColor(Color *outColor, Vector3 position) const {
Vector3 positionT = _matrix * position;
outColor->r = 0.0f;
outColor->g = 0.0f;
outColor->b = 0.0f;
if (positionT.z < 0.0f) {
float v11 = attenuation(_angleStart, _angleEnd, fabs(positionT.y));
float v12 = attenuation(_angleStart, _angleEnd, fabs(positionT.x));
float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());
outColor->r = v11 * v12 * v13 * _color.r;
outColor->g = v11 * v12 * v13 * _color.g;
outColor->b = v11 * v12 * v13 * _color.b;
}
}
float LightAmbient::calculate(Vector3 start, Vector3 end) const {
return 1.0e30f;
}
void LightAmbient::calculateColor(Color *outColor, Vector3 position) const {
outColor->r = _color.r;
outColor->g = _color.g;
outColor->b = _color.b;
}
} // End of namespace BladeRunner

118
engines/bladerunner/light.h Normal file
View File

@@ -0,0 +1,118 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_LIGHT_H
#define BLADERUNNER_LIGHT_H
#include "bladerunner/matrix.h"
#include "bladerunner/color.h"
#include "common/stream.h"
namespace Common{
class ReadStream;
}
namespace BladeRunner {
class Lights;
class Light {
friend class Debugger;
friend class Lights;
friend class SliceRenderer;
protected:
Common::String _name;
int _frameCount;
int _animated;
int _animatedParameters;
Matrix4x3 _matrix;
Color _color;
float _falloffStart;
float _falloffEnd;
float _angleStart;
float _angleEnd;
float *_animationData;
float *_m11ptr;
float *_m12ptr;
float *_m13ptr;
float *_m14ptr;
float *_m21ptr;
float *_m22ptr;
float *_m23ptr;
float *_m24ptr;
float *_m31ptr;
float *_m32ptr;
float *_m33ptr;
float *_m34ptr;
float *_colorRPtr;
float *_colorGPtr;
float *_colorBPtr;
float *_falloffStartPtr;
float *_falloffEndPtr;
float *_angleStartPtr;
float *_angleEndPtr;
public:
Light();
virtual ~Light();
void read(Common::ReadStream *stream, int frameCount, int frame, int animated);
void readVqa(Common::ReadStream *stream, int frameCount, int frame, int animated);
void setupFrame(int frame);
virtual float calculate(Vector3 start, Vector3 end) const;
virtual void calculateColor(Color *outColor, Vector3 position) const;
protected:
float calculateFalloutCoefficient(Vector3 start, Vector3 end, float a3, float a4) const;
float attenuation(float min, float max, float distance) const;
};
class Light1 : public Light {
float calculate(Vector3 start, Vector3 end) const override;
void calculateColor(Color *outColor, Vector3 position) const override;
};
class Light2 : public Light {
float calculate(Vector3 start, Vector3 end) const override;
void calculateColor(Color *outColor, Vector3 position) const override;
};
class Light3 : public Light {
void calculateColor(Color *outColor, Vector3 position) const override;
};
class Light4 : public Light {
void calculateColor(Color *outColor, Vector3 position) const override;
};
class LightAmbient : public Light {
float calculate(Vector3 start, Vector3 end) const override;
void calculateColor(Color *outColor, Vector3 position) const override;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,134 @@
/* 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/lights.h"
namespace BladeRunner {
Lights::Lights(BladeRunnerEngine *vm) {
_vm = vm;
_ambientLightColor.r = 1.0;
_ambientLightColor.g = 0.0;
_ambientLightColor.b = 0.0;
_frame = 0;
}
Lights::~Lights() {
reset();
}
void Lights::read(Common::ReadStream *stream, int frameCount) {
_ambientLightColor.r = stream->readFloatLE();
_ambientLightColor.g = stream->readFloatLE();
_ambientLightColor.b = stream->readFloatLE();
uint _lightCount = stream->readUint32LE();
for (uint i = 0; i < _lightCount; ++i) {
Light *light;
int type = stream->readUint32LE();
switch (type) {
case 1:
light = new Light1();
break;
case 2:
light = new Light2();
break;
case 3:
light = new Light3();
break;
case 4:
light = new Light4();
break;
case 5:
light = new LightAmbient();
break;
default:
light = new Light();
}
light->read(stream, frameCount, _frame, 0);
_lights.push_back(light);
}
}
void Lights::removeAnimated() {
for (int i = (int)(_lights.size() - 1); i >= 0; --i) {
if (_lights[i]->_animated) {
delete _lights.remove_at(i);
}
}
}
void Lights::readVqa(Common::ReadStream *stream) {
removeAnimated();
if (stream->eos()) {
return;
}
int frameCount = stream->readUint32LE();
int count = stream->readUint32LE();
for (int i = 0; i < count; ++i) {
int lightType = stream->readUint32LE();
Light *light;
switch (lightType) {
case 5:
light = new LightAmbient();
break;
case 4:
light = new Light4();
break;
case 3:
light = new Light3();
break;
case 2:
light = new Light2();
break;
case 1:
light = new Light1();
break;
default:
light = new Light();
}
light->readVqa(stream, frameCount, _frame, 1);
_lights.push_back(light);
}
}
void Lights::setupFrame(int frame) {
if (frame == _frame) {
return;
}
for (uint i = 0; i < _lights.size(); ++i) {
_lights[i]->setupFrame(frame);
}
}
void Lights::reset() {
for (int i = (int)(_lights.size() - 1); i >= 0; --i) {
delete _lights.remove_at(i);
}
_lights.clear();
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_LIGHTS_H
#define BLADERUNNER_LIGHTS_H
#include "bladerunner/bladerunner.h"
#include "bladerunner/color.h"
#include "bladerunner/light.h"
#include "common/stream.h"
namespace BladeRunner {
class Lights {
friend class Debugger;
friend class SliceRendererLights;
BladeRunnerEngine *_vm;
Color _ambientLightColor;
Common::Array<Light *> _lights;
int _frame;
public:
Lights(BladeRunnerEngine *vm);
~Lights();
void read(Common::ReadStream *stream, int frameCount);
void readVqa(Common::ReadStream *stream);
void reset();
void setupFrame(int frame);
private:
void removeAnimated();
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,168 @@
/* 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/matrix.h"
#include "common/scummsys.h"
namespace BladeRunner {
Matrix3x2::Matrix3x2() {
for (int r = 0; r != 2; ++r)
for (int c = 0; c != 3; ++c)
_m[r][c] = (r == c) ? 1.0f : 0.0f;
}
Matrix3x2::Matrix3x2(float d[6]) {
for (int r = 0; r != 2; ++r)
for (int c = 0; c != 3; ++c)
_m[r][c] = d[r*3+c];
}
Matrix3x2::Matrix3x2(
float m00, float m01, float m02,
float m10, float m11, float m12) {
_m[0][0] = m00;
_m[0][1] = m01;
_m[0][2] = m02;
_m[1][0] = m10;
_m[1][1] = m11;
_m[1][2] = m12;
}
Matrix4x3::Matrix4x3() {
for (int r = 0; r != 3; ++r)
for (int c = 0; c != 4; ++c)
_m[r][c] = (r == c) ? 1.0f : 0.0f;
}
Matrix4x3::Matrix4x3(float d[12]) {
for (int r = 0; r != 3; ++r)
for (int c = 0; c != 4; ++c)
_m[r][c] = d[r*4+c];
}
Matrix4x3::Matrix4x3(
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23) {
_m[0][0] = m00;
_m[0][1] = m01;
_m[0][2] = m02;
_m[0][3] = m03;
_m[1][0] = m10;
_m[1][1] = m11;
_m[1][2] = m12;
_m[1][3] = m13;
_m[2][0] = m20;
_m[2][1] = m21;
_m[2][2] = m22;
_m[2][3] = m23;
}
Matrix4x3 rotationMatrixX(float angle) {
float ca = cos(angle);
float sa = sin(angle);
return Matrix4x3( 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, ca, sa, 0.0f,
0.0f, -sa, ca, 0.0f );
}
static inline void swapRows(double *r1, double *r2) {
for (int c = 0; c != 8; ++c) {
double t = r1[c];
r1[c] = r2[c];
r2[c] = t;
}
}
static inline void subtractRow(double *r1, double factor, double *r2) {
for (int c = 0; c != 8; ++c)
r1[c] -= factor * r2[c];
}
static inline void divideRow(double *r1, double d) {
for (int c = 0; c != 8; ++c)
r1[c] /= d;
}
Matrix4x3 invertMatrix(const Matrix4x3 &m) {
double w[3][8];
for (int r = 0; r != 3; ++r) {
for (int c = 0; c != 4; ++c) {
w[r][c] = m(r, c);
w[r][c+4] = (r == c) ? 1.0 : 0.0;
}
}
if (w[0][0] == 0.0) {
if (w[1][0] != 0.0)
swapRows(w[0], w[1]);
else
swapRows(w[0], w[2]);
}
divideRow(w[0], w[0][0]);
subtractRow(w[1], w[1][0], w[0]);
subtractRow(w[2], w[2][0], w[0]);
if (w[1][1] == 0.0)
swapRows(w[1], w[2]);
divideRow(w[1], w[1][1]);
subtractRow(w[0], w[0][1], w[1]);
subtractRow(w[2], w[2][1], w[1]);
divideRow(w[2], w[2][2]);
subtractRow(w[0], w[0][2], w[2]);
subtractRow(w[1], w[1][2], w[2]);
for (int r = 0; r != 3; ++r) {
w[r][7] = -w[r][3];
w[r][3] = 0.0;
}
Matrix4x3 result;
for (int r = 0; r != 3; ++r)
for (int c = 0; c != 4; ++c)
result(r, c) = float(w[r][c+4]);
return result;
}
void Matrix4x3::unknown() {
Matrix4x3 t;
// Transpose the 3x3 top left submatrix
for (int r = 0; r != 3; ++r)
for (int c = 0; c != 3; ++c)
t(r, c) = _m[c][r];
t(0,3) = -(_m[0][3] * _m[0][0] + _m[1][3] * _m[1][0] + _m[2][3] * _m[2][0]);
t(1,3) = -(_m[0][3] * _m[0][1] + _m[1][3] * _m[1][1] + _m[2][3] * _m[2][1]);
t(2,3) = -(_m[0][3] * _m[0][2] + _m[1][3] * _m[1][2] + _m[2][3] * _m[2][2]);
*this = t;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,119 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_MATRIX_H
#define BLADERUNNER_MATRIX_H
#include "bladerunner/vector.h"
namespace BladeRunner {
class Matrix3x2 {
public:
float _m[2][3];
Matrix3x2();
Matrix3x2(float d[6]);
Matrix3x2(
float m00, float m01, float m02,
float m10, float m11, float m12);
float &operator()(int r, int c) { assert(r >= 0 && r < 2); assert(c >= 0 && c < 3); return _m[r][c]; }
const float &operator()(int r, int c) const { assert(r >= 0 && r < 2); assert(c >= 0 && c < 3); return _m[r][c]; }
};
inline Matrix3x2 operator*(const Matrix3x2 &a, const Matrix3x2 &b) {
Matrix3x2 t;
t(0, 0) = a(0, 0) * b(0, 0) + a(0, 1) * b(1, 0);
t(0, 1) = a(0, 0) * b(0, 1) + a(0, 1) * b(1, 1);
t(0, 2) = a(0, 0) * b(0, 2) + a(0, 1) * b(1, 2) + a(0, 2);
t(1, 0) = a(1, 0) * b(0, 0) + a(1, 1) * b(1, 0);
t(1, 1) = a(1, 0) * b(0, 1) + a(1, 1) * b(1, 1);
t(1, 2) = a(1, 0) * b(0, 2) + a(1, 1) * b(1, 2) + a(1, 2);
return t;
}
inline Matrix3x2 operator+(const Matrix3x2 &a, Vector2 b) {
Matrix3x2 t(a);
t(0, 2) += b.x;
t(1, 2) += b.y;
return t;
}
inline Vector2 operator*(const Matrix3x2 &a, Vector2 b) {
Vector2 t;
t.x = a(0, 0) * b.x + a(0, 1) * b.y + a(0, 2);
t.y = a(1, 0) * b.x + a(1, 1) * b.y + a(1, 2);
return t;
}
class Matrix4x3 {
public:
float _m[3][4];
Matrix4x3();
Matrix4x3(float d[12]);
Matrix4x3(
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23);
float &operator()(int r, int c) { assert(r >= 0 && r < 3); assert(c >= 0 && c < 4); return _m[r][c]; }
const float &operator()(int r, int c) const { assert(r >= 0 && r < 3); assert(c >= 0 && c < 4); return _m[r][c]; }
void unknown();
};
Matrix4x3 invertMatrix(const Matrix4x3 &m);
Matrix4x3 rotationMatrixX(float angle);
inline Matrix4x3 operator*(const Matrix4x3 &a, const Matrix4x3 &b) {
Matrix4x3 t;
for (int i = 0; i !=3; ++i) {
t(i, 0) = a(i, 0) * b(0, 0) + a(i, 1) * b(1, 0) + a(i, 2) * b(2, 0);
t(i, 1) = a(i, 0) * b(0, 1) + a(i, 1) * b(1, 1) + a(i, 2) * b(2, 1);
t(i, 2) = a(i, 0) * b(0, 2) + a(i, 1) * b(1, 2) + a(i, 2) * b(2, 2);
t(i, 3) = a(i, 0) * b(0, 3) + a(i, 1) * b(1, 3) + a(i, 2) * b(2, 3) + a(i, 3);
}
return t;
}
inline Vector3 operator*(const Matrix4x3 &m, const Vector3 &v) {
Vector3 r;
r.x = m(0, 0) * v.x + m(0, 1) * v.y + m(0, 2) * v.z + m(0, 3);
r.y = m(1, 0) * v.x + m(1, 1) * v.y + m(1, 2) * v.z + m(1, 3);
r.z = m(2, 0) * v.x + m(2, 1) * v.y + m(2, 2) * v.z + m(2, 3);
return r;
}
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,408 @@
/* 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/detection.h"
#include "bladerunner/savefile.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/standard-actions.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/serializer.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
namespace BladeRunner {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_SITCOM,
{
_s("Sitcom mode"),
_s("Game will add laughter after actor's line or narration"),
"sitcom",
false,
0,
0
}
},
{
GAMEOPTION_SHORTY,
{
_s("Shorty mode"),
_s("Game will shrink the actors and make their voices high pitched"),
"shorty",
false,
0,
0
}
},
{
GAMEOPTION_FRAMELIMITER_NODELAYMILLIS,
{
_s("Frame limiter high performance mode"),
_s("This mode may result in high CPU usage! It avoids use of delayMillis() function."),
"nodelaymillisfl",
false,
0,
0
}
},
{
GAMEOPTION_FRAMELIMITER_FPS,
{
_s("Max frames per second limit"),
_s("This mode targets a maximum of 120 fps. When disabled, the game targets 60 fps"),
"frames_per_secondfl",
false,
0,
0
}
},
{
GAMEOPTION_DISABLE_STAMINA_DRAIN,
{
_s("Disable McCoy's quick stamina drain"),
_s("When running, McCoy won't start slowing down as soon as the player stops clicking the mouse"),
"disable_stamina_drain",
false,
0,
0
}
},
{
GAMEOPTION_SHOW_SUBS_IN_CRAWL,
{
_s("Show subtitles during text crawl"),
_s("During the intro cutscene, show subtitles during the text crawl"),
"use_crawl_subs",
true,
0,
0
}
},
{
GAMEOPTION_FIX_SPANISH_CREDITS,
{
_s("Fix credits for voice actors"),
_s("Updates the end credits with corrected credits for the Spanish voice actors"),
"correct_spanish_credits",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
} // End of namespace BladeRunner
class BladeRunnerMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override;
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return BladeRunner::optionsList;
}
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
bool hasFeature(MetaEngineFeature f) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
bool removeSaveState(const char *target, int slot) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
// Disable autosave (see mirrored method in bladerunner.h for detailed explanation)
int getAutosaveSlot() const override { return -1; }
};
const char *BladeRunnerMetaEngine::getName() const {
return "bladerunner";
}
Common::Error BladeRunnerMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
*engine = new BladeRunner::BladeRunnerEngine(syst, desc);
return Common::kNoError;
}
bool BladeRunnerMetaEngine::hasFeature(MetaEngineFeature f) const {
return
f == kSupportsListSaves ||
f == kSupportsLoadingDuringStartup ||
f == kSupportsDeleteSave ||
f == kSavesSupportMetaInfo ||
f == kSavesSupportThumbnail ||
f == kSavesSupportCreationDate ||
f == kSavesSupportPlayTime ||
f == kSimpleSavesNames;
}
Common::KeymapArray BladeRunnerMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace BladeRunner;
Common::String gameId = ConfMan.get("gameid", target);
Common::U32String gameDesc;
Keymap *commonKeymap;
Keymap *gameplayKeymap;
Keymap *kiaOnlyKeymap;
if (gameId == "bladerunner") {
gameDesc = "Blade Runner";
} else if (gameId == "bladerunner-final") {
gameDesc = "Blade Runner (Restored Content)";
} else if (gameId == "bladerunner-ee") {
gameDesc = "Blade Runner: Enhanced Edition";
}
if (gameDesc.empty()) {
return AdvancedMetaEngine::initKeymaps(target);
}
// We use 3 keymaps: common (main game and KIA), gameplay (main game only) and kia (KIA only).
// This helps us with disabling unneeded keymaps, which is especially useful in KIA, when typing in a saved game.
// In general, Blade Runner by default, can bind a key (eg. spacebar) to multiple actions
// (eg. skip cutscene, toggle combat, enter a blank space in save game input field).
// We need to be able to disable the conflicting keymaps, while keeping others that should still work in KIA
// (eg. "Esc" (by default) toggling KIA should work in normal gameplay and also within KIA).
// Another related issue we tackle is that a custom action event does not maintain the keycode and ascii value
// (if it was associated with a keyboard key), and there's no obvious way to retrieve those from it.
// Thus, a custom action event cannot be somehow utilised to produce keyboard key presses
// (again if a keyboard key is mapped to that action), so it cannot by itself be used
// for text entering in the save file name input field, or for typing the Easter Egg strings.
// I18N: These are keymaps that work in the main gameplay and also when KIA (Knowledge Integration Assistant) is open.
commonKeymap = new Keymap(Keymap::kKeymapTypeGame, BladeRunnerEngine::kCommonKeymapId, gameDesc + Common::U32String(" - ") + _("common shortcuts"));
// I18N: These are keymaps which work only in the main gameplay and not within KIA's (Knowledge Integration Assistant) screens.
gameplayKeymap = new Keymap(Keymap::kKeymapTypeGame, BladeRunnerEngine::kGameplayKeymapId, gameDesc + Common::U32String(" - ") + _("main game shortcuts"));
// I18N: These are keymaps that work only within KIA's (Knowledge Integration Assistant) screens.
kiaOnlyKeymap = new Keymap(Keymap::kKeymapTypeGame, BladeRunnerEngine::kKiaKeymapId, gameDesc + Common::U32String(" - ") + _("KIA only shortcuts"));
Action *act;
// Look at backends\keymapper\hardware-input.cpp for the strings that can be used in InputMapping
// I18N: This keymap is the main way for the user interact with the game.
// It is used with the game's cursor to select, walk-to, run-to, look-at, talk-to, pick up, use, shoot (combat mode), open KIA (when clicking on McCoy).
act = new Action(kStandardActionLeftClick, _("Walk / Look / Talk / Select / Shoot"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
commonKeymap->addAction(act);
// I18N: This keymap toggles McCoy's status between combat mode (drawing his gun) and normal mode (holstering his gun)
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// TOGGLE MCCOY'S COMBAT MODE
act = new Action("COMBAT", _("Toggle combat"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionToggleCombat);
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("MOUSE_MIDDLE");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("SPACE");
gameplayKeymap->addAction(act);
// I18N: This keymap allows skipping video cutscenes
act = new Action("SKIPVIDEO", _("Skip cutscene"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionCutsceneSkip);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("SPACE");
act->addDefaultInputMapping("JOY_Y");
gameplayKeymap->addAction(act);
// I18N: This keymap allows skipping the current line of dialog.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// SKIP PAST CURRENT LINE OF DIALOG
act = new Action("SKIPDLG", _("Skip dialog"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionDialogueSkip);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("JOY_X");
gameplayKeymap->addAction(act);
// I18N: This keymap toggles between opening the KIA in the Game Options tab, and closing the KIA.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// GAME OPTIONS
act = new Action("KIAOPTS", _("Game options"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionToggleKiaOptions);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_Y");
commonKeymap->addAction(act);
// I18N: This keymap opens the KIA database on one of the investigation tabs,
// CRIME SCENE DATABASE, SUSPECT DATABASE and CLUES DATABASE.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// ACTIVATE KIA CLUE DATABASE SYSTEM
act = new Action("KIADB", _("Open KIA database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKiaDatabase);
act->addDefaultInputMapping("TAB");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameplayKeymap->addAction(act);
// I18N: This keymap works within the KIA Save Game screen
// and allows confirming popup dialog prompts (eg. for save game deletion or overwriting)
// and also submitting a new save game name, or choosing an existing save game for overwriting.
act = new Action("KIACONFIRMDLG", _("Confirm"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpConfirmDlg);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("JOY_B");
kiaOnlyKeymap->addAction(act);
// I18N: This keymap works within the KIA Save Game screen
// and allows submitting a selected existing save game for deletion.
act = new Action("KIADELETESVDGAME", _("Delete selected saved game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpDeleteSelectedSvdGame);
act->addDefaultInputMapping("DELETE");
// TODO In the original KP_PERIOD with NUMLOCK on, would work as a normal '.' character.
// KP_PERIOD with NUMLOCK off, would work as a delete request for the selected saved game.
// However, NUMLOCK is currently not working as a modifier key for keymaps,
// so maybe we should implement this original behavior more accurately,
// when that is fixed in the keymapper or hardware-input code.
// For now, KP_PERIOD will work (by default) as a delete request.
act->addDefaultInputMapping("KP_PERIOD");
act->addDefaultInputMapping("JOY_X");
kiaOnlyKeymap->addAction(act);
// I18N: This keymap allows scrolling texts and lists upwards
act = new Action("KIASCROLLUP", _("Scroll up"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionScrollUp);
act->addDefaultInputMapping("MOUSE_WHEEL_UP");
act->addDefaultInputMapping("JOY_UP");
kiaOnlyKeymap->addAction(act);
// I18N: This keymap allows scrolling texts and lists downwards
act = new Action("KIASCROLLDOWN", _("Scroll down"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionScrollDown);
act->addDefaultInputMapping("MOUSE_WHEEL_DOWN");
act->addDefaultInputMapping("JOY_DOWN");
kiaOnlyKeymap->addAction(act);
// I18N: This keymap allows (in KIA only) for a clue to be set as private or public
// (only when the KIA is upgraded).
act = new Action("KIATOGGLECLUEPRIVACY", _("Toggle clue privacy"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionToggleCluePrivacy);
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
kiaOnlyKeymap->addAction(act);
// I18N: This keymap opens KIA's HELP tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// ONLINE HELP
act = new Action("KIAHLP", _("Help"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabHelp);
act->addDefaultInputMapping("F1");
commonKeymap->addAction(act);
// I18N: This keymap opens KIA's SAVE GAME tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// SAVE GAME
act = new Action("KIASAVE", _("Save game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabSaveGame);
act->addDefaultInputMapping("F2");
commonKeymap->addAction(act);
// I18N: This keymap opens KIA's LOAD GAME tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// LOAD GAME
act = new Action("KIALOAD", _("Load game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabLoadGame);
act->addDefaultInputMapping("F3");
commonKeymap->addAction(act);
// I18N: This keymap opens KIA's CRIME SCENE DATABASE tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// CRIME SCENE DATABASE
act = new Action("KIACRIMES", _("Crime scene database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabCrimeSceneDatabase);
act->addDefaultInputMapping("F4");
commonKeymap->addAction(act);
// I18N: This keymap opens KIA's SUSPECT DATABASE tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// SUSPECT DATABASE
act = new Action("KIASUSPECTS", _("Suspect database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabSuspectDatabase);
act->addDefaultInputMapping("F5");
commonKeymap->addAction(act);
// I18N: This keymap opens KIA's CLUE DATABASE tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// CLUE DATABASE
act = new Action("KIACLUES", _("Clue database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabClueDatabase);
act->addDefaultInputMapping("F6");
commonKeymap->addAction(act);
// I18N: This keymap opens KIA's QUIT GAME tab.
// In Blade Runner's official localizations, there is a description of this keymap
// on the KIA Help Page, under Keyboard Shortcuts. In the English version it is
// QUIT GAME
act = new Action("KIAQUIT", _("Quit game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabQuitGame);
act->addDefaultInputMapping("F10");
commonKeymap->addAction(act);
KeymapArray keymaps(3);
keymaps[0] = commonKeymap;
keymaps[1] = gameplayKeymap;
keymaps[2] = kiaOnlyKeymap;
return keymaps;
}
SaveStateList BladeRunnerMetaEngine::listSaves(const char *target) const {
return BladeRunner::SaveFileManager::list(this, target);
}
int BladeRunnerMetaEngine::getMaximumSaveSlot() const {
return 999;
}
bool BladeRunnerMetaEngine::removeSaveState(const char *target, int slot) const {
return BladeRunner::SaveFileManager::remove(target, slot);
}
SaveStateDescriptor BladeRunnerMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
return BladeRunner::SaveFileManager::queryMetaInfos(this, target, slot);
}
#if PLUGIN_ENABLED_DYNAMIC(BLADERUNNER)
REGISTER_PLUGIN_DYNAMIC(BLADERUNNER, PLUGIN_TYPE_ENGINE, BladeRunnerMetaEngine);
#else
REGISTER_PLUGIN_STATIC(BLADERUNNER, PLUGIN_TYPE_ENGINE, BladeRunnerMetaEngine);
#endif

View File

@@ -0,0 +1,294 @@
MODULE := engines/bladerunner
MODULE_OBJS = \
actor.o \
actor_clues.o \
actor_combat.o \
actor_dialogue_queue.o \
actor_walk.o \
adpcm_decoder.o \
ambient_sounds.o \
archive.o \
aud_stream.o \
audio_cache.o \
audio_mixer.o \
audio_player.o \
audio_speech.o \
bladerunner.o \
boundingbox.o \
chapters.o \
color.o \
combat.o \
crimes_database.o \
debugger.o \
decompress_lcw.o \
decompress_lzo.o \
dialogue_menu.o \
framelimiter.o \
fog.o \
font.o \
game_flags.o \
game_info.o \
image.o \
item.o \
item_pickup.o \
items.o \
light.o \
lights.o \
matrix.o \
metaengine.o \
mouse.o \
movement_track.o \
music.o \
obstacles.o \
outtake.o \
overlays.o \
regions.o \
savefile.o \
scene.o \
scene_objects.o \
screen_effects.o \
script/script.o \
script/init_script.o \
script/kia_script.o \
script/vk_script.o \
script/esper_script.o \
script/police_maze.o \
script/ai_script.o \
script/ai/answering_machine.o \
script/ai/baker.o \
script/ai/blimp_guy.o \
script/ai/bryant.o \
script/ai/bullet_bob.o \
script/ai/chew.o \
script/ai/clovis.o \
script/ai/crazylegs.o \
script/ai/dektora.o \
script/ai/desk_clerk.o \
script/ai/dispatcher.o \
script/ai/early_q.o \
script/ai/early_q_bartender.o \
script/ai/fish_dealer.o \
script/ai/free_slot_a.o \
script/ai/free_slot_b.o \
script/ai/gaff.o \
script/ai/general_doll.o \
script/ai/generic_walker_a.o \
script/ai/generic_walker_b.o \
script/ai/generic_walker_c.o \
script/ai/gordo.o \
script/ai/governor_kolvig.o \
script/ai/grigorian.o \
script/ai/guzza.o \
script/ai/hanoi.o \
script/ai/hasan.o \
script/ai/hawkers_barkeep.o \
script/ai/hawkers_parrot.o \
script/ai/holloway.o \
script/ai/howie_lee.o \
script/ai/hysteria_patron1.o \
script/ai/hysteria_patron2.o \
script/ai/hysteria_patron3.o \
script/ai/insect_dealer.o \
script/ai/isabella.o \
script/ai/izo.o \
script/ai/klein.o \
script/ai/lance.o \
script/ai/leon.o \
script/ai/lockup_guard.o \
script/ai/lucy.o \
script/ai/luther.o \
script/ai/maggie.o \
script/ai/male_announcer.o \
script/ai/marcus.o \
script/ai/mccoy.o \
script/ai/mia.o \
script/ai/moraji.o \
script/ai/murray.o \
script/ai/mutant1.o \
script/ai/mutant2.o \
script/ai/mutant3.o \
script/ai/newscaster.o \
script/ai/officer_grayford.o \
script/ai/officer_leary.o \
script/ai/photographer.o \
script/ai/rachael.o \
script/ai/rajif.o \
script/ai/runciter.o \
script/ai/sadik.o \
script/ai/sebastian.o \
script/ai/sergeant_walls.o \
script/ai/shoeshine_man.o \
script/ai/steele.o \
script/ai/taffy.o \
script/ai/taffy_patron.o \
script/ai/teenager.o \
script/ai/the_bard.o \
script/ai/transient.o \
script/ai/tyrell.o \
script/ai/tyrell_guard.o \
script/ai/zuben.o \
script/scene_script.o \
script/scene/ar01.o \
script/scene/ar02.o \
script/scene/bb01.o \
script/scene/bb02.o \
script/scene/bb03.o \
script/scene/bb04.o \
script/scene/bb05.o \
script/scene/bb06.o \
script/scene/bb07.o \
script/scene/bb08.o \
script/scene/bb09.o \
script/scene/bb10.o \
script/scene/bb11.o \
script/scene/bb12.o \
script/scene/bb51.o \
script/scene/ct01.o \
script/scene/ct02.o \
script/scene/ct03.o \
script/scene/ct04.o \
script/scene/ct05.o \
script/scene/ct06.o \
script/scene/ct07.o \
script/scene/ct08.o \
script/scene/ct09.o \
script/scene/ct10.o \
script/scene/ct11.o \
script/scene/ct12.o \
script/scene/ct51.o \
script/scene/dr01.o \
script/scene/dr02.o \
script/scene/dr03.o \
script/scene/dr04.o \
script/scene/dr05.o \
script/scene/dr06.o \
script/scene/hc01.o \
script/scene/hc02.o \
script/scene/hc03.o \
script/scene/hc04.o \
script/scene/hf01.o \
script/scene/hf02.o \
script/scene/hf03.o \
script/scene/hf04.o \
script/scene/hf05.o \
script/scene/hf06.o \
script/scene/hf07.o \
script/scene/kp01.o \
script/scene/kp02.o \
script/scene/kp03.o \
script/scene/kp04.o \
script/scene/kp05.o \
script/scene/kp06.o \
script/scene/kp07.o \
script/scene/ma01.o \
script/scene/ma02.o \
script/scene/ma04.o \
script/scene/ma05.o \
script/scene/ma06.o \
script/scene/ma07.o \
script/scene/ma08.o \
script/scene/nr01.o \
script/scene/nr02.o \
script/scene/nr03.o \
script/scene/nr04.o \
script/scene/nr05.o \
script/scene/nr06.o \
script/scene/nr07.o \
script/scene/nr08.o \
script/scene/nr09.o \
script/scene/nr10.o \
script/scene/nr11.o \
script/scene/ps01.o \
script/scene/ps02.o \
script/scene/ps03.o \
script/scene/ps04.o \
script/scene/ps05.o \
script/scene/ps06.o \
script/scene/ps07.o \
script/scene/ps09.o \
script/scene/ps10.o \
script/scene/ps11.o \
script/scene/ps12.o \
script/scene/ps13.o \
script/scene/ps14.o \
script/scene/ps15.o \
script/scene/rc01.o \
script/scene/rc02.o \
script/scene/rc03.o \
script/scene/rc04.o \
script/scene/rc51.o \
script/scene/tb02.o \
script/scene/tb03.o \
script/scene/tb05.o \
script/scene/tb06.o \
script/scene/tb07.o \
script/scene/ug01.o \
script/scene/ug02.o \
script/scene/ug03.o \
script/scene/ug04.o \
script/scene/ug05.o \
script/scene/ug06.o \
script/scene/ug07.o \
script/scene/ug08.o \
script/scene/ug09.o \
script/scene/ug10.o \
script/scene/ug12.o \
script/scene/ug13.o \
script/scene/ug14.o \
script/scene/ug15.o \
script/scene/ug16.o \
script/scene/ug17.o \
script/scene/ug18.o \
script/scene/ug19.o \
set.o \
settings.o \
set_effects.o \
shape.o \
slice_animations.o \
slice_renderer.o \
subtitles.o \
suspects_database.o \
text_resource.o \
time.o \
ui/elevator.o \
ui/end_credits.o \
ui/esper.o \
ui/kia.o \
ui/kia_log.o \
ui/kia_section_base.o \
ui/kia_section_clues.o \
ui/kia_section_crimes.o \
ui/kia_section_diagnostic.o \
ui/kia_section_help.o \
ui/kia_section_load.o \
ui/kia_section_pogo.o \
ui/kia_section_save.o \
ui/kia_section_settings.o \
ui/kia_section_suspects.o \
ui/scores.o \
ui/spinner.o \
ui/ui_check_box.o \
ui/ui_container.o \
ui/ui_image_picker.o \
ui/ui_input_box.o \
ui/ui_scroll_box.o \
ui/ui_slider.o \
ui/ui_dropdown.o \
ui/vk.o \
view.o \
vqa_decoder.o \
vqa_player.o \
waypoints.o \
zbuffer.o
# This module can be built as a plugin
ifeq ($(ENABLE_BLADERUNNER), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

View File

@@ -0,0 +1,680 @@
/* 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/mouse.h"
#include "bladerunner/actor.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/combat.h"
#include "bladerunner/debugger.h"
#include "bladerunner/dialogue_menu.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/items.h"
#include "bladerunner/regions.h"
#include "bladerunner/scene.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/settings.h"
#include "bladerunner/shape.h"
#include "bladerunner/time.h"
#include "bladerunner/view.h"
#include "bladerunner/zbuffer.h"
#include "graphics/surface.h"
namespace BladeRunner {
Mouse::Mouse(BladeRunnerEngine *vm) {
_vm = vm;
_cursor = 0;
_frame = 3;
_hotspotX = 0;
_hotspotY = 0;
_x = 0;
_y = 0;
_disabledCounter = 0;
_lastFrameTime = 0u;
_animCounter = 0;
_randomCountdownX = 0;
_randomCountdownY = 0;
_randomX = 0;
_randomY = 0;
_drawModeBitFlags = 0;
}
Mouse::~Mouse() {
}
void Mouse::setCursor(int cursor) {
assert(cursor >= 0 && cursor <= 17);
if (cursor == _cursor) {
return;
}
_cursor = cursor;
_drawModeBitFlags = 0;
switch (_cursor) {
case 0:
// normal cursor (white)
// (also the default init value for mouse cursor)
_frame = 3;
_hotspotX = 0;
_hotspotY = 0;
break;
case 1:
// normal cursor over hotspot (not exit) (green rotating)
// animating: 8 frames (4-11)
_frame = 4;
_hotspotX = 0;
_hotspotY = 0;
if (_vm->_debugger->_useAdditiveDrawModeForMouseCursorMode0
|| _vm->_debugger->_useAdditiveDrawModeForMouseCursorMode1) {
_drawModeBitFlags |= MouseDrawFlags::SPECIAL;
if (_vm->_debugger->_useAdditiveDrawModeForMouseCursorMode0) {
_drawModeBitFlags |= MouseDrawFlags::ADDITIVE_MODE0;
} else {
_drawModeBitFlags |= MouseDrawFlags::ADDITIVE_MODE1;
}
}
break;
case 2:
// static exit cursor (upwards/North)
_frame = 12;
_hotspotX = 12;
_hotspotY = 0;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::ESPER_UP);
} else {
_drawModeBitFlags &= ~(MouseDrawFlags::CUSTOM | MouseDrawFlags::ESPER_UP);
}
break;
case 3:
// static exit cursor (right/East)
_frame = 15;
_hotspotX = 23;
_hotspotY = 12;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::ESPER_RIGHT);
} else {
_drawModeBitFlags &= ~(MouseDrawFlags::CUSTOM | MouseDrawFlags::ESPER_RIGHT);
}
break;
case 4:
// static exit cursor (downwards/South)
_frame = 13;
_hotspotX = 12;
_hotspotY = 23;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::ESPER_DOWN);
}
break;
case 5:
// static exit cursor (left/West)
_frame = 14;
_hotspotX = 0;
_hotspotY = 12;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::ESPER_LEFT);
}
break;
case 6:
// combat cursor, simple bullets (normal / no target)
_frame = 16;
_hotspotX = 19;
_hotspotY = 19;
break;
case 7:
// combat cursor, simple bullets (hot target)
// animating: 8 frames (17-24)
_frame = 17;
_hotspotX = 19;
_hotspotY = 19;
break;
case 8:
// combat cursor, advanced bullets (normal / no target)
_frame = 25;
_hotspotX = 19;
_hotspotY = 19;
break;
case 9:
// combat cursor, advanced bullets (hot target)
// animating: 8 frames (26-33)
_frame = 26;
_hotspotX = 19;
_hotspotY = 19;
break;
case 10:
// combat cursor, best bullets (normal / no target)
_frame = 34;
_hotspotX = 19;
_hotspotY = 19;
break;
case 11:
// combat cursor, best bullets (hot target)
// animating: 8 frames (35-42)
_frame = 35;
_hotspotX = 19;
_hotspotY = 19;
break;
case 12:
// exit cursor (upwards/North)
// resets animCounter too (as opposed to _cursor == 2)
// bouncy animation (handled in updateCursorFrame())
_frame = 12;
_hotspotX = 12;
_hotspotY = 0;
_animCounter = 0;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::EXIT_UP);
}
break;
case 13:
// exit cursor (right/East)
// resets animCounter too (as opposed to _cursor == 3)
// bouncy animation (handled in updateCursorFrame())
_frame = 15;
_hotspotX = 23;
_hotspotY = 12;
_animCounter = 0;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::EXIT_RIGHT);
}
break;
case 14:
// exit cursor (downwards/South)
// resets animCounter too (as opposed to _cursor == 4)
// bouncy animation (handled in updateCursorFrame())
_frame = 13;
_hotspotX = 12;
_hotspotY = 23;
_animCounter = 0;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::EXIT_DOWN);
}
break;
case 15:
// exit cursor (left/West)
// resets animCounter too (as opposed to _cursor == 5)
// bouncy animation (handled in updateCursorFrame())
_frame = 14;
_hotspotX = 0;
_hotspotY = 12;
_animCounter = 0;
if (_vm->_debugger->_useBetaCrosshairsCursor) {
_drawModeBitFlags |= (MouseDrawFlags::CUSTOM | MouseDrawFlags::EXIT_LEFT);
}
break;
case 16:
// (beta version) combat cursor (inactive)
_drawModeBitFlags &= ~(0x01 << _vm->_settings->getAmmoType());
_drawModeBitFlags &= ~(MouseDrawFlags::SPECIAL);
#if !BLADERUNNER_ORIGINAL_BUGS
_frame = 0;
_hotspotX = 11;
_hotspotY = 11;
break;
case 17:
#endif
// (beta version) combat cursor (white or flashing white/blue)
// Original behavior in the code remnandts seems to have been
// that this cursor (id: 16) would animate (3 frames (0-2)),
// essentially continuously flashing fast.
// In the preview trailers, the cursor is white while not on a target
// and red when on target (shown on Zuben) -- perhaps other solid colors were used,
// (it's hard to tell the color at the shooting grounds shown in the preview trailer).
// We introduce an extra case (id 17) to differentiate the beta crosshairs
// depending on whether they're hovering over a hot target or not.
// TODO Maybe replace the solid colored frames with a color based on McCoy's bullet type
// So:
// id 16: inactive (beta) combat crosshairs
// id 17: active (beta) combat crosshairs
_drawModeBitFlags |= (0x01 << _vm->_settings->getAmmoType());
_drawModeBitFlags |= MouseDrawFlags::SPECIAL;
_frame = 1;
_hotspotX = 11;
_hotspotY = 11;
break;
default:
break;
}
}
void Mouse::getXY(int *x, int *y) const {
*x = _x;
*y = _y;
}
void Mouse::setMouseJitterUp() {
switch (_vm->_settings->getDifficulty()) {
default:
// fall through intended
case kGameDifficultyEasy:
_randomCountdownX = 2;
_randomX = _vm->_rnd.getRandomNumberRng(0, 6) - 3;
_randomY = _vm->_rnd.getRandomNumberRng(0, 10) - 20;
break;
case kGameDifficultyMedium:
_randomCountdownX = 3;
_randomX = _vm->_rnd.getRandomNumberRng(0, 8) - 4;
_randomY = _vm->_rnd.getRandomNumberRng(0, 10) - 25;
break;
case kGameDifficultyHard:
_randomCountdownX = 4;
_randomX = _vm->_rnd.getRandomNumberRng(0, 10) - 5;
_randomY = _vm->_rnd.getRandomNumberRng(0, 10) - 30;
break;
}
}
void Mouse::setMouseJitterDown() {
switch (_vm->_settings->getDifficulty()) {
default:
// fall through intended
case kGameDifficultyEasy:
_randomCountdownY = 2;
_randomX = _vm->_rnd.getRandomNumberRng(0, 6) - 3;
_randomY = _vm->_rnd.getRandomNumberRng(10, 20);
break;
case kGameDifficultyMedium:
_randomCountdownY = 3;
_randomX = _vm->_rnd.getRandomNumberRng(0, 8) - 4;
_randomY = _vm->_rnd.getRandomNumberRng(15, 25);
break;
case kGameDifficultyHard:
_randomCountdownY = 4;
_randomX = _vm->_rnd.getRandomNumberRng(0, 10) - 5;
_randomY = _vm->_rnd.getRandomNumberRng(20, 30);
break;
}
}
void Mouse::disable() {
++_disabledCounter;
_randomCountdownX = 0;
_randomCountdownY = 0;
}
void Mouse::enable(bool force) {
if (force || --_disabledCounter <= 0) {
_disabledCounter = 0;
}
}
bool Mouse::isDisabled() const {
return _disabledCounter > 0;
}
void Mouse::draw(Graphics::Surface &surface, int x, int y) {
if (_disabledCounter) {
_randomCountdownX = 0;
_randomCountdownY = 0;
return;
}
if (_randomCountdownX > 0) {
--_randomCountdownX;
x += _randomX;
y += _randomY;
if (!_randomCountdownX)
setMouseJitterDown();
} else if (_randomCountdownY > 0) {
--_randomCountdownY;
x += _randomX;
y += _randomY;
}
_x = CLIP(x, 0, surface.w - 1);
_y = CLIP(y, 0, surface.h - 1);
_vm->_shapes->get(_frame)->draw(surface, _x - _hotspotX, _y - _hotspotY, _drawModeBitFlags);
updateCursorFrame();
}
void Mouse::updateCursorFrame() {
uint32 now = _vm->_time->current();
const int offset[4] = { 0, 6, 12, 6 };
if (now - _lastFrameTime < 66) {
return;
}
_lastFrameTime = now;
switch (_cursor) {
case 0:
break;
case 1:
if (++_frame > 11)
_frame = 4;
break;
case 2:
// fall through
case 3:
// fall through
case 4:
// fall through
case 5:
// fall through
// 2,3,4,5 are case for "static" exit arrows, used in ESPER
case 6:
// 6 is combat cursor, simple bullets (normal / no target)
break;
case 7:
if (++_frame > 24)
_frame = 17;
break;
case 8:
break;
case 9:
if (++_frame > 33)
_frame = 26;
break;
case 10:
break;
case 11:
if (++_frame > 42)
_frame = 35;
break;
case 12:
if ((_drawModeBitFlags & Mouse::MouseDrawFlags::CUSTOM)
&& (_drawModeBitFlags & Mouse::MouseDrawFlags::EXIT_UP)) {
// use the 3 least significant bits in place of "frame" index
_drawModeBitFlags +=1;
if ((_drawModeBitFlags & 0x7) == 0x7) {
_drawModeBitFlags &= ~0x7;
}
} else {
if (++_animCounter >= 4) {
_animCounter = 0;
}
_hotspotY = -offset[_animCounter];
}
break;
case 13:
if ((_drawModeBitFlags & Mouse::MouseDrawFlags::CUSTOM)
&& (_drawModeBitFlags & Mouse::MouseDrawFlags::EXIT_RIGHT)) {
// use the 3 least significant bits in place of "frame" index
_drawModeBitFlags +=1;
if ((_drawModeBitFlags & 0x7) == 0x7) {
_drawModeBitFlags &= ~0x7;
}
} else {
if (++_animCounter >= 4) {
_animCounter = 0;
}
_hotspotX = 23 + offset[_animCounter];
}
break;
case 14:
if ((_drawModeBitFlags & Mouse::MouseDrawFlags::CUSTOM)
&& (_drawModeBitFlags & Mouse::MouseDrawFlags::EXIT_DOWN)) {
// use the 3 least significant bits in place of "frame" index
_drawModeBitFlags +=1;
if ((_drawModeBitFlags & 0x7) == 0x7) {
_drawModeBitFlags &= ~0x7;
}
} else {
if (++_animCounter >= 4) {
_animCounter = 0;
}
_hotspotY = 23 + offset[_animCounter];
}
break;
case 15:
if ((_drawModeBitFlags & Mouse::MouseDrawFlags::CUSTOM)
&& (_drawModeBitFlags & Mouse::MouseDrawFlags::EXIT_LEFT)) {
// use the 3 least significant bits in place of "frame" index
_drawModeBitFlags +=1;
if ((_drawModeBitFlags & 0x7) == 0x7) {
_drawModeBitFlags &= ~0x7;
}
} else {
if (++_animCounter >= 4) {
_animCounter = 0;
}
_hotspotX = -offset[_animCounter];
}
break;
case 16:
#if !BLADERUNNER_ORIGINAL_BUGS
break;
case 17:
#endif
if (++_frame > 2)
#if BLADERUNNER_ORIGINAL_BUGS
_frame = 0;
#else
// Better not to flash the white frame (frame 0),
// while quickly animating the beta cursor.
// It's less annoying to the eyes this way.
_frame = 1;
#endif
break;
default:
break;
}
}
void Mouse::tick(int x, int y) {
if (!_vm->playerHasControl() || isDisabled()) {
return;
}
if (_vm->_dialogueMenu->isVisible()) {
setCursor(0);
return;
}
Vector3 scenePosition = getXYZ(x, y);
int cursorId = 0;
bool isClickable = false;
bool isObstacle = false;
bool isTarget = false;
int sceneObjectId = _vm->_sceneObjects->findByXYZ(&isClickable, &isObstacle, &isTarget, scenePosition, true, false, true);
int exitType = _vm->_scene->_exits->getTypeAtXY(x, y);
if (sceneObjectId >= kSceneObjectOffsetActors && sceneObjectId < kSceneObjectOffsetItems) {
exitType = -1;
}
if (exitType != -1) {
switch (exitType) {
case 0:
cursorId = 12;
break;
case 1:
cursorId = 13;
break;
case 2:
cursorId = 14;
break;
case 3:
cursorId = 15;
break;
default:
break;
}
setCursor(cursorId);
return;
}
if (!_vm->_combat->isActive()) {
if (sceneObjectId == kActorMcCoy + kSceneObjectOffsetActors
|| (sceneObjectId > 0 && isClickable)
|| _vm->_scene->_regions->getRegionAtXY(x, y) >= 0) {
cursorId = 1;
}
setCursor(cursorId);
return;
}
int animationMode = _vm->_playerActor->getAnimationMode();
int actorId = Actor::findTargetUnderMouse(_vm, x, y);
int itemId = _vm->_items->findTargetUnderMouse(x, y);
bool isObject = isTarget && sceneObjectId >= kSceneObjectOffsetObjects && sceneObjectId <= (95 + kSceneObjectOffsetObjects);
if (!_vm->_playerActor->isMoving()) {
if (actorId > 0) {
_vm->_playerActor->faceActor(actorId, false);
} else if (itemId >= 0) {
_vm->_playerActor->faceItem(itemId, false);
} else if (isObject) {
_vm->_playerActor->faceXYZ(scenePosition, false);
}
}
if (actorId >= 0 || itemId >= 0 || isObject) {
if (_vm->_debugger->_useBetaCrosshairsCursor) {
cursorId = 17;
} else {
switch (_vm->_settings->getAmmoType()) {
case 0:
cursorId = 7;
break;
case 1:
cursorId = 9;
break;
case 2:
cursorId = 11;
break;
default:
break;
}
}
if (!_vm->_playerActor->isMoving() && animationMode != kAnimationModeCombatAim && animationMode != kAnimationModeCombatHit && animationMode != kAnimationModeCombatDie) {
_vm->_playerActor->changeAnimationMode(kAnimationModeCombatAim, false);
}
} else {
if (_vm->_debugger->_useBetaCrosshairsCursor) {
cursorId = 16;
} else {
switch (_vm->_settings->getAmmoType()) {
case 0:
cursorId = 6;
break;
case 1:
cursorId = 8;
break;
case 2:
cursorId = 10;
break;
default:
break;
}
}
if (!_vm->_playerActor->isMoving() && animationMode != kAnimationModeCombatIdle && animationMode != kAnimationModeCombatHit && animationMode != kAnimationModeCombatDie) {
_vm->_playerActor->changeAnimationMode(kAnimationModeCombatIdle, false);
}
}
setCursor(cursorId);
}
// This method checks if jitter (due to gun recoil) is currently ongoing
bool Mouse::isRandomized() const {
return _randomCountdownX > 0 || _randomCountdownY > 0;
}
bool Mouse::isInactive() const {
// Note: This only refers to "inactive" cursor in combat mode!
return _cursor == 6 || _cursor == 8 || _cursor == 10 || _cursor == 16;
}
// TEST: RC01 after intro: [290, 216] -> [-204.589249 51.450668 7.659241]
Vector3 Mouse::getXYZ(int x, int y) const {
if (_vm->_scene->getSetId() == -1)
return Vector3();
int screenRight = BladeRunnerEngine::kOriginalGameWidth - x;
int screenDown = BladeRunnerEngine::kOriginalGameHeight - y;
float zcoef = 1.0f / tan(_vm->_view->_fovX / 2.0f);
// Division of float by int is float, so no precision is lost here
float x3d = (2.0f / BladeRunnerEngine::kOriginalGameWidth * screenRight - 1.0f);
float y3d = (2.0f / BladeRunnerEngine::kOriginalGameHeight * screenDown - 1.0f) * 0.75f;
uint16 zbufval = _vm->_zbuffer->getZValue(x, y);
Vector3 pos;
pos.z = zbufval / 25.5f;
pos.x = pos.z / zcoef * x3d;
pos.y = pos.z / zcoef * y3d;
Matrix4x3 matrix = _vm->_view->_frameViewMatrix;
matrix.unknown();
return matrix * pos;
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,99 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_MOUSE_H
#define BLADERUNNER_MOUSE_H
#include "bladerunner/vector.h"
namespace Graphics {
struct Surface;
}
namespace BladeRunner {
class BladeRunnerEngine;
class Mouse {
BladeRunnerEngine *_vm;
int _cursor;
int _frame;
int _hotspotX;
int _hotspotY;
int _x;
int _y;
int _disabledCounter;
uint32 _lastFrameTime;
int _animCounter;
int _randomCountdownX;
int _randomCountdownY;
int _randomX;
int _randomY;
uint16 _drawModeBitFlags; // replaces the additive bool with a set of bit flags (including flags for additive mode)
public:
Mouse(BladeRunnerEngine *vm);
~Mouse();
void setCursor(int cursor);
void getXY(int *x, int *y) const;
void setMouseJitterUp();
void setMouseJitterDown();
void disable();
void enable(bool force = false);
bool isDisabled() const;
void draw(Graphics::Surface &surface, int x, int y);
void updateCursorFrame();
void tick(int x, int y);
bool isRandomized() const;
bool isInactive() const;
Vector3 getXYZ(int x, int y) const;
typedef enum mouseDrawFlags {
REDCROSSHAIRS = 0x0001,
YELLOWCROSSHAIRS = 0x0002,
BLUECROSSHAIRS = 0x0004,
SPECIAL = 0x0008,
ADDITIVE_MODE0 = 0x0010,
ADDITIVE_MODE1 = 0x0020,
CUSTOM = 0x0040,
EXIT_UP = 0x0080,
EXIT_DOWN = 0x0100,
EXIT_LEFT = 0x0200,
EXIT_RIGHT = 0x0400,
ESPER_UP = 0x0800,
ESPER_DOWN = 0x1000,
ESPER_LEFT = 0x2000,
ESPER_RIGHT = 0x4000
} MouseDrawFlags;
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,139 @@
/* 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/movement_track.h"
#include "bladerunner/savefile.h"
namespace BladeRunner {
MovementTrack::MovementTrack() {
reset();
}
MovementTrack::~MovementTrack() {
reset();
}
void MovementTrack::reset() {
_currentIndex = -1;
_lastIndex = 0;
_hasNext = false;
_paused = false;
for (int i = 0; i < kSize; ++i) {
_entries[i].waypointId = -1;
_entries[i].delay = -1;
_entries[i].angle = -1;
_entries[i].run = false;
}
}
int MovementTrack::append(int waypointId, int32 delayMillis, bool run) {
return append(waypointId, delayMillis, -1, run);
}
int MovementTrack::append(int waypointId, int32 delayMillis, int angle, bool run) {
if (_lastIndex >= kSize) {
return 0;
}
_entries[_lastIndex].waypointId = waypointId;
_entries[_lastIndex].delay = delayMillis;
_entries[_lastIndex].angle = angle;
_entries[_lastIndex].run = run;
++_lastIndex;
_hasNext = true;
_currentIndex = 0;
return 1;
}
void MovementTrack::flush() {
reset();
}
void MovementTrack::repeat() {
_currentIndex = 0;
_hasNext = true;
}
void MovementTrack::pause() {
_paused = true;
}
void MovementTrack::unpause() {
_paused = false;
}
bool MovementTrack::isPaused() const {
return _paused;
}
bool MovementTrack::hasNext() const {
return _hasNext;
}
bool MovementTrack::next(int *waypointId, int32 *delayMillis, int *angle, bool *run) {
if (_currentIndex < _lastIndex && _hasNext) {
*waypointId = _entries[_currentIndex].waypointId;
*delayMillis = _entries[_currentIndex].delay;
*angle = _entries[_currentIndex].angle;
*run = _entries[_currentIndex++].run;
return true;
} else {
*waypointId = -1;
*delayMillis = -1;
*angle = -1;
*run = false;
_hasNext = false;
return false;
}
}
void MovementTrack::save(SaveFileWriteStream &f) {
f.writeInt(_currentIndex);
f.writeInt(_lastIndex);
f.writeBool(_hasNext);
f.writeBool(_paused);
for (int i = 0; i < kSize; ++i) {
Entry &e = _entries[i];
f.writeInt(e.waypointId);
f.writeInt(e.delay);
f.writeInt(e.angle);
f.writeBool(e.run);
}
}
void MovementTrack::load(SaveFileReadStream &f) {
_currentIndex = f.readInt();
_lastIndex = f.readInt();
_hasNext = f.readBool();
_paused = f.readBool();
for (int i = 0; i < kSize; ++i) {
Entry &e = _entries[i];
e.waypointId = f.readInt();
e.delay = f.readInt();
e.angle = f.readInt();
e.run = f.readBool();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,73 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_MOVEMENT_TRACK_H
#define BLADERUNNER_MOVEMENT_TRACK_H
#include "bladerunner/bladerunner.h"
namespace BladeRunner {
class BladeRunnerEngine;
class BoundingBox;
class SaveFileReadStream;
class SaveFileWriteStream;
class MovementTrack {
static const int kSize = 100;
struct Entry {
int waypointId;
// delay specifies how long (in milliseconds) to stay at the target waypoint (when reached)
int32 delay;
int angle;
bool run;
};
int _currentIndex;
int _lastIndex;
bool _hasNext;
bool _paused;
Entry _entries[kSize];
public:
MovementTrack();
~MovementTrack();
int append(int waypointId, int32 delayMillis, bool run);
int append(int waypointId, int32 delayMillis, int angle, bool run);
void flush();
void repeat();
void pause();
void unpause();
bool isPaused() const;
bool hasNext() const;
bool next(int *waypointId, int32 *delayMillis, int *angle, bool *run);
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
void reset();
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,468 @@
/* 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/music.h"
#include "bladerunner/audio_mixer.h"
#include "bladerunner/aud_stream.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/game_info.h"
#include "bladerunner/savefile.h"
#include "bladerunner/game_constants.h"
#include "common/timer.h"
namespace BladeRunner {
Music::Music(BladeRunnerEngine *vm) {
_vm = vm;
// _musicVolumeFactorOriginalEngine here sets a percentage to be applied on the music tracks' volume
// before sending them to the audio player.
// This is how the original engine set the volume via the in-game KIA volume slider controls.
// Setting _musicVolumeFactorOriginalEngine to 100, for the purposes ScummVM engine, renders it indifferent,
// so sound volume can be controlled by ScummVM's Global Main Menu / ConfMan/ syncSoundSettings().
_musicVolumeFactorOriginalEngine = BLADERUNNER_ORIGINAL_SETTINGS ? 65 : 100;
reset();
}
Music::~Music() {
stop(0u);
while (isPlaying()) {
// wait for the mixer to finish
}
#if BLADERUNNER_ORIGINAL_BUGS
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
#else
// probably not really needed, but tidy up anyway
reset();
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicNext);
#endif
}
void Music::reset() {
_current.name = "";
_next.name = "";
_channel = -1;
_isPlaying = false;
_isPaused = false;
_current.loop = 0;
_isNextPresent = false;
_data = nullptr;
_stream = nullptr;
}
// volume should be in [0, 100]
// pan should be in [-100, 100]
// A negative (typically -1) value for timePlaySeconds, means "play the whole music track".
bool Music::play(const Common::String &trackName, int volume, int pan, int32 timeFadeInSeconds, int32 timePlaySeconds, int loop, int32 timeFadeOutSeconds) {
//Common::StackLock lock(_mutex);
// TODO Does this mean that when music volume slider is at mute (0) (in the original engine),
// the music track would *not* be "played silently" and would not be loaded nor queued at all?
if (_musicVolumeFactorOriginalEngine <= 0) {
return false;
}
int volumeAdjusted = (volume * _musicVolumeFactorOriginalEngine) / 100; //volumeAdjusted will be in [0, 100]
int volumeStart = volumeAdjusted;
if (timeFadeInSeconds > 0) {
volumeStart = 1;
}
// Queuing mechanism:
// if a music track is already playing, then:
// if the requested track is a different track
// queue it as "next", to play after the current one.
// However, if a "next" track already exists,
// then stop the _current track after 2 seconds (force stop current playing track)
// Also the previous "next" track still gets replaced by the new requested one.
// This can be best tested at Animoid Row, within Hawker's Circle,
// moving from Izo's Pawn Shop to the Bar.
// if the requested track is the same as the currently playing,
// update the loop int value of the _current to the new one
// and adjust its fadeIn and balance/pan
// In both of the above cases, the _current track is not (yet) changed.
if (isPlaying()) {
if (!_current.name.equalsIgnoreCase(trackName)) {
_next.name = trackName;
_next.volume = volume; // Don't store the adjusted volume - This is a "target" volume (before any adjustments from volume sliders or special mode like VK)
_next.pan = pan; // This is a "target" value for the pan (balance)
_next.timeFadeInSeconds = timeFadeInSeconds;
_next.timePlaySeconds = timePlaySeconds;
_next.loop = loop;
_next.timeFadeOutSeconds = timeFadeOutSeconds;
if (_isNextPresent) {
stop(2u);
}
_isNextPresent = true;
} else {
_current.loop = loop;
if (timeFadeInSeconds < 0) {
timeFadeInSeconds = 0;
}
#if BLADERUNNER_ORIGINAL_BUGS
adjustVolume(volumeAdjusted, timeFadeInSeconds);
#else
adjustVolume(volume, timeFadeInSeconds);
#endif // BLADERUNNER_ORIGINAL_BUGS
adjustPan(pan, timeFadeInSeconds);
}
return true;
}
// If we reach here, there is no music track currently playing
// So we load it from the game's resources
_data = getData(trackName);
if (_data == nullptr) {
return false;
}
_stream = new AudStream(_data);
_isNextPresent = false;
uint32 trackLengthInMillis = _stream->getLength();
uint32 secondToStart = 0;
// loop > 1 can only happen in restored content, so no need to check for _vm->_cutContent explicitly here
if (loop > 1 && trackLengthInMillis > 0) {
// start at some point within the first half of the track
if (timePlaySeconds > 0 && trackLengthInMillis/1000 > (uint32)timePlaySeconds) {
secondToStart = _vm->_rnd.getRandomNumberRng(0, MIN(trackLengthInMillis/2000, (trackLengthInMillis/1000 - (uint32)timePlaySeconds)));
} else if (timeFadeOutSeconds >= 0 && trackLengthInMillis/1000 > (uint32)timeFadeOutSeconds) {
secondToStart = _vm->_rnd.getRandomNumberRng(0, MIN(trackLengthInMillis/2000, (trackLengthInMillis/1000 - (uint32)timeFadeOutSeconds)));
}
}
if (secondToStart > 0) {
_stream->startAtSecond(secondToStart);
}
_channel = _vm->_audioMixer->playMusic(_stream, volumeStart, mixerChannelEnded, this, trackLengthInMillis);
if (_channel < 0) {
delete _stream;
_stream = nullptr;
delete[] _data;
_data = nullptr;
return false;
}
if (timeFadeInSeconds > 0) {
#if BLADERUNNER_ORIGINAL_BUGS
adjustVolume(volumeAdjusted, timeFadeInSeconds);
#else
adjustVolume(volume, timeFadeInSeconds);
#endif // BLADERUNNER_ORIGINAL_BUGS
}
_current.name = trackName;
if (timePlaySeconds > 0) {
// Removes any previous fadeout timer and installs a new one.
// Uses the timeFadeOutSeconds value (see Music::fadeOut())
#if BLADERUNNER_ORIGINAL_BUGS
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, timePlaySeconds * 1000 * 1000, this, "BladeRunnerMusicFadeoutTimer");
#else
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicFadeOut, timePlaySeconds * 1000u);
#endif //BLADERUNNER_ORIGINAL_BUGS
} else if (timeFadeOutSeconds > 0) {
#if BLADERUNNER_ORIGINAL_BUGS
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, (trackLengthInMillis - timeFadeOutSeconds * 1000) * 1000, this, "BladeRunnerMusicFadeoutTimer");
#else
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicFadeOut, (trackLengthInMillis - timeFadeOutSeconds * 1000u));
#endif //BLADERUNNER_ORIGINAL_BUGS
}
_isPlaying = true;
_current.volume = volume; // Don't store the adjusted volume - This is a "target" volume (before any adjustments from volume sliders or special mode like VK)
_current.pan = pan; // This is a "target" value for the pan (balance)
_current.timeFadeInSeconds = timeFadeInSeconds;
_current.timePlaySeconds = timePlaySeconds;
_current.loop = loop;
// loop == kMusicLoopPlayOnceRandomStart can only happen in restored content, so no need to check for _vm->_cutContent explicitly here
if (_current.loop == kMusicLoopRepeatRandomStart) {
// loop value to store (and use in next loop) should be kMusicLoopRepeat
_current.loop = kMusicLoopRepeat;
}
_current.timeFadeOutSeconds = timeFadeOutSeconds;
return true;
}
void Music::stop(uint32 delaySeconds) {
Common::StackLock lock(_mutex);
if (_channel < 0) {
return;
}
#if !BLADERUNNER_ORIGINAL_BUGS
// In original game, on queued music was not removed and it started playing after actor left the scene
_isNextPresent = false;
#endif
_current.loop = 0;
_vm->_audioMixer->stop(_channel, 60u * delaySeconds);
}
// adjust() is a public method, and note that it accepts special values for its arguments
// volume should be in [0, 100], with "-1" being a special value for skipping volume adjustment
// pan should be in [-100, 100], with "-101" being a special value for skipping pan (balance) adjustment
void Music::adjust(int volume, int pan, uint32 delaySeconds) {
// value -1 for volume means "do not adjust volume for this music track (while it's playing)"
if (volume != -1) {
#if BLADERUNNER_ORIGINAL_BUGS
adjustVolume((volume * _musicVolumeFactorOriginalEngine)/ 100, delaySeconds);
#else
adjustVolume(volume, delaySeconds);
#endif // BLADERUNNER_ORIGINAL_BUGS
}
// value -101 for pan means "do not adjust pan for this music track (while it's playing)"
if (pan != -101) {
adjustPan(pan, delaySeconds);
}
}
bool Music::isPlaying() {
return _channel >= 0 && _isPlaying;
}
// TODO Evaluate if for ScummVM we can avoid using and modifying _musicVolumeFactorOriginalEngine altogether.
// While we no longer use the original engine's mechanism to set the music tracks' volume
// with the Music::setVolume() public method (when using the in-game KIA volume slider),
// we do use this method as did the original engine to temporarily set the volume levels
// in VK mode.
// volume should be in [0, 100]. If volume < 0 then it's treated as 0, and the track is stopped with 2 seconds fade out.
void Music::setVolume(int volume) {
// Just don't set the _musicVolumeFactorOriginalEngine to a negative value
_musicVolumeFactorOriginalEngine = (volume < 0) ? 0 : volume;
if (_musicVolumeFactorOriginalEngine == 0) {
stop(2u);
} else if (isPlaying()) {
// delay is 2 seconds (multiplied by 60u as expected by AudioMixer::adjustVolume())
_vm->_audioMixer->adjustVolume(_channel, (_current.volume * _musicVolumeFactorOriginalEngine) / 100, 120u);
}
}
int Music::getVolume() const {
return _musicVolumeFactorOriginalEngine;
}
void Music::playSample() {
if (!isPlaying()) {
play(_vm->_gameInfo->getSfxTrack(kSfxMUSVOL8), 100, 0, 2, -1, kMusicLoopPlayOnce, 3);
}
}
void Music::save(SaveFileWriteStream &f) {
f.writeBool(_isNextPresent);
f.writeBool(_isPlaying);
f.writeBool(_isPaused);
f.writeStringSz(_current.name, 13);
f.writeInt(_current.volume);
f.writeInt(_current.pan);
f.writeInt(_current.timeFadeInSeconds);
f.writeInt(_current.timePlaySeconds);
f.writeInt(_current.loop);
f.writeInt(_current.timeFadeOutSeconds);
f.writeStringSz(_next.name, 13);
f.writeInt(_next.volume);
f.writeInt(_next.pan);
f.writeInt(_next.timeFadeInSeconds);
f.writeInt(_next.timePlaySeconds);
f.writeInt(_next.loop);
f.writeInt(_next.timeFadeOutSeconds);
}
void Music::load(SaveFileReadStream &f) {
_isNextPresent = f.readBool();
_isPlaying = f.readBool();
_isPaused = f.readBool();
_current.name = f.readStringSz(13);
_current.volume = f.readInt();
_current.pan = f.readInt();
_current.timeFadeInSeconds = f.readInt();
_current.timePlaySeconds = f.readInt();
_current.loop = f.readInt();
_current.timeFadeOutSeconds = f.readInt();
_next.name = f.readStringSz(13);
_next.volume = f.readInt();
_next.pan = f.readInt();
_next.timeFadeInSeconds = f.readInt();
_next.timePlaySeconds = f.readInt();
_next.loop = f.readInt();
_next.timeFadeOutSeconds = f.readInt();
stop(2u);
if (_isPlaying) {
if (_channel == -1) {
play(_current.name,
_current.volume,
_current.pan,
_current.timeFadeInSeconds,
_current.timePlaySeconds,
_current.loop,
_current.timeFadeOutSeconds);
} else {
_isNextPresent = true;
_next.name = _current.name;
_next.volume = _current.volume;
_next.pan = _current.pan;
_next.timeFadeInSeconds = _current.timeFadeInSeconds;
_next.timePlaySeconds = _current.timePlaySeconds;
_next.loop = _current.loop;
_next.timeFadeOutSeconds = _current.timeFadeOutSeconds;
}
}
}
// volume should be in [0, 100]
#if BLADERUNNER_ORIGINAL_BUGS
void Music::adjustVolume(int adjustedVolume, uint32 delaySeconds) {
// adjustVolume() takes an "adjusted volume" value as an argument
// We don't store that as the target _current.volume. Music::play() stores the proper value
// However, adjustVolume() is called from Music::play() but also from Music::adjust() (called by ScriptBase::Music_Adjust()).
if (_channel >= 0) {
_vm->_audioMixer->adjustVolume(_channel, adjustedVolume, 60u * delaySeconds);
}
}
#else
void Music::adjustVolume(int volume, uint32 delaySeconds) {
// adjustVolume() takes a "target" volume value as an argument
// We store the new volume value as the _current.volume (which is a target volume).
// adjustVolume() is called from Music::play() but also from Music::adjust() (called by ScriptBase::Music_Adjust()).
_current.volume = volume;
if (_channel >= 0) {
_vm->_audioMixer->adjustVolume(_channel, (volume * _musicVolumeFactorOriginalEngine) / 100, 60u * delaySeconds);
}
}
#endif // BLADERUNNER_ORIGINAL_BUGS
// pan should be in [-100, 100]
void Music::adjustPan(int pan, uint32 delaySeconds) {
// adjustPan() is called from Music::play() but also from Music::adjust().
// We store the new pan value here as the _current.pan (which is a target pan).
_current.pan = pan;
if (_channel >= 0) {
_vm->_audioMixer->adjustPan(_channel, pan, 60u * delaySeconds);
}
}
void Music::ended() {
Common::StackLock lock(_mutex);
_isPlaying = false;
_channel = -1;
delete[] _data;
_data = nullptr;
// The timer that checks for a next track is started here.
// When it expires, it should check for queued music (_isNextPresent) or looping music (_current.loop)
#if BLADERUNNER_ORIGINAL_BUGS
_vm->getTimerManager()->installTimerProc(timerCallbackNext, 100 * 1000, this, "BladeRunnerMusicNextTimer");
#else
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicNext, 100u);
#endif // BLADERUNNER_ORIGINAL_BUGS
}
void Music::fadeOut() {
#if BLADERUNNER_ORIGINAL_BUGS
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
#else
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
#endif // BLADERUNNER_ORIGINAL_BUGS
if (_channel >= 0) {
if (_current.timeFadeOutSeconds < 0) {
_current.timeFadeOutSeconds = 0;
}
_vm->_audioMixer->stop(_channel, 60u * _current.timeFadeOutSeconds);
}
}
#if BLADERUNNER_ORIGINAL_BUGS
void Music::timerCallbackFadeOut(void *refCon) {
((Music *)refCon)->fadeOut();
}
void Music::timerCallbackNext(void *refCon) {
((Music *)refCon)->next();
}
void Music::next() {
_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
if (_isNextPresent) {
if (_isPaused) {
// postpone loading the next track (re-arm the BladeRunnerMusicNextTimer timer)
_vm->getTimerManager()->installTimerProc(timerCallbackNext, 2000 * 1000, this, "BladeRunnerMusicNextTimer");
} else {
play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeInSeconds, _next.timePlaySeconds, _next.loop, _next.timeFadeOutSeconds);
}
// This should not come after a possible call to play() which could swap the "_current" for the new (_next) track
// Setting the loop to 0 here, would then make the new track non-looping, even if it is supposed to be looping
_current.loop = 0;
} else if (_current.loop) {
play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeInSeconds, _current.timePlaySeconds, _current.loop, _current.timeFadeOutSeconds);
}
}
#else
void Music::next() {
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicNext);
if (_isNextPresent) {
if (_isPaused) {
// postpone loading the next track (re-arm the BladeRunnerMusicNextTimer timer)
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicNext, 2000u);
_current.loop = 0;
} else {
_current.loop = 0;
play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeInSeconds, _next.timePlaySeconds, _next.loop, _next.timeFadeOutSeconds);
}
} else if (_current.loop) {
play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeInSeconds, _current.timePlaySeconds, _current.loop, _current.timeFadeOutSeconds);
}
}
#endif // BLADERUNNER_ORIGINAL_BUGS
void Music::mixerChannelEnded(int channel, void *data) {
if (data != nullptr) {
((Music *)data)->ended();
}
}
byte *Music::getData(const Common::String &name) {
// NOTE: This is not part original game, loading data is done in the mixer and its using buffering to limit memory usage
Common::SeekableReadStream *stream = _vm->getResourceStream(_vm->_enhancedEdition ? ("audio/" + name) : name);
if (stream == nullptr) {
return nullptr;
}
uint32 size = stream->size();
byte *data = new byte[size];
stream->read(data, size);
delete stream;
return data;
}
} // End of namespace BladeRunner

109
engines/bladerunner/music.h Normal file
View File

@@ -0,0 +1,109 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_MUSIC_H
#define BLADERUNNER_MUSIC_H
#include "common/mutex.h"
#include "common/str.h"
#include "bladerunner/bladerunner.h" // For BLADERUNNER_ORIGINAL_BUGS and BLADERUNNER_ORIGINAL_SETTINGS symbols
namespace BladeRunner {
class AudStream;
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class Music {
struct Track {
Common::String name;
int volume; // A value in [0, 100] - It is the set (target) volume for the track regardless of fadeIn and fadeOut transitions
int pan; // A value in [-100, 100]. -100 is left, 100 is right and 0 is center - It is the set (target) pan/balance for the track regardless of any ongoing adjustments
int32 timeFadeInSeconds; // how long will it take for the track to reach target volume (in seconds)
int32 timePlaySeconds; // how long the track will play before starting fading out (in seconds) - uses timeFadeOutSeconds for fadeout
// -1: Special value for playing the whole track
int loop; // values from enum MusicTrackLoop (see game_constants.h)
int32 timeFadeOutSeconds; // how long the fade out will be for the track at its end (in seconds)
};
BladeRunnerEngine *_vm;
Common::Mutex _mutex;
int _musicVolumeFactorOriginalEngine; // should be in [0, 100]
int _channel;
bool _isNextPresent;
bool _isPlaying;
bool _isPaused;
Track _current;
Track _next;
byte *_data;
AudStream *_stream;
public:
Music(BladeRunnerEngine *vm);
~Music();
bool play(const Common::String &trackName, int volume, int pan, int32 timeFadeInSeconds, int32 timePlaySeconds, int loop, int32 timeFadeOutSeconds);
void stop(uint32 delaySeconds);
void adjust(int volume, int pan, uint32 delaySeconds);
bool isPlaying();
void setVolume(int volume);
int getVolume() const;
void playSample();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
#if !BLADERUNNER_ORIGINAL_BUGS
// moved to public access
void fadeOut();
void next();
#endif // !BLADERUNNER_ORIGINAL_BUGS
private:
void reset();
#if BLADERUNNER_ORIGINAL_BUGS
void adjustVolume(int adjustedVolume, uint32 delaySeconds);
#else
void adjustVolume(int volume, uint32 delaySeconds);
#endif // BLADERUNNER_ORIGINAL_BUGS
void adjustPan(int pan, uint32 delaySeconds);
void ended();
#if BLADERUNNER_ORIGINAL_BUGS
void fadeOut();
void next();
static void timerCallbackFadeOut(void *refCon);
static void timerCallbackNext(void *refCon);
#endif // BLADERUNNER_ORIGINAL_BUGS
static void mixerChannelEnded(int channel, void *data);
byte *getData(const Common::String &name);
};
} // End of namespace BladeRunner
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_OBSTACLES_H
#define BLADERUNNER_OBSTACLES_H
#include "bladerunner/rect_float.h"
#include "bladerunner/vector.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class Obstacles {
static const int kVertexCount = 150;
static const int kPolygonCount = 50;
static const int kPolygonVertexCount = 160;
static const int kMaxPathSize = 500;
enum VertexType {
BOTTOM_LEFT,
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT
};
struct LineSegment {
Vector2 start;
Vector2 end;
};
struct Polygon {
bool isPresent;
int verticeCount;
RectFloat rect;
Vector2 vertices[kPolygonVertexCount];
VertexType vertexType[kPolygonVertexCount];
Polygon() : isPresent(false), verticeCount(0), vertexType()
{}
};
BladeRunnerEngine *_vm;
Polygon *_polygons;
Polygon *_polygonsBackup;
Vector2 *_path;
int _pathSize;
int _count;
bool _backup;
static bool lineLineIntersection(LineSegment a, LineSegment b, Vector2 *intersectionPoint);
static bool linePolygonIntersection(LineSegment lineA, VertexType lineAType, Polygon *polyB, Vector2 *intersectionPoint, int *intersectionIndex, int pathLengthSinceLastIntersection);
bool mergePolygons(Polygon &polyA, Polygon &PolyB);
public:
Obstacles(BladeRunnerEngine *vm);
~Obstacles();
void clear();
void add(RectFloat rect);
void add(float x0, float z0, float x1, float z1) { add(RectFloat(x0, z0, x1, z1)); }
int findEmptyPolygon() const;
static float getLength(float x0, float z0, float x1, float z1);
bool findNextWaypoint(const Vector3 &from, const Vector3 &to, Vector3 *next);
bool findIntersectionNearest(int polygonIndex, Vector2 from, Vector2 to,
int *outVertexIndex, float *outDistance, Vector2 *out) const;
bool findIntersectionFarthest(int polygonIndex, Vector2 from, Vector2 to,
int *outVertexIndex, float *outDistance, Vector2 *out) const;
float pathTotalDistance(const Vector2 *path, int pathSize, Vector2 from, Vector2 to) const;
bool findPolygonVerticeByXZ(int *polygonIndex, int *verticeIndex, int *verticeCount, float x, float z) const;
bool findPolygonVerticeByXZWithinTolerance(float x, float z, int *polygonIndex, int *verticeIndex, int startSearchFromPolygonIdx) const;
void clearPath();
int buildNegativePath(int polyIndex, int vertStartIndex, Vector2 startPos, int vertEndIndex, Vector2 endPos, Vector2 *path, int pathCapacity, bool *pathBlocked);
int buildPositivePath(int polyIndex, int vertStartIndex, Vector2 startPos, int vertEndIndex, Vector2 endPos, Vector2 *path, int pathCapacity, bool *pathBlocked);
bool verticesCanIntersect(int lineType0, int lineType1, float x0, float y0, float x1, float y1) const;
bool findFarthestAvailablePathVertex(Vector2 *path, int pathSize, Vector3 start, Vector3 *next) const;
void backup();
void restore();
void reset();
void draw();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,194 @@
/* 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/outtake.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/chapters.h"
#include "bladerunner/subtitles.h"
#include "bladerunner/vqa_player.h"
#include "bladerunner/time.h"
#include "bladerunner/ambient_sounds.h"
#include "bladerunner/audio_player.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/game_info.h"
#include "bladerunner/framelimiter.h"
#include "common/debug.h"
#include "common/events.h"
#include "common/system.h"
namespace BladeRunner {
OuttakePlayer::OuttakePlayer(BladeRunnerEngine *vm) {
_vm = vm;
_surfaceVideo.create(_vm->_surfaceBack.w, _vm->_surfaceBack.h, _vm->_surfaceBack.format);
}
OuttakePlayer::~OuttakePlayer() {
_surfaceVideo.free();
}
void OuttakePlayer::play(const Common::String &name, bool noLocalization, int container) {
Common::String oldOuttakeFile;
Common::String newOuttakeFile;
if (container > 0) {
oldOuttakeFile = Common::String::format("OUTTAKE%d.MIX", _vm->_chapters->currentResourceId());
newOuttakeFile = Common::String::format("OUTTAKE%d.MIX", container);
if (_vm->isArchiveOpen(oldOuttakeFile)
&& _vm->_chapters->currentResourceId() != container) {
_vm->closeArchive(oldOuttakeFile);
}
if (!_vm->isArchiveOpen(newOuttakeFile)) {
_vm->openArchive(newOuttakeFile);
}
}
_vm->playerLosesControl();
Common::String resNameNoVQASuffix = name;
if (!noLocalization) {
resNameNoVQASuffix = resNameNoVQASuffix + "_" + _vm->_languageCode;
}
VQAPlayer vqaPlayer(_vm, &_surfaceVideo, resNameNoVQASuffix + ".VQA"); // in original game _surfaceFront is used here, but for proper subtitles rendering we need separate surface
vqaPlayer.open();
// NOTE: The VQAPlayer::open():
// - calls VQAPlayer::close(), and that calls VQADecoder::close(), at its start to cleanup any previous state.
// - later it calls _decoder.loadStream() which also calls VQADecoder::close().
// Since VQADecoder::close() cleans up (deletes) the VQPTable,
// we need to do the allocation for it and loading of its values here, *after* vqaPlayer.open().
// The parsing of the VQP files is needed for the case of the non-interactive Blade Runner demo
// and the support of other old VQA files that have accompanying VQP files (eg. for demo reels for other Westwood games)
if (container == -2) {
// container value: -2 indicates potential existence of VQP file
if (!vqaPlayer.loadVQPTable(resNameNoVQASuffix + ".VQP")) {
debug("Unable to load VQP table");
}
}
_vm->_vqaIsPlaying = true;
_vm->_vqaStopIsRequested = false;
while (!_vm->_vqaStopIsRequested && !_vm->shouldQuit()) {
_vm->handleEvents();
if (!_vm->_windowIsActive) {
continue;
}
int frame = vqaPlayer.update();
blit(_surfaceVideo, _vm->_surfaceFront); // This helps to make subtitles disappear properly, if the video is rendered in separate surface and then pushed to the front surface
if (frame == -3) { // end of video
if (_vm->_cutContent && resNameNoVQASuffix.equals("FLYTRU_E")) {
_vm->_ambientSounds->removeAllNonLoopingSounds(true);
_vm->_ambientSounds->removeAllLoopingSounds(1u);
}
break;
}
if (frame >= 0) {
_vm->_subtitles->loadOuttakeSubsText(resNameNoVQASuffix, frame);
_vm->_subtitles->tickOuttakes(_vm->_surfaceFront);
_vm->blitToScreen(_vm->_surfaceFront);
if (_vm->_cutContent && resNameNoVQASuffix.equals("FLYTRU_E")) {
// This FLYTRU_E outtake has 150 frames
//
// We can have at most kLoopingSounds (3) looping ambient tracks
// Outtakes in general use a specific Mixer Sound Type (ie. VQAPlayer::kVQASoundType)
// so the sounds here should conform too.
// (see VQAPlayer::update())
//
// No need for _ambientSounds->tick() since it's not required for looping ambient tracks,
// nor for explicitly played Sounds (via playSound())
// It is harmless however, so it could remain.
//_vm->_ambientSounds->tick();
switch (frame) {
case 0:
_vm->_ambientSounds->addLoopingSound(kSfxLABAMB1, 95, 0, 0u, VQAPlayer::kVQASoundType);
_vm->_ambientSounds->addLoopingSound(kSfxROOFAIR1, 100, 0, 0u, VQAPlayer::kVQASoundType);
_vm->_ambientSounds->addLoopingSound(kSfxPSPA6, 74, 0, 1u, VQAPlayer::kVQASoundType);
break;
case 18:
_vm->_ambientSounds->playSound(kSfxSPIN2A, 100, 90, 20, 99, VQAPlayer::kVQASoundType);
break;
case 24:
_vm->_ambientSounds->playSound(kSfxSWEEP4, 45, 90, 20, 99, VQAPlayer::kVQASoundType);
break;
case 32:
if (_vm->_rnd.getRandomNumberRng(1, 5) < 4)
_vm->_ambientSounds->playSound(kSfxTHNDER3, 82, -20, -20, 99, VQAPlayer::kVQASoundType);
break;
case 41:
_vm->_ambientSounds->playSound(kSfxMUSVOL8, 22, 46, 46, 99, VQAPlayer::kVQASoundType);
break;
case 52:
if (_vm->_rnd.getRandomNumberRng(1, 4) < 4)
_vm->_ambientSounds->playSound(kSfxTHNDR3, 90, 10, 10, 89, VQAPlayer::kVQASoundType);
break;
case 78:
if (_vm->_rnd.getRandomNumberRng(1, 5) < 5)
_vm->_ambientSounds->playSound(kSfxSIREN2, 62, -60, 45, 99, VQAPlayer::kVQASoundType);
break;
case 105:
_vm->_ambientSounds->playSound(kSfxSWEEP3, 22, 20, 95, 99, VQAPlayer::kVQASoundType);
break;
case 112:
if (_vm->_rnd.getRandomNumberRng(1, 5) < 4)
_vm->_ambientSounds->playSound(kSfxTHNDER4, 95, -20, -20, 99, VQAPlayer::kVQASoundType);
break;
}
}
} else {
_vm->_framelimiter->wait();
}
}
if ((_vm->_vqaStopIsRequested || _vm->shouldQuit())
&& _vm->_cutContent && resNameNoVQASuffix.equals("FLYTRU_E")) {
_vm->_ambientSounds->removeAllNonLoopingSounds(true);
_vm->_ambientSounds->removeAllLoopingSounds(0u);
_vm->_audioPlayer->stopAll();
}
_vm->_vqaIsPlaying = false;
_vm->_vqaStopIsRequested = false;
vqaPlayer.close();
_vm->playerGainsControl();
if (container > 0) {
if (_vm->isArchiveOpen(newOuttakeFile)
&& _vm->_chapters->currentResourceId() != container) {
_vm->closeArchive(newOuttakeFile);
}
if (!_vm->isArchiveOpen(oldOuttakeFile)) {
_vm->openArchive(oldOuttakeFile);
}
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,47 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_OUTTAKE_H
#define BLADERUNNER_OUTTAKE_H
#include "common/str.h"
#include "graphics/surface.h"
namespace BladeRunner {
class BladeRunnerEngine;
class OuttakePlayer {
BladeRunnerEngine *_vm;
Graphics::Surface _surfaceVideo;
public:
OuttakePlayer(BladeRunnerEngine *vm);
~OuttakePlayer();
void play(const Common::String &name, bool noLocalization, int container);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,247 @@
/* 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/overlays.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/archive.h"
#include "bladerunner/savefile.h"
#include "bladerunner/vqa_player.h"
#include "graphics/surface.h"
namespace BladeRunner {
Overlays::Overlays(BladeRunnerEngine *vm) {
_vm = vm;
}
bool Overlays::init() {
reset();
_videos.resize(kOverlayVideos);
for (int i = 0; i < kOverlayVideos; ++i) {
_videos[i].vqaPlayer = nullptr;
resetSingle(i);
}
return true;
}
Overlays::~Overlays() {
for (int i = 0; i < kOverlayVideos; ++i) {
resetSingle(i);
}
_videos.clear();
reset();
}
int Overlays::play(const Common::String &name, int loopId, bool loopForever, bool startNow, int a6) {
assert(name.size() <= 12);
if (loopId < 0) {
warning("Overlays::play - loop id can't be a negative number!");
return -1;
}
int32 hash = MIXArchive::getHash(name);
int index = findByHash(hash);
if (index < 0) {
index = findEmpty();
if (index < 0) {
return index;
}
_videos[index].loaded = true;
_videos[index].name = name;
_videos[index].hash = hash;
_videos[index].loopId = loopId;
_videos[index].enqueuedLoopId = -1;
_videos[index].loopForever = loopForever;
_videos[index].vqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceFront, Common::String::format("%s.VQA", name.c_str()));
if (!_videos[index].vqaPlayer) {
resetSingle(index);
return -1;
}
// TODO? Removed as redundant
// repeat forever
//_videos[index].vqaPlayer->setBeginAndEndFrame(0, 0, -1, kLoopSetModeJustStart, nullptr, nullptr);
}
bool skipNewVQAPlayerOpen = false;
if (_videos[index].vqaPlayer
&& !startNow
&& _videos[index].vqaPlayer->getFrameCount() > 0
) {
skipNewVQAPlayerOpen = true;
// INFO The actual enqueuing happens in VQAPlayer -- see: kLoopSetModeEnqueue()
// The enqueuedLoopId is a field that is not stored separately, but will be stored as the value of "loopId"
// (see Overlays::save()) so that, when loading, the engine will know
// that it needs to reach the end frame of the *queued* loop.
// It is not used elsewhere. The actual enqueuing as well as the decision of what loop id should be resumed
// are done in the VQAPlayer class.
_videos[index].enqueuedLoopId = loopId;
}
if (skipNewVQAPlayerOpen || _videos[index].vqaPlayer->open()) {
_videos[index].vqaPlayer->setLoop(loopId,
loopForever ? -1 : 0,
startNow ? kLoopSetModeImmediate : kLoopSetModeEnqueue,
nullptr,
nullptr);
} else {
resetSingle(index);
return -1;
}
return index;
}
void Overlays::resume(bool isLoadingGame) {
for (int i = 0; i < kOverlayVideos; ++i) {
if (_videos[i].loaded && isLoadingGame) {
_videos[i].vqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceFront, Common::String::format("%s.VQA", _videos[i].name.c_str()));
if (!_videos[i].vqaPlayer) {
resetSingle(i);
continue;
}
_videos[i].vqaPlayer->open();
_videos[i].vqaPlayer->setLoop(
_videos[i].loopId,
_videos[i].loopForever ? -1 : 0,
kLoopSetModeImmediate,
nullptr, nullptr);
_videos[i].vqaPlayer->seekToFrame(_videos[i].frame);
_videos[i].vqaPlayer->update(true);
// Update the enqueued loop id, if it was changed within the vqaPlayer->update() call
// so that if the user saves the game, the correct queued id will be stored
if (_videos[i].enqueuedLoopId != -1 && _videos[i].enqueuedLoopId != _videos[i].vqaPlayer->getLoopIdTarget()) {
_videos[i].enqueuedLoopId = _videos[i].vqaPlayer->getLoopIdTarget();
}
}
}
}
void Overlays::remove(const Common::String &name) {
int index = findByHash(MIXArchive::getHash(name));
if (index >= 0) {
resetSingle(index);
}
}
void Overlays::removeAll() {
for (int i = 0; i < kOverlayVideos; ++i) {
if (_videos[i].loaded) {
resetSingle(i);
}
}
}
void Overlays::tick() {
for (int i = 0; i < kOverlayVideos; ++i) {
if (_videos[i].loaded) {
_videos[i].frame = _videos[i].vqaPlayer->update(true);
// Update the enqueued loop id, if it was changed within the vqaPlayer->update() call
// so that if the user saves the game, the correct queued id will be stored
if (_videos[i].enqueuedLoopId != -1 && _videos[i].enqueuedLoopId != _videos[i].vqaPlayer->getLoopIdTarget()) {
_videos[i].enqueuedLoopId = _videos[i].vqaPlayer->getLoopIdTarget();
}
if (_videos[i].frame < 0) {
resetSingle(i);
}
}
}
}
int Overlays::findByHash(int32 hash) const {
for (int i = 0; i < kOverlayVideos; ++i) {
if (_videos[i].loaded && _videos[i].hash == hash) {
return i;
}
}
return -1;
}
int Overlays::findEmpty() const {
for (int i = 0; i < kOverlayVideos; ++i) {
if (!_videos[i].loaded) {
return i;
}
}
return -1;
}
void Overlays::resetSingle(int i) {
assert(i >= 0 && i < (int)_videos.size());
if (_videos[i].vqaPlayer) {
delete _videos[i].vqaPlayer;
_videos[i].vqaPlayer = nullptr;
}
_videos[i].loaded = false;
_videos[i].hash = 0;
_videos[i].frame = -1;
_videos[i].name.clear();
}
void Overlays::reset() {
_videos.clear();
}
void Overlays::save(SaveFileWriteStream &f) {
for (int i = 0; i < kOverlayVideos; ++i) {
// 37 bytes per overlay
Video &ov = _videos[i];
f.writeBool(ov.loaded);
f.writeInt(0); // vqaPlayer pointer
f.writeStringSz(ov.name, 13);
f.writeSint32LE(ov.hash);
if (ov.enqueuedLoopId != -1) {
// When there is an enqueued video, save that loop Id instead
f.writeInt(ov.enqueuedLoopId);
} else {
f.writeInt(ov.loopId);
}
f.writeBool(ov.loopForever);
f.writeInt(ov.frame);
}
}
void Overlays::load(SaveFileReadStream &f) {
for (int i = 0; i < kOverlayVideos; ++i) {
// 37 bytes per overlay
Video &ov = _videos[i];
ov.loaded = f.readBool();
f.skip(4); // vqaPlayer pointer
ov.vqaPlayer = nullptr;
ov.name = f.readStringSz(13);
ov.hash = f.readSint32LE();
ov.loopId = f.readInt();
ov.loopForever = f.readBool();
ov.frame = f.readInt();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,82 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_OVERLAYS_H
#define BLADERUNNER_OVERLAYS_H
#include "common/array.h"
#include "common/str.h"
namespace Graphics {
struct Surface;
}
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class VQAPlayer;
class Overlays {
friend class Debugger;
static const int kOverlayVideos = 5;
struct Video {
bool loaded;
VQAPlayer *vqaPlayer;
Common::String name;
int32 hash;
int loopId;
int enqueuedLoopId;
bool loopForever;
int frame;
};
BladeRunnerEngine *_vm;
Common::Array<Video> _videos;
public:
Overlays(BladeRunnerEngine *vm);
bool init();
~Overlays();
int play(const Common::String &name, int loopId, bool loopForever, bool startNow, int a6);
void resume(bool isLoadingGame);
void remove(const Common::String &name);
void removeAll();
void tick();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
int findByHash(int32 hash) const;
int findEmpty() const;
void resetSingle(int i);
void reset();
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,75 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_RECT_FLOAT_H
#define BLADERUNNER_RECT_FLOAT_H
#include "common/debug.h"
#include "common/types.h"
#include "common/util.h"
#include "math/utils.h"
namespace BladeRunner {
struct RectFloat {
float x0;
float y0;
float x1;
float y1;
RectFloat()
: x0(0.0f), y0(0.0f), x1(0.0f), y1(0.0f)
{}
RectFloat(float x0_, float y0_, float x1_, float y1_)
: x0(x0_), y0(y0_), x1(x1_), y1(y1_)
{}
void expand(float d) {
x0 -= d;
y0 -= d;
x1 += d;
y1 += d;
}
void trunc_2_decimals() {
x0 = Math::trunc(x0 * 100.0f) / 100.0f;
y0 = Math::trunc(y0 * 100.0f) / 100.0f;
x1 = Math::trunc(x1 * 100.0f) / 100.0f;
y1 = Math::trunc(y1 * 100.0f) / 100.0f;
}
};
inline bool overlaps(const RectFloat &a, const RectFloat &b) {
return !(a.y1 < b.y0 || a.y0 > b.y1 || a.x0 > b.x1 || a.x1 < b.x0);
}
inline RectFloat merge(const RectFloat &a, const RectFloat &b) {
RectFloat c;
c.x0 = MIN(a.x0, b.x0);
c.y0 = MIN(a.y0, b.y0);
c.x1 = MAX(a.x1, b.x1);
c.y1 = MAX(a.y1, b.y1);
return c;
}
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,120 @@
/* 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/regions.h"
#include "bladerunner/savefile.h"
namespace BladeRunner {
Regions::Regions() {
_enabled = true;
_regions.resize(10);
clear();
}
void BladeRunner::Regions::clear() {
for (int i = 0; i < 10; ++i)
remove(i);
}
bool Regions::add(int index, Common::Rect rect, int type) {
if (index < 0 || index >= 10)
return false;
if (_regions[index].present)
return false;
_regions[index].rectangle = rect;
_regions[index].type = type;
_regions[index].present = 1;
return true;
}
bool Regions::remove(int index) {
if (index < 0 || index >= 10)
return false;
_regions[index].rectangle = Common::Rect(-1, -1, -1, -1);
_regions[index].type = -1;
_regions[index].present = 0;
return true;
}
int Regions::getTypeAtXY(int x, int y) const {
int index = getRegionAtXY(x, y);
if (index == -1)
return -1;
return _regions[index].type;
}
int Regions::getRegionAtXY(int x, int y) const {
if (!_enabled)
return -1;
for (int i = 0; i != 10; ++i) {
if (!_regions[i].present)
continue;
// Common::Rect::contains is exclusive of right and bottom but
// Blade Runner wants inclusive, so we adjust the edges.
Common::Rect r = _regions[i].rectangle;
++(r.right);
++(r.bottom);
if (r.contains(x, y))
return i;
}
return -1;
}
void Regions::setEnabled(bool enabled) {
_enabled = enabled;
}
void Regions::enable() {
_enabled = true;
}
void Regions::save(SaveFileWriteStream &f) {
f.writeBool(_enabled);
for (int i = 0; i != 10; ++i) {
f.writeRect(_regions[i].rectangle);
f.writeInt(_regions[i].type);
f.writeInt(_regions[i].present);
}
}
void Regions::load(SaveFileReadStream &f) {
_enabled = f.readBool();
for (int i = 0; i != 10; ++i) {
_regions[i].rectangle = f.readRect();
_regions[i].type = f.readInt();
_regions[i].present = f.readInt();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,66 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_REGIONS_H
#define BLADERUNNER_REGIONS_H
#include "bladerunner/bladerunner.h"
#include "common/array.h"
#include "common/rect.h"
namespace BladeRunner {
class SaveFileReadStream;
class SaveFileWriteStream;
class Regions {
friend class Debugger;
struct Region {
Common::Rect rectangle;
int type; // Arrow Icon on mouse-over (has meaning only for Exits) 0: Upward , 1: Right, 2: Downward, 3: Left
int present;
};
Common::Array<Region> _regions;
bool _enabled;
public:
Regions();
void clear();
bool add(int index, Common::Rect rect, int type);
bool remove(int index);
int getTypeAtXY(int x, int y) const;
int getRegionAtXY(int x, int y) const;
void setEnabled(bool enabled);
void enable();
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,330 @@
/* 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/savefile.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/boundingbox.h"
#include "bladerunner/vector.h"
#include "common/rect.h"
#include "common/savefile.h"
#include "common/system.h"
#include "graphics/thumbnail.h"
namespace BladeRunner {
SaveStateList SaveFileManager::list(const MetaEngine *metaEngine, const Common::String &target) {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray files = saveFileMan->listSavefiles(target + ".###");
SaveStateList saveList;
for (Common::StringArray::const_iterator fileName = files.begin(); fileName != files.end(); ++fileName) {
Common::InSaveFile *saveFile = saveFileMan->openForLoading(*fileName);
if (saveFile == nullptr || saveFile->err()) {
delete saveFile;
continue;
}
BladeRunner::SaveFileHeader header;
readHeader(*saveFile, header);
int slotNum = atoi(fileName->c_str() + fileName->size() - 3);
saveList.push_back(SaveStateDescriptor(metaEngine, slotNum, header._name));
delete saveFile;
}
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
SaveStateDescriptor SaveFileManager::queryMetaInfos(const MetaEngine *metaEngine, const Common::String &target, int slot) {
Common::String filename = Common::String::format("%s.%03d", target.c_str(), slot);
Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(filename);
if (saveFile == nullptr || saveFile->err()) {
return SaveStateDescriptor();
}
BladeRunner::SaveFileHeader header;
if (!BladeRunner::SaveFileManager::readHeader(*saveFile, header, false)) {
delete saveFile;
return SaveStateDescriptor();
}
delete saveFile;
SaveStateDescriptor desc(metaEngine, slot, header._name);
desc.setThumbnail(header._thumbnail);
desc.setSaveDate(header._year, header._month, header._day);
desc.setSaveTime(header._hour, header._minute);
desc.setPlayTime(header._playTime);
return desc;
}
Common::InSaveFile *SaveFileManager::openForLoading(const Common::String &target, int slot) {
Common::String filename = Common::String::format("%s.%03d", target.c_str(), slot);
return g_system->getSavefileManager()->openForLoading(filename);
}
Common::OutSaveFile *SaveFileManager::openForSaving(const Common::String &target, int slot) {
Common::String filename = Common::String::format("%s.%03d", target.c_str(), slot);
return g_system->getSavefileManager()->openForSaving(filename);
}
bool SaveFileManager::remove(const Common::String &target, int slot) {
Common::String filename = Common::String::format("%s.%03d", target.c_str(), slot);
return g_system->getSavefileManager()->removeSavefile(filename);
}
bool SaveFileManager::readHeader(Common::SeekableReadStream &in, SaveFileHeader &header, bool skipThumbnail) {
SaveFileReadStream s(in);
if (s.readUint32BE() != kTag) {
warning("No header found in save file");
return false;
}
header._version = s.readByte();
if (header._version > kVersion) {
warning("Unsupported version of save file %u, supported is %u", header._version, kVersion);
return false;
}
if (header._version < 3) {
// this includes versions 0 and 1 here (even though they are non-existent)
header._name = s.readStringSz(kNameLengthV2);
} else {
// Version 3
header._name = s.readStringSz(kNameLength);
}
header._year = s.readUint16LE();
header._month = s.readUint16LE();
header._day = s.readUint16LE();
header._hour = s.readUint16LE();
header._minute = s.readUint16LE();
header._playTime = 0;
if (header._version >= 2) {
header._playTime = s.readUint32LE();
}
header._thumbnail = nullptr;
// Early check of possible corrupted save file (missing thumbnail and other data)
int32 pos = s.pos();
int32 sizeOfSaveFile = s.size();
if (sizeOfSaveFile > 0 && sizeOfSaveFile < (int32) (pos + 4 + kThumbnailSize)) {
warning("Unexpected end of save file \"%s\" (%02d:%02d %02d/%02d/%04d) reached. Size of file was: %d bytes",
header._name.c_str(),
header._hour,
header._minute,
header._day,
header._month,
header._year,
sizeOfSaveFile);
return false;
}
if (!skipThumbnail) {
s.skip(4); //skip size;
if (header._version >= 4) {
Graphics::loadThumbnail(s, header._thumbnail);
} else {
uint16 alphamask = (0xFF >> gameDataPixelFormat().aLoss) << gameDataPixelFormat().aShift;
uint16 *thumbnailData = (uint16*)malloc(kThumbnailSize); // freed by ScummVM's smartptr
assert(thumbnailData);
for (uint i = 0; i < kThumbnailSize / 2; ++i) {
thumbnailData[i] = s.readUint16LE() | alphamask; // We set all pixels to non-transparency
}
header._thumbnail = new Graphics::Surface();
header._thumbnail->init(80, 60, 160, thumbnailData, gameDataPixelFormat());
}
s.seek(pos);
}
return true;
}
bool SaveFileManager::writeHeader(Common::WriteStream &out, SaveFileHeader &header) {
SaveFileWriteStream s(out);
s.writeUint32BE(kTag);
s.writeByte(kVersion);
s.writeStringSz(header._name, kNameLength);
TimeDate td;
g_system->getTimeAndDate(td);
s.writeUint16LE(td.tm_year + 1900);
s.writeUint16LE(td.tm_mon + 1);
s.writeUint16LE(td.tm_mday);
s.writeUint16LE(td.tm_hour);
s.writeUint16LE(td.tm_min);
s.writeUint32LE(header._playTime);
return true;
}
SaveFileWriteStream::SaveFileWriteStream(Common::WriteStream &s) : _s(s) {}
void SaveFileWriteStream::debug(char *p) {
write(p, strlen(p) + 1);
}
void SaveFileWriteStream::padBytes(int count) {
for (int i = 0; i < count; ++i) {
writeByte(0);
}
}
void SaveFileWriteStream::writeInt(int32 v) {
writeUint32LE(v);
}
void SaveFileWriteStream::writeFloat(float v) {
writeFloatLE(v);
}
void SaveFileWriteStream::writeBool(bool v) {
writeUint32LE(v);
}
void SaveFileWriteStream::writeStringSz(const Common::String &s, uint sz) {
uint32 sizeToWrite = MIN(sz, s.size());
write(s.begin(), sizeToWrite);
if (sizeToWrite < sz) {
padBytes(sz - sizeToWrite);
}
}
void SaveFileWriteStream::writeVector2(const Vector2 &v) {
writeFloatLE(v.x);
writeFloatLE(v.y);
}
void SaveFileWriteStream::writeVector3(const Vector3 &v) {
writeFloatLE(v.x);
writeFloatLE(v.y);
writeFloatLE(v.z);
}
void SaveFileWriteStream::writeRect(const Common::Rect &v) {
writeUint32LE(v.left);
writeUint32LE(v.top);
writeUint32LE(v.right);
writeUint32LE(v.bottom);
}
void SaveFileWriteStream::writeBoundingBox(const BoundingBox &v, bool serialized) {
float x0, y0, z0, x1, y1, z1;
v.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
writeFloatLE(x0);
writeFloatLE(y0);
writeFloatLE(z0);
writeFloatLE(x1);
writeFloatLE(y1);
writeFloatLE(z1);
// Bounding boxes have a lot of extra data that's never actually used
int count = serialized ? 96 : 64;
for (int i = 0; i < count; ++i) {
writeFloatLE(0.0f);
}
}
SaveFileReadStream::SaveFileReadStream(Common::SeekableReadStream &s) : _s(s) {}
int32 SaveFileReadStream::readInt() {
return readUint32LE();
}
float SaveFileReadStream::readFloat() {
return readFloatLE();
}
bool SaveFileReadStream::readBool() {
return readUint32LE();
}
Common::String SaveFileReadStream::readStringSz(uint sz) {
char *buf = new char[sz + 1];
read(buf, sz);
buf[sz] = 0;
Common::String result(buf);
delete[] buf;
return result;
}
Vector2 SaveFileReadStream::readVector2() {
Vector2 result;
result.x = readFloatLE();
result.y = readFloatLE();
return result;
}
Vector3 SaveFileReadStream::readVector3() {
Vector3 result;
result.x = readFloatLE();
result.y = readFloatLE();
result.z = readFloatLE();
return result;
}
Common::Rect SaveFileReadStream::readRect() {
Common::Rect result;
result.left = readUint32LE();
result.top = readUint32LE();
result.right = readUint32LE();
result.bottom = readUint32LE();
return result;
}
BoundingBox SaveFileReadStream::readBoundingBox(bool serialized) {
float x0, y0, z0, x1, y1, z1;
x0 = readFloatLE();
y0 = readFloatLE();
z0 = readFloatLE();
x1 = readFloatLE();
y1 = readFloatLE();
z1 = readFloatLE();
// Bounding boxes have a lot of extra data that's never actually used, and there two formats for storing bounding boxes.
int count = serialized ? 96 : 64;
for (int i = 0; i < count; ++i) {
readFloatLE();
}
return BoundingBox(x0, y0, z0, x1, y1, z1);
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,138 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_SAVEFILE_H
#define BLADERUNNER_SAVEFILE_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/savefile.h"
#include "common/types.h"
#include "graphics/surface.h"
#include "engines/savestate.h"
namespace Common {
class OutSaveFile;
class String;
struct Rect;
}
namespace BladeRunner {
class Vector2;
class Vector3;
class BoundingBox;
struct SaveFileHeader {
uint8 _version;
Common::String _name;
int _year;
int _month;
int _day;
int _hour;
int _minute;
uint32 _playTime;
Graphics::Surface *_thumbnail;
};
class SaveFileManager {
private:
static const uint32 kTag = MKTAG('B', 'R', 'S', 'V');
// kVersion: 3 as of Feb 5th 2020 (UTC) - ScummVM development version 2.2.0git
// kVersion: 4 as of Apr 17th 2021 - Added full-size thumbnails
static const uint32 kVersion = 4;
public:
// kVersion
// ----------
// 2:: max of 32 characters for the saved game name
// 3:: max of 41 characters for the saved game name (this matches the original game's setting)
static const uint32 kNameLengthV2 = 32;
static const uint32 kNameLength = 41;
static const uint32 kThumbnailSize = 9600; // 80x60x16bpp
static SaveStateList list(const MetaEngine *metaEngine, const Common::String &target);
static SaveStateDescriptor queryMetaInfos(const MetaEngine *metaEngine, const Common::String &target, int slot);
static Common::InSaveFile *openForLoading(const Common::String &target, int slot);
static Common::OutSaveFile *openForSaving(const Common::String &target, int slot);
static bool remove(const Common::String &target, int slot);
static bool readHeader(Common::SeekableReadStream &in, SaveFileHeader &header, bool skipThumbnail = true);
static bool writeHeader(Common::WriteStream &out, SaveFileHeader &header);
};
class SaveFileWriteStream : public Common::WriteStream {
private:
Common::WriteStream &_s;
public:
SaveFileWriteStream(Common::WriteStream &s);
uint32 write(const void *dataPtr, uint32 dataSize) override { return _s.write(dataPtr, dataSize); }
bool flush() override { return _s.flush(); }
int64 pos() const override { return _s.pos(); }
void debug(char *p);
void padBytes(int count);
void writeInt(int32 v); // this writes a 4 byte int (uses writeUint32LE)
void writeFloat(float v);
void writeBool(bool v);
void writeStringSz(const Common::String &s, uint sz);
void writeVector2(const Vector2 &v);
void writeVector3(const Vector3 &v);
void writeRect(const Common::Rect &v);
void writeBoundingBox(const BoundingBox &v, bool serialized);
};
class SaveFileReadStream : public Common::SeekableReadStream {
private:
Common::SeekableReadStream &_s;
public:
SaveFileReadStream(Common::SeekableReadStream &s);
bool eos() const override { return _s.eos(); }
uint32 read(void *dataPtr, uint32 dataSize) override { return _s.read(dataPtr, dataSize); }
int64 pos() const override { return _s.pos(); }
int64 size() const override { return _s.size(); }
bool seek(int64 offset, int whence = SEEK_SET) override { return _s.seek(offset, whence); }
int32 readInt();
float readFloat();
bool readBool();
Common::String readStringSz(uint sz);
Vector2 readVector2();
Vector3 readVector3();
Common::Rect readRect();
BoundingBox readBoundingBox(bool serialized);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,488 @@
/* 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/scene.h"
#include "bladerunner/actor.h"
#include "bladerunner/actor_dialogue_queue.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/chapters.h"
#include "bladerunner/game_info.h"
#include "bladerunner/items.h"
#include "bladerunner/overlays.h"
#include "bladerunner/regions.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/screen_effects.h"
#include "bladerunner/set.h"
#include "bladerunner/settings.h"
#include "bladerunner/slice_renderer.h"
#include "bladerunner/script/police_maze.h"
#include "bladerunner/script/scene_script.h"
#include "bladerunner/ui/spinner.h"
#include "bladerunner/vqa_player.h"
#include "bladerunner/zbuffer.h"
#include "common/str.h"
namespace BladeRunner {
Scene::Scene(BladeRunnerEngine *vm)
: _vm(vm),
_setId(-1),
_sceneId(-1),
_vqaPlayer(nullptr),
_defaultLoop(0),
_defaultLoopSet(false),
_specialLoopMode(kSceneLoopModeLoseControl),
_specialLoop(0),
_defaultLoopPreloadedSet(false),
// _introFinished(false),
_nextSetId(-1),
_nextSceneId(-1),
_frame(0),
_actorStartFacing(0),
_playerWalkedIn(false),
_set(new Set(vm)),
_regions(new Regions()),
_exits(new Regions()) {
}
Scene::~Scene() {
delete _set;
delete _regions;
delete _exits;
delete _vqaPlayer;
}
bool Scene::open(int setId, int sceneId, bool isLoadingGame) {
if (!isLoadingGame) {
_vm->_actorDialogueQueue->flush(1, false);
}
_vm->walkingReset();
_setId = setId;
_sceneId = sceneId;
const Common::String sceneName = _vm->_gameInfo->getSceneName(_sceneId);
if (isLoadingGame) {
_vm->_overlays->resume(true);
} else {
_regions->clear();
_exits->clear();
#if BLADERUNNER_ORIGINAL_BUGS
#else
_vm->_screenEffects->toggleEntry(-1, false); // clear the skip list
#endif
_vm->_screenEffects->_entries.clear();
_vm->_overlays->removeAll();
_defaultLoop = 0;
_defaultLoopSet = false;
_defaultLoopPreloadedSet = false;
_specialLoopMode = kSceneLoopModeNone;
_specialLoop = -1;
_frame = -1;
}
Common::String vqaName;
int currentResourceId = _vm->_chapters->currentResourceId();
if (currentResourceId == 1) {
vqaName = Common::String::format("%s.VQA", sceneName.c_str());
} else {
vqaName = Common::String::format("%s_%d.VQA", sceneName.c_str(), MIN(currentResourceId, 3));
}
if (_vqaPlayer != nullptr) {
delete _vqaPlayer;
}
_vqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceBack, vqaName);
if (!_vm->_sceneScript->open(sceneName)) {
return false;
}
if (!isLoadingGame) {
_vm->_sceneScript->initializeScene();
}
Common::String setResourceName = Common::String::format("%s-MIN.SET", sceneName.c_str());
if (!_set->open(setResourceName)) {
return false;
}
_vm->_sliceRenderer->setView(_vm->_view);
if ((setId == kSetMA02_MA04 || setId == kSetMA04)
&& sceneId == kSceneMA04) {
_vm->setExtraCNotify(0);
}
if (isLoadingGame) {
resume(true);
if (sceneId == kScenePS10 // police maze
|| sceneId == kScenePS11 // police maze
|| sceneId == kScenePS12 // police maze
|| sceneId == kScenePS13 // police maze
#if BLADERUNNER_ORIGINAL_BUGS
#else
|| sceneId == kSceneUG01 // Steam room
#endif // BLADERUNNER_ORIGINAL_BUGS
) {
_vm->_sceneScript->sceneLoaded();
}
return true;
}
if (!_vqaPlayer->open()) {
return false;
}
if (_specialLoopMode == kSceneLoopModeNone) {
startDefaultLoop();
}
// This frame advancement (frame skip) may be required here
// It is in the original code and possible initializes some variables
// (or perhaps z-buffering related stuff)
// It may cause issues when in a scene we need to trigger some action
// based on the first frame of the loop when entering the scene (using SceneFrameAdvanced())
// (eg. it is contributing to the barrel flame glitch in pan from DR04 to DR01)
// However, better to resolve those issues with a workaround (eg. using InitializeScene())
advanceFrame();
_vm->_playerActor->setAtXYZ(_actorStartPosition, _actorStartFacing);
_vm->_playerActor->setSetId(setId);
_vm->_sceneScript->sceneLoaded();
_vm->_sceneObjects->clear();
// Init click map
int actorCount = _vm->_gameInfo->getActorCount();
for (int i = 0; i != actorCount; ++i) {
Actor *actor = _vm->_actors[i];
if (actor->getSetId() == setId) {
//debug("Actor added: %d", i);
#if !BLADERUNNER_ORIGINAL_BUGS
// ensure that actors' "hotspot" areas from previous scene are cleared up
actor->resetScreenRectangleAndBbox();
#endif
_vm->_sceneObjects->addActor(
i + kSceneObjectOffsetActors,
actor->getBoundingBox(),
actor->getScreenRectangle(),
true,
false,
actor->isTarget(),
actor->isRetired()
);
}
}
_set->addObjectsToScene(_vm->_sceneObjects);
_vm->_items->addToSet(setId);
_vm->_sceneObjects->updateObstacles();
if (_specialLoopMode != kSceneLoopModeLoseControl) {
_vm->_sceneScript->playerWalkedIn();
}
return true;
}
bool Scene::close(bool isLoadingGame) {
bool result = true;
if (getSetId() == -1) {
return true;
}
_vm->_policeMaze->clear(!isLoadingGame);
if (isLoadingGame) {
_vm->_sceneScript->playerWalkedOut();
}
// if (SceneScript_isLoaded() && !SceneScript_unload()) {
// result = false;
// }
if (_vqaPlayer != nullptr) {
//_vqaPlayer->stop();
delete _vqaPlayer;
_vqaPlayer = nullptr;
}
_sceneId = -1;
_setId = -1;
return result;
}
int Scene::advanceFrame(bool useTime) {
int frame = _vqaPlayer->update(false, true, useTime);
if (frame >= 0) {
blit(_vm->_surfaceBack, _vm->_surfaceFront);
_vqaPlayer->updateZBuffer(_vm->_zbuffer);
_vqaPlayer->updateView(_vm->_view);
_vqaPlayer->updateScreenEffects(_vm->_screenEffects);
_vqaPlayer->updateLights(_vm->_lights);
}
if (_specialLoopMode == kSceneLoopModeLoseControl || _specialLoopMode == kSceneLoopModeOnce || _specialLoopMode == kSceneLoopModeOnceNStay || _specialLoopMode == kSceneLoopModeSpinner) {
if (!_defaultLoopSet) {
_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeEnqueue, &Scene::loopEndedStatic, this);
_defaultLoopSet = true;
if (_specialLoopMode == kSceneLoopModeLoseControl) {
_vm->playerLosesControl();
}
}
} else if (_specialLoopMode == kSceneLoopModeChangeSet) {
if (frame == -3) { // EOF
_vm->_settings->setNewSetAndScene(_nextSetId, _nextSceneId);
_vm->playerGainsControl();
}
} else if (_specialLoopMode == kSceneLoopModeNone) {
if (!_defaultLoopPreloadedSet) {
_vqaPlayer->setLoop(_defaultLoop + 1, -1, kLoopSetModeJustStart, &Scene::loopEndedStatic, this);
_defaultLoopPreloadedSet = true;
}
}
if (frame >= 0) {
_frame = frame;
}
return frame;
}
void Scene::resume(bool isLoadingGame) {
if (!_vqaPlayer) {
return;
}
int targetFrame = _frame;
if (isLoadingGame) {
_vqaPlayer->open();
} else {
_vm->_zbuffer->disable();
}
if (_specialLoopMode == kSceneLoopModeNone) {
startDefaultLoop();
} else {
if (_specialLoopMode == kSceneLoopModeChangeSet) {
_vm->_settings->setNewSetAndScene(_setId, _sceneId);
}
if (_defaultLoopPreloadedSet) {
_specialLoopMode = kSceneLoopModeNone;
startDefaultLoop();
advanceFrame(false);
loopStartSpecial(_specialLoopMode, _specialLoop, false);
} else {
_defaultLoopPreloadedSet = true;
loopStartSpecial(_specialLoopMode, _specialLoop, true);
if (_specialLoopMode == kSceneLoopModeLoseControl || _specialLoopMode == kSceneLoopModeChangeSet) {
_vm->playerGainsControl();
}
}
if (_specialLoopMode == kSceneLoopModeChangeSet) {
_vm->_settings->clearNewSetAndScene();
}
}
int frame, frameStart, frameEnd;
do {
// fast forward to targetFrame but do, at the very least, one advanceFrame() (with time=false)
frame = advanceFrame(false);
// check if active loop has changed, and we need to adjust targetFrame
if (_vqaPlayer->getCurrentBeginAndEndFrame(frame, &frameStart, &frameEnd)
&& targetFrame >= 0
&& (targetFrame < frameStart || targetFrame > frameEnd)
) {
// active loop has changed, and targetFrame has a remnant frame value from the previous loop's frameset,
// so set it to the current loop's start
targetFrame = frameStart;
}
} while (frame >= 0 && frame != targetFrame);
if (!isLoadingGame) {
_vm->_zbuffer->enable();
}
}
void Scene::startDefaultLoop() {
_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeImmediate, nullptr, nullptr);
_defaultLoopSet = true;
_defaultLoopPreloadedSet = false;
}
void Scene::setActorStart(Vector3 position, int facing) {
_actorStartPosition = position;
_actorStartFacing = facing;
}
void Scene::loopSetDefault(int loopId) {
_defaultLoop = loopId;
}
void Scene::loopStartSpecial(int specialLoopMode, int loopId, bool immediately) {
_specialLoopMode = specialLoopMode;
_specialLoop = loopId;
int repeats = -1;
if (_specialLoopMode == kSceneLoopModeChangeSet || _specialLoopMode == kSceneLoopModeOnceNStay) {
repeats = 0;
}
int loopMode = kLoopSetModeEnqueue;
if (immediately) {
loopMode = kLoopSetModeImmediate;
}
_vqaPlayer->setLoop(_specialLoop, repeats, loopMode, &Scene::loopEndedStatic, this);
if (_specialLoopMode == kSceneLoopModeChangeSet) {
_nextSetId = _vm->_settings->getNewSet();
_nextSceneId = _vm->_settings->getNewScene();
}
if (immediately) {
_defaultLoopPreloadedSet = true;
loopEnded(0, _specialLoop);
}
}
int Scene::findObject(const Common::String &objectName) {
return _set->findObject(objectName);
}
bool Scene::objectSetHotMouse(int objectId) {
return _set->objectSetHotMouse(objectId);
}
bool Scene::objectGetBoundingBox(int objectId, BoundingBox *boundingBox) {
return _set->objectGetBoundingBox(objectId, boundingBox);
}
void Scene::objectSetIsClickable(int objectId, bool isClickable, bool sceneLoaded) {
_set->objectSetIsClickable(objectId, isClickable);
if (sceneLoaded) {
_vm->_sceneObjects->setIsClickable(objectId + kSceneObjectOffsetObjects, isClickable);
}
}
void Scene::objectSetIsObstacle(int objectId, bool isObstacle, bool sceneLoaded, bool updateWalkpath) {
_set->objectSetIsObstacle(objectId, isObstacle);
if (sceneLoaded) {
_vm->_sceneObjects->setIsObstacle(objectId + kSceneObjectOffsetObjects, isObstacle);
if (updateWalkpath) {
_vm->_sceneObjects->updateObstacles();
}
}
}
void Scene::objectSetIsObstacleAll(bool isObstacle, bool sceneLoaded) {
int i;
for (i = 0; i < (int)_set->getObjectCount(); ++i) {
_set->objectSetIsObstacle(i, isObstacle);
if (sceneLoaded) {
_vm->_sceneObjects->setIsObstacle(i + kSceneObjectOffsetObjects, isObstacle);
}
}
}
void Scene::objectSetIsTarget(int objectId, bool isTarget, bool sceneLoaded) {
_set->objectSetIsTarget(objectId, isTarget);
if (sceneLoaded) {
_vm->_sceneObjects->setIsTarget(objectId + kSceneObjectOffsetObjects, isTarget);
}
}
const Common::String &Scene::objectGetName(int objectId) {
return _set->objectGetName(objectId);
}
void Scene::loopEnded(int frame, int loopId) {
if (_specialLoopMode == kSceneLoopModeLoseControl || _specialLoopMode == kSceneLoopModeOnce || _specialLoopMode == kSceneLoopModeSpinner) {
if (_defaultLoopPreloadedSet) {
_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeEnqueue, &Scene::loopEndedStatic, this);
_defaultLoopSet = true;
_defaultLoopPreloadedSet = false;
if (_specialLoopMode == kSceneLoopModeLoseControl) {
_vm->playerLosesControl();
}
} else {
if (_specialLoopMode == kSceneLoopModeLoseControl) {
_vm->playerGainsControl();
_playerWalkedIn = true;
}
if (_specialLoopMode == kSceneLoopModeSpinner) {
_vm->_spinner->open();
}
_specialLoopMode = kSceneLoopModeNone;
_specialLoop = -1;
_vqaPlayer->setLoop(_defaultLoop + 1, -1, kLoopSetModeJustStart, nullptr, nullptr);
_defaultLoopPreloadedSet = true;
}
} else if (_specialLoopMode == kSceneLoopModeChangeSet) {
_defaultLoopSet = true;
_defaultLoopPreloadedSet = false;
_vm->playerLosesControl();
}
}
void Scene::loopEndedStatic(void *data, int frame, int loopId) {
((Scene *)data)->loopEnded(frame, loopId);
}
void Scene::save(SaveFileWriteStream &f) {
f.writeInt(_setId);
f.writeInt(_sceneId);
f.writeInt(_defaultLoop);
f.writeBool(_defaultLoopSet);
f.writeBool(_defaultLoopPreloadedSet);
f.writeInt(_specialLoopMode);
f.writeInt(_specialLoop);
f.writeInt(_nextSetId);
f.writeInt(_nextSceneId);
f.writeInt(_frame);
f.writeVector3(_actorStartPosition);
f.writeInt(_actorStartFacing);
f.writeBool(_playerWalkedIn);
}
void Scene::load(SaveFileReadStream &f) {
_setId = f.readInt();
_sceneId = f.readInt();
_defaultLoop = f.readInt();
_defaultLoopSet = f.readBool();
_defaultLoopPreloadedSet = f.readBool();
_specialLoopMode = f.readInt();
_specialLoop = f.readInt();
_nextSetId = f.readInt();
_nextSceneId = f.readInt();
_frame = f.readInt();
_actorStartPosition = f.readVector3();
_actorStartFacing = f.readInt();
_playerWalkedIn = f.readBool();
}
} // End of namespace BladeRunner

105
engines/bladerunner/scene.h Normal file
View File

@@ -0,0 +1,105 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_SCENE_H
#define BLADERUNNER_SCENE_H
#include "bladerunner/vector.h"
#include "common/str.h"
namespace BladeRunner {
class BladeRunnerEngine;
class BoundingBox;
class Regions;
class SaveFileReadStream;
class SaveFileWriteStream;
class Set;
class VQAPlayer;
class Scene {
friend class Debugger;
BladeRunnerEngine *_vm;
int _setId;
int _sceneId;
VQAPlayer *_vqaPlayer;
int _defaultLoop;
bool _defaultLoopSet;
bool _defaultLoopPreloadedSet;
int _specialLoopMode;
int _specialLoop;
// int _introFinished;
int _nextSetId;
int _nextSceneId;
int _frame;
Vector3 _actorStartPosition;
int _actorStartFacing;
bool _playerWalkedIn;
public:
Set *_set;
Regions *_regions;
Regions *_exits;
public:
Scene(BladeRunnerEngine *vm);
~Scene();
bool open(int setId, int sceneId, bool isLoadingGame);
bool close(bool isLoadingGame);
int advanceFrame(bool useTime = true);
void resume(bool isLoadingGame = false);
void startDefaultLoop();
void setActorStart(Vector3 position, int facing);
void loopSetDefault(int loopId);
void loopStartSpecial(int specialLoopMode, int loopId, bool immediately);
int getSetId() const { return _setId; }
int getSceneId() const { return _sceneId; }
bool didPlayerWalkIn() { bool r = _playerWalkedIn; _playerWalkedIn = false; return r; }
int findObject(const Common::String &objectName);
bool objectSetHotMouse(int objectId);
bool objectGetBoundingBox(int objectId, BoundingBox *boundingBox);
void objectSetIsClickable(int objectId, bool isClickable, bool sceneLoaded);
void objectSetIsObstacle(int objectId, bool isObstacle, bool sceneLoaded, bool updateWalkpath);
void objectSetIsObstacleAll(bool isObstacle, bool sceneLoaded);
void objectSetIsTarget(int objectId, bool isTarget, bool sceneLoaded);
const Common::String &objectGetName(int objectId);
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
void loopEnded(int frame, int loopId);
static void loopEndedStatic(void *data, int frame, int loopId);
};
} // End of namespace BladeRunner
#endif

View File

@@ -0,0 +1,428 @@
/* 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/scene_objects.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/obstacles.h"
#include "bladerunner/savefile.h"
#include "bladerunner/view.h"
namespace BladeRunner {
SceneObjects::SceneObjects(BladeRunnerEngine *vm, View *view) {
_vm = vm;
_view = view;
_count = 0;
clear();
}
SceneObjects::~SceneObjects() {
_vm = nullptr;
_view = nullptr;
_count = 0;
}
void SceneObjects::clear() {
for (int i = 0; i < kSceneObjectCount; ++i) {
_sceneObjects[i].id = -1;
_sceneObjects[i].type = kSceneObjectTypeUnknown;
_sceneObjects[i].distanceToCamera = 0.0f;
_sceneObjects[i].isPresent = false;
_sceneObjects[i].isClickable = false;
_sceneObjects[i].isObstacle = false;
_sceneObjects[i].unknown1 = 0;
_sceneObjects[i].isTarget = false;
_sceneObjects[i].isMoving = false;
_sceneObjects[i].isRetired = false;
_sceneObjectsSortedByDistance[i] = -1;
}
_count = 0;
}
bool SceneObjects::addActor(int sceneObjectId, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isClickable, bool isMoving, bool isTarget, bool isRetired) {
return addSceneObject(sceneObjectId, kSceneObjectTypeActor, boundingBox, screenRectangle, isClickable, false, 0, isTarget, isMoving, isRetired);
}
bool SceneObjects::addObject(int sceneObjectId, const BoundingBox &boundingBox, bool isClickable, bool isObstacle, uint8 unknown1, bool isTarget) {
Common::Rect rect(-1, -1, -1, -1);
return addSceneObject(sceneObjectId, kSceneObjectTypeObject, boundingBox, rect, isClickable, isObstacle, unknown1, isTarget, false, false);
}
bool SceneObjects::addItem(int sceneObjectId, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isTarget, bool isObstacle) {
return addSceneObject(sceneObjectId, kSceneObjectTypeItem, boundingBox, screenRectangle, isObstacle, 0, 0, isTarget, 0, 0);
}
bool SceneObjects::remove(int sceneObjectId) {
int i = findById(sceneObjectId);
if (i == -1) {
return false;
}
_sceneObjects[i].isPresent = false;
int j;
for (j = 0; j < _count; ++j) {
if (_sceneObjectsSortedByDistance[j] == i) {
break;
}
}
for (int k = j; k < _count - 1; ++k) {
_sceneObjectsSortedByDistance[k] = _sceneObjectsSortedByDistance[k + 1];
}
--_count;
return true;
}
int SceneObjects::findByXYZ(bool *isClickable, bool *isObstacle, bool *isTarget, Vector3 &position, bool findClickables, bool findObstacles, bool findTargets) const {
*isClickable = false;
*isObstacle = false;
*isTarget = false;
for (int i = 0; i < _count; ++i) {
assert(_sceneObjectsSortedByDistance[i] < kSceneObjectCount);
const SceneObject *sceneObject = &_sceneObjects[_sceneObjectsSortedByDistance[i]];
if ((findClickables && sceneObject->isClickable) ||
(findObstacles && sceneObject->isObstacle) ||
(findTargets && sceneObject->isTarget)) {
BoundingBox boundingBox = sceneObject->boundingBox;
if (sceneObject->type == kSceneObjectTypeActor) {
boundingBox.expand(-4.0, 0.0, -4.0, 4.0, 0.0, 4.0);
}
if (boundingBox.inside(position)) {
*isClickable = sceneObject->isClickable;
*isObstacle = sceneObject->isObstacle;
*isTarget = sceneObject->isTarget;
return sceneObject->id;
}
}
}
return -1;
}
bool SceneObjects::existsOnXZ(int exceptSceneObjectId, float x, float z, bool movingActorIsObstacle, bool standingActorIsObstacle) const {
float xMin = x - 12.5f;
float xMax = x + 12.5f;
float zMin = z - 12.5f;
float zMax = z + 12.5f;
int count = _count;
if (count > 0) {
for (int i = 0; i < count; ++i) {
const SceneObject *sceneObject = &_sceneObjects[_sceneObjectsSortedByDistance[i]];
bool isObstacle = false;
if (sceneObject->type == kSceneObjectTypeActor) {
if (sceneObject->isRetired) {
isObstacle = false;
} else if (sceneObject->isMoving) {
isObstacle = movingActorIsObstacle;
} else {
isObstacle = standingActorIsObstacle;
}
} else {
isObstacle = sceneObject->isObstacle;
}
if (isObstacle && sceneObject->id != exceptSceneObjectId) {
float x1, y1, z1, x2, y2, z2;
sceneObject->boundingBox.getXYZ(&x1, &y1, &z1, &x2, &y2, &z2);
if (z1 <= zMax && z2 >= zMin && x1 <= xMax && x2 >= xMin) {
// if (sceneObject->type == kSceneObjectTypeObject) {
// Vector3 a(x1,y1,z1);
// Vector3 b(x2,y2,z2);
// Vector3 pos = _vm->_view->calculateScreenPosition(0.5 * (a + b));
// debug("%d: %s (Clk: %s, Trg: %s, Prs: %s, Obs: %s, Mvg: %s), Pos(%02.2f,%02.2f,%02.2f)\n Bbox(%02.2f,%02.2f,%02.2f) ~ (%02.2f,%02.2f,%02.2f)\n",
// sceneObject->id - kSceneObjectOffsetObjects,
// _vm->_scene->objectGetName(sceneObject->id - kSceneObjectOffsetObjects).c_str(),
// sceneObject->isClickable? "T" : "F",
// sceneObject->isTarget? "T" : "F",
// sceneObject->isPresent? "T" : "F",
// sceneObject->isObstacle? "T" : "F",
// sceneObject->isMoving? "T" : "F",
// pos.x, pos.y, pos.z,
// a.x, a.y, a.z, b.x, b.y, b.z);
// }
return true;
}
}
}
}
return false;
}
int SceneObjects::findById(int sceneObjectId) const {
for (int i = 0; i < _count; ++i) {
int j = this->_sceneObjectsSortedByDistance[i];
if (_sceneObjects[j].isPresent && _sceneObjects[j].id == sceneObjectId) {
return j;
}
}
return -1;
}
bool SceneObjects::addSceneObject(int sceneObjectId, SceneObjectType sceneObjectType, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isClickable, bool isObstacle, uint8 unknown1, bool isTarget, bool isMoving, bool isRetired) {
int index = findEmpty();
if (index == -1) {
return false;
}
_sceneObjects[index].id = sceneObjectId;
_sceneObjects[index].type = sceneObjectType;
_sceneObjects[index].isPresent = true;
_sceneObjects[index].boundingBox = boundingBox;
_sceneObjects[index].screenRectangle = screenRectangle;
_sceneObjects[index].isClickable = isClickable;
_sceneObjects[index].isObstacle = isObstacle;
_sceneObjects[index].unknown1 = unknown1;
_sceneObjects[index].isTarget = isTarget;
_sceneObjects[index].isMoving = isMoving;
_sceneObjects[index].isRetired = isRetired;
float centerZ = (_sceneObjects[index].boundingBox.getZ0() + _sceneObjects[index].boundingBox.getZ1()) / 2.0f;
float distanceToCamera = fabs(-centerZ - _view->_cameraPosition.y); // y<->z is intentional, not a bug
_sceneObjects[index].distanceToCamera = distanceToCamera;
// insert according to distance from camera
int i;
for (i = 0; i < _count; ++i) {
if (distanceToCamera < _sceneObjects[_sceneObjectsSortedByDistance[i]].distanceToCamera) {
break;
}
}
for (int j = CLIP(_count - 1, 0, kSceneObjectCount - 2); j >= i; --j) {
_sceneObjectsSortedByDistance[j + 1] = _sceneObjectsSortedByDistance[j];
}
_sceneObjectsSortedByDistance[i] = index;
++_count;
return true;
}
int SceneObjects::findEmpty() const {
for (int i = 0; i < kSceneObjectCount; ++i) {
if (!_sceneObjects[i].isPresent)
return i;
}
return -1;
}
void SceneObjects::setMoving(int sceneObjectId, bool isMoving) {
int i = findById(sceneObjectId);
if (i == -1) {
return;
}
_sceneObjects[i].isMoving = isMoving;
}
void SceneObjects::setRetired(int sceneObjectId, bool isRetired) {
int i = findById(sceneObjectId);
if (i == -1) {
return;
}
_sceneObjects[i].isRetired = isRetired;
}
bool SceneObjects::isBetween(float sourceX, float sourceZ, float targetX, float targetZ, int sceneObjectId) const {
int i = findById(sceneObjectId);
if (i == -1) {
return false;
}
float objectX1, objectY1, objectZ1, objectX2, objectY2, objectZ2;
_sceneObjects[i].boundingBox.getXYZ(&objectX1, &objectY1, &objectZ1, &objectX2, &objectY2, &objectZ2);
Vector2 intersection;
return lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX1, objectZ1), Vector2(objectX2, objectZ1), &intersection)
|| lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX2, objectZ1), Vector2(objectX2, objectZ2), &intersection)
|| lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX2, objectZ2), Vector2(objectX1, objectZ2), &intersection)
|| lineIntersection(Vector2(sourceX, sourceZ), Vector2(targetX, targetZ), Vector2(objectX1, objectZ2), Vector2(objectX1, objectZ1), &intersection);
}
bool SceneObjects::isObstacleBetween(const Vector3 &source, const Vector3 &target, int exceptSceneObjectId) const {
for (int i = 0; i < _count; ++i) {
const SceneObject *sceneObject = &_sceneObjects[_sceneObjectsSortedByDistance[i]];
if (sceneObject->type == kSceneObjectTypeActor || !sceneObject->isObstacle || sceneObject->id == exceptSceneObjectId) {
continue;
}
float objectX1, objectY1, objectZ1, objectX2, objectY2, objectZ2;
sceneObject->boundingBox.getXYZ(&objectX1, &objectY1, &objectZ1, &objectX2, &objectY2, &objectZ2);
if (84.0f <= objectY1 - source.y || 72.0f >= objectY2 - source.y) {
continue;
}
float xAdjustement = (objectX2 - objectX1) * 0.1f;
float zAdjustement = (objectZ2 - objectZ1) * 0.1f;
objectX1 = objectX1 + xAdjustement;
objectZ1 = objectZ1 + zAdjustement;
objectX2 = objectX2 - xAdjustement;
objectZ2 = objectZ2 - zAdjustement;
Vector2 intersection;
if (lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX1, objectZ1), Vector2(objectX2, objectZ1), &intersection)
|| lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX2, objectZ1), Vector2(objectX2, objectZ2), &intersection)
|| lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX2, objectZ2), Vector2(objectX1, objectZ2), &intersection)
|| lineIntersection(Vector2(source.x, source.z), Vector2(target.x, target.z), Vector2(objectX1, objectZ2), Vector2(objectX1, objectZ1), &intersection)) {
return true;
}
}
return false;
}
void SceneObjects::setIsClickable(int sceneObjectId, bool isClickable) {
int i = findById(sceneObjectId);
if (i == -1) {
return;
}
_sceneObjects[i].isClickable = isClickable;
}
void SceneObjects::setIsObstacle(int sceneObjectId, bool isObstacle) {
int i = findById(sceneObjectId);
if (i == -1) {
return;
}
_sceneObjects[i].isObstacle = isObstacle;
}
void SceneObjects::setIsTarget(int sceneObjectId, bool isTarget) {
int i = findById(sceneObjectId);
if (i == -1) {
return;
}
_sceneObjects[i].isTarget = isTarget;
}
void SceneObjects::updateObstacles() {
_vm->_obstacles->clear();
for (int i = 0; i < _count; ++i) {
int index = _sceneObjectsSortedByDistance[i];
const SceneObject *sceneObject = &_sceneObjects[index];
if (sceneObject->isObstacle) {
float x0, y0, z0, x1, y1, z1;
sceneObject->boundingBox.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
_vm->_obstacles->add(x0, z0, x1, z1);
}
}
_vm->_obstacles->backup();
}
bool SceneObjects::isEmptyScreenRectangle(int sceneObjectId) {
int index = findById(sceneObjectId);
if (index != -1) {
const SceneObject *sceneObject = &_sceneObjects[index];
return sceneObject->screenRectangle.isEmpty();
}
return true;
}
int SceneObjects::compareScreenRectangle(int sceneObjectId, const Common::Rect &rectangle) {
int index = findById(sceneObjectId);
if (index != -1) {
const SceneObject *sceneObject = &_sceneObjects[index];
if (sceneObject->screenRectangle == rectangle) {
return 0;
}
}
return -1;
}
void SceneObjects::resetScreenRectangleAndBbox(int sceneObjectId) {
int index = findById(sceneObjectId);
if (index != -1) {
SceneObject *sceneObject = &_sceneObjects[index];
sceneObject->screenRectangle.left = -1;
sceneObject->screenRectangle.top = -1;
sceneObject->screenRectangle.right = -1;
sceneObject->screenRectangle.bottom = -1;
sceneObject->boundingBox.setXYZ(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
}
}
void SceneObjects::synchScreenRectangle(int sceneObjectId, const Common::Rect &targetScreenRect) {
int index = findById(sceneObjectId);
if (index != -1) {
SceneObject *sceneObject = &_sceneObjects[index];
sceneObject->screenRectangle.left = targetScreenRect.left;
sceneObject->screenRectangle.top = targetScreenRect.top;
sceneObject->screenRectangle.right = targetScreenRect.right;
sceneObject->screenRectangle.bottom = targetScreenRect.bottom;
}
}
void SceneObjects::save(SaveFileWriteStream &f) {
f.writeInt(_count);
for (int i = 0; i < kSceneObjectCount; ++i) {
f.writeInt(_sceneObjects[i].id);
f.writeInt(_sceneObjects[i].type);
f.writeBoundingBox(_sceneObjects[i].boundingBox, true);
f.writeRect(_sceneObjects[i].screenRectangle);
f.writeFloat(_sceneObjects[i].distanceToCamera);
f.writeBool(_sceneObjects[i].isPresent);
f.writeBool(_sceneObjects[i].isClickable);
f.writeBool(_sceneObjects[i].isObstacle);
f.writeInt(_sceneObjects[i].unknown1);
f.writeBool(_sceneObjects[i].isTarget);
f.writeBool(_sceneObjects[i].isMoving);
f.writeBool(_sceneObjects[i].isRetired);
}
for (int i = 0; i < kSceneObjectCount; ++i) {
f.writeInt(_sceneObjectsSortedByDistance[i]);
}
}
void SceneObjects::load(SaveFileReadStream &f) {
_count = f.readInt();
for (int i = 0; i < kSceneObjectCount; ++i) {
_sceneObjects[i].id = f.readInt();
_sceneObjects[i].type = (SceneObjectType)f.readInt();
_sceneObjects[i].boundingBox = f.readBoundingBox(true);
_sceneObjects[i].screenRectangle = f.readRect();
_sceneObjects[i].distanceToCamera = f.readFloat();
_sceneObjects[i].isPresent = f.readBool();
_sceneObjects[i].isClickable = f.readBool();
_sceneObjects[i].isObstacle = f.readBool();
_sceneObjects[i].unknown1 = f.readInt();
_sceneObjects[i].isTarget = f.readBool();
_sceneObjects[i].isMoving = f.readBool();
_sceneObjects[i].isRetired = f.readBool();
}
for (int i = 0; i < kSceneObjectCount; ++i) {
_sceneObjectsSortedByDistance[i] = f.readInt();
}
}
} // End of namespace BladeRunner

View File

@@ -0,0 +1,107 @@
/* 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/>.
*
*/
#ifndef BLADERUNNER_SCENE_OBJECTS_H
#define BLADERUNNER_SCENE_OBJECTS_H
#include "bladerunner/boundingbox.h"
#include "common/rect.h"
namespace BladeRunner {
class BladeRunnerEngine;
class SaveFileReadStream;
class SaveFileWriteStream;
class View;
enum SceneObjectType {
kSceneObjectTypeUnknown = -1,
kSceneObjectTypeActor = 0,
kSceneObjectTypeObject = 1,
kSceneObjectTypeItem = 2
};
class SceneObjects {
friend class Debugger;
static const int kSceneObjectCount = 115;
struct SceneObject {
int id;
SceneObjectType type;
BoundingBox boundingBox;
Common::Rect screenRectangle;
float distanceToCamera;
bool isPresent;
bool isClickable;
bool isObstacle;
int unknown1;
bool isTarget;
bool isMoving;
bool isRetired;
};
BladeRunnerEngine *_vm;
View *_view;
int _count;
SceneObject _sceneObjects[kSceneObjectCount];
int _sceneObjectsSortedByDistance[kSceneObjectCount];
public:
SceneObjects(BladeRunnerEngine *vm, View *view);
~SceneObjects();
bool addActor(int sceneObjectId, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isClickable, bool isMoving, bool isTarget, bool isRetired);
bool addObject(int sceneObjectId, const BoundingBox &boundingBox, bool isClickable, bool isObstacle, uint8 unknown1, bool isTarget);
bool addItem(int sceneObjectId, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isTarget, bool isObstacle);
bool remove(int sceneObjectId);
void clear();
int findByXYZ(bool *isClickable, bool *isObstacle, bool *isTarget, Vector3 &position, bool findClickables, bool findObstacles, bool findTargets) const;
bool existsOnXZ(int exceptSceneObjectId, float x, float z, bool movingActorIsObstacle, bool standingActorIsObstacle) const;
void setMoving(int sceneObjectId, bool isMoving);
void setRetired(int sceneObjectId, bool isRetired);
bool isBetween(float sourceX, float sourceZ, float targetX, float targetZ, int sceneObjectId) const;
bool isObstacleBetween(const Vector3 &source, const Vector3 &target, int exceptSceneObjectId) const;
void setIsClickable(int sceneObjectId, bool isClickable);
void setIsObstacle(int sceneObjectId, bool isObstacle);
void setIsTarget(int sceneObjectId, bool isTarget);
void updateObstacles();
int getCount() { return _count; }
int findById(int sceneObjectId) const;
bool isEmptyScreenRectangle(int sceneObjectId);
int compareScreenRectangle(int sceneObjectId, const Common::Rect &rectangle);
void resetScreenRectangleAndBbox(int sceneObjectId);
void synchScreenRectangle(int sceneObjectId, const Common::Rect &targetScreenRect);
void save(SaveFileWriteStream &f);
void load(SaveFileReadStream &f);
private:
bool addSceneObject(int sceneObjectId, SceneObjectType sceneObjectType, const BoundingBox &boundingBox, const Common::Rect &screenRectangle, bool isClickable, bool isObstacle, uint8 unknown1, bool isTarget, bool isMoving, bool isRetired);
int findEmpty() const;
};
} // End of namespace BladeRunner
#endif

Some files were not shown because too many files have changed in this diff Show More