scummvm/engines/composer/composer.cpp
Colin Snover 432fd522d2 ENGINES: Remove default1x scaler flag
This flag is removed for a few reasons:

* Engines universally set this flag to true for widths > 320,
  which made it redundant everywhere;
* This flag functioned primarily as a "force 1x scaler" flag,
  since its behaviour was almost completely undocumented and users
  would need to figure out that they'd need an explicit non-default
  scaler set to get a scaler to operate at widths > 320;
* (Most importantly) engines should not be in the business of
  deciding how the backend may choose to render its virtual screen.
  The choice of rendering behaviour belongs to the user, and the
  backend, in that order.

A nearby future commit restores the default1x scaler behaviour in
the SDL backend code for the moment, but in the future it is my
hope that there will be a better configuration UI to allow users
to specify how they want scaling to work for high resolutions.
2017-10-07 12:30:29 -05:00

690 lines
19 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/random.h"
#include "common/keyboard.h"
#include "graphics/cursorman.h"
#include "graphics/surface.h"
#include "graphics/pixelformat.h"
#include "graphics/wincursor.h"
#include "engines/util.h"
#include "composer/composer.h"
#include "composer/graphics.h"
#include "composer/resource.h"
#include "composer/console.h"
namespace Composer {
ComposerEngine::ComposerEngine(OSystem *syst, const ComposerGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
_rnd = new Common::RandomSource("composer");
_audioStream = NULL;
_currSoundPriority = 0;
_currentTime = 0;
_lastTime = 0;
_needsUpdate = true;
_directoriesToStrip = 1;
_mouseVisible = true;
_mouseEnabled = false;
_mouseSpriteId = 0;
_lastButton = NULL;
_console = NULL;
}
ComposerEngine::~ComposerEngine() {
DebugMan.clearAllDebugChannels();
stopPipes();
for (Common::List<OldScript *>::iterator i = _oldScripts.begin(); i != _oldScripts.end(); i++)
delete *i;
for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++)
delete *i;
for (Common::List<Library>::iterator i = _libraries.begin(); i != _libraries.end(); i++)
delete i->_archive;
for (Common::List<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++)
i->_surface.free();
delete _rnd;
delete _console;
}
Common::Error ComposerEngine::run() {
Common::Event event;
_vars.resize(1000);
for (uint i = 0; i < _vars.size(); i++)
_vars[i] = 0;
_queuedScripts.resize(10);
for (uint i = 0; i < _queuedScripts.size(); i++) {
_queuedScripts[i]._count = 0;
_queuedScripts[i]._scriptId = 0;
}
if (!_bookIni.loadFromFile("book.ini")) {
_directoriesToStrip = 0;
if (!_bookIni.loadFromFile("programs/book.ini")) {
// mac version?
if (!_bookIni.loadFromFile("Darby the Dragon.ini"))
if (!_bookIni.loadFromFile("Gregory.ini"))
error("failed to find book.ini");
}
}
uint width = 640;
if (_bookIni.hasKey("Width", "Common"))
width = atoi(getStringFromConfig("Common", "Width").c_str());
uint height = 480;
if (_bookIni.hasKey("Height", "Common"))
height = atoi(getStringFromConfig("Common", "Height").c_str());
initGraphics(width, height);
_screen.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
Graphics::Cursor *cursor = Graphics::makeDefaultWinCursor();
CursorMan.replaceCursor(cursor->getSurface(), cursor->getWidth(), cursor->getHeight(), cursor->getHotspotX(),
cursor->getHotspotY(), cursor->getKeyColor());
CursorMan.replaceCursorPalette(cursor->getPalette(), cursor->getPaletteStartIndex(), cursor->getPaletteCount());
delete cursor;
_console = new Console(this);
loadLibrary(0);
uint fps = atoi(getStringFromConfig("Common", "FPS").c_str());
uint frameTime = 125; // Default to 125ms (1000/8)
if (fps != 0)
frameTime = 1000 / fps;
else
warning("FPS in book.ini is zero. Defaulting to 8...");
uint32 lastDrawTime = 0;
_lastSaveTime = _system->getMillis();
bool loadFromLauncher = ConfMan.hasKey("save_slot");
while (!shouldQuit()) {
for (uint i = 0; i < _pendingPageChanges.size(); i++) {
if (_pendingPageChanges[i]._remove)
unloadLibrary(_pendingPageChanges[i]._pageId);
else
loadLibrary(_pendingPageChanges[i]._pageId);
lastDrawTime = 0;
}
_pendingPageChanges.clear();
uint32 thisTime = _system->getMillis();
// maintain our own internal timing, since otherwise we get
// confused when starved of CPU (for example when the user
// is dragging the scummvm window around)
if (thisTime > _lastTime + frameTime)
_currentTime += frameTime;
else
_currentTime += thisTime - _lastTime;
_lastTime = thisTime;
for (uint i = 0; i < _queuedScripts.size(); i++) {
QueuedScript &script = _queuedScripts[i];
if (!script._count)
continue;
if (script._baseTime + script._duration > _currentTime)
continue;
if (script._count != 0xffffffff)
script._count--;
script._baseTime = _currentTime;
runScript(script._scriptId, i, 0, 0);
}
if (lastDrawTime + frameTime <= thisTime) {
// catch up if we're more than 2 frames behind
if (lastDrawTime + (frameTime * 2) <= thisTime)
lastDrawTime = thisTime;
else
lastDrawTime += frameTime;
tickOldScripts();
redraw();
processAnimFrame();
} else if (_needsUpdate) {
redraw();
}
if (loadFromLauncher) {
loadGameState(ConfMan.getInt("save_slot"));
loadFromLauncher = false;
}
if (shouldPerformAutoSave(_lastSaveTime))
saveGameState(0, "Autosave");
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_LBUTTONDOWN:
onMouseDown(event.mouse);
break;
case Common::EVENT_LBUTTONUP:
break;
case Common::EVENT_RBUTTONDOWN:
break;
case Common::EVENT_MOUSEMOVE:
onMouseMove(event.mouse);
break;
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_d:
if (event.kbd.hasFlags(Common::KBD_CTRL)) {
// Start the debugger
getDebugger()->attach();
getDebugger()->onFrame();
}
break;
case Common::KEYCODE_q:
if (event.kbd.hasFlags(Common::KBD_CTRL))
quitGame();
break;
default:
break;
}
onKeyDown(event.kbd.keycode);
break;
default:
break;
}
}
_system->delayMillis(20);
}
_screen.free();
return Common::kNoError;
}
void ComposerEngine::onMouseDown(const Common::Point &pos) {
if (!_mouseEnabled || !_mouseVisible)
return;
const Sprite *sprite = getSpriteAtPos(pos);
const Button *button = getButtonFor(sprite, pos);
if (!button)
return;
debug(3, "mouseDown on button id %d", button->_id);
// TODO: other buttons?
uint16 buttonsDown = 1; // MK_LBUTTON
uint16 spriteId = sprite ? sprite->_id : 0;
runScript(button->_scriptId, (getGameType() == GType_ComposerV1) ? 0 : button->_id, buttonsDown, spriteId);
}
void ComposerEngine::onMouseMove(const Common::Point &pos) {
_lastMousePos = pos;
if (!_mouseEnabled || !_mouseVisible)
return;
// TODO: do we need to keep track of this?
uint buttonsDown = 0;
const Sprite *sprite = getSpriteAtPos(pos);
const Button *button = getButtonFor(sprite, pos);
if (_lastButton != button) {
if (_lastButton && _lastButton->_scriptIdRollOff)
runScript(_lastButton->_scriptIdRollOff, (getGameType() == GType_ComposerV1) ? 0 : _lastButton->_id, buttonsDown, 0);
_lastButton = button;
if (_lastButton && _lastButton->_scriptIdRollOn)
runScript(_lastButton->_scriptIdRollOn, (getGameType() == GType_ComposerV1) ? 0 : _lastButton->_id, buttonsDown, 0);
}
if (_mouseSpriteId) {
addSprite(_mouseSpriteId, 0, 0, _lastMousePos - _mouseOffset);
}
_needsUpdate = true;
}
void ComposerEngine::onKeyDown(uint16 keyCode) {
runEvent(kEventKeyDown, keyCode, 0, 0);
runEvent(kEventChar, keyCode, 0, 0);
for (Common::List<Library>::iterator i = _libraries.begin(); i != _libraries.end(); i++) {
for (Common::List<KeyboardHandler>::iterator j = i->_keyboardHandlers.begin(); j != i->_keyboardHandlers.end(); j++) {
const KeyboardHandler &handler = *j;
if (keyCode != handler.keyId)
continue;
int modifiers = g_system->getEventManager()->getModifierState();
switch (handler.modifierId) {
case 0x10: // shift
if (!(modifiers & Common::KBD_SHIFT))
continue;
break;
case 0x11: // control
if (!(modifiers & Common::KBD_CTRL))
continue;
break;
case 0:
break;
default:
warning("unknown keyb modifier %d", handler.modifierId);
continue;
}
runScript(handler.scriptId);
}
}
}
void ComposerEngine::setCursor(uint16 id, const Common::Point &offset) {
_mouseOffset = offset;
if (_mouseSpriteId == id)
return;
if (_mouseSpriteId && _mouseVisible) {
removeSprite(_mouseSpriteId, 0);
}
_mouseSpriteId = id;
if (_mouseSpriteId && _mouseVisible) {
addSprite(_mouseSpriteId, 0, 0, _lastMousePos - _mouseOffset);
}
}
void ComposerEngine::setCursorVisible(bool visible) {
if (visible && !_mouseVisible) {
_mouseVisible = true;
if (_mouseSpriteId)
addSprite(_mouseSpriteId, 0, 0, _lastMousePos - _mouseOffset);
else
CursorMan.showMouse(true);
onMouseMove(_lastMousePos);
} else if (!visible && _mouseVisible) {
_mouseVisible = false;
if (_mouseSpriteId)
removeSprite(_mouseSpriteId, 0);
else
CursorMan.showMouse(false);
}
}
Common::String ComposerEngine::getStringFromConfig(const Common::String &section, const Common::String &key) {
Common::String value;
if (!_bookIni.getKey(key, section, value))
error("failed to find key '%s' in section '%s' of book config", key.c_str(), section.c_str());
return value;
}
Common::String ComposerEngine::getFilename(const Common::String &section, uint id) {
Common::String key = Common::String::format("%d", id);
Common::String filename = getStringFromConfig(section, key);
return mangleFilename(filename);
}
Common::String ComposerEngine::mangleFilename(Common::String filename) {
while (filename.size() && (filename[0] == '~' || filename[0] == ':' || filename[0] == '\\'))
filename = filename.c_str() + 1;
uint slashesToStrip = _directoriesToStrip;
while (slashesToStrip--) {
for (uint i = 0; i < filename.size(); i++) {
if (filename[i] != '\\' && filename[i] != ':')
continue;
filename = filename.c_str() + i + 1;
break;
}
}
Common::String outFilename;
for (uint i = 0; i < filename.size(); i++) {
if (filename[i] == '\\' || filename[i] == ':')
outFilename += '/';
else
outFilename += filename[i];
}
return outFilename;
}
void ComposerEngine::loadLibrary(uint id) {
if (getGameType() == GType_ComposerV1 && !_libraries.empty()) {
// kill the previous page, starting with any scripts running on it
for (Common::List<OldScript *>::iterator i = _oldScripts.begin(); i != _oldScripts.end(); i++)
delete *i;
_oldScripts.clear();
Library *library = &_libraries.front();
unloadLibrary(library->_id);
}
Common::String filename;
Common::String oldGroup = _bookGroup;
if (getGameType() == GType_ComposerV1) {
if (!id || _bookGroup.empty())
filename = getStringFromConfig("Common", "StartPage");
else
filename = getStringFromConfig(_bookGroup, Common::String::format("%d", id));
filename = mangleFilename(filename);
// bookGroup is the basename of the path.
// TODO: tidy this up.
_bookGroup.clear();
for (uint i = 0; i < filename.size(); i++) {
if (filename[i] == '~' || filename[i] == '/' || filename[i] == ':')
continue;
for (uint j = 0; j < filename.size(); j++) {
if (filename[j] == '/') {
_bookGroup.clear();
continue;
}
if (filename[j] == '.')
break;
_bookGroup += filename[j];
}
break;
}
} else {
if (!id)
id = atoi(getStringFromConfig("Common", "StartUp").c_str());
filename = getFilename("Libs", id);
}
Library library;
library._id = id;
library._group = oldGroup;
library._archive = new ComposerArchive();
if (!library._archive->openFile(filename))
error("failed to open '%s'", filename.c_str());
_libraries.push_front(library);
Library &newLib = _libraries.front();
Common::Array<uint16> buttonResources = library._archive->getResourceIDList(ID_BUTN);
for (uint i = 0; i < buttonResources.size(); i++) {
uint16 buttonId = buttonResources[i];
Common::SeekableReadStream *stream = library._archive->getResource(ID_BUTN, buttonId);
Button button(stream, buttonId, getGameType());
bool inserted = false;
for (Common::List<Button>::iterator b = newLib._buttons.begin(); b != newLib._buttons.end(); b++) {
if (button._zorder < b->_zorder)
continue;
newLib._buttons.insert(b, button);
inserted = true;
break;
}
if (!inserted)
newLib._buttons.push_back(button);
}
Common::Array<uint16> ambientResources = library._archive->getResourceIDList(ID_AMBI);
for (uint i = 0; i < ambientResources.size(); i++) {
Common::SeekableReadStream *stream = library._archive->getResource(ID_AMBI, ambientResources[i]);
Button button(stream);
newLib._buttons.insert(newLib._buttons.begin(), button);
}
Common::Array<uint16> accelResources = library._archive->getResourceIDList(ID_ACEL);
for (uint i = 0; i < accelResources.size(); i++) {
Common::SeekableReadStream *stream = library._archive->getResource(ID_ACEL, accelResources[i]);
KeyboardHandler handler;
handler.keyId = stream->readUint16LE();
handler.modifierId = stream->readUint16LE();
handler.scriptId = stream->readUint16LE();
newLib._keyboardHandlers.push_back(handler);
}
Common::Array<uint16> randResources = library._archive->getResourceIDList(ID_RAND);
for (uint i = 0; i < randResources.size(); i++) {
Common::SeekableReadStream *stream = library._archive->getResource(ID_RAND, randResources[i]);
Common::Array<RandomEvent> &events = _randomEvents[randResources[i]];
uint16 count = stream->readUint16LE();
for (uint j = 0; j < count; j++) {
RandomEvent random;
random.scriptId = stream->readUint16LE();
random.weight = stream->readUint16LE();
events.push_back(random);
}
delete stream;
}
// add background sprite, if it exists
if (hasResource(ID_BMAP, 1000))
setBackground(1000);
// TODO: better CTBL logic
loadCTBL(1000, 100);
// Run the startup script.
runScript(1000, 0, 0, 0);
_mouseEnabled = true;
onMouseMove(_lastMousePos);
runEvent(kEventLoad, id, 0, 0);
}
void ComposerEngine::unloadLibrary(uint id) {
for (Common::List<Library>::iterator i = _libraries.begin(); i != _libraries.end(); i++) {
if (i->_id != id)
continue;
for (Common::List<Animation *>::iterator j = _anims.begin(); j != _anims.end(); j++) {
delete *j;
}
_anims.clear();
stopPipes();
_randomEvents.clear();
for (Common::List<Sprite>::iterator j = _sprites.begin(); j != _sprites.end(); j++) {
j->_surface.free();
}
_sprites.clear();
i->_buttons.clear();
_lastButton = NULL;
_mixer->stopAll();
_audioStream = NULL;
for (uint j = 0; j < _queuedScripts.size(); j++) {
_queuedScripts[j]._count = 0;
_queuedScripts[j]._scriptId = 0;
}
delete i->_archive;
_libraries.erase(i);
runEvent(kEventUnload, id, 0, 0);
return;
}
error("tried to unload library %d, which isn't loaded", id);
}
bool ComposerEngine::hasResource(uint32 tag, uint16 id) {
for (Common::List<Library>::iterator i = _libraries.begin(); i != _libraries.end(); i++)
if (i->_archive->hasResource(tag, id))
return true;
return false;
}
Common::SeekableReadStream *ComposerEngine::getResource(uint32 tag, uint16 id) {
for (Common::List<Library>::iterator i = _libraries.begin(); i != _libraries.end(); i++)
if (i->_archive->hasResource(tag, id))
return i->_archive->getResource(tag, id);
error("No loaded library contains '%s' %04x", tag2str(tag), id);
}
Button::Button(Common::SeekableReadStream *stream, uint16 id, uint gameType) {
_id = id;
_type = stream->readUint16LE();
_active = (_type & 0x8000) ? true : false;
bool hasRollover = (gameType == GType_ComposerV1) && (_type & 0x4000);
_type &= 0xfff;
debug(9, "button %d: type %d, active %d", id, _type, _active);
uint16 size = 4;
if (gameType == GType_ComposerV1) {
stream->skip(2);
_zorder = 0;
_scriptId = stream->readUint16LE();
_scriptIdRollOn = 0;
_scriptIdRollOff = 0;
} else {
_zorder = stream->readUint16LE();
_scriptId = stream->readUint16LE();
_scriptIdRollOn = stream->readUint16LE();
_scriptIdRollOff = stream->readUint16LE();
stream->skip(4);
size = stream->readUint16LE();
}
switch (_type) {
case kButtonRect:
case kButtonEllipse:
if (size != 4)
error("button %d of type %d had %d points, not 4", id, _type, size);
_rect.left = stream->readSint16LE();
_rect.top = stream->readSint16LE();
_rect.right = stream->readSint16LE();
_rect.bottom = stream->readSint16LE();
break;
case kButtonSprites:
if (gameType == GType_ComposerV1)
error("encountered kButtonSprites in V1 data");
for (uint i = 0; i < size; i++) {
_spriteIds.push_back(stream->readUint16LE());
}
break;
default:
error("unknown button type %d", _type);
}
if (hasRollover) {
_scriptIdRollOn = stream->readUint16LE();
_scriptIdRollOff = stream->readUint16LE();
}
delete stream;
}
// AMBI-style button
Button::Button(Common::SeekableReadStream *stream) {
_id = 0;
_zorder = 0;
_active = true;
_type = kButtonSprites;
_scriptIdRollOn = 0;
_scriptIdRollOff = 0;
_scriptId = stream->readUint16LE();
uint16 count = stream->readUint16LE();
for (uint j = 0; j < count; j++) {
uint16 spriteId = stream->readUint16LE();
_spriteIds.push_back(spriteId);
}
delete stream;
}
bool Button::contains(const Common::Point &pos) const {
switch (_type) {
case kButtonRect:
return _rect.contains(pos);
case kButtonEllipse:
if (!_rect.contains(pos))
return false;
{
int16 a = _rect.width() / 2;
int16 b = _rect.height() / 2;
if (!a || !b)
return false;
Common::Point adjustedPos = pos - Common::Point(_rect.left + a, _rect.top + b);
return ((adjustedPos.x*adjustedPos.x)/(a*a) + (adjustedPos.y*adjustedPos.y)/(b*b) < 1);
}
case kButtonSprites:
return false;
default:
error("internal error (button type %d)", _type);
}
}
const Button *ComposerEngine::getButtonFor(const Sprite *sprite, const Common::Point &pos) {
for (Common::List<Library>::iterator l = _libraries.begin(); l != _libraries.end(); l++) {
for (Common::List<Button>::iterator i = l->_buttons.reverse_begin(); i != l->_buttons.end(); --i) {
if (!i->_active)
continue;
if (i->_spriteIds.empty()) {
if (i->contains(pos))
return &(*i);
continue;
}
if (!sprite)
continue;
for (uint j = 0; j < i->_spriteIds.size(); j++) {
if (i->_spriteIds[j] == sprite->_id)
return &(*i);
}
}
}
return NULL;
}
void ComposerEngine::setButtonActive(uint16 id, bool active) {
for (Common::List<Library>::iterator l = _libraries.begin(); l != _libraries.end(); l++) {
for (Common::List<Button>::iterator i = l->_buttons.begin(); i != l->_buttons.end(); i++) {
if (i->_id != id)
continue;
i->_active = active;
}
}
onMouseMove(_lastMousePos);
}
} // End of namespace Composer