aboutsummaryrefslogtreecommitdiff
path: root/engines/mads/messages.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/mads/messages.cpp')
-rw-r--r--engines/mads/messages.cpp566
1 files changed, 566 insertions, 0 deletions
diff --git a/engines/mads/messages.cpp b/engines/mads/messages.cpp
new file mode 100644
index 0000000000..263d8fa661
--- /dev/null
+++ b/engines/mads/messages.cpp
@@ -0,0 +1,566 @@
+/* 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 {
+
+RandomMessages::RandomMessages() {
+ reserve(RANDOM_MESSAGE_SIZE);
+ _randomSpacing = 0;
+}
+
+void RandomMessages::reset() {
+ for (uint i = 0; i < size(); ++i) {
+ (*this)[i]._handle = -1;
+ (*this)[i]._quoteId = -1;
+ }
+}
+
+
+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);
+
+ _talkFont = _vm->_font->getFont(FONT_CONVERSATION);
+ _randomMessages.clear();
+}
+
+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._position.x;
+ y1 = seqEntry._position.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;
+ }
+}
+
+#define RANDOM_MESSAGE_TRIGGER 240
+
+void KernelMessages::randomServer() {
+ if ((_vm->_game->_trigger >= RANDOM_MESSAGE_TRIGGER) &&
+ (_vm->_game->_trigger < (RANDOM_MESSAGE_TRIGGER + (int)_randomMessages.size()))) {
+ _randomMessages[_vm->_game->_trigger - RANDOM_MESSAGE_TRIGGER]._handle = -1;
+ _randomMessages[_vm->_game->_trigger - RANDOM_MESSAGE_TRIGGER]._quoteId = -1;
+ }
+}
+
+int KernelMessages::checkRandom() {
+ int total = 0;
+
+ for (uint i = 0; i < _randomMessages.size(); ++i) {
+ if (_randomMessages[i]._handle >= 0)
+ ++total;
+ }
+
+ return total;
+}
+
+bool KernelMessages::generateRandom(int major, int minor) {
+ bool generatedMessage = false;
+
+ // Scan through the random messages array
+ for (uint msgCtr = 0; msgCtr < _randomMessages.size(); msgCtr++) {
+ // Find currently active random messages
+ if (_randomMessages[msgCtr]._handle < 0) {
+ // Check whether there's any existing 'scrolling in' message
+ bool bad = false;
+ for (uint scanCtr = 0; scanCtr < _randomMessages.size(); ++scanCtr) {
+ if (_randomMessages[scanCtr]._handle >= 0) {
+ if (_entries[_randomMessages[scanCtr]._handle]._flags & KMSG_SCROLL) {
+ bad = true;
+ break;
+ }
+ }
+ }
+
+ // Do a random check for a new message to appear
+ if (_vm->getRandomNumber(major) <= minor && !bad) {
+ int quoteId;
+
+ // Pick a random quote to display from the available list
+ do {
+ int quoteIdx = _vm->getRandomNumber(_randomQuotes.size() - 1);
+ quoteId = _randomQuotes[quoteIdx];
+
+ // Ensure the quote isn't already in use
+ bad = false;
+ for (uint scanCtr = 0; scanCtr < _randomMessages.size(); ++scanCtr) {
+ if (quoteId == _randomMessages[scanCtr]._quoteId) {
+ bad = true;
+ break;
+ }
+ }
+ } while (bad);
+
+ // Store the quote Id to be used
+ _randomMessages[msgCtr]._quoteId = quoteId;
+
+ // Position the message at a random position
+ Common::Point textPos;
+ textPos.x = _vm->getRandomNumber(_randomMessages._bounds.left,
+ _randomMessages._bounds.right);
+
+ // Figure out Y position, making sure not to be overlapping with
+ // any other on-screen message
+ int abortCounter = 0;
+
+ do {
+ // Ensure we don't get stuck in an infinite loop if too many messages
+ // are alrady on-screen
+ if (abortCounter++ > 100) goto done;
+ bad = false;
+
+ // Set potential new Y position
+ textPos.y = _vm->getRandomNumber(_randomMessages._bounds.top,
+ _randomMessages._bounds.bottom);
+
+ // Ensure it doesn't overlap an existing on-screen message
+ for (uint msgCtr2 = 0; msgCtr2 < _randomMessages.size(); ++msgCtr2) {
+ if (_randomMessages[msgCtr2]._handle >= 0) {
+ int lastY = _entries[_randomMessages[msgCtr2]._handle]._position.y;
+
+ if ((textPos.y >= (lastY - _randomMessages._randomSpacing)) &&
+ (textPos.y <= (lastY + _randomMessages._randomSpacing))) {
+ bad = true;
+ }
+ }
+ }
+ } while (bad);
+
+ // Add the message
+ _randomMessages[msgCtr]._handle = add(textPos, _randomMessages._color, 0,
+ RANDOM_MESSAGE_TRIGGER + msgCtr, _randomMessages._duration,
+ _vm->_game->getQuote(_randomMessages[msgCtr]._quoteId));
+
+ if (_randomMessages._scrollRate > 0) {
+ if (_randomMessages[msgCtr]._handle >= 0) {
+ setQuoted(_randomMessages[msgCtr]._handle,
+ _randomMessages._scrollRate, true);
+ }
+ }
+
+ generatedMessage = true;
+ break;
+ }
+ }
+ }
+
+done:
+ return generatedMessage;
+}
+
+void KernelMessages::initRandomMessages(int maxSimultaneousMessages,
+ const Common::Rect &bounds, int minYSpacing, int scrollRate,
+ int color, int duration, int quoteId, ...) {
+ // Reset the random messages list
+ _randomMessages.clear();
+ _randomMessages.resize(maxSimultaneousMessages);
+
+ // Store passed parameters
+ _randomMessages._bounds = bounds;
+ _randomMessages._randomSpacing = minYSpacing;
+ _randomMessages._scrollRate = scrollRate;
+ _randomMessages._color = color;
+ _randomMessages._duration = duration;
+
+ // Store the variable length random quote list
+ va_list va;
+ va_start(va, quoteId);
+ _randomQuotes.clear();
+
+ while (quoteId > 0) {
+ _randomQuotes.push_back(quoteId);
+ assert(_randomQuotes.size() < 100);
+ quoteId = va_arg(va, int);
+ }
+
+ va_end(va);
+}
+
+
+/*------------------------------------------------------------------------*/
+
+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