/* 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 "mads/mads.h" #include "mads/font.h" #include "mads/screen.h" #include "mads/messages.h" #include "mads/scene_data.h" namespace MADS { KernelMessages::KernelMessages(MADSEngine *vm): _vm(vm) { for (int i = 0; i < KERNEL_MESSAGES_SIZE; ++i) { KernelMessage rec; _entries.push_back(rec); } _talkFont = _vm->_font->getFont(FONT_CONVERSATION); } KernelMessages::~KernelMessages() { } void KernelMessages::clear() { Scene &scene = _vm->_game->_scene; for (uint i = 0; i < _entries.size(); ++i) _entries[i]._flags = 0; scene._textSpacing = -1; } int KernelMessages::add(const Common::Point &pt, uint fontColor, uint8 flags, uint8 abortTimers, uint32 timeout, const Common::String &msg) { Scene &scene = _vm->_game->_scene; // Find a free slot uint idx = 0; while ((idx < _entries.size()) && ((_entries[idx]._flags & KMSG_ACTIVE) != 0)) ++idx; if (idx == _entries.size()) { if (abortTimers == 0) return -1; error("KernelMessages overflow"); } KernelMessage &rec = _entries[idx]; rec._msg = msg; rec._flags = flags | KMSG_ACTIVE; rec._color1 = fontColor & 0xff; rec._color2 = fontColor >> 8; rec._position = pt; rec._textDisplayIndex = -1; rec._timeout = timeout; rec._frameTimer = _vm->_game->_priorFrameTimer; rec._trigger = abortTimers; rec._abortMode = _vm->_game->_triggerSetupMode; rec._actionDetails = scene._action._activeAction; if (flags & KMSG_PLAYER_TIMEOUT) rec._frameTimer = _vm->_game->_player._ticksAmount + _vm->_game->_player._priorTimer; return idx; } int KernelMessages::addQuote(int quoteId, int abortTimers, uint32 timeout) { Common::String quoteStr = _vm->_game->getQuote(quoteId); return add(Common::Point(), 0x1110, KMSG_PLAYER_TIMEOUT | KMSG_CENTER_ALIGN, abortTimers, timeout, quoteStr); } void KernelMessages::scrollMessage(int msgIndex, int numTicks, bool quoted) { if (msgIndex < 0) return; _entries[msgIndex]._flags |= quoted ? (KMSG_SCROLL | KMSG_QUOTED) : KMSG_SCROLL; _entries[msgIndex]._msgOffset = 0; _entries[msgIndex]._numTicks = numTicks; _entries[msgIndex]._frameTimer2 = _vm->_game->_priorFrameTimer; Common::String msg = _entries[msgIndex]._msg; if (_entries[msgIndex]._flags & KMSG_PLAYER_TIMEOUT) _entries[msgIndex]._frameTimer2 = _vm->_game->_player._ticksAmount + _vm->_game->_player._priorTimer; _entries[msgIndex]._frameTimer = _entries[msgIndex]._frameTimer2; } void KernelMessages::setSeqIndex(int msgIndex, int seqIndex) { if (msgIndex >= 0) { _entries[msgIndex]._flags |= KMSG_SEQ_ENTRY; _entries[msgIndex]._sequenceIndex = seqIndex; } } void KernelMessages::remove(int msgIndex) { KernelMessage &rec = _entries[msgIndex]; Scene &scene = _vm->_game->_scene; if (rec._flags & KMSG_ACTIVE) { if (rec._flags & KMSG_SCROLL) { // WORKAROUND: Code here no longer needed in ScummVM } if (rec._textDisplayIndex >= 0) scene._textDisplay.expire(rec._textDisplayIndex); rec._flags &= ~KMSG_ACTIVE; } } void KernelMessages::reset() { for (uint i = 0; i < _entries.size(); ++i) remove(i); warning("TODO: KernelMessages::reset - sub_20454"); } void KernelMessages::update() { uint32 currentTimer = _vm->_game->_scene._frameStartTime; for (uint i = 0; i < _entries.size() && !_vm->_game->_trigger; ++i) { KernelMessage &msg = _entries[i]; if (((msg._flags & KMSG_ACTIVE) != 0) && (currentTimer >= msg._frameTimer)) processText(i); } } void KernelMessages::processText(int msgIndex) { Scene &scene = _vm->_game->_scene; KernelMessage &msg = _entries[msgIndex]; uint32 currentTimer = _vm->_game->_priorFrameTimer; bool flag = false; if ((msg._flags & KMSG_EXPIRE) != 0) { scene._textDisplay.expire(msg._textDisplayIndex); msg._flags &= !KMSG_ACTIVE; return; } if ((msg._flags & KMSG_SCROLL) == 0) { msg._timeout -= 3; } if (msg._flags & KMSG_SEQ_ENTRY) { SequenceEntry &seqEntry = scene._sequences[msg._sequenceIndex]; if (seqEntry._doneFlag || !seqEntry._active) msg._timeout = 0; } if ((msg._timeout <= 0) && (_vm->_game->_trigger == 0)) { msg._flags |= KMSG_EXPIRE; if (msg._trigger != 0) { _vm->_game->_trigger = msg._trigger; _vm->_game->_triggerMode = msg._abortMode; if (_vm->_game->_triggerMode != SEQUENCE_TRIGGER_DAEMON) { scene._action._activeAction = msg._actionDetails; } } } msg._frameTimer = currentTimer + 3; int x1 = 0, y1 = 0; if (msg._flags & KMSG_SEQ_ENTRY) { SequenceEntry &seqEntry = scene._sequences[msg._sequenceIndex]; if (!seqEntry._nonFixed) { SpriteAsset &spriteSet = *scene._sprites[seqEntry._spritesIndex]; MSprite *frame = spriteSet.getFrame(seqEntry._frameIndex - 1); x1 = frame->getBounds().left; y1 = frame->getBounds().top; } else { x1 = seqEntry._msgPos.x; y1 = seqEntry._msgPos.y; } } Player &player = _vm->_game->_player; if (msg._flags & KMSG_PLAYER_TIMEOUT) { if (player._beenVisible) { SpriteAsset &asset = *_vm->_game->_scene._sprites[player._spritesStart + player._spritesIdx]; MSprite *frame = asset.getFrame(player._frameNumber - 1); int yAmount = player._currentScale * player._centerOfGravity / 100; x1 = player._playerPos.x; y1 = (frame->h * player._currentScale / -100) + yAmount + player._playerPos.y - 15; } else { x1 = 160; y1 = 78; } } x1 += msg._position.x; y1 += msg._position.y; Common::String displayMsg = msg._msg; if ((msg._flags & KMSG_SCROLL) && (msg._frameTimer >= currentTimer)) { ++msg._msgOffset; if (msg._msgOffset >= msg._msg.size()) { // End of message msg._flags &= ~KMSG_SCROLL; } else { displayMsg = Common::String(msg._msg.c_str(), msg._msg.c_str() + msg._msgOffset); } msg._frameTimer = msg._frameTimer2 = currentTimer + msg._numTicks; flag = true; } int strWidth = _talkFont->getWidth(displayMsg, scene._textSpacing); if (msg._flags & (KMSG_RIGHT_ALIGN | KMSG_CENTER_ALIGN)) { x1 -= (msg._flags & KMSG_CENTER_ALIGN) ? strWidth / 2 : strWidth; } // Make sure text appears entirely on-screen int x2 = x1 + strWidth; if (x2 > MADS_SCREEN_WIDTH) x1 -= x2 - MADS_SCREEN_WIDTH; if (x1 > (MADS_SCREEN_WIDTH - 1)) x1 = MADS_SCREEN_WIDTH - 1; if (x1 < 0) x1 = 0; if (y1 >(MADS_SCENE_HEIGHT - 1)) y1 = MADS_SCENE_HEIGHT - 1; if (y1 < 0) y1 = 0; if (msg._textDisplayIndex >= 0) { TextDisplay &textEntry = scene._textDisplay[msg._textDisplayIndex]; if (flag || (textEntry._bounds.left != x1) || (textEntry._bounds.top != y1)) { // Mark the associated text entry as deleted, so it can be re-created scene._textDisplay.expire(msg._textDisplayIndex); msg._textDisplayIndex = -1; } } if (msg._textDisplayIndex < 0) { // Need to create a new text display entry for this message int idx = scene._textDisplay.add(x1, y1, msg._color1 | (msg._color2 << 8), scene._textSpacing, displayMsg, _talkFont); if (idx >= 0) msg._textDisplayIndex = idx; } } void KernelMessages::delay(uint32 priorFrameTime, uint32 currentTime) { for (uint i = 0; i < _entries.size(); ++i) { _entries[i]._timeout += currentTime - priorFrameTime; } } void KernelMessages::setQuoted(int msgIndex, int numTicks, bool quoted) { if (msgIndex >= 0) { KernelMessage &msg = _entries[msgIndex]; msg._flags |= KMSG_SCROLL; if (quoted) msg._flags |= KMSG_QUOTED; msg._msgOffset = 0; msg._numTicks = numTicks; msg._frameTimer2 = _vm->_game->_scene._frameStartTime; if (msg._flags & KMSG_PLAYER_TIMEOUT) { msg._frameTimer2 = _vm->_game->_player._priorTimer + _vm->_game->_player._ticksAmount; } msg._frameTimer = msg._frameTimer2; } } /*------------------------------------------------------------------------*/ TextDisplay::TextDisplay() { _active = false; _expire = 0; _spacing = 0; _color1 = 0; _color2 = 0; _font = nullptr; } /*------------------------------------------------------------------------*/ TextDisplayList::TextDisplayList(MADSEngine *vm) : _vm(vm) { for (int i = 0; i < TEXT_DISPLAY_SIZE; ++i) { TextDisplay rec; rec._active = false; rec._expire = 0; push_back(rec); } } void TextDisplayList::reset() { for (int i = 0; i < TEXT_DISPLAY_SIZE; ++i) (*this)[i]._active = false; } int TextDisplayList::add(int xp, int yp, uint fontColor, int charSpacing, const Common::String &msg, Font *font) { int usedSlot = -1; for (int idx = 0; idx < TEXT_DISPLAY_SIZE; ++idx) { TextDisplay &td = (*this)[idx]; if (!td._active) { usedSlot = idx; td._bounds.left = xp; td._bounds.top = yp; td._font = font; td._msg = msg; td._bounds.setWidth(font->getWidth(msg, charSpacing)); td._bounds.setHeight(font->getHeight()); td._color1 = fontColor & 0xff; td._color2 = fontColor >> 8; td._spacing = charSpacing; td._expire = 1; td._active = true; break; } } return usedSlot; } void TextDisplayList::setDirtyAreas() { Scene &scene = _vm->_game->_scene; for (uint idx = 0, dirtyIdx = SPRITE_SLOTS_MAX_SIZE; dirtyIdx < size(); ++idx, ++dirtyIdx) { if (((*this)[idx]._expire >= 0) || !(*this)[idx]._active) { scene._dirtyAreas[dirtyIdx]._active = false; } else { scene._dirtyAreas[dirtyIdx]._textActive = true; scene._dirtyAreas[dirtyIdx].setTextDisplay(&(*this)[idx]); } } } void TextDisplayList::setDirtyAreas2() { Scene &scene = _vm->_game->_scene; for (uint idx = 0, dirtyIdx = SPRITE_SLOTS_MAX_SIZE; idx < size(); ++idx, ++dirtyIdx) { if ((*this)[idx]._active && ((*this)[idx]._expire >= 0)) { scene._dirtyAreas[dirtyIdx].setTextDisplay(&(*this)[idx]); scene._dirtyAreas[dirtyIdx]._textActive = ((*this)[idx]._expire <= 0) ? 0 : 1; } } } void TextDisplayList::draw(MSurface *s) { for (uint idx = 0; idx < size(); ++idx) { TextDisplay &td = (*this)[idx]; if (td._active && (td._expire >= 0)) { td._font->setColors(0xFF, td._color1, td._color2, 0); td._font->writeString(s, td._msg, Common::Point(td._bounds.left, td._bounds.top), td._spacing, td._bounds.width()); } } } void TextDisplayList::cleanUp() { for (uint idx = 0; idx < size(); ++idx) { if ((*this)[idx]._expire < 0) { (*this)[idx]._active = false; (*this)[idx]._expire = 0; } } } void TextDisplayList::expire(int idx) { (*this)[idx]._expire = -1; } } // End of namespace MADS