/* 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 .
*
*/
#include "agds/agds.h"
#include "agds/animation.h"
#include "agds/character.h"
#include "agds/console.h"
#include "agds/database.h"
#include "agds/detection.h"
#include "agds/font.h"
#include "agds/mjpgPlayer.h"
#include "agds/object.h"
#include "agds/patch.h"
#include "agds/process.h"
#include "agds/region.h"
#include "agds/screen.h"
#include "agds/systemVariable.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/events.h"
#include "common/file.h"
#include "common/formats/ini-file.h"
#include "common/memstream.h"
#include "common/savefile.h"
#include "common/system.h"
#include "engines/util.h"
#include "graphics/fonts/ttf.h"
#include "graphics/managed_surface.h"
namespace AGDS {
AGDSEngine::AGDSEngine(OSystem *system, const ADGameDescription *gameDesc) : Engine(system),
_gameDescription(gameDesc),
_resourceManager(version()),
_soundManager(this, system->getMixer()), _pictureCacheId(1),
_processes(MaxProcesses),
_sharedStorageIndex(-2),
_shadowIntensity(0), _mjpgPlayer(),
_filmStarted(0),
_currentScreen(),
_currentCharacter(),
_nextScreenType(ScreenLoadingType::Normal),
_defaultMouseCursor(),
_mouse(400, 300), _userEnabled(true),
_systemUserEnabled(true),
_random("agds"),
_inventory(this),
_inventoryRegion(),
_dialog(this),
_tellTextTimer(0),
_syncSoundId(-1),
_ambientSoundId(-1),
_curtainTimer(-1),
_curtainScreen(0),
_fastMode(true),
_hintMode(false) {
}
AGDSEngine::~AGDSEngine() {
for (PictureCacheType::iterator i = _pictureCache.begin(); i != _pictureCache.end(); ++i) {
i->_value->free();
}
_pictureCache.clear();
}
bool AGDSEngine::initGraphics(int w, int h) {
_pixelFormat = Graphics::BlendBlit::getSupportedPixelFormat();
::initGraphics(w, h, &_pixelFormat);
return true;
}
void AGDSEngine::Color::FromString(const Common::String &rgb) {
uint cr, cg, cb;
if (sscanf(rgb.c_str(), "%u,%u,%u", &cr, &cg, &cb) == 3) {
r = cr;
g = cg;
b = cb;
}
}
uint32 AGDSEngine::Color::map(const Graphics::PixelFormat &format) const {
return format.RGBToColor(r, g, b);
}
Common::String AGDSEngine::Color::ToString() const {
return Common::String::format("#%02x%02x%02x", r, g, b);
}
bool AGDSEngine::load() {
Common::INIFile config;
Common::File configFile;
if (!configFile.open("agds.cfg"))
return false;
configFile.readLine(); // skip first line
config.setDefaultSectionName("core");
if (!config.loadFromStream(configFile))
return false;
int w = 800, h = 600, d = 32;
Common::String videoMode;
if (config.getKey("videomode", "core", videoMode) && sscanf(videoMode.c_str(), "%dx%dx%d", &w, &h, &d) == 3) {
debug("config videomode = %dx%d", w, h);
}
{
Common::String transparency, shadowMin, shadowMax;
if (config.getKey("transparency", "core", transparency)) {
_colorKey.FromString(transparency);
}
debug("transparent color: %s", _colorKey.ToString().c_str());
if (config.getKey("shadow_min", "core", shadowMin)) {
_minShadowColor.FromString(shadowMin);
}
debug("shadow color min: %s", _minShadowColor.ToString().c_str());
if (config.getKey("shadow_max", "core", shadowMax)) {
_maxShadowColor.FromString(shadowMax);
}
debug("shadow color max: %s", _maxShadowColor.ToString().c_str());
}
if (!initGraphics(w, h)) {
warning("no video mode found");
return false;
}
Common::INIFile::SectionKeyList values = config.getKeys("core");
for (Common::INIFile::SectionKeyList::iterator i = values.begin(); i != values.end(); ++i) {
if (i->key == "path") {
auto path = i->value;
if (path.hasPrefix(".\\"))
path = path.substr(2);
if (!_resourceManager.addPath(Common::Path{path, '\\'}))
return false;
}
}
if (!_data.open("data.adb"))
return false;
initSystemVariables();
setNextScreenName("main", ScreenLoadingType::Normal);
{
Common::File file;
if (file.open("patch.adb")) {
Database patch;
patch.open("patch.adb");
loadPatches(file, patch);
}
}
{
Common::ScopedPtr file(new Common::File());
if (file->open("jokes.chr")) {
_jokes.reset(new Character(this, "jokes"));
_jokes->load(*file);
}
}
return true;
}
RegionPtr AGDSEngine::loadRegion(const Common::String &name) {
debug("loading region %s", name.c_str());
Common::ScopedPtr stream(_data.getEntry(name));
if (!stream) {
error("no database entry for %s\n", name.c_str());
return {};
}
return RegionPtr(new Region(name, *stream));
}
Common::String AGDSEngine::loadText(const Common::String &entryName) {
if (entryName.empty())
return Common::String();
Common::ScopedPtr stream(_data.getEntry(entryName));
return _resourceManager.loadText(*stream);
}
ObjectPtr AGDSEngine::loadObject(const Common::String &name, const Common::String &prototype, bool allowInitialise) {
debug("loadObject %s %s, allow init: %d", name.c_str(), prototype.c_str(), allowInitialise);
Common::String clone = prototype.empty() ? name : prototype;
Common::ScopedPtr stream(_data.getEntry(clone));
if (!stream)
error("no database entry for %s\n", clone.c_str());
ObjectPtr object(new Object(name, *stream, version()));
object->allowInitialise(allowInitialise);
if (!prototype.empty()) {
object->persistent(false);
}
return object;
}
void AGDSEngine::runObject(const ObjectPtr &object) {
if (_currentScreen) {
if (_currentScreen->add(object)) {
runProcess(object);
auto it = _objectPatches.find(object->getName());
if (it != _objectPatches.end()) {
auto &patch = *it->_value;
if (!patch.region.empty()) {
RegionPtr region = loadRegion(patch.region);
debug("runObject: patch region: %s", region->toString().c_str());
object->region(region);
}
if (!patch.text.empty()) {
auto text = loadText(patch.text);
debug("runObject: patch title: %s -> %s", patch.text.c_str(), text.c_str());
object->title(text);
}
object->z(patch.z);
}
} else
debug("object %s is in scene, skip run", object->getName().c_str());
} else
warning("object %s has been loaded, but was not added to any screen", object->getName().c_str());
}
void AGDSEngine::runProcess(const ObjectPtr &object, uint ip) {
const auto &objectName = object->getName();
debug("starting process %s:%04x", object->getName().c_str(), ip);
for (uint i = 0; i < _processes.size(); ++i) {
auto &process = _processes[i];
if (ip != 0 && process && process->getName() == objectName && process->entryPoint() == ip) {
debug("found existing process, skipping...");
return;
}
if (!process) {
process = ProcessPtr(new Process(this, object, ip, version()));
process->run();
return;
}
}
error("process table exhausted");
}
bool AGDSEngine::hasActiveProcesses(const Common::String &name) const {
for (auto &process : _processes) {
if (process && process->getName() == name && !process->finished())
return true;
}
return false;
}
ObjectPtr AGDSEngine::getCurrentScreenObject(const Common::String &name) {
return _currentScreen ? _currentScreen->find(name) : ObjectPtr();
}
ObjectPtr AGDSEngine::runObject(const Common::String &name, const Common::String &prototype, bool allowInitialise) {
debug("runObject %s %s", name.c_str(), prototype.c_str());
ObjectPtr object = getCurrentScreenObject(name);
if (!object) {
object = loadObject(name, prototype, allowInitialise);
}
runObject(object);
return object;
}
PatchPtr AGDSEngine::getPatch(const Common::String &screenName) const {
auto it = _patches.find(screenName);
return it != _patches.end() ? it->_value : PatchPtr();
}
PatchPtr AGDSEngine::createPatch(const Common::String &screenName) {
auto &patch = _patches[screenName];
if (!patch)
patch = PatchPtr(new Patch());
return patch;
}
ObjectPatchPtr AGDSEngine::getObjectPatch(const Common::String &objectName) const {
auto it = _objectPatches.find(objectName);
return it != _objectPatches.end() ? it->_value : ObjectPatchPtr();
}
ObjectPatchPtr AGDSEngine::createObjectPatch(const Common::String &objectName) {
auto &patch = _objectPatches[objectName];
if (!patch)
patch = ObjectPatchPtr(new ObjectPatch());
return patch;
}
void AGDSEngine::saveScreenPatch() {
if (!_currentScreen || _currentScreenName.empty())
return;
PatchPtr &patch = _patches[_currentScreenName];
if (!patch)
patch = PatchPtr(new Patch());
_currentScreen->save(patch);
patch->characterPresent = _currentCharacter != nullptr && _currentCharacter->visible();
if (_currentCharacter) {
patch->characterPosition = _currentCharacter->position();
patch->characterDirection = _currentCharacter->direction();
}
patch->defaultMouseCursor = _defaultMouseCursorName;
}
void AGDSEngine::loadScreen(const Common::String &name, ScreenLoadingType loadingType, bool savePatch) {
debug("loadScreen %s [type: %d, save patch: %d, previous: %s]", name.c_str(), static_cast(loadingType), savePatch, _currentScreenName.c_str());
if (savePatch)
saveScreenPatch();
if (_currentCharacter && _currentCharacter->visible()) {
_currentCharacter->reset();
if (loadingType != ScreenLoadingType::Normal) {
_currentCharacter->visible(false);
}
}
resetCurrentInventoryObject();
{
bool userEnabled = _userEnabled;
_userEnabled = false;
debug("loadScreen: hiding all mouse areas");
_mouseMap.hideAll(this);
_inventory.visible(false);
_userEnabled = userEnabled;
}
auto previousScreenName = _currentScreenName;
resetCurrentScreen();
for (uint i = 0; i < _processes.size(); ++i) {
_processes[i].reset();
}
auto patch = getPatch(name);
bool doPatch = patch && loadingType != ScreenLoadingType::SaveOrLoad;
bool hasScreenPatch = doPatch && patch->screenSaved;
_currentScreenName = name;
_currentScreen.reset();
auto screenObject = loadObject(name, Common::String(), !hasScreenPatch);
_currentScreen.reset(new Screen(this, screenObject, loadingType, previousScreenName));
runProcess(screenObject);
if (doPatch) {
_currentScreen->load(patch);
if (_currentCharacter) {
_currentCharacter->visible(patch->characterPresent);
_currentCharacter->position(patch->characterPosition);
_currentCharacter->direction(patch->characterDirection);
}
if (!patch->defaultMouseCursor.empty())
loadDefaultMouseCursor(patch->defaultMouseCursor);
}
}
void AGDSEngine::resetCurrentScreen() {
_currentScreen.reset();
_currentScreenName.clear();
}
void AGDSEngine::runProcesses() {
fadeAndReactivate();
for (uint i = 0; i < _processes.size(); ++i) {
ProcessPtr process = _processes[i];
if (!process || process->parentScreenName() != _currentScreenName)
continue;
if (process->active()) {
process->run();
}
if (process->finished()) {
debug("deleting process %s", process->getName().c_str());
_processes[i].reset();
} else {
// debug("suspended process %s", process->getName().c_str());
}
}
}
Console *AGDSEngine::getConsole() {
return static_cast(getDebugger());
}
void AGDSEngine::newGame() {
SystemVariable *doneVar = getSystemVariable("done_resources");
Common::String done = doneVar->getString();
if (!done.empty()) {
debug("running engine resource dtor: %s", done.c_str());
runObject(done);
}
_patches.clear();
_objectPatches.clear();
_inventory.clear();
_globals.clear();
Console *console = getConsole();
if (console)
console->clearVars();
SystemVariable *initVar = getSystemVariable("init_resources");
Common::String init = initVar->getString();
if (!init.empty()) {
debug("running engine resource ctor: %s", init.c_str());
runObject(init);
}
}
void AGDSEngine::curtain(const Common::String &process, int screen, int sound, int music, bool updateGlobals) {
assert(!process.empty());
if (updateGlobals) {
getSystemVariable("screen_curtain")->setInteger(screen >= 0 ? screen : -screen);
getSystemVariable("sound_curtain")->setInteger(sound >= 0 ? sound : -sound);
getSystemVariable("music_curtain")->setInteger(music >= 0 ? music : -music);
}
_curtainProcess = process;
_curtainTimer = 100;
_curtainScreen = screen;
enableSystemUser(false);
fadeAndReactivate();
}
void AGDSEngine::fadeAndReactivate() {
if (_curtainTimer >= 0 && !_curtainProcess.empty()) {
_curtainTimer -= 5;
if (_curtainTimer < 0) {
_curtainTimer = -1;
enableSystemUser(true);
reactivate(_curtainProcess, "curtainTimer");
_curtainProcess.clear();
}
}
}
void AGDSEngine::tick() {
loadNextScreen();
if (!_currentScreen)
return;
_currentScreen->tick();
_mouseMap.hideInactive(this, _mouse);
bool dialogActive = _dialog.tick();
if (!dialogActive) {
tickInventory();
if (_currentCharacter)
_currentCharacter->tick(!_textLayout.valid());
}
runProcesses();
}
AnimationPtr AGDSEngine::loadMouseCursor(const Common::String &name) {
auto animation = loadAnimation(name);
animation->loop(true);
animation->phaseVar(Common::String());
return animation;
}
void AGDSEngine::changeMouseArea(int id, int enabled) {
if (id < 0) {
warning("invalid mouse area %d", id);
return;
}
MouseRegion *mouseArea = _mouseMap.find(id);
if (mouseArea) {
switch (enabled) {
case 1:
debug("enabling mouse area %d", id);
mouseArea->enable();
break;
case 0:
debug("disabling mouse area %d", id);
mouseArea->hide(this);
mouseArea->disable();
break;
case -1:
debug("removing mouse area %d", id);
_mouseMap.remove(id);
break;
default:
warning("invalid value for changeMouseArea: %d", enabled);
}
} else
warning("mouse area %d could not be found", id);
}
Common::Error AGDSEngine::run() {
if (!load())
return Common::kNoGameDataFoundError;
setDebugger(new Console(this));
int loadSlot = ConfMan.getInt("save_slot");
debug("save_slot = %d", loadSlot);
if (loadSlot >= 0)
loadGameState(loadSlot);
Common::EventManager *eventManager = _system->getEventManager();
uint32 frameStarted = _system->getMillis();
while (!shouldQuit()) {
Common::Event event;
while (eventManager->pollEvent(event)) {
if (!_currentScreen)
continue;
switch (event.type) {
case Common::EVENT_KEYDOWN: {
Common::String key;
switch (event.kbd.keycode) {
case Common::KEYCODE_SPACE:
skipFilm();
key = "space";
break;
case Common::KEYCODE_ESCAPE:
skipFilm();
key = "escape";
break;
case Common::KEYCODE_TAB:
key = "tab";
break;
case Common::KEYCODE_f:
if (event.kbd.flags & Common::KBD_CTRL) {
_fastMode = !_fastMode;
}
break;
case Common::KEYCODE_LCTRL:
_hintMode = true;
break;
default:
if (event.kbd.ascii)
key = Common::String(static_cast(event.kbd.ascii));
};
if (userEnabled() && !key.empty()) {
Screen::KeyHandler handler = _currentScreen->findKeyHandler(key);
if (handler.object) {
debug("found handler for key %s: %s %08x", key.c_str(), handler.object->getName().c_str(), handler.ip + 7);
runProcess(handler.object, handler.ip);
}
}
} break;
case Common::EVENT_KEYUP: {
switch (event.kbd.keycode) {
case Common::KEYCODE_LCTRL:
_hintMode = false;
break;
default:
break;
}
} break;
case Common::EVENT_MOUSEMOVE:
_mouse = event.mouse;
break;
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
_mouse = event.mouse;
if (userEnabled()) {
bool lclick = event.type == Common::EVENT_LBUTTONDOWN;
debug("%s %d, %d inv: %s", lclick ? "lclick" : "rclick", _mouse.x, _mouse.y,
_currentInventoryObject ? _currentInventoryObject->getName().c_str() : "none");
ObjectPtr runObject;
uint ip = 0;
if (_currentInventoryObject) {
if (lclick) {
ip = _currentInventoryObject->useOnHandler();
if (ip) {
debug("found useOn handler in current inventory object");
runObject = _currentInventoryObject;
}
} else {
ip = _currentInventoryObject->getUseHandler(_currentInventoryObject->getName());
if (ip) {
debug("found self use handler");
runObject = _currentInventoryObject;
} else {
ip = _currentInventoryObject->throwHandler();
if (ip) {
debug("found throw handler in current inventory object");
runObject = _currentInventoryObject;
}
}
}
}
if (!runObject) {
auto objects = _currentScreen->find(_mouse);
if (objects.empty() && !_currentInventoryObject) { // allow inventory to be selected
auto object = _inventory.find(_mouse);
if (object)
objects.push_back(object);
}
for (auto &object : objects) {
debug("found object %s", object->getName().c_str());
if (lclick) {
if (_currentInventoryObject) {
ip = object->getUseHandler(_currentInventoryObject->getName());
if (ip) {
runObject = object;
}
if (ip)
debug("found use handler");
if (!ip) {
ip = object->getHandlerC1();
if (ip) {
debug("found C1 handler");
runObject = object;
}
}
} else if (!ip) {
ip = object->getClickHandler();
if (ip) {
debug("found click handler");
runObject = object;
}
}
} else if (!_currentInventoryObject) {
ip = object->getExamineHandler();
if (ip) {
debug("found examine handler");
runObject = object;
}
}
if (runObject)
break;
}
}
if (runObject) {
if (ip) {
debug("found handler: %s %04x", runObject->getName().c_str(), ip);
runProcess(runObject, ip);
break;
}
} else {
debug("no handler found");
if (lclick) {
if (_currentCharacter && _currentCharacter->active() && _currentScreen && _currentScreen->region()) {
auto ®ion = _currentScreen->region();
if (region->pointIn(_mouse)) {
// FIXME: some object requires character to be in "trap" region
// Remove this after movement implementation.
_currentCharacter->moveTo(Common::String(), _mouse, -1);
}
}
auto scroll = _currentScreen->scrollPosition();
scroll.x += _mouse.x - g_system->getWidth() / 2;
_currentScreen->scrollTo(scroll);
}
}
}
break;
default:
break;
}
}
_soundManager.tick();
if (active())
tick();
Graphics::Surface *backbuffer = _system->lockScreen();
backbuffer->fillRect(backbuffer->getRect(), 0);
AnimationPtr mouseCursor;
if (userEnabled() && _currentScreen) {
auto objects = _currentScreen->find(_mouse);
if (objects.empty()) {
auto object = _inventory.find(_mouse);
if (object)
objects.push_back(object);
}
AnimationPtr cursor;
for (auto &object : objects) {
cursor = object->getMouseCursor();
if (cursor)
break;
}
if (cursor)
mouseCursor = cursor;
for (auto &object : objects) {
if (!object->title().empty()) {
auto &title = object->title();
auto font = getFont(getSystemVariable("objtext_font")->getInteger());
int w = font->getStringWidth(title);
int x = getSystemVariable("objtext_x")->getInteger() - w / 2;
int y = getSystemVariable("objtext_y")->getInteger();
font->drawString(backbuffer, title, x, y, backbuffer->w - x, 0);
break;
}
}
}
if (_mjpgPlayer) {
_mjpgPlayer->paint(*this, *backbuffer);
if (_mjpgPlayer->eos()) {
skipFilm();
}
} else if (_currentScreen) {
_currentScreen->paint(*backbuffer);
}
if (!mouseCursor)
mouseCursor = _defaultMouseCursor;
if (userEnabled()) {
if (auto *picture = _currentInventoryObject ? _currentInventoryObject->getPicture() : nullptr) {
Common::Rect srcRect = picture->getBounds();
Common::Point dst = _mouse;
dst.x -= srcRect.width() / 2;
dst.y -= srcRect.height() / 2;
uint32 color = picture->format.ARGBToColor(_currentInventoryObject->alpha(), 255, 255, 255);
if (Common::Rect::getBlitRect(dst, srcRect, backbuffer->getRect())) {
picture->blendBlitTo(*backbuffer, dst.x, dst.y, Graphics::FLIP_NONE, &srcRect, color);
}
} else if (auto cursor = (_currentInventoryObject ? _currentInventoryObject->getMouseCursor() : AnimationPtr())) {
cursor->rotate(_currentInventoryObject->rotation());
cursor->tick();
auto pos = _mouse;
pos.x -= cursor->visibleCenter();
pos.y -= cursor->visibleHeight() / 2;
cursor->paint(*backbuffer, pos);
} else if (mouseCursor) {
mouseCursor->tick();
mouseCursor->paint(*backbuffer, _mouse);
}
}
if (_textLayout.valid()) {
if (_syncSoundId >= 0) {
if (!_soundManager.playing(_syncSoundId)) {
debug("sync sound %d finished, resetting text layout...", _syncSoundId);
_textLayout.reset(*this);
_syncSoundId = -1;
_tellTextTimer = 0;
}
} else if (_tellTextTimer > 0) {
--_tellTextTimer;
} else {
_tellTextTimer = 0;
debug("text timer expired, resetting text layout");
_textLayout.reset(*this);
}
}
if (_textLayout.valid()) {
_textLayout.paint(*this, *backbuffer);
}
if (_curtainTimer != -1 && _currentScreen) {
if (_curtainScreen > 0)
_currentScreen->fade(100 - _curtainTimer);
else if (_curtainScreen < 0)
_currentScreen->fade(_curtainTimer);
}
_system->unlockScreen();
_system->updateScreen();
if (!_fastMode) {
uint32 ts = _system->getMillis();
if (_mjpgPlayer) {
uint32 elapsed = ts - _filmStarted;
uint32 expected = _mjpgPlayer->getNextFrameTimestamp();
if (expected > elapsed)
_system->delayMillis(expected - elapsed);
} else {
static const uint32 kFPS = 25;
static const uint32 kMaxTick = 1000 / kFPS;
uint32 dt = ts - frameStarted;
if (dt < kMaxTick)
_system->delayMillis(kMaxTick - dt);
}
frameStarted = ts;
}
}
return Common::kNoError;
}
void AGDSEngine::playFilm(Process &process, const Common::String &video, const Common::String &audio, const Common::String &subtitles) {
_mjpgPlayer.reset();
_filmProcess = process.getName();
_mjpgPlayer.reset(new MJPGPlayer(_resourceManager.getResource(video), subtitles));
_soundManager.stopAll();
_filmStarted = _system->getMillis();
_syncSoundId = _soundManager.play(process.getName(), Common::String(), audio, Common::String(), true, 100, 0);
}
void AGDSEngine::skipFilm() {
debug("skip");
_mjpgPlayer.reset();
if (_syncSoundId >= 0) {
debug("skip: stopping sound %d", _syncSoundId);
_mixer->stopID(_syncSoundId);
_syncSoundId = -1;
}
_tellTextTimer = 0;
_textLayout.reset(*this);
reactivate(_filmProcess, "skipFilm");
}
int AGDSEngine::appendToSharedStorage(const Common::String &value) {
int index = _sharedStorageIndex;
_sharedStorage[-2 - (_sharedStorageIndex--)] = value;
if (_sharedStorageIndex <= -12)
_sharedStorageIndex = -2;
return index;
}
const Common::String &AGDSEngine::getSharedStorage(int id) const {
int index = -2 - id;
if (index < 0 || index >= 10)
error("shared storage id is out of range");
return _sharedStorage[index];
}
void AGDSEngine::setGlobal(const Common::String &name, int value) {
debug("setting global %s -> %d", name.c_str(), value);
bool create = !_globals.contains(name);
_globals.setVal(name, value);
if (create) {
Console *console = getConsole();
if (console)
console->registerVar(name, &_globals[name]);
}
}
int AGDSEngine::getGlobal(const Common::String &name) const {
GlobalsType::const_iterator i = _globals.find(name);
if (i != _globals.end())
return i->_value;
else {
// debug("global %s was not declared, returning 0", name.c_str());
return 0;
}
}
AnimationPtr AGDSEngine::loadAnimation(const Common::String &name) {
debug("loadAnimation %s", name.c_str());
Common::SharedPtr animation(new Animation(this, name));
if (v2()) {
debug("v2 stub");
return animation;
}
Common::ScopedPtr stream(_resourceManager.getResource(name));
if (!stream)
error("could not load animation from %s", name.c_str());
if (!animation->load(stream.release(), name))
error("could not load animation from %s", name.c_str());
return animation;
}
AnimationPtr AGDSEngine::findAnimationByPhaseVar(const Common::String &phaseVar) {
return _currentScreen ? _currentScreen->findAnimationByPhaseVar(phaseVar) : nullptr;
}
void AGDSEngine::loadCharacter(const Common::String &id, const Common::String &filename, const Common::String &object) {
debug("loadCharacter %s %s %s", id.c_str(), filename.c_str(), object.c_str());
_currentCharacterName = id;
_currentCharacterFilename = filename;
_currentCharacterObject = object;
_currentCharacter.reset(new Character(this, id));
auto resName = loadText(filename);
if (v2()) {
debug("character resource name: %s, stub\n", resName.c_str());
_currentCharacter->associate(object);
return;
}
Common::ScopedPtr stream(_resourceManager.getResource(resName));
_currentCharacter->load(*stream);
_currentCharacter->associate(object);
}
Graphics::ManagedSurface *AGDSEngine::loadPicture(const Common::String &name) {
return convertToTransparent(_resourceManager.loadPicture(name, _pixelFormat));
}
int AGDSEngine::loadFromCache(const Common::String &name) const {
PictureCacheLookup::const_iterator i = _pictureCacheLookup.find(name);
return i != _pictureCacheLookup.end() ? i->_value : -1;
}
int AGDSEngine::saveToCache(const Common::String &name, Graphics::ManagedSurface *surface) {
if (!surface)
return -1;
int id = _pictureCacheId++;
_pictureCacheLookup[name] = id;
_pictureCache[id].reset(surface);
return id;
}
Graphics::ManagedSurface *AGDSEngine::loadFromCache(int id) const {
PictureCacheType::const_iterator i = _pictureCache.find(id);
return (i != _pictureCache.end()) ? i->_value.get() : NULL;
}
void AGDSEngine::loadFont(int id, const Common::String &name, int gw, int gh) {
if (v2()) {
debug("loadTTF %d %s, pixelSize: %d", id, name.c_str(), gh);
#ifdef USE_FREETYPE2
_fonts[id].reset(Graphics::loadTTFFontFromArchive(name, gh));
#endif
} else {
debug("loadFont %d %s %d %d", id, name.c_str(), gw, gh);
Graphics::ManagedSurface *surface = loadPicture(name);
_fonts[id].reset(new Font(surface, gw, gh));
}
}
const Graphics::Font *AGDSEngine::getFont(int id) const {
FontsType::const_iterator i = _fonts.find(id);
if (i == _fonts.end())
error("no font with id %d", id);
return i->_value.get();
}
Graphics::Surface *AGDSEngine::createSurface(int w, int h) {
Graphics::Surface *surface = new Graphics::Surface();
surface->create(w, h, _pixelFormat);
return surface;
}
Graphics::ManagedSurface *AGDSEngine::convertToTransparent(Graphics::Surface *s) {
if (!s)
return NULL;
Graphics::ManagedSurface *t = new Graphics::ManagedSurface(s->w, s->h, _pixelFormat);
assert(s->format.bytesPerPixel == 4);
assert(t->format.bytesPerPixel == 4);
uint8 *dst = static_cast(t->getPixels());
const uint8 *src = static_cast(s->getPixels());
uint8 shadowAlpha = 255 * _shadowIntensity / 100;
auto &dstFormat = t->format;
auto &srcFormat = s->format;
auto dstBPP = dstFormat.bytesPerPixel;
auto srcBPP = srcFormat.bytesPerPixel;
uint dstDelta = t->pitch - t->w * dstBPP;
uint srcDelta = s->pitch - s->w * srcBPP;
for (uint16 i = 0; i < t->h; ++i, dst += dstDelta, src += srcDelta) {
for (uint16 j = 0; j < t->w; ++j, dst += dstBPP, src += srcBPP) {
uint8 r, g, b, a;
srcFormat.colorToARGB(*reinterpret_cast(src), a, r, g, b);
if (r == _colorKey.r && g == _colorKey.g && b == _colorKey.b) {
r = g = b = a = 0;
} else if (
r >= _minShadowColor.r && r <= _maxShadowColor.r &&
g >= _minShadowColor.g && g <= _maxShadowColor.g &&
b >= _minShadowColor.b && b <= _maxShadowColor.b) {
r = g = b = 0;
a = shadowAlpha;
}
*reinterpret_cast(dst) = dstFormat.ARGBToColor(a, r, g, b);
}
}
return t;
}
void AGDSEngine::addSystemVar(const Common::String &name, SystemVariable *var) {
_systemVarList.push_back(name);
_systemVars[name] = var;
}
void AGDSEngine::tell(Process &process, const Common::String ®ionName, Common::String text, Common::String sound, bool npc) {
if (getSystemVariable("tell_close_inv")->getInteger()) {
_inventory.enable(false);
}
int font_id = getSystemVariable(npc ? "npc_tell_font" : "tell_font")->getInteger();
Common::Point pos;
if (regionName.empty()) {
pos.x = getSystemVariable("subtitle_x")->getInteger();
pos.y = getSystemVariable("subtitle_y")->getInteger();
} else {
RegionPtr region = loadRegion(regionName);
pos = region->center;
}
if (text.empty())
text = _dialog.getNextDialogLine();
if (sound.empty())
sound = _dialog.getNextDialogSound();
_tellTextTimer = _dialog.textDelay(text);
_textLayout.layout(*this, process, text, pos, font_id, npc);
if (!sound.empty()) {
int id = _soundManager.play(Common::String(), Common::String(), sound, Common::String(), true, 100, 0);
playSoundSync(id);
}
}
void AGDSEngine::stopAmbientSound() {
if (_ambientSoundId >= 0) {
_mixer->stopID(_ambientSoundId);
_ambientSoundId = -1;
}
}
void AGDSEngine::initSystemVariables() {
addSystemVar("inventory_scr", new StringSystemVariable());
addSystemVar("escape_scr", new StringSystemVariable("none"));
addSystemVar("load_scr", new StringSystemVariable());
addSystemVar("save_scr", new StringSystemVariable());
addSystemVar("gfx_bright", new IntegerSystemVariable(50));
addSystemVar("gfx_contrast", new IntegerSystemVariable(50));
addSystemVar("sound_volume", new IntegerSystemVariable(100));
addSystemVar("music_volume", new IntegerSystemVariable(80));
addSystemVar("tell_volume", new IntegerSystemVariable(100));
addSystemVar("text_speed", new IntegerSystemVariable(70));
addSystemVar("tell_mode", new IntegerSystemVariable(3));
addSystemVar("version", new IntegerSystemVariable(1));
addSystemVar("objtext_x", new IntegerSystemVariable(-1));
addSystemVar("objtext_y", new IntegerSystemVariable(-1));
addSystemVar("objtext_mode", new IntegerSystemVariable(-1));
addSystemVar("objtext_font", new IntegerSystemVariable(-1));
addSystemVar("inv_open", new StringSystemVariable());
addSystemVar("inv_close", new StringSystemVariable());
addSystemVar("inv_region", new StringSystemVariable());
addSystemVar("anim_zoom", new IntegerSystemVariable(1));
addSystemVar("screen_curtain", new IntegerSystemVariable(1));
addSystemVar("music_curtain", new IntegerSystemVariable(1));
addSystemVar("sound_curtain", new IntegerSystemVariable(1));
addSystemVar("old_music_volume", new IntegerSystemVariable());
addSystemVar("old_sound_volume", new IntegerSystemVariable());
addSystemVar("old_screen_fade", new IntegerSystemVariable());
addSystemVar("subtitle_x", new IntegerSystemVariable());
addSystemVar("subtitle_y", new IntegerSystemVariable());
addSystemVar("subtitle_type", new IntegerSystemVariable(3));
addSystemVar("subtitles", new IntegerSystemVariable());
addSystemVar("tell_font", new IntegerSystemVariable());
addSystemVar("npc_tell_font", new IntegerSystemVariable());
addSystemVar("edit_font", new IntegerSystemVariable());
addSystemVar("delay_after_tell", new IntegerSystemVariable());
addSystemVar("scroll_factor", new IntegerSystemVariable(30));
addSystemVar("dialog_var", new IntegerSystemVariable());
addSystemVar("subtitle_width", new IntegerSystemVariable(-1));
addSystemVar("flash_mouse", new IntegerSystemVariable());
addSystemVar("scale_char", new IntegerSystemVariable());
addSystemVar("init_resources", new StringSystemVariable());
addSystemVar("done_resources", new StringSystemVariable());
addSystemVar("tell_close_inv", new IntegerSystemVariable(1));
addSystemVar("gamma", new IntegerSystemVariable());
}
SystemVariable *AGDSEngine::getSystemVariable(const Common::String &name) {
SystemVariablesType::iterator i = _systemVars.find(name);
if (i != _systemVars.end())
return i->_value;
error("no system variable %s", name.c_str());
}
void AGDSEngine::tickInventory() {
const Common::String &inv_region_name = getSystemVariable("inv_region")->getString();
if (!inv_region_name.empty()) {
if (!_inventoryRegion || _inventoryRegionName != inv_region_name) {
_inventoryRegionName = inv_region_name;
_inventoryRegion = loadRegion(inv_region_name);
}
} else {
_inventoryRegionName.clear();
_inventoryRegion.reset();
}
if (userEnabled() && _inventoryRegion && _inventory.enabled()) {
if (_mouseMap.disabled()) {
_inventory.visible(false);
} else {
if (_userEnabled) {
_inventory.visible(_inventoryRegion->pointIn(_mouse));
}
}
}
}
bool AGDSEngine::hasFeature(EngineFeature f) const {
switch (f) {
case kSupportsSubtitleOptions:
case kSupportsReturnToLauncher:
case kSupportsChangingOptionsDuringRuntime:
return true;
default:
return false;
}
}
void AGDSEngine::loadPatches(Common::SeekableReadStream &file, Database &db) {
debug("loading patches");
_patches.clear();
_objectPatches.clear();
Common::Array entries = db.getEntries();
for (uint i = 0; i < entries.size(); ++i) {
const Common::String &name = entries[i];
if (name[0] == '_')
continue;
debug("loading patch for %s", name.c_str());
Common::ScopedPtr patchStream(db.getEntry(file, name));
if (patchStream->size() != ObjectPatch::Size) {
PatchPtr patch(new Patch());
patch->load(*patchStream);
_patches[name] = patch;
} else {
ObjectPatchPtr patch(new ObjectPatch());
patch->load(*patchStream);
_objectPatches[name] = patch;
}
}
debug("done loading patches");
}
Common::Error AGDSEngine::loadGameState(int slot) {
// saveAutosaveIfEnabled();
auto fileName = getSaveStateName(slot);
Common::ScopedPtr saveFile(_saveFileMan->openForLoading(fileName));
if (!saveFile)
return Common::kReadingFailed;
Database db;
if (!db.open(fileName, *saveFile))
return Common::kReadingFailed;
stopAmbientSound();
_syncSoundId = -1;
_soundManager.stopAll();
{
// Compiled version (should be 2)
Common::ScopedPtr agds_ver(db.getEntry(*saveFile, "__agds_ver"));
uint version = agds_ver->readUint32LE();
debug("version: %d", version);
if (version != 2) {
warning("wrong engine version (%d)", version);
return Common::kReadingFailed;
}
}
SystemVariable *doneVar = getSystemVariable("done_resources");
Common::String done = doneVar->getString();
if (!done.empty())
runObject(done);
{
// Current character
Common::ScopedPtr agds_c(db.getEntry(*saveFile, "__agds_c"));
Common::String object = readString(*agds_c);
Common::String filename = readString(*agds_c);
Common::String id = readString(*agds_c);
debug("savegame character %s %s -> %s %s", object.c_str(), filename.c_str(), filename.c_str(), id.c_str());
loadCharacter(id, filename, object);
auto character = getCharacter(id);
if (character) {
character->loadState(*agds_c);
character->visible(true);
} else
warning("no character");
}
{
// Saved ambient sound
Common::ScopedPtr agds_a(db.getEntry(*saveFile, "__agds_a"));
auto resource = readString(*agds_a);
auto filename = loadText(resource);
auto phaseVar = readString(*agds_a);
uint volume = agds_a->readUint32LE();
uint type = agds_a->readUint32LE();
debug("saved audio state: sample: '%s:%s', var: '%s' %u %u", resource.c_str(), filename.c_str(), phaseVar.c_str(), volume, type);
debug("phase var for sample -> %d", getGlobal(phaseVar));
_ambientSoundId = _soundManager.play(Common::String(), resource, filename, phaseVar, true,
volume, /*pan=*/0, /*id=*/-1, /*ambient=*/true);
debug("ambient sound id = %d", _ambientSoundId);
}
{
// System vars
Common::ScopedPtr agds_d(db.getEntry(*saveFile, "__agds_d"));
for (uint i = 0, n = _systemVarList.size(); i < n; ++i) {
Common::String &name = _systemVarList[i];
_systemVars[name]->read(*agds_d);
}
}
{
Common::ScopedPtr agds_i(db.getEntry(*saveFile, "__agds_i"));
_inventory.load(*agds_i);
}
Common::String screenName;
{
// Palette and screen name
Common::ScopedPtr agds_s(db.getEntry(*saveFile, "__agds_s"));
screenName = readString(*agds_s);
}
{
// Global vars
_globals.clear();
Common::ScopedPtr agds_v(db.getEntry(*saveFile, "__agds_v"));
uint32 n = agds_v->readUint32LE();
debug("reading %u vars...", n);
while (n--) {
Common::String name = readString(*agds_v);
int value = agds_v->readSint32LE();
debug("setting var %s to %d", name.c_str(), value);
setGlobal(name, value);
}
}
SystemVariable *initVar = getSystemVariable("init_resources");
runObject(initVar->getString());
loadPatches(*saveFile, db);
loadScreen(screenName, ScreenLoadingType::Normal, false);
return Common::kNoError;
}
void AGDSEngine::loadNextScreen() {
while (!_nextScreenName.empty()) {
Common::String nextScreenName = _nextScreenName;
debug("loadNextScreen %s", nextScreenName.c_str());
auto nextScreenType = _nextScreenType;
_nextScreenName.clear();
_nextScreenType = ScreenLoadingType::Normal;
loadScreen(nextScreenName, nextScreenType);
}
}
Common::Error AGDSEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
debug("saveGameState %d %s autosave: %d", slot, desc.c_str(), isAutosave);
auto fileName = getSaveStateName(slot);
Common::ScopedPtr saveFile(getSaveFileManager()->openForSaving(fileName, false));
if (!saveFile)
return Common::kWritingFailed;
while (_currentScreen && _currentScreen->loadingType() == ScreenLoadingType::SaveOrLoad) {
returnToPreviousScreen();
loadNextScreen();
}
Common::HashMap> entries;
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
stream.writeUint32LE(2);
entries["__agds_ver"].assign(stream.getData(), stream.getData() + stream.size());
}
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
writeString(stream, _currentCharacterObject);
writeString(stream, _currentCharacterFilename);
writeString(stream, _currentCharacterName);
auto character = getCharacter(_currentCharacterName);
if (character) {
character->saveState(stream);
} else
warning("no character to save");
auto size = stream.size();
if (size < 106) {
Common::Array filler(106 - size);
stream.write(filler.data(), filler.size());
}
entries["__agds_c"].assign(stream.getData(), stream.getData() + stream.size());
}
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
writeString(stream, _currentScreenName);
debug("saving screen name: %s", _currentScreenName.c_str());
char palette[0x300];
memset(palette, 0xaa, sizeof(palette));
stream.write(palette, sizeof(palette));
entries["__agds_s"].assign(stream.getData(), stream.getData() + stream.size());
}
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
for (uint i = 0, n = _systemVarList.size(); i < n; ++i) {
Common::String &name = _systemVarList[i];
_systemVars[name]->write(stream);
}
entries["__agds_d"].assign(stream.getData(), stream.getData() + stream.size());
}
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
auto n = _globals.size();
stream.writeUint32LE(n);
debug("saving %u vars...", n);
for (auto &global : _globals) {
writeString(stream, global._key);
stream.writeUint32LE(global._value);
}
entries["__agds_v"].assign(stream.getData(), stream.getData() + stream.size());
}
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
_inventory.save(stream);
entries["__agds_i"].assign(stream.getData(), stream.getData() + stream.size());
}
{
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
debug("ambient sound id: %d", _ambientSoundId);
auto sound = _soundManager.find(_ambientSoundId);
if (sound) {
writeString(stream, sound->resource);
writeString(stream, sound->phaseVar);
} else {
writeString(stream, Common::String());
writeString(stream, Common::String());
}
stream.writeUint32LE(70); // volume
stream.writeUint32LE(30); // type
entries["__agds_a"].assign(stream.getData(), stream.getData() + stream.size());
}
for (auto &objectPatch : _objectPatches) {
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
objectPatch._value->save(stream);
entries[objectPatch._key].assign(stream.getData(), stream.getData() + stream.size());
}
for (auto &patch : _patches) {
Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
patch._value->save(stream);
entries[patch._key].assign(stream.getData(), stream.getData() + stream.size());
}
Database::write(*saveFile, entries);
return Common::kNoError;
}
void AGDSEngine::reactivate(const Common::String &name, const Common::String &where, bool runNow) {
if (name.empty())
return;
for (uint i = 0; i < _processes.size(); ++i) {
ProcessPtr &process = _processes[i];
if (process && process->getName() == name) {
debug("reactivate %s now: %d, %s", name.c_str(), runNow, where.c_str());
process->activate();
if (runNow)
_pendingReactivatedProcesses.push_back(process);
}
}
}
void AGDSEngine::runPendingReactivatedProcesses() {
while (!_pendingReactivatedProcesses.empty()) {
ProcessListType processes;
_pendingReactivatedProcesses.swap(processes);
for (auto &process : processes) {
process->run();
}
}
}
ProcessPtr AGDSEngine::findProcess(const Common::String &name) const {
for (uint i = 0; i < _processes.size(); ++i) {
const ProcessPtr &process = _processes[i];
if (process && !process->finished() && process->getName() == name)
return process;
}
return {};
}
void AGDSEngine::currentInventoryObject(const ObjectPtr &object) {
if (_currentInventoryObject)
warning("setting current inventory object to %s, old: %s", object ? object->getName().c_str() : "none",
_currentInventoryObject ? _currentInventoryObject->getName().c_str() : "none");
_currentInventoryObject = object;
}
ObjectPtr AGDSEngine::popCurrentInventoryObject() {
auto object = _currentInventoryObject;
_currentInventoryObject.reset();
return object;
}
void AGDSEngine::resetCurrentInventoryObject() {
auto object = popCurrentInventoryObject();
if (!object)
return;
debug("returnCurrentInventoryObject %s", object->getName().c_str());
_inventory.add(object);
runObject(object);
}
void AGDSEngine::setNextScreenName(const Common::String &nextScreenName, ScreenLoadingType type) {
debug("setNextScreenName %s:%d", nextScreenName.c_str(), static_cast(type));
_nextScreenName = nextScreenName;
_nextScreenType = type;
}
void AGDSEngine::returnToPreviousScreen() {
auto previousScreenName = _currentScreen ? _currentScreen->getPreviousScreenName() : Common::String();
debug("returnToPreviousScreen from %s, previous screen: %s", _currentScreenName.c_str(), previousScreenName.c_str());
if (!previousScreenName.empty()) {
_nextScreenName = previousScreenName;
_nextScreenType = ScreenLoadingType::Previous;
}
}
int AGDSEngine::getRandomNumber(int max) {
return max > 0 ? _random.getRandomNumber(max - 1) : 0;
}
bool AGDSEngine::v2() const {
return _gameDescription->flags & AGDS_V2;
}
int AGDSEngine::version() const {
if (_gameDescription->flags & ADGF_DEMO)
return 0;
if (v2())
return 2;
return 1;
}
} // End of namespace AGDS