Files
scummvm-cursorfix/engines/teenagent/teenagent.cpp
2026-02-02 04:50:13 +01:00

1333 lines
35 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/text-to-speech.h"
#include "backends/audiocd/audiocd.h"
#include "engines/advancedDetector.h"
#include "engines/util.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
#include "graphics/thumbnail.h"
#include "teenagent/console.h"
#include "teenagent/dialog.h"
#include "teenagent/inventory.h"
#include "teenagent/music.h"
#include "teenagent/objects.h"
#include "teenagent/pack.h"
#include "teenagent/resources.h"
#include "teenagent/scene.h"
#include "teenagent/teenagent.h"
namespace TeenAgent {
#ifdef USE_TTS
static const uint16 polishConversionTable[] = {
0x23, 0xc499, 0x24, 0xc59b, 0x25, 0xc582, 0x2a, 0xc3b3, 0x2b, 0xc484, 0x3b, 0xc583,
0x3c, 0xc5bc, 0x3d, 0xc584, 0x3e, 0xc5ba, 0x40, 0xc485, 0x5b, 0xc498, 0x5c, 0xc486,
0x5d, 0xc581, 0x5e, 0xc487, 0x7b, 0xc393, 0x7c, 0xc59a, 0x7d, 0xc5bb, 0x7e, 0xc5b9,
0
};
static const uint16 czechConversionTable[] = {
0x23, 0xc3a1, 0x24, 0xc48d, 0x25, 0xc48f, 0x2a, 0xc3a9, 0x2b, 0xc49b, 0x3b, 0xc3ad,
0x3c, 0xc588, 0x3d, 0xc3b3, 0x3e, 0xc599, 0x40, 0xc5a1, 0x5b, 0xc5a5, 0x5c, 0xc3ba,
0x5d, 0xc5af, 0x5e, 0xc3bd, 0x7b, 0xc5be, 0x7c, 0xc48c, 0x7d, 0xc598, 0x7e, 0xc5bd,
0
};
#endif
TeenAgentEngine *g_engine = nullptr;
TeenAgentEngine::TeenAgentEngine(OSystem *system, const ADGameDescription *gd)
: Engine(system), _action(kActionNone), _gameDescription(gd), _rnd("teenagent") {
g_engine = this;
music = new MusicPlayer(this);
dialog = new Dialog(this);
res = new Resources();
scene = 0;
inventory = 0;
_sceneBusy = false;
_dstObject = 0;
_musicStream = 0;
_markDelay = 0;
_gameDelay = 0;
}
TeenAgentEngine::~TeenAgentEngine() {
delete dialog;
dialog = 0;
delete scene;
scene = 0;
delete inventory;
inventory = 0;
delete music;
music = 0;
_mixer->stopAll();
_useHotspots.clear();
delete res;
res = 0;
CursorMan.popCursor();
}
bool TeenAgentEngine::trySelectedObject() {
InventoryObject *inv = inventory->selectedObject();
if (inv == NULL)
return false;
debugC(0, kDebugObject, "checking active object %u on %u", inv->id, _dstObject->id);
//mouse time challenge hack:
if ((res->dseg.get_byte(dsAddr_timedCallbackState) == 1 && inv->id == kInvItemRock && _dstObject->id == 5) ||
(res->dseg.get_byte(dsAddr_timedCallbackState) == 2 && inv->id == kInvItemSuperGlue && _dstObject->id == 5)) {
//putting rock into hole or superglue on rock
fnPutRockInHole();
return true;
}
const Common::Array<UseHotspot> &hotspots = _useHotspots[scene->getId() - 1];
for (uint i = 0; i < hotspots.size(); ++i) {
const UseHotspot &spot = hotspots[i];
if (spot.inventoryId == inv->id && _dstObject->id == spot.objectId) {
debugC(0, kDebugObject, "use object on hotspot!");
spot.dump();
if (spot.actorX != 0xffff && spot.actorY != 0xffff)
moveTo(spot.actorX, spot.actorY, spot.orientation);
if (!processCallback(spot.callback))
debugC(0, kDebugObject, "FIXME: display proper description");
inventory->resetSelectedObject();
return true;
}
}
// error
inventory->resetSelectedObject();
displayMessage(res->getMessageAddr(kObjErrorMsg)); // "That's no good"
return true;
}
void TeenAgentEngine::processObject() {
if (_dstObject == NULL)
return;
switch (_action) {
case kActionExamine: {
if (trySelectedObject())
break;
byte *dcall = res->dseg.ptr(dsAddr_objExamineCallbackTablePtr);
dcall = res->dseg.ptr(READ_LE_UINT16(dcall + scene->getId() * 2 - 2));
dcall += 2 * _dstObject->id - 2;
uint16 callback = READ_LE_UINT16(dcall);
if (callback == 0 || !processCallback(callback))
displayMessage(_dstObject->description, res->getVoiceIndex(_dstObject->getAddr()));
}
break;
case kActionUse: {
if (trySelectedObject())
break;
byte *dcall = res->dseg.ptr(dsAddr_objUseCallbackTablePtr);
dcall = res->dseg.ptr(READ_LE_UINT16(dcall + scene->getId() * 2 - 2));
dcall += 2 * _dstObject->id - 2;
uint16 callback = READ_LE_UINT16(dcall);
if (!processCallback(callback))
displayMessage(_dstObject->description, 0);
}
break;
case kActionNone:
default:
break;
}
}
void TeenAgentEngine::use(Object *object) {
if (object == NULL || scene->eventRunning())
return;
_dstObject = object;
object->rect.dump();
object->actorRect.dump();
_action = kActionUse;
if (object->actorRect.valid())
scene->moveTo(Common::Point(object->actorRect.right, object->actorRect.bottom), object->actorOrientation);
else if (object->actorOrientation > 0)
scene->setOrientation(object->actorOrientation);
}
void TeenAgentEngine::examine(const Common::Point &point, Object *object) {
if (scene->eventRunning())
return;
if (object != NULL) {
Common::Point dst = object->actorRect.center();
debugC(0, kDebugObject, "click %d, %d, object %d, %d", point.x, point.y, dst.x, dst.y);
_action = kActionExamine;
if (object->actorRect.valid())
scene->moveTo(dst, object->actorOrientation, true); // validate examine message. Original engine does not let you into walkboxes
_dstObject = object;
} else if (!_sceneBusy) {
// do not reset anything while scene is busy, but allow interrupts while walking.
debugC(0, kDebugObject, "click %d, %d", point.x, point.y);
_action = kActionNone;
scene->moveTo(point, 0, true);
_dstObject = NULL;
}
}
void TeenAgentEngine::init() {
_markDelay = 80;
_gameDelay = 110;
_useHotspots.resize(42);
byte *sceneHotspots = res->dseg.ptr(dsAddr_sceneHotspotsPtr);
for (byte i = 0; i < 42; ++i) {
Common::Array<UseHotspot> & hotspots = _useHotspots[i];
byte *hotspotsPtr = res->dseg.ptr(READ_LE_UINT16(sceneHotspots + i * 2));
while (*hotspotsPtr) {
UseHotspot h;
h.load(hotspotsPtr);
hotspotsPtr += 9;
hotspots.push_back(h);
}
}
}
Common::Error TeenAgentEngine::loadGameState(int slot) {
debug(0, "loading from slot %d", slot);
Common::ScopedPtr<Common::InSaveFile> in(_saveFileMan->openForLoading(
getSaveStateName(slot)));
if (!in)
in.reset(_saveFileMan->openForLoading(Common::String::format("teenagent.%d", slot)));
if (!in)
return Common::kReadPermissionDenied;
assert(res->dseg.size() >= dsAddr_saveState + saveStateSize);
char *data = (char *)malloc(saveStateSize);
if (!data)
error("[TeenAgentEngine::loadGameState] Cannot allocate buffer");
in->seek(0);
if (in->read(data, saveStateSize) != saveStateSize) {
free(data);
return Common::kReadingFailed;
}
memcpy(res->dseg.ptr(dsAddr_saveState), data, saveStateSize);
free(data);
uint32 tag = in->readUint32BE();
if (tag == MKTAG('T', 'H', 'M', 'B')) { // Old save (before TEENAGENT_SAVEGAME_VERSION was added)
uint16 baseAddr = dsAddr_sceneObjectTablePtr;
uint32 sceneObjectStartAddr = res->getSceneObjectsStartPos();
// Copy scene object data in the dseg to sceneObjectsSeg
Common::copy(res->dseg.ptr(baseAddr), res->dseg.ptr(0xb4f3), res->eseg.ptr(sceneObjectStartAddr));
// Set correct addresses, i.e., make them relative to dsAddr_sceneObjectTablePtr
for (byte i = 0; i < 42; i++) {
uint16 sceneTable = res->dseg.get_word(baseAddr + (i * 2));
res->eseg.set_word(sceneObjectStartAddr + i * 2, sceneTable - baseAddr);
uint16 objectAddr;
while ((objectAddr = res->dseg.get_word(sceneTable)) != 0) {
res->eseg.set_word(sceneObjectStartAddr + sceneTable - baseAddr, objectAddr - baseAddr);
sceneTable += 2;
}
res->eseg.set_word(sceneObjectStartAddr + sceneTable - baseAddr, 0);
}
} else {
if (tag != MKTAG('T', 'N', 'G', 'T')) {
warning("loadGameState(): Invalid save file");
return Common::kUnknownError;
}
byte saveVersion = in->readByte();
if (saveVersion != TEENAGENT_SAVEGAME_VERSION) {
warning("loadGameState(): Failed to load %d - incorrect version", slot);
return Common::kUnknownError;
}
uint32 resourceSize = in->readUint32LE();
if (in->read(res->eseg.ptr(res->getSceneObjectsStartPos()), resourceSize) != resourceSize) {
warning("loadGameState(): corrupted data");
return Common::kReadingFailed;
}
}
scene->clear();
inventory->activate(false);
inventory->reload();
setMusic(res->dseg.get_byte(dsAddr_currentMusic));
int id = res->dseg.get_byte(dsAddr_currentScene);
uint16 x = res->dseg.get_word(dsAddr_egoX), y = res->dseg.get_word(dsAddr_egoY);
scene->loadObjectData();
scene->init(id, Common::Point(x, y));
scene->setPalette(4);
scene->intro = false;
return Common::kNoError;
}
Common::String TeenAgentEngine::getSaveStateName(int slot) const {
return Common::String::format("%s.%02d", _targetName.c_str(), slot);
}
Common::Error TeenAgentEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
debug(0, "saving to slot %d", slot);
Common::ScopedPtr<Common::OutSaveFile> out(_saveFileMan->openForSaving(
getSaveStateName(slot)));
if (!out)
return Common::kWritingFailed;
res->dseg.set_byte(dsAddr_currentScene, scene->getId());
Common::Point pos = scene->getPosition();
res->dseg.set_word(dsAddr_egoX, pos.x);
res->dseg.set_word(dsAddr_egoY, pos.y);
assert(res->dseg.size() >= dsAddr_saveState + saveStateSize);
// FIXME: Description string is 24 bytes and null based on detection.cpp code, not 22?
strncpy((char *)res->dseg.ptr(dsAddr_saveState), desc.c_str(), 22);
out->write(res->dseg.ptr(dsAddr_saveState), saveStateSize);
// Write tag
out->writeUint32BE(MKTAG('T', 'N', 'G', 'T'));
// Write save version
out->writeByte(TEENAGENT_SAVEGAME_VERSION);
// Write scene object data
out->writeUint32LE(res->sceneObjectsBlockSize());
out->write(res->eseg.ptr(res->getSceneObjectsStartPos()), res->sceneObjectsBlockSize());
if (!Graphics::saveThumbnail(*out))
warning("saveThumbnail failed");
out->finalize();
return Common::kNoError;
}
int TeenAgentEngine::skipEvents() const {
Common::EventManager *_event = _system->getEventManager();
Common::Event event;
while (_event->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_QUIT:
case Common::EVENT_RETURN_TO_LAUNCHER:
return -1;
case Common::EVENT_MAINMENU:
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
return 1;
case Common::EVENT_KEYDOWN:
if (event.kbd.ascii)
return 1;
default:
break;
}
}
return 0;
}
bool TeenAgentEngine::showCDLogo() {
Common::File cdlogo;
if (!cdlogo.exists("cdlogo.res") || !cdlogo.open("cdlogo.res"))
return true;
const uint bgSize = kScreenWidth * kScreenHeight;
const uint paletteSize = 3 * 256;
byte *bg = (byte *)malloc(bgSize);
if (!bg)
error("[TeenAgentEngine::showCDLogo] Cannot allocate background buffer");
byte *palette = (byte *)malloc(paletteSize);
if (!palette) {
free(bg);
error("[TeenAgentEngine::showCDLogo] Cannot allocate palette buffer");
}
cdlogo.read(bg, bgSize);
cdlogo.read(palette, paletteSize);
for (uint c = 0; c < paletteSize; ++c)
palette[c] *= 4;
_system->getPaletteManager()->setPalette(palette, 0, 256);
_system->copyRectToScreen(bg, kScreenWidth, 0, 0, kScreenWidth, kScreenHeight);
_system->updateScreen();
free(bg);
free(palette);
for (uint i = 0; i < 20; ++i) {
int r = skipEvents();
if (r != 0)
return r > 0 ? true : false;
_system->delayMillis(100);
}
cdlogo.close();
return true;
}
bool TeenAgentEngine::showLogo() {
FilePack logo;
if (!logo.open("unlogic.res"))
return true;
Common::ScopedPtr<Common::SeekableReadStream> frame(logo.getStream(1));
if (!frame)
return true;
const uint bgSize = kScreenWidth * kScreenHeight;
const uint paletteSize = 3 * 256;
byte *bg = (byte *)malloc(bgSize);
if (!bg)
error("[TeenAgentEngine::showLogo] Cannot allocate background buffer");
byte *palette = (byte *)malloc(paletteSize);
if (!palette) {
free(bg);
error("[TeenAgentEngine::showLogo] Cannot allocate palette buffer");
}
frame->read(bg, bgSize);
frame->read(palette, paletteSize);
for (uint c = 0; c < paletteSize; ++c)
palette[c] *= 4;
_system->getPaletteManager()->setPalette(palette, 0, 256);
free(palette);
uint n = logo.fileCount();
for (uint f = 0; f < 4; ++f)
for (uint i = 2; i <= n; ++i) {
{
int r = skipEvents();
if (r != 0) {
free(bg);
return r > 0 ? true : false;
}
}
_system->copyRectToScreen(bg, kScreenWidth, 0, 0, kScreenWidth, kScreenHeight);
frame.reset(logo.getStream(i));
if (!frame) {
free(bg);
return true;
}
Surface s;
s.load(*frame, Surface::kTypeOns);
if (s.empty()) {
free(bg);
return true;
}
_system->copyRectToScreen(s.getPixels(), s.w, s.x, s.y, s.w, s.h);
_system->updateScreen();
_system->delayMillis(100);
}
free(bg);
return true;
}
bool TeenAgentEngine::showMetropolis() {
_system->fillScreen(0);
_system->updateScreen();
FilePack varia;
varia.open("varia.res");
const uint paletteSize = 3 * 256;
byte *palette = (byte *)malloc(paletteSize);
if (!palette)
error("[TeenAgentEngine::showMetropolis] Cannot allocate palette buffer");
{
Common::ScopedPtr<Common::SeekableReadStream> s(varia.getStream(5));
s->read(palette, paletteSize);
for (uint c = 0; c < paletteSize; ++c)
palette[c] *= 4;
}
_system->getPaletteManager()->setPalette(palette, 0, 256);
free(palette);
const uint varia6Size = 21760;
const uint varia9Size = 18302;
byte *varia6Data = (byte *)malloc(varia6Size);
byte *varia9Data = (byte *)malloc(varia9Size);
if (!varia6Data || !varia9Data) {
free(varia6Data);
free(varia9Data);
error("[TeenAgentEngine::showMetropolis] Cannot allocate buffer");
}
varia.read(6, varia6Data, varia6Size);
varia.read(9, varia9Data, varia9Size);
const uint colorsSize = 56 * 160 * 2;
byte *colors = (byte *)malloc(colorsSize);
if (!colors)
error("[TeenAgentEngine::showMetropolis] Cannot allocate colors buffer");
memset(colors, 0, colorsSize);
int logo_y = -56;
for (uint f = 0; f < 300; ++f) {
{
int r = skipEvents();
if (r != 0) {
free(varia6Data);
free(varia9Data);
free(colors);
return r > 0 ? true : false;
}
}
if (logo_y > 0) {
g_system->fillScreen(Common::Rect(0, 0, kScreenWidth, logo_y), 0);
}
Graphics::Surface *surface = _system->lockScreen();
{
//generate colors matrix
memmove(colors + 320, colors + 480, 8480);
for (uint c = 0; c < 17; ++c) {
byte x = (_rnd.getRandomNumber(184) + 5) & 0xff;
uint offset = 8800 + _rnd.getRandomNumber(158);
colors[offset++] = x;
colors[offset++] = x;
}
for (uint y = 1; y < 56; ++y) {
for (uint x = 1; x < 160; ++x) {
uint offset = y * 160 + x;
uint v =
(uint)colors[offset - 161] + colors[offset - 160] + colors[offset - 159] +
(uint)colors[offset - 1] + colors[offset + 1] +
(uint)colors[offset + 161] + colors[offset + 160] + colors[offset + 159];
v >>= 3;
colors[offset + 8960] = v;
}
}
memmove(colors, colors + 8960, 8960);
}
byte *dst = (byte *)surface->getBasePtr(0, 131);
byte *src = varia6Data;
for (uint y = 0; y < 68; ++y) {
for (uint x = 0; x < 320; ++x) {
if (*src++ == 1) {
*dst++ = colors[19 * 160 + y / 2 * 160 + x / 2];
} else
++dst;
}
}
_system->unlockScreen();
_system->copyRectToScreen(
varia9Data + (logo_y < 0 ? -logo_y * 320 : 0), 320,
0, logo_y >= 0 ? logo_y : 0,
320, logo_y >= 0 ? 57 : 57 + logo_y);
if (logo_y < 82 - 57)
++logo_y;
_system->updateScreen();
_system->delayMillis(100);
}
free(varia6Data);
free(varia9Data);
free(colors);
return true;
}
Common::Error TeenAgentEngine::run() {
const Common::FSNode gameDataDir(ConfMan.getPath("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "music");
if (!res->loadArchives(_gameDescription))
return Common::kUnknownError;
Common::EventManager *_event = _system->getEventManager();
initGraphics(kScreenWidth, kScreenHeight);
setDebugger(new Console(this));
scene = new Scene(this);
inventory = new Inventory(this);
init();
CursorMan.pushCursor(res->dseg.ptr(dsAddr_cursor), 8, 12, 0, 0, 1);
syncSoundSettings();
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr) {
ttsMan->setLanguage(ConfMan.get("language"));
ttsMan->enable(ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech"));
}
// Initialize CD audio
if (_gameDescription->flags & ADGF_CD)
g_system->getAudioCDManager()->open();
setMusic(1);
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, music, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, false);
int loadSlot = ConfMan.getInt("save_slot");
if (loadSlot >= 0) {
loadGameState(loadSlot);
} else {
if (!showCDLogo())
return Common::kNoError;
if (!showLogo())
return Common::kNoError;
if (!showMetropolis())
return Common::kNoError;
scene->intro = true;
_sceneBusy = true;
fnIntro();
}
CursorMan.showMouse(true);
uint32 gameTimer = 0;
uint32 markTimer = 0;
Common::Event event;
Common::Point mouse;
uint32 timer = _system->getMillis();
do {
Object *currentObject = scene->findObject(mouse);
while (_event->pollEvent(event)) {
if (event.type == Common::EVENT_RETURN_TO_LAUNCHER)
return Common::kNoError;
if ((!_sceneBusy && inventory->processEvent(event)) || scene->processEvent(event))
continue;
debug(5, "event");
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if (event.customType == kActionFastMode) {
_markDelay = _markDelay == 80 ? 40 : 80;
debug(5, "markDelay = %u", _markDelay);
}
break;
case Common::EVENT_LBUTTONDOWN:
if (scene->getId() < 0)
break;
examine(event.mouse, currentObject);
break;
case Common::EVENT_RBUTTONDOWN:
if (currentObject)
debugC(0, kDebugObject, "%d, %s", currentObject->id, currentObject->name.c_str());
if (scene->getId() < 0)
break;
if (currentObject == NULL)
break;
if (res->dseg.get_byte(dsAddr_timedCallbackState) == 3 && currentObject->id == 1) {
fnGuardDrinking();
break;
}
if (res->dseg.get_byte(dsAddr_timedCallbackState) == 4 && currentObject->id == 5) {
fnGotAnchor();
break;
}
use(currentObject);
break;
case Common::EVENT_MOUSEMOVE:
mouse = event.mouse;
break;
default:
;
}
}
//game delays: slow 16, normal 11, fast 5, crazy 1
//mark delays: 4 * (3 - hero_speed), normal == 1
//game delays in 1/100th of seconds
uint32 newTimer = _system->getMillis();
uint32 delta = newTimer - timer;
timer = newTimer;
bool tickGame = gameTimer <= delta;
if (tickGame)
gameTimer = _gameDelay - ((delta - gameTimer) % _gameDelay);
else
gameTimer -= delta;
bool tickMark = markTimer <= delta;
if (tickMark)
markTimer = _markDelay - ((delta - markTimer) % _markDelay);
else
markTimer -= delta;
if (tickGame || tickMark) {
bool b = scene->render(tickGame, tickMark, delta);
if (!inventory->active() && !b && _action != kActionNone) {
processObject();
_action = kActionNone;
_dstObject = NULL;
}
_sceneBusy = b;
}
_system->showMouse(scene->getMessage().empty() && !_sceneBusy);
bool busy = inventory->active() || _sceneBusy;
Graphics::Surface *surface = _system->lockScreen();
if (!busy) {
InventoryObject *selectedObject = inventory->selectedObject();
if (currentObject || selectedObject) {
Common::String name;
if (selectedObject) {
name += selectedObject->name;
name += " & ";
}
if (currentObject)
name += currentObject->name;
sayText(name);
uint w = res->font7.render(NULL, 0, 0, name, textColorMark);
res->font7.render(surface, (kScreenWidth - w) / 2, 180, name, textColorMark, true);
#if 0
if (currentObject) {
currentObject->rect.render(surface, 0x80);
currentObject->actorRect.render(surface, 0x81);
}
#endif
} else {
_previousSaid.clear();
}
}
inventory->render(surface, tickGame ? 1 : 0);
_system->unlockScreen();
_system->updateScreen();
uint32 nextTick = MIN(gameTimer, markTimer);
if (nextTick > 0) {
_system->delayMillis(nextTick > 40 ? 40 : nextTick);
}
} while (!shouldQuit());
return Common::kNoError;
}
Common::String TeenAgentEngine::parseMessage(uint32 addr) {
Common::String message;
for (
const char *str = (const char *)res->eseg.ptr(addr);
str[0] != 0 || str[1] != 0;
++str) {
char c = str[0];
message += c != 0 && (signed char)c != -1 ? c : '\n';
}
if (message.empty()) {
warning("empty message parsed for %04x", addr);
}
return message;
}
void TeenAgentEngine::displayMessage(const Common::String &str, uint16 voiceIndex, CharacterID characterID, uint16 x, uint16 y) {
if (str.empty()) {
return;
}
if (characterDialogData[characterID].textColor == textColorMark) { // mark's
SceneEvent e(SceneEvent::kPlayAnimation);
e.animation = 0;
e.slot = 0x80;
scene->push(e);
}
{
SceneEvent event(SceneEvent::kMessage);
event.message = str;
event.color = characterDialogData[characterID].textColor;
event.slot = 0;
event.dst.x = x;
event.dst.y = y;
event.characterID = characterID;
event.voiceId = voiceIndex;
scene->push(event);
}
{
SceneEvent e(SceneEvent::kPauseAnimation);
e.animation = 0;
e.slot = 0x80;
scene->push(e);
}
}
void TeenAgentEngine::displayMessage(uint32 addr, CharacterID characterID, uint16 x, uint16 y) {
displayMessage(parseMessage(addr), res->getVoiceIndex(addr), characterID, x, y);
}
void TeenAgentEngine::displayAsyncMessage(uint32 addr, uint16 x, uint16 y, uint16 firstFrame, uint16 lastFrame, CharacterID characterID) {
SceneEvent event(SceneEvent::kMessage);
event.message = parseMessage(addr);
event.voiceId = res->getVoiceIndex(addr);
event.slot = 0;
event.color = characterDialogData[characterID].textColor;
event.dst.x = x;
event.dst.y = y;
event.firstFrame = firstFrame;
event.lastFrame = lastFrame;
event.characterID = characterID;
scene->push(event);
}
void TeenAgentEngine::displayAsyncMessageInSlot(uint32 addr, byte slot, uint16 firstFrame, uint16 lastFrame, byte color) {
SceneEvent event(SceneEvent::kMessage);
event.message = parseMessage(addr);
event.voiceId = res->getVoiceIndex(addr);
event.slot = slot + 1;
event.color = color;
event.firstFrame = firstFrame;
event.lastFrame = lastFrame;
scene->push(event);
}
void TeenAgentEngine::displayCredits(uint32 addr, uint16 timer) {
SceneEvent event(SceneEvent::kCreditsMessage);
const byte *src = res->eseg.ptr(addr);
event.orientation = *src++;
event.color = *src++;
event.lan = 8;
event.dst.y = *src;
while (true) {
++src; // skip y position
Common::String line((const char *)src);
event.message += line;
src += line.size() + 1;
if (*src == 0)
break;
event.message += "\n";
}
int w = res->font8.render(NULL, 0, 0, event.message, textColorCredits);
event.dst.x = (kScreenWidth - w) / 2;
event.timer = timer;
event.characterID = kCreditsText;
scene->push(event);
}
void TeenAgentEngine::displayCredits() {
SceneEvent event(SceneEvent::kCredits);
event.message = parseMessage(res->getCreditAddr(6));
event.dst.y = kScreenHeight;
int lines = 1;
for (uint i = 0; i < event.message.size(); ++i)
if (event.message[i] == '\n')
++lines;
event.dst.x = (kScreenWidth - res->font7.render(NULL, 0, 0, event.message, textColorCredits)) / 2;
event.timer = 11 * lines - event.dst.y + 22;
event.characterID = kCreditsText;
debug(2, "credits = %s", event.message.c_str());
scene->push(event);
}
void TeenAgentEngine::displayCutsceneMessage(uint32 addr, uint16 x, uint16 y) {
SceneEvent event(SceneEvent::kCreditsMessage);
event.message = parseMessage(addr);
event.dst.x = x;
event.dst.y = y;
event.lan = 7;
event.characterID = kMark;
event.voiceId = res->getVoiceIndex(addr);
scene->push(event);
}
void TeenAgentEngine::moveTo(const Common::Point &dst, byte o, bool warp) {
moveTo(dst.x, dst.y, o, warp);
}
void TeenAgentEngine::moveTo(Object *obj) {
moveTo(obj->actorRect.right, obj->actorRect.bottom, obj->actorOrientation);
}
void TeenAgentEngine::moveTo(uint16 x, uint16 y, byte o, bool warp) {
SceneEvent event(SceneEvent::kWalk);
event.dst.x = x;
event.dst.y = y;
if (o > 4) {
warning("invalid orientation %d", o);
o = 0;
}
event.orientation = o;
event.color = warp ? 1 : 0;
scene->push(event);
}
void TeenAgentEngine::moveRel(int16 x, int16 y, byte o, bool warp) {
SceneEvent event(SceneEvent::kWalk);
event.dst.x = x;
event.dst.y = y;
event.orientation = o;
event.color = (warp ? 1 : 0) | 2;
scene->push(event);
}
void TeenAgentEngine::playAnimation(uint16 id, byte slot, bool async, bool ignore, bool loop) {
SceneEvent event(SceneEvent::kPlayAnimation);
event.animation = id;
event.slot = (slot + 1) | (ignore ? 0x20 : 0) | (loop ? 0x80 : 0);
scene->push(event);
if (!async)
waitAnimation();
}
void TeenAgentEngine::playActorAnimation(uint16 id, bool async, bool ignore) {
SceneEvent event(SceneEvent::kPlayActorAnimation);
event.animation = id;
event.slot = ignore ? 0x20 : 0;
scene->push(event);
if (!async)
waitAnimation();
}
void TeenAgentEngine::loadScene(byte id, const Common::Point &pos, byte o) {
loadScene(id, pos.x, pos.y, o);
}
void TeenAgentEngine::loadScene(byte id, uint16 x, uint16 y, byte o) {
if (scene->last_event_type() != SceneEvent::kCreditsMessage)
fadeOut();
SceneEvent event(SceneEvent::kLoadScene);
event.scene = id;
event.dst.x = x;
event.dst.y = y;
event.orientation = o;
scene->push(event);
fadeIn();
}
void TeenAgentEngine::enableOn(bool enable) {
SceneEvent event(SceneEvent::kSetOn);
event.ons = 0;
event.color = enable ? 1 : 0;
scene->push(event);
}
void TeenAgentEngine::setOns(byte id, byte value, byte sceneId) {
SceneEvent event(SceneEvent::kSetOn);
event.ons = id + 1;
event.color = value;
event.scene = sceneId;
scene->push(event);
}
void TeenAgentEngine::setLan(byte id, byte value, byte sceneId) {
if (id == 0)
error("setting lan 0 is invalid");
SceneEvent event(SceneEvent::kSetLan);
event.lan = id;
event.color = value;
event.scene = sceneId;
scene->push(event);
}
void TeenAgentEngine::setFlag(uint16 addr, byte value) {
SceneEvent event(SceneEvent::kSetFlag);
event.callback = addr;
event.color = value;
scene->push(event);
}
byte TeenAgentEngine::getFlag(uint16 addr) {
return scene->peekFlagEvent(addr);
}
void TeenAgentEngine::reloadLan() {
SceneEvent event(SceneEvent::kSetLan);
event.lan = 0;
scene->push(event);
}
void TeenAgentEngine::playMusic(byte id) {
SceneEvent event(SceneEvent::kPlayMusic);
event.music = id;
scene->push(event);
}
void TeenAgentEngine::playSound(byte id, byte skipFrames) {
if (skipFrames > 0)
--skipFrames;
SceneEvent event(SceneEvent::kPlaySound);
event.sound = id;
event.color = skipFrames;
scene->push(event);
}
void TeenAgentEngine::enableObject(byte id, byte sceneId) {
SceneEvent event(SceneEvent::kEnableObject);
event.object = id + 1;
event.color = 1;
event.scene = sceneId;
scene->push(event);
}
void TeenAgentEngine::disableObject(byte id, byte sceneId) {
SceneEvent event(SceneEvent::kEnableObject);
event.object = id + 1;
event.color = 0;
event.scene = sceneId;
scene->push(event);
}
void TeenAgentEngine::hideActor() {
SceneEvent event(SceneEvent::kHideActor);
event.color = 1;
scene->push(event);
}
void TeenAgentEngine::showActor() {
SceneEvent event(SceneEvent::kHideActor);
event.color = 0;
scene->push(event);
}
void TeenAgentEngine::waitAnimation() {
SceneEvent event(SceneEvent::kWaitForAnimation);
scene->push(event);
}
void TeenAgentEngine::waitLanAnimationFrame(byte slot, uint16 frame) {
SceneEvent event(SceneEvent::kWaitLanAnimationFrame);
if (frame > 0)
--frame;
event.slot = slot - 1;
event.animation = frame;
scene->push(event);
}
void TeenAgentEngine::setTimerCallback(uint16 addr, uint16 frames) {
SceneEvent event(SceneEvent::kTimer);
event.callback = addr;
event.timer = frames;
scene->push(event);
}
void TeenAgentEngine::shakeScreen() {
SceneEvent event(SceneEvent::kEffect);
scene->push(event);
}
void TeenAgentEngine::fadeIn() {
SceneEvent event(SceneEvent::kFade);
event.orientation = 0;
scene->push(event);
}
void TeenAgentEngine::fadeOut() {
SceneEvent event(SceneEvent::kFade);
event.orientation = 1;
scene->push(event);
}
void TeenAgentEngine::wait(uint16 frames) {
SceneEvent event(SceneEvent::kWait);
event.timer = frames * 10;
scene->push(event);
}
void TeenAgentEngine::playSoundNow(Pack *pack, uint32 id) {
uint size = pack->getSize(id);
if (size == 0) {
warning("skipping invalid sound %u", id);
return;
}
byte *data = (byte *)malloc(size);
pack->read(id, data, size);
debug(3, "playing %u samples...", size);
Audio::AudioStream *stream = Audio::makeRawStream(data, size, 11025, 0);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, stream); // dispose is YES by default
}
void TeenAgentEngine::playVoiceNow(Pack *pack, uint32 id) {
// Only the Polish version has voices
if (_gameDescription->language != Common::PL_POL)
return;
uint size = pack->getSize(id);
if (size == 0) {
warning("skipping invalid sound %u", id);
return;
}
if (!_mixer->isSoundHandleActive(_voiceHandle) && id != _previousVoiceId) {
byte *data = (byte *)malloc(size);
pack->read(id, data, size);
debug(3, "playing %u samples...", size);
Audio::AudioStream *stream = Audio::makeRawStream(data, size, 11025, 0);
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_voiceHandle, stream);
_previousVoiceId = id;
}
}
void TeenAgentEngine::stopVoice() {
_mixer->stopHandle(_voiceHandle);
_previousVoiceId = 0;
}
void TeenAgentEngine::setMusic(byte id) {
debugC(0, kDebugMusic, "starting music %u", id);
if (id != 1) // intro music
res->dseg.set_byte(dsAddr_currentMusic, id);
if (_gameDescription->flags & ADGF_CD) {
byte track2cd[] = {7, 2, 0, 9, 3, 6, 8, 10, 4, 5, 11};
if (id == 0 || id > 11 || track2cd[id - 1] == 0) {
debugC(0, kDebugMusic, "no cd music for id %u", id);
return;
}
byte track = track2cd[id - 1];
debugC(0, kDebugMusic, "playing cd track %u", track);
_system->getAudioCDManager()->play(track, -1, 0, 0);
} else if (music->load(id))
music->start();
}
bool TeenAgentEngine::hasFeature(EngineFeature f) const {
switch (f) {
case kSupportsReturnToLauncher:
case kSupportsSubtitleOptions:
case kSupportsLoadingDuringRuntime:
case kSupportsSavingDuringRuntime:
return true;
default:
return false;
}
}
void TeenAgentEngine::sayText(const Common::String &text, bool isSubtitle) {
#ifdef USE_TTS
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
bool voice = (!isSubtitle && ConfMan.getBool("tts_enabled_objects")) || (isSubtitle && ConfMan.getBool("tts_enabled_speech"));
// _previousSaid is used to prevent the TTS from looping when sayText calls are inside loops
if (ttsMan && voice && _previousSaid != text) {
if (_gameDescription->language != Common::EN_ANY) {
ttsMan->say(convertText(text));
} else {
ttsMan->say(text, Common::CodePage::kDos850);
}
_previousSaid = text;
}
#endif
}
void TeenAgentEngine::stopTextToSpeech() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech")) && ttsMan->isSpeaking()) {
ttsMan->stop();
_previousSaid.clear();
}
}
void TeenAgentEngine::setTTSVoice(CharacterID characterID) const {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech"))) {
Common::Array<int> voices;
int pitch = 0;
Common::TTSVoice::Gender gender;
if (characterDialogData[characterID].male) {
voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
gender = Common::TTSVoice::MALE;
} else {
voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
gender = Common::TTSVoice::FEMALE;
}
// If no voice is available for the necessary gender, set the voice to default
if (voices.empty()) {
ttsMan->setVoice(0);
} else {
int voiceIndex = characterDialogData[characterID].voiceID % voices.size();
ttsMan->setVoice(voices[voiceIndex]);
}
// If no voices are available for this gender, alter the pitch to mimic a voice
// of the other gender
if (ttsMan->getVoice().getGender() != gender) {
if (gender == Common::TTSVoice::MALE) {
pitch -= 50;
} else {
pitch += 50;
}
}
ttsMan->setPitch(pitch);
}
}
#ifdef USE_TTS
Common::U32String TeenAgentEngine::convertText(const Common::String &text) const {
const byte *bytes = (const byte *)text.c_str();
byte *convertedBytes = new byte[text.size() * 3 + 1];
const uint16 *conversionTable = nullptr;
if (_gameDescription->language == Common::PL_POL) {
conversionTable = polishConversionTable;
} else if (_gameDescription->language == Common::CS_CZE) {
conversionTable = czechConversionTable;
}
int i = 0;
for (const byte *b = bytes; *b; ++b) {
if (_gameDescription->language == Common::RU_RUS) {
if (*b == 0x26) { // & needs to be converted to и
convertedBytes[i] = 0xd0;
convertedBytes[i + 1] = 0xb8;
i += 2;
continue;
}
if (*b == 0x3e) { // For ё
convertedBytes[i] = 0xd1;
convertedBytes[i + 1] = 0x91;
i += 2;
continue;
}
if (*b > 0x3f) {
int translated = *b;
if (*b > 0x70) {
translated += 0xd10f;
} else {
translated += 0xd04f;
}
convertedBytes[i] = (translated >> 8) & 0xff;
convertedBytes[i + 1] = translated & 0xff;
i += 2;
} else {
convertedBytes[i] = *b;
i++;
}
} else {
if (*b == 0x60) { // TM
convertedBytes[i] = 0xe2;
convertedBytes[i + 1] = 0x84;
convertedBytes[i + 2] = 0xa2;
i += 3;
continue;
}
if (*b == 0x80) { // %
convertedBytes[i] = 0x25;
i++;
continue;
}
bool inTable = false;
for (uint j = 0; conversionTable[j]; j += 2) {
if (*b == conversionTable[j]) {
convertedBytes[i] = (conversionTable[j + 1] >> 8) & 0xff;
convertedBytes[i + 1] = conversionTable[j + 1] & 0xff;
i += 2;
inTable = true;
break;
}
}
if (!inTable) {
convertedBytes[i] = *b;
i++;
}
}
}
convertedBytes[i] = 0;
Common::U32String result((char *)convertedBytes);
delete[] convertedBytes;
return result;
}
#endif
} // End of namespace TeenAgent