scummvm/engines/mohawk/view.cpp
2018-03-31 13:36:09 +02:00

838 lines
20 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 "mohawk/view.h"
#include "mohawk/resource.h"
#include "mohawk/graphics.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/palette.h"
namespace Mohawk {
Module::Module() {
}
Module::~Module() {
}
Feature::Feature(View *view) : _view(view) {
_next = _prev = nullptr;
_drawProc = nullptr;
_moveProc = nullptr;
_doneProc = nullptr;
_frameProc = nullptr;
_timeProc = nullptr;
_region = 0;
_id = 0;
_scrbId = 0;
_storedScrbId = 0;
_flags = 0;
_nextTime = 0;
_delayTime = 0;
_dirty = false;
_needsReset = false;
_justReset = false;
_done = false;
}
Feature::~Feature() {
}
void Feature::setNodeDefaults(Feature *prev, Feature *next) {
_prev = prev;
_next = next;
_moveProc = nullptr;
_drawProc = nullptr;
_doneProc = nullptr;
_frameProc = nullptr;
_data.bounds = Common::Rect();
_data.clipRect = Common::Rect();
_data.useClipRect = 0;
_region = 0;
_id = 0; // This is dealt with elsewhere.
_scrbId = 0;
_storedScrbId = 0;
_data.scrbIndex = 0;
_data.compoundSHAPIndex = 0;
_data.bitmapIds[0] = 0;
_data.unknown192 = 0;
_data.currFrame = 0;
_data.syncChannel = 0;
_data.enabled = 1;
_data.paused = 0; // new
_data.hidden = 0; // new
_flags = 0;
_dirty = true;
_needsReset = true;
_justReset = false; // old
_done = false; // new
_nextTime = 0;
_delayTime = 0;
}
void Feature::resetFeatureScript(uint16 enabled, uint16 scrbId) {
if (!scrbId)
scrbId = _scrbId;
if (scrbId != _scrbId || _needsReset) {
if (_needsReset)
_data.bounds = Common::Rect();
_scrbId = scrbId;
_view->getnthScriptSetGroup(_data.scrbIndex, _data.compoundSHAPIndex, scrbId);
}
if (_data.scrbIndex == 0xFFFF) {
_data.enabled = 0;
_data.bitmapIds[0] = 0;
_data.scrbIndex = 0;
_data.compoundSHAPIndex = 0;
resetFrame();
return;
}
resetScript();
resetFrame();
_nextTime = 0; // New feature code uses _view->_lastIdleTime, but should be equivalent.
_data.enabled = enabled;
_dirty = true;
finishResetFeatureScript();
_needsReset = false;
if (_region) {
// TODO: mark _region as dirty
} else {
// TODO: mark _data.bounds as dirty
}
}
void Feature::resetFeature(bool notifyDone, Module::FeatureProc doneProc, uint16 scrbId) {
resetFeatureScript(1, scrbId);
_doneProc = doneProc;
}
void Feature::hide(bool clip) {
// FIXME: stuff
if (!_data.hidden && clip) {
if (_region) {
// TODO: mark _region as dirty
} else {
// TODO: mark _data.bounds as dirty
}
}
_data.hidden++;
_data.paused++;
}
void Feature::show() {
if (_data.hidden == 1) {
if (_region) {
// TODO: mark _region as dirty
} else {
// TODO: mark _data.bounds as dirty
}
}
_data.hidden--;
_data.paused--;
}
void Feature::moveAndUpdate(Common::Point newPos) {
if (newPos == _data.currentPos)
return;
_nextTime = 0;
_dirty = true;
// TODO: mark _data.bounds as dirty
if (_data.bitmapIds[0])
_data.bounds.moveTo(newPos);
int xDiff = _data.currentPos.x - newPos.x;
int yDiff = _data.currentPos.y - newPos.y;
for (uint i = 0; i < FEATURE_BITMAP_ITEMS; i++) {
uint16 bitmapId = _data.bitmapIds[i];
if (!bitmapId) // || bitmapId > compoundSHAP.size()
break;
_data.bitmapPos[i].x -= xDiff;
_data.bitmapPos[i].y -= yDiff;
}
_data.currentPos = newPos;
}
void Feature::defaultDraw() {
if (_data.useClipRect) {
// TODO: set clip rect
}
uint16 compoundSHAPId = _view->getCompoundSHAPId(_data.compoundSHAPIndex);
for (uint i = 0; i < FEATURE_BITMAP_ITEMS; i++) {
uint16 bitmapId = _data.bitmapIds[i];
if (!bitmapId) // || bitmapId > compoundSHAP.size()
break;
_view->getGfx()->copyAnimSubImageToScreen(compoundSHAPId, bitmapId - 1, _data.bitmapPos[i].x, _data.bitmapPos[i].y);
}
if (_data.useClipRect) {
// TODO: restore clip rgn
}
}
OldFeature::OldFeature(View *view) : Feature(view) {
}
OldFeature::~OldFeature() {
}
void OldFeature::resetFrame() {
_data.currFrame = 0;
_data.currOffset = 1;
}
void OldFeature::resetFeatureScript(uint16 enabled, uint16 scrbId) {
if ((_flags & kFeatureOldAlternateScripts) && (_justReset || !_needsReset)) {
if (_storedScrbId)
return;
if (_flags & kFeatureOldRandom) {
_storedScrbId = -(int16)_scrbId;
_flags &= ~kFeatureOldRandom;
} else {
_storedScrbId = _scrbId;
}
}
Feature::resetFeatureScript(enabled, scrbId);
}
void OldFeature::resetScript() {
Common::SeekableReadStream *ourSCRB = _view->getSCRB(_data.scrbIndex, _scrbId);
_data.endFrame = ourSCRB->readUint16BE() - 1;
delete ourSCRB;
}
void OldFeature::finishResetFeatureScript() {
_justReset = true;
if (_flags & kFeatureOldAdjustByPos) {
Common::SeekableReadStream *ourSCRB = _view->getSCRB(_data.scrbIndex, _scrbId);
ourSCRB->seek(4);
_data.nextPos.x = ourSCRB->readUint16BE();
_data.nextPos.y = ourSCRB->readUint16BE();
delete ourSCRB;
}
}
NewFeature::NewFeature(View *view) : Feature(view) {
_unknown168 = 0;
_pickupProc = nullptr;
_dropProc = nullptr;
_dragMoveProc = nullptr;
_oldMoveProc = nullptr;
_dragFlags = 0;
_oldFlags = 0;
}
NewFeature::~NewFeature() {
}
void NewFeature::resetFrame() {
_data.currOffset = 26;
}
void NewFeature::resetFeatureScript(uint16 enabled, uint16 scrbId) {
// TODO: _frameProc(this, -3);
// TODO: set unknown184 to 0x01010101
Feature::resetFeatureScript(enabled, scrbId);
}
void NewFeature::resetScript() {
// FIXME: registrations, etc
Common::SeekableReadStream *ourSCRB = _view->getSCRB(_data.scrbIndex, _scrbId);
ourSCRB->seek(16);
Common::Point scriptBase, scriptSize;
scriptBase.x = ourSCRB->readUint16BE();
scriptBase.y = ourSCRB->readUint16BE();
scriptSize.x = ourSCRB->readUint16BE();
scriptSize.y = ourSCRB->readUint16BE();
ourSCRB->seek(26);
Common::Point one, two;
while (true) {
if (ourSCRB->pos() == ourSCRB->size())
error("resetScript (getNewXYAndReg) ran out of script");
byte opcode = ourSCRB->readByte();
byte size = ourSCRB->readByte();
if (opcode != 0x10) {
ourSCRB->skip(size - 2);
} else if (size) {
assert(size >= 1);
ourSCRB->skip(2);
int16 x = ourSCRB->readUint16BE();
int16 y = ourSCRB->readUint16BE();
one.x = -x;
one.y = -y;
two.x = scriptBase.x + x;
two.y = scriptBase.y + y;
break;
}
}
delete ourSCRB;
if ((_needsReset || false /* TODO: param */) && (_unknown168 == 0x7FFFFFFF || false /* TODO: param */)) {
_data.currentPos = two;
_data.nextPos = one;
_unknown168 = 0;
if (_needsReset || false /* TODO: param */) {
_data.bounds = Common::Rect(scriptBase.x, scriptBase.y, scriptSize.x, scriptSize.y);
}
} else {
if (false /* FIXME: 0 shapes? */) {
_data.nextPos.x = one.x + two.x - _data.currentPos.x;
_data.nextPos.y = one.y + two.y - _data.currentPos.y;
} else if (_unknown168 != 0x7FFFFFFF) {
_data.nextPos = one;
}
}
// _needsReset = 0; (handled by caller)
}
void NewFeature::finishResetFeatureScript() {
_done = false;
}
View::View(MohawkEngine *vm) : _vm(vm) {
_currentModule = nullptr;
_backgroundId = 0xffff;
for (uint i = 0; i < 14; i++) { // used to be 8
_compoundSHAPGroups[i] = 0;
}
_numSCRBGroups = 0;
_lastIdleTime = 0;
_needsUpdate = false;
_gfx = nullptr;
_rootNode = nullptr;
_cursorNode = nullptr;
}
View::~View() {
}
void View::idleView() {
assert(_currentModule);
_lastIdleTime = getTime();
for (Feature *node = _rootNode; node; node = node->_next) {
if (node->_moveProc)
(_currentModule->*(node->_moveProc))(node);
}
// TODO: find a way this works for all clients
//if (/* TODO: _sortView */ true && !_inDialog) {
// sortView();
//}
sortView();
for (Feature *node = _rootNode; node; node = node->_next) {
if (node->_dirty) {
// TODO: clipping
_needsUpdate = true;
}
if (node->_drawProc)
(_currentModule->*(node->_drawProc))(node);
node->_dirty = false;
}
if (_needsUpdate) {
finishDraw();
_vm->_system->updateScreen();
_needsUpdate = false;
if (_backgroundId != 0xffff)
_gfx->copyAnimImageToScreen(_backgroundId);
}
}
void View::setModule(Module *module) {
if (_currentModule) {
_currentModule->shutdown();
delete _currentModule;
}
_currentModule = nullptr;
if (module) {
_currentModule = module;
module->init();
}
}
Common::Array<uint16> View::getSHPL(uint16 id) {
Common::SeekableReadStream *stream;
if (_vm->hasResource(ID_TCNT, id)) {
stream = _vm->getResource(ID_TCNT, id);
} else {
stream = _vm->getResource(ID_SHPL, id);
stream->seek(4);
setColors(stream);
stream->seek(0);
}
uint16 base = stream->readUint16BE();
uint16 count = stream->readUint16BE();
delete stream;
Common::Array<uint16> items;
for (uint i = 0; i < count; i++)
items.push_back(base + i);
return items;
}
void View::installBG(uint16 id) {
// getShapes
Common::Array<uint16> shapes = getSHPL(id);
if (_vm->hasResource(ID_TPAL, id)) {
Common::SeekableReadStream *stream = _vm->getResource(ID_TPAL, id);
setColors(stream);
delete stream;
}
if (shapes.size() != 1) {
// TODO
warning("background with id 0x%04x has the wrong number of shapes (%d)", id, shapes.size());
_backgroundId = id;
_gfx->copyAnimImageToScreen(_backgroundId);
} else {
// DrawViewBackground
_backgroundId = shapes[0];
_gfx->copyAnimImageToScreen(_backgroundId);
}
}
void View::setColors(Common::SeekableReadStream *tpalStream) {
uint16 colorStart = tpalStream->readUint16BE();
uint16 colorCount = tpalStream->readUint16BE();
byte *palette = new byte[colorCount * 3];
for (uint16 i = 0; i < colorCount; i++) {
palette[i * 3 + 0] = tpalStream->readByte();
palette[i * 3 + 1] = tpalStream->readByte();
palette[i * 3 + 2] = tpalStream->readByte();
tpalStream->readByte();
}
// TODO: copy into temporary buffer
_vm->_system->getPaletteManager()->setPalette(palette, colorStart, colorCount);
delete[] palette;
// original does pdLightenUp here..
}
void View::copyFadeColors(uint start, uint count) {
// TODO
}
uint16 View::getCompoundSHAPId(uint16 shapIndex) {
return _compoundSHAPGroups[shapIndex];
}
void View::installGroupOfSCRBs(bool main, uint base, uint size, uint count) {
if (main) {
// TODO: _dropSpots.clear();
_numSCRBGroups = 0;
_SCRBEntries.clear();
}
if (_numSCRBGroups >= 14) // used to be 8
error("installGroupOfSCRBs called when we already had 14 groups");
for (uint i = 0; i < size; i++)
_SCRBEntries.push_back(base + i);
// TODO: think about this
if (count == 0)
count = size;
else if (count > size) {
for (uint i = 0; i < count - size; i++)
_SCRBEntries.push_back(0);
} else
error("installGroupOfSCRBs got count %d, size %d", count, size);
_SCRBGroupBases[_numSCRBGroups] = base;
_SCRBGroupSizes[_numSCRBGroups] = count;
_numSCRBGroups++;
}
void View::freeScripts() {
freeFeatureShapes();
for (uint i = 0; i < 14; i++) { // used to be 8
_SCRBGroupBases[i] = 0;
_SCRBGroupSizes[i] = 0;
}
_SCRBEntries.clear();
_numSCRBGroups = 0;
}
void View::installFeatureShapes(bool regs, uint groupId, uint16 resourceBase) {
if (groupId >= 14) // used to be 8
error("installFeatureShapes called for invalid group %d", groupId);
if (_compoundSHAPGroups[groupId])
error("installFeatureShapes called for existing group %d", groupId);
_compoundSHAPGroups[groupId] = resourceBase;
if (regs) {
// TODO
}
}
void View::freeFeatureShapes() {
for (uint i = 0; i < 14; i++) { // used to be 8
_compoundSHAPGroups[i] = 0;
// TODO: wipe regs data
}
}
uint16 View::getGroupFromBaseId(uint16 baseId) {
for (uint i = 0; i < 14; i++) {
if (_compoundSHAPGroups[i] == baseId)
return i;
}
// TODO: error?
return 0xffff;
}
void View::getnthScriptSetGroup(uint16 &scrbIndex, uint16 &shapIndex, uint16 scrbId) {
scrbIndex = 0;
for (uint i = 0; i < _numSCRBGroups; i++) {
if (_SCRBGroupBases[i] <= scrbId && _SCRBGroupBases[i] + _SCRBGroupSizes[i] > scrbId) {
shapIndex = i;
scrbIndex += scrbId - _SCRBGroupBases[i];
return;
}
scrbIndex += _SCRBGroupSizes[i];
}
scrbIndex = 0xffff;
}
Common::SeekableReadStream *View::getSCRB(uint16 index, uint16 id) {
// If we don't have an entry, load the load provided id.
// (The 0xffff check is a default parameter hack.)
if (!_SCRBEntries[index] && id != 0xffff)
_SCRBEntries[index] = id;
// FIXME
if (_vm->hasResource(ID_SCRB, _SCRBEntries[index]))
return _vm->getResource(ID_SCRB, _SCRBEntries[index]);
return _vm->getResource(ID_TSCR, _SCRBEntries[index]);
}
Feature *View::getFeaturePtr(uint16 id) {
for (Feature *node = _cursorNode; node; node = node->_prev) {
if (node->_id == id)
return node;
}
return nullptr;
}
uint16 View::getNewFeatureId() {
uint16 nextId = 0;
Feature *node;
for (node = _rootNode; node; node = node->_next) {
// The original doesn't check for 0xffff but I don't want to fudge with signed integers.
if (node->_id != 0xffff && node->_id > nextId)
nextId = node->_id;
}
return nextId + 1;
}
void View::removeFeature(Feature *feature, bool free) {
// TODO: or bounds into dirty feature bounds
feature->_prev->_next = feature->_next;
feature->_next->_prev = feature->_prev;
feature->_next = nullptr;
feature->_prev = nullptr;
if (free)
delete feature;
}
void View::insertUnderCursor(Feature *feature) {
feature->_next = _cursorNode;
feature->_prev = _cursorNode->_prev;
feature->_prev->_next = feature;
feature->_next->_prev = feature;
}
Feature *View::pointOnFeature(bool topdown, uint32 flags, Common::Point pos) {
flags &= 0x7fffff;
Feature *curr = _rootNode->_next;
if (topdown)
curr = _cursorNode->_prev;
while (curr) {
if ((curr->_flags & 0x7fffff) == flags)
if (curr->_data.bounds.contains(pos))
return curr;
if (topdown)
curr = curr->_prev;
else
curr = curr->_next;
}
return nullptr;
}
void View::sortView() {
Feature *base = _rootNode;
Feature *next = base->_next;
Feature *otherRoot = nullptr;
Feature *otherBase = nullptr;
Feature *objectRoot = nullptr;
Feature *objectBase = nullptr;
Feature *staticRoot = nullptr;
Feature *staticBase = nullptr;
// Remove all features.
base->_next = nullptr;
// Iterate through all the previous features, placing them in the appropriate list.
while (next) {
Feature *curr = next;
next = next->_next;
if (curr->_flags & kFeatureSortBackground) {
// These are behind everything else (e.g. stars, drop spot highlights),
// so we insert this node directly after the current base.
base->_next = curr;
curr->_prev = base;
curr->_next = nullptr;
base = base->_next;
} else if (curr->_flags & kFeatureSortStatic) {
// Insert this node into the list of static objects.
if (staticBase) {
staticBase->_next = curr;
curr->_prev = staticBase;
curr->_next = nullptr;
staticBase = curr;
} else {
staticBase = curr;
staticRoot = curr;
curr->_prev = nullptr;
curr->_next = nullptr;
}
} else if (curr->_flags & kFeatureObjectMask) { // This is == 1 or == 2 in old code.
// Insert this node into the list of objects.
if (objectRoot) {
objectBase->_next = curr;
curr->_prev = objectBase;
curr->_next = nullptr;
objectBase = curr;
} else {
objectBase = curr;
objectRoot = curr;
curr->_prev = nullptr;
curr->_next = nullptr;
}
} else {
if (!(curr->_flags & kFeatureOldSortForeground))
curr->_flags |= kFeatureSortStatic;
// Insert this node into the list of other features.
if (otherRoot) {
otherBase->_next = curr;
curr->_prev = otherBase;
curr->_next = nullptr;
otherBase = curr;
} else {
otherBase = curr;
otherRoot = curr;
curr->_prev = nullptr;
curr->_next = nullptr;
}
}
}
// Add the static features after the background ones.
Feature *curr = staticRoot;
while (curr) {
Feature *prev = curr;
curr = curr->_next;
base->_next = prev;
prev->_prev = base;
base = base->_next;
base->_next = nullptr;
}
// Add the other features on top..
_rootNode = mergeLists(_rootNode, sortOneList(otherRoot));
// Then finally, add the objects.
_rootNode = mergeLists(_rootNode, sortOneList(objectRoot));
}
Feature *View::sortOneList(Feature *root) {
if (!root)
return nullptr;
// Save the next feature and then clear the list.
Feature *curr = root->_next;
root->_next = nullptr;
root->_prev = nullptr;
// Iterate over all the features.
while (curr) {
Feature *prev = curr;
curr = curr->_next;
Common::Rect &prevRect = prev->_data.bounds;
// Check against all features currently in the list.
Feature *check = root;
while (check) {
Common::Rect &checkRect = check->_data.bounds;
if ((prev->_flags & kFeatureOldSortForeground) || (prevRect.bottom >= checkRect.bottom && (prevRect.bottom != checkRect.bottom || prevRect.left >= checkRect.left))) {
// If we're meant to be in front of everything else, or we're in front of the check object..
if (!check->_next) {
// This is the end of the list: add ourselves there.
check->_next = prev;
prev->_prev = check;
prev->_next = nullptr;
break;
}
} else {
// We're meant to be behind this object. Insert ourselves here.
prev->_prev = check->_prev;
prev->_next = check;
check->_prev = prev;
if (prev->_prev)
prev->_prev->_next = prev;
else
root = prev;
break;
}
check = check->_next;
}
}
return root;
}
Feature *View::mergeLists(Feature *root, Feature *mergeRoot) {
Feature *base = root;
// Skip anything marked as being behind everything else.
while (base->_next && (base->_next->_flags & kFeatureSortBackground))
base = base->_next;
// Iterate over all the objects in the root to be merged.
Feature *curr = mergeRoot;
while (curr) {
Feature *prev = curr;
curr = curr->_next;
Common::Rect &prevRect = prev->_data.bounds;
// Check against all objects currently in the list.
Feature *check = base;
if (prev->_flags & kFeatureOldSortForeground) {
// This object is meant to be in front of everything else,
// put it at the end of the list.
while (check && check->_next)
check = check->_next;
check->_next = prev;
prev->_prev = check;
prev->_next = nullptr;
continue;
}
while (check) {
if (check->_flags & kFeatureOldSortForeground) {
// The other object is meant to be in front of everything else,
// put ourselves before it.
prev->_prev = check->_prev;
prev->_next = check;
check->_prev = prev;
// The original doesn't bother with this 'if'.
if (prev->_prev)
prev->_prev->_next = prev;
else
root = prev;
break;
}
if (!check->_next) {
// We're at the end of the list, so we have to go here.
check->_next = prev;
prev->_prev = check;
prev->_next = nullptr;
base = prev;
break;
}
Common::Rect &checkRect = check->_data.bounds;
if (prevRect.bottom < checkRect.bottom || (prevRect.bottom == checkRect.bottom && prevRect.left < checkRect.left)) {
if (prevRect.bottom < checkRect.top || (
(!(check->_flags & kFeatureSortCheckLeft) || prevRect.left >= checkRect.left) &&
(!(check->_flags & kFeatureSortCheckTop) || prevRect.top >= checkRect.top) &&
(!(check->_flags & kFeatureSortCheckRight) || prevRect.right <= checkRect.right))) {
// Insert ourselves before this one.
prev->_prev = check->_prev;
prev->_next = check;
check->_prev = prev;
if (prev->_prev)
prev->_prev->_next = prev;
else
root = prev;
base = prev->_next;
break;
}
}
check = check->_next;
}
}
return root;
}
} // End of namespace Mohawk