Initial commit
This commit is contained in:
1
engines/adl/POTFILES
Normal file
1
engines/adl/POTFILES
Normal file
@@ -0,0 +1 @@
|
||||
engines/adl/metaengine.cpp
|
||||
1587
engines/adl/adl.cpp
Normal file
1587
engines/adl/adl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
482
engines/adl/adl.h
Normal file
482
engines/adl/adl.h
Normal file
@@ -0,0 +1,482 @@
|
||||
/* 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 ADL_ADL_H
|
||||
#define ADL_ADL_H
|
||||
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/array.h"
|
||||
#include "common/rect.h"
|
||||
#include "common/str.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/hash-str.h"
|
||||
#include "common/func.h"
|
||||
#include "common/ptr.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/keyboard.h"
|
||||
|
||||
#include "engines/engine.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/softsynth/pcspk.h"
|
||||
|
||||
#include "adl/console.h"
|
||||
#include "adl/disk.h"
|
||||
#include "adl/sound.h"
|
||||
#include "adl/detection.h"
|
||||
|
||||
namespace Common {
|
||||
class ReadStream;
|
||||
class WriteStream;
|
||||
class SeekableReadStream;
|
||||
class File;
|
||||
struct Event;
|
||||
class RandomSource;
|
||||
}
|
||||
|
||||
namespace Adl {
|
||||
|
||||
Common::Path getDiskImageName(const AdlGameDescription &adlDesc, byte volume);
|
||||
GameType getGameType(const AdlGameDescription &desc);
|
||||
GameVersion getGameVersion(const AdlGameDescription &desc);
|
||||
Common::Language getLanguage(const AdlGameDescription &desc);
|
||||
Common::Platform getPlatform(const AdlGameDescription &desc);
|
||||
|
||||
class Console;
|
||||
class Display;
|
||||
class GraphicsMan;
|
||||
class ScriptEnv;
|
||||
|
||||
enum kDebugChannels {
|
||||
kDebugChannelScript = 1,
|
||||
};
|
||||
|
||||
enum ADLAction {
|
||||
kADLActionNone,
|
||||
kADLActionQuit,
|
||||
|
||||
kADLActionCount
|
||||
};
|
||||
|
||||
// Save and restore opcodes
|
||||
#define IDO_ACT_SAVE 0x0f
|
||||
#define IDO_ACT_LOAD 0x10
|
||||
|
||||
#define IDI_CUR_ROOM 0xfc
|
||||
#define IDI_VOID_ROOM 0xfd
|
||||
#define IDI_ANY 0xfe
|
||||
|
||||
#define IDI_WORD_SIZE 8
|
||||
|
||||
enum Direction {
|
||||
IDI_DIR_NORTH,
|
||||
IDI_DIR_SOUTH,
|
||||
IDI_DIR_EAST,
|
||||
IDI_DIR_WEST,
|
||||
IDI_DIR_UP,
|
||||
IDI_DIR_DOWN,
|
||||
IDI_DIR_TOTAL
|
||||
};
|
||||
|
||||
struct Room {
|
||||
Room() :
|
||||
description(0),
|
||||
picture(0),
|
||||
curPicture(0),
|
||||
isFirstTime(true) {
|
||||
memset(connections, 0, sizeof(connections));
|
||||
}
|
||||
|
||||
byte description;
|
||||
byte connections[IDI_DIR_TOTAL];
|
||||
Common::DataBlockPtr data;
|
||||
byte picture;
|
||||
byte curPicture;
|
||||
bool isFirstTime;
|
||||
};
|
||||
|
||||
typedef Common::HashMap<byte, Common::DataBlockPtr> PictureMap;
|
||||
|
||||
typedef Common::Array<byte> Script;
|
||||
|
||||
struct Command {
|
||||
byte room;
|
||||
byte verb, noun;
|
||||
byte numCond, numAct;
|
||||
Script script;
|
||||
};
|
||||
|
||||
class ScriptEnv {
|
||||
public:
|
||||
ScriptEnv(const Command &cmd, byte room, byte verb, byte noun) :
|
||||
_cmd(cmd), _room(room), _verb(verb), _noun(noun), _ip(0) { }
|
||||
|
||||
virtual ~ScriptEnv() { }
|
||||
|
||||
enum kOpType {
|
||||
kOpTypeDone,
|
||||
kOpTypeCond,
|
||||
kOpTypeAct
|
||||
};
|
||||
|
||||
byte op() const { return _cmd.script[_ip]; }
|
||||
virtual kOpType getOpType() const = 0;
|
||||
// We keep this 1-based for easier comparison with the original engine
|
||||
byte arg(uint i) const { return _cmd.script[_ip + i]; }
|
||||
virtual void next(uint numArgs) = 0;
|
||||
|
||||
bool isMatch() const {
|
||||
return (_cmd.room == IDI_ANY || _cmd.room == _room) &&
|
||||
(_cmd.verb == IDI_ANY || _cmd.verb == _verb) &&
|
||||
(_cmd.noun == IDI_ANY || _cmd.noun == _noun);
|
||||
}
|
||||
|
||||
byte getNoun() const { return _noun; }
|
||||
const Command &getCommand() const { return _cmd; }
|
||||
|
||||
protected:
|
||||
byte _ip;
|
||||
|
||||
private:
|
||||
const Command &_cmd;
|
||||
const byte _room, _verb, _noun;
|
||||
};
|
||||
|
||||
enum {
|
||||
IDI_ITEM_NOT_MOVED,
|
||||
IDI_ITEM_DROPPED,
|
||||
IDI_ITEM_DOESNT_MOVE
|
||||
};
|
||||
|
||||
struct Item {
|
||||
byte id;
|
||||
byte noun;
|
||||
byte region;
|
||||
byte room;
|
||||
byte picture;
|
||||
bool isShape;
|
||||
Common::Point position;
|
||||
int state;
|
||||
byte description;
|
||||
Common::Array<byte> roomPictures;
|
||||
bool isOnScreen;
|
||||
|
||||
Item() : id(0), noun(0), region(0), room(0), picture(0), isShape(false), state(0), description(0), isOnScreen(false) { }
|
||||
};
|
||||
|
||||
struct Time {
|
||||
byte hours, minutes;
|
||||
|
||||
Time() : hours(12), minutes(0) { }
|
||||
};
|
||||
|
||||
struct RoomState {
|
||||
byte picture;
|
||||
byte isFirstTime;
|
||||
};
|
||||
|
||||
struct Region {
|
||||
Common::Array<byte> vars;
|
||||
Common::Array<RoomState> rooms;
|
||||
};
|
||||
|
||||
struct State {
|
||||
Common::Array<Region> regions;
|
||||
Common::Array<Room> rooms;
|
||||
Common::List<Item> items;
|
||||
Common::Array<byte> vars;
|
||||
|
||||
byte region, prevRegion;
|
||||
byte room;
|
||||
byte curPicture;
|
||||
uint16 moves;
|
||||
bool isDark;
|
||||
Time time;
|
||||
|
||||
State() : region(0), prevRegion(0), room(1), curPicture(0), moves(1), isDark(false) { }
|
||||
};
|
||||
|
||||
typedef Common::List<Command> Commands;
|
||||
typedef Common::HashMap<Common::String, uint> WordMap;
|
||||
|
||||
struct RoomData {
|
||||
Common::String description;
|
||||
PictureMap pictures;
|
||||
Commands commands;
|
||||
};
|
||||
|
||||
// Opcode debugging macros
|
||||
#define OP_DEBUG_0(F) do { \
|
||||
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F)) \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#define OP_DEBUG_1(F, P1) do { \
|
||||
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1)) \
|
||||
return 1; \
|
||||
} while (0)
|
||||
|
||||
#define OP_DEBUG_2(F, P1, P2) do { \
|
||||
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2)) \
|
||||
return 2; \
|
||||
} while (0)
|
||||
|
||||
#define OP_DEBUG_3(F, P1, P2, P3) do { \
|
||||
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3)) \
|
||||
return 3; \
|
||||
} while (0)
|
||||
|
||||
#define OP_DEBUG_4(F, P1, P2, P3, P4) do { \
|
||||
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3, P4)) \
|
||||
return 4; \
|
||||
} while (0)
|
||||
|
||||
class AdlEngine : public Engine {
|
||||
friend class Console;
|
||||
public:
|
||||
~AdlEngine() override;
|
||||
|
||||
bool pollEvent(Common::Event &event) const;
|
||||
void bell(uint count = 1) const;
|
||||
|
||||
protected:
|
||||
AdlEngine(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
// Engine
|
||||
Common::Error loadGameState(int slot) override;
|
||||
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
|
||||
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
Common::String getSaveStateName(int slot) const override;
|
||||
int getAutosaveSlot() const override { return 15; }
|
||||
|
||||
Common::Path getDiskImageName(byte volume) const { return Adl::getDiskImageName(*_gameDescription, volume); }
|
||||
GameType getGameType() const { return Adl::getGameType(*_gameDescription); }
|
||||
GameVersion getGameVersion() const { return Adl::getGameVersion(*_gameDescription); }
|
||||
Common::Language getLanguage() const { return Adl::getLanguage(*_gameDescription); }
|
||||
virtual void gameLoop();
|
||||
virtual void loadState(Common::ReadStream &stream);
|
||||
virtual void saveState(Common::WriteStream &stream);
|
||||
Common::String readString(Common::ReadStream &stream, byte until = 0, const char *key = "") const;
|
||||
Common::String readStringAt(Common::SeekableReadStream &stream, uint offset, byte until = 0) const;
|
||||
void extractExeStrings(Common::ReadStream &stream, uint16 printAddr, Common::StringArray &strings) const;
|
||||
|
||||
virtual void printString(const Common::String &str) = 0;
|
||||
virtual Common::String loadMessage(uint idx) const = 0;
|
||||
virtual void printMessage(uint idx);
|
||||
virtual Common::String getItemDescription(const Item &item) const;
|
||||
void delay(uint32 ms) const;
|
||||
|
||||
virtual Common::String getLine();
|
||||
Common::String inputString(byte prompt = 0) const;
|
||||
byte inputKey(bool showCursor = true) const;
|
||||
void waitKey(uint32 ms = 0, Common::KeyCode keycode = Common::KEYCODE_INVALID) const;
|
||||
virtual void getInput(uint &verb, uint &noun);
|
||||
Common::String getWord(const Common::String &line, uint &index) const;
|
||||
|
||||
virtual Common::String formatVerbError(const Common::String &verb) const;
|
||||
virtual Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
|
||||
void loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri, uint count = 0) const;
|
||||
void readCommands(Common::ReadStream &stream, Commands &commands);
|
||||
void removeCommand(Commands &commands, uint idx);
|
||||
Command &getCommand(Commands &commands, uint idx);
|
||||
void removeMessage(uint idx);
|
||||
void checkInput(byte verb, byte noun);
|
||||
virtual bool isInputValid(byte verb, byte noun, bool &is_any);
|
||||
virtual bool isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any);
|
||||
virtual void applyRoomWorkarounds(byte roomNr) { }
|
||||
virtual void applyRegionWorkarounds() { }
|
||||
|
||||
virtual void setupOpcodeTables();
|
||||
virtual void initState();
|
||||
virtual void switchRoom(byte roomNr);
|
||||
virtual byte roomArg(byte room) const;
|
||||
virtual void advanceClock() { }
|
||||
void loadDroppedItemOffsets(Common::ReadStream &stream, byte count);
|
||||
|
||||
void stopTextToSpeech() const;
|
||||
|
||||
// Opcodes
|
||||
typedef Common::SharedPtr<Common::Functor1<ScriptEnv &, int> > Opcode;
|
||||
|
||||
template <class T>
|
||||
Opcode opcode(int (T::*f)(ScriptEnv &)) {
|
||||
return Opcode(new Common::Functor1Mem<ScriptEnv &, int, T>(static_cast<T *>(this), f));
|
||||
}
|
||||
|
||||
virtual int o_isItemInRoom(ScriptEnv &e);
|
||||
virtual int o_isMovesGT(ScriptEnv &e);
|
||||
virtual int o_isVarEQ(ScriptEnv &e);
|
||||
virtual int o_isCurPicEQ(ScriptEnv &e);
|
||||
virtual int o_isItemPicEQ(ScriptEnv &e);
|
||||
|
||||
virtual int o_varAdd(ScriptEnv &e);
|
||||
virtual int o_varSub(ScriptEnv &e);
|
||||
virtual int o_varSet(ScriptEnv &e);
|
||||
virtual int o_listInv(ScriptEnv &e);
|
||||
virtual int o_moveItem(ScriptEnv &e);
|
||||
virtual int o_setRoom(ScriptEnv &e);
|
||||
virtual int o_setCurPic(ScriptEnv &e);
|
||||
virtual int o_setPic(ScriptEnv &e);
|
||||
virtual int o_printMsg(ScriptEnv &e);
|
||||
virtual int o_setLight(ScriptEnv &e);
|
||||
virtual int o_setDark(ScriptEnv &e);
|
||||
virtual int o_save(ScriptEnv &e);
|
||||
virtual int o_restore(ScriptEnv &e);
|
||||
virtual int o_restart(ScriptEnv &e);
|
||||
virtual int o_quit(ScriptEnv &e);
|
||||
virtual int o_placeItem(ScriptEnv &e);
|
||||
virtual int o_setItemPic(ScriptEnv &e);
|
||||
virtual int o_resetPic(ScriptEnv &e);
|
||||
virtual int o_takeItem(ScriptEnv &e);
|
||||
virtual int o_dropItem(ScriptEnv &e);
|
||||
virtual int o_setRoomPic(ScriptEnv &e);
|
||||
|
||||
virtual int goDirection(ScriptEnv &e, Direction D);
|
||||
int o_goNorth(ScriptEnv &e) { return goDirection(e, IDI_DIR_NORTH); }
|
||||
int o_goSouth(ScriptEnv &e) { return goDirection(e, IDI_DIR_SOUTH); }
|
||||
int o_goEast(ScriptEnv &e) { return goDirection(e, IDI_DIR_EAST); }
|
||||
int o_goWest(ScriptEnv &e) { return goDirection(e, IDI_DIR_WEST); }
|
||||
int o_goUp(ScriptEnv &e) { return goDirection(e, IDI_DIR_UP); }
|
||||
int o_goDown(ScriptEnv &e) { return goDirection(e, IDI_DIR_DOWN); }
|
||||
|
||||
// Graphics
|
||||
void drawPic(byte pic, Common::Point pos = Common::Point()) const;
|
||||
|
||||
// Sound
|
||||
bool playTones(const Tones &tones, bool isMusic, bool allowSkip = false) const;
|
||||
|
||||
// Game state functions
|
||||
const Region &getRegion(uint i) const;
|
||||
Region &getRegion(uint i);
|
||||
const Room &getRoom(uint i) const;
|
||||
Room &getRoom(uint i);
|
||||
const Region &getCurRegion() const;
|
||||
Region &getCurRegion();
|
||||
const Room &getCurRoom() const;
|
||||
Room &getCurRoom();
|
||||
const Item &getItem(uint i) const;
|
||||
Item &getItem(uint i);
|
||||
byte getVar(uint i) const;
|
||||
void setVar(uint i, byte value);
|
||||
virtual void takeItem(byte noun);
|
||||
virtual void dropItem(byte noun);
|
||||
bool matchCommand(ScriptEnv &env) const;
|
||||
void doActions(ScriptEnv &env);
|
||||
bool doOneCommand(const Commands &commands, byte verb, byte noun);
|
||||
void doAllCommands(const Commands &commands, byte verb, byte noun);
|
||||
virtual ScriptEnv *createScriptEnv(const Command &cmd, byte room, byte verb, byte noun);
|
||||
|
||||
// Debug functions
|
||||
static Common::String toAscii(const Common::String &str);
|
||||
Common::String itemStr(uint i) const;
|
||||
Common::String roomStr(uint i) const;
|
||||
Common::String itemRoomStr(uint i) const;
|
||||
Common::String verbStr(uint i) const;
|
||||
Common::String nounStr(uint i) const;
|
||||
Common::String msgStr(uint i) const;
|
||||
Common::String dirStr(Direction dir) const;
|
||||
bool op_debug(const char *fmt, ...) const;
|
||||
Common::DumpFile *_dumpFile;
|
||||
|
||||
Display *_display;
|
||||
GraphicsMan *_graphics;
|
||||
bool _textMode;
|
||||
|
||||
// Opcodes
|
||||
Common::Array<Opcode> _condOpcodes, _actOpcodes;
|
||||
// Message strings in data file
|
||||
Common::Array<Common::DataBlockPtr> _messages;
|
||||
// Picture data
|
||||
PictureMap _pictures;
|
||||
// Dropped item screen offsets
|
||||
Common::Array<Common::Point> _itemOffsets;
|
||||
// <room, verb, noun, script> lists
|
||||
Commands _roomCommands;
|
||||
Commands _globalCommands;
|
||||
// Data related to the current room
|
||||
RoomData _roomData;
|
||||
|
||||
WordMap _verbs;
|
||||
WordMap _nouns;
|
||||
Common::StringArray _priVerbs;
|
||||
Common::StringArray _priNouns;
|
||||
|
||||
struct {
|
||||
Common::String enterCommand;
|
||||
Common::String verbError;
|
||||
Common::String nounError;
|
||||
Common::String playAgain;
|
||||
Common::String pressReturn;
|
||||
Common::String lineFeeds;
|
||||
} _strings;
|
||||
|
||||
uint32 _verbErrorPos, _nounErrorPos;
|
||||
|
||||
struct {
|
||||
uint cantGoThere;
|
||||
uint dontUnderstand;
|
||||
uint itemDoesntMove;
|
||||
uint itemNotHere;
|
||||
uint thanksForPlaying;
|
||||
} _messageIds;
|
||||
|
||||
// Game state
|
||||
State _state;
|
||||
|
||||
uint _linesPrinted;
|
||||
bool _isRestarting, _isRestoring, _isQuitting;
|
||||
bool _canSaveNow, _canRestoreNow;
|
||||
bool _abortScript;
|
||||
Common::RandomSource *_random;
|
||||
|
||||
const AdlGameDescription *_gameDescription;
|
||||
|
||||
mutable Common::File *_inputScript;
|
||||
mutable uint _scriptDelay;
|
||||
mutable bool _scriptPaused;
|
||||
|
||||
private:
|
||||
virtual void runIntro() { }
|
||||
virtual void init() = 0;
|
||||
virtual void initGameState() = 0;
|
||||
virtual void drawItems() = 0;
|
||||
virtual void drawItem(Item &item, const Common::Point &pos) = 0;
|
||||
virtual void loadRoom(byte roomNr) = 0;
|
||||
virtual void showRoom() = 0;
|
||||
virtual void switchRegion(byte region) { }
|
||||
void runScript(const char *filename) const;
|
||||
void stopScript() const;
|
||||
void setScriptDelay(uint scriptDelay) const { _scriptDelay = scriptDelay; }
|
||||
Common::String getScriptLine() const;
|
||||
// Engine
|
||||
Common::Error run() override;
|
||||
bool hasFeature(EngineFeature f) const override;
|
||||
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
|
||||
// Text input
|
||||
byte convertKey(uint16 ascii) const;
|
||||
|
||||
byte _saveVerb, _saveNoun, _restoreVerb, _restoreNoun;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
644
engines/adl/adl_v2.cpp
Normal file
644
engines/adl/adl_v2.cpp
Normal file
@@ -0,0 +1,644 @@
|
||||
/* 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 "common/random.h"
|
||||
#include "common/error.h"
|
||||
|
||||
#include "adl/adl_v2.h"
|
||||
#include "adl/display.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/detection.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
AdlEngine_v2::~AdlEngine_v2() {
|
||||
delete _disk;
|
||||
}
|
||||
|
||||
AdlEngine_v2::AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine(syst, gd),
|
||||
_maxLines(4),
|
||||
_disk(nullptr),
|
||||
_currentVolume(0),
|
||||
_itemRemoved(false),
|
||||
_roomOnScreen(0),
|
||||
_picOnScreen(0),
|
||||
_itemsOnScreen(0) { }
|
||||
|
||||
void AdlEngine_v2::mapExeStrings(const Common::StringArray &strings) {
|
||||
if (strings.size() < 11)
|
||||
error("Not enough strings found in executable");
|
||||
|
||||
// Parser messages
|
||||
_strings.verbError = strings[2];
|
||||
_strings.nounError = strings[3];
|
||||
_strings.enterCommand = strings[4];
|
||||
|
||||
// Line feeds
|
||||
_strings.lineFeeds = strings[0];
|
||||
|
||||
// Opcode strings
|
||||
_strings_v2.saveInsert = strings[5];
|
||||
_strings_v2.saveReplace = strings[6];
|
||||
_strings_v2.restoreInsert = strings[7];
|
||||
_strings_v2.restoreReplace = strings[8];
|
||||
_strings.playAgain = strings[9];
|
||||
_strings.pressReturn = strings[10];
|
||||
}
|
||||
|
||||
void AdlEngine_v2::insertDisk(byte volume) {
|
||||
delete _disk;
|
||||
_disk = new Common::DiskImage();
|
||||
|
||||
if (!_disk->open(getDiskImageName(volume)))
|
||||
error("Failed to open disk volume %d", volume);
|
||||
|
||||
_currentVolume = volume;
|
||||
}
|
||||
|
||||
void AdlEngine_v2::setupOpcodeTables() {
|
||||
AdlEngine::setupOpcodeTables();
|
||||
|
||||
_condOpcodes[0x01] = opcode(&AdlEngine_v2::o_isFirstTime);
|
||||
_condOpcodes[0x02] = opcode(&AdlEngine_v2::o_isRandomGT);
|
||||
_condOpcodes[0x04] = opcode(&AdlEngine_v2::o_isNounNotInRoom);
|
||||
_condOpcodes[0x07] = opcode(&AdlEngine_v2::o_isCarryingSomething);
|
||||
|
||||
_actOpcodes.resize(0x21);
|
||||
_actOpcodes[0x0c] = opcode(&AdlEngine_v2::o_moveAllItems);
|
||||
_actOpcodes[0x1e] = opcode(&AdlEngine_v2::o_tellTime);
|
||||
_actOpcodes[0x1f] = opcode(&AdlEngine_v2::o_setRoomFromVar);
|
||||
_actOpcodes[0x20] = opcode(&AdlEngine_v2::o_initDisk);
|
||||
}
|
||||
|
||||
void AdlEngine_v2::initState() {
|
||||
AdlEngine::initState();
|
||||
|
||||
_linesPrinted = 0;
|
||||
_picOnScreen = 0;
|
||||
_roomOnScreen = 0;
|
||||
_itemRemoved = false;
|
||||
_itemsOnScreen = 0;
|
||||
}
|
||||
|
||||
byte AdlEngine_v2::roomArg(byte room) const {
|
||||
if (room == IDI_CUR_ROOM)
|
||||
return _state.room;
|
||||
return room;
|
||||
}
|
||||
|
||||
void AdlEngine_v2::advanceClock() {
|
||||
Time &time = _state.time;
|
||||
|
||||
time.minutes += 5;
|
||||
|
||||
if (time.minutes == 60) {
|
||||
time.minutes = 0;
|
||||
|
||||
++time.hours;
|
||||
|
||||
if (time.hours == 13)
|
||||
time.hours = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v2::checkTextOverflow(char c) {
|
||||
if (c != _display->asciiToNative('\r'))
|
||||
return;
|
||||
|
||||
++_linesPrinted;
|
||||
|
||||
if (_linesPrinted >= _maxLines)
|
||||
handleTextOverflow();
|
||||
}
|
||||
|
||||
void AdlEngine_v2::handleTextOverflow() {
|
||||
_linesPrinted = 0;
|
||||
_display->renderText();
|
||||
|
||||
if (_inputScript) {
|
||||
// Set pause flag to activate regular behaviour of delay and inputKey
|
||||
_scriptPaused = true;
|
||||
|
||||
if (_scriptDelay > 0)
|
||||
delay(_scriptDelay);
|
||||
else
|
||||
inputKey();
|
||||
|
||||
_scriptPaused = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bell();
|
||||
|
||||
while (true) {
|
||||
char key = inputKey(false);
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
if (key == _display->asciiToNative('\r'))
|
||||
break;
|
||||
|
||||
bell(3);
|
||||
}
|
||||
}
|
||||
|
||||
Common::String AdlEngine_v2::loadMessage(uint idx) const {
|
||||
if (_messages[idx]) {
|
||||
Common::StreamPtr strStream(_messages[idx]->createReadStream());
|
||||
return readString(*strStream, 0xff);
|
||||
}
|
||||
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
void AdlEngine_v2::printString(const Common::String &str) {
|
||||
Common::String s(str);
|
||||
const uint textWidth = _display->getTextWidth();
|
||||
uint endPos = textWidth - 1;
|
||||
uint startPos = 0;
|
||||
uint pos = 0;
|
||||
|
||||
const char spaceChar = _display->asciiToNative(' ');
|
||||
const char returnChar = _display->asciiToNative('\r');
|
||||
|
||||
while (pos < s.size()) {
|
||||
s.setChar(_display->asciiToNative(s[pos]), pos);
|
||||
|
||||
if (pos == endPos) {
|
||||
while (s[pos] != spaceChar && s[pos] != returnChar) {
|
||||
if (pos-- == startPos)
|
||||
error("Word wrapping failed");
|
||||
}
|
||||
|
||||
s.setChar(returnChar, pos);
|
||||
endPos = pos + textWidth;
|
||||
startPos = pos + 1;
|
||||
}
|
||||
|
||||
++pos;
|
||||
}
|
||||
|
||||
for (pos = 0; pos < s.size(); ++pos) {
|
||||
checkTextOverflow(s[pos]);
|
||||
_display->printChar(s[pos]);
|
||||
}
|
||||
|
||||
checkTextOverflow(returnChar);
|
||||
_display->printChar(returnChar);
|
||||
_display->renderText();
|
||||
}
|
||||
|
||||
void AdlEngine_v2::drawItem(Item &item, const Common::Point &pos) {
|
||||
item.isOnScreen = true;
|
||||
|
||||
if (item.picture == 0 || (uint)(item.picture - 1) >= _itemPics.size()) {
|
||||
warning("Item picture %d not found", item.picture);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::StreamPtr stream(_itemPics[item.picture - 1]->createReadStream());
|
||||
stream->readByte(); // Skip clear opcode
|
||||
_graphics->drawPic(*stream, pos);
|
||||
}
|
||||
|
||||
void AdlEngine_v2::loadRoom(byte roomNr) {
|
||||
if (Common::find(_brokenRooms.begin(), _brokenRooms.end(), roomNr) != _brokenRooms.end()) {
|
||||
warning("Attempt to load non-existent room %d", roomNr);
|
||||
_roomData.description.clear();
|
||||
_roomData.pictures.clear();
|
||||
_roomData.commands.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Room &room = getRoom(roomNr);
|
||||
Common::StreamPtr stream(room.data->createReadStream());
|
||||
|
||||
uint16 descOffset = stream->readUint16LE();
|
||||
uint16 commandOffset = stream->readUint16LE();
|
||||
|
||||
_roomData.pictures.clear();
|
||||
// There's no picture count. The original engine always checks at most
|
||||
// five pictures. We use the description offset to bound our search.
|
||||
uint16 picCount = (descOffset - 4) / 5;
|
||||
|
||||
for (uint i = 0; i < picCount; ++i) {
|
||||
byte nr = stream->readByte();
|
||||
_roomData.pictures[nr] = readDataBlockPtr(*stream);
|
||||
}
|
||||
|
||||
_roomData.description = readStringAt(*stream, descOffset, 0xff);
|
||||
|
||||
_roomData.commands.clear();
|
||||
if (commandOffset != 0) {
|
||||
stream->seek(commandOffset);
|
||||
readCommands(*stream, _roomData.commands);
|
||||
}
|
||||
|
||||
applyRoomWorkarounds(roomNr);
|
||||
}
|
||||
|
||||
void AdlEngine_v2::showRoom() {
|
||||
bool redrawPic = false;
|
||||
|
||||
_state.curPicture = getCurRoom().curPicture;
|
||||
|
||||
if (_state.room != _roomOnScreen) {
|
||||
loadRoom(_state.room);
|
||||
_graphics->clearScreen();
|
||||
|
||||
if (!_state.isDark)
|
||||
redrawPic = true;
|
||||
} else {
|
||||
if (_state.curPicture != _picOnScreen || _itemRemoved)
|
||||
redrawPic = true;
|
||||
}
|
||||
|
||||
if (redrawPic) {
|
||||
_roomOnScreen = _state.room;
|
||||
_picOnScreen = _state.curPicture;
|
||||
|
||||
drawPic(_state.curPicture);
|
||||
_itemRemoved = false;
|
||||
_itemsOnScreen = 0;
|
||||
|
||||
for (auto &item : _state.items)
|
||||
item.isOnScreen = false;
|
||||
}
|
||||
|
||||
if (!_state.isDark)
|
||||
drawItems();
|
||||
|
||||
_display->renderGraphics();
|
||||
printString(_roomData.description);
|
||||
}
|
||||
|
||||
// TODO: Merge this into AdlEngine?
|
||||
void AdlEngine_v2::takeItem(byte noun) {
|
||||
for (auto &item : _state.items) {
|
||||
if (item.noun == noun && item.room == _state.room && item.region == _state.region) {
|
||||
if (item.state == IDI_ITEM_DOESNT_MOVE) {
|
||||
printMessage(_messageIds.itemDoesntMove);
|
||||
return;
|
||||
}
|
||||
|
||||
bool itemIsHere = false;
|
||||
|
||||
if (item.state == IDI_ITEM_DROPPED) {
|
||||
itemIsHere = true;
|
||||
} else {
|
||||
for (const auto &pic : item.roomPictures) {
|
||||
if (pic == getCurRoom().curPicture || pic == IDI_ANY) {
|
||||
itemIsHere = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (itemIsHere) {
|
||||
if (!isInventoryFull()) {
|
||||
item.room = IDI_ANY;
|
||||
_itemRemoved = true;
|
||||
item.state = IDI_ITEM_DROPPED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printMessage(_messageIds.itemNotHere);
|
||||
}
|
||||
|
||||
void AdlEngine_v2::drawItems() {
|
||||
for (auto &item : _state.items) {
|
||||
// Skip items not in this room
|
||||
if (item.region == _state.region && item.room == _state.room && !item.isOnScreen) {
|
||||
if (item.state == IDI_ITEM_DROPPED) {
|
||||
// Draw dropped item if in normal view
|
||||
if (getCurRoom().picture == getCurRoom().curPicture && _itemsOnScreen < _itemOffsets.size())
|
||||
drawItem(item, _itemOffsets[_itemsOnScreen++]);
|
||||
} else {
|
||||
// Draw fixed item if current view is in the pic list
|
||||
for (const auto &pic : item.roomPictures) {
|
||||
if (pic == _state.curPicture || pic == IDI_ANY) {
|
||||
drawItem(item, item.position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
|
||||
byte track = f.readByte();
|
||||
byte sector = f.readByte();
|
||||
byte offset = f.readByte();
|
||||
byte size = f.readByte();
|
||||
|
||||
if (f.eos() || f.err())
|
||||
error("Error reading DataBlockPtr");
|
||||
|
||||
if (track == 0 && sector == 0 && offset == 0 && size == 0)
|
||||
return Common::DataBlockPtr();
|
||||
|
||||
adjustDataBlockPtr(track, sector, offset, size);
|
||||
|
||||
return _disk->getDataBlock(track, sector, offset, size);
|
||||
}
|
||||
|
||||
void AdlEngine_v2::loadItems(Common::ReadStream &stream) {
|
||||
byte id;
|
||||
while ((id = stream.readByte()) != 0xff && !stream.eos() && !stream.err()) {
|
||||
Item item;
|
||||
|
||||
item.id = id;
|
||||
item.noun = stream.readByte();
|
||||
item.room = stream.readByte();
|
||||
item.picture = stream.readByte();
|
||||
item.region = stream.readByte();
|
||||
item.position.x = stream.readByte();
|
||||
item.position.y = stream.readByte();
|
||||
item.state = stream.readByte();
|
||||
item.description = stream.readByte();
|
||||
|
||||
stream.readByte(); // Struct size
|
||||
|
||||
byte picListSize = stream.readByte();
|
||||
|
||||
// Flag to keep track of what has been drawn on the screen
|
||||
stream.readByte();
|
||||
|
||||
for (uint i = 0; i < picListSize; ++i)
|
||||
item.roomPictures.push_back(stream.readByte());
|
||||
|
||||
_state.items.push_back(item);
|
||||
}
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Error loading items");
|
||||
}
|
||||
|
||||
void AdlEngine_v2::loadRooms(Common::ReadStream &stream, byte count) {
|
||||
for (uint i = 0; i < count; ++i) {
|
||||
Room room;
|
||||
|
||||
stream.readByte(); // number
|
||||
for (uint j = 0; j < 6; ++j)
|
||||
room.connections[j] = stream.readByte();
|
||||
room.data = readDataBlockPtr(stream);
|
||||
room.picture = stream.readByte();
|
||||
room.curPicture = stream.readByte();
|
||||
room.isFirstTime = stream.readByte();
|
||||
|
||||
_state.rooms.push_back(room);
|
||||
}
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Error loading rooms");
|
||||
}
|
||||
|
||||
void AdlEngine_v2::loadMessages(Common::ReadStream &stream, byte count) {
|
||||
for (uint i = 0; i < count; ++i)
|
||||
_messages.push_back(readDataBlockPtr(stream));
|
||||
}
|
||||
|
||||
void AdlEngine_v2::loadPictures(Common::ReadStream &stream) {
|
||||
byte picNr;
|
||||
while ((picNr = stream.readByte()) != 0xff) {
|
||||
if (stream.eos() || stream.err())
|
||||
error("Error reading global pic list");
|
||||
|
||||
_pictures[picNr] = readDataBlockPtr(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v2::loadItemPictures(Common::ReadStream &stream, byte count) {
|
||||
for (uint i = 0; i < count; ++i) {
|
||||
stream.readByte(); // number
|
||||
_itemPics.push_back(readDataBlockPtr(stream));
|
||||
}
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_isFirstTime(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\t&& IS_FIRST_TIME()");
|
||||
|
||||
bool oldFlag = getCurRoom().isFirstTime;
|
||||
|
||||
getCurRoom().isFirstTime = false;
|
||||
|
||||
if (!oldFlag)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_isRandomGT(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\t&& RAND() > %d", e.arg(1));
|
||||
|
||||
byte rnd = _random->getRandomNumber(255);
|
||||
|
||||
if (rnd > e.arg(1))
|
||||
return 1;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_isNounNotInRoom(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
|
||||
|
||||
for (const auto &item : _state.items)
|
||||
if (item.noun == e.getNoun() && (item.room == roomArg(e.arg(1))))
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_isCarryingSomething(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\t&& IS_CARRYING_SOMETHING()");
|
||||
|
||||
for (const auto &item : _state.items)
|
||||
if (item.room == IDI_ANY)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_moveItem(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
|
||||
|
||||
byte room = roomArg(e.arg(2));
|
||||
|
||||
Item &item = getItem(e.arg(1));
|
||||
|
||||
if (item.room == _roomOnScreen)
|
||||
_picOnScreen = 0;
|
||||
|
||||
// Set items that move from inventory to a room to state "dropped"
|
||||
if (item.room == IDI_ANY && room != IDI_VOID_ROOM)
|
||||
item.state = IDI_ITEM_DROPPED;
|
||||
|
||||
item.room = room;
|
||||
return 2;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_setCurPic(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1));
|
||||
|
||||
getCurRoom().curPicture = _state.curPicture = e.arg(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_setPic(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1));
|
||||
|
||||
getCurRoom().picture = getCurRoom().curPicture = _state.curPicture = e.arg(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_moveAllItems(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
|
||||
|
||||
byte room1 = roomArg(e.arg(1));
|
||||
|
||||
if (room1 == _state.room)
|
||||
_picOnScreen = 0;
|
||||
|
||||
byte room2 = roomArg(e.arg(2));
|
||||
|
||||
for (auto &item : _state.items)
|
||||
if (item.room == room1) {
|
||||
item.room = room2;
|
||||
if (room1 == IDI_ANY)
|
||||
item.state = IDI_ITEM_DROPPED;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_save(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tSAVE_GAME()");
|
||||
|
||||
int slot = askForSlot(_strings_v2.saveInsert);
|
||||
|
||||
if (slot < 0)
|
||||
return -1;
|
||||
|
||||
saveGameState(slot, "");
|
||||
|
||||
_display->printString(_strings_v2.saveReplace);
|
||||
inputString();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_restore(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tRESTORE_GAME()");
|
||||
|
||||
int slot = askForSlot(_strings_v2.restoreInsert);
|
||||
|
||||
if (slot < 0)
|
||||
return -1;
|
||||
|
||||
loadGameState(slot);
|
||||
_isRestoring = false;
|
||||
|
||||
_display->printString(_strings_v2.restoreReplace);
|
||||
inputString();
|
||||
_picOnScreen = 0;
|
||||
_roomOnScreen = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_placeItem(ScriptEnv &e) {
|
||||
OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4));
|
||||
|
||||
Item &item = getItem(e.arg(1));
|
||||
|
||||
item.room = roomArg(e.arg(2));
|
||||
item.position.x = e.arg(3);
|
||||
item.position.y = e.arg(4);
|
||||
item.state = IDI_ITEM_NOT_MOVED;
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_tellTime(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tTELL_TIME()");
|
||||
|
||||
Common::String time = _strings_v2.time;
|
||||
|
||||
if (time.size() <= 16)
|
||||
error("Invalid time string");
|
||||
|
||||
const char zeroChar = _display->asciiToNative('0');
|
||||
|
||||
time.setChar(zeroChar + _state.time.hours / 10, 12);
|
||||
time.setChar(zeroChar + _state.time.hours % 10, 13);
|
||||
time.setChar(zeroChar + _state.time.minutes / 10, 15);
|
||||
time.setChar(zeroChar + _state.time.minutes % 10, 16);
|
||||
|
||||
printString(time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_setRoomFromVar(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\tROOM = VAR[%d]", e.arg(1));
|
||||
getCurRoom().curPicture = getCurRoom().picture;
|
||||
_state.room = getVar(e.arg(1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::o_initDisk(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tINIT_DISK()");
|
||||
|
||||
_display->printAsciiString("NOT REQUIRED\r");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AdlEngine_v2::canSaveGameStateCurrently(Common::U32String *msg) {
|
||||
if (!_canSaveNow)
|
||||
return false;
|
||||
|
||||
// Back up first visit flag as it may be changed by this test
|
||||
const bool isFirstTime = getCurRoom().isFirstTime;
|
||||
const bool retval = AdlEngine::canSaveGameStateCurrently(msg);
|
||||
|
||||
getCurRoom().isFirstTime = isFirstTime;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int AdlEngine_v2::askForSlot(const Common::String &question) {
|
||||
while (1) {
|
||||
_display->printString(question);
|
||||
|
||||
Common::String input = inputString();
|
||||
|
||||
if (shouldQuit())
|
||||
return -1;
|
||||
|
||||
if (input.size() > 0 && input[0] >= _display->asciiToNative('A') && input[0] <= _display->asciiToNative('O'))
|
||||
return input[0] - _display->asciiToNative('A');
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
100
engines/adl/adl_v2.h
Normal file
100
engines/adl/adl_v2.h
Normal 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 ADL_ADL_V2_H
|
||||
#define ADL_ADL_V2_H
|
||||
|
||||
#include "adl/adl.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class AdlEngine_v2 : public AdlEngine {
|
||||
public:
|
||||
~AdlEngine_v2() override;
|
||||
|
||||
protected:
|
||||
AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
// AdlEngine
|
||||
void setupOpcodeTables() override;
|
||||
void initState() override;
|
||||
byte roomArg(byte room) const override;
|
||||
void advanceClock() override;
|
||||
void printString(const Common::String &str) override;
|
||||
Common::String loadMessage(uint idx) const override;
|
||||
void drawItems() override;
|
||||
void drawItem(Item &item, const Common::Point &pos) override;
|
||||
void loadRoom(byte roomNr) override;
|
||||
void showRoom() override;
|
||||
void takeItem(byte noun) override;
|
||||
|
||||
// Engine
|
||||
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
|
||||
void mapExeStrings(const Common::StringArray &strings);
|
||||
void insertDisk(byte volume);
|
||||
virtual Common::DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
|
||||
virtual void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const { }
|
||||
void loadItems(Common::ReadStream &stream);
|
||||
void loadRooms(Common::ReadStream &stream, byte count);
|
||||
void loadMessages(Common::ReadStream &stream, byte count);
|
||||
void loadPictures(Common::ReadStream &stream);
|
||||
void loadItemPictures(Common::ReadStream &stream, byte count);
|
||||
virtual bool isInventoryFull() { return false; }
|
||||
int askForSlot(const Common::String &question);
|
||||
|
||||
void checkTextOverflow(char c);
|
||||
void handleTextOverflow();
|
||||
|
||||
virtual int o_isFirstTime(ScriptEnv &e);
|
||||
virtual int o_isRandomGT(ScriptEnv &e);
|
||||
virtual int o_isNounNotInRoom(ScriptEnv &e);
|
||||
virtual int o_isCarryingSomething(ScriptEnv &e);
|
||||
|
||||
int o_moveItem(ScriptEnv &e) override;
|
||||
int o_setCurPic(ScriptEnv &e) override;
|
||||
int o_setPic(ScriptEnv &e) override;
|
||||
virtual int o_moveAllItems(ScriptEnv &e);
|
||||
int o_save(ScriptEnv &e) override;
|
||||
int o_restore(ScriptEnv &e) override ;
|
||||
int o_placeItem(ScriptEnv &e) override;
|
||||
virtual int o_tellTime(ScriptEnv &e);
|
||||
virtual int o_setRoomFromVar(ScriptEnv &e);
|
||||
virtual int o_initDisk(ScriptEnv &e);
|
||||
|
||||
struct {
|
||||
Common::String time;
|
||||
Common::String saveInsert, saveReplace;
|
||||
Common::String restoreInsert, restoreReplace;
|
||||
} _strings_v2;
|
||||
|
||||
uint _maxLines;
|
||||
Common::DiskImage *_disk;
|
||||
byte _currentVolume;
|
||||
Common::Array<Common::DataBlockPtr> _itemPics;
|
||||
bool _itemRemoved;
|
||||
byte _roomOnScreen, _picOnScreen, _itemsOnScreen;
|
||||
Common::Array<byte> _brokenRooms;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
75
engines/adl/adl_v3.cpp
Normal file
75
engines/adl/adl_v3.cpp
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "adl/adl_v3.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
AdlEngine_v3::AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v2(syst, gd) {
|
||||
}
|
||||
|
||||
Common::String AdlEngine_v3::getItemDescription(const Item &item) const {
|
||||
return _itemDesc[item.description - 1];
|
||||
}
|
||||
|
||||
void AdlEngine_v3::loadItemDescriptions(Common::SeekableReadStream &stream, byte count) {
|
||||
int32 startPos = stream.pos();
|
||||
uint16 baseAddr = stream.readUint16LE();
|
||||
|
||||
// This code assumes that the first pointer points to a string that
|
||||
// directly follows the pointer table
|
||||
assert(baseAddr != 0);
|
||||
baseAddr -= count * 2;
|
||||
|
||||
for (uint i = 0; i < count; ++i) {
|
||||
stream.seek(startPos + i * 2);
|
||||
uint16 offset = stream.readUint16LE();
|
||||
|
||||
if (offset > 0) {
|
||||
stream.seek(startPos + offset - baseAddr);
|
||||
_itemDesc.push_back(readString(stream, 0xff));
|
||||
} else
|
||||
_itemDesc.push_back(Common::String());
|
||||
}
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Error loading item descriptions");
|
||||
}
|
||||
|
||||
int AdlEngine_v3::o_isNounNotInRoom(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
|
||||
|
||||
bool isAnItem = false;
|
||||
|
||||
for (const auto &item : _state.items) {
|
||||
if (item.noun == e.getNoun()) {
|
||||
isAnItem = true;
|
||||
|
||||
if (item.room == roomArg(e.arg(1)))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (isAnItem ? 1 : -1);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
48
engines/adl/adl_v3.h
Normal file
48
engines/adl/adl_v3.h
Normal 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 ADL_ADL_V3_H
|
||||
#define ADL_ADL_V3_H
|
||||
|
||||
#include "adl/adl_v2.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class AdlEngine_v3 : public AdlEngine_v2 {
|
||||
public:
|
||||
~AdlEngine_v3() override { }
|
||||
|
||||
protected:
|
||||
AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
// AdlEngine
|
||||
Common::String getItemDescription(const Item &item) const override;
|
||||
|
||||
void loadItemDescriptions(Common::SeekableReadStream &stream, byte count);
|
||||
|
||||
int o_isNounNotInRoom(ScriptEnv &e) override;
|
||||
|
||||
Common::Array<Common::String> _itemDesc;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
600
engines/adl/adl_v4.cpp
Normal file
600
engines/adl/adl_v4.cpp
Normal file
@@ -0,0 +1,600 @@
|
||||
/* 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 "common/error.h"
|
||||
|
||||
#include "adl/adl_v4.h"
|
||||
#include "adl/display.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
AdlEngine_v4::AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v3(syst, gd),
|
||||
_itemPicIndex(nullptr) {
|
||||
|
||||
}
|
||||
|
||||
AdlEngine_v4::~AdlEngine_v4() {
|
||||
delete _itemPicIndex;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::gameLoop() {
|
||||
uint verb = 0, noun = 0;
|
||||
_isRestarting = false;
|
||||
|
||||
if (_isRestoring) {
|
||||
// Game restored from launcher. As this version of ADL long jumps to
|
||||
// the game loop after restoring, no special action is required.
|
||||
_isRestoring = false;
|
||||
}
|
||||
|
||||
showRoom();
|
||||
|
||||
if (_isRestarting || shouldQuit())
|
||||
return;
|
||||
|
||||
_canSaveNow = _canRestoreNow = true;
|
||||
getInput(verb, noun);
|
||||
_canSaveNow = _canRestoreNow = false;
|
||||
|
||||
if (_isRestoring) {
|
||||
// Game restored from GMM. Move cursor to next line and jump to
|
||||
// start of game loop.
|
||||
_display->printAsciiString("\r");
|
||||
_isRestoring = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isRestarting || shouldQuit())
|
||||
return;
|
||||
|
||||
_linesPrinted = 0;
|
||||
|
||||
checkInput(verb, noun);
|
||||
|
||||
if (_isRestarting || shouldQuit())
|
||||
return;
|
||||
|
||||
doAllCommands(_globalCommands, verb, noun);
|
||||
|
||||
if (_isRestarting || shouldQuit())
|
||||
return;
|
||||
|
||||
_state.moves++;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::loadState(Common::ReadStream &stream) {
|
||||
_state.room = stream.readByte();
|
||||
_state.region = stream.readByte();
|
||||
_state.prevRegion = stream.readByte();
|
||||
|
||||
uint32 size = stream.readUint32BE();
|
||||
if (size != _state.regions.size())
|
||||
error("Region count mismatch (expected %i; found %i)", _state.regions.size(), size);
|
||||
|
||||
for (auto ®ion : _state.regions) {
|
||||
size = stream.readUint32BE();
|
||||
if (size != region.rooms.size())
|
||||
error("Room count mismatch (expected %i; found %i)", region.rooms.size(), size);
|
||||
|
||||
for (auto &room : region.rooms) {
|
||||
room.picture = stream.readByte();
|
||||
room.isFirstTime = stream.readByte();
|
||||
}
|
||||
|
||||
size = stream.readUint32BE();
|
||||
if (size != region.vars.size())
|
||||
error("Variable count mismatch (expected %i; found %i)", region.vars.size(), size);
|
||||
|
||||
for (uint i = 0; i < region.vars.size(); ++i)
|
||||
region.vars[i] = stream.readByte();
|
||||
}
|
||||
|
||||
size = stream.readUint32BE();
|
||||
if (size != _state.items.size())
|
||||
error("Item count mismatch (expected %i; found %i)", _state.items.size(), size);
|
||||
|
||||
for (auto &item : _state.items) {
|
||||
item.room = stream.readByte();
|
||||
item.region = stream.readByte();
|
||||
item.state = stream.readByte();
|
||||
}
|
||||
|
||||
size = stream.readUint32BE();
|
||||
const uint expectedSize = _state.vars.size() - getRegion(1).vars.size();
|
||||
if (size != expectedSize)
|
||||
error("Variable count mismatch (expected %i; found %i)", expectedSize, size);
|
||||
|
||||
for (uint i = getRegion(1).vars.size(); i < _state.vars.size(); ++i)
|
||||
_state.vars[i] = stream.readByte();
|
||||
|
||||
if (stream.err() || stream.eos())
|
||||
return;
|
||||
|
||||
loadRegion(_state.region);
|
||||
restoreRoomState(_state.room);
|
||||
_roomOnScreen = _picOnScreen = 0;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::saveState(Common::WriteStream &stream) {
|
||||
getCurRoom().isFirstTime = false;
|
||||
backupVars();
|
||||
backupRoomState(_state.room);
|
||||
|
||||
stream.writeByte(_state.room);
|
||||
stream.writeByte(_state.region);
|
||||
stream.writeByte(_state.prevRegion);
|
||||
|
||||
stream.writeUint32BE(_state.regions.size());
|
||||
for (const auto ®ion : _state.regions) {
|
||||
stream.writeUint32BE(region.rooms.size());
|
||||
for (const auto &room : region.rooms) {
|
||||
stream.writeByte(room.picture);
|
||||
stream.writeByte(room.isFirstTime);
|
||||
}
|
||||
|
||||
stream.writeUint32BE(region.vars.size());
|
||||
for (uint i = 0; i < region.vars.size(); ++i)
|
||||
stream.writeByte(region.vars[i]);
|
||||
}
|
||||
|
||||
stream.writeUint32BE(_state.items.size());
|
||||
for (const auto &item : _state.items) {
|
||||
stream.writeByte(item.room);
|
||||
stream.writeByte(item.region);
|
||||
stream.writeByte(item.state);
|
||||
}
|
||||
|
||||
stream.writeUint32BE(_state.vars.size() - getRegion(1).vars.size());
|
||||
for (uint i = getRegion(1).vars.size(); i < _state.vars.size(); ++i)
|
||||
stream.writeByte(_state.vars[i]);
|
||||
}
|
||||
|
||||
Common::String AdlEngine_v4::loadMessage(uint idx) const {
|
||||
if (_messages[idx]) {
|
||||
Common::StreamPtr strStream(_messages[idx]->createReadStream());
|
||||
return readString(*strStream, 0xff, "AVISDURGAN");
|
||||
}
|
||||
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
Common::String AdlEngine_v4::getItemDescription(const Item &item) const {
|
||||
return _itemDesc[item.id - 1];
|
||||
}
|
||||
|
||||
void AdlEngine_v4::loadRegionLocations(Common::ReadStream &stream, uint regions) {
|
||||
for (uint r = 0; r < regions; ++r) {
|
||||
RegionLocation loc;
|
||||
loc.track = stream.readByte();
|
||||
loc.sector = stream.readByte();
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Failed to read region locations");
|
||||
|
||||
_regionLocations.push_back(loc);
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v4::loadRegionInitDataOffsets(Common::ReadStream &stream, uint regions) {
|
||||
for (uint r = 0; r < regions; ++r) {
|
||||
RegionInitDataOffset initOfs;
|
||||
initOfs.track = stream.readByte();
|
||||
initOfs.sector = stream.readByte();
|
||||
initOfs.offset = stream.readByte();
|
||||
initOfs.volume = stream.readByte();
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Failed to read region init data offsets");
|
||||
|
||||
_regionInitDataOffsets.push_back(initOfs);
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v4::initRoomState(RoomState &roomState) const {
|
||||
roomState.picture = 1;
|
||||
roomState.isFirstTime = 1;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::initRegions(const byte *roomsPerRegion, uint regions) {
|
||||
_state.regions.resize(regions);
|
||||
|
||||
for (uint r = 0; r < regions; ++r) {
|
||||
Region ®n = _state.regions[r];
|
||||
// Each region has 24 variables
|
||||
regn.vars.resize(24);
|
||||
|
||||
regn.rooms.resize(roomsPerRegion[r]);
|
||||
for (uint rm = 0; rm < roomsPerRegion[r]; ++rm)
|
||||
initRoomState(regn.rooms[rm]);
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v4::fixupDiskOffset(byte &track, byte §or) const {
|
||||
if (_state.region == 0)
|
||||
return;
|
||||
|
||||
sector += _regionLocations[_state.region - 1].sector;
|
||||
if (sector >= 16) {
|
||||
sector -= 16;
|
||||
++track;
|
||||
}
|
||||
|
||||
track += _regionLocations[_state.region - 1].track;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const {
|
||||
fixupDiskOffset(track, sector);
|
||||
}
|
||||
|
||||
AdlEngine_v4::RegionChunkType AdlEngine_v4::getRegionChunkType(const uint16 addr) const {
|
||||
switch (addr) {
|
||||
case 0x9000:
|
||||
return kRegionChunkMessages;
|
||||
case 0x4a80:
|
||||
return kRegionChunkGlobalPics;
|
||||
case 0x4000:
|
||||
return kRegionChunkVerbs;
|
||||
case 0x1800:
|
||||
return kRegionChunkNouns;
|
||||
case 0x0e00:
|
||||
return kRegionChunkRooms;
|
||||
case 0x7b00:
|
||||
return kRegionChunkRoomCmds;
|
||||
case 0x9500:
|
||||
return kRegionChunkGlobalCmds;
|
||||
default:
|
||||
return kRegionChunkUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v4::loadRegion(byte region) {
|
||||
if (_currentVolume != _regionInitDataOffsets[region - 1].volume) {
|
||||
insertDisk(_regionInitDataOffsets[region - 1].volume);
|
||||
|
||||
// FIXME: This shouldn't be needed, but currently is, due to
|
||||
// implementation choices made earlier on for DataBlockPtr and DiskImage.
|
||||
_state.region = 0; // To avoid region offset being applied
|
||||
_itemPics.clear();
|
||||
_itemPicIndex->seek(0);
|
||||
loadItemPictures(*_itemPicIndex, _itemPicIndex->size() / 5);
|
||||
}
|
||||
|
||||
_state.region = region;
|
||||
|
||||
byte track = _regionInitDataOffsets[region - 1].track;
|
||||
byte sector = _regionInitDataOffsets[region - 1].sector;
|
||||
uint offset = _regionInitDataOffsets[region - 1].offset;
|
||||
|
||||
fixupDiskOffset(track, sector);
|
||||
|
||||
for (uint block = 0; block < 7; ++block) {
|
||||
Common::StreamPtr stream(_disk->createReadStream(track, sector, offset, 1));
|
||||
|
||||
uint16 addr = stream->readUint16LE();
|
||||
uint16 size = stream->readUint16LE();
|
||||
|
||||
stream.reset(_disk->createReadStream(track, sector, offset, size / 256 + 1));
|
||||
stream->skip(4);
|
||||
|
||||
switch (getRegionChunkType(addr)) {
|
||||
case kRegionChunkMessages: {
|
||||
// Messages
|
||||
_messages.clear();
|
||||
uint count = size / 4;
|
||||
loadMessages(*stream, count);
|
||||
break;
|
||||
}
|
||||
case kRegionChunkGlobalPics: {
|
||||
// Global pics
|
||||
_pictures.clear();
|
||||
loadPictures(*stream);
|
||||
break;
|
||||
}
|
||||
case kRegionChunkVerbs:
|
||||
// Verbs
|
||||
loadWords(*stream, _verbs, _priVerbs);
|
||||
break;
|
||||
case kRegionChunkNouns:
|
||||
// Nouns
|
||||
loadWords(*stream, _nouns, _priNouns);
|
||||
break;
|
||||
case kRegionChunkRooms: {
|
||||
// Rooms
|
||||
uint count = size / 14 - 1;
|
||||
stream->skip(14); // Skip invalid room 0
|
||||
|
||||
_state.rooms.clear();
|
||||
loadRooms(*stream, count);
|
||||
break;
|
||||
}
|
||||
case kRegionChunkRoomCmds:
|
||||
// Room commands
|
||||
readCommands(*stream, _roomCommands);
|
||||
break;
|
||||
case kRegionChunkGlobalCmds:
|
||||
// Global commands
|
||||
readCommands(*stream, _globalCommands);
|
||||
break;
|
||||
default:
|
||||
error("Unknown data block found (addr %04x; size %04x)", addr, size);
|
||||
}
|
||||
|
||||
offset += 4 + size;
|
||||
while (offset >= 256) {
|
||||
offset -= 256;
|
||||
++sector;
|
||||
if (sector >= 16) {
|
||||
sector = 0;
|
||||
++track;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRegionWorkarounds();
|
||||
restoreVars();
|
||||
}
|
||||
|
||||
void AdlEngine_v4::loadItemPicIndex(Common::ReadStream &stream, uint items) {
|
||||
_itemPicIndex = stream.readStream(items * 5);
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Error reading item index");
|
||||
}
|
||||
|
||||
void AdlEngine_v4::backupRoomState(byte room) {
|
||||
RoomState &backup = getCurRegion().rooms[room - 1];
|
||||
|
||||
backup.isFirstTime = getRoom(room).isFirstTime;
|
||||
backup.picture = getRoom(room).picture;
|
||||
}
|
||||
|
||||
byte AdlEngine_v4::restoreRoomState(byte room) {
|
||||
const RoomState &backup = getCurRegion().rooms[room - 1];
|
||||
|
||||
if (backup.isFirstTime != 1) {
|
||||
getRoom(room).curPicture = getRoom(room).picture = backup.picture;
|
||||
getRoom(room).isFirstTime = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// WORKAROUND: Fix for bug #15379: "ADL: HIRES5: Game unsolvable after
|
||||
// loading savegame. Save state not properly restored?".
|
||||
// There's a problem in the original engine when a picture is set for a
|
||||
// room that hasn't been visited yet. If the user then saves before
|
||||
// visiting that room, the picture change will be ignored when play is
|
||||
// resumed from that savegame.
|
||||
if (backup.picture != 1) {
|
||||
warning("Detected picture change for unvisited room %d in region %d", room, _state.region);
|
||||
getRoom(room).curPicture = getRoom(room).picture = backup.picture;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::backupVars() {
|
||||
Region ®ion = getCurRegion();
|
||||
|
||||
for (uint i = 0; i < region.vars.size(); ++i)
|
||||
region.vars[i] = getVar(i);
|
||||
}
|
||||
|
||||
void AdlEngine_v4::restoreVars() {
|
||||
const Region ®ion = getCurRegion();
|
||||
|
||||
for (uint i = 0; i < region.vars.size(); ++i)
|
||||
setVar(i, region.vars[i]);
|
||||
}
|
||||
|
||||
void AdlEngine_v4::switchRegion(byte region) {
|
||||
backupVars();
|
||||
backupRoomState(_state.room);
|
||||
_state.prevRegion = _state.region;
|
||||
_state.region = region;
|
||||
loadRegion(region);
|
||||
_state.room = 1;
|
||||
_picOnScreen = _roomOnScreen = 0;
|
||||
}
|
||||
|
||||
void AdlEngine_v4::switchRoom(byte roomNr) {
|
||||
getCurRoom().curPicture = getCurRoom().picture;
|
||||
getCurRoom().isFirstTime = false;
|
||||
backupRoomState(_state.room);
|
||||
_state.room = roomNr;
|
||||
restoreRoomState(_state.room);
|
||||
}
|
||||
|
||||
void AdlEngine_v4::setupOpcodeTables() {
|
||||
AdlEngine_v3::setupOpcodeTables();
|
||||
|
||||
_condOpcodes[0x08] = opcode(&AdlEngine_v4::o_isVarGT);
|
||||
_condOpcodes[0x0a].reset();
|
||||
|
||||
_actOpcodes[0x0a] = opcode(&AdlEngine_v4::o_setRegionToPrev);
|
||||
_actOpcodes[0x0b].reset();
|
||||
_actOpcodes[0x0e] = opcode(&AdlEngine_v4::o_setRegion);
|
||||
_actOpcodes[0x12] = opcode(&AdlEngine_v4::o_setRegionRoom);
|
||||
_actOpcodes[0x13].reset();
|
||||
_actOpcodes[0x1e].reset();
|
||||
_actOpcodes[0x1f].reset();
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_isItemInRoom(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
|
||||
|
||||
const Item &item = getItem(e.arg(1));
|
||||
|
||||
if (e.arg(2) != IDI_ANY && item.region != _state.region)
|
||||
return -1;
|
||||
|
||||
if (item.room == roomArg(e.arg(2)))
|
||||
return 2;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_isVarGT(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
|
||||
|
||||
if (getVar(e.arg(1)) > e.arg(2))
|
||||
return 2;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_moveItem(ScriptEnv &e) {
|
||||
AdlEngine_v3::o_moveItem(e);
|
||||
getItem(e.arg(1)).region = _state.region;
|
||||
return 2;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_setRegionToPrev(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tREGION = PREV_REGION");
|
||||
|
||||
switchRegion(_state.prevRegion);
|
||||
// Long jump
|
||||
_isRestarting = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_moveAllItems(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
|
||||
|
||||
byte room1 = roomArg(e.arg(1));
|
||||
|
||||
if (room1 == _state.room)
|
||||
_picOnScreen = 0;
|
||||
|
||||
byte room2 = roomArg(e.arg(2));
|
||||
|
||||
for (auto &item : _state.items) {
|
||||
if (room1 != item.room)
|
||||
continue;
|
||||
|
||||
if (room1 != IDI_ANY) {
|
||||
if (_state.region != item.region)
|
||||
continue;
|
||||
if (room2 == IDI_ANY) {
|
||||
if (isInventoryFull())
|
||||
break;
|
||||
if (item.state == IDI_ITEM_DOESNT_MOVE)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
item.room = room2;
|
||||
item.region = _state.region;
|
||||
|
||||
if (room1 == IDI_ANY)
|
||||
item.state = IDI_ITEM_DROPPED;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_setRegion(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\tREGION = %d", e.arg(1));
|
||||
|
||||
switchRegion(e.arg(1));
|
||||
// Long jump
|
||||
_isRestarting = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_save(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tSAVE_GAME()");
|
||||
|
||||
_display->printString(_strings_v2.saveReplace);
|
||||
const char key = inputKey();
|
||||
|
||||
if (shouldQuit())
|
||||
return -1;
|
||||
|
||||
if (key != _display->asciiToNative('Y'))
|
||||
return 0;
|
||||
|
||||
const int slot = askForSlot(_strings_v2.saveInsert);
|
||||
|
||||
if (slot < 0)
|
||||
return -1;
|
||||
|
||||
saveGameState(slot, "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_restore(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tRESTORE_GAME()");
|
||||
|
||||
const int slot = askForSlot(_strings_v2.restoreInsert);
|
||||
|
||||
if (slot < 0)
|
||||
return -1;
|
||||
|
||||
loadGameState(slot);
|
||||
_isRestoring = false;
|
||||
|
||||
_picOnScreen = 0;
|
||||
_roomOnScreen = 0;
|
||||
|
||||
// Long jump
|
||||
_isRestarting = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_restart(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tRESTART_GAME()");
|
||||
|
||||
while (true) {
|
||||
_display->printString(_strings.playAgain);
|
||||
const Common::String input(inputString());
|
||||
|
||||
if (shouldQuit())
|
||||
return -1;
|
||||
|
||||
if (input.firstChar() == _display->asciiToNative('N')) {
|
||||
return o_quit(e);
|
||||
} else if (input.firstChar() == _display->asciiToNative('Y')) {
|
||||
// The original game loads a special save game from volume 3
|
||||
initState();
|
||||
// Long jump
|
||||
_isRestarting = true;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_setRegionRoom(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\tSET_REGION_ROOM(%d, %d)", e.arg(1), e.arg(2));
|
||||
|
||||
switchRegion(e.arg(1));
|
||||
_state.room = e.arg(2);
|
||||
// Long jump
|
||||
_isRestarting = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v4::o_setRoomPic(ScriptEnv &e) {
|
||||
AdlEngine_v3::o_setRoomPic(e);
|
||||
backupRoomState(e.arg(1));
|
||||
return 2;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
106
engines/adl/adl_v4.h
Normal file
106
engines/adl/adl_v4.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/* 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 ADL_ADL_V4_H
|
||||
#define ADL_ADL_V4_H
|
||||
|
||||
#include "adl/adl_v3.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
// Base track/sector for a region
|
||||
struct RegionLocation {
|
||||
byte track;
|
||||
byte sector;
|
||||
};
|
||||
|
||||
// Location of the 7 initial data blocks, relative to RegionLocation
|
||||
struct RegionInitDataOffset {
|
||||
byte track;
|
||||
byte sector;
|
||||
byte offset;
|
||||
byte volume;
|
||||
};
|
||||
|
||||
class AdlEngine_v4 : public AdlEngine_v3 {
|
||||
public:
|
||||
~AdlEngine_v4() override;
|
||||
|
||||
protected:
|
||||
AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
// AdlEngine
|
||||
void setupOpcodeTables() override;
|
||||
void gameLoop() override;
|
||||
void loadState(Common::ReadStream &stream) override;
|
||||
void saveState(Common::WriteStream &stream) override;
|
||||
Common::String loadMessage(uint idx) const override;
|
||||
Common::String getItemDescription(const Item &item) const override;
|
||||
void switchRegion(byte region) override;
|
||||
void switchRoom(byte roomNr) override;
|
||||
|
||||
// AdlEngine_v2
|
||||
void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const override;
|
||||
|
||||
enum RegionChunkType {
|
||||
kRegionChunkUnknown,
|
||||
kRegionChunkMessages,
|
||||
kRegionChunkGlobalPics,
|
||||
kRegionChunkVerbs,
|
||||
kRegionChunkNouns,
|
||||
kRegionChunkRooms,
|
||||
kRegionChunkRoomCmds,
|
||||
kRegionChunkGlobalCmds
|
||||
};
|
||||
|
||||
void loadRegionLocations(Common::ReadStream &stream, uint regions);
|
||||
void loadRegionInitDataOffsets(Common::ReadStream &stream, uint regions);
|
||||
void initRegions(const byte *roomsPerRegion, uint regions);
|
||||
void fixupDiskOffset(byte &track, byte §or) const;
|
||||
virtual RegionChunkType getRegionChunkType(const uint16 addr) const;
|
||||
void loadRegion(byte region);
|
||||
void loadItemPicIndex(Common::ReadStream &stream, uint items);
|
||||
void backupRoomState(byte room);
|
||||
virtual void initRoomState(RoomState &roomState) const;
|
||||
virtual byte restoreRoomState(byte room);
|
||||
void backupVars();
|
||||
void restoreVars();
|
||||
|
||||
int o_isItemInRoom(ScriptEnv &e) override;
|
||||
virtual int o_isVarGT(ScriptEnv &e);
|
||||
int o_moveItem(ScriptEnv &e) override;
|
||||
virtual int o_setRegionToPrev(ScriptEnv &e);
|
||||
int o_moveAllItems(ScriptEnv &e) override;
|
||||
virtual int o_setRegion(ScriptEnv &e);
|
||||
int o_save(ScriptEnv &e) override;
|
||||
int o_restore(ScriptEnv &e) override;
|
||||
int o_restart(ScriptEnv &e) override;
|
||||
virtual int o_setRegionRoom(ScriptEnv &e);
|
||||
int o_setRoomPic(ScriptEnv &e) override;
|
||||
|
||||
Common::Array<RegionLocation> _regionLocations;
|
||||
Common::Array<RegionInitDataOffset> _regionInitDataOffsets;
|
||||
Common::SeekableReadStream *_itemPicIndex;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
162
engines/adl/adl_v5.cpp
Normal file
162
engines/adl/adl_v5.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
/* 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 "common/random.h"
|
||||
#include "common/error.h"
|
||||
|
||||
#include "adl/adl_v5.h"
|
||||
#include "adl/display.h"
|
||||
#include "adl/graphics.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
AdlEngine_v5::AdlEngine_v5(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v4(syst, gd) {
|
||||
}
|
||||
|
||||
void AdlEngine_v5::initRoomState(RoomState &roomState) const {
|
||||
roomState.picture = 0xff;
|
||||
roomState.isFirstTime = 0xff;
|
||||
}
|
||||
|
||||
byte AdlEngine_v5::restoreRoomState(byte room) {
|
||||
const RoomState &backup = getCurRegion().rooms[room - 1];
|
||||
|
||||
if (backup.isFirstTime != 0xff) {
|
||||
getRoom(room).curPicture = getRoom(room).picture = backup.picture;
|
||||
|
||||
// CHECKME: Why doesn't this just copy the flag unconditionally?
|
||||
if (backup.isFirstTime != 1) {
|
||||
getRoom(room).isFirstTime = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return backup.isFirstTime;
|
||||
}
|
||||
|
||||
AdlEngine_v5::RegionChunkType AdlEngine_v5::getRegionChunkType(const uint16 addr) const {
|
||||
switch (addr) {
|
||||
case 0x7b00:
|
||||
return kRegionChunkGlobalCmds;
|
||||
case 0x9500:
|
||||
return kRegionChunkRoomCmds;
|
||||
default:
|
||||
return AdlEngine_v4::getRegionChunkType(addr);
|
||||
}
|
||||
}
|
||||
|
||||
void AdlEngine_v5::setupOpcodeTables() {
|
||||
AdlEngine_v4::setupOpcodeTables();
|
||||
|
||||
_condOpcodes[0x0a] = opcode(&AdlEngine_v5::o_abortScript);
|
||||
|
||||
_actOpcodes[0x0a] = opcode(&AdlEngine_v5::o_dummy);
|
||||
_actOpcodes[0x0b] = opcode(&AdlEngine_v5::o_setTextMode);
|
||||
_actOpcodes[0x0e] = opcode(&AdlEngine_v5::o_dummy);
|
||||
_actOpcodes[0x13] = opcode(&AdlEngine_v5::o_dummy);
|
||||
}
|
||||
|
||||
int AdlEngine_v5::o_isNounNotInRoom(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
|
||||
|
||||
setVar(24, 0);
|
||||
|
||||
for (const auto &item : _state.items)
|
||||
if (item.noun == e.getNoun()) {
|
||||
setVar(24, 1);
|
||||
|
||||
if (item.room == roomArg(e.arg(1)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AdlEngine_v5::o_abortScript(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\t&& ABORT_SCRIPT()");
|
||||
|
||||
_abortScript = true;
|
||||
setVar(2, 0);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v5::o_dummy(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tDUMMY()");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AdlEngine_v5::o_setTextMode(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1));
|
||||
|
||||
switch (e.arg(1)) {
|
||||
case 1:
|
||||
if (_linesPrinted != 0) {
|
||||
_display->printChar(_display->asciiToNative(' '));
|
||||
handleTextOverflow();
|
||||
_display->moveCursorTo(Common::Point(0, 23));
|
||||
_maxLines = 4;
|
||||
}
|
||||
return 1;
|
||||
case 2:
|
||||
_textMode = true;
|
||||
_display->setMode(Display::kModeText);
|
||||
_display->home();
|
||||
_maxLines = 24;
|
||||
_linesPrinted = 0;
|
||||
return 1;
|
||||
case 3:
|
||||
// We re-use the restarting flag here, to simulate a long jump
|
||||
_isRestarting = true;
|
||||
return -1;
|
||||
default:
|
||||
error("Invalid text mode %d", e.arg(1));
|
||||
}
|
||||
}
|
||||
|
||||
int AdlEngine_v5::o_setRegionRoom(ScriptEnv &e) {
|
||||
OP_DEBUG_2("\tSET_REGION_ROOM(%d, %d)", e.arg(1), e.arg(2));
|
||||
|
||||
getCurRoom().curPicture = getCurRoom().picture;
|
||||
getCurRoom().isFirstTime = false;
|
||||
switchRegion(e.arg(1));
|
||||
_state.room = e.arg(2);
|
||||
restoreRoomState(_state.room);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AdlEngine_v5::o_setRoomPic(ScriptEnv &e) {
|
||||
const byte isFirstTime = restoreRoomState(e.arg(1));
|
||||
|
||||
// CHECKME: More peculiar isFirstTime handling (see also restoreRoomState).
|
||||
// Is this here to prevent changing the backed up flag from 1 to 0? Since
|
||||
// that could only happen if the room isFirstTime is 0 while the backed up flag
|
||||
// is 1, is this scenario even possible?
|
||||
if (isFirstTime != 0xff)
|
||||
getRoom(e.arg(1)).isFirstTime = isFirstTime;
|
||||
|
||||
AdlEngine_v4::o_setRoomPic(e);
|
||||
return 2;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
54
engines/adl/adl_v5.h
Normal file
54
engines/adl/adl_v5.h
Normal 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 ADL_ADL_V5_H
|
||||
#define ADL_ADL_V5_H
|
||||
|
||||
#include "adl/adl_v4.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class AdlEngine_v5 : public AdlEngine_v4 {
|
||||
public:
|
||||
~AdlEngine_v5() override { }
|
||||
|
||||
protected:
|
||||
AdlEngine_v5(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
// AdlEngine
|
||||
void setupOpcodeTables() override;
|
||||
|
||||
// AdlEngine_v4
|
||||
RegionChunkType getRegionChunkType(const uint16 addr) const override;
|
||||
void initRoomState(RoomState &roomState) const override;
|
||||
byte restoreRoomState(byte room) override;
|
||||
|
||||
int o_isNounNotInRoom(ScriptEnv &e) override;
|
||||
virtual int o_abortScript(ScriptEnv &e);
|
||||
virtual int o_dummy(ScriptEnv &e);
|
||||
virtual int o_setTextMode(ScriptEnv &e);
|
||||
int o_setRegionRoom(ScriptEnv &e) override;
|
||||
int o_setRoomPic(ScriptEnv &e) override;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
3
engines/adl/configure.engine
Normal file
3
engines/adl/configure.engine
Normal 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 adl "ADL" yes "" "" "16bit highres"
|
||||
460
engines/adl/console.cpp
Normal file
460
engines/adl/console.cpp
Normal file
@@ -0,0 +1,460 @@
|
||||
/* 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 "common/debug-channels.h"
|
||||
|
||||
#include "adl/console.h"
|
||||
#include "adl/display.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/adl.h"
|
||||
#include "adl/disk.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
Console::Console(AdlEngine *engine) : GUI::Debugger() {
|
||||
_engine = engine;
|
||||
|
||||
registerCmd("nouns", WRAP_METHOD(Console, Cmd_Nouns));
|
||||
registerCmd("verbs", WRAP_METHOD(Console, Cmd_Verbs));
|
||||
registerCmd("dump_scripts", WRAP_METHOD(Console, Cmd_DumpScripts));
|
||||
registerCmd("valid_cmds", WRAP_METHOD(Console, Cmd_ValidCommands));
|
||||
registerCmd("region", WRAP_METHOD(Console, Cmd_Region));
|
||||
registerCmd("room", WRAP_METHOD(Console, Cmd_Room));
|
||||
registerCmd("items", WRAP_METHOD(Console, Cmd_Items));
|
||||
registerCmd("give_item", WRAP_METHOD(Console, Cmd_GiveItem));
|
||||
registerCmd("vars", WRAP_METHOD(Console, Cmd_Vars));
|
||||
registerCmd("var", WRAP_METHOD(Console, Cmd_Var));
|
||||
registerCmd("convert_disk", WRAP_METHOD(Console, Cmd_ConvertDisk));
|
||||
registerCmd("run_script", WRAP_METHOD(Console, Cmd_RunScript));
|
||||
registerCmd("stop_script", WRAP_METHOD(Console, Cmd_StopScript));
|
||||
registerCmd("set_script_delay", WRAP_METHOD(Console, Cmd_SetScriptDelay));
|
||||
}
|
||||
|
||||
Common::String Console::toAscii(const Common::String &str) {
|
||||
Common::String ascii(str);
|
||||
|
||||
for (uint i = 0; i < ascii.size(); ++i)
|
||||
ascii.setChar(ascii[i] & 0x7f, i);
|
||||
|
||||
return ascii;
|
||||
}
|
||||
|
||||
Common::String Console::toNative(const Common::String &str) {
|
||||
Common::String native(str);
|
||||
|
||||
if (native.size() > IDI_WORD_SIZE)
|
||||
native.erase(IDI_WORD_SIZE);
|
||||
native.toUppercase();
|
||||
|
||||
for (uint i = 0; i < native.size(); ++i)
|
||||
native.setChar(_engine->_display->asciiToNative(native[i]), i);
|
||||
|
||||
while (native.size() < IDI_WORD_SIZE)
|
||||
native += _engine->_display->asciiToNative(' ');
|
||||
|
||||
return native;
|
||||
}
|
||||
|
||||
bool Console::Cmd_Verbs(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
debugPrintf("Verbs in alphabetical order:\n");
|
||||
printWordMap(_engine->_verbs);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_Nouns(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
debugPrintf("Nouns in alphabetical order:\n");
|
||||
printWordMap(_engine->_nouns);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_ValidCommands(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_any;
|
||||
|
||||
for (const auto &verb : _engine->_verbs) {
|
||||
for (const auto &noun : _engine->_nouns) {
|
||||
if (_engine->isInputValid(verb._value, noun._value, is_any) && !is_any)
|
||||
debugPrintf("%s %s\n", toAscii(verb._key).c_str(), toAscii(noun._key).c_str());
|
||||
}
|
||||
if (_engine->isInputValid(verb._value, IDI_ANY, is_any))
|
||||
debugPrintf("%s *\n", toAscii(verb._key).c_str());
|
||||
}
|
||||
if (_engine->isInputValid(IDI_ANY, IDI_ANY, is_any))
|
||||
debugPrintf("* *\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Console::dumpScripts(const Common::Path &prefix) {
|
||||
for (byte roomNr = 1; roomNr <= _engine->_state.rooms.size(); ++roomNr) {
|
||||
_engine->loadRoom(roomNr);
|
||||
if (_engine->_roomData.commands.size() != 0) {
|
||||
_engine->_dumpFile->open(prefix.append(Common::String::format("%03d.ADL", roomNr)));
|
||||
_engine->doAllCommands(_engine->_roomData.commands, IDI_ANY, IDI_ANY);
|
||||
_engine->_dumpFile->close();
|
||||
}
|
||||
}
|
||||
_engine->loadRoom(_engine->_state.room);
|
||||
|
||||
_engine->_dumpFile->open(prefix.append("GLOBAL.ADL"));
|
||||
_engine->doAllCommands(_engine->_globalCommands, IDI_ANY, IDI_ANY);
|
||||
_engine->_dumpFile->close();
|
||||
|
||||
_engine->_dumpFile->open(prefix.append("RESPONSE.ADL"));
|
||||
_engine->doAllCommands(_engine->_roomCommands, IDI_ANY, IDI_ANY);
|
||||
_engine->_dumpFile->close();
|
||||
}
|
||||
|
||||
bool Console::Cmd_DumpScripts(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool oldFlag = DebugMan.isDebugChannelEnabled(kDebugChannelScript);
|
||||
|
||||
DebugMan.enableDebugChannel("Script");
|
||||
|
||||
_engine->_dumpFile = new Common::DumpFile();
|
||||
|
||||
if (_engine->_state.regions.empty()) {
|
||||
dumpScripts();
|
||||
} else {
|
||||
const byte oldRegion = _engine->_state.region;
|
||||
const byte oldPrevRegion = _engine->_state.prevRegion;
|
||||
const byte oldRoom = _engine->_state.room;
|
||||
|
||||
for (byte regionNr = 1; regionNr <= _engine->_state.regions.size(); ++regionNr) {
|
||||
_engine->switchRegion(regionNr);
|
||||
dumpScripts(Common::Path(Common::String::format("%03d-", regionNr)));
|
||||
}
|
||||
|
||||
_engine->switchRegion(oldRegion);
|
||||
_engine->_state.prevRegion = oldPrevRegion;
|
||||
_engine->_state.room = oldRoom;
|
||||
_engine->loadRoom(oldRoom);
|
||||
}
|
||||
|
||||
delete _engine->_dumpFile;
|
||||
_engine->_dumpFile = nullptr;
|
||||
|
||||
if (!oldFlag)
|
||||
DebugMan.disableDebugChannel("Script");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Console::prepareGame() {
|
||||
_engine->_graphics->clearScreen();
|
||||
_engine->loadRoom(_engine->_state.room);
|
||||
_engine->showRoom();
|
||||
_engine->_display->renderText();
|
||||
_engine->_display->renderGraphics();
|
||||
}
|
||||
|
||||
bool Console::Cmd_Region(int argc, const char **argv) {
|
||||
if (argc > 2) {
|
||||
debugPrintf("Usage: %s [<new_region>]\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argc == 2) {
|
||||
if (!_engine->_canRestoreNow) {
|
||||
debugPrintf("Cannot change regions right now\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
uint regionCount = _engine->_state.regions.size();
|
||||
uint region = strtoul(argv[1], nullptr, 0);
|
||||
if (region < 1 || region > regionCount) {
|
||||
debugPrintf("Region %u out of valid range [1, %u]\n", region, regionCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
_engine->switchRegion(region);
|
||||
prepareGame();
|
||||
}
|
||||
|
||||
debugPrintf("Current region: %u\n", _engine->_state.region);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_Room(int argc, const char **argv) {
|
||||
if (argc > 2) {
|
||||
debugPrintf("Usage: %s [<new_room>]\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argc == 2) {
|
||||
if (!_engine->_canRestoreNow) {
|
||||
debugPrintf("Cannot change rooms right now\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
uint roomCount = _engine->_state.rooms.size();
|
||||
uint room = strtoul(argv[1], nullptr, 0);
|
||||
if (room < 1 || room > roomCount) {
|
||||
debugPrintf("Room %u out of valid range [1, %u]\n", room, roomCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
_engine->switchRoom(room);
|
||||
prepareGame();
|
||||
}
|
||||
|
||||
debugPrintf("Current room: %u\n", _engine->_state.room);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_Items(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &item : _engine->_state.items)
|
||||
printItem(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_GiveItem(int argc, const char **argv) {
|
||||
if (argc != 2) {
|
||||
debugPrintf("Usage: %s <ID | name>\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
char *end;
|
||||
uint id = strtoul(argv[1], &end, 0);
|
||||
|
||||
if (*end != 0) {
|
||||
Common::Array<Item *> matches;
|
||||
|
||||
Common::String name = toNative(argv[1]);
|
||||
|
||||
if (!_engine->_nouns.contains(name)) {
|
||||
debugPrintf("Item '%s' not found\n", argv[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
byte noun = _engine->_nouns[name];
|
||||
|
||||
for (auto &item : _engine->_state.items) {
|
||||
if (item.noun == noun)
|
||||
matches.push_back(&item);
|
||||
}
|
||||
|
||||
if (matches.size() == 0) {
|
||||
debugPrintf("Item '%s' not found\n", argv[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matches.size() > 1) {
|
||||
debugPrintf("Multiple matches found, please use item ID:\n");
|
||||
for (uint i = 0; i < matches.size(); ++i)
|
||||
printItem(*matches[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
matches[0]->room = IDI_ANY;
|
||||
debugPrintf("OK\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &item : _engine->_state.items)
|
||||
if (item.id == id) {
|
||||
item.room = IDI_ANY;
|
||||
debugPrintf("OK\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
debugPrintf("Item %i not found\n", id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_Vars(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::StringArray vars;
|
||||
for (uint i = 0; i < _engine->_state.vars.size(); ++i)
|
||||
vars.push_back(Common::String::format("%3d: %3d", i, _engine->_state.vars[i]));
|
||||
|
||||
debugPrintf("Variables:\n");
|
||||
debugPrintColumns(vars);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_Var(int argc, const char **argv) {
|
||||
if (argc < 2 || argc > 3) {
|
||||
debugPrintf("Usage: %s <index> [<value>]\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint varCount = _engine->_state.vars.size();
|
||||
uint var = strtoul(argv[1], nullptr, 0);
|
||||
|
||||
if (var >= varCount) {
|
||||
debugPrintf("Variable %u out of valid range [0, %u]\n", var, varCount - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argc == 3) {
|
||||
uint value = strtoul(argv[2], nullptr, 0);
|
||||
_engine->_state.vars[var] = value;
|
||||
}
|
||||
|
||||
debugPrintf("%3d: %3d\n", var, _engine->_state.vars[var]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Console::printItem(const Item &item) {
|
||||
Common::String name, desc, state;
|
||||
|
||||
if (item.noun > 0)
|
||||
name = _engine->_priNouns[item.noun - 1];
|
||||
|
||||
desc = toAscii(_engine->getItemDescription(item));
|
||||
if (desc.lastChar() == '\r')
|
||||
desc.deleteLastChar();
|
||||
|
||||
switch (item.state) {
|
||||
case IDI_ITEM_NOT_MOVED:
|
||||
state = "PLACED";
|
||||
break;
|
||||
case IDI_ITEM_DROPPED:
|
||||
state = "DROPPED";
|
||||
break;
|
||||
case IDI_ITEM_DOESNT_MOVE:
|
||||
state = "FIXED";
|
||||
break;
|
||||
default:
|
||||
state = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
debugPrintf("%3d %s %-30s %-10s %-8s (%3d, %3d)\n", item.id, name.c_str(), desc.c_str(), _engine->itemRoomStr(item.room).c_str(), state.c_str(), item.position.x, item.position.y);
|
||||
}
|
||||
|
||||
void Console::printWordMap(const WordMap &wordMap) {
|
||||
Common::StringArray words;
|
||||
|
||||
for (const auto &verb : wordMap)
|
||||
words.push_back(Common::String::format("%s: %3d", toAscii(verb._key).c_str(), wordMap[verb._key]));
|
||||
|
||||
Common::sort(words.begin(), words.end());
|
||||
|
||||
debugPrintColumns(words);
|
||||
}
|
||||
|
||||
bool Console::Cmd_ConvertDisk(int argc, const char **argv) {
|
||||
if (argc != 3) {
|
||||
debugPrintf("Usage: %s <source> <dest>\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::DiskImage inDisk;
|
||||
if (!inDisk.open(argv[1])) {
|
||||
debugPrintf("Failed to open '%s' for reading\n", argv[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::DumpFile outDisk;
|
||||
if (!outDisk.open(argv[2])) {
|
||||
debugPrintf("Failed to open '%s' for writing\n", argv[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint sectors = inDisk.getTracks() * inDisk.getSectorsPerTrack();
|
||||
const uint size = sectors * inDisk.getBytesPerSector();
|
||||
|
||||
byte *const buf = new byte[size];
|
||||
|
||||
Common::StreamPtr stream(inDisk.createReadStream(0, 0, 0, sectors - 1));
|
||||
if (stream->read(buf, size) < size) {
|
||||
debugPrintf("Failed to read from stream");
|
||||
delete[] buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (outDisk.write(buf, size) < size)
|
||||
debugPrintf("Failed to write to '%s'", argv[2]);
|
||||
|
||||
delete[] buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_RunScript(int argc, const char **argv) {
|
||||
if (argc != 2) {
|
||||
debugPrintf("Usage: %s <file>\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
_engine->runScript(argv[1]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Console::Cmd_StopScript(int argc, const char **argv) {
|
||||
if (argc != 1) {
|
||||
debugPrintf("Usage: %s\n", argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
_engine->stopScript();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::Cmd_SetScriptDelay(int argc, const char **argv) {
|
||||
if (argc != 2) {
|
||||
debugPrintf("Usage: %s <delay>\n", argv[0]);
|
||||
debugPrintf("A delay of zero indicates wait-for-key\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::String value(argv[1]);
|
||||
_engine->setScriptDelay((uint)value.asUint64());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
72
engines/adl/console.h
Normal file
72
engines/adl/console.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* 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 ADL_CONSOLE_H
|
||||
#define ADL_CONSOLE_H
|
||||
|
||||
#include "gui/debugger.h"
|
||||
|
||||
#include "common/hashmap.h"
|
||||
#include "common/path.h"
|
||||
|
||||
namespace Common {
|
||||
class String;
|
||||
}
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class AdlEngine;
|
||||
struct Item;
|
||||
|
||||
class Console : public GUI::Debugger {
|
||||
public:
|
||||
Console(AdlEngine *engine);
|
||||
|
||||
static Common::String toAscii(const Common::String &str);
|
||||
Common::String toNative(const Common::String &str);
|
||||
|
||||
private:
|
||||
bool Cmd_Nouns(int argc, const char **argv);
|
||||
bool Cmd_Verbs(int argc, const char **argv);
|
||||
bool Cmd_DumpScripts(int argc, const char **argv);
|
||||
bool Cmd_ValidCommands(int argc, const char **argv);
|
||||
bool Cmd_Region(int argc, const char **argv);
|
||||
bool Cmd_Room(int argc, const char **argv);
|
||||
bool Cmd_Items(int argc, const char **argv);
|
||||
bool Cmd_GiveItem(int argc, const char **argv);
|
||||
bool Cmd_Vars(int argc, const char **argv);
|
||||
bool Cmd_Var(int argc, const char **argv);
|
||||
bool Cmd_ConvertDisk(int argc, const char **argv);
|
||||
bool Cmd_RunScript(int argc, const char **argv);
|
||||
bool Cmd_StopScript(int argc, const char **argv);
|
||||
bool Cmd_SetScriptDelay(int argc, const char **argv);
|
||||
|
||||
void printItem(const Item &item);
|
||||
void printWordMap(const Common::HashMap<Common::String, uint> &wordMap);
|
||||
void dumpScripts(const Common::Path &prefix = Common::Path());
|
||||
void prepareGame();
|
||||
|
||||
AdlEngine *_engine;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
3
engines/adl/credits.pl
Normal file
3
engines/adl/credits.pl
Normal file
@@ -0,0 +1,3 @@
|
||||
begin_section("ADL");
|
||||
add_person("Walter van Niftrik", "waltervn", "");
|
||||
end_section();
|
||||
613
engines/adl/detection.cpp
Normal file
613
engines/adl/detection.cpp
Normal file
@@ -0,0 +1,613 @@
|
||||
/* 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 "common/file.h"
|
||||
#include "common/md5.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
#include "adl/detection.h"
|
||||
#include "adl/disk.h"
|
||||
#include "adl/disk_image_helpers.h"
|
||||
#include "adl/adl.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
static const DebugChannelDef debugFlagList[] = {
|
||||
{Adl::kDebugChannelScript, "Script", "Trace script execution"},
|
||||
DEBUG_CHANNEL_END
|
||||
};
|
||||
|
||||
#define DEFAULT_OPTIONS GUIO7(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI, GAMEOPTION_TTS)
|
||||
#define MH_OPTIONS GUIO7(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI, GAMEOPTION_TTS)
|
||||
|
||||
static const PlainGameDescriptor adlGames[] = {
|
||||
{ "hires0", "Hi-Res Adventure #0: Mission Asteroid" },
|
||||
{ "hires1", "Hi-Res Adventure #1: Mystery House" },
|
||||
{ "hires2", "Hi-Res Adventure #2: Wizard and the Princess" },
|
||||
{ "hires3", "Hi-Res Adventure #3: Cranston Manor" },
|
||||
{ "hires4", "Hi-Res Adventure #4: Ulysses and the Golden Fleece" },
|
||||
{ "hires5", "Hi-Res Adventure #5: Time Zone" },
|
||||
{ "hires6", "Hi-Res Adventure #6: The Dark Crystal" },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
static const AdlGameDescription gameFileDescriptions[] = {
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Simi Valley
|
||||
{
|
||||
"hires1", "On-Line Systems [A]",
|
||||
{
|
||||
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
|
||||
{ "AUTO LOAD OBJ", 0, "a2ab7be25842e1fa9f1343b0894a8b6f", 4095 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_SIMI
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - Without MIXEDON
|
||||
{
|
||||
"hires1", "On-Line Systems [B]",
|
||||
{
|
||||
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
|
||||
{ "AUTO LOAD OBJ", 0, "669b5f313ffdfb373ab8dce5961688d3", 12288 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_COARSE
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - French - Without MIXEDON
|
||||
{
|
||||
"hires1", "Malibu Microcomputing [A]",
|
||||
{
|
||||
{ "ADVENTURE", 0, "6e2245979871b44a9fec46b4b2ba590a", 29952 },
|
||||
{ "AUTO LOAD OBJ", 0, "3d417e923e70abe9a82e51155974027d", 12288 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::FR_FRA,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_VF1
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - French - Modified parser
|
||||
{
|
||||
"hires1", "Malibu Microcomputing [B]",
|
||||
{
|
||||
{ "ADVENTURE", 0, "f9a1add3609b4bc24b5dc4a9db1fec67", 29952 },
|
||||
{ "AUTO LOAD OBJ", 0, "2a348058363da4c78a069ee5a2d81a31", 12287 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::FR_FRA,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_VF2
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - With MIXEDON
|
||||
{
|
||||
"hires1", "On-Line Systems [C]",
|
||||
{
|
||||
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
|
||||
{ "AUTO LOAD OBJ", 0, "f6a6ac60c04c6ba6dff68b92cc279ba2", 12291 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_COARSE
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Public Domain
|
||||
{
|
||||
"hires1", "Public Domain",
|
||||
{
|
||||
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
|
||||
{ "AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_PD
|
||||
},
|
||||
{ AD_TABLE_END_MARKER, GAME_TYPE_NONE, GAME_VER_NONE }
|
||||
};
|
||||
|
||||
static const AdlGameDescription gameDiskDescriptions[] = {
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Simi Valley - DOS 3.2 only
|
||||
{
|
||||
"hires1", "On-Line Systems [A]",
|
||||
AD_ENTRY1s("mysthous", "629b9d034cbf8d8e3a612398f53a8dfc", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_SIMI
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - Without MIXEDON
|
||||
{
|
||||
"hires1", "On-Line Systems [B]",
|
||||
AD_ENTRY1s("mysthous", "b22561b5327c7dcdb659e2d649749310", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_COARSE
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - French - Modified parser
|
||||
{
|
||||
"hires1", "Malibu Microcomputing [B]",
|
||||
AD_ENTRY1s("mysthous", "7bd1918ffc28e551e5b3baf610982bd3", 116480),
|
||||
Common::FR_FRA,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_VF2
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - With MIXEDON
|
||||
{
|
||||
"hires1", "On-Line Systems [C]",
|
||||
AD_ENTRY1s("mysthous", "8df0b3b3e609a2e40237e2419c1cb767", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_COARSE
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Roberta Williams Anthology
|
||||
{
|
||||
"hires1", "Public Domain",
|
||||
AD_ENTRY1s("mysthous", "54d20eb1ef0084ac3c2d16c31c5b7eb7", 143360),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_PD
|
||||
},
|
||||
{ // Hi-Res Adventure #1: Mystery House - Apple II - Public Domain
|
||||
{
|
||||
"hires1", "Public Domain",
|
||||
AD_ENTRY1s("mysthous", "bc0f34d153a530b9bb4e554b0a42f8d7", 143360),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
MH_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_VER_HR1_PD
|
||||
},
|
||||
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II - DOS 3.2 only
|
||||
{
|
||||
"hires2", "On-Line Systems [A]",
|
||||
AD_ENTRY1s("wizard", "5201c87db24ba7e3fa447471f4f2ec99", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES2,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II
|
||||
{
|
||||
"hires2", "On-Line Systems [B]",
|
||||
AD_ENTRY1s("wizard", "1de984859212ff11cf61b29cb9b55f8c", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES2,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II - Green Valley Publishing
|
||||
{
|
||||
"hires2", "Green Valley [A]",
|
||||
AD_ENTRY1s("wizard", "73cd373e9a2946c3181b72fdf7a32c77", 143360),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES2,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II - Roberta Williams Anthology
|
||||
{
|
||||
"hires2", "Green Valley [B]",
|
||||
AD_ENTRY1s("wizard", "72b114bf8f94fafe5672daac2a70c765", 143360),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES2,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #0: Mission Asteroid - Apple II - Roberta Williams Anthology
|
||||
{
|
||||
"hires0", "",
|
||||
AD_ENTRY1s("mission", "6bc53f51a3c8ee65c020af55fb8bd875", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES0,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #3: Cranston Manor - Apple II
|
||||
{
|
||||
"hires3", "",
|
||||
AD_ENTRY1s("cranston", "e4d35440791a36e55299c7be1ccd2b04", 116480),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES3,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Original release
|
||||
{
|
||||
"hires4", "On-Line Systems [A]",
|
||||
{
|
||||
{ "ulyssesa", 0, "fac225127a35cf2596d41e91647a532c", 143360 },
|
||||
{ "ulyssesb", 1, "793a01392a094d5e2988deab5510e9fc", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES4,
|
||||
GAME_VER_HR4_V1_0
|
||||
},
|
||||
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Version 1.1
|
||||
{
|
||||
"hires4", "On-Line Systems [B]",
|
||||
{
|
||||
{ "ulyssesa", 0, "420f515e64612d21446ede8078055f0e", 143360 },
|
||||
{ "ulyssesb", 1, "9fa8552255ae651b252844168b8b6617", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES4,
|
||||
GAME_VER_HR4_V1_1
|
||||
},
|
||||
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Green Valley Publishing - Version 0.0
|
||||
{
|
||||
"hires4", "Green Valley [A]",
|
||||
{
|
||||
{ "ulyssesa", 0, "1eaeb2f1a773ce2d1cb9f16b2ef09049", 143360 },
|
||||
{ "ulyssesb", 1, "9fa8552255ae651b252844168b8b6617", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES4,
|
||||
GAME_VER_HR4_LNG
|
||||
},
|
||||
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Green Valley Publishing - Version 1.1
|
||||
{
|
||||
"hires4", "Green Valley [B]",
|
||||
{
|
||||
{ "ulyssesa", 0, "35b6dce492c893327796645f481737ca", 143360 },
|
||||
{ "ulyssesb", 1, "9fa8552255ae651b252844168b8b6617", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES4,
|
||||
GAME_VER_HR4_LNG
|
||||
},
|
||||
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Atari 8-bit - Re-release
|
||||
{
|
||||
"hires4", "",
|
||||
{
|
||||
{ "ulys1b", 0, "bb6aab9a35b41d160b6eefa088165f56", 92160 },
|
||||
{ "ulys1a", 0, "c227eeee34d0bacd62b2d6231c409204", 92160 },
|
||||
// Load 'N' Go Software release XAG-0646 appears to be missing the second disk
|
||||
{ "ulys2c", 0, "8c6a76d1767e4ffa2f0118c9c56c0e90", 92160 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformAtari8Bit,
|
||||
ADGF_UNSTABLE,
|
||||
GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
|
||||
},
|
||||
GAME_TYPE_HIRES4,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #5: Time Zone - Apple II - On-Line Systems - Version 1.1
|
||||
{
|
||||
"hires5", "On-Line Systems",
|
||||
{
|
||||
{"tzone1a", 2, "6c1f4c9f774dbd9697e3b5b1fe2fb858", 143360},
|
||||
{"tzone1b", 3, "4eaf8d790e3f93097cca9ddbe863df50", 143360},
|
||||
{"tzone2c", 4, "e3aa4f56e727339b1ec00978ce9d435b", 143360},
|
||||
{"tzone2d", 5, "77b8219a380410015c986fa192d4c3bf", 143360},
|
||||
{"tzone3e", 6, "f7acc03edd8d8aecb90711cd5f9e5593", 143360},
|
||||
{"tzone3f", 7, "ed74c056976ecea2eab07448c8a72eb8", 143360},
|
||||
{"tzone4g", 8, "de7bda8a641169fc2dedd8a7b0b7e7de", 143360},
|
||||
{"tzone4h", 9, "21cf76d97505ff09fff5d5e4711bc47c", 143360},
|
||||
{"tzone5i", 10, "d665df374e594cd0978b73c3490e5de2", 143360},
|
||||
{"tzone5j", 11, "5095be23d13201d0897b9169c4e473df", 143360},
|
||||
{"tzone6k", 12, "bef044503f21af5f0a4088e99aa778b1", 143360},
|
||||
{"tzone6l", 13, "84801b7c2ab6c09e62a2a0809b94d16a", 143360},
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES5,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #5: Time Zone - Apple II - Roberta Williams Anthology - Version 1.1
|
||||
{
|
||||
"hires5", "Sierra On-Line",
|
||||
{
|
||||
{ "tzone1a", 2, "731844b1d19c2801e3a5bc61d109af54", 143360 },
|
||||
{ "tzone1b", 3, "4eaf8d790e3f93097cca9ddbe863df50", 143360 },
|
||||
{ "tzone2c", 4, "e3aa4f56e727339b1ec00978ce9d435b", 143360 },
|
||||
{ "tzone2d", 5, "77b8219a380410015c986fa192d4c3bf", 143360 },
|
||||
{ "tzone3e", 6, "f7acc03edd8d8aecb90711cd5f9e5593", 143360 },
|
||||
{ "tzone3f", 7, "ed74c056976ecea2eab07448c8a72eb8", 143360 },
|
||||
{ "tzone4g", 8, "de7bda8a641169fc2dedd8a7b0b7e7de", 143360 },
|
||||
{ "tzone4h", 9, "21cf76d97505ff09fff5d5e4711bc47c", 143360 },
|
||||
{ "tzone5i", 10, "d665df374e594cd0978b73c3490e5de2", 143360 },
|
||||
{ "tzone5j", 11, "5095be23d13201d0897b9169c4e473df", 143360 },
|
||||
{ "tzone6k", 12, "bef044503f21af5f0a4088e99aa778b1", 143360 },
|
||||
{ "tzone6l", 13, "84801b7c2ab6c09e62a2a0809b94d16a", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES5,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #6: The Dark Crystal - Apple II - Roberta Williams Anthology / SierraVenture
|
||||
{
|
||||
"hires6", "SierraVenture [A]",
|
||||
{
|
||||
{ "dark1a", 0, "9a5968a8f378c84454d88f4cd4e143a9", 143360 },
|
||||
{ "dark1b", 3, "1271ff9c3e1bdb4942301dd37dd0ef87", 143360 },
|
||||
{ "dark2a", 4, "090e77563add7b4c9ab25f444d727316", 143360 },
|
||||
{ "dark2b", 5, "f2db96af0955324900b800505af4d91f", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES6,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ // Hi-Res Adventure #6: The Dark Crystal - Apple II - SierraVenture
|
||||
{
|
||||
"hires6", "SierraVenture [B]",
|
||||
{
|
||||
{ "dark1a", 0, "d0b8e808b02564b6ce58b5ea5cc61ead", 143360 },
|
||||
{ "dark1b", 3, "1271ff9c3e1bdb4942301dd37dd0ef87", 143360 },
|
||||
{ "dark2a", 4, "090e77563add7b4c9ab25f444d727316", 143360 },
|
||||
{ "dark2b", 5, "f2db96af0955324900b800505af4d91f", 143360 },
|
||||
AD_LISTEND
|
||||
},
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformApple2,
|
||||
ADGF_NO_FLAGS,
|
||||
DEFAULT_OPTIONS
|
||||
},
|
||||
GAME_TYPE_HIRES6,
|
||||
GAME_VER_NONE
|
||||
},
|
||||
{ AD_TABLE_END_MARKER, GAME_TYPE_NONE, GAME_VER_NONE }
|
||||
};
|
||||
|
||||
class AdlMetaEngineDetection : public AdvancedMetaEngineDetection<AdlGameDescription> {
|
||||
public:
|
||||
AdlMetaEngineDetection() : AdvancedMetaEngineDetection(gameFileDescriptions, adlGames) { }
|
||||
|
||||
const char *getEngineName() const override {
|
||||
return "ADL";
|
||||
}
|
||||
|
||||
const char *getName() const override {
|
||||
return "adl";
|
||||
}
|
||||
|
||||
const char *getOriginalCopyright() const override {
|
||||
return "Copyright (C) Sierra On-Line";
|
||||
}
|
||||
|
||||
const DebugChannelDef *getDebugChannels() const override {
|
||||
return debugFlagList;
|
||||
}
|
||||
|
||||
ADDetectedGames detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, uint32 skipADFlags, bool skipIncomplete) override;
|
||||
|
||||
bool addFileProps(const FileMap &allFiles, const Common::Path &fname, FilePropertiesMap &filePropsMap) const;
|
||||
};
|
||||
|
||||
bool AdlMetaEngineDetection::addFileProps(const FileMap &allFiles, const Common::Path &fname, FilePropertiesMap &filePropsMap) const {
|
||||
if (filePropsMap.contains(fname))
|
||||
return true;
|
||||
|
||||
if (!allFiles.contains(fname))
|
||||
return false;
|
||||
|
||||
FileProperties fileProps;
|
||||
fileProps.size = computeMD5(allFiles[fname], fileProps.md5, 16384);
|
||||
|
||||
if (fileProps.size != -1) {
|
||||
debugC(3, kDebugGlobalDetection, "> '%s': '%s'", fname.toString().c_str(), fileProps.md5.c_str());
|
||||
filePropsMap[fname] = fileProps;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Based on AdvancedMetaEngine::detectGame
|
||||
ADDetectedGames AdlMetaEngineDetection::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, uint32 skipADFlags, bool skipIncomplete) {
|
||||
// We run the file-based detector first, if it finds a match we do not search for disk images
|
||||
ADDetectedGames matched = AdvancedMetaEngineDetection::detectGame(parent, allFiles, language, platform, extra, skipADFlags, skipIncomplete);
|
||||
|
||||
if (!matched.empty())
|
||||
return matched;
|
||||
|
||||
debugC(3, kDebugGlobalDetection, "Starting disk image detection in dir '%s'", parent.getPath().toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
FilePropertiesMap filesProps;
|
||||
|
||||
for (uint g = 0; gameDiskDescriptions[g].desc.gameId != nullptr; ++g) {
|
||||
ADDetectedGame game(&gameDiskDescriptions[g].desc);
|
||||
|
||||
// Skip games that don't meet the language/platform/extra criteria
|
||||
if (language != Common::UNK_LANG && game.desc->language != Common::UNK_LANG) {
|
||||
if (game.desc->language != language && !(language == Common::EN_ANY && (game.desc->flags & ADGF_ADDENGLISH)))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (platform != Common::kPlatformUnknown && game.desc->platform != Common::kPlatformUnknown && game.desc->platform != platform)
|
||||
continue;
|
||||
|
||||
if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && game.desc->extra != extra)
|
||||
continue;
|
||||
|
||||
bool allFilesPresent = true;
|
||||
|
||||
for (uint f = 0; game.desc->filesDescriptions[f].fileName; ++f) {
|
||||
const ADGameFileDescription &fDesc = game.desc->filesDescriptions[f];
|
||||
Common::Path fileName;
|
||||
bool foundDiskImage = false;
|
||||
|
||||
for (uint e = 0; e < ARRAYSIZE(diskImageExts); ++e) {
|
||||
if (diskImageExts[e].platform == game.desc->platform) {
|
||||
Common::Path testFileName(fDesc.fileName, Common::Path::kNoSeparator);
|
||||
testFileName.appendInPlace(diskImageExts[e].extension);
|
||||
|
||||
if (addFileProps(allFiles, testFileName, filesProps)) {
|
||||
if (foundDiskImage) {
|
||||
warning("Ignoring '%s' (already found '%s')", testFileName.toString().c_str(), fileName.toString().c_str());
|
||||
filesProps.erase(testFileName);
|
||||
} else {
|
||||
foundDiskImage = true;
|
||||
fileName = testFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDiskImage) {
|
||||
allFilesPresent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
game.matchedFiles[fileName] = filesProps[fileName];
|
||||
|
||||
if (game.hasUnknownFiles)
|
||||
continue;
|
||||
|
||||
if (fDesc.md5 && fDesc.md5 != filesProps[fileName].md5) {
|
||||
debugC(3, kDebugGlobalDetection, "MD5 Mismatch. Skipping (%s) (%s)", fDesc.md5, filesProps[fileName].md5.c_str());
|
||||
game.hasUnknownFiles = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fDesc.fileSize != AD_NO_SIZE && fDesc.fileSize != filesProps[fileName].size) {
|
||||
debugC(3, kDebugGlobalDetection, "Size Mismatch. Skipping");
|
||||
game.hasUnknownFiles = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
debugC(3, kDebugGlobalDetection, "Matched file: %s", fileName.toString().c_str());
|
||||
}
|
||||
|
||||
// This assumes that the detection table groups together games that have the same gameId and platform
|
||||
if (allFilesPresent) {
|
||||
if (!game.hasUnknownFiles) {
|
||||
debugC(2, kDebugGlobalDetection, "Found game: %s (%s/%s) (%d)", game.desc->gameId, getPlatformDescription(game.desc->platform), getLanguageDescription(game.desc->language), g);
|
||||
// If we just added an unknown variant for this game and platform, remove it
|
||||
if (!matched.empty() && strcmp(matched.back().desc->gameId, game.desc->gameId) == 0 && matched.back().desc->platform == game.desc->platform)
|
||||
matched.pop_back();
|
||||
matched.push_back(game);
|
||||
} else {
|
||||
debugC(5, kDebugGlobalDetection, "Skipping game: %s (%s/%s) (%d)", game.desc->gameId, getPlatformDescription(game.desc->platform), getLanguageDescription(game.desc->language), g);
|
||||
// If we already added a known or unknown variant for this game and platform, don't add another
|
||||
if (matched.empty() || strcmp(matched.back().desc->gameId, game.desc->gameId) != 0 || matched.back().desc->platform != game.desc->platform)
|
||||
matched.push_back(game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
REGISTER_PLUGIN_STATIC(ADL_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Adl::AdlMetaEngineDetection);
|
||||
91
engines/adl/detection.h
Normal file
91
engines/adl/detection.h
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ADL_DETECTION_H
|
||||
#define ADL_DETECTION_H
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
#define SAVEGAME_VERSION 0
|
||||
#define SAVEGAME_NAME_LEN 32
|
||||
|
||||
enum GameType {
|
||||
GAME_TYPE_NONE,
|
||||
GAME_TYPE_HIRES0,
|
||||
GAME_TYPE_HIRES1,
|
||||
GAME_TYPE_HIRES2,
|
||||
GAME_TYPE_HIRES3,
|
||||
GAME_TYPE_HIRES4,
|
||||
GAME_TYPE_HIRES5,
|
||||
GAME_TYPE_HIRES6
|
||||
};
|
||||
|
||||
/*
|
||||
* ====== Mystery House supported versions ======
|
||||
* GAME_VER_HR1_SIMI:
|
||||
* - Instructions always shown (no prompt)
|
||||
* - Instructions contain Simi Valley address
|
||||
* - On-Line Systems title screen in main executable only and waits for key
|
||||
* GAME_VER_HR1_COARSE:
|
||||
* - Longer instructions, now containing Coarsegold address
|
||||
* - On-Line Systems title screen with instructions prompt
|
||||
* GAME_VER_HR1_PD:
|
||||
* - Public Domain disclaimer on startup
|
||||
* - Sierra On-Line title screen with instructions prompt
|
||||
*
|
||||
* Note: there are probably at least two or three more variants
|
||||
*/
|
||||
|
||||
enum GameVersion {
|
||||
GAME_VER_NONE = 0,
|
||||
GAME_VER_HR1_SIMI = 0,
|
||||
GAME_VER_HR1_COARSE,
|
||||
GAME_VER_HR1_VF1,
|
||||
GAME_VER_HR1_VF2,
|
||||
GAME_VER_HR1_PD,
|
||||
GAME_VER_HR4_V1_0,
|
||||
GAME_VER_HR4_V1_1,
|
||||
GAME_VER_HR4_LNG
|
||||
};
|
||||
|
||||
struct AdlGameDescription {
|
||||
AD_GAME_DESCRIPTION_HELPERS(desc);
|
||||
|
||||
ADGameDescription desc;
|
||||
GameType gameType;
|
||||
GameVersion version;
|
||||
};
|
||||
|
||||
// Mystery House was designed for monochrome display, so we default to
|
||||
// monochrome mode there. All the other games default to color mode.
|
||||
#define GAMEOPTION_COLOR_DEFAULT_OFF GUIO_GAMEOPTIONS1
|
||||
#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2
|
||||
#define GAMEOPTION_COLOR_DEFAULT_ON GUIO_GAMEOPTIONS3
|
||||
#define GAMEOPTION_NTSC GUIO_GAMEOPTIONS4
|
||||
#define GAMEOPTION_MONO_TEXT GUIO_GAMEOPTIONS5
|
||||
#define GAMEOPTION_APPLE2E_CURSOR GUIO_GAMEOPTIONS6
|
||||
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS7
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif // ADL_DETECTION_H
|
||||
85
engines/adl/disk.cpp
Normal file
85
engines/adl/disk.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/* 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 "common/stream.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "common/formats/disk_image.h"
|
||||
|
||||
#include "adl/disk.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
void DataBlock_PC::read(Common::SeekableReadStream &stream, byte *const dataPtr, const uint32 size) const {
|
||||
uint32 ofs = 0;
|
||||
|
||||
while (ofs < size) {
|
||||
const uint bps = _disk->getBytesPerSector();
|
||||
uint bytesToRead = bps - ((_offset + stream.pos()) % bps);
|
||||
|
||||
if (bytesToRead == bps) {
|
||||
stream.readByte(); // Skip volume byte
|
||||
--bytesToRead;
|
||||
}
|
||||
|
||||
if (bytesToRead > size - ofs)
|
||||
bytesToRead = size - ofs;
|
||||
|
||||
if (stream.read(dataPtr + ofs, bytesToRead) < bytesToRead)
|
||||
error("Failed to read data block");
|
||||
|
||||
ofs += bytesToRead;
|
||||
}
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *DataBlock_PC::createReadStream() const {
|
||||
const uint bps = _disk->getBytesPerSector();
|
||||
uint sectors = 0;
|
||||
|
||||
// Every data sector starts with a volume byte that we need to skip,
|
||||
// so we need to take that into account during our computations here
|
||||
if (_offset == bps - 1)
|
||||
sectors = 1;
|
||||
|
||||
Common::StreamPtr diskStream(_disk->createReadStream(_track, _sector, _offset, sectors));
|
||||
|
||||
byte sizeBuf[2];
|
||||
read(*diskStream, sizeBuf, 2);
|
||||
|
||||
uint16 blockSize = READ_LE_UINT16(sizeBuf);
|
||||
sectors = 0;
|
||||
|
||||
const uint16 remSize = _disk->getBytesPerSector() - MAX<uint>(_offset, 1);
|
||||
|
||||
if (blockSize + 2 > remSize)
|
||||
sectors = (blockSize + 2 - remSize - 1) / (_disk->getBytesPerSector() - 1) + 1;
|
||||
|
||||
diskStream.reset(_disk->createReadStream(_track, _sector, _offset, sectors));
|
||||
read(*diskStream, sizeBuf, 2);
|
||||
|
||||
byte *buf = static_cast<byte *>(malloc(blockSize));
|
||||
read(*diskStream, buf, blockSize);
|
||||
|
||||
return new Common::MemoryReadStream(buf, blockSize, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
||||
65
engines/adl/disk.h
Normal file
65
engines/adl/disk.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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 "common/ptr.h"
|
||||
#include "common/file.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "common/formats/disk_image.h"
|
||||
|
||||
#ifndef ADL_DISK_H
|
||||
#define ADL_DISK_H
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
class String;
|
||||
}
|
||||
|
||||
namespace Adl {
|
||||
|
||||
// On the Apple II, sector headers contain a disk volume number. This number
|
||||
// is used by ADL multi-disk games. The PC port has the disk volume number
|
||||
// as the first data byte of every sector that contains game data. We need
|
||||
// to skip this number as we read in the data. Additionally, the data is now
|
||||
// prefixed with an uint16 containing the data size.
|
||||
class DataBlock_PC : public Common::DataBlock {
|
||||
public:
|
||||
DataBlock_PC(Common::DiskImage *disk, byte track, byte sector, uint16 offset = 0) :
|
||||
_disk(disk),
|
||||
_track(track),
|
||||
_sector(sector),
|
||||
_offset(offset) { }
|
||||
|
||||
~DataBlock_PC() override { }
|
||||
|
||||
Common::SeekableReadStream *createReadStream() const override;
|
||||
|
||||
private:
|
||||
void read(Common::SeekableReadStream &stream, byte *const dataPtr, const uint32 size) const;
|
||||
|
||||
Common::DiskImage *_disk;
|
||||
byte _track, _sector;
|
||||
uint16 _offset;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
43
engines/adl/disk_image_helpers.h
Normal file
43
engines/adl/disk_image_helpers.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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 ADL_DISK_IMAGE_HELPERS_H
|
||||
#define ADL_DISK_IMAGE_HELPERS_H
|
||||
|
||||
namespace Adl {
|
||||
|
||||
struct DiskImageExt {
|
||||
Common::Platform platform;
|
||||
const char *extension;
|
||||
};
|
||||
|
||||
const DiskImageExt diskImageExts[] = {
|
||||
{ Common::kPlatformApple2, ".woz" },
|
||||
{ Common::kPlatformApple2, ".nib" },
|
||||
{ Common::kPlatformApple2, ".dsk" },
|
||||
{ Common::kPlatformApple2, ".d13" },
|
||||
{ Common::kPlatformAtari8Bit, ".xfd" },
|
||||
{ Common::kPlatformDOS, ".img" }
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif // ADL_DISK_IMAGE_HELPERS_H
|
||||
137
engines/adl/display.cpp
Normal file
137
engines/adl/display.cpp
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/rect.h"
|
||||
#include "common/str.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#include "adl/display.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
Display::~Display() {
|
||||
delete[] _textBuf;
|
||||
}
|
||||
|
||||
void Display::createTextBuffer(uint textWidth, uint textHeight) {
|
||||
_textWidth = textWidth;
|
||||
_textHeight = textHeight;
|
||||
|
||||
_textBuf = new byte[textWidth * textHeight];
|
||||
memset(_textBuf, (byte)asciiToNative(' '), textWidth * textHeight);
|
||||
}
|
||||
|
||||
void Display::setMode(Display::Mode mode) {
|
||||
_mode = mode;
|
||||
|
||||
if (_mode == Display::kModeText || _mode == Display::kModeMixed)
|
||||
renderText();
|
||||
if (_mode == Display::kModeGraphics || _mode == Display::kModeMixed)
|
||||
renderGraphics();
|
||||
}
|
||||
|
||||
void Display::home() {
|
||||
memset(_textBuf, (byte)asciiToNative(' '), _textWidth * _textHeight);
|
||||
_cursorPos = 0;
|
||||
}
|
||||
|
||||
void Display::moveCursorForward() {
|
||||
++_cursorPos;
|
||||
|
||||
if (_cursorPos >= _textWidth * _textHeight)
|
||||
scrollUp();
|
||||
}
|
||||
|
||||
void Display::moveCursorBackward() {
|
||||
if (_cursorPos > 0)
|
||||
--_cursorPos;
|
||||
}
|
||||
|
||||
void Display::moveCursorTo(const Common::Point &pos) {
|
||||
_cursorPos = pos.y * _textWidth + pos.x;
|
||||
|
||||
if (_cursorPos >= _textWidth * _textHeight)
|
||||
error("Cursor position (%i, %i) out of bounds", pos.x, pos.y);
|
||||
}
|
||||
|
||||
void Display::printString(const Common::String &str, bool voiceString) {
|
||||
for (const auto &c : str)
|
||||
printChar(c);
|
||||
|
||||
if (voiceString) {
|
||||
sayText(str);
|
||||
}
|
||||
|
||||
renderText();
|
||||
}
|
||||
|
||||
void Display::printAsciiString(const Common::String &str, bool voiceString) {
|
||||
for (const auto &c : str)
|
||||
printChar(asciiToNative(c));
|
||||
|
||||
if (voiceString) {
|
||||
sayText(str);
|
||||
}
|
||||
|
||||
renderText();
|
||||
}
|
||||
|
||||
void Display::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
|
||||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||||
if (ttsMan != nullptr && ConfMan.getBool("tts_enabled")) {
|
||||
ttsMan->say(convertText(text), action);
|
||||
}
|
||||
}
|
||||
|
||||
Common::U32String Display::convertText(const Common::String &text) const {
|
||||
Common::String result(text);
|
||||
|
||||
bool startsWithDash = result[0] == '\xad';
|
||||
|
||||
for (uint i = 0; i < result.size(); i++) {
|
||||
// Convert carriage returns and dashes to spaces
|
||||
// Only convert dashes if the line starts with a dash (which is usually the case with the "enter command"
|
||||
// prompt), to avoid converting mid-line dashes to spaces and causing odd voicing
|
||||
// (i.e. if the dash in "Hi-Res Adventure" is replaced, English TTS pronounces it as "Hi Residential Adventure")
|
||||
if (result[i] == '\x8d' || (startsWithDash && result[i] == '\xad')) {
|
||||
result[i] = '\xa0';
|
||||
}
|
||||
|
||||
result[i] &= 0x7f;
|
||||
}
|
||||
|
||||
return Common::U32String(result, Common::CodePage::kASCII);
|
||||
}
|
||||
|
||||
void Display::setCharAtCursor(byte c) {
|
||||
_textBuf[_cursorPos] = c;
|
||||
}
|
||||
|
||||
void Display::scrollUp() {
|
||||
memmove(_textBuf, _textBuf + _textWidth, (_textHeight - 1) * _textWidth);
|
||||
memset(_textBuf + (_textHeight - 1) * _textWidth, asciiToNative(' '), _textWidth);
|
||||
if (_cursorPos >= _textWidth)
|
||||
_cursorPos -= _textWidth;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
80
engines/adl/display.h
Normal file
80
engines/adl/display.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/* 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 ADL_DISPLAY_H
|
||||
#define ADL_DISPLAY_H
|
||||
|
||||
#include "common/text-to-speech.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class String;
|
||||
struct Point;
|
||||
}
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class Display {
|
||||
public:
|
||||
enum Mode {
|
||||
kModeGraphics,
|
||||
kModeText,
|
||||
kModeMixed
|
||||
};
|
||||
|
||||
virtual ~Display();
|
||||
|
||||
virtual void init() = 0;
|
||||
void setMode(Mode mode);
|
||||
virtual void renderText() = 0;
|
||||
virtual void renderGraphics() = 0;
|
||||
|
||||
virtual char asciiToNative(char c) const = 0;
|
||||
virtual void printChar(char c) = 0;
|
||||
virtual void showCursor(bool enable) = 0;
|
||||
void home();
|
||||
void moveCursorTo(const Common::Point &pos);
|
||||
void moveCursorForward();
|
||||
void moveCursorBackward();
|
||||
void printString(const Common::String &str, bool voiceString = true);
|
||||
void printAsciiString(const Common::String &str, bool voiceString = true);
|
||||
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::QUEUE) const;
|
||||
Common::U32String convertText(const Common::String &text) const;
|
||||
void setCharAtCursor(byte c);
|
||||
uint getTextWidth() const { return _textWidth; }
|
||||
uint getTextHeight() const { return _textHeight; }
|
||||
void scrollUp();
|
||||
|
||||
protected:
|
||||
Display() : _textBuf(nullptr), _cursorPos(0), _mode(kModeText), _textWidth(0), _textHeight(0) { }
|
||||
|
||||
void createTextBuffer(uint textWidth, uint textHeight);
|
||||
|
||||
byte *_textBuf;
|
||||
uint _cursorPos;
|
||||
Mode _mode;
|
||||
uint _textWidth;
|
||||
uint _textHeight;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
668
engines/adl/display_a2.cpp
Normal file
668
engines/adl/display_a2.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on AppleWin's code for NTSC emulation and its RGB Monitor palette
|
||||
// Copyright (C) 2010-2011, William S Simms
|
||||
// Copyright (C) 2014-2016, Michael Pohoreski, Tom Charlesworth
|
||||
// Licensed under GPLv2+
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/rect.h"
|
||||
#include "common/system.h"
|
||||
#include "common/str.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "graphics/surface.h"
|
||||
#include "graphics/thumbnail.h"
|
||||
|
||||
#include "math/utils.h"
|
||||
|
||||
#include "engines/util.h"
|
||||
|
||||
#include "adl/display_a2.h"
|
||||
#include "adl/adl.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
#define NTSC_REMOVE_BLACK_GHOSTING
|
||||
// #define NTSC_REMOVE_WHITE_RINGING
|
||||
|
||||
// Uppercase-only Apple II font (manually created).
|
||||
const byte Display_A2::_font[64][8] = {
|
||||
{ 0x00, 0x1c, 0x22, 0x2a, 0x3a, 0x1a, 0x02, 0x3c }, { 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22 }, // @A
|
||||
{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x1e }, { 0x00, 0x1c, 0x22, 0x02, 0x02, 0x02, 0x22, 0x1c }, // BC
|
||||
{ 0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1e }, { 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x3e }, // DE
|
||||
{ 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x3c, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3c }, // FG
|
||||
{ 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22 }, { 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c }, // HI
|
||||
{ 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c }, { 0x00, 0x22, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22 }, // JK
|
||||
{ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e }, { 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22 }, // LM
|
||||
{ 0x00, 0x22, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // NO
|
||||
{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x12, 0x2c }, // PQ
|
||||
{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x0a, 0x12, 0x22 }, { 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c }, // RS
|
||||
{ 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // TU
|
||||
{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22 }, // VW
|
||||
{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22 }, { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08 }, // XY
|
||||
{ 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e }, // Z[
|
||||
{ 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, { 0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e }, // \]
|
||||
{ 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e }, // ^_
|
||||
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08 }, // !
|
||||
{ 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14 }, // "#
|
||||
{ 0x00, 0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x1e, 0x08 }, { 0x00, 0x06, 0x26, 0x10, 0x08, 0x04, 0x32, 0x30 }, // $%
|
||||
{ 0x00, 0x04, 0x0a, 0x0a, 0x04, 0x2a, 0x12, 0x2c }, { 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00 }, // &'
|
||||
{ 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08 }, { 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08 }, // ()
|
||||
{ 0x00, 0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08 }, { 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00 }, // *+
|
||||
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x04 }, { 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00 }, // ,-
|
||||
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }, { 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // ./
|
||||
{ 0x00, 0x1c, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x1c }, { 0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x1c }, // 01
|
||||
{ 0x00, 0x1c, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x20, 0x10, 0x18, 0x20, 0x22, 0x1c }, // 23
|
||||
{ 0x00, 0x10, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x10 }, { 0x00, 0x3e, 0x02, 0x1e, 0x20, 0x20, 0x22, 0x1c }, // 45
|
||||
{ 0x00, 0x38, 0x04, 0x02, 0x1e, 0x22, 0x22, 0x1c }, { 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04 }, // 67
|
||||
{ 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c }, { 0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x10, 0x0e }, // 89
|
||||
{ 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x04 }, // :;
|
||||
{ 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10 }, { 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00 }, // <=
|
||||
{ 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04 }, { 0x00, 0x1c, 0x22, 0x10, 0x08, 0x08, 0x00, 0x08 } // >?
|
||||
};
|
||||
|
||||
struct LineDoubleBright {
|
||||
static uint8 blend(uint8 c1, uint8 c2) {
|
||||
return c1;
|
||||
}
|
||||
};
|
||||
|
||||
struct LineDoubleDim {
|
||||
static uint8 blend(uint8 c1, uint8 c2) {
|
||||
return (c1 >> 1) + (c1 >> 2);
|
||||
}
|
||||
};
|
||||
|
||||
struct BlendBright {
|
||||
static uint8 blend(uint8 c1, uint8 c2) {
|
||||
return (c1 + c2) >> 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct BlendDim {
|
||||
static uint8 blend(uint8 c1, uint8 c2) {
|
||||
// AppleWin does c1 >>= 2; return (c1 < c2 ? c2 - c1 : 0);
|
||||
// I think the following looks a lot better:
|
||||
return ((c1 + c2) >> 2) + ((c1 + c2) >> 3);
|
||||
}
|
||||
};
|
||||
|
||||
static const uint kColorPhases = 4;
|
||||
// All PixelWriters have been adjusted to have 3 pixels of "pre-render" that
|
||||
// will be cut off when blitting to the screen
|
||||
static const uint kPreRender = 3;
|
||||
|
||||
template<typename ColorType, typename T>
|
||||
class PixelWriter {
|
||||
public:
|
||||
PixelWriter() : _ptr(nullptr), _format(g_system->getScreenFormat()), _phase(0), _window(0) { }
|
||||
|
||||
void setupWrite(ColorType *dest) {
|
||||
_ptr = dest;
|
||||
_phase = 3;
|
||||
_window = 0;
|
||||
}
|
||||
|
||||
void writePixels(uint bits) {
|
||||
for (uint b = 0; b < 14; ++b) {
|
||||
_window <<= 1;
|
||||
_window |= bits & 1;
|
||||
bits >>= 1;
|
||||
*_ptr++ = static_cast<T *>(this)->getColor();
|
||||
_phase = (_phase + 1) & 3;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
ColorType *_ptr;
|
||||
Graphics::PixelFormat _format;
|
||||
uint _phase;
|
||||
uint _window;
|
||||
};
|
||||
|
||||
template<typename ColorType>
|
||||
class PixelWriterColor : public PixelWriter<ColorType, PixelWriterColor<ColorType> > {
|
||||
public:
|
||||
static const uint kColors = 16;
|
||||
typedef LineDoubleBright BlendRegular;
|
||||
typedef LineDoubleDim BlendScanlines;
|
||||
|
||||
PixelWriterColor() {
|
||||
const byte palette[kColors][3] = {
|
||||
{ 0x00, 0x00, 0x00 }, { 0x9d, 0x09, 0x66 }, { 0x2a, 0x2a, 0xe5 }, { 0xc7, 0x34, 0xff },
|
||||
{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x80 }, { 0x0d, 0xa1, 0xff }, { 0xaa, 0xaa, 0xff },
|
||||
{ 0x55, 0x55, 0x00 }, { 0xf2, 0x5e, 0x00 }, { 0xc0, 0xc0, 0xc0 }, { 0xff, 0x89, 0xe5 },
|
||||
{ 0x38, 0xcb, 0x00 }, { 0xd5, 0xd5, 0x1a }, { 0x62, 0xf6, 0x99 }, { 0xff, 0xff, 0xff }
|
||||
};
|
||||
|
||||
for (uint pattern = 0; pattern < kColors; ++pattern) {
|
||||
uint color = ((pattern & 1) << 3) | ((pattern & 2) << 1) | ((pattern & 4) >> 1) | ((pattern & 8) >> 3);
|
||||
|
||||
for (uint phase = 0; phase < kColorPhases; ++phase) {
|
||||
_colors[phase][pattern] = this->_format.RGBToColor(palette[color][0], palette[color][1], palette[color][2]);
|
||||
color = ((color & 8) >> 3) | ((color << 1) & 0x0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// >> 2 to synchronize rendering output with NTSC
|
||||
ColorType getColor() { return _colors[this->_phase][(this->_window >> 2) & (kColors - 1)]; }
|
||||
|
||||
private:
|
||||
ColorType _colors[kColorPhases][kColors];
|
||||
};
|
||||
|
||||
template<typename ColorType, uint8 R, uint8 G, uint8 B>
|
||||
class PixelWriterMono : public PixelWriter<ColorType, PixelWriterMono<ColorType, R, G, B> > {
|
||||
public:
|
||||
static const uint kColors = 2;
|
||||
|
||||
typedef LineDoubleBright BlendRegular;
|
||||
typedef LineDoubleDim BlendScanlines;
|
||||
|
||||
PixelWriterMono() {
|
||||
_colors[0] = this->_format.RGBToColor(0, 0, 0);
|
||||
_colors[1] = this->_format.RGBToColor(R, G, B);
|
||||
}
|
||||
|
||||
ColorType getColor() { return _colors[(this->_window >> 3) & (kColors - 1)]; }
|
||||
|
||||
private:
|
||||
ColorType _colors[kColors];
|
||||
};
|
||||
|
||||
static double filterChroma(double z) {
|
||||
static double x[3] = {0, 0, 0};
|
||||
static double y[3] = {0, 0, 0};
|
||||
|
||||
x[0] = x[1];
|
||||
x[1] = x[2];
|
||||
x[2] = z / 7.438011255;
|
||||
|
||||
y[0] = y[1];
|
||||
y[1] = y[2];
|
||||
y[2] = -x[0] + x[2] + (-0.7318893645 * y[0]) + (1.2336442711 * y[1]);
|
||||
|
||||
return y[2];
|
||||
}
|
||||
|
||||
static double filterLuma(double z) {
|
||||
static double x[3] = {0, 0, 0};
|
||||
static double y[3] = {0, 0, 0};
|
||||
|
||||
x[0] = x[1];
|
||||
x[1] = x[2];
|
||||
x[2] = z / 13.71331570;
|
||||
|
||||
y[0] = y[1];
|
||||
y[1] = y[2];
|
||||
y[2] = x[0] + x[2] + (2.f * x[1]) + (-0.3961075449 * y[0]) + (1.1044202472 * y[1]);
|
||||
|
||||
return y[2];
|
||||
}
|
||||
|
||||
static double filterSignal(double z) {
|
||||
static double x[3] = {0, 0, 0};
|
||||
static double y[3] = {0, 0, 0};
|
||||
|
||||
x[0] = x[1];
|
||||
x[1] = x[2];
|
||||
x[2] = z / 7.614490548;
|
||||
|
||||
y[0] = y[1];
|
||||
y[1] = y[2];
|
||||
y[2] = x[0] + x[2] + (2.0 * x[1]) + (-0.2718798058 * y[0]) + (0.7465656072 * y[1]);
|
||||
|
||||
return y[2];
|
||||
}
|
||||
|
||||
template<typename ColorType>
|
||||
class PixelWriterColorNTSC : public PixelWriter<ColorType, PixelWriterColorNTSC<ColorType> > {
|
||||
public:
|
||||
static const uint kColors = 4096;
|
||||
|
||||
typedef BlendBright BlendRegular;
|
||||
typedef BlendDim BlendScanlines;
|
||||
|
||||
PixelWriterColorNTSC() {
|
||||
for (uint phase = 0; phase < kColorPhases; ++phase) {
|
||||
double phi = Math::deg2rad(phase * 90.0 + 45.0);
|
||||
for (uint s = 0; s < kColors; ++s) {
|
||||
uint t = s;
|
||||
double y;
|
||||
double i = 0.0;
|
||||
double q = 0.0;
|
||||
|
||||
for (uint n = 0; n < 12; ++n) {
|
||||
double z = (double)(0 != (t & 0x800));
|
||||
t = t << 1;
|
||||
|
||||
for (uint k = 0; k < 2; k++ ) {
|
||||
const double zz = filterSignal(z);
|
||||
double c = filterChroma(zz);
|
||||
y = filterLuma(zz - c);
|
||||
|
||||
c = c * 2.0;
|
||||
i = i + (c * cos(phi) - i) / 8.0;
|
||||
q = q + (c * sin(phi) - q) / 8.0;
|
||||
|
||||
phi += Math::deg2rad(45.0);
|
||||
}
|
||||
}
|
||||
|
||||
// YIQ to RGB
|
||||
const double r64 = y + (0.956 * i) + (0.621 * q);
|
||||
const double g64 = y + (-0.272 * i) + (-0.647 * q);
|
||||
const double b64 = y + (-1.105 * i) + (1.702 * q);
|
||||
|
||||
uint8 r = CLIP<double>(r64, 0.0, 1.0) * 255;
|
||||
uint8 g = CLIP<double>(g64, 0.0, 1.0) * 255;
|
||||
uint8 b = CLIP<double>(b64, 0.0, 1.0) * 255;
|
||||
|
||||
#ifdef NTSC_REMOVE_WHITE_RINGING
|
||||
if ((s & 0xf) == 15) {
|
||||
// white
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 255;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef NTSC_REMOVE_BLACK_GHOSTING
|
||||
if ((s & 0xf) == 0) {
|
||||
// Black
|
||||
r = 0;
|
||||
g = 0;
|
||||
b = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
_colors[phase][s] = this->_format.RGBToColor(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorType getColor() { return _colors[this->_phase][(this->_window >> 1) & (kColors - 1)]; }
|
||||
|
||||
private:
|
||||
ColorType _colors[kColorPhases][kColors];
|
||||
};
|
||||
|
||||
template<typename ColorType>
|
||||
class PixelWriterMonoNTSC : public PixelWriter<ColorType, PixelWriterMonoNTSC<ColorType> > {
|
||||
public:
|
||||
static const uint kColors = 4096;
|
||||
|
||||
typedef BlendBright BlendRegular;
|
||||
typedef BlendDim BlendScanlines;
|
||||
|
||||
PixelWriterMonoNTSC() {
|
||||
for (uint s = 0; s < kColors; ++s) {
|
||||
uint t = s;
|
||||
double y;
|
||||
|
||||
for (uint n = 0; n < 12; ++n) {
|
||||
double z = (double)(0 != (t & 0x800));
|
||||
t = t << 1;
|
||||
|
||||
for (uint k = 0; k < 2; k++ ) {
|
||||
const double zz = filterSignal(z);
|
||||
double c = filterChroma(zz);
|
||||
y = filterLuma(zz - c);
|
||||
}
|
||||
}
|
||||
|
||||
const uint8 brightness = CLIP<double>(y, 0.0, 1.0) * 255;
|
||||
_colors[s] = this->_format.RGBToColor(brightness, brightness, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
ColorType getColor() { return _colors[(this->_window >> 1) & (kColors - 1)]; }
|
||||
|
||||
private:
|
||||
ColorType _colors[kColors];
|
||||
};
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
class DisplayImpl_A2 : public Display_A2 {
|
||||
public:
|
||||
DisplayImpl_A2();
|
||||
~DisplayImpl_A2() override;
|
||||
|
||||
void renderText() override;
|
||||
void renderGraphics() override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
kRenderBufWidth = (kGfxPitch + 1) * 14, // one extra chunk to account for pre-render
|
||||
kRenderBufHeight = (kGfxHeight * 2) + 1 // one extra line to simplify scanline mixing
|
||||
};
|
||||
|
||||
template<typename BlendFunc>
|
||||
void blendScanlines(uint yStart, uint yEnd);
|
||||
|
||||
template<typename Reader, typename Writer>
|
||||
void render(Writer &writer);
|
||||
|
||||
ColorType *_renderBuf;
|
||||
uint16 _doublePixelMasks[128];
|
||||
|
||||
GfxWriter _writerColor;
|
||||
TextWriter _writerMono;
|
||||
};
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::DisplayImpl_A2() : _doublePixelMasks() {
|
||||
_renderBuf = new ColorType[kRenderBufHeight * kRenderBufWidth]();
|
||||
|
||||
for (uint8 val = 0; val < ARRAYSIZE(_doublePixelMasks); ++val)
|
||||
for (uint8 mask = 0; mask < 7; mask++)
|
||||
if (val & (1 << mask))
|
||||
_doublePixelMasks[val] |= 3 << (mask * 2);
|
||||
}
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::~DisplayImpl_A2() {
|
||||
delete[] _renderBuf;
|
||||
}
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
template<typename Reader, typename Writer>
|
||||
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::render(Writer &writer) {
|
||||
uint startY = Reader::getStartY(this);
|
||||
const uint endY = Reader::getEndY(this);
|
||||
|
||||
ColorType *ptr = _renderBuf + startY * kRenderBufWidth * 2;
|
||||
|
||||
for (uint y = startY; y < endY; ++y) {
|
||||
uint16 lastBit = 0;
|
||||
|
||||
writer.setupWrite(ptr);
|
||||
|
||||
for (uint x = 0; x < kGfxPitch; ++x) {
|
||||
const uint8 m = Reader::getBits(this, y, x);
|
||||
|
||||
uint16 bits = _doublePixelMasks[m & 0x7F];
|
||||
|
||||
if (m & 0x80)
|
||||
bits = (bits << 1) | lastBit;
|
||||
|
||||
lastBit = (bits >> 13) & 1;
|
||||
|
||||
writer.writePixels(bits);
|
||||
}
|
||||
|
||||
// Because of the pre-render, we need to feed
|
||||
// in some more bits to get the full picture
|
||||
writer.writePixels(0);
|
||||
|
||||
// The odd lines will be filled in later, so skip a line
|
||||
ptr += 2 * kRenderBufWidth;
|
||||
}
|
||||
|
||||
if (_enableScanlines)
|
||||
blendScanlines<typename Writer::BlendScanlines>(startY, endY);
|
||||
else
|
||||
blendScanlines<typename Writer::BlendRegular>(startY, endY);
|
||||
|
||||
// For the NTSC modes we need to redo the scanline that blends with our first line
|
||||
if (GfxWriter::kColors == 4096 && startY > 0) {
|
||||
--startY;
|
||||
if (_enableScanlines)
|
||||
blendScanlines<typename GfxWriter::BlendScanlines>(startY, startY + 1);
|
||||
else
|
||||
blendScanlines<typename GfxWriter::BlendRegular>(startY, startY + 1);
|
||||
}
|
||||
|
||||
g_system->copyRectToScreen(_renderBuf + startY * 2 * kRenderBufWidth + kPreRender, kRenderBufWidth * sizeof(ColorType), 0, startY * 2, kGfxWidth * 2, (endY - startY) * 2);
|
||||
g_system->updateScreen();
|
||||
}
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
template<typename BlendType>
|
||||
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::blendScanlines(uint yStart, uint yEnd) {
|
||||
const Graphics::PixelFormat rgbFormat = g_system->getScreenFormat();
|
||||
|
||||
// Note: this reads line yEnd * 2 of _renderBuf!
|
||||
for (uint y = yStart; y < yEnd; ++y) {
|
||||
ColorType *buf = &_renderBuf[y * 2 * kRenderBufWidth];
|
||||
for (uint x = 0; x < kRenderBufWidth; ++x) {
|
||||
const ColorType color1 = buf[x];
|
||||
const ColorType color2 = buf[2 * kRenderBufWidth + x];
|
||||
|
||||
uint8 r1, g1, b1, r2, g2, b2;
|
||||
|
||||
rgbFormat.colorToRGB(color1, r1, g1, b1);
|
||||
rgbFormat.colorToRGB(color2, r2, g2, b2);
|
||||
|
||||
const uint8 r3 = BlendType::blend(r1, r2);
|
||||
const uint8 g3 = BlendType::blend(g1, g2);
|
||||
const uint8 b3 = BlendType::blend(b1, b2);
|
||||
|
||||
buf[kRenderBufWidth + x] = rgbFormat.RGBToColor(r3, g3, b3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderText() {
|
||||
if (_mode == kModeGraphics)
|
||||
return;
|
||||
|
||||
_blink = (g_system->getMillis() / 270) & 1;
|
||||
|
||||
if (_mode == kModeMixed && _enableColor && !_enableMonoText)
|
||||
render<TextReader>(_writerColor);
|
||||
else
|
||||
render<TextReader>(_writerMono);
|
||||
}
|
||||
|
||||
template<typename ColorType, typename GfxWriter, typename TextWriter>
|
||||
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderGraphics() {
|
||||
if (_mode == kModeText)
|
||||
return;
|
||||
|
||||
render<GfxReader>(_writerColor);
|
||||
}
|
||||
|
||||
Display_A2::Display_A2() :
|
||||
_frameBuf(nullptr),
|
||||
_showCursor(false),
|
||||
_enableColor(false),
|
||||
_enableScanlines(false),
|
||||
_enableMonoText(false),
|
||||
_enableApple2eCursor(false),
|
||||
_blink(false) { }
|
||||
|
||||
Display_A2::~Display_A2() {
|
||||
delete[] _frameBuf;
|
||||
}
|
||||
|
||||
void Display_A2::init() {
|
||||
createTextBuffer(Display_A2::kTextWidth, Display_A2::kTextHeight);
|
||||
|
||||
_frameBuf = new byte[Display_A2::kGfxSize]();
|
||||
|
||||
_enableColor = ConfMan.getBool("color");
|
||||
_enableScanlines = ConfMan.getBool("scanlines");
|
||||
_enableMonoText = ConfMan.getBool("monotext");
|
||||
_enableApple2eCursor = ConfMan.getBool("apple2e_cursor");
|
||||
}
|
||||
|
||||
void Display_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) const {
|
||||
for (uint j = 0; j < 8; ++j) {
|
||||
for (uint i = 0; i < 8; ++i) {
|
||||
stream.read(dst, Display_A2::kGfxPitch);
|
||||
dst += Display_A2::kGfxPitch * 64;
|
||||
stream.read(dst, Display_A2::kGfxPitch);
|
||||
dst += Display_A2::kGfxPitch * 64;
|
||||
stream.read(dst, Display_A2::kGfxPitch);
|
||||
stream.readUint32LE();
|
||||
stream.readUint32LE();
|
||||
dst -= Display_A2::kGfxPitch * 120;
|
||||
}
|
||||
dst -= Display_A2::kGfxPitch * 63;
|
||||
}
|
||||
|
||||
if (stream.eos() || stream.err())
|
||||
error("Failed to read frame buffer");
|
||||
}
|
||||
|
||||
void Display_A2::loadFrameBuffer(Common::ReadStream &stream) {
|
||||
loadFrameBuffer(stream, _frameBuf);
|
||||
}
|
||||
|
||||
void Display_A2::putPixel(const Common::Point &p, byte color) {
|
||||
const byte offset = p.x / 7;
|
||||
byte mask = 0x80 | (1 << (p.x % 7));
|
||||
|
||||
// Since white and black are in both palettes, we leave
|
||||
// the palette bit alone
|
||||
if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0)
|
||||
mask &= 0x7f;
|
||||
|
||||
// Adjust colors starting with bits '01' or '10' for
|
||||
// odd offsets
|
||||
if (offset & 1) {
|
||||
byte c = color << 1;
|
||||
if (c >= 0x40 && c < 0xc0)
|
||||
color ^= 0x7f;
|
||||
}
|
||||
|
||||
writeFrameBuffer(p, color, mask);
|
||||
}
|
||||
|
||||
void Display_A2::setPixelByte(const Common::Point &p, byte color) {
|
||||
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
|
||||
|
||||
_frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7] = color;
|
||||
}
|
||||
|
||||
void Display_A2::setPixelBit(const Common::Point &p, byte color) {
|
||||
writeFrameBuffer(p, color, 1 << (p.x % 7));
|
||||
}
|
||||
|
||||
void Display_A2::setPixelPalette(const Common::Point &p, byte color) {
|
||||
writeFrameBuffer(p, color, 0x80);
|
||||
}
|
||||
|
||||
byte Display_A2::getPixelByte(const Common::Point &p) const {
|
||||
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
|
||||
|
||||
return _frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7];
|
||||
}
|
||||
|
||||
bool Display_A2::getPixelBit(const Common::Point &p) const {
|
||||
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
|
||||
|
||||
const byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
|
||||
return *b & (1 << (p.x % 7));
|
||||
}
|
||||
|
||||
void Display_A2::clear(byte color) {
|
||||
byte val = 0;
|
||||
|
||||
const byte c = color << 1;
|
||||
if (c >= 0x40 && c < 0xc0)
|
||||
val = 0x7f;
|
||||
|
||||
for (uint i = 0; i < Display_A2::kGfxSize; ++i) {
|
||||
_frameBuf[i] = color;
|
||||
color ^= val;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This does not currently update the surfaces
|
||||
void Display_A2::printChar(char c) {
|
||||
if (c == Display_A2::asciiToNative('\r'))
|
||||
_cursorPos = (_cursorPos / Display_A2::kTextWidth + 1) * Display_A2::kTextWidth;
|
||||
else if (c == Display_A2::asciiToNative('\a')) {
|
||||
renderText();
|
||||
static_cast<AdlEngine *>(g_engine)->bell();
|
||||
} else if ((byte)c < 0x80 || (byte)c >= 0xa0) {
|
||||
setCharAtCursor(c);
|
||||
++_cursorPos;
|
||||
}
|
||||
|
||||
if (_cursorPos == Display_A2::kTextWidth * Display_A2::kTextHeight)
|
||||
scrollUp();
|
||||
}
|
||||
|
||||
void Display_A2::showCursor(bool enable) {
|
||||
_showCursor = enable;
|
||||
}
|
||||
|
||||
void Display_A2::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
|
||||
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
|
||||
|
||||
byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
|
||||
color ^= *b;
|
||||
color &= mask;
|
||||
*b ^= color;
|
||||
}
|
||||
|
||||
template<typename ColorType>
|
||||
static Display_A2 *Display_A2_create_helper() {
|
||||
const bool ntsc = ConfMan.getBool("ntsc");
|
||||
const bool color = ConfMan.getBool("color");
|
||||
const bool monotext = ConfMan.getBool("monotext");
|
||||
|
||||
typedef PixelWriterMono<ColorType, 0xff, 0xff, 0xff> PixelWriterMonoWhite;
|
||||
typedef PixelWriterMono<ColorType, 0x00, 0xc0, 0x00> PixelWriterMonoGreen;
|
||||
|
||||
if (ntsc) {
|
||||
if (color) {
|
||||
if (monotext)
|
||||
return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoWhite>;
|
||||
else
|
||||
return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
|
||||
} else {
|
||||
if (monotext)
|
||||
return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoWhite>;
|
||||
else
|
||||
return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
|
||||
}
|
||||
}
|
||||
|
||||
if (color)
|
||||
return new DisplayImpl_A2<ColorType, PixelWriterColor<ColorType>, PixelWriterMonoWhite>;
|
||||
else
|
||||
return new DisplayImpl_A2<ColorType, PixelWriterMonoGreen, PixelWriterMonoGreen>;
|
||||
}
|
||||
|
||||
Display_A2 *Display_A2_create() {
|
||||
initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2, nullptr);
|
||||
debugN(1, "Initialized graphics with format: %s", g_system->getScreenFormat().toString().c_str());
|
||||
|
||||
const uint bpp = g_system->getScreenFormat().bytesPerPixel;
|
||||
|
||||
if (bpp == 4)
|
||||
return Display_A2_create_helper<uint32>();
|
||||
else if (bpp == 2)
|
||||
return Display_A2_create_helper<uint16>();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
138
engines/adl/display_a2.h
Normal file
138
engines/adl/display_a2.h
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "adl/display.h"
|
||||
|
||||
#ifndef ADL_DISPLAY_A2_H
|
||||
#define ADL_DISPLAY_A2_H
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class Display_A2 : public Display {
|
||||
public:
|
||||
Display_A2();
|
||||
~Display_A2() override;
|
||||
|
||||
enum {
|
||||
kGfxWidth = 280,
|
||||
kGfxHeight = 192,
|
||||
kGfxPitch = kGfxWidth / 7,
|
||||
kGfxSize = kGfxPitch * kGfxHeight,
|
||||
kTextWidth = 40,
|
||||
kTextHeight = 24,
|
||||
kSplitHeight = 32
|
||||
};
|
||||
|
||||
void init() override;
|
||||
|
||||
// Graphics
|
||||
uint getGfxWidth() const { return kGfxWidth; }
|
||||
uint getGfxHeight() const { return kGfxHeight; }
|
||||
uint getGfxPitch() const { return kGfxPitch; }
|
||||
void loadFrameBuffer(Common::ReadStream &stream, byte *dst) const ;
|
||||
void loadFrameBuffer(Common::ReadStream &stream);
|
||||
void putPixel(const Common::Point &p, byte color);
|
||||
void setPixelByte(const Common::Point &p, byte color);
|
||||
void setPixelBit(const Common::Point &p, byte color);
|
||||
void setPixelPalette(const Common::Point &p, byte color);
|
||||
byte getPixelByte(const Common::Point &p) const;
|
||||
bool getPixelBit(const Common::Point &p) const;
|
||||
void clear(byte color);
|
||||
|
||||
// Text
|
||||
char asciiToNative(char c) const override { return c | 0x80; }
|
||||
void printChar(char c) override;
|
||||
void showCursor(bool enable) override;
|
||||
|
||||
protected:
|
||||
class TextReader {
|
||||
public:
|
||||
static uint16 getBits(const Display_A2 *display, uint y, uint x) {
|
||||
const uint charPos = (y >> 3) * kTextWidth + x;
|
||||
byte m = display->_textBuf[charPos];
|
||||
byte b = _font[m & 0x3f][y % 8];
|
||||
|
||||
if (display->_showCursor && charPos == display->_cursorPos) {
|
||||
if (!display->_enableApple2eCursor) {
|
||||
m = (m & 0x3f) | 0x40;
|
||||
} else {
|
||||
if (display->_blink) {
|
||||
byte cursor[] = {
|
||||
0x00, 0x00, 0x2a, 0x14,
|
||||
0x2a, 0x14, 0x2a, 0x00
|
||||
};
|
||||
|
||||
b = cursor[y % 8];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(m & 0x80) && (!(m & 0x40) || display->_blink))
|
||||
b = ~b;
|
||||
|
||||
return b & 0x7f;
|
||||
}
|
||||
|
||||
static uint8 getStartY(const Display_A2 *display) {
|
||||
if (display->_mode == kModeText)
|
||||
return 0;
|
||||
else
|
||||
return kGfxHeight - kSplitHeight;
|
||||
}
|
||||
|
||||
static uint8 getEndY(const Display_A2 *display) { return kGfxHeight; }
|
||||
};
|
||||
|
||||
class GfxReader {
|
||||
public:
|
||||
static uint16 getBits(const Display_A2 *display, uint y, uint x) {
|
||||
return display->_frameBuf[y * kGfxPitch + x];
|
||||
}
|
||||
|
||||
static uint8 getStartY(const Display_A2 *display) { return 0; }
|
||||
|
||||
static uint8 getEndY(const Display_A2 *display) {
|
||||
if (display->_mode == kModeGraphics)
|
||||
return kGfxHeight;
|
||||
else
|
||||
return kGfxHeight - kSplitHeight;
|
||||
}
|
||||
};
|
||||
|
||||
byte *_frameBuf;
|
||||
bool _showCursor;
|
||||
bool _enableColor;
|
||||
bool _enableScanlines;
|
||||
bool _enableMonoText;
|
||||
bool _enableApple2eCursor;
|
||||
bool _blink;
|
||||
|
||||
private:
|
||||
void writeFrameBuffer(const Common::Point &p, byte color, byte mask);
|
||||
|
||||
static const byte _font[64][8];
|
||||
};
|
||||
|
||||
Display_A2 *Display_A2_create();
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
591
engines/adl/graphics.h
Normal file
591
engines/adl/graphics.h
Normal file
@@ -0,0 +1,591 @@
|
||||
/* 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 ADL_GRAPHICS_H
|
||||
#define ADL_GRAPHICS_H
|
||||
|
||||
#include "common/rect.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "adl/display.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class GraphicsMan {
|
||||
public:
|
||||
virtual ~GraphicsMan() { }
|
||||
|
||||
// Applesoft BASIC HLINE
|
||||
virtual void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const = 0;
|
||||
// Applesoft BASIC DRAW
|
||||
virtual void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const = 0;
|
||||
virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) = 0;
|
||||
virtual void clearScreen() const = 0;
|
||||
void setBounds(const Common::Rect &r) { _bounds = r; }
|
||||
|
||||
protected:
|
||||
Common::Rect _bounds;
|
||||
};
|
||||
|
||||
// Used in hires1
|
||||
template <class T>
|
||||
class GraphicsMan_v1 : public GraphicsMan {
|
||||
public:
|
||||
GraphicsMan_v1(T &display) : _display(display) { this->setBounds(Common::Rect(280, 160)); }
|
||||
|
||||
void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const override;
|
||||
void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const override;
|
||||
void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) override;
|
||||
void clearScreen() const override;
|
||||
|
||||
protected:
|
||||
T &_display;
|
||||
void putPixel(const Common::Point &p, byte color) const;
|
||||
|
||||
private:
|
||||
void drawShapePixel(Common::Point &p, byte color, byte bits, byte quadrant) const;
|
||||
virtual byte getClearColor() const { return 0x00; }
|
||||
};
|
||||
|
||||
// Used in hires0 and hires2-hires4
|
||||
template <class T>
|
||||
class GraphicsMan_v2 : public GraphicsMan_v1<T> {
|
||||
public:
|
||||
GraphicsMan_v2(T &display) : GraphicsMan_v1<T>(display), _color(0) { }
|
||||
void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) override;
|
||||
|
||||
protected:
|
||||
bool canFillAt(const Common::Point &p, const bool stopBit = false);
|
||||
void fillRow(Common::Point p, const byte pattern, const bool stopBit = false);
|
||||
byte getPatternColor(const Common::Point &p, byte pattern);
|
||||
|
||||
private:
|
||||
static bool readByte(Common::SeekableReadStream &pic, byte &b);
|
||||
bool readPoint(Common::SeekableReadStream &pic, Common::Point &p);
|
||||
void drawCorners(Common::SeekableReadStream &pic, bool yFirst);
|
||||
void drawRelativeLines(Common::SeekableReadStream &pic);
|
||||
void drawAbsoluteLines(Common::SeekableReadStream &pic);
|
||||
void fill(Common::SeekableReadStream &pic);
|
||||
virtual void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit);
|
||||
virtual void fillAt(Common::Point p, const byte pattern);
|
||||
byte getClearColor() const override { return 0xff; }
|
||||
|
||||
byte _color;
|
||||
Common::Point _offset;
|
||||
};
|
||||
|
||||
// Used in hires5, hires6 and gelfling (possibly others as well)
|
||||
template <class T>
|
||||
class GraphicsMan_v3 : public GraphicsMan_v2<T> {
|
||||
public:
|
||||
GraphicsMan_v3(T &display) : GraphicsMan_v2<T>(display) { }
|
||||
|
||||
private:
|
||||
void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) override;
|
||||
void fillAt(Common::Point p, const byte pattern) override;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v1<T>::clearScreen() const {
|
||||
_display.setMode(Display::kModeMixed);
|
||||
_display.clear(getClearColor());
|
||||
}
|
||||
|
||||
// Draws a four-connected line
|
||||
template <class T>
|
||||
void GraphicsMan_v1<T>::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const {
|
||||
int16 deltaX = p2.x - p1.x;
|
||||
int8 xStep = 1;
|
||||
|
||||
if (deltaX < 0) {
|
||||
deltaX = -deltaX;
|
||||
xStep = -1;
|
||||
}
|
||||
|
||||
int16 deltaY = p2.y - p1.y;
|
||||
int8 yStep = -1;
|
||||
|
||||
if (deltaY > 0) {
|
||||
deltaY = -deltaY;
|
||||
yStep = 1;
|
||||
}
|
||||
|
||||
Common::Point p(p1);
|
||||
int16 steps = deltaX - deltaY + 1;
|
||||
int16 err = deltaX + deltaY;
|
||||
|
||||
while (true) {
|
||||
putPixel(p, color);
|
||||
|
||||
if (--steps == 0)
|
||||
return;
|
||||
|
||||
if (err < 0) {
|
||||
p.y += yStep;
|
||||
err += deltaX;
|
||||
} else {
|
||||
p.x += xStep;
|
||||
err += deltaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v1<T>::putPixel(const Common::Point &p, byte color) const {
|
||||
if (this->_bounds.contains(p))
|
||||
_display.putPixel(p, color);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v1<T>::drawShapePixel(Common::Point &p, byte color, byte bits, byte quadrant) const {
|
||||
if (bits & 4)
|
||||
putPixel(p, color);
|
||||
|
||||
bits += quadrant;
|
||||
|
||||
if (bits & 1)
|
||||
p.x += (bits & 2 ? -1 : 1);
|
||||
else
|
||||
p.y += (bits & 2 ? 1 : -1);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v1<T>::drawShape(Common::ReadStream &corners, Common::Point &pos, byte rotation, byte scaling, byte color) const {
|
||||
const byte stepping[] = {
|
||||
0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5,
|
||||
0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18,
|
||||
0xff
|
||||
};
|
||||
|
||||
byte quadrant = rotation >> 4;
|
||||
rotation &= 0xf;
|
||||
byte xStep = stepping[rotation];
|
||||
byte yStep = stepping[(rotation ^ 0xf) + 1] + 1;
|
||||
|
||||
while (true) {
|
||||
byte b = corners.readByte();
|
||||
|
||||
if (corners.eos() || corners.err())
|
||||
error("Error reading corners");
|
||||
|
||||
if (b == 0)
|
||||
return;
|
||||
|
||||
do {
|
||||
byte xFrac = 0x80;
|
||||
byte yFrac = 0x80;
|
||||
for (uint j = 0; j < scaling; ++j) {
|
||||
if (xFrac + xStep + 1 > 255)
|
||||
drawShapePixel(pos, color, b, quadrant);
|
||||
xFrac += xStep + 1;
|
||||
if (yFrac + yStep > 255)
|
||||
drawShapePixel(pos, color, b, quadrant + 1);
|
||||
yFrac += yStep;
|
||||
}
|
||||
b >>= 3;
|
||||
} while (b != 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v1<T>::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) {
|
||||
byte x, y;
|
||||
bool bNewLine = false;
|
||||
byte oldX = 0, oldY = 0;
|
||||
while (1) {
|
||||
x = pic.readByte();
|
||||
y = pic.readByte();
|
||||
|
||||
if (pic.err() || pic.eos())
|
||||
error("Error reading picture");
|
||||
|
||||
if (x == 0xff && y == 0xff)
|
||||
return;
|
||||
|
||||
if (x == 0 && y == 0) {
|
||||
bNewLine = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
x += pos.x;
|
||||
y += pos.y;
|
||||
|
||||
if (y > 160)
|
||||
y = 160;
|
||||
|
||||
if (bNewLine) {
|
||||
putPixel(Common::Point(x, y), 0x7f);
|
||||
bNewLine = false;
|
||||
} else {
|
||||
drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f);
|
||||
}
|
||||
|
||||
oldX = x;
|
||||
oldY = y;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GraphicsMan_v2<T>::readByte(Common::SeekableReadStream &pic, byte &b) {
|
||||
b = pic.readByte();
|
||||
|
||||
if (pic.eos() || pic.err())
|
||||
error("Error reading picture");
|
||||
|
||||
if (b >= 0xe0) {
|
||||
pic.seek(-1, SEEK_CUR);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GraphicsMan_v2<T>::readPoint(Common::SeekableReadStream &pic, Common::Point &p) {
|
||||
byte b;
|
||||
|
||||
if (!readByte(pic, b))
|
||||
return false;
|
||||
|
||||
p.x = b + _offset.x;
|
||||
p.x <<= 1;
|
||||
|
||||
if (!readByte(pic, b))
|
||||
return false;
|
||||
|
||||
p.y = b + _offset.y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
byte GraphicsMan_v2<T>::getPatternColor(const Common::Point &p, byte pattern) {
|
||||
const byte fillPatterns[][4] = {
|
||||
{ 0x00, 0x00, 0x00, 0x00 },
|
||||
{ 0x80, 0x80, 0x80, 0x80 },
|
||||
{ 0xff, 0xff, 0xff, 0xff },
|
||||
{ 0x7f, 0x7f, 0x7f, 0x7f },
|
||||
{ 0x2a, 0x55, 0x2a, 0x55 },
|
||||
{ 0xaa, 0xd5, 0xaa, 0xd5 },
|
||||
{ 0x55, 0x2a, 0x55, 0x2a },
|
||||
{ 0xd5, 0xaa, 0xd5, 0xaa },
|
||||
{ 0x33, 0x66, 0x4c, 0x19 },
|
||||
{ 0xb3, 0xe6, 0xcc, 0x99 },
|
||||
{ 0x22, 0x44, 0x08, 0x11 },
|
||||
{ 0xa2, 0xc4, 0x88, 0x91 },
|
||||
{ 0x11, 0x22, 0x44, 0x08 },
|
||||
{ 0x91, 0xa2, 0xc4, 0x88 },
|
||||
{ 0x6e, 0x5d, 0x3b, 0x77 },
|
||||
{ 0xee, 0xdd, 0xbb, 0xf7 },
|
||||
{ 0x5d, 0x3b, 0x77, 0x6e },
|
||||
{ 0xdd, 0xbb, 0xf7, 0xee },
|
||||
{ 0x66, 0x4c, 0x19, 0x33 },
|
||||
{ 0xe6, 0xcc, 0x99, 0xb3 },
|
||||
{ 0x33, 0x66, 0x4c, 0x19 },
|
||||
{ 0xb3, 0xe6, 0xcc, 0x99 }
|
||||
};
|
||||
|
||||
if (pattern >= ARRAYSIZE(fillPatterns))
|
||||
error("Invalid fill pattern %i encountered in picture", pattern);
|
||||
|
||||
byte offset = (p.y & 1) << 1;
|
||||
offset += (p.x / 7) & 3;
|
||||
|
||||
return fillPatterns[pattern][offset % sizeof(fillPatterns[0])];
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::drawCorners(Common::SeekableReadStream &pic, bool yFirst) {
|
||||
Common::Point p;
|
||||
|
||||
if (!readPoint(pic, p))
|
||||
return;
|
||||
|
||||
if (yFirst)
|
||||
goto doYStep;
|
||||
|
||||
while (true) {
|
||||
byte b;
|
||||
int16 n;
|
||||
|
||||
if (!readByte(pic, b))
|
||||
return;
|
||||
|
||||
n = b + _offset.x;
|
||||
|
||||
this->putPixel(p, _color);
|
||||
|
||||
n <<= 1;
|
||||
this->drawLine(p, Common::Point(n, p.y), _color);
|
||||
p.x = n;
|
||||
|
||||
doYStep:
|
||||
if (!readByte(pic, b))
|
||||
return;
|
||||
|
||||
n = b + _offset.y;
|
||||
|
||||
this->putPixel(p, _color);
|
||||
this->drawLine(p, Common::Point(p.x, n), _color);
|
||||
|
||||
this->putPixel(Common::Point(p.x + 1, p.y), _color);
|
||||
this->drawLine(Common::Point(p.x + 1, p.y), Common::Point(p.x + 1, n), _color);
|
||||
|
||||
p.y = n;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::drawRelativeLines(Common::SeekableReadStream &pic) {
|
||||
Common::Point p1;
|
||||
|
||||
if (!readPoint(pic, p1))
|
||||
return;
|
||||
|
||||
this->putPixel(p1, _color);
|
||||
|
||||
while (true) {
|
||||
Common::Point p2(p1);
|
||||
|
||||
byte n;
|
||||
|
||||
if (!readByte(pic, n))
|
||||
return;
|
||||
|
||||
byte h = (n & 0x70) >> 4;
|
||||
byte l = n & 7;
|
||||
|
||||
if (n & 0x80)
|
||||
p2.x -= (h << 1);
|
||||
else
|
||||
p2.x += (h << 1);
|
||||
|
||||
if (n & 8)
|
||||
p2.y -= l;
|
||||
else
|
||||
p2.y += l;
|
||||
|
||||
this->drawLine(p1, p2, _color);
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::drawAbsoluteLines(Common::SeekableReadStream &pic) {
|
||||
Common::Point p1;
|
||||
|
||||
if (!readPoint(pic, p1))
|
||||
return;
|
||||
|
||||
this->putPixel(p1, _color);
|
||||
|
||||
while (true) {
|
||||
Common::Point p2;
|
||||
|
||||
if (!readPoint(pic, p2))
|
||||
return;
|
||||
|
||||
this->drawLine(p1, p2, _color);
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GraphicsMan_v2<T>::canFillAt(const Common::Point &p, const bool stopBit) {
|
||||
return this->_display.getPixelBit(p) != stopBit && this->_display.getPixelBit(Common::Point(p.x + 1, p.y)) != stopBit;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) {
|
||||
byte color = getPatternColor(p, pattern);
|
||||
|
||||
while (--p.x >= this->_bounds.left) {
|
||||
if ((p.x % 7) == 6) {
|
||||
color = getPatternColor(p, pattern);
|
||||
this->_display.setPixelPalette(p, color);
|
||||
}
|
||||
if (this->_display.getPixelBit(p) == stopBit)
|
||||
break;
|
||||
this->_display.setPixelBit(p, color);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::fillRow(Common::Point p, const byte pattern, const bool stopBit) {
|
||||
// Set pixel at p and palette
|
||||
byte color = getPatternColor(p, pattern);
|
||||
this->_display.setPixelPalette(p, color);
|
||||
this->_display.setPixelBit(p, color);
|
||||
|
||||
// Fill left of p
|
||||
fillRowLeft(p, pattern, stopBit);
|
||||
|
||||
// Fill right of p
|
||||
while (++p.x < this->_bounds.right) {
|
||||
if ((p.x % 7) == 0) {
|
||||
color = getPatternColor(p, pattern);
|
||||
// Palette is set before the first bit is tested
|
||||
this->_display.setPixelPalette(p, color);
|
||||
}
|
||||
if (this->_display.getPixelBit(p) == stopBit)
|
||||
break;
|
||||
this->_display.setPixelBit(p, color);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::fillAt(Common::Point p, const byte pattern) {
|
||||
const bool stopBit = !this->_display.getPixelBit(p);
|
||||
|
||||
// Move up into the open space above p
|
||||
while (--p.y >= this->_bounds.top && canFillAt(p, stopBit)) {}
|
||||
|
||||
// Then fill by moving down
|
||||
while (++p.y < this->_bounds.bottom && canFillAt(p, stopBit))
|
||||
fillRow(p, pattern, stopBit);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::fill(Common::SeekableReadStream &pic) {
|
||||
byte pattern;
|
||||
|
||||
if (!readByte(pic, pattern))
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
Common::Point p;
|
||||
|
||||
if (!readPoint(pic, p))
|
||||
return;
|
||||
|
||||
if (this->_bounds.contains(p))
|
||||
fillAt(p, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v2<T>::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) {
|
||||
// NOTE: The original engine only resets the color for overlays. As a result, room
|
||||
// pictures that draw without setting a color or clearing the screen, will use the
|
||||
// last color set by the previous picture. We assume this is unintentional and do
|
||||
// not copy this behavior.
|
||||
_color = 0;
|
||||
_offset = pos;
|
||||
|
||||
while (true) {
|
||||
byte opcode = pic.readByte();
|
||||
|
||||
if (pic.eos() || pic.err())
|
||||
error("Error reading picture");
|
||||
|
||||
switch (opcode) {
|
||||
case 0xe0:
|
||||
drawCorners(pic, false);
|
||||
break;
|
||||
case 0xe1:
|
||||
drawCorners(pic, true);
|
||||
break;
|
||||
case 0xe2:
|
||||
drawRelativeLines(pic);
|
||||
break;
|
||||
case 0xe3:
|
||||
drawAbsoluteLines(pic);
|
||||
break;
|
||||
case 0xe4:
|
||||
fill(pic);
|
||||
break;
|
||||
case 0xe5:
|
||||
this->clearScreen();
|
||||
_color = 0x00;
|
||||
break;
|
||||
case 0xf0:
|
||||
_color = 0x00;
|
||||
break;
|
||||
case 0xf1:
|
||||
_color = 0x2a;
|
||||
break;
|
||||
case 0xf2:
|
||||
_color = 0x55;
|
||||
break;
|
||||
case 0xf3:
|
||||
_color = 0x7f;
|
||||
break;
|
||||
case 0xf4:
|
||||
_color = 0x80;
|
||||
break;
|
||||
case 0xf5:
|
||||
_color = 0xaa;
|
||||
break;
|
||||
case 0xf6:
|
||||
_color = 0xd5;
|
||||
break;
|
||||
case 0xf7:
|
||||
_color = 0xff;
|
||||
break;
|
||||
case 0xff:
|
||||
return;
|
||||
default:
|
||||
if (opcode >= 0xe0)
|
||||
error("Invalid pic opcode %02x", opcode);
|
||||
else
|
||||
warning("Expected pic opcode, but found data byte %02x", opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v3<T>::fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) {
|
||||
byte color = this->getPatternColor(p, pattern);
|
||||
|
||||
while (--p.x >= this->_bounds.left) {
|
||||
// In this version, when moving left, it no longer sets the palette first
|
||||
if (!this->_display.getPixelBit(p))
|
||||
return;
|
||||
if ((p.x % 7) == 6) {
|
||||
color = this->getPatternColor(p, pattern);
|
||||
this->_display.setPixelPalette(p, color);
|
||||
}
|
||||
this->_display.setPixelBit(p, color);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void GraphicsMan_v3<T>::fillAt(Common::Point p, const byte pattern) {
|
||||
// If the row at p cannot be filled, we do nothing
|
||||
if (!this->canFillAt(p))
|
||||
return;
|
||||
|
||||
this->fillRow(p, pattern);
|
||||
|
||||
Common::Point q(p);
|
||||
|
||||
// Fill up from p
|
||||
while (--q.y >= this->_bounds.top && this->canFillAt(q))
|
||||
this->fillRow(q, pattern);
|
||||
|
||||
// Fill down from p
|
||||
while (++p.y < this->_bounds.bottom && this->canFillAt(p))
|
||||
this->fillRow(p, pattern);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
673
engines/adl/hires1.cpp
Normal file
673
engines/adl/hires1.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
/* 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 "common/system.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/ptr.h"
|
||||
|
||||
#include "adl/adl.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/display_a2.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
#define IDS_HR1_EXE_0 "AUTO LOAD OBJ"
|
||||
#define IDS_HR1_EXE_1 "ADVENTURE"
|
||||
#define IDS_HR1_MESSAGES "MESSAGES"
|
||||
|
||||
#define IDI_HR1_NUM_ROOMS 41
|
||||
#define IDI_HR1_NUM_PICS 97
|
||||
#define IDI_HR1_NUM_VARS 20
|
||||
#define IDI_HR1_NUM_ITEM_OFFSETS 21
|
||||
#define IDI_HR1_NUM_MESSAGES 168
|
||||
|
||||
// Messages used outside of scripts
|
||||
#define IDI_HR1_MSG_CANT_GO_THERE 137
|
||||
#define IDI_HR1_MSG_DONT_UNDERSTAND 37
|
||||
#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151
|
||||
#define IDI_HR1_MSG_ITEM_NOT_HERE 152
|
||||
#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140
|
||||
#define IDI_HR1_MSG_DONT_HAVE_IT 127
|
||||
#define IDI_HR1_MSG_GETTING_DARK 7
|
||||
|
||||
#define IDI_HR1_OFS_PD_TEXT_0 0x005d
|
||||
#define IDI_HR1_OFS_PD_TEXT_1 0x012b
|
||||
#define IDI_HR1_OFS_PD_TEXT_2 0x016d
|
||||
#define IDI_HR1_OFS_PD_TEXT_3 0x0259
|
||||
|
||||
#define IDI_HR1_OFS_ITEMS 0x0100
|
||||
#define IDI_HR1_OFS_ROOMS 0x050a
|
||||
#define IDI_HR1_OFS_PICS 0x4b03
|
||||
#define IDI_HR1_OFS_CMDS_0 0x3c00
|
||||
#define IDI_HR1_OFS_CMDS_1 0x3d00
|
||||
#define IDI_HR1_OFS_MSGS 0x4d00
|
||||
|
||||
#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff
|
||||
#define IDI_HR1_OFS_SHAPES 0x4f00
|
||||
|
||||
class HiRes1Engine : public AdlEngine {
|
||||
public:
|
||||
HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine(syst, gd),
|
||||
_files(nullptr),
|
||||
_messageDelay(true) { }
|
||||
~HiRes1Engine() override { delete _files; }
|
||||
|
||||
protected:
|
||||
// AdlEngine
|
||||
void runIntro() override;
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
void restartGame();
|
||||
void printString(const Common::String &str) override;
|
||||
Common::String loadMessage(uint idx) const override;
|
||||
void printMessage(uint idx) override;
|
||||
void drawItems() override;
|
||||
void drawItem(Item &item, const Common::Point &pos) override;
|
||||
void loadRoom(byte roomNr) override;
|
||||
void showRoom() override;
|
||||
|
||||
void showInstructions(Common::SeekableReadStream &stream);
|
||||
void wordWrap(Common::String &str) const;
|
||||
|
||||
Common::Files *_files;
|
||||
Common::File _exe;
|
||||
Common::Array<Common::DataBlockPtr> _corners;
|
||||
Common::Array<byte> _roomDesc;
|
||||
bool _messageDelay;
|
||||
|
||||
struct {
|
||||
Common::String cantGoThere;
|
||||
Common::String dontHaveIt;
|
||||
Common::String dontUnderstand;
|
||||
Common::String gettingDark;
|
||||
} _gameStrings;
|
||||
};
|
||||
|
||||
void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
|
||||
_display->setMode(Display::kModeText);
|
||||
|
||||
Common::String ttsMessage;
|
||||
|
||||
for (;;) {
|
||||
byte opc = stream.readByte();
|
||||
|
||||
if (opc != 0x20)
|
||||
error("Error reading instructions");
|
||||
|
||||
uint16 addr = stream.readUint16BE();
|
||||
|
||||
if (addr == 0x58fc) {
|
||||
// HOME
|
||||
_display->home();
|
||||
} else if (addr == 0x6ffd) {
|
||||
_display->sayText(ttsMessage);
|
||||
ttsMessage.clear();
|
||||
|
||||
// GETLN1
|
||||
inputString();
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
} else {
|
||||
// We assume print string call (addr varies per game)
|
||||
Common::String str = readString(stream);
|
||||
ttsMessage += str;
|
||||
|
||||
// If the string ends in two carriage returns, add a newline to the end of the TTS message
|
||||
// (since carriage returns will be replaced with spaces)
|
||||
if (str.size() > 1 && str[str.size() - 1] == '\x8d' && str[str.size() - 2] == '\x8d') {
|
||||
ttsMessage += '\n';
|
||||
}
|
||||
|
||||
if (stream.err() || stream.eos())
|
||||
error("Error reading instructions");
|
||||
|
||||
// Ctrl-D signifies system command (main binary would be loaded here)
|
||||
size_t posChr4 = str.findFirstOf(_display->asciiToNative(4));
|
||||
|
||||
if (posChr4 != str.npos) {
|
||||
_display->printString(str.substr(0, posChr4), false);
|
||||
return;
|
||||
}
|
||||
|
||||
_display->printString(str, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes1Engine::runIntro() {
|
||||
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
|
||||
|
||||
// Early version have no bitmap in 'AUTO LOAD OBJ'
|
||||
if (getGameVersion() >= GAME_VER_HR1_COARSE) {
|
||||
// Later binaries have a MIXEDON prepended to it, by skipping it
|
||||
// we can use the same offsets for both variants
|
||||
if (stream->readUint16BE() == 0xad53)
|
||||
stream.reset(_files->createReadStream(IDS_HR1_EXE_0, 3));
|
||||
|
||||
stream->seek(0x1000);
|
||||
_display->setMode(Display::kModeGraphics);
|
||||
static_cast<Display_A2 *>(_display)->loadFrameBuffer(*stream);
|
||||
_display->renderGraphics();
|
||||
|
||||
if (getGameVersion() == GAME_VER_HR1_PD) {
|
||||
// Only the PD version shows a title screen during the load
|
||||
delay(4000);
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Common::String str;
|
||||
|
||||
// Show the PD disclaimer for the PD release
|
||||
if (getGameVersion() == GAME_VER_HR1_PD) {
|
||||
// The P.D.E. version on the Roberta Williams Anthology has a renamed HELLO file
|
||||
const char *fileName = "MYSTERY.HELLO";
|
||||
|
||||
if (!_files->exists(fileName))
|
||||
fileName = "HELLO";
|
||||
|
||||
Common::StreamPtr basic(_files->createReadStream(fileName));
|
||||
|
||||
_display->setMode(Display::kModeText);
|
||||
_display->home();
|
||||
|
||||
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_0, '"');
|
||||
_display->printAsciiString(str + '\r', false);
|
||||
Common::String ttsMessage = str + ' ';
|
||||
|
||||
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_1, '"');
|
||||
_display->printAsciiString(str + "\r\r", false);
|
||||
ttsMessage += str + '\n';
|
||||
|
||||
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_2, '"');
|
||||
_display->printAsciiString(str + "\r\r", false);
|
||||
ttsMessage += str + '\n';
|
||||
|
||||
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_3, '"');
|
||||
_display->printAsciiString(str + '\r', false);
|
||||
_display->sayText(ttsMessage + ' ' + str);
|
||||
|
||||
inputKey();
|
||||
stopTextToSpeech();
|
||||
if (shouldQuit())
|
||||
return;
|
||||
}
|
||||
|
||||
_display->setMode(Display::kModeMixed);
|
||||
|
||||
str = readStringAt(*stream, 0xc);
|
||||
|
||||
if (getGameVersion() >= GAME_VER_HR1_COARSE) {
|
||||
bool instructions = false;
|
||||
|
||||
char keyInstr = 'I';
|
||||
char keyGame = 'G';
|
||||
|
||||
if (getLanguage() == Common::FR_FRA) {
|
||||
keyInstr = 'M';
|
||||
keyGame = 'J';
|
||||
}
|
||||
|
||||
while (1) {
|
||||
_display->printString(str);
|
||||
Common::String s = inputString();
|
||||
|
||||
if (shouldQuit())
|
||||
break;
|
||||
|
||||
if (s.empty())
|
||||
continue;
|
||||
|
||||
if (s[0] == _display->asciiToNative(keyInstr)) {
|
||||
instructions = true;
|
||||
break;
|
||||
} else if (s[0] == _display->asciiToNative(keyGame)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (instructions) {
|
||||
stream->seek(0x5d);
|
||||
showInstructions(*stream);
|
||||
_display->printAsciiString("\r");
|
||||
}
|
||||
} else {
|
||||
// This version shows the last page during the loading of the game
|
||||
// We wait for a key instead (even though there's no prompt for that).
|
||||
stream->seek(3);
|
||||
showInstructions(*stream);
|
||||
inputString();
|
||||
}
|
||||
|
||||
stream.reset(_files->createReadStream(IDS_HR1_EXE_1));
|
||||
stream->seek(0x1800);
|
||||
static_cast<Display_A2 *>(_display)->loadFrameBuffer(*stream);
|
||||
_display->renderGraphics();
|
||||
|
||||
// The title screen would normally be visible during loading.
|
||||
// We add a short delay here to simulate that.
|
||||
delay(2000);
|
||||
|
||||
_display->setMode(Display::kModeMixed);
|
||||
|
||||
if (getGameVersion() == GAME_VER_HR1_SIMI) {
|
||||
// The original waits for the key after initializing the state.
|
||||
// This causes it to also wait for a key on a blank screen when
|
||||
// a game is restarted. We only wait for a key here during the
|
||||
// intro.
|
||||
|
||||
// This does mean we need to push out some extra line feeds to clear the screen
|
||||
_display->printString(_strings.lineFeeds);
|
||||
inputKey();
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes1Engine::init() {
|
||||
if (Common::File::exists("ADVENTURE")) {
|
||||
_files = new Common::Files_Plain();
|
||||
} else {
|
||||
Common::Files_AppleDOS *files = new Common::Files_AppleDOS();
|
||||
if (!files->open(getDiskImageName(0)))
|
||||
error("Failed to open '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
|
||||
_files = files;
|
||||
}
|
||||
|
||||
_graphics = new GraphicsMan_v1<Display_A2>(*static_cast<Display_A2 *>(_display));
|
||||
_display->moveCursorTo(Common::Point(0, 23)); // DOS 3.3
|
||||
|
||||
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
|
||||
|
||||
Common::StringArray exeStrings;
|
||||
extractExeStrings(*stream, 0x1576, exeStrings);
|
||||
|
||||
if (exeStrings.size() != 18)
|
||||
error("Failed to load strings from executable");
|
||||
|
||||
// Some messages have overrides inside the executable
|
||||
_gameStrings.cantGoThere = exeStrings[12];
|
||||
_gameStrings.dontHaveIt = exeStrings[13];
|
||||
_gameStrings.dontUnderstand = exeStrings[14];
|
||||
_gameStrings.gettingDark = exeStrings[15];
|
||||
|
||||
// Load other strings from executable
|
||||
_strings.enterCommand = exeStrings[5];
|
||||
_strings.verbError = exeStrings[3];
|
||||
_strings.nounError = exeStrings[4];
|
||||
_strings.playAgain = exeStrings[8];
|
||||
_strings.pressReturn = exeStrings[10];
|
||||
_strings.lineFeeds = exeStrings[1];
|
||||
|
||||
// Set message IDs
|
||||
_messageIds.cantGoThere = IDI_HR1_MSG_CANT_GO_THERE;
|
||||
_messageIds.dontUnderstand = IDI_HR1_MSG_DONT_UNDERSTAND;
|
||||
_messageIds.itemDoesntMove = IDI_HR1_MSG_ITEM_DOESNT_MOVE;
|
||||
_messageIds.itemNotHere = IDI_HR1_MSG_ITEM_NOT_HERE;
|
||||
_messageIds.thanksForPlaying = IDI_HR1_MSG_THANKS_FOR_PLAYING;
|
||||
|
||||
if (getLanguage() == Common::FR_FRA) {
|
||||
_verbErrorPos = 15;
|
||||
_nounErrorPos = 31;
|
||||
}
|
||||
|
||||
// Load message offsets
|
||||
stream->seek(IDI_HR1_OFS_MSGS);
|
||||
for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i)
|
||||
_messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE()));
|
||||
|
||||
// The French version has 5 additional strings
|
||||
if (getLanguage() == Common::FR_FRA)
|
||||
for (uint i = 0; i < 5; ++i)
|
||||
_messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE()));
|
||||
|
||||
// Load picture data from executable
|
||||
stream->seek(IDI_HR1_OFS_PICS);
|
||||
for (uint i = 1; i <= IDI_HR1_NUM_PICS; ++i) {
|
||||
byte block = stream->readByte();
|
||||
Common::String name = Common::String::format("BLOCK%i", block);
|
||||
uint16 offset = stream->readUint16LE();
|
||||
_pictures[i] = _files->getDataBlock(Common::Path(name), offset);
|
||||
}
|
||||
|
||||
// Load commands from executable
|
||||
stream->seek(IDI_HR1_OFS_CMDS_1);
|
||||
readCommands(*stream, _roomCommands);
|
||||
|
||||
stream->seek(IDI_HR1_OFS_CMDS_0);
|
||||
readCommands(*stream, _globalCommands);
|
||||
|
||||
// Load dropped item offsets
|
||||
stream->seek(IDI_HR1_OFS_ITEM_OFFSETS);
|
||||
loadDroppedItemOffsets(*stream, IDI_HR1_NUM_ITEM_OFFSETS);
|
||||
|
||||
// Load shapes
|
||||
stream->seek(IDI_HR1_OFS_SHAPES);
|
||||
uint16 cornersCount = stream->readUint16LE();
|
||||
for (uint i = 0; i < cornersCount; ++i)
|
||||
_corners.push_back(_files->getDataBlock(IDS_HR1_EXE_1, IDI_HR1_OFS_SHAPES + stream->readUint16LE()));
|
||||
|
||||
if (stream->eos() || stream->err())
|
||||
error("Failed to read game data from '" IDS_HR1_EXE_1 "'");
|
||||
|
||||
stream->seek(getLanguage() == Common::FR_FRA ? 0x900 : 0x3800);
|
||||
loadWords(*stream, _verbs, _priVerbs);
|
||||
|
||||
stream->seek(0xf00);
|
||||
loadWords(*stream, _nouns, _priNouns);
|
||||
}
|
||||
|
||||
void HiRes1Engine::initGameState() {
|
||||
_state.vars.resize(IDI_HR1_NUM_VARS);
|
||||
|
||||
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
|
||||
|
||||
// Load room data from executable
|
||||
_roomDesc.clear();
|
||||
stream->seek(IDI_HR1_OFS_ROOMS);
|
||||
for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) {
|
||||
Room room;
|
||||
stream->readByte();
|
||||
_roomDesc.push_back(stream->readByte());
|
||||
for (uint j = 0; j < 6; ++j)
|
||||
room.connections[j] = stream->readByte();
|
||||
room.picture = stream->readByte();
|
||||
room.curPicture = stream->readByte();
|
||||
_state.rooms.push_back(room);
|
||||
}
|
||||
|
||||
// Load item data from executable
|
||||
stream->seek(IDI_HR1_OFS_ITEMS);
|
||||
byte id;
|
||||
while ((id = stream->readByte()) != 0xff) {
|
||||
Item item;
|
||||
|
||||
item.id = id;
|
||||
item.noun = stream->readByte();
|
||||
item.room = stream->readByte();
|
||||
item.picture = stream->readByte();
|
||||
item.isShape = stream->readByte();
|
||||
item.position.x = stream->readByte();
|
||||
item.position.y = stream->readByte();
|
||||
item.state = stream->readByte();
|
||||
item.description = stream->readByte();
|
||||
|
||||
stream->readByte();
|
||||
|
||||
byte size = stream->readByte();
|
||||
|
||||
for (uint i = 0; i < size; ++i)
|
||||
item.roomPictures.push_back(stream->readByte());
|
||||
|
||||
_state.items.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes1Engine::restartGame() {
|
||||
_display->printString(_strings.pressReturn);
|
||||
initState();
|
||||
_display->printAsciiString(_strings.lineFeeds);
|
||||
}
|
||||
|
||||
void HiRes1Engine::printString(const Common::String &str) {
|
||||
Common::String wrap = str;
|
||||
wordWrap(wrap);
|
||||
_display->printString(wrap);
|
||||
|
||||
if (_messageDelay)
|
||||
delay(getLanguage() == Common::FR_FRA ? 2900 : 2250);
|
||||
}
|
||||
|
||||
Common::String HiRes1Engine::loadMessage(uint idx) const {
|
||||
const char returnChar = _display->asciiToNative('\r');
|
||||
Common::StreamPtr stream(_messages[idx]->createReadStream());
|
||||
return readString(*stream, returnChar) + returnChar;
|
||||
}
|
||||
|
||||
void HiRes1Engine::printMessage(uint idx) {
|
||||
// In the English version, messages with hardcoded overrides don't delay
|
||||
// after printing. It's unclear if this is a bug or not. In most cases
|
||||
// the slow drawing of the room will give the player a chance to read
|
||||
// it. This isn't the case in ScummVM however, so we add a delay after
|
||||
// these messages.
|
||||
|
||||
// In the French version, messages with hardcoded overrides delay
|
||||
// based on string length. This leads to overly long delays on longer
|
||||
// strings. This might be a bug, since other messages have a fixed
|
||||
// delay (that is slightly longer than the English version).
|
||||
// We've chosen to stick with fixed delays here as well.
|
||||
|
||||
// NOTE: Later games wait for a key when the text window overflows and
|
||||
// don't use delays. It might be better to use that system for this game
|
||||
// as well.
|
||||
switch (idx) {
|
||||
case IDI_HR1_MSG_CANT_GO_THERE:
|
||||
_display->printString(_gameStrings.cantGoThere);
|
||||
break;
|
||||
case IDI_HR1_MSG_DONT_HAVE_IT:
|
||||
_display->printString(_gameStrings.dontHaveIt);
|
||||
break;
|
||||
case IDI_HR1_MSG_DONT_UNDERSTAND:
|
||||
_display->printString(_gameStrings.dontUnderstand);
|
||||
break;
|
||||
case IDI_HR1_MSG_GETTING_DARK:
|
||||
_display->printString(_gameStrings.gettingDark);
|
||||
break;
|
||||
default:
|
||||
return printString(loadMessage(idx));
|
||||
}
|
||||
|
||||
delay(1500);
|
||||
}
|
||||
|
||||
void HiRes1Engine::drawItems() {
|
||||
uint dropped = 0;
|
||||
|
||||
for (auto &item : _state.items) {
|
||||
// Skip items not in this room
|
||||
if (item.room != _state.room)
|
||||
continue;
|
||||
|
||||
if (item.state == IDI_ITEM_DROPPED) {
|
||||
// Draw dropped item if in normal view
|
||||
if (getCurRoom().picture == getCurRoom().curPicture)
|
||||
drawItem(item, _itemOffsets[dropped++]);
|
||||
} else {
|
||||
// Draw fixed item if current view is in the pic list
|
||||
for (const auto &pic : item.roomPictures) {
|
||||
if (pic == getCurRoom().curPicture) {
|
||||
drawItem(item, item.position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes1Engine::drawItem(Item &item, const Common::Point &pos) {
|
||||
if (item.isShape) {
|
||||
Common::StreamPtr stream(_corners[item.picture - 1]->createReadStream());
|
||||
Common::Point p(pos);
|
||||
_graphics->drawShape(*stream, p);
|
||||
} else
|
||||
drawPic(item.picture, pos);
|
||||
}
|
||||
|
||||
void HiRes1Engine::loadRoom(byte roomNr) {
|
||||
_roomData.description = loadMessage(_roomDesc[_state.room - 1]);
|
||||
}
|
||||
|
||||
void HiRes1Engine::showRoom() {
|
||||
_state.curPicture = getCurRoom().curPicture;
|
||||
_graphics->clearScreen();
|
||||
loadRoom(_state.room);
|
||||
|
||||
if (!_state.isDark) {
|
||||
drawPic(getCurRoom().curPicture);
|
||||
drawItems();
|
||||
}
|
||||
|
||||
_display->renderGraphics();
|
||||
_messageDelay = false;
|
||||
printString(_roomData.description);
|
||||
_messageDelay = true;
|
||||
}
|
||||
|
||||
void HiRes1Engine::wordWrap(Common::String &str) const {
|
||||
uint end = 39;
|
||||
|
||||
const char spaceChar = _display->asciiToNative(' ');
|
||||
const char returnChar = _display->asciiToNative('\r');
|
||||
|
||||
while (1) {
|
||||
if (str.size() <= end)
|
||||
return;
|
||||
|
||||
while (str[end] != spaceChar)
|
||||
--end;
|
||||
|
||||
str.setChar(returnChar, end);
|
||||
end += 40;
|
||||
}
|
||||
}
|
||||
|
||||
class HiRes1Engine_VF : public HiRes1Engine {
|
||||
public:
|
||||
HiRes1Engine_VF(OSystem *syst, const AdlGameDescription *gd) :
|
||||
HiRes1Engine(syst, gd) { }
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void runIntro() override;
|
||||
void getInput(uint &verb, uint &noun) override;
|
||||
};
|
||||
|
||||
void HiRes1Engine_VF::getInput(uint &verb, uint &noun) {
|
||||
// This version has a modified "parser"
|
||||
while (1) {
|
||||
_display->printString(_strings.enterCommand);
|
||||
const Common::String line = getLine();
|
||||
|
||||
if (shouldQuit() || _isRestoring)
|
||||
return;
|
||||
|
||||
uint index = 0;
|
||||
Common::String verbString = getWord(line, index);
|
||||
|
||||
if (!_verbs.contains(verbString)) {
|
||||
// If the verb is not found and it looks like an imperative, try to build the infinitive
|
||||
const size_t ezPos = verbString.find("\xc5\xda"); // "EZ"
|
||||
|
||||
bool found = false;
|
||||
|
||||
if (ezPos != verbString.npos) {
|
||||
const char *suf[] = { "\xc5\xd2", "\xc9\xd2", "\xd2\xc5", nullptr }; // "ER", "IR", "RE"
|
||||
|
||||
for (uint i = 0; suf[i]; ++i) {
|
||||
verbString.replace(ezPos, 2, suf[i]);
|
||||
if (_verbs.contains(verbString)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
_display->printString(formatVerbError(verbString));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
verb = _verbs[verbString];
|
||||
|
||||
while (1) {
|
||||
// Go over all nouns to find one we know. At the end of the string,
|
||||
// it will always match the empty word (which is in the noun list).
|
||||
|
||||
// The original has a code path to return a noun error here, but
|
||||
// it appears to be non-functional.
|
||||
const Common::String nounString = getWord(line, index);
|
||||
|
||||
if (_nouns.contains(nounString)) {
|
||||
noun = _nouns[nounString];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes1Engine_VF::runIntro() {
|
||||
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
|
||||
|
||||
stream->seek(0x1000);
|
||||
|
||||
// Title image is one padding byte short, so we first read it into a buffer
|
||||
const uint frameBufferSize = 0x2000;
|
||||
byte *const frameBuffer = (byte *)malloc(frameBufferSize);
|
||||
|
||||
if (stream->read(frameBuffer, frameBufferSize - 1) < frameBufferSize - 1)
|
||||
error("Failed to read title image");
|
||||
|
||||
// Set missing byte
|
||||
frameBuffer[frameBufferSize - 1] = 0;
|
||||
Common::MemoryReadStream frameBufferStream(frameBuffer, frameBufferSize, DisposeAfterUse::YES);
|
||||
|
||||
_display->setMode(Display::kModeGraphics);
|
||||
static_cast<Display_A2 *>(_display)->loadFrameBuffer(frameBufferStream);
|
||||
_display->renderGraphics();
|
||||
|
||||
_display->setMode(Display::kModeMixed);
|
||||
|
||||
Common::String str = readStringAt(*stream, 0xf);
|
||||
|
||||
while (1) {
|
||||
_display->printString(str);
|
||||
const char key = inputKey();
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
if (key == _display->asciiToNative('M')) {
|
||||
stream->seek(0x75);
|
||||
_display->sayText("M", Common::TextToSpeechManager::INTERRUPT);
|
||||
showInstructions(*stream);
|
||||
return;
|
||||
} else if (key == _display->asciiToNative('J')) {
|
||||
_display->sayText("J", Common::TextToSpeechManager::INTERRUPT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
if (gd->version == GAME_VER_HR1_VF2)
|
||||
return new HiRes1Engine_VF(syst, gd);
|
||||
|
||||
return new HiRes1Engine(syst, gd);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
187
engines/adl/hires2.cpp
Normal file
187
engines/adl/hires2.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
/* 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 "common/system.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "adl/adl_v2.h"
|
||||
#include "adl/display_a2.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/disk.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class HiResBaseEngine : public AdlEngine_v2 {
|
||||
public:
|
||||
HiResBaseEngine(OSystem *syst, const AdlGameDescription *gd, const byte numRooms, const byte numMsgs, const byte numItemPics);
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
|
||||
const byte _numRooms, _numMsgs, _numItemPics;
|
||||
};
|
||||
|
||||
HiResBaseEngine::HiResBaseEngine(OSystem *syst, const AdlGameDescription *gd, const byte numRooms, const byte numMsgs, const byte numItemPics) :
|
||||
AdlEngine_v2(syst, gd),
|
||||
_numRooms(numRooms),
|
||||
_numMsgs(numMsgs),
|
||||
_numItemPics(numItemPics) {
|
||||
|
||||
_messageIds.cantGoThere = 110;
|
||||
_messageIds.dontUnderstand = 112;
|
||||
_messageIds.itemDoesntMove = 114;
|
||||
_messageIds.itemNotHere = 115;
|
||||
_messageIds.thanksForPlaying = 113;
|
||||
}
|
||||
|
||||
void HiResBaseEngine::init() {
|
||||
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
|
||||
|
||||
_disk = new Common::DiskImage();
|
||||
if (!_disk->open(getDiskImageName(0)))
|
||||
error("Failed to open disk image '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
_disk->setSectorLimit(13);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
|
||||
loadMessages(*stream, _numMsgs);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 25, 13));
|
||||
Common::StringArray exeStrings;
|
||||
extractExeStrings(*stream, 0x1566, exeStrings);
|
||||
mapExeStrings(exeStrings);
|
||||
|
||||
// Heuristic to test for early versions that differ slightly
|
||||
// Later versions have two additional strings for "INIT DISK"
|
||||
const bool oldEngine = exeStrings.size() < 13;
|
||||
|
||||
if (!oldEngine) {
|
||||
stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7));
|
||||
_strings_v2.time = readString(*stream, 0xff);
|
||||
}
|
||||
|
||||
// Load global picture data
|
||||
stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0));
|
||||
loadPictures(*stream);
|
||||
|
||||
// Load item picture data
|
||||
stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05));
|
||||
loadItemPictures(*stream, _numItemPics);
|
||||
|
||||
// Load commands from executable
|
||||
stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 4));
|
||||
readCommands(*stream, _roomCommands);
|
||||
|
||||
stream.reset(_disk->createReadStream((oldEngine ? 0x19 : 0x1f), 0x7, 0x00, 3));
|
||||
readCommands(*stream, _globalCommands);
|
||||
|
||||
// Load dropped item offsets
|
||||
stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15));
|
||||
loadDroppedItemOffsets(*stream, 16);
|
||||
|
||||
// Load verbs
|
||||
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3));
|
||||
loadWords(*stream, _verbs, _priVerbs);
|
||||
|
||||
// Load nouns
|
||||
stream.reset(_disk->createReadStream(0x22, 0x2, 0x00, 7));
|
||||
loadWords(*stream, _nouns, _priNouns);
|
||||
}
|
||||
|
||||
void HiResBaseEngine::initGameState() {
|
||||
_state.vars.resize(40);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7));
|
||||
loadRooms(*stream, _numRooms);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2));
|
||||
loadItems(*stream);
|
||||
}
|
||||
|
||||
class HiRes2Engine : public HiResBaseEngine {
|
||||
public:
|
||||
HiRes2Engine(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void runIntro() override;
|
||||
};
|
||||
|
||||
HiRes2Engine::HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) :
|
||||
HiResBaseEngine(syst, gd, 135, 255, 38) {
|
||||
|
||||
_messageIds.cantGoThere = 123;
|
||||
_messageIds.dontUnderstand = 19;
|
||||
_messageIds.itemDoesntMove = 242;
|
||||
_messageIds.itemNotHere = 4;
|
||||
_messageIds.thanksForPlaying = 239;
|
||||
}
|
||||
|
||||
void HiRes2Engine::runIntro() {
|
||||
// Only the Green Valley version has a title screen
|
||||
if (_disk->getSectorsPerTrack() != 16)
|
||||
return;
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1));
|
||||
|
||||
_display->setMode(Display::kModeText);
|
||||
|
||||
Common::String str = readString(*stream);
|
||||
|
||||
if (stream->eos() || stream->err())
|
||||
error("Error reading disk image");
|
||||
|
||||
_display->printString(str);
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
class HiRes3Engine : public HiResBaseEngine {
|
||||
public:
|
||||
HiRes3Engine(OSystem *syst, const AdlGameDescription *gd);
|
||||
};
|
||||
|
||||
HiRes3Engine::HiRes3Engine(OSystem *syst, const AdlGameDescription *gd) :
|
||||
HiResBaseEngine(syst, gd, 138, 255, 36) {
|
||||
|
||||
const byte brokenRooms[] = { 18, 24, 54, 98, 102, 108 };
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(brokenRooms); ++i)
|
||||
_brokenRooms.push_back(brokenRooms[i]);
|
||||
}
|
||||
|
||||
Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
return new HiRes2Engine(syst, gd);
|
||||
}
|
||||
|
||||
Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
return new HiResBaseEngine(syst, gd, 43, 142, 2);
|
||||
}
|
||||
|
||||
Engine *HiRes3Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
return new HiRes3Engine(syst, gd);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
822
engines/adl/hires4.cpp
Normal file
822
engines/adl/hires4.cpp
Normal file
@@ -0,0 +1,822 @@
|
||||
/* 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 "common/system.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/events.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "adl/adl_v3.h"
|
||||
#include "adl/detection.h"
|
||||
#include "adl/display_a2.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/disk.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
#define IDI_HR4_NUM_ROOMS 164
|
||||
#define IDI_HR4_NUM_MESSAGES 255
|
||||
#define IDI_HR4_NUM_VARS 40
|
||||
#define IDI_HR4_NUM_ITEM_DESCS 44
|
||||
#define IDI_HR4_NUM_ITEM_PICS 41
|
||||
#define IDI_HR4_NUM_ITEM_OFFSETS 16
|
||||
|
||||
// Messages used outside of scripts
|
||||
#define IDI_HR4_MSG_CANT_GO_THERE 110
|
||||
#define IDI_HR4_MSG_DONT_UNDERSTAND 112
|
||||
#define IDI_HR4_MSG_ITEM_DOESNT_MOVE 114
|
||||
#define IDI_HR4_MSG_ITEM_NOT_HERE 115
|
||||
#define IDI_HR4_MSG_THANKS_FOR_PLAYING 113
|
||||
|
||||
class HiRes4BaseEngine : public AdlEngine_v3 {
|
||||
public:
|
||||
HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd);
|
||||
~HiRes4BaseEngine() override;
|
||||
|
||||
protected:
|
||||
// AdlEngine
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
|
||||
Common::DiskImage *_boot;
|
||||
};
|
||||
|
||||
HiRes4BaseEngine::HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v3(syst, gd),
|
||||
_boot(nullptr) {
|
||||
|
||||
_brokenRooms.push_back(121);
|
||||
_messageIds.cantGoThere = IDI_HR4_MSG_CANT_GO_THERE;
|
||||
_messageIds.dontUnderstand = IDI_HR4_MSG_DONT_UNDERSTAND;
|
||||
_messageIds.itemDoesntMove = IDI_HR4_MSG_ITEM_DOESNT_MOVE;
|
||||
_messageIds.itemNotHere = IDI_HR4_MSG_ITEM_NOT_HERE;
|
||||
_messageIds.thanksForPlaying = IDI_HR4_MSG_THANKS_FOR_PLAYING;
|
||||
}
|
||||
|
||||
HiRes4BaseEngine::~HiRes4BaseEngine() {
|
||||
delete _boot;
|
||||
}
|
||||
|
||||
void HiRes4BaseEngine::init() {
|
||||
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
|
||||
|
||||
_boot = new Common::DiskImage();
|
||||
if (!_boot->open(getDiskImageName(0)))
|
||||
error("Failed to open disk image '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
insertDisk(1);
|
||||
}
|
||||
|
||||
void HiRes4BaseEngine::initGameState() {
|
||||
_state.vars.resize(IDI_HR4_NUM_VARS);
|
||||
}
|
||||
|
||||
class HiRes4Engine_v1_0 : public HiRes4BaseEngine {
|
||||
public:
|
||||
HiRes4Engine_v1_0(OSystem *syst, const AdlGameDescription *gd) : HiRes4BaseEngine(syst, gd) { }
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void runIntro() override;
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
};
|
||||
|
||||
void HiRes4Engine_v1_0::runIntro() {
|
||||
Common::StreamPtr stream(_boot->createReadStream(0x06, 0x3, 0xb9, 1));
|
||||
|
||||
_display->setMode(Display::kModeText);
|
||||
|
||||
Common::String str = readString(*stream);
|
||||
|
||||
if (stream->eos() || stream->err())
|
||||
error("Error reading disk image");
|
||||
|
||||
_display->printString(str);
|
||||
|
||||
waitKey(0, Common::KEYCODE_RETURN);
|
||||
}
|
||||
|
||||
void HiRes4Engine_v1_0::init() {
|
||||
HiRes4BaseEngine::init();
|
||||
|
||||
Common::StreamPtr stream(_boot->createReadStream(0x9, 0x1, 0x00, 13));
|
||||
Common::StringArray exeStrings;
|
||||
extractExeStrings(*stream, 0x1566, exeStrings);
|
||||
mapExeStrings(exeStrings);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x0e, 0x5, 0x00, 3, 13));
|
||||
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x09, 0x0, 0x80, 0, 13));
|
||||
loadPictures(*stream);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x0d, 0xc, 0x05, 0, 13));
|
||||
loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x07, 0x0, 0x15, 2, 13));
|
||||
loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x0c, 0x9, 0xa5, 5, 13));
|
||||
readCommands(*stream, _roomCommands);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x07, 0xc, 0x00, 3, 13));
|
||||
readCommands(*stream, _globalCommands);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x0a, 0x7, 0x15, 0, 13));
|
||||
loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x08, 0x3, 0x00, 3, 13));
|
||||
loadWords(*stream, _verbs, _priVerbs, 80); // Missing terminator
|
||||
|
||||
stream.reset(_boot->createReadStream(0x05, 0x7, 0x00, 6, 13));
|
||||
loadWords(*stream, _nouns, _priNouns, 109); // Missing terminator
|
||||
}
|
||||
|
||||
void HiRes4Engine_v1_0::initGameState() {
|
||||
HiRes4BaseEngine::initGameState();
|
||||
|
||||
Common::StreamPtr stream(_boot->createReadStream(0x04, 0xa, 0x0e, 9, 13));
|
||||
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
|
||||
|
||||
stream.reset(_boot->createReadStream(0x04, 0x5, 0x00, 12, 13));
|
||||
loadItems(*stream);
|
||||
}
|
||||
|
||||
class HiRes4Engine_v1_1 : public HiRes4BaseEngine {
|
||||
public:
|
||||
HiRes4Engine_v1_1(OSystem *syst, const AdlGameDescription *gd) : HiRes4BaseEngine(syst, gd) { }
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void runIntro() override;
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
};
|
||||
|
||||
// TODO: It might be worth replacing this with a more generic variant that
|
||||
// can be used in both hires4 and hires6
|
||||
static Common::MemoryReadStream *readSkewedSectors(Common::DiskImage *disk, byte track, byte sector, byte count) {
|
||||
const uint bytesPerSector = disk->getBytesPerSector();
|
||||
const uint sectorsPerTrack = disk->getSectorsPerTrack();
|
||||
const uint bufSize = count * bytesPerSector;
|
||||
byte *const buf = (byte *)malloc(bufSize);
|
||||
byte *p = buf;
|
||||
|
||||
while (count-- != 0) {
|
||||
Common::StreamPtr stream(disk->createReadStream(track, sector));
|
||||
stream->read(p, bytesPerSector);
|
||||
|
||||
if (stream->err() || stream->eos())
|
||||
error("Error loading from disk image");
|
||||
|
||||
p += bytesPerSector;
|
||||
sector += 5;
|
||||
sector %= sectorsPerTrack;
|
||||
if (sector == 0)
|
||||
++track;
|
||||
}
|
||||
|
||||
return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
static Common::MemoryReadStream *decodeData(Common::SeekableReadStream &stream, const uint startOffset, uint endOffset, const byte xorVal) {
|
||||
assert(stream.size() >= 0);
|
||||
|
||||
uint streamSize(stream.size());
|
||||
|
||||
if (endOffset > streamSize)
|
||||
endOffset = streamSize;
|
||||
|
||||
byte *const buf = (byte *)malloc(streamSize);
|
||||
stream.read(buf, streamSize);
|
||||
|
||||
if (stream.err() || stream.eos())
|
||||
error("Failed to read data for decoding");
|
||||
|
||||
for (uint i = startOffset; i < endOffset; ++i)
|
||||
buf[i] ^= xorVal;
|
||||
|
||||
return new Common::MemoryReadStream(buf, streamSize, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
void HiRes4Engine_v1_1::runIntro() {
|
||||
Common::ScopedPtr<Common::Files_AppleDOS> files(new Common::Files_AppleDOS());
|
||||
files->open(getDiskImageName(0));
|
||||
|
||||
Common::StreamPtr menu(files->createReadStream("\b\b\b\b\b\b\bULYSSES\r(C) 1982"));
|
||||
menu->seek(0x2eb);
|
||||
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
const int16 y[4] = { 0, 2, 4, 16 };
|
||||
Common::String s = menu->readString(0, 39);
|
||||
_display->moveCursorTo(Common::Point(0, y[i]));
|
||||
_display->printString(s);
|
||||
}
|
||||
|
||||
waitKey(3000);
|
||||
}
|
||||
|
||||
void HiRes4Engine_v1_1::init() {
|
||||
HiRes4BaseEngine::init();
|
||||
|
||||
Common::StreamPtr stream(readSkewedSectors(_boot, 0x05, 0x6, 1));
|
||||
_strings.verbError = readStringAt(*stream, 0x4f);
|
||||
_strings.nounError = readStringAt(*stream, 0x8e);
|
||||
_strings.enterCommand = readStringAt(*stream, 0xbc);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x05, 0x3, 1));
|
||||
stream->skip(0xd7);
|
||||
_strings_v2.time = readString(*stream, 0xff);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x05, 0x7, 2));
|
||||
_strings.lineFeeds = readStringAt(*stream, 0xf8);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x06, 0xf, 3));
|
||||
_strings_v2.saveInsert = readStringAt(*stream, 0x5f);
|
||||
_strings_v2.saveReplace = readStringAt(*stream, 0xe5);
|
||||
_strings_v2.restoreInsert = readStringAt(*stream, 0x132);
|
||||
_strings_v2.restoreReplace = readStringAt(*stream, 0x1c2);
|
||||
_strings.playAgain = readStringAt(*stream, 0x225);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x0a, 0x0, 5));
|
||||
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x05, 0x2, 1));
|
||||
stream->skip(0x80);
|
||||
loadPictures(*stream);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x09, 0x2, 1));
|
||||
stream->skip(0x05);
|
||||
loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x04, 0x0, 3));
|
||||
stream->skip(0x15);
|
||||
loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x08, 0x2, 6));
|
||||
stream->skip(0xa5);
|
||||
readCommands(*stream, _roomCommands);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x04, 0xc, 4));
|
||||
stream.reset(decodeData(*stream, 0x218, 0x318, 0xee));
|
||||
readCommands(*stream, _globalCommands);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x06, 0x6, 1));
|
||||
stream->skip(0x15);
|
||||
loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x05, 0x0, 4));
|
||||
loadWords(*stream, _verbs, _priVerbs);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x0b, 0xb, 7));
|
||||
loadWords(*stream, _nouns, _priNouns);
|
||||
}
|
||||
|
||||
void HiRes4Engine_v1_1::initGameState() {
|
||||
HiRes4BaseEngine::initGameState();
|
||||
|
||||
Common::StreamPtr stream(readSkewedSectors(_boot, 0x0b, 0x9, 10));
|
||||
stream->skip(0x0e);
|
||||
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
|
||||
|
||||
stream.reset(readSkewedSectors(_boot, 0x0b, 0x0, 13));
|
||||
stream.reset(decodeData(*stream, 0x43, 0x143, 0x91));
|
||||
loadItems(*stream);
|
||||
}
|
||||
|
||||
class HiRes4Engine_LNG : public HiRes4Engine_v1_1 {
|
||||
public:
|
||||
HiRes4Engine_LNG(OSystem *syst, const AdlGameDescription *gd) : HiRes4Engine_v1_1(syst, gd) { }
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void runIntro() override;
|
||||
|
||||
void putSpace(uint x, uint y) const;
|
||||
void drawChar(byte c, Common::SeekableReadStream &shapeTable, Common::Point &pos) const;
|
||||
void drawText(const Common::String &str, Common::SeekableReadStream &shapeTable, const float ht, const float vt) const;
|
||||
|
||||
void runIntroLogo(Common::SeekableReadStream &ms2);
|
||||
void runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2);
|
||||
void runIntroInstructions(Common::SeekableReadStream &instructions);
|
||||
void runIntroLoading(Common::SeekableReadStream &adventure);
|
||||
|
||||
static const uint kClock = 1022727; // Apple II CPU clock rate
|
||||
};
|
||||
|
||||
void HiRes4Engine_LNG::putSpace(uint x, uint y) const {
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
_display->moveCursorTo(Common::Point(x, y));
|
||||
_display->printChar(' ');
|
||||
_display->renderText();
|
||||
delay(2);
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::drawChar(byte c, Common::SeekableReadStream &shapeTable, Common::Point &pos) const {
|
||||
shapeTable.seek(0);
|
||||
byte entries = shapeTable.readByte();
|
||||
|
||||
if (c >= entries)
|
||||
error("Character %d is not in the shape table", c);
|
||||
|
||||
shapeTable.seek(c * 2 + 2);
|
||||
uint16 offset = shapeTable.readUint16LE();
|
||||
|
||||
shapeTable.seek(offset);
|
||||
|
||||
_graphics->drawShape(shapeTable, pos);
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::drawText(const Common::String &str, Common::SeekableReadStream &shapeTable, const float ht, const float vt) const {
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
Common::Point pos((int16)(ht * 7), (int16)(vt * 7.7f));
|
||||
|
||||
drawChar(99, shapeTable, pos);
|
||||
|
||||
for (uint i = 0; i < str.size(); ++i) {
|
||||
const byte c = str[i] - 32;
|
||||
|
||||
drawChar(c, shapeTable, pos);
|
||||
drawChar(98, shapeTable, pos);
|
||||
|
||||
_display->renderGraphics();
|
||||
delay(15);
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::runIntroLogo(Common::SeekableReadStream &ms2) {
|
||||
Display_A2 *display = static_cast<Display_A2 *>(_display);
|
||||
const uint width = display->getGfxWidth();
|
||||
const uint height = display->getGfxHeight();
|
||||
const uint pitch = display->getGfxPitch();
|
||||
|
||||
display->clear(0x00);
|
||||
display->setMode(Display::kModeGraphics);
|
||||
byte *logo = new byte[pitch * height];
|
||||
display->loadFrameBuffer(ms2, logo);
|
||||
|
||||
for (uint x = 0; x < width; ++x) {
|
||||
for (uint y = 0; y < height; ++y) {
|
||||
const byte p = logo[y * pitch + x / 7];
|
||||
display->setPixelBit(Common::Point(x, y), p);
|
||||
if (x % 7 == 6)
|
||||
display->setPixelPalette(Common::Point(x, y), p);
|
||||
}
|
||||
display->renderGraphics();
|
||||
|
||||
if (shouldQuit()) {
|
||||
delete[] logo;
|
||||
return;
|
||||
}
|
||||
|
||||
delay(7);
|
||||
}
|
||||
|
||||
delete[] logo;
|
||||
|
||||
for (uint i = 38; i != 0; --i) {
|
||||
Common::Point p;
|
||||
|
||||
for (p.y = 1; p.y < (int)height; ++p.y)
|
||||
for (p.x = 0; p.x < (int)width; p.x += 7)
|
||||
display->setPixelByte(Common::Point(p.x, p.y - 1), display->getPixelByte(p));
|
||||
|
||||
display->renderGraphics();
|
||||
|
||||
Tones tone;
|
||||
tone.push_back(Tone(kClock / 2.0 / ((i * 4 + 1) * 10.0 + 10.0), 12.5));
|
||||
playTones(tone, false, false);
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2) {
|
||||
ms2.seek(0x2290);
|
||||
Common::StreamPtr shapeTable(ms2.readStream(0x450));
|
||||
if (ms2.err() || ms2.eos())
|
||||
error("Failed to read shape table");
|
||||
|
||||
Common::String titleString(readStringAt(menu, 0x1f5, '"'));
|
||||
drawText(titleString, *shapeTable, 4.0f, 22.5f);
|
||||
|
||||
titleString = readStringAt(menu, 0x22b, '"');
|
||||
drawText(titleString, *shapeTable, 5.0f, 24.0f);
|
||||
|
||||
// Draw "TM" with lines
|
||||
_graphics->drawLine(Common::Point(200, 170), Common::Point(200, 174), 0x7f);
|
||||
_graphics->drawLine(Common::Point(198, 170), Common::Point(202, 170), 0x7f);
|
||||
_display->renderGraphics();
|
||||
delay(7);
|
||||
_graphics->drawLine(Common::Point(204, 170), Common::Point(204, 174), 0x7f);
|
||||
_graphics->drawLine(Common::Point(204, 170), Common::Point(207, 173), 0x7f);
|
||||
_graphics->drawLine(Common::Point(207, 173), Common::Point(209, 170), 0x7f);
|
||||
_graphics->drawLine(Common::Point(209, 170), Common::Point(209, 174), 0x7f);
|
||||
_display->renderGraphics();
|
||||
delay(7);
|
||||
|
||||
titleString = readStringAt(menu, 0x46c);
|
||||
drawText(titleString, *shapeTable, 20.0f - titleString.size() / 2.0f, 10.6f);
|
||||
|
||||
titleString = readStringAt(menu, 0x490);
|
||||
drawText(titleString, *shapeTable, 20.0f - titleString.size() / 2.0f, 11.8f);
|
||||
|
||||
Common::StringArray menuStrings;
|
||||
menuStrings.push_back(readStringAt(menu, 0x515));
|
||||
menuStrings.push_back(readStringAt(menu, 0x52b));
|
||||
|
||||
for (uint i = 0; i < menuStrings.size(); ++i)
|
||||
drawText(Common::String::format("%d) ", i + 1) + menuStrings[i], *shapeTable, 12.5f, 14.0f + i * 1.2f);
|
||||
|
||||
titleString = readStringAt(menu, 0x355, '"');
|
||||
drawText(titleString, *shapeTable, 12.5f, 14.0f + menuStrings.size() * 1.2f + 2.0f);
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::runIntroInstructions(Common::SeekableReadStream &instructions) {
|
||||
Common::String line;
|
||||
Common::String pressKey(readStringAt(instructions, 0xad6, '"'));
|
||||
instructions.seek(0);
|
||||
|
||||
_display->home();
|
||||
_display->setMode(Display::kModeText);
|
||||
|
||||
// Search for PRINT commands in tokenized BASIC
|
||||
while (1) {
|
||||
char c;
|
||||
|
||||
do {
|
||||
c = instructions.readByte();
|
||||
|
||||
if (instructions.err() || instructions.eos())
|
||||
error("Error reading instructions file");
|
||||
|
||||
// GOSUB (calls "press any key" routine)
|
||||
if (c == (char)0xb0) {
|
||||
_display->moveCursorTo(Common::Point(6, 23));
|
||||
_display->printAsciiString(pressKey);
|
||||
inputKey();
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
_display->home();
|
||||
}
|
||||
} while (c != (char)0xba); // PRINT
|
||||
|
||||
uint quotes = 0;
|
||||
while (1) {
|
||||
c = instructions.readByte();
|
||||
|
||||
if (instructions.err() || instructions.eos())
|
||||
error("Error reading instructions file");
|
||||
|
||||
if (c == '"') {
|
||||
++quotes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
if (quotes == 1)
|
||||
line += c;
|
||||
else if (c == ':') // Separator
|
||||
break;
|
||||
else if (c == '4') // CTRL-D before "RUN MENU"
|
||||
return;
|
||||
};
|
||||
|
||||
line += '\r';
|
||||
_display->printAsciiString(line);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::runIntroLoading(Common::SeekableReadStream &adventure) {
|
||||
_display->home();
|
||||
_display->setMode(Display::kModeText);
|
||||
|
||||
const uint kStrings = 4;
|
||||
const uint kStringLen = 39;
|
||||
char text[kStrings][kStringLen];
|
||||
|
||||
adventure.seek(0x2eb);
|
||||
|
||||
if (adventure.read(text, sizeof(text)) < sizeof(text))
|
||||
error("Failed to read loading screen text");
|
||||
|
||||
const uint yPos[kStrings] = { 2, 19, 8, 22 };
|
||||
|
||||
for (uint i = 0; i < kStrings; ++i) {
|
||||
_display->moveCursorTo(Common::Point(0, yPos[i]));
|
||||
_display->printString(Common::String(text[i], kStringLen));
|
||||
}
|
||||
|
||||
waitKey(3000);
|
||||
}
|
||||
|
||||
void HiRes4Engine_LNG::runIntro() {
|
||||
Common::ScopedPtr<Common::Files_AppleDOS> files(new Common::Files_AppleDOS());
|
||||
files->open(getDiskImageName(0));
|
||||
|
||||
while (!shouldQuit()) {
|
||||
Common::StreamPtr menu(files->createReadStream("MENU"));
|
||||
|
||||
const bool oldVersion = files->exists("MS2");
|
||||
|
||||
if (oldVersion) {
|
||||
// Version 0.0
|
||||
Common::StreamPtr ms2(files->createReadStream("MS2"));
|
||||
runIntroLogo(*ms2);
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
_graphics->setBounds(Common::Rect(280, 192));
|
||||
|
||||
runIntroTitle(*menu, *ms2);
|
||||
_graphics->setBounds(Common::Rect(280, 160));
|
||||
} else {
|
||||
// Version 1.1
|
||||
// This version also has a publisher logo, but it uses BASIC
|
||||
// graphics routines that have not been implemented, so we skip it.
|
||||
|
||||
// File offset, x and y coordinates for each line of text in the title screen
|
||||
// We skip the "create data disk" menu option
|
||||
const uint text[][3] = { { 0x13, 4, 1 }, { 0x42, 7, 9 }, { 0x66, 7, 11 }, { 0xaa, 7, 17 } };
|
||||
|
||||
for (uint i = 0; i < ARRAYSIZE(text); ++i) {
|
||||
Common::String str = readStringAt(*menu, text[i][0], '"');
|
||||
for (char &c : str) {
|
||||
c = _display->asciiToNative(c);
|
||||
if (i == 0)
|
||||
c &= ~0xc0; // Invert first line
|
||||
}
|
||||
_display->moveCursorTo(Common::Point(text[i][1], text[i][2]));
|
||||
_display->printString(str);
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
const char key = inputKey(false);
|
||||
|
||||
if (shouldQuit())
|
||||
return;
|
||||
|
||||
if (key == _display->asciiToNative('1')) {
|
||||
Common::StreamPtr instructions(files->createReadStream("INSTRUCTIONS"));
|
||||
runIntroInstructions(*instructions);
|
||||
break;
|
||||
} else if (key == _display->asciiToNative('2')) {
|
||||
Common::StreamPtr adventure(files->createReadStream("THE ADVENTURE"));
|
||||
runIntroLoading(*adventure);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class HiRes4Engine_Atari : public AdlEngine_v3 {
|
||||
public:
|
||||
HiRes4Engine_Atari(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v3(syst, gd),
|
||||
_boot(nullptr),
|
||||
_curDisk(0) { _brokenRooms.push_back(121); }
|
||||
~HiRes4Engine_Atari() override;
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
void loadRoom(byte roomNr) override;
|
||||
Common::String formatVerbError(const Common::String &verb) const override;
|
||||
Common::String formatNounError(const Common::String &verb, const Common::String &noun) const override;
|
||||
|
||||
// AdlEngine_v2
|
||||
void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const override;
|
||||
|
||||
Common::SeekableReadStream *createReadStream(Common::DiskImage *disk, byte track, byte sector, byte offset = 0, byte size = 0) const;
|
||||
void loadCommonData();
|
||||
void insertDisk(byte diskNr);
|
||||
void rebindDisk();
|
||||
|
||||
Common::DiskImage *_boot;
|
||||
byte _curDisk;
|
||||
};
|
||||
|
||||
static const char *const atariDisks[] = { "ULYS1A.XFD", "ULYS1B.XFD", "ULYS2C.XFD" };
|
||||
|
||||
HiRes4Engine_Atari::~HiRes4Engine_Atari() {
|
||||
delete _boot;
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::init() {
|
||||
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
|
||||
|
||||
_boot = new Common::DiskImage();
|
||||
if (!_boot->open(atariDisks[0]))
|
||||
error("Failed to open disk image '%s'", atariDisks[0]);
|
||||
|
||||
insertDisk(1);
|
||||
loadCommonData();
|
||||
|
||||
Common::StreamPtr stream(createReadStream(_boot, 0x06, 0x2));
|
||||
_strings.verbError = readStringAt(*stream, 0x4f);
|
||||
_strings.nounError = readStringAt(*stream, 0x83);
|
||||
_strings.enterCommand = readStringAt(*stream, 0xa6);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x05, 0xb, 0xd7));
|
||||
_strings_v2.time = readString(*stream, 0xff);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x06, 0x7, 0x00, 2));
|
||||
_strings_v2.saveInsert = readStringAt(*stream, 0x62);
|
||||
_strings_v2.saveReplace = readStringAt(*stream, 0xdd);
|
||||
_strings_v2.restoreInsert = readStringAt(*stream, 0x12a);
|
||||
_strings_v2.restoreReplace = readStringAt(*stream, 0x1b8);
|
||||
_strings.playAgain = readStringAt(*stream, 0x21b);
|
||||
// TODO: restart sequence has "insert side a/b" strings
|
||||
|
||||
_messageIds.cantGoThere = IDI_HR4_MSG_CANT_GO_THERE;
|
||||
_messageIds.dontUnderstand = IDI_HR4_MSG_DONT_UNDERSTAND;
|
||||
_messageIds.itemDoesntMove = IDI_HR4_MSG_ITEM_DOESNT_MOVE;
|
||||
_messageIds.itemNotHere = IDI_HR4_MSG_ITEM_NOT_HERE;
|
||||
_messageIds.thanksForPlaying = IDI_HR4_MSG_THANKS_FOR_PLAYING;
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x06, 0xd, 0x12, 2));
|
||||
loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x07, 0x1, 0xf4));
|
||||
loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x08, 0xe, 0xa5, 5));
|
||||
readCommands(*stream, _roomCommands);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x0a, 0x9, 0x00, 3));
|
||||
readCommands(*stream, _globalCommands);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x05, 0x4, 0x00, 3));
|
||||
loadWords(*stream, _verbs, _priVerbs);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x03, 0xb, 0x00, 6));
|
||||
loadWords(*stream, _nouns, _priNouns);
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::loadRoom(byte roomNr) {
|
||||
if (roomNr >= 59 && roomNr < 113) {
|
||||
if (_curDisk != 2) {
|
||||
insertDisk(2);
|
||||
rebindDisk();
|
||||
}
|
||||
} else if (_curDisk != 1) {
|
||||
insertDisk(1);
|
||||
rebindDisk();
|
||||
}
|
||||
|
||||
AdlEngine_v3::loadRoom(roomNr);
|
||||
}
|
||||
|
||||
Common::String HiRes4Engine_Atari::formatVerbError(const Common::String &verb) const {
|
||||
Common::String err = _strings.verbError;
|
||||
for (uint i = 0; i < verb.size(); ++i)
|
||||
err.setChar(verb[i], i + 8);
|
||||
return err;
|
||||
}
|
||||
|
||||
Common::String HiRes4Engine_Atari::formatNounError(const Common::String &verb, const Common::String &noun) const {
|
||||
Common::String err = _strings.nounError;
|
||||
for (uint i = 0; i < verb.size(); ++i)
|
||||
err.setChar(verb[i], i + 8);
|
||||
for (uint i = 0; i < noun.size(); ++i)
|
||||
err.setChar(noun[i], i + 19);
|
||||
return err;
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::insertDisk(byte diskNr) {
|
||||
if (_curDisk == diskNr)
|
||||
return;
|
||||
|
||||
_curDisk = diskNr;
|
||||
|
||||
delete _disk;
|
||||
_disk = new Common::DiskImage();
|
||||
if (!_disk->open(atariDisks[diskNr]))
|
||||
error("Failed to open disk image '%s'", atariDisks[diskNr]);
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::rebindDisk() {
|
||||
// As room.data is bound to the Common::DiskImage, we need to rebind them here
|
||||
// We cannot simply reload the rooms as that would reset their state
|
||||
|
||||
// FIXME: Remove DataBlockPtr-Common::DiskImage coupling?
|
||||
|
||||
Common::StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
|
||||
for (uint i = 0; i < IDI_HR4_NUM_ROOMS; ++i) {
|
||||
stream->skip(7);
|
||||
_state.rooms[i].data = readDataBlockPtr(*stream);
|
||||
stream->skip(3);
|
||||
}
|
||||
|
||||
// Rebind data that is on both side B and C
|
||||
loadCommonData();
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::loadCommonData() {
|
||||
_messages.clear();
|
||||
Common::StreamPtr stream(createReadStream(_boot, 0x0a, 0x4, 0x00, 3));
|
||||
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
|
||||
|
||||
_pictures.clear();
|
||||
stream.reset(createReadStream(_boot, 0x05, 0xe, 0x80));
|
||||
loadPictures(*stream);
|
||||
|
||||
_itemPics.clear();
|
||||
stream.reset(createReadStream(_boot, 0x09, 0xe, 0x05));
|
||||
loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::initGameState() {
|
||||
_state.vars.resize(IDI_HR4_NUM_VARS);
|
||||
|
||||
Common::StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
|
||||
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
|
||||
|
||||
stream.reset(createReadStream(_boot, 0x02, 0xc, 0x00, 12));
|
||||
loadItems(*stream);
|
||||
|
||||
// FIXME
|
||||
_display->moveCursorTo(Common::Point(0, 23));
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *HiRes4Engine_Atari::createReadStream(Common::DiskImage *disk, byte track, byte sector, byte offset, byte size) const {
|
||||
adjustDataBlockPtr(track, sector, offset, size);
|
||||
return disk->createReadStream(track, sector, offset, size);
|
||||
}
|
||||
|
||||
void HiRes4Engine_Atari::adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const {
|
||||
// Convert the Apple II disk offsets in the game, to Atari disk offsets
|
||||
uint sectorIndex = (track * 16 + sector + 1) << 1;
|
||||
|
||||
// Atari uses 128 bytes per sector vs. 256 on the Apple II
|
||||
// Note that size indicates *additional* sectors to read after reading one sector
|
||||
size *= 2;
|
||||
|
||||
if (offset >= 128) {
|
||||
// Offset in the second half of an Apple II sector, skip one sector and adjust offset
|
||||
++sectorIndex;
|
||||
offset -= 128;
|
||||
} else {
|
||||
// Offset in the first half of an Apple II sector, we need to read one additional sector
|
||||
++size;
|
||||
}
|
||||
|
||||
// Compute track/sector for Atari's 18 sectors per track (sectorIndex is 1-based)
|
||||
track = (sectorIndex - 1) / 18;
|
||||
sector = (sectorIndex - 1) % 18;
|
||||
}
|
||||
|
||||
Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
switch (getPlatform(*gd)) {
|
||||
case Common::kPlatformApple2:
|
||||
switch (getGameVersion(*gd)) {
|
||||
case GAME_VER_HR4_LNG:
|
||||
return new HiRes4Engine_LNG(syst, gd);
|
||||
case GAME_VER_HR4_V1_1:
|
||||
return new HiRes4Engine_v1_1(syst, gd);
|
||||
default:
|
||||
return new HiRes4Engine_v1_0(syst, gd);
|
||||
}
|
||||
case Common::kPlatformAtari8Bit:
|
||||
return new HiRes4Engine_Atari(syst, gd);
|
||||
default:
|
||||
error("Unsupported platform");
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
405
engines/adl/hires5.cpp
Normal file
405
engines/adl/hires5.cpp
Normal file
@@ -0,0 +1,405 @@
|
||||
/* 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 "common/system.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "adl/adl_v4.h"
|
||||
#include "adl/detection.h"
|
||||
#include "adl/display_a2.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/disk.h"
|
||||
#include "adl/sound.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class HiRes5Engine : public AdlEngine_v4 {
|
||||
public:
|
||||
HiRes5Engine(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v4(syst, gd),
|
||||
_doAnimation(false) { }
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void setupOpcodeTables() override;
|
||||
void runIntro() override;
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
void applyRegionWorkarounds() override;
|
||||
void applyRoomWorkarounds(byte roomNr) override;
|
||||
Common::String getLine() override;
|
||||
|
||||
// AdlEngine_v4
|
||||
bool isInventoryFull() override;
|
||||
|
||||
void loadSong(Common::ReadStream &stream);
|
||||
void drawLight(uint index, byte color) const;
|
||||
void animateLights() const;
|
||||
|
||||
int o_checkItemTimeLimits(ScriptEnv &e);
|
||||
int o_startAnimation(ScriptEnv &e);
|
||||
int o_winGame(ScriptEnv &e);
|
||||
|
||||
static const uint kClock = 1022727; // Apple II CPU clock rate
|
||||
static const uint kRegions = 41;
|
||||
static const uint kItems = 69;
|
||||
|
||||
Common::Array<byte> _itemTimeLimits;
|
||||
Common::String _itemTimeLimitMsg;
|
||||
Tones _song;
|
||||
bool _doAnimation;
|
||||
|
||||
struct {
|
||||
Common::String itemTimeLimit;
|
||||
Common::String carryingTooMuch;
|
||||
} _gameStrings;
|
||||
};
|
||||
|
||||
Common::String HiRes5Engine::getLine() {
|
||||
if (_doAnimation) {
|
||||
animateLights();
|
||||
_doAnimation = false;
|
||||
}
|
||||
|
||||
return AdlEngine_v4::getLine();
|
||||
}
|
||||
|
||||
void HiRes5Engine::drawLight(uint index, byte color) const {
|
||||
Display_A2 *display = static_cast<Display_A2 *>(_display);
|
||||
const byte xCoord[5] = { 189, 161, 133, 105, 77 };
|
||||
const byte yCoord = 72;
|
||||
|
||||
assert(index < 5);
|
||||
|
||||
for (int yDelta = 0; yDelta < 4; ++yDelta)
|
||||
for (int xDelta = 0; xDelta < 7; ++xDelta)
|
||||
display->putPixel(Common::Point(xCoord[index] + xDelta, yCoord + yDelta), color);
|
||||
|
||||
display->renderGraphics();
|
||||
}
|
||||
|
||||
void HiRes5Engine::animateLights() const {
|
||||
// Skip this if we're running a debug script
|
||||
if (_inputScript)
|
||||
return;
|
||||
|
||||
int index;
|
||||
byte color = 0x2a;
|
||||
|
||||
for (index = 4; index >= 0; --index)
|
||||
drawLight(index, color);
|
||||
|
||||
index = 4;
|
||||
|
||||
while (!g_engine->shouldQuit()) {
|
||||
drawLight(index, color ^ 0x7f);
|
||||
|
||||
// There's a delay here in the original engine. We leave it out as
|
||||
// we're already slower than the original without any delay.
|
||||
|
||||
const uint kLoopCycles = 25;
|
||||
const byte period = (index + 1) << 4;
|
||||
const double freq = kClock / 2.0 / (period * kLoopCycles);
|
||||
const double len = 128 * period * kLoopCycles * 1000 / (double)kClock;
|
||||
|
||||
Tones tone;
|
||||
tone.push_back(Tone(freq, len));
|
||||
|
||||
if (playTones(tone, false, true))
|
||||
break;
|
||||
|
||||
drawLight(index, color ^ 0xff);
|
||||
|
||||
if (--index < 0) {
|
||||
index = 4;
|
||||
color ^= 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes5Engine::setupOpcodeTables() {
|
||||
AdlEngine_v4::setupOpcodeTables();
|
||||
|
||||
_actOpcodes[0x0b] = opcode(&HiRes5Engine::o_checkItemTimeLimits);
|
||||
_actOpcodes[0x13] = opcode(&HiRes5Engine::o_startAnimation);
|
||||
_actOpcodes[0x1e] = opcode(&HiRes5Engine::o_winGame);
|
||||
}
|
||||
|
||||
bool HiRes5Engine::isInventoryFull() {
|
||||
byte weight = 0;
|
||||
|
||||
for (const auto &item : _state.items) {
|
||||
if (item.room == IDI_ANY)
|
||||
weight += item.description;
|
||||
}
|
||||
|
||||
if (weight >= 100) {
|
||||
_display->printString(_gameStrings.carryingTooMuch);
|
||||
inputString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HiRes5Engine::loadSong(Common::ReadStream &stream) {
|
||||
while (true) {
|
||||
const byte period = stream.readByte();
|
||||
|
||||
if (stream.err() || stream.eos())
|
||||
error("Error loading song");
|
||||
|
||||
if (period == 0xff)
|
||||
return;
|
||||
|
||||
byte length = stream.readByte();
|
||||
|
||||
if (stream.err() || stream.eos())
|
||||
error("Error loading song");
|
||||
|
||||
const uint kLoopCycles = 20; // Delay loop cycles
|
||||
|
||||
double freq = 0.0;
|
||||
|
||||
// This computation ignores CPU cycles spent on overflow handling and
|
||||
// speaker control. As a result, our tone frequencies will be slightly
|
||||
// higher than those on original hardware.
|
||||
if (period != 0)
|
||||
freq = kClock / 2.0 / (period * kLoopCycles);
|
||||
|
||||
const double len = (length > 0 ? length - 1 : 255) * 256 * kLoopCycles * 1000 / (double)kClock;
|
||||
|
||||
_song.push_back(Tone(freq, len));
|
||||
}
|
||||
}
|
||||
|
||||
int HiRes5Engine::o_checkItemTimeLimits(ScriptEnv &e) {
|
||||
OP_DEBUG_1("\tCHECK_ITEM_TIME_LIMITS(VARS[%d])", e.arg(1));
|
||||
|
||||
bool lostAnItem = false;
|
||||
|
||||
for (auto &item : _state.items) {
|
||||
const byte room = item.room;
|
||||
const byte region = item.region;
|
||||
|
||||
if (room == IDI_ANY || room == IDI_CUR_ROOM || (room == _state.room && region == _state.region)) {
|
||||
if (getVar(e.arg(1)) < _itemTimeLimits[item.id - 1]) {
|
||||
item.room = IDI_VOID_ROOM;
|
||||
lostAnItem = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lostAnItem) {
|
||||
_display->printString(_gameStrings.itemTimeLimit);
|
||||
inputString();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int HiRes5Engine::o_startAnimation(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tSTART_ANIMATION()");
|
||||
|
||||
_doAnimation = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HiRes5Engine::o_winGame(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tWIN_GAME()");
|
||||
|
||||
showRoom();
|
||||
playTones(_song, true);
|
||||
|
||||
return o_quit(e);
|
||||
}
|
||||
|
||||
void HiRes5Engine::runIntro() {
|
||||
Display_A2 *display = static_cast<Display_A2 *>(_display);
|
||||
|
||||
insertDisk(2);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x10, 0x0, 0x00, 31));
|
||||
|
||||
display->setMode(Display::kModeGraphics);
|
||||
display->loadFrameBuffer(*stream);
|
||||
display->renderGraphics();
|
||||
|
||||
inputKey();
|
||||
|
||||
display->home();
|
||||
display->setMode(Display::kModeText);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x03, 0xc, 0x34, 1));
|
||||
Common::String menu(readString(*stream));
|
||||
|
||||
while (!g_engine->shouldQuit()) {
|
||||
display->home();
|
||||
display->printString(menu);
|
||||
|
||||
Common::String cmd(inputString());
|
||||
|
||||
// We ignore the backup and format menu options
|
||||
if (!cmd.empty() && cmd[0] == _display->asciiToNative('1'))
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void HiRes5Engine::init() {
|
||||
_graphics = new GraphicsMan_v3<Display_A2>(*static_cast<Display_A2 *>(_display));
|
||||
|
||||
insertDisk(2);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x5, 0x0, 0x02));
|
||||
loadRegionLocations(*stream, kRegions);
|
||||
|
||||
stream.reset(_disk->createReadStream(0xd, 0x2, 0x04));
|
||||
loadRegionInitDataOffsets(*stream, kRegions);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x7, 0xe));
|
||||
_strings.verbError = readStringAt(*stream, 0x4f);
|
||||
_strings.nounError = readStringAt(*stream, 0x8e);
|
||||
_strings.enterCommand = readStringAt(*stream, 0xbc);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x7, 0xc));
|
||||
_strings.lineFeeds = readString(*stream);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x8, 0x3, 0x00, 2));
|
||||
_strings_v2.saveInsert = readStringAt(*stream, 0x66);
|
||||
_strings_v2.saveReplace = readStringAt(*stream, 0x112);
|
||||
_strings_v2.restoreInsert = readStringAt(*stream, 0x180);
|
||||
_strings.playAgain = readStringAt(*stream, 0x247, 0xff);
|
||||
|
||||
_messageIds.cantGoThere = 110;
|
||||
_messageIds.dontUnderstand = 112;
|
||||
_messageIds.itemDoesntMove = 114;
|
||||
_messageIds.itemNotHere = 115;
|
||||
_messageIds.thanksForPlaying = 113;
|
||||
|
||||
stream.reset(_disk->createReadStream(0xe, 0x1, 0x13, 4));
|
||||
loadItemDescriptions(*stream, kItems);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x8, 0xd, 0xfd, 1));
|
||||
loadDroppedItemOffsets(*stream, 16);
|
||||
|
||||
stream.reset(_disk->createReadStream(0xb, 0xa, 0x05, 1));
|
||||
loadItemPicIndex(*stream, kItems);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x7, 0x8, 0x01));
|
||||
for (uint i = 0; i < kItems; ++i)
|
||||
_itemTimeLimits.push_back(stream->readByte());
|
||||
|
||||
if (stream->eos() || stream->err())
|
||||
error("Failed to read item time limits");
|
||||
|
||||
stream.reset(_disk->createReadStream(0x8, 0x2, 0x2d));
|
||||
_gameStrings.itemTimeLimit = readString(*stream);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x8, 0x7, 0x02));
|
||||
_gameStrings.carryingTooMuch = readString(*stream);
|
||||
|
||||
stream.reset(_disk->createReadStream(0xc, 0xb, 0x20));
|
||||
loadSong(*stream);
|
||||
}
|
||||
|
||||
void HiRes5Engine::initGameState() {
|
||||
_state.vars.resize(40);
|
||||
|
||||
insertDisk(2);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x5, 0x1, 0x00, 3));
|
||||
loadItems(*stream);
|
||||
|
||||
// A combined total of 1213 rooms
|
||||
static const byte rooms[kRegions] = {
|
||||
6, 16, 24, 57, 40, 30, 76, 40,
|
||||
54, 38, 44, 21, 26, 42, 49, 32,
|
||||
59, 69, 1, 1, 1, 1, 1, 18,
|
||||
25, 13, 28, 28, 11, 23, 9, 31,
|
||||
6, 29, 29, 34, 9, 10, 95, 86,
|
||||
1
|
||||
};
|
||||
|
||||
initRegions(rooms, kRegions);
|
||||
|
||||
loadRegion(1);
|
||||
_state.room = 5;
|
||||
|
||||
_doAnimation = false;
|
||||
}
|
||||
|
||||
void HiRes5Engine::applyRegionWorkarounds() {
|
||||
// WORKAROUND: Remove/fix buggy commands
|
||||
switch (_state.region) {
|
||||
case 3:
|
||||
// "USE PIN" references a missing message, but cannot
|
||||
// be triggered due to shadowing of the "USE" verb.
|
||||
// We remove it anyway to allow script dumping to proceed.
|
||||
// TODO: Investigate if we should fix this command instead
|
||||
// of removing it.
|
||||
removeCommand(_roomCommands, 12);
|
||||
break;
|
||||
case 14:
|
||||
// "WITH SHOVEL" references a missing message. This bug
|
||||
// is game-breaking in the original, but unlikely to occur
|
||||
// in practice due to the "DIG" command not asking for what
|
||||
// to dig with. Probably a remnant of an earlier version
|
||||
// of the script.
|
||||
removeCommand(_roomCommands, 0);
|
||||
break;
|
||||
case 32:
|
||||
// This broken message appears right before the game restarts,
|
||||
// and should probably explain that the user fell down some
|
||||
// stairs. We remove the broken message.
|
||||
// TODO: Maybe we could put in a new string?
|
||||
removeMessage(29);
|
||||
break;
|
||||
case 40:
|
||||
// Locking the gate prints a broken message, followed by
|
||||
// "O.K.". Maybe there was supposed to be a more elaborate
|
||||
// message, in the style of the one printed when you unlock
|
||||
// the gate. But "O.K." should be enough, so we remove the
|
||||
// first one.
|
||||
removeMessage(172);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes5Engine::applyRoomWorkarounds(byte roomNr) {
|
||||
// WORKAROUND: Remove/fix buggy commands
|
||||
if (_state.region == 17 && roomNr == 49) {
|
||||
// "GET WATER" references a missing message when you already
|
||||
// have water. This message should be 117 instead of 17.
|
||||
getCommand(_roomData.commands, 8).script[4] = 117;
|
||||
}
|
||||
}
|
||||
|
||||
Engine *HiRes5Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
return new HiRes5Engine(syst, gd);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
438
engines/adl/hires6.cpp
Normal file
438
engines/adl/hires6.cpp
Normal file
@@ -0,0 +1,438 @@
|
||||
/* 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 "common/system.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "adl/adl_v5.h"
|
||||
#include "adl/display_a2.h"
|
||||
#include "adl/graphics.h"
|
||||
#include "adl/disk.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class HiRes6Engine : public AdlEngine_v5 {
|
||||
public:
|
||||
HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) :
|
||||
AdlEngine_v5(syst, gd),
|
||||
_currVerb(0),
|
||||
_currNoun(0) {
|
||||
}
|
||||
|
||||
private:
|
||||
// AdlEngine
|
||||
void gameLoop() override;
|
||||
void setupOpcodeTables() override;
|
||||
void runIntro() override;
|
||||
void init() override;
|
||||
void initGameState() override;
|
||||
void showRoom() override;
|
||||
int goDirection(ScriptEnv &e, Direction dir) override;
|
||||
Common::String formatVerbError(const Common::String &verb) const override;
|
||||
Common::String formatNounError(const Common::String &verb, const Common::String &noun) const override;
|
||||
void loadState(Common::ReadStream &stream) override;
|
||||
void saveState(Common::WriteStream &stream) override;
|
||||
|
||||
// AdlEngine_v2
|
||||
void printString(const Common::String &str) override;
|
||||
|
||||
// Engine
|
||||
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
|
||||
int o_fluteSound(ScriptEnv &e);
|
||||
|
||||
static const uint kRegions = 3;
|
||||
static const uint kItems = 15;
|
||||
|
||||
byte _currVerb, _currNoun;
|
||||
};
|
||||
|
||||
void HiRes6Engine::gameLoop() {
|
||||
AdlEngine_v5::gameLoop();
|
||||
|
||||
// Variable 25 starts at 5 and counts down every 160 moves.
|
||||
// When it reaches 0, the game ends. This variable determines
|
||||
// what you see when you "LOOK SUNS".
|
||||
// Variable 39 is used to advance the suns based on game events,
|
||||
// so even a fast player will see the suns getting closer together
|
||||
// as he progresses.
|
||||
if (getVar(39) != 0) {
|
||||
if (getVar(39) < getVar(25))
|
||||
setVar(25, getVar(39));
|
||||
setVar(39, 0);
|
||||
}
|
||||
|
||||
if (getVar(25) != 0) {
|
||||
if (getVar(25) > 5)
|
||||
error("Variable 25 has unexpected value %d", getVar(25));
|
||||
if ((6 - getVar(25)) * 160 == _state.moves)
|
||||
setVar(25, getVar(25) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void HiRes6Engine::setupOpcodeTables() {
|
||||
AdlEngine_v5::setupOpcodeTables();
|
||||
|
||||
_actOpcodes[0x1e] = opcode(&HiRes6Engine::o_fluteSound);
|
||||
}
|
||||
|
||||
int HiRes6Engine::goDirection(ScriptEnv &e, Direction dir) {
|
||||
OP_DEBUG_0((Common::String("\tGO_") + dirStr(dir) + "()").c_str());
|
||||
|
||||
byte room = getCurRoom().connections[dir];
|
||||
|
||||
if (room == 0) {
|
||||
// Don't penalize invalid directions at escapable Garthim encounter
|
||||
if (getVar(33) == 2)
|
||||
setVar(34, getVar(34) + 1);
|
||||
|
||||
printMessage(_messageIds.cantGoThere);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switchRoom(room);
|
||||
|
||||
// Escapes an escapable Garthim encounter by going to a different room
|
||||
if (getVar(33) == 2) {
|
||||
printMessage(102);
|
||||
setVar(33, 0);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int HiRes6Engine::o_fluteSound(ScriptEnv &e) {
|
||||
OP_DEBUG_0("\tFLUTE_SOUND()");
|
||||
|
||||
Tones tones;
|
||||
|
||||
tones.push_back(Tone(1072.0, 587.6));
|
||||
tones.push_back(Tone(1461.0, 495.8));
|
||||
tones.push_back(Tone(0.0, 1298.7));
|
||||
|
||||
playTones(tones, false);
|
||||
|
||||
_linesPrinted = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool HiRes6Engine::canSaveGameStateCurrently(Common::U32String *msg) {
|
||||
if (!_canSaveNow)
|
||||
return false;
|
||||
|
||||
// Back up variables that may be changed by this test
|
||||
const byte var2 = getVar(2);
|
||||
const byte var24 = getVar(24);
|
||||
const bool abortScript = _abortScript;
|
||||
|
||||
const bool retval = AdlEngine_v5::canSaveGameStateCurrently(msg);
|
||||
|
||||
setVar(2, var2);
|
||||
setVar(24, var24);
|
||||
_abortScript = abortScript;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
#define SECTORS_PER_TRACK 16
|
||||
#define BYTES_PER_SECTOR 256
|
||||
|
||||
static Common::MemoryReadStream *loadSectors(Common::DiskImage *disk, byte track, byte sector = SECTORS_PER_TRACK - 1, byte count = SECTORS_PER_TRACK) {
|
||||
const int bufSize = count * BYTES_PER_SECTOR;
|
||||
byte *const buf = (byte *)malloc(bufSize);
|
||||
byte *p = buf;
|
||||
|
||||
while (count-- > 0) {
|
||||
Common::StreamPtr stream(disk->createReadStream(track, sector, 0, 0));
|
||||
stream->read(p, BYTES_PER_SECTOR);
|
||||
|
||||
if (stream->err() || stream->eos())
|
||||
error("Error loading from disk image");
|
||||
|
||||
p += BYTES_PER_SECTOR;
|
||||
if (sector > 0)
|
||||
--sector;
|
||||
else {
|
||||
++track;
|
||||
|
||||
// Skip VTOC track
|
||||
if (track == 17)
|
||||
++track;
|
||||
|
||||
sector = SECTORS_PER_TRACK - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
void HiRes6Engine::runIntro() {
|
||||
Display_A2 *display = static_cast<Display_A2 *>(_display);
|
||||
|
||||
insertDisk(0);
|
||||
|
||||
Common::StreamPtr stream(loadSectors(_disk, 11, 1, 96));
|
||||
|
||||
display->setMode(Display::kModeGraphics);
|
||||
display->loadFrameBuffer(*stream);
|
||||
display->renderGraphics();
|
||||
delay(256 * 8609 / 1000);
|
||||
|
||||
display->loadFrameBuffer(*stream);
|
||||
display->renderGraphics();
|
||||
delay(256 * 8609 / 1000);
|
||||
|
||||
display->loadFrameBuffer(*stream);
|
||||
|
||||
// Load copyright string from boot file
|
||||
Common::Files_AppleDOS *files(new Common::Files_AppleDOS());
|
||||
|
||||
if (!files->open(getDiskImageName(0)))
|
||||
error("Failed to open disk volume 0");
|
||||
|
||||
stream.reset(files->createReadStream("\010\010\010\010\010\010"));
|
||||
Common::String copyright(readStringAt(*stream, 0x103, _display->asciiToNative('\r')));
|
||||
|
||||
delete files;
|
||||
|
||||
display->renderGraphics();
|
||||
display->home();
|
||||
display->setMode(Display::kModeMixed);
|
||||
display->moveCursorTo(Common::Point(0, 21));
|
||||
display->printString(copyright);
|
||||
delay(256 * 8609 / 1000);
|
||||
}
|
||||
|
||||
void HiRes6Engine::init() {
|
||||
_graphics = new GraphicsMan_v3<Display_A2>(*static_cast<Display_A2 *>(_display));
|
||||
|
||||
insertDisk(0);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x3, 0xf, 0x05));
|
||||
loadRegionLocations(*stream, kRegions);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x5, 0xa, 0x07));
|
||||
loadRegionInitDataOffsets(*stream, kRegions);
|
||||
|
||||
stream.reset(loadSectors(_disk, 0x7));
|
||||
|
||||
_strings.verbError = readStringAt(*stream, 0x666);
|
||||
_strings.nounError = readStringAt(*stream, 0x6bd);
|
||||
_strings.enterCommand = readStringAt(*stream, 0x6e9);
|
||||
|
||||
_strings.lineFeeds = readStringAt(*stream, 0x408);
|
||||
|
||||
_strings_v2.saveInsert = readStringAt(*stream, 0xad8);
|
||||
_strings_v2.saveReplace = readStringAt(*stream, 0xb95);
|
||||
_strings_v2.restoreInsert = readStringAt(*stream, 0xc07);
|
||||
_strings.playAgain = readStringAt(*stream, 0xcdf, 0xff);
|
||||
|
||||
_messageIds.cantGoThere = 249;
|
||||
_messageIds.dontUnderstand = 247;
|
||||
_messageIds.itemDoesntMove = 253;
|
||||
_messageIds.itemNotHere = 254;
|
||||
_messageIds.thanksForPlaying = 252;
|
||||
|
||||
stream.reset(loadSectors(_disk, 0x6, 0xb, 2));
|
||||
stream->seek(0x16);
|
||||
loadItemDescriptions(*stream, kItems);
|
||||
|
||||
stream.reset(_disk->createReadStream(0x8, 0x9, 0x16));
|
||||
loadDroppedItemOffsets(*stream, 16);
|
||||
|
||||
stream.reset(_disk->createReadStream(0xb, 0xd, 0x08));
|
||||
loadItemPicIndex(*stream, kItems);
|
||||
}
|
||||
|
||||
void HiRes6Engine::initGameState() {
|
||||
_state.vars.resize(40);
|
||||
|
||||
insertDisk(0);
|
||||
|
||||
Common::StreamPtr stream(_disk->createReadStream(0x3, 0xe, 0x03));
|
||||
loadItems(*stream);
|
||||
|
||||
// A combined total of 91 rooms
|
||||
static const byte rooms[kRegions] = { 35, 29, 27 };
|
||||
|
||||
initRegions(rooms, kRegions);
|
||||
|
||||
loadRegion(1);
|
||||
|
||||
_currVerb = _currNoun = 0;
|
||||
}
|
||||
|
||||
void HiRes6Engine::showRoom() {
|
||||
_state.curPicture = getCurRoom().curPicture;
|
||||
|
||||
bool redrawPic = false;
|
||||
|
||||
if (getVar(26) == 0xfe)
|
||||
setVar(26, 0);
|
||||
else if (getVar(26) != 0xff)
|
||||
setVar(26, _state.room);
|
||||
|
||||
if (_state.room != _roomOnScreen) {
|
||||
loadRoom(_state.room);
|
||||
|
||||
if (getVar(26) < 0x80 && getCurRoom().isFirstTime)
|
||||
setVar(26, 0);
|
||||
|
||||
_graphics->clearScreen();
|
||||
|
||||
if (!_state.isDark)
|
||||
redrawPic = true;
|
||||
} else {
|
||||
if (getCurRoom().curPicture != _picOnScreen || _itemRemoved)
|
||||
redrawPic = true;
|
||||
}
|
||||
|
||||
if (redrawPic) {
|
||||
_roomOnScreen = _state.room;
|
||||
_picOnScreen = getCurRoom().curPicture;
|
||||
|
||||
drawPic(getCurRoom().curPicture);
|
||||
_itemRemoved = false;
|
||||
_itemsOnScreen = 0;
|
||||
|
||||
for (auto &item : _state.items)
|
||||
item.isOnScreen = false;
|
||||
}
|
||||
|
||||
if (!_state.isDark)
|
||||
drawItems();
|
||||
|
||||
_display->renderGraphics();
|
||||
setVar(2, 0xff);
|
||||
printString(_roomData.description);
|
||||
}
|
||||
|
||||
Common::String HiRes6Engine::formatVerbError(const Common::String &verb) const {
|
||||
Common::String err = _strings.verbError;
|
||||
|
||||
for (uint i = 0; i < verb.size(); ++i)
|
||||
err.setChar(verb[i], i + 24);
|
||||
|
||||
const char spaceChar = _display->asciiToNative(' ');
|
||||
|
||||
err.setChar(spaceChar, 32);
|
||||
|
||||
uint i = 24;
|
||||
while (err[i] != spaceChar)
|
||||
++i;
|
||||
|
||||
err.setChar(_display->asciiToNative('.'), i);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
Common::String HiRes6Engine::formatNounError(const Common::String &verb, const Common::String &noun) const {
|
||||
Common::String err = _strings.nounError;
|
||||
|
||||
for (uint i = 0; i < noun.size(); ++i)
|
||||
err.setChar(noun[i], i + 24);
|
||||
|
||||
const char spaceChar = _display->asciiToNative(' ');
|
||||
|
||||
for (uint i = 35; i > 31; --i)
|
||||
err.setChar(spaceChar, i);
|
||||
|
||||
uint i = 24;
|
||||
while (err[i] != spaceChar)
|
||||
++i;
|
||||
|
||||
err.setChar(_display->asciiToNative('I'), i + 1);
|
||||
err.setChar(_display->asciiToNative('S'), i + 2);
|
||||
err.setChar(_display->asciiToNative('.'), i + 3);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void HiRes6Engine::loadState(Common::ReadStream &stream) {
|
||||
AdlEngine_v5::loadState(stream);
|
||||
_state.moves = (getVar(39) << 8) | getVar(24);
|
||||
setVar(39, 0);
|
||||
}
|
||||
|
||||
void HiRes6Engine::saveState(Common::WriteStream &stream) {
|
||||
// Move counter is stuffed into variables, in order to save it
|
||||
setVar(24, _state.moves & 0xff);
|
||||
setVar(39, _state.moves >> 8);
|
||||
AdlEngine_v5::saveState(stream);
|
||||
setVar(39, 0);
|
||||
}
|
||||
|
||||
void HiRes6Engine::printString(const Common::String &str) {
|
||||
Common::String s;
|
||||
uint found = 0;
|
||||
|
||||
// Variable 27 is 1 when Kira is present, 0 otherwise. It's used for choosing
|
||||
// between singular and plural variants of a string.
|
||||
// This does not emulate the corner cases of the original, hence this check
|
||||
if (getVar(27) > 1)
|
||||
error("Invalid value %i encountered for variable 27", getVar(27));
|
||||
|
||||
for (uint i = 0; i < str.size(); ++i) {
|
||||
if (str[i] == '%') {
|
||||
++found;
|
||||
if (found == 3)
|
||||
found = 0;
|
||||
} else {
|
||||
if (found == 0 || found - 1 == getVar(27))
|
||||
s += str[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Variables 2 and 26 are used for controlling the printing of room descriptions
|
||||
if (getVar(2) == 0xff) {
|
||||
if (getVar(26) == 0) {
|
||||
// This checks for special room description string " "
|
||||
if (str.size() == 1 && _display->asciiToNative(str[0]) == _display->asciiToNative(' ')) {
|
||||
setVar(2, 160);
|
||||
} else {
|
||||
AdlEngine_v5::printString(s);
|
||||
setVar(2, 1);
|
||||
}
|
||||
} else if (getVar(26) == 0xff) {
|
||||
// Storing the room number in a variable allows for range comparisons
|
||||
setVar(26, _state.room);
|
||||
setVar(2, 1);
|
||||
} else {
|
||||
setVar(2, 80);
|
||||
}
|
||||
|
||||
doAllCommands(_globalCommands, _currVerb, _currNoun);
|
||||
} else {
|
||||
AdlEngine_v5::printString(s);
|
||||
}
|
||||
}
|
||||
|
||||
Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
||||
return new HiRes6Engine(syst, gd);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
367
engines/adl/metaengine.cpp
Normal file
367
engines/adl/metaengine.cpp
Normal file
@@ -0,0 +1,367 @@
|
||||
/* 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 "engines/advancedDetector.h"
|
||||
|
||||
#include "common/system.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/file.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
#include "graphics/thumbnail.h"
|
||||
|
||||
#include "backends/keymapper/action.h"
|
||||
#include "backends/keymapper/keymapper.h"
|
||||
|
||||
#include "adl/adl.h"
|
||||
#include "adl/detection.h"
|
||||
#include "adl/disk_image_helpers.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
static const ADExtraGuiOptionsMap optionsList[] = {
|
||||
{
|
||||
GAMEOPTION_NTSC,
|
||||
{
|
||||
_s("TV emulation"),
|
||||
_s("Emulate composite output to an NTSC TV"),
|
||||
"ntsc",
|
||||
true,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_COLOR_DEFAULT_OFF,
|
||||
{
|
||||
_s("Color graphics"),
|
||||
_s("Use color graphics instead of monochrome"),
|
||||
"color",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_COLOR_DEFAULT_ON,
|
||||
{
|
||||
_s("Color graphics"),
|
||||
_s("Use color graphics instead of monochrome"),
|
||||
"color",
|
||||
true,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_SCANLINES,
|
||||
{
|
||||
_s("Show scanlines"),
|
||||
_s("Darken every other scanline to mimic the look of a CRT"),
|
||||
"scanlines",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_MONO_TEXT,
|
||||
{
|
||||
_s("Always use sharp monochrome text"),
|
||||
_s("Do not emulate NTSC artifacts for text"),
|
||||
"monotext",
|
||||
true,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_APPLE2E_CURSOR,
|
||||
{
|
||||
_s("Use checkered cursor"),
|
||||
_s("Use the checkered cursor from later Apple II models"),
|
||||
"apple2e_cursor",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
#ifdef USE_TTS
|
||||
{
|
||||
GAMEOPTION_TTS,
|
||||
{
|
||||
_s("Enable Text to Speech"),
|
||||
_s("Use TTS to read text in the game (if TTS is available)"),
|
||||
"tts_enabled",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
#endif
|
||||
|
||||
AD_EXTRA_GUI_OPTIONS_TERMINATOR
|
||||
};
|
||||
|
||||
Common::Path getDiskImageName(const AdlGameDescription &adlDesc, byte volume) {
|
||||
const ADGameDescription &desc = adlDesc.desc;
|
||||
for (uint i = 0; desc.filesDescriptions[i].fileName; ++i) {
|
||||
const ADGameFileDescription &fDesc = desc.filesDescriptions[i];
|
||||
|
||||
if (fDesc.fileType == volume) {
|
||||
for (uint e = 0; e < ARRAYSIZE(diskImageExts); ++e) {
|
||||
if (diskImageExts[e].platform == desc.platform) {
|
||||
Common::Path testFileName(fDesc.fileName);
|
||||
testFileName.appendInPlace(diskImageExts[e].extension);
|
||||
if (Common::File::exists(testFileName))
|
||||
return testFileName;
|
||||
}
|
||||
}
|
||||
|
||||
error("Failed to find disk image '%s'", fDesc.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
error("Disk volume %d not found", volume);
|
||||
}
|
||||
|
||||
GameType getGameType(const AdlGameDescription &adlDesc) {
|
||||
return adlDesc.gameType;
|
||||
}
|
||||
|
||||
GameVersion getGameVersion(const AdlGameDescription &adlDesc) {
|
||||
return adlDesc.version;
|
||||
}
|
||||
|
||||
Common::Language getLanguage(const AdlGameDescription &adlDesc) {
|
||||
return adlDesc.desc.language;
|
||||
}
|
||||
|
||||
Common::Platform getPlatform(const AdlGameDescription &adlDesc) {
|
||||
return adlDesc.desc.platform;
|
||||
}
|
||||
|
||||
class AdlMetaEngine : public AdvancedMetaEngine<AdlGameDescription> {
|
||||
public:
|
||||
const char *getName() const override {
|
||||
return "adl";
|
||||
}
|
||||
|
||||
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
|
||||
return optionsList;
|
||||
}
|
||||
|
||||
bool hasFeature(MetaEngineFeature f) const override;
|
||||
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
|
||||
int getAutosaveSlot() const override { return 15; }
|
||||
int getMaximumSaveSlot() const override { return 15; }
|
||||
SaveStateList listSaves(const char *target) const override;
|
||||
bool removeSaveState(const char *target, int slot) const override;
|
||||
|
||||
Common::Error createInstance(OSystem *syst, Engine **engine, const AdlGameDescription *adlGd) const override;
|
||||
Common::KeymapArray initKeymaps(const char *target) const override;
|
||||
};
|
||||
|
||||
bool AdlMetaEngine::hasFeature(MetaEngineFeature f) const {
|
||||
switch(f) {
|
||||
case kSupportsListSaves:
|
||||
case kSupportsLoadingDuringStartup:
|
||||
case kSupportsDeleteSave:
|
||||
case kSavesSupportMetaInfo:
|
||||
case kSavesSupportThumbnail:
|
||||
case kSavesSupportCreationDate:
|
||||
case kSavesSupportPlayTime:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SaveStateDescriptor AdlMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
|
||||
Common::String fileName = Common::String::format("%s.s%02d", target, slot);
|
||||
Common::InSaveFile *inFile = g_system->getSavefileManager()->openForLoading(fileName);
|
||||
|
||||
if (!inFile)
|
||||
return SaveStateDescriptor();
|
||||
|
||||
if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) {
|
||||
delete inFile;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
byte saveVersion = inFile->readByte();
|
||||
if (saveVersion != SAVEGAME_VERSION) {
|
||||
delete inFile;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
char name[SAVEGAME_NAME_LEN] = { };
|
||||
inFile->read(name, sizeof(name) - 1);
|
||||
inFile->readByte();
|
||||
|
||||
if (inFile->eos() || inFile->err()) {
|
||||
delete inFile;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
SaveStateDescriptor sd(this, slot, name);
|
||||
|
||||
int year = inFile->readUint16BE();
|
||||
int month = inFile->readByte();
|
||||
int day = inFile->readByte();
|
||||
sd.setSaveDate(year + 1900, month + 1, day);
|
||||
|
||||
int hour = inFile->readByte();
|
||||
int minutes = inFile->readByte();
|
||||
sd.setSaveTime(hour, minutes);
|
||||
|
||||
uint32 playTime = inFile->readUint32BE();
|
||||
sd.setPlayTime(playTime);
|
||||
|
||||
if (inFile->eos() || inFile->err()) {
|
||||
delete inFile;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
Graphics::Surface *thumbnail;
|
||||
if (!Graphics::loadThumbnail(*inFile, thumbnail)) {
|
||||
delete inFile;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
sd.setThumbnail(thumbnail);
|
||||
|
||||
delete inFile;
|
||||
return sd;
|
||||
}
|
||||
|
||||
SaveStateList AdlMetaEngine::listSaves(const char *target) const {
|
||||
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
|
||||
Common::StringArray files = saveFileMan->listSavefiles(Common::String(target) + ".s##");
|
||||
|
||||
SaveStateList saveList;
|
||||
|
||||
for (uint i = 0; i < files.size(); ++i) {
|
||||
const Common::String &fileName = files[i];
|
||||
Common::InSaveFile *inFile = saveFileMan->openForLoading(fileName);
|
||||
if (!inFile) {
|
||||
warning("Cannot open save file '%s'", fileName.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) {
|
||||
warning("No header found in '%s'", fileName.c_str());
|
||||
delete inFile;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte saveVersion = inFile->readByte();
|
||||
if (saveVersion != SAVEGAME_VERSION) {
|
||||
warning("Unsupported save game version %i found in '%s'", saveVersion, fileName.c_str());
|
||||
delete inFile;
|
||||
continue;
|
||||
}
|
||||
|
||||
char name[SAVEGAME_NAME_LEN] = { };
|
||||
inFile->read(name, sizeof(name) - 1);
|
||||
delete inFile;
|
||||
|
||||
int slotNum = atoi(fileName.c_str() + fileName.size() - 2);
|
||||
SaveStateDescriptor sd(this, slotNum, name);
|
||||
saveList.push_back(sd);
|
||||
}
|
||||
|
||||
// Sort saves based on slot number.
|
||||
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
|
||||
return saveList;
|
||||
}
|
||||
|
||||
bool AdlMetaEngine::removeSaveState(const char *target, int slot) const {
|
||||
Common::String fileName = Common::String::format("%s.s%02d", target, slot);
|
||||
return g_system->getSavefileManager()->removeSavefile(fileName);
|
||||
}
|
||||
|
||||
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
Engine *HiRes3Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
Engine *HiRes5Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd);
|
||||
|
||||
Common::Error AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const AdlGameDescription *adlGd) const {
|
||||
switch (adlGd->gameType) {
|
||||
case GAME_TYPE_HIRES1:
|
||||
*engine = HiRes1Engine_create(syst, adlGd);
|
||||
break;
|
||||
case GAME_TYPE_HIRES2:
|
||||
*engine = HiRes2Engine_create(syst, adlGd);
|
||||
break;
|
||||
case GAME_TYPE_HIRES0:
|
||||
*engine = HiRes0Engine_create(syst, adlGd);
|
||||
break;
|
||||
case GAME_TYPE_HIRES3:
|
||||
*engine = HiRes3Engine_create(syst, adlGd);
|
||||
break;
|
||||
case GAME_TYPE_HIRES4:
|
||||
*engine = HiRes4Engine_create(syst, adlGd);
|
||||
break;
|
||||
case GAME_TYPE_HIRES5:
|
||||
*engine = HiRes5Engine_create(syst, adlGd);
|
||||
break;
|
||||
case GAME_TYPE_HIRES6:
|
||||
*engine = HiRes6Engine_create(syst, adlGd);
|
||||
break;
|
||||
default:
|
||||
return Common::kUnsupportedGameidError;
|
||||
}
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
Common::KeymapArray AdlMetaEngine::initKeymaps(const char *target) const {
|
||||
using namespace Common;
|
||||
|
||||
Keymap *engineKeymap = new Keymap(Keymap::kKeymapTypeGame, "adl", "ADL");
|
||||
|
||||
Action *act;
|
||||
|
||||
act = new Action("QUIT", _("Quit"));
|
||||
act->setCustomEngineActionEvent(kADLActionQuit);
|
||||
act->addDefaultInputMapping("C+q");
|
||||
engineKeymap->addAction(act);
|
||||
|
||||
return Keymap::arrayOf(engineKeymap);
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#if PLUGIN_ENABLED_DYNAMIC(ADL)
|
||||
REGISTER_PLUGIN_DYNAMIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine);
|
||||
#else
|
||||
REGISTER_PLUGIN_STATIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine);
|
||||
#endif
|
||||
|
||||
40
engines/adl/module.mk
Normal file
40
engines/adl/module.mk
Normal file
@@ -0,0 +1,40 @@
|
||||
MODULE := engines/adl
|
||||
|
||||
MODULE_OBJS := \
|
||||
adl.o \
|
||||
adl_v2.o \
|
||||
adl_v3.o \
|
||||
adl_v4.o \
|
||||
adl_v5.o \
|
||||
console.o \
|
||||
disk.o \
|
||||
display.o \
|
||||
display_a2.o \
|
||||
hires1.o \
|
||||
hires2.o \
|
||||
hires4.o \
|
||||
hires5.o \
|
||||
hires6.o \
|
||||
metaengine.o \
|
||||
sound.o
|
||||
|
||||
MODULE_DIRS += \
|
||||
engines/adl
|
||||
|
||||
# This module can be built as a plugin
|
||||
ifeq ($(ENABLE_ADL), DYNAMIC_PLUGIN)
|
||||
PLUGIN := 1
|
||||
endif
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
||||
|
||||
# Detection objects
|
||||
DETECT_OBJS += $(MODULE)/detection.o
|
||||
|
||||
# Skip building the following objects if a static
|
||||
# module is enabled, because it already has the contents.
|
||||
ifneq ($(ENABLE_ADL), STATIC_PLUGIN)
|
||||
# External dependencies for detection.
|
||||
DETECT_OBJS += $(MODULE)/disk.o
|
||||
endif
|
||||
146
engines/adl/sound.cpp
Normal file
146
engines/adl/sound.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/* 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 "common/system.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
|
||||
#include "adl/sound.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
// Generic PC-speaker synth
|
||||
// This produces more accurate frequencies than Audio::PCSpeaker, but only
|
||||
// does square waves.
|
||||
class Speaker {
|
||||
public:
|
||||
Speaker(int sampleRate);
|
||||
|
||||
void startTone(double freq);
|
||||
void stopTone();
|
||||
void generateSamples(int16 *buffer, int numSamples);
|
||||
|
||||
private:
|
||||
int _rate;
|
||||
frac_t _halfWaveLen, _halfWaveRem;
|
||||
int16 _curSample;
|
||||
};
|
||||
|
||||
Speaker::Speaker(int sampleRate) :
|
||||
_rate(sampleRate),
|
||||
_halfWaveLen(0),
|
||||
_halfWaveRem(0),
|
||||
_curSample(32767) { }
|
||||
|
||||
void Speaker::startTone(double freq) {
|
||||
_halfWaveLen = _halfWaveRem = doubleToFrac(_rate / freq / 2);
|
||||
|
||||
if (_halfWaveLen < (frac_t)FRAC_ONE) {
|
||||
// Tone out of range at this sample rate
|
||||
stopTone();
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::stopTone() {
|
||||
_halfWaveLen = 0;
|
||||
}
|
||||
|
||||
void Speaker::generateSamples(int16 *buffer, int numSamples) {
|
||||
if (_halfWaveLen == 0) {
|
||||
// Silence
|
||||
memset(buffer, 0, numSamples * sizeof(int16));
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
|
||||
while (offset < numSamples) {
|
||||
if (_halfWaveRem >= 0 && _halfWaveRem < (frac_t)FRAC_ONE) {
|
||||
// Rising/falling edge
|
||||
// Switch level
|
||||
_curSample = ~_curSample;
|
||||
// Use transition point fraction for current sample value
|
||||
buffer[offset++] = _halfWaveRem ^ _curSample;
|
||||
// Compute next transition point
|
||||
_halfWaveRem += _halfWaveLen - FRAC_ONE;
|
||||
} else {
|
||||
// Low/high level
|
||||
// Generate as many samples as we can
|
||||
const int samples = MIN(numSamples - offset, (int)fracToInt(_halfWaveRem));
|
||||
Common::fill(buffer + offset, buffer + offset + samples, _curSample);
|
||||
offset += samples;
|
||||
|
||||
// Count down to level transition point
|
||||
_halfWaveRem -= intToFrac(samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(const Tones &tones) :
|
||||
_tones(tones),
|
||||
_toneIndex(0),
|
||||
_samplesRem(0) {
|
||||
|
||||
_rate = g_system->getMixer()->getOutputRate();
|
||||
_speaker = new Speaker(_rate);
|
||||
}
|
||||
|
||||
Sound::~Sound() {
|
||||
delete _speaker;
|
||||
}
|
||||
|
||||
bool Sound::endOfData() const {
|
||||
return _samplesRem == 0 && _toneIndex == _tones.size();
|
||||
}
|
||||
|
||||
int Sound::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int offset = 0;
|
||||
|
||||
while (offset < numSamples) {
|
||||
if (_samplesRem == 0) {
|
||||
// Set up next tone
|
||||
|
||||
if (_toneIndex == _tones.size()) {
|
||||
// No more tones
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (_tones[_toneIndex].freq == 0.0)
|
||||
_speaker->stopTone();
|
||||
else
|
||||
_speaker->startTone(_tones[_toneIndex].freq);
|
||||
|
||||
// Compute length of tone
|
||||
_samplesRem = _rate * _tones[_toneIndex++].len / 1000;
|
||||
}
|
||||
|
||||
// Generate as many samples as we can
|
||||
const int samples = MIN(numSamples - offset, _samplesRem);
|
||||
_speaker->generateSamples(buffer + offset, samples);
|
||||
|
||||
_samplesRem -= samples;
|
||||
offset += samples;
|
||||
}
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
} // End of namespace Adl
|
||||
65
engines/adl/sound.h
Normal file
65
engines/adl/sound.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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 ADL_SOUND_H
|
||||
#define ADL_SOUND_H
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/frac.h"
|
||||
|
||||
namespace Adl {
|
||||
|
||||
class Speaker;
|
||||
|
||||
struct Tone {
|
||||
double freq; // Hz
|
||||
double len; // ms
|
||||
|
||||
Tone(double frequency, double length) : freq(frequency), len(length) { }
|
||||
};
|
||||
|
||||
typedef Common::Array<Tone> Tones;
|
||||
|
||||
class Sound : public Audio::AudioStream {
|
||||
public:
|
||||
Sound(const Tones &tones);
|
||||
~Sound() override;
|
||||
|
||||
// AudioStream
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
bool isStereo() const override { return false; }
|
||||
bool endOfData() const override;
|
||||
int getRate() const override { return _rate; }
|
||||
|
||||
private:
|
||||
const Tones &_tones;
|
||||
|
||||
Speaker *_speaker;
|
||||
int _rate;
|
||||
uint _toneIndex;
|
||||
int _samplesRem;
|
||||
};
|
||||
|
||||
} // End of namespace Adl
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user