scummvm/engines/mohawk/cstime_view.cpp
2021-12-26 18:48:43 +01:00

573 lines
17 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 "mohawk/cstime_game.h" // debugging..
#include "mohawk/cstime_ui.h"
#include "mohawk/cstime_view.h"
#include "mohawk/resource.h"
#include "mohawk/cursors.h"
#include "common/events.h"
#include "common/system.h"
#include "common/textconsole.h"
namespace Mohawk {
CSTimeView::CSTimeView(MohawkEngine_CSTime *vm) : View(vm) {
_timeVm = vm;
_gfx = vm->_gfx;
_bitmapCursorId = 0;
}
uint32 CSTimeView::getTime() {
return _vm->_system->getMillis();
}
void CSTimeView::setupView() {
_rootNode = new NewFeature(this);
_cursorNode = new NewFeature(this);
_rootNode->setNodeDefaults(nullptr, _cursorNode);
_rootNode->_id = 1; // TODO: 10 in new?
_rootNode->_data.enabled = 0;
_rootNode->_flags = kFeatureSortBackground;
_rootNode->_moveProc = nullptr;
_rootNode->_drawProc = nullptr;
_rootNode->_timeProc = nullptr;
_cursorNode->setNodeDefaults(_rootNode, nullptr);
_cursorNode->_id = 0xffff; // TODO: 1 in new?
_cursorNode->_data.enabled = 0;
_cursorNode->_flags = kFeatureOldSortForeground; // TODO: 0x4000 in new..
_cursorNode->_moveProc = (Module::FeatureProc)&CSTimeModule::cursorMoveProc;
_cursorNode->_drawProc = (Module::FeatureProc)&CSTimeModule::cursorDrawProc;
_cursorNode->_timeProc = nullptr;
}
Feature *CSTimeView::installViewFeature(uint16 scrbId, uint32 flags, Common::Point *pos) {
Feature *node = _rootNode;
// FIXME: find the right node to insert under
while (node) {
if (node->_next && (node->_next->_id == 0xffff || ((flags & 0x8000) && !(node->_next->_flags & 0x8000))))
break;
node = node->_next;
}
if (!node)
error("failed to install view feature");
NewFeature *feature = new NewFeature(this);
feature->setNodeDefaults(node, node->_next);
feature->_moveProc = (Module::FeatureProc)&CSTimeModule::defaultMoveProc;
feature->_drawProc = (Module::FeatureProc)&CSTimeModule::defaultDrawProc;
feature->_timeProc = (Module::BooleanProc)&CSTimeModule::defaultTimeProc;
feature->_pickupProc = (Module::PickupProc)&CSTimeModule::defaultPickupProc;
feature->_dropProc = (Module::FeatureProc)&CSTimeModule::defaultDropProc;
feature->_dragMoveProc = (Module::FeatureProc)&CSTimeModule::defaultDragMoveProc;
feature->_oldMoveProc = nullptr;
feature->_dragFlags = 0x8000;
feature->_id = getNewFeatureId();
node->_next = feature;
feature->_next->_prev = feature;
if (pos) {
feature->_data.currentPos = *pos;
feature->_unknown168 = 1;
} else {
feature->_data.currentPos = Common::Point();
feature->_unknown168 = 0x7FFFFFFF;
}
feature->_data.nextPos = Common::Point();
feature->_scrbId = scrbId;
feature->_flags = flags;
feature->_delayTime = 100;
return feature;
}
void CSTimeView::installGroup(uint16 resourceId, uint size, uint count, bool regs, uint16 baseId) {
// TODO: make sure this is in sync!
assert(_numSCRBGroups < 14);
installFeatureShapes(regs, _numSCRBGroups, resourceId);
if (baseId == 0xffff)
baseId = resourceId;
_SCRBGroupResources[_numSCRBGroups] = resourceId; // TODO: Meh. This needs some rethinking.
installGroupOfSCRBs(false, baseId, size, count);
}
void CSTimeView::removeGroup(uint16 resourceId) {
// FIXME: deal with zero resourceId
if (resourceId == 0)
error("removeGroup got zero resourceId");
uint16 groupId = getGroupFromBaseId(resourceId);
if (groupId == 0xffff)
return;
removeObjectsUsingBaseId(resourceId);
freeShapesUsingResourceId(resourceId);
freeScriptsUsingResourceId(resourceId);
// adjustShapeGroups(groupId); - TODO: unnecessary?
}
void CSTimeView::removeObjectsUsingBaseId(uint16 baseId) {
uint16 groupId = getGroupFromBaseId(baseId);
Feature *node = _rootNode->_next;
while (node->_next) {
Feature *curr = node;
node = node->_next;
if (curr->_data.compoundSHAPIndex == groupId) {
removeFeature(curr, true);
}
}
}
void CSTimeView::freeShapesUsingResourceId(uint16 resourceId) {
// TODO: Meh. This needs some rethinking.
for (int i = _numSCRBGroups - 1; i >= 0; i--) {
if (_SCRBGroupResources[i] != resourceId)
continue;
for (uint j = i; j < 13; j++)
_compoundSHAPGroups[j] = _compoundSHAPGroups[j + 1];
_compoundSHAPGroups[13] = 0;
// TODO: deal with REGS
}
}
void CSTimeView::freeScriptsUsingResourceId(uint16 resourceId) {
// TODO: Meh. This needs some rethinking.
for (int i = _numSCRBGroups - 1; i >= 0; i--) {
if (_SCRBGroupResources[i] == resourceId)
groupFreeScript(i);
}
}
void CSTimeView::groupFreeScript(uint index) {
uint count = _SCRBGroupSizes[index];
_numSCRBGroups--;
for (uint i = index; i < _numSCRBGroups; i++) {
_SCRBGroupBases[i] = _SCRBGroupBases[i + 1];
_SCRBGroupSizes[i] = _SCRBGroupSizes[i + 1];
_SCRBGroupResources[i] = _SCRBGroupResources[i + 1]; // TODO: Meh. This needs some rethinking.
}
uint base = 0;
for (uint i = 0; i < index; i++)
base += _SCRBGroupSizes[i];
for (uint i = 0; i < count; i++)
_SCRBEntries.remove_at(base);
// TODO: kill any actual scripts
groupAdjustView(index, count);
}
void CSTimeView::groupAdjustView(uint index, uint count) {
for (Feature *node = _rootNode->_next; node->_next; node = node->_next) {
if (node->_data.compoundSHAPIndex < index)
continue;
node->_data.compoundSHAPIndex--;
node->_data.scrbIndex -= count;
}
}
void CSTimeView::loadBitmapCursors(uint16 baseId) {
// TODO
}
void CSTimeView::setBitmapCursor(uint16 id) {
if (_bitmapCursorId == id)
return;
if (!id) {
_vm->_cursor->showCursor();
} else {
_vm->_cursor->hideCursor();
}
_bitmapCursorId = id;
}
void CSTimeView::dragFeature(NewFeature *feature, Common::Point pos, uint mode, uint32 flags, Common::Rect *rect) {
feature->_data.hidden = 0;
if (mode == 2) {
if (feature->_dragFlags & 0x800000) {
feature->_dragFlags = flags | 0x8000;
if (!(flags & 1))
(_currentModule->*(feature->_dropProc))(feature);
}
return;
}
if (feature->_dragFlags & 0x800000)
(_currentModule->*(feature->_dropProc))(feature);
else
(_currentModule->*(feature->_pickupProc))(feature, pos, flags, rect);
}
void CSTimeView::finishDraw() {
// TODO: This is a kinda stupid hack, here just for debugging.
((MohawkEngine_CSTime *)_vm)->getCase()->getCurrScene()->drawHotspots();
}
CSTimeModule::CSTimeModule(MohawkEngine_CSTime *vm) : _vm(vm) {
}
void CSTimeModule::defaultMoveProc(Feature *feature) {
if (feature->_data.paused > 0)
return;
if (!feature->_data.enabled)
return;
if (feature->_timeProc && !(this->*(feature->_timeProc))(feature))
return;
if (feature->_needsReset) {
feature->resetFeatureScript(1, feature->_scrbId);
if ((feature->_flags & kFeatureNewDisable) || (feature->_flags & kFeatureNewDisableOnReset)) {
feature->_data.enabled = 0;
}
feature->_dirty = true;
if (feature->_flags & kFeatureInternalRegion) {
// TODO: create region [+140] (if not already done)
}
} else {
if (!(feature->_flags & kFeatureNewClip)) {
if (feature->_data.useClipRect) {
// TODO: or clip with _unknown228
} else if (feature->_region) {
// TODO: or clip with region
} else {
// TODO: or clip with bounds
}
}
feature->_dirty = true;
if (feature->_flags & kFeatureNewInternalTiming) {
feature->_nextTime += feature->_delayTime;
} else {
feature->_nextTime = _vm->getView()->_lastIdleTime + feature->_delayTime;
}
if (feature->_done) {
if (feature->_flags & kFeatureNewNoLoop) {
// FIXME: sync channel reset
uint16 unknown184 = 1, unknown186 = 1; // FIXME: XXX
if (feature->_flags & kFeatureDisableOnEnd || (unknown184 != 0 && unknown186 != 0)) { // FIXME: XXX
feature->_data.enabled = 0;
if (feature->_doneProc) {
(this->*(feature->_doneProc))(feature); // TODO: with -2
}
}
return;
}
feature->_data.currOffset = 26;
feature->_done = false;
}
if (feature->_flags & kFeatureNewDisable)
feature->_data.enabled = 0;
}
int xOffset = feature->_data.currentPos.x + feature->_data.nextPos.x;
int yOffset = feature->_data.currentPos.y + feature->_data.nextPos.y;
Common::SeekableReadStream *ourSCRB = _vm->getView()->getSCRB(feature->_data.scrbIndex);
ourSCRB->seek(feature->_data.currOffset);
bool setBitmap = false;
uint bitmapId = 0;
bool done = false;
while (!done) {
byte opcode = ourSCRB->readByte();
byte size = ourSCRB->readByte();
switch (opcode) {
case 1:
ourSCRB->skip(size - 2);
opcode = ourSCRB->readByte();
size = ourSCRB->readByte();
if (opcode != 0) {
ourSCRB->seek(-2, SEEK_CUR);
done = true;
break;
}
// fall through
// FIXME: Unclear if fall through is intentional...
case 0:
// TODO: set ptr +176 to 1
feature->_done = true;
if (feature->_doneProc) {
(this->*(feature->_doneProc))(feature); // TODO: with -1
}
done = true;
break;
case 3:
{
int32 pos = ourSCRB->pos();
ourSCRB->seek(2);
uint16 base = ourSCRB->readUint16BE();
ourSCRB->seek(pos);
base += ourSCRB->readUint16BE();
if (base) {
// FIXME: sound?
}
ourSCRB->skip(size - 4);
}
warning("saw feature opcode 0x3 (size %d)", size);
break;
case 4:
// FIXME
if (false /* TODO: !+72 */) {
ourSCRB->skip(size - 2);
} else {
uint16 time = ourSCRB->readUint16BE();
// FIXME: not right
feature->_delayTime = time;
ourSCRB->skip(size - 4);
}
warning("saw feature opcode 0x4 (size %d)", size);
break;
case 9:
// FIXME
ourSCRB->skip(size - 2);
warning("ignoring feature opcode 0x9 (size %d)", size);
break;
case 0xf:
// FIXME
ourSCRB->skip(size - 2);
warning("ignoring feature opcode 0xf (size %d)", size);
break;
case 0x10:
while (bitmapId < 48) {
if (!size)
break;
size--;
feature->_data.bitmapIds[bitmapId] = ourSCRB->readUint16BE() & 0xFFF;
feature->_data.bitmapPos[bitmapId].x = ourSCRB->readUint16BE() + xOffset;
feature->_data.bitmapPos[bitmapId].y = ourSCRB->readUint16BE() + yOffset;
bitmapId++;
}
feature->_data.bitmapIds[bitmapId] = 0;
setBitmap = true;
break;
default:
warning("unknown new feature opcode %d", opcode);
ourSCRB->skip(size - 2);
break;
}
}
feature->_data.currOffset = ourSCRB->pos();
if (!setBitmap) {
// TODO: set fail flag
return;
}
if (feature->_frameProc) {
(this->*(feature->_frameProc))(feature);
}
// TODO: set palette if needed
// TODO: adjust for regs if needed
Common::Array<int16> regsX, regsY;
Common::SeekableReadStream *regsStream;
uint16 compoundSHAPIndex = _vm->getView()->getCompoundSHAPId(feature->_data.compoundSHAPIndex);
regsStream = _vm->getResource(ID_REGS, compoundSHAPIndex);
while (regsStream->pos() != regsStream->size())
regsX.push_back(regsStream->readSint16BE());
delete regsStream;
regsStream = _vm->getResource(ID_REGS, compoundSHAPIndex + 1);
while (regsStream->pos() != regsStream->size())
regsY.push_back(regsStream->readSint16BE());
delete regsStream;
for (uint i = 0; i < 48; i++) {
uint16 thisBitmapId = feature->_data.bitmapIds[i];
if (!thisBitmapId)
break;
feature->_data.bitmapPos[i].x -= regsX[thisBitmapId];
feature->_data.bitmapPos[i].y -= regsY[thisBitmapId];
}
// TODO: set bounds
// TODO: unset fail flag
}
void CSTimeModule::defaultDrawProc(Feature *feature) {
if (feature->_data.hidden > 0)
return;
feature->defaultDraw();
}
bool CSTimeModule::defaultTimeProc(Feature *feature) {
return (feature->_nextTime <= _vm->getView()->getTime());
}
void CSTimeModule::defaultPickupProc(NewFeature *feature, Common::Point pos, uint32 flags, Common::Rect *rect) {
_vm->getView()->removeFeature(feature, false);
feature->_dragFlags |= flags | 0x800000;
feature->_oldFlags = feature->_flags;
feature->_data.useClipRect = 0;
// TODO: these flags are weird/different
feature->_flags = (feature->_flags & ~kFeatureSortBackground) | kFeatureOldSortForeground | kFeatureSortStatic | 0x2000;
_vm->getView()->insertUnderCursor(feature);
feature->_nextTime = 0;
// FIXME: preserve old delayTime (see also script op 4)
feature->_delayTime = 50;
feature->_oldPos = feature->_data.currentPos;
feature->_posDiff.x = pos.x - feature->_data.currentPos.x;
feature->_posDiff.y = pos.y - feature->_data.currentPos.y;
debug("defaultPickupProc: diff is %d, %d", feature->_posDiff.x, feature->_posDiff.y);
feature->_oldMoveProc = feature->_moveProc;
feature->_moveProc = feature->_dragMoveProc;
// FIXME: deal with rect
if (rect)
error("defaultPickupProc doesn't handle rect yet");
}
void CSTimeModule::defaultDropProc(NewFeature *feature) {
// FIXME: invalidation
feature->_flags = feature->_oldFlags;
// FIXME: restore old delayTime
feature->_dragFlags &= ~0x800000;
if (feature->_dragFlags & 0x800)
feature->moveAndUpdate(feature->_oldPos);
if (feature->_dragFlags & 0x200)
feature->hide(true);
feature->_moveProc = feature->_oldMoveProc;
}
void CSTimeModule::defaultDragMoveProc(NewFeature *feature) {
// FIXME
if (feature->_dragFlags & 0x8000)
feature->_currDragPos = _vm->getEventManager()->getMousePos();
Common::Point pos = feature->_currDragPos;
pos.x -= feature->_posDiff.x;
pos.y -= feature->_posDiff.y;
if (feature->_dragFlags & 0x80) {
// FIXME: handle 0x80 case
error("encountered 0x80 case in defaultDragMoveProc");
}
feature->moveAndUpdate(pos);
(this->*(feature->_oldMoveProc))(feature);
}
void CSTimeModule::cursorMoveProc(Feature *feature) {
uint16 cursor = _vm->getView()->getBitmapCursor();
if (!cursor)
return;
Common::Point pos = _vm->getEventManager()->getMousePos();
// FIXME: shouldn't be hardcoded
uint16 compoundSHAPIndex = 200;
// FIXME: stupid REGS stuff..
Common::SeekableReadStream *regsStream = _vm->getResource(ID_REGS, compoundSHAPIndex);
regsStream->seek(cursor * 2);
feature->_data.bounds.left = pos.x - regsStream->readSint16BE();
delete regsStream;
regsStream = _vm->getResource(ID_REGS, compoundSHAPIndex + 1);
regsStream->seek(cursor * 2);
feature->_data.bounds.top = pos.y - regsStream->readSint16BE();
delete regsStream;
}
void CSTimeModule::cursorDrawProc(Feature *feature) {
uint16 cursor = _vm->getView()->getBitmapCursor();
if (!cursor)
return;
// FIXME: shouldn't be hardcoded
uint16 compoundSHAPIndex = 200;
_vm->getView()->getGfx()->copyAnimSubImageToScreen(compoundSHAPIndex, cursor - 1, feature->_data.bounds.left, feature->_data.bounds.top);
}
void CSTimeModule::rolloverTextMoveProc(Feature *feature) {
// Should OR the whole bounds into the dirty region, if the text changed.
}
void CSTimeModule::rolloverTextDrawProc(Feature *feature) {
// TODO: if timeBook->getState() is 2, return
const Common::String &text = _vm->getInterface()->getRolloverText();
if (!text.empty()) {
Common::Rect &rect = feature->_data.bounds;
Graphics::Surface *screen = g_system->lockScreen();
_vm->getInterface()->getRolloverFont().drawString(screen, text, rect.left, rect.top, rect.width(), 32, Graphics::kTextAlignCenter);
g_system->unlockScreen();
}
// TODO: some special case about dragging in case 1, scene 4 (torch?)
// TODO: unset text changed flag
}
void CSTimeModule::dialogTextMoveProc(Feature *feature) {
// FIXME
}
void CSTimeModule::dialogTextDrawProc(Feature *feature) {
const Common::Array<Common::String> &lines = _vm->getInterface()->getDialogLines();
const Common::Array<byte> &colors = _vm->getInterface()->getDialogLineColors();
const Common::Rect &bounds = feature->_data.bounds;
const Graphics::Font &font = _vm->getInterface()->getDialogFont();
Graphics::Surface *screen = _vm->_system->lockScreen();
for (uint i = 0; i < lines.size(); i++)
font.drawString(screen, lines[i], bounds.left, bounds.top + 1 + i*15, bounds.width(), colors[i], Graphics::kTextAlignCenter);
_vm->_system->unlockScreen();
// FIXME
}
void CSTimeModule::bubbleTextMoveProc(Feature *feature) {
// FIXME
}
void CSTimeModule::bubbleTextDrawProc(Feature *feature) {
Common::Rect bounds = feature->_data.bounds;
bounds.grow(-5);
const Graphics::Font &font = _vm->getInterface()->getDialogFont();
uint height = font.getFontHeight();
Common::Array<Common::String> lines;
font.wordWrapText(_vm->getInterface()->getCurrBubbleText(), bounds.width(), lines);
Graphics::Surface *screen = _vm->_system->lockScreen();
for (int x = -2; x < 2; x++)
for (int y = -1; y < 3; y++)
for (uint i = 0; i < lines.size(); i++)
font.drawString(screen, lines[i], bounds.left + x, bounds.top + y + i*height, bounds.width(), 241, Graphics::kTextAlignCenter);
for (uint i = 0; i < lines.size(); i++)
font.drawString(screen, lines[i], bounds.left, bounds.top + i*height, bounds.width(), 32, Graphics::kTextAlignCenter);
_vm->_system->unlockScreen();
}
} // End of namespace Mohawk