scummvm/engines/saga2/speech.cpp
D G Turner 894813870e SAGA2: Fix Redundant Declaration Warnings
These warnings are generated by GCC if -Wredundant-decls is enabled.
2022-03-07 18:30:58 +00:00

1120 lines
33 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
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/fta.h"
#include "saga2/fontlib.h"
#include "saga2/speech.h"
#include "saga2/motion.h"
#include "saga2/panel.h"
#include "saga2/grabinfo.h"
#include "saga2/player.h"
#include "saga2/annoy.h"
#include "saga2/cmisc.h"
#include "saga2/tile.h"
#include "saga2/tilemode.h"
namespace Saga2 {
struct TextSpan {
char *text; // pointer to 1st char of span
int16 charWidth, // number of characters in span
pixelWidth; // number of pixels in span
};
//-----------------------------------------------------------------------
// externs
extern StaticPoint16 fineScroll;
int kludgeHeight = 15;
extern StaticTilePoint viewCenter; // coordinates of view on map
//-----------------------------------------------------------------------
// constants
const int maxWidth = 420;
const int defaultWidth = 380;
const int actorHeight = 80; // Assume 80
const int lineLeading = 2; // space between lines
const int outlineWidth = 2; // width of character outline
const int bulletWidth = 13; // width of bullet symbol
//-----------------------------------------------------------------------
// prototypes
int16 buttonWrap(
TextSpan *lineList, // indicates where line breaks are
TextSpan *buttonList, // indicates where button breaks are
int16 &buttonCount, // returns number of buttons
char *text, // text to wrap
int16 width, // width of text
int16 supressText,
gPort &textPort);
//-----------------------------------------------------------------------
// locals
// Temporary: Alarm which determines when speech finishes
Alarm speechFinished;
// The list of active and non-active speech tasks for all actors
uint8 speechListBuffer[sizeof(SpeechTaskList)];
SpeechTaskList &speechList = *((SpeechTaskList *)speechListBuffer);
static TextSpan speechLineList[64], // list of speech lines
speechButtonList[64]; // list of speech buttons
int16 speechLineCount, // count of speech lines
speechButtonCount; // count of speech buttons
static StaticPoint16 initialSpeechPosition = {0, 0}; // inital coords of speech
// Image data for the little "bullet"
static uint8 BulletData[] = {
0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // Row 0
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // Row 1
0x00, 0x18, 0x18, 0x4C, 0x4A, 0x4A, 0x18, 0x18, 0x00, // Row 2
0x18, 0x18, 0x4E, 0x4C, 0x4A, 0x0A, 0x4A, 0x18, 0x18, // Row 3
0x18, 0x18, 0x50, 0x4E, 0x4C, 0x4A, 0x4A, 0x18, 0x18, // Row 4
0x18, 0x18, 0x4E, 0x50, 0x50, 0x4E, 0x4E, 0x18, 0x18, // Row 5
0x00, 0x18, 0x18, 0x4E, 0x50, 0x50, 0x18, 0x18, 0x00, // Row 6
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // Row 7
0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // Row 8
};
static StaticPixelMap BulletImage = {{9, 9}, BulletData};
//-----------------------------------------------------------------------
// Speech button mode override.
extern gPanelList *speakButtonControls; // controls for embedded speech button
//-----------------------------------------------------------------------
// Audio resource ID generator
static char convBuf[5];
inline uint32 extendID(int16 smallID) {
sprintf(convBuf, "%4.4d", smallID);
return smallID ? MKTAG(convBuf[0] + 'A' - '0', convBuf[1], convBuf[2], convBuf[3]) : 0 ;
}
/* ===================================================================== *
Speech member functions
* ===================================================================== */
void Speech::read(Common::InSaveFile *in) {
// Restore the sample count and character count
sampleCount = in->readSint16LE();
charCount = in->readSint16LE();
// Restore the text boundaries
bounds.read(in);
// Restore the pen color and outline color
penColor = in->readUint16LE();
outlineColor = in->readUint16LE();
// Restore the object ID
objID = in->readUint16LE();
// Restore the thread ID
thread = in->readSint16LE();
// Restore the flags
speechFlags = in->readSint16LE();
debugC(4, kDebugSaveload, "...... sampleCount = %d", sampleCount);
debugC(4, kDebugSaveload, "...... charCount = %d", charCount);
debugC(4, kDebugSaveload, "...... penColor = %d", penColor);
debugC(4, kDebugSaveload, "...... outlineColor = %d", outlineColor);
debugC(4, kDebugSaveload, "...... bounds = (%d, %d, %d, %d)",
bounds.x, bounds.y, bounds.width, bounds.height);
debugC(4, kDebugSaveload, "...... objID = %d", objID);
debugC(4, kDebugSaveload, "...... thread = %d", thread);
debugC(4, kDebugSaveload, "...... speechFlags = %d", speechFlags);
// Restore the sample ID's
for (int i = 0; i < sampleCount; i++) {
sampleID[i] = in->readUint32BE();
debugC(4, kDebugSaveload, "...... sampleID[%d] = %d", i, sampleID[i]);
}
// Restore the text
in->read(speechBuffer, charCount);
speechBuffer[charCount] = '\0';
debugC(4, kDebugSaveload, "...... speechBuffer = %s", speechBuffer);
// Requeue the speech if needed
if (speechFlags & spQueued) {
// Add to the active list
speechList.remove(this);
speechList._list.push_back(this);
}
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive this SpeechTask
int32 Speech::archiveSize() {
return sizeof(sampleCount)
+ sizeof(charCount)
+ sizeof(bounds)
+ sizeof(penColor)
+ sizeof(outlineColor)
+ sizeof(objID)
+ sizeof(thread)
+ sizeof(speechFlags)
+ sizeof(uint32) * sampleCount
+ sizeof(char) * charCount;
}
void Speech::write(Common::MemoryWriteStreamDynamic *out) {
// Store the sample count and character count
out->writeSint16LE(sampleCount);
out->writeSint16LE(charCount);
// Store the text boundaries
bounds.write(out);
// Store the pen color and outline color
out->writeUint16LE(penColor);
out->writeUint16LE(outlineColor);
// Store the object's ID
out->writeUint16LE(objID);
// Store the thread ID
out->writeSint16LE(thread);
// Store the flags. NOTE: Make sure this speech is not stored
// as being active
out->writeSint16LE(speechFlags & ~spActive);
debugC(4, kDebugSaveload, "...... sampleCount = %d", sampleCount);
debugC(4, kDebugSaveload, "...... charCount = %d", charCount);
debugC(4, kDebugSaveload, "...... penColor = %d", penColor);
debugC(4, kDebugSaveload, "...... outlineColor = %d", outlineColor);
debugC(4, kDebugSaveload, "...... bounds = (%d, %d, %d, %d)",
bounds.x, bounds.y, bounds.width, bounds.height);
debugC(4, kDebugSaveload, "...... objID = %d", objID);
debugC(4, kDebugSaveload, "...... thread = %d", thread);
debugC(4, kDebugSaveload, "...... speechFlags = %d", speechFlags);
for (int i = 0; i < sampleCount; i++) {
out->writeUint32BE(sampleID[i]);
debugC(4, kDebugSaveload, "...... sampleID[%d] = %d", i, sampleID[i]);
}
// Store the text
out->write(speechBuffer, charCount);
debugC(4, kDebugSaveload, "...... speechBuffer = %s", speechBuffer);
}
//-----------------------------------------------------------------------
// Append text and sample to existing speech record
bool Speech::append(char *text, int32 sampID) {
int16 len = strlen(text);
// Check to see if there's enough room in the character buffer
if (charCount + len >= (long)sizeof(speechBuffer)
|| sampleCount >= MAX_SAMPLES) return false;
// Copy text to end of text in buffer, including '\0'
memcpy(&speechBuffer[charCount], text, len + 1);
charCount += len;
// Append sample ID to list of samples.
// REM: We should translate sample ID's from index to resource
// number here.
if (sampID)
sampleID[sampleCount++] = extendID(sampID);
return true;
}
//-----------------------------------------------------------------------
// Move speech to active list
bool Speech::activate() {
// Remove from existing list
speechList.remove(this);
// Add to the active list
speechList._list.push_back(this);
speechFlags |= spQueued;
// This routine can't fail
return true;
}
//-----------------------------------------------------------------------
// Move speech to active list
bool Speech::setupActive() {
int16 x, y;
int16 buttonNum = 0,
buttonChars;
speechFlags |= spActive;
speechFinished.set((charCount * 4 / 2) + ticksPerSecond);
//Turn Actor Towards Person They Are Talking To
// MotionTask::turnObject( *obj, GameObject::objectAddress(32794)->getLocation());
// Actor *a = (Actor *)obj;
// if(!a->setAction( actionJumpUp, animateRandom ))
// throw gError( "Could Not Set Talk Animation");
// Set up temp gport for blitting to bitmap
_textPort.setStyle(textStyleThickOutline); // extra Thick Outline
_textPort.setOutlineColor(outlineColor); // outline black
_textPort.setFont(&Amber13Font); // speech font
_textPort.setColor(penColor); // color of letters
_textPort.setMode(drawModeMatte); // insure transparency
setWidth();
// If speech position is off-screen, then skip
if (!calcPosition(initialSpeechPosition)) return false;
if (sampleCount) {
GameObject *go = GameObject::objectAddress(objID);
Location loc = go->notGetWorldLocation();
sampleID[sampleCount] = 0;
//sayVoice(sampleID);
/// EO SEARCH ///
if (sayVoiceAt(sampleID, loc))
speechFlags |= spHasVoice;
else
speechFlags &= ~spHasVoice;
} else speechFlags &= ~spHasVoice;
speechLineCount = buttonWrap(speechLineList,
speechButtonList,
speechButtonCount,
speechBuffer,
bounds.width,
!g_vm->_speechText && (speechFlags & spHasVoice),
_textPort);
// Compute height of bitmap based on number of lines of text.
// Include 4 for outline width
bounds.height =
(speechLineCount * (_textPort.font->height + lineLeading))
+ outlineWidth * 2;
// Blit to temp bitmap
_speechImage.size.x = bounds.width;
_speechImage.size.y = bounds.height;
_speechImage.data = new uint8[_speechImage.bytes()]();
_textPort.setMap(&_speechImage);
y = outlineWidth; // Plus 2 for Outlines
buttonChars = speechButtonList[buttonNum].charWidth;
for (int i = 0; i < speechLineCount; i++) {
int16 lineChars = speechLineList[i].charWidth;
char *lineText = speechLineList[i].text;
x = (bounds.width - speechLineList[i].pixelWidth) / 2
+ outlineWidth;
_textPort.moveTo(x, y);
// Draw each button on the line in turn.
while (lineChars > 0) {
int16 dChars;
// If this is the end of a button
if (buttonChars <= 0) {
// Incr to next button
buttonNum++;
// If no more buttons, then go nae ferther...
// Fer death awaits with nasty, pointy teeth...!
if (buttonNum > speechButtonCount) break;
buttonChars = speechButtonList[buttonNum].charWidth;
_textPort.setColor(1 + 9);
// Blit the little bullet symbol
lineChars--;
lineText++;
buttonChars--;
_textPort.bltPixels(
BulletImage, 0, 0,
_textPort.penPos.x, _textPort.penPos.y + 1,
BulletImage.size.x, BulletImage.size.y);
_textPort.move(bulletWidth, 0);
}
// Compute how much of this button is on this line.
dChars = MIN(lineChars, buttonChars);
// Draw however much of this button is on this line.
_textPort.drawText(lineText, dChars);
// Move forward by dChars
lineChars -= dChars;
buttonChars -= dChars;
lineText += dChars;
}
y += _textPort.font->height + lineLeading;
}
if (speechButtonCount > 0) {
// REM: Also set pointer to arrow shape.
g_vm->_mouseInfo->setIntent(GrabInfo::WalkTo);
speakButtonControls->enable(true);
speechList.SetLock(false);
} else {
// If there is a lock flag on this speech, then LockUI()
speechList.SetLock(speechFlags & spLock);
}
if (!(speechFlags & spNoAnimate) && isActor(objID)) {
Actor *a = (Actor *)GameObject::objectAddress(objID);
if (!a->isDead() && !a->isMoving()) MotionTask::talk(*a);
}
// speechFinished.set( ticksPerSecond*2 );
return (true);
}
//This Function Sets Up Width And Height For A Speech
void Speech::setWidth() {
TextSpan speechLineList_[32], // list of speech lines
speechButtonList_[32]; // list of speech buttons
int16 speechLineCount_, // count of speech lines
speechButtonCount_; // count of speech buttons
// How many word-wrapped lines does the speech take up if we word-wrap
// it to the default line width?
speechLineCount_ = buttonWrap(speechLineList_,
speechButtonList_,
speechButtonCount_,
speechBuffer,
defaultWidth,
!g_vm->_speechText && (speechFlags & spHasVoice),
_textPort);
// If it's more than 3 lines, then use the max line width.
if (speechLineCount_ > 3) {
speechLineCount_ = buttonWrap(speechLineList_,
speechButtonList_,
speechButtonCount_,
speechBuffer,
maxWidth,
!g_vm->_speechText && (speechFlags & spHasVoice),
_textPort);
}
// The actual width of the bounds is the widest of the lines.
bounds.width = 0;
for (int i = 0; i < speechLineCount_; i++) {
bounds.width = MAX(bounds.width, speechLineList_[i].pixelWidth);
}
bounds.width += outlineWidth * 2 + 4; // Some padding just in case.
}
//-----------------------------------------------------------------------
// Calculate the position of the speech, emanating from the actor.
bool Speech::calcPosition(StaticPoint16 &p) {
GameObject *obj = GameObject::objectAddress(objID);
TilePoint tp = obj->getWorldLocation();
if (!isVisible(obj)) return false;
TileToScreenCoords(tp, p);
p.x = clamp(8,
p.x - bounds.width / 2,
8 + maxWidth - bounds.width);
p.y = clamp(kTileRectY + 8,
p.y - (bounds.height + actorHeight),
kTileRectHeight - 50 - bounds.height);
return true;
}
//-----------------------------------------------------------------------
// Draw the text on the back buffer
bool Speech::displayText() {
StaticPoint16 p;
// If there are button in the speech, then don't scroll the
// speech along with the display. Otherwise, calculate the
// position from the actor.
if (speechButtonCount > 0)
p = initialSpeechPosition;
else if (!calcPosition(p))
return false;
// Blit to the port
g_vm->_backPort.setMode(drawModeMatte);
g_vm->_backPort.bltPixels(_speechImage,
0, 0,
p.x + fineScroll.x,
p.y + fineScroll.y,
bounds.width, bounds.height);
return true;
}
//-----------------------------------------------------------------------
// Dispose of this speech object. If this is the one being displayed,
// then dealloc the speech image
void Speech::dispose() {
if (speechList.currentActive() == this) {
// Actor *a = (Actor *)sp->obj;
// a->animationFlags |= animateFinished;
// a->setAction( actionStand, animateRandom );
if (!longEnough())
playVoice(0);
// Wake up the thread, and return the # of the button
// that was selected
wakeUpThread(thread, selectedButton);
// De-allocate the speech data
delete[] _speechImage.data;
_speechImage.data = nullptr;
// Clear the number of active buttons
speechLineCount = speechButtonCount = 0;
speakButtonControls->enable(false);
if (!(speechFlags & spNoAnimate) && isActor(objID)) {
Actor *a = (Actor *)GameObject::objectAddress(objID);
if (a->_moveTask)
a->_moveTask->finishTalking();
}
} else wakeUpThread(thread, 0);
GameObject *obj = GameObject::objectAddress(objID);
debugC(1, kDebugTasks, "Speech: Disposing %p for %p (%s) (total = %d)'", (void *)this, (void *)obj, obj->objName(), speechList.speechCount());
remove();
}
//-----------------------------------------------------------------------
// Render the speech object at the head of the speech queue.
void updateSpeech() {
Speech *sp;
// if there is a speech object
if ((sp = speechList.currentActive()) != nullptr) {
// If there is no bitmap, then set one up.
if (!(sp->speechFlags & Speech::spActive)) {
sp->setupActive();
// If speech failed to set up, then skip it
if (sp->_speechImage.data == nullptr) {
sp->dispose();
return;
}
}
// Draw the speech bitmap
sp->displayText();
// If this speech has timed-out, then dispose of it.
if (sp->longEnough() &&
(speechButtonCount == 0 || sp->selectedButton != 0))
sp->dispose();
} else speechList.SetLock(false);
}
bool Speech::longEnough() {
if (speechFlags & spHasVoice)
return (!stillDoingVoice(sampleID));
else
return (selectedButton != 0 || speechFinished.check());
}
// Gets rid of the current speech
void Speech::abortSpeech() {
// Start by displaying first frame straight off, no delay
speechFinished.set(0);
if (speechFlags & spHasVoice) {
PlayVoice(nullptr);
}
}
void abortSpeech() {
if (speechList.currentActive()) speechList.currentActive()->abortSpeech();
}
//-----------------------------------------------------------------------
// Delete all speeches relating to a particular actor
void deleteSpeech(ObjectID id) { // voice sound sample ID
Speech *sp;
while ((sp = speechList.findSpeech(id)) != nullptr) sp->dispose();
}
//-----------------------------------------------------------------------
// This routine does a word-wrap on the input text, and also checks for
// the '@' symbol to see if there are any embedded buttons in the text.
int16 buttonWrap(
TextSpan *lineList, // indicates where line breaks are
TextSpan *buttonList, // indicates where button breaks are
int16 &buttonCount, // returns number of buttons
char *text, // text to wrap
int16 width, // width of text
int16 supressText,
gPort &textPort) {
int16 i, // loop counter
line_start, // start of current line
last_space, // last space encountered
last_space_pixels = 0, // pixel pos of last space
charPixels, // pixel length of character
linePixels, // pixels in current line
buttonPixels, // pixels in current button
buttonChars, // char count of current button
lineCount = 0; // number of lines
// If we are not showing the text of the speech, skip over all text
// until we come to the first button definition.
if (supressText) {
while (*text && *text != '@') text++;
}
lineList->text = text; // set ptr to 1st line
last_space = -1; // no spaces to word-wrap yet
line_start = 0; // start index of 1st line
linePixels = 0; // no pixels counted yet
width -= outlineWidth * 2; // compensate for size of outline
// For each character in the string, check for word wrap
for (i = 0; ; i++) {
uint8 c = text[i];
// REM: Translate from foreign character set if needed...
// c = TranslationTable[c];
// If deliberate end of line
if (c == '\n' || c == '\r' || c == '\0') {
lineList->charWidth = i - line_start;
lineList->pixelWidth = linePixels;
lineList++;
lineCount++;
line_start = i + 1;
if (c == '\0') break;
lineList->text = &text[line_start];
last_space = -1;
linePixels = 0;
continue;
} else if (c == '@') { // button indicator...
// Set width of 'bullet' symbol.
charPixels = bulletWidth;
} else { // Any other character
// if it's a space, save the word wrap position.
if (c == ' ') {
last_space = i;
last_space_pixels = linePixels;
}
// Add to pixel length
charPixels
= textPort.font->charKern[c]
+ textPort.font->charSpace[c];
}
linePixels += charPixels;
// If pixel runs off end of line
if (linePixels > width && last_space > 0) {
lineList->charWidth = last_space - line_start;
lineList->pixelWidth = last_space_pixels;
lineList++;
lineCount++;
line_start = last_space + 1;
lineList->text = &text[line_start];
last_space = -1;
linePixels = 0;
i = line_start - 1;
}
}
buttonCount = 0; // assume zero buttons
buttonPixels = 0; // no pixels counted yet
buttonChars = 0; // no chars counted yet
buttonList->text = text; // set ptr to 1st button
lineList -= lineCount; // reset line list
// For each line, look for button markers
for (int l = 0; l < lineCount; l++, lineList++) {
for (i = 0; i < lineList->charWidth; i++) {
uint8 c = lineList->text[i];
// REM: Translate from foreign character set if needed...
// c = TranslationTable[c];
if (c == '@') { // button indicator...
// A new button
buttonList->charWidth = buttonChars;
buttonList->pixelWidth = buttonPixels;
buttonPixels = 0;
buttonChars = 0;
buttonList++;
buttonCount++;
buttonList->text = text; // set ptr to 1st button
// Set width of 'bullet' symbol.
charPixels = bulletWidth;
} else { // Any other character
// Add to pixel length
charPixels
= textPort.font->charKern[c]
+ textPort.font->charSpace[c];
}
buttonPixels += charPixels;
buttonChars++;
}
}
// Clean up the final button
buttonList->charWidth = buttonChars;
buttonList->pixelWidth = buttonPixels;
return lineCount;
}
//-----------------------------------------------------------------------
// Given the original word-wrap info, determines which button (if any)
// was clicked.
int16 pickButton(
Point16 &pt,
TextSpan *lineList, // indicates where line breaks are
int16 numLines, // number of line breaks
TextSpan *buttonList, // indicates where button breaks are
int16 buttonCount, // number of buttons
int16 width,
gPort textPort) { // width of rectangle
int16 pickLine,
pickPixels = 0,
centerWidth;
if (pt.y < 0 // picked off top edge
|| pt.x < 0 // picked off left edge
|| buttonCount < 1) // no buttons defined
return 0;
pickLine = pt.y / (textPort.font->height + lineLeading);
if (pickLine >= numLines) return 0;
// Strange algorithm:
//
// When we first built these data structures, we took a continuous
// string of text and broke it up into several lines, and also
// broke it up into several buttons. Each of these has a count
// of how many pixels wide it was.
//
// Now, consider if we were to lay each of those lines end
// to end as though we were reconstructing the original non-
// wrapped text string. And suppose the pick-point were carried
// along with the line, so that now our 2-d pixel point is
// now a 1-d pixel offset into the line.
//
// We can do this by adding the length of each previous line
// to the pixel offset, and subtracting the margin space used
// for centering.
for (int i = 0; i < pickLine; i++) {
pickPixels += lineList[i].pixelWidth;
}
centerWidth = (width - lineList[pickLine].pixelWidth) / 2;
// Return 0 if mouse off left or right edge of text.
if (pt.x < centerWidth || pt.x > width - centerWidth) return 0;
pickPixels += pt.x - (width - lineList[pickLine].pixelWidth) / 2;
// Now, we lay all the buttons end to end in a similar fashion,
// and determine which button the pick point fell into, in a
// simple 1-d comparison.
for (int j = 0; j <= buttonCount; j++) {
pickPixels -= buttonList[j].pixelWidth;
if (pickPixels < 0) return j;
}
return 0;
}
bool isVisible(GameObject *obj) {
TilePoint tp = obj->getWorldLocation();
Point16 p, vp;
TileToScreenCoords(tp, p);
//For Determining If Object Is Being Displayed
//Could We Just Check Display List ???
int16 distanceX, distanceY;
int16 viewSizeY = kTileRectHeight;
int16 viewSizeX = kTileRectWidth;
//I Figure This Differently Than In Dispnode
int16 loadDistX = viewSizeX / 2;
int16 loadDistY = viewSizeY / 2;
TileToScreenCoords(viewCenter, vp);
distanceX = ABS(vp.x - p.x);
distanceY = ABS(vp.y - p.y);
if ((distanceY >= loadDistY) ||
(distanceX >= loadDistX))
return (false);
return (true);
}
/* ===================================================================== *
SpeechTaskList member functions
* ===================================================================== */
void SpeechTaskList::remove(Speech *p) {
for (Common::List<Speech *>::iterator it = _list.begin();
it != _list.end(); ++it) {
if (p == *it) {
_list.remove(p);
break;
}
}
for (Common::List<Speech *>::iterator it = _inactiveList.begin();
it != _inactiveList.end(); ++it) {
if (p == *it) {
_inactiveList.remove(p);
break;
}
}
}
//-----------------------------------------------------------------------
// Initialize the SpeechTaskList
SpeechTaskList::SpeechTaskList() {
lockFlag = false;
}
SpeechTaskList::SpeechTaskList(Common::InSaveFile *in) {
int16 count;
lockFlag = false;
// Get the speech count
count = in->readSint16LE();
debugC(3, kDebugSaveload, "... count = %d", count);
// Restore the speeches
for (int i = 0; i < count; i++) {
Speech *sp = new Speech;
assert(sp != nullptr);
debugC(3, kDebugSaveload, "Loading Speech %d", i++);
_inactiveList.push_back(sp);
sp->read(in);
}
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive the speech tasks
int32 SpeechTaskList::archiveSize() {
int32 size = 0;
size += sizeof(int16); // Speech count
for (Common::List<Speech *>::iterator it = _list.begin();
it != _list.end(); ++it) {
size += (*it)->archiveSize();
}
for (Common::List<Speech *>::iterator it = _inactiveList.begin();
it != _inactiveList.end(); ++it) {
size += (*it)->archiveSize();
}
return size;
}
void SpeechTaskList::write(Common::MemoryWriteStreamDynamic *out) {
int i = 0;
int16 count = 0;
count += _list.size() + _inactiveList.size();
// Store speech count
out->writeSint16LE(count);
debugC(3, kDebugSaveload, "... count = %d", count);
// Store active speeches
for (Common::List<Speech *>::iterator it = _list.begin();
it != _list.end(); ++it) {
debugC(3, kDebugSaveload, "Saving Speech %d (active)", i++);
(*it)->write(out);
}
// Store inactive speeches
for (Common::List<Speech *>::iterator it = _inactiveList.begin();
it != _inactiveList.end(); ++it) {
debugC(3, kDebugSaveload, "Saving Speech %d (inactive)", i++);
(*it)->write(out);
}
}
//-----------------------------------------------------------------------
// Cleanup the speech tasks
void SpeechTaskList::cleanup() {
for (Common::List<Speech *>::iterator it = speechList._list.begin();
it != speechList._list.end(); ++it) {
delete *it;
}
for (Common::List<Speech *>::iterator it = speechList._inactiveList.begin();
it != speechList._inactiveList.end(); ++it) {
delete *it;
}
_list.clear();
_inactiveList.clear();
}
//-----------------------------------------------------------------------
// Search for a speech task associated with a particular GameObject.
Speech *SpeechTaskList::findSpeech(ObjectID id) {
for (Common::List<Speech *>::iterator it = speechList._inactiveList.begin();
it != speechList._inactiveList.end(); ++it) {
if ((*it)->objID == id)
return *it;
}
return nullptr;
}
//-----------------------------------------------------------------------
// Get a new speech task, if there is one available, and initialize it.
Speech *SpeechTaskList::newTask(ObjectID id, uint16 flags) {
Speech *sp;
GameObject *obj = GameObject::objectAddress(id);
// Actors cannot speak if not in the world
if (obj->world() != currentWorld) return nullptr;
if (speechCount() >= MAX_SPEECH_PTRS) {
warning("Too many speech tasks: > %d", MAX_SPEECH_PTRS);
return nullptr;
}
sp = new Speech;
#if DEBUG
if (sp == NULL) fatal("Ran out of Speech Tasks, Object = %s\n", obj->objName());
#endif
if (sp == nullptr) return nullptr;
debugC(1, kDebugTasks, "Speech: New Task: %p for %p (%s) (flags = %d) (total = %d)", (void *)sp, (void *)obj, obj->objName(), flags, speechCount());
sp->sampleCount = sp->charCount = 0;
sp->objID = id;
sp->speechFlags = flags & (Speech::spNoAnimate | Speech::spLock);
sp->outlineColor = 15 + 9;
sp->thread = NoThread;
sp->selectedButton = 0;
// Set the pen color of the speech based on the actor
if (isActor(id)) {
Actor *a = (Actor *)obj;
// If actor has color table loaded, then get the speech
// color for this particular color scheme; else use a
// default color.
if (a == getCenterActor()) sp->penColor = 3 + 9 /* 1 */;
else if (a->_appearance
&& a->_appearance->schemeList) {
sp->penColor =
a->_appearance->schemeList->_schemes[a->_colorScheme]->speechColor + 9;
} else sp->penColor = 4 + 9;
} else {
sp->penColor = 4 + 9;
}
_inactiveList.push_back(sp);
return sp;
}
void SpeechTaskList::SetLock(int newState) {
if (newState && lockFlag == false) {
noStickyMap();
LockUI(true);
lockFlag = true;
} else if (lockFlag && newState == false) {
LockUI(false);
lockFlag = false;
}
}
//-----------------------------------------------------------------------
// When a speech task is finished, call this function to delete it.
void Speech::remove() {
speechList.remove(this);
}
//-----------------------------------------------------------------------
// AppFunc for handling clicks on speech
int16 pickSpeechButton(Point16 mouse, int16 size, gPort &textPort) {
Point16 p = mouse - initialSpeechPosition;
p.x -= kTileRectX;
p.y -= kTileRectY;
return pickButton(p,
speechLineList, speechLineCount,
speechButtonList, speechButtonCount,
size,
textPort);
}
APPFUNC(cmdClickSpeech) {
Speech *sp;
switch (ev.eventType) {
case gEventMouseMove:
case gEventMouseDrag:
g_vm->_mouseInfo->setDoable(Rect16(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight).ptInside(ev.mouse));
break;
case gEventMouseDown:
if ((sp = speechList.currentActive()) != nullptr) {
sp->selectedButton = pickSpeechButton(ev.mouse, sp->_speechImage.size.x, sp->_textPort);
}
break;
default:
break;
}
}
/* ===================================================================== *
SpeechTask management functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the speech task list
void initSpeechTasks() {
// Simply call the SpeechTaskList default constructor
new (&speechList) SpeechTaskList;
}
void saveSpeechTasks(Common::OutSaveFile *outS) {
debugC(2, kDebugSaveload, "Saving Speech Tasks");
outS->write("SPCH", 4);
CHUNK_BEGIN;
speechList.write(out);
CHUNK_END;
}
void loadSpeechTasks(Common::InSaveFile *in, int32 chunkSize) {
debugC(2, kDebugSaveload, "Loading Speech Tasks");
// If there is no saved data, simply call the default constructor
if (chunkSize == 0) {
new (&speechList) SpeechTaskList;
return;
}
// Reconstruct stackList from archived data
new (&speechList) SpeechTaskList(in);
}
//-----------------------------------------------------------------------
// Cleanup the speech task list
void cleanupSpeechTasks() {
// Call speechList's cleanup() function
speechList.cleanup();
}
} // end of namespace Saga2