scummvm/engines/saga2/speech.cpp

1338 lines
37 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
* aint32 with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL // FIXME: Remove
#include "saga2/std.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/savefile.h"
#include "saga2/cmisc.h"
#include "saga2/config.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 Point16 fineScroll;
extern Rect16 tileRect;
int kludgeHeight = 15;
extern TilePoint viewCenter; // coordinates of view on map
extern configuration globalConfig;
//-----------------------------------------------------------------------
// constants
const int maxWidth = 420;
const int defaultWidth = 380;
const int maxPhraseLength = 1000;
const int actorHeight = 80; // Assume 80
const int buttonColor = 1 + 9; // color for button text.
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);
//-----------------------------------------------------------------------
// locals
// pixelmap which holds the rendered text
gPixelMap speechImage;
gPort tempTextPort;
// 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 Point16 initialSpeechPosition; // inital coords of speech
// Image data for the little "bullet"
#if 0
static uint8 BulletData[] = {
0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, // Row 0
0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, // Row 1
0x00, 0x0F, 0x0F, 0x43, 0x41, 0x41, 0x0F, 0x0F, 0x00, // Row 2
0x0F, 0x0F, 0x45, 0x43, 0x41, 0x01, 0x41, 0x0F, 0x0F, // Row 3
0x0F, 0x0F, 0x47, 0x45, 0x43, 0x41, 0x41, 0x0F, 0x0F, // Row 4
0x0F, 0x0F, 0x45, 0x47, 0x47, 0x45, 0x45, 0x0F, 0x0F, // Row 5
0x00, 0x0F, 0x0F, 0x45, 0x47, 0x47, 0x0F, 0x0F, 0x00, // Row 6
0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, // Row 7
0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, // Row 8
};
#else
// Remapped by hand into new palette
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
};
#endif
static gStaticImage 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
* ===================================================================== */
//-----------------------------------------------------------------------
// Reconstruct this SpeechTask from an archive buffer
void *Speech::restore(void *buf) {
int16 i;
// Restore the sample count and character count
sampleCount = *((int16 *)buf);
charCount = *((int16 *)buf + 1);
buf = (int16 *)buf + 2;
// Restore the text boundaries
bounds = *((Rect16 *)buf);
buf = (Rect16 *)buf + 1;
// Restore the pen color and outline color
penColor = *((uint16 *)buf);
outlineColor = *((uint16 *)buf + 1);
buf = (uint16 *)buf + 2;
// Restore the object ID
objID = *((ObjectID *)buf);
buf = (ObjectID *)buf + 1;
// Restore the thread ID
thread = *((ThreadID *)buf);
buf = (ThreadID *)buf + 1;
// Restore the flags
speechFlags = *((int16 *)buf);
buf = (int16 *)buf + 1;
// Restore the sample ID's
for (i = 0; i < sampleCount; i++) {
sampleID[i] = *((uint32 *)buf);
buf = (uint32 *)buf + 1;
}
// Restore the text
memcpy(speechBuffer, buf, charCount);
buf = (char *)buf + charCount;
speechBuffer[charCount] = '\0';
// Requeue the speech if needed
if (speechFlags & spQueued) {
// Add to the active list
DNode::remove();
speechList.activeList.addTail(*this);
}
return buf;
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive this SpeechTask
int32 Speech::archiveSize(void) {
return sizeof(sampleCount)
+ sizeof(charCount)
+ sizeof(bounds)
+ sizeof(penColor)
+ sizeof(outlineColor)
+ sizeof(objID)
+ sizeof(thread)
+ sizeof(speechFlags)
+ sizeof(uint32) * sampleCount
+ sizeof(char) * charCount;
}
//-----------------------------------------------------------------------
// Archive this SpeechTask in a buffer
void *Speech::archive(void *buf) {
int16 i;
// Store the sample count and character count
*((int16 *)buf) = sampleCount;
*((int16 *)buf + 1) = charCount;
buf = (int16 *)buf + 2;
// Store the text boundaries
*((Rect16 *)buf) = bounds;
buf = (Rect16 *)buf + 1;
// Store the pen color and outline color
*((uint16 *)buf) = penColor;
*((uint16 *)buf + 1) = outlineColor;
buf = (uint16 *)buf + 2;
// Store the object's ID
*((ObjectID *)buf) = objID;
buf = (ObjectID *)buf + 1;
// Store the thread ID
*((ThreadID *)buf) = thread;
buf = (ThreadID *)buf + 1;
// Store the flags. NOTE: Make sure this speech is not stored
// as being active
*((int16 *)buf) = speechFlags & ~spActive;
buf = (int16 *)buf + 1;
for (i = 0; i < sampleCount; i++) {
*((uint32 *)buf) = sampleID[i];
buf = (uint32 *)buf + 1;
}
// Store the text
memcpy(buf, speechBuffer, charCount);
buf = (char *)buf + charCount;
return buf;
}
//-----------------------------------------------------------------------
// 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 >= 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(void) {
// Remove from existing list
DNode::remove();
// Add to the active list
speechList.activeList.addTail(*this);
speechFlags |= spQueued;
// This routine can't fail
return true;
}
//-----------------------------------------------------------------------
// Move speech to active list
bool Speech::setupActive(void) {
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
tempTextPort.setStyle(textStyleThickOutline); // extra Thick Outline
tempTextPort.setOutlineColor(outlineColor); // outline black
tempTextPort.setFont(&Amber13Font); // speech font
tempTextPort.setColor(penColor); // color of letters
tempTextPort.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,
!globalConfig.speechText && (speechFlags & spHasVoice));
// Compute height of bitmap based on number of lines of text.
// Include 4 for outline width
bounds.height =
(speechLineCount * (tempTextPort.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()]();
tempTextPort.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;
tempTextPort.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;
tempTextPort.setColor(1 + 9);
// Blit the little bullet symbol
lineChars--;
lineText++;
buttonChars--;
tempTextPort.bltPixels(
BulletImage, 0, 0,
tempTextPort.penPos.x, tempTextPort.penPos.y + 1,
BulletImage.size.x, BulletImage.size.y);
tempTextPort.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.
tempTextPort.drawText(lineText, dChars);
// Move forward by dChars
lineChars -= dChars;
buttonChars -= dChars;
lineText += dChars;
}
y += tempTextPort.font->height + lineLeading;
}
if (speechButtonCount > 0) {
// REM: Also set pointer to arrow shape.
mouseInfo.setIntent(GrabInfo::WalkTo);
// mouseInfo.setDoable( tileRect.ptInside( ev.mouse ) );
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,
!globalConfig.speechText && (speechFlags & spHasVoice));
// If it's more than 3 lines, then use the max line width.
if (speechLineCount > 3) {
speechLineCount = buttonWrap(speechLineList,
speechButtonList,
speechButtonCount,
speechBuffer,
maxWidth,
!globalConfig.speechText && (speechFlags & spHasVoice));
}
// 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(Point16 &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 /* tileRect.width - 40 */ - bounds.width);
p.y = clamp(tileRect.y + 8,
p.y - (bounds.height + actorHeight),
tileRect.height - 50 - bounds.height);
return true;
}
//-----------------------------------------------------------------------
// Draw the text on the back buffer
bool Speech::displayText(void) {
Point16 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
backPort.setMode(drawModeMatte);
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(void) {
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 = NULL;
// 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);
remove();
}
//-----------------------------------------------------------------------
// Render the speech object at the head of the speech queue.
void updateSpeech(void) {
Speech *sp;
// if there is a speech object
if ((sp = speechList.currentActive()) != NULL) {
// 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 (speechImage.data == NULL) {
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(void) {
if (speechFlags & spHasVoice)
return (!stillDoingVoice(sampleID[0]));
else
return (selectedButton != 0 || speechFinished.check());
}
// Gets rid of the current speech
void Speech::abortSpeech(void) {
// Start by displaying first frame straight off, no delay
speechFinished.set(0);
if (speechFlags & spHasVoice) {
PlayVoice(0);
}
}
void abortSpeech(void) {
if (speechList.currentActive()) speechList.currentActive()->abortSpeech();
}
#if 0
//Non Member Speech Functions / User Interface Calls
void queueActorSpeech(
GameObject *obj,
char *text,
int count,
int32 sampleID,
int flags
) {
Speech *sp;
// Check see if there's already speech associated with this object.
for (sp = (Speech *)speechList.nonActiveList.first();
sp;
sp = (Speech *)sp->next()) {
if (sp->obj == obj) {
if (!sp->addSpeech(text, sampleID, flags))
throw gError("Cant Append Speech");
return;
}
}
sp = speechList.newTask(obj, text, count, sampleID, flags);
}
// Causes all speeches to be skipped over
void abortAllSpeeches(void) {
// int16 i;
// activeSpeech.speechFinished.set( 0 );
// SetAlarm( &activeSpeech.speechFinished, 0 );
// if (activeSpeech.sampleID != -1)
// {
// StopVoices();
// activeSpeech.sampleID = -1;
// }
// if (abortEnabled)
// {
// skipSpeeches = true;
// wakeUpThreads( TWAIT_SPEECH );
// }
// for (i=0; i<10; i++) dispatchThreads();
}
void sentenceGenerator(char *sentence) {
int16 index;
index = rand() % elementsof(names);
strcat(sentence, names[index]);
index = rand() % elementsof(verbs);
strcat(sentence, verbs[index]);
index = rand() % elementsof(adjectives);
strcat(sentence, adjectives[index]);
index = rand() % elementsof(nouns);
strcat(sentence, nouns[index]);
// for(int i=0; i<elementsof(sentenceParts); i++)
// {
// strcat(sentence,sentenceParts[i].index = rand() % elementsof(sentenceParts[i]);
// }
}
#endif
//-----------------------------------------------------------------------
// Delete all speeches relating to a particular actor
void deleteSpeech(ObjectID id) { // voice sound sample ID
Speech *sp;
while ((sp = speechList.findSpeech(id)) != NULL) sp->dispose();
}
int16 TextWrap(
char *lines[], // array of line pointers
int16 line_chars[], // character count of each line
int16 line_pixels[], // pixel count of each line
char *text, // the text to render
int16 width // width to constrain text
)
{
int16 i, // loop counter
line_start, // start of current line
last_space, // last space encountered
last_space_pixels, // pixel pos of last space
pixel_len, // pixel length of line
line_count = 0; // number of lines
lines[line_count] = text;
last_space = -1;
line_start = 0;
pixel_len = 0;
// 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 (c == '\n' || c == '\r' || c == '\0') { // if deliberate end of line
line_chars[line_count] = i - line_start; //
line_pixels[line_count] = pixel_len;
line_start = i + 1;
if (c == '\0') {
line_count++;
break;
}
lines[++line_count] = &text[line_start];
last_space = -1;
pixel_len = 0;
continue;
} else if (c == ' ') {
last_space = i;
last_space_pixels = pixel_len;
}
pixel_len +=
tempTextPort.font->charKern[c] + tempTextPort.font->charSpace[c];
if (pixel_len > width - 2 && last_space > 0) {
line_chars[line_count] = last_space - line_start;
line_pixels[line_count] = last_space_pixels;
line_start = last_space + 1;
lines[++line_count] = &text[line_start];
last_space = -1;
pixel_len = 0;
i = line_start - 1;
}
}
return line_count;
}
//-----------------------------------------------------------------------
// 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) {
int16 i, // loop counter
line_start, // start of current line
last_space, // last space encountered
last_space_pixels, // 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
= tempTextPort.font->charKern[c]
+ tempTextPort.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
= tempTextPort.font->charKern[c]
+ tempTextPort.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) { // 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 / (tempTextPort.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 = tileRect.height;
int16 viewSizeX = tileRect.width;
//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
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the SpeechTaskList
SpeechTaskList::SpeechTaskList(void) {
lockFlag = false;
for (int i = 0; i < elementsof(array); i++) {
free.addTail(array[i]);
}
}
//-----------------------------------------------------------------------
// Constructor -- reconstruct from archive buffer
SpeechTaskList::SpeechTaskList(void **buf) {
void *bufferPtr = *buf;
int16 i,
count;
lockFlag = false;
// Initialize the free list
for (i = 0; i < elementsof(array); i++) {
free.addTail(array[i]);
}
// Get the speech count
count = *((int16 *)bufferPtr);
bufferPtr = (int16 *)bufferPtr + 1;
// Restore the speeches
for (i = 0; i < count; i++) {
Speech *sp = (Speech *)free.remHead();
assert(sp != NULL);
nonActiveList.addTail(*sp);
bufferPtr = sp->restore(bufferPtr);
}
*buf = bufferPtr;
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive the speech tasks
int32 SpeechTaskList::archiveSize(void) {
int32 size = 0;
Speech *sp;
size += sizeof(int16); // Speech count
// Tally active speeches
for (sp = (Speech *)activeList.first();
sp != NULL;
sp = (Speech *)sp->next())
size += sp->archiveSize();
// Tally inactive speeches
for (sp = (Speech *)nonActiveList.first();
sp != NULL;
sp = (Speech *)sp->next())
size += sp->archiveSize();
return size;
}
//-----------------------------------------------------------------------
// Create an archive of the speech tasks in an archive buffer
void *SpeechTaskList::archive(void *buf) {
int16 count = 0;
Speech *sp;
// Tally active speeches
for (sp = (Speech *)activeList.first();
sp != NULL;
sp = (Speech *)sp->next())
count++;
// Tally inactive speeches
for (sp = (Speech *)nonActiveList.first();
sp != NULL;
sp = (Speech *)sp->next())
count++;
// Store speech count
*((int16 *)buf) = count;
buf = (int16 *)buf + 1;
// Store active speeches
for (sp = (Speech *)activeList.first();
sp != NULL;
sp = (Speech *)sp->next())
buf = sp->archive(buf);
// Store inactive speeches
for (sp = (Speech *)nonActiveList.first();
sp != NULL;
sp = (Speech *)sp->next())
buf = sp->archive(buf);
return buf;
}
//-----------------------------------------------------------------------
// Cleanup the speech tasks
void SpeechTaskList::cleanup(void) {
Speech *sp;
Speech *nextSP;
// Remove active speeches
for (sp = (Speech *)activeList.first();
sp != NULL;
sp = nextSP) {
nextSP = (Speech *)sp->next();
sp->remove();
}
// Remove inactive speeches
for (sp = (Speech *)nonActiveList.first();
sp != NULL;
sp = nextSP) {
nextSP = (Speech *)sp->next();
sp->remove();
}
}
//-----------------------------------------------------------------------
// Search for a speech task associated with a particular GameObject.
Speech *SpeechTaskList::findSpeech(ObjectID id) {
Speech *sp;
for (sp = (Speech *)speechList.nonActiveList.first();
sp;
sp = (Speech *)sp->next()) {
if (sp->objID == id) return sp;
}
return NULL;
}
//-----------------------------------------------------------------------
// 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 NULL;
sp = (Speech *)free.remHead();
#if DEBUG
if (sp == NULL) fatal("Ran out of Speech Tasks, Object = %s\n", obj->objName());
#endif
if (sp == NULL) return NULL;
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;
}
nonActiveList.addTail(*sp);
return sp;
}
void SpeechTaskList::SetLock(int newState) {
if (newState && lockFlag == false) {
extern void noStickyMap(void);
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(void) {
DNode::remove();
speechList.free.addTail(*this);
}
//-----------------------------------------------------------------------
// AppFunc for handling clicks on speech
int16 pickSpeechButton(Point16 mouse) {
Point16 p = mouse - initialSpeechPosition;
p.x -= tileRect.x;
p.y -= tileRect.y;
return pickButton(p,
speechLineList, speechLineCount,
speechButtonList, speechButtonCount,
speechImage.size.x);
}
APPFUNC(cmdClickSpeech) {
Speech *sp;
switch (ev.eventType) {
case gEventMouseMove:
case gEventMouseDrag:
mouseInfo.setDoable(tileRect.ptInside(ev.mouse));
break;
case gEventMouseDown:
if ((sp = speechList.currentActive()) != NULL) {
sp->selectedButton = pickSpeechButton(ev.mouse);
#if 0
if (sp->selectedButton != 0)
sp->dispose();
#endif
}
break;
}
}
/* ===================================================================== *
SpeechTask management functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the speech task list
void initSpeechTasks(void) {
// Simply call the SpeechTaskList default constructor
new (&speechList) SpeechTaskList;
}
//-----------------------------------------------------------------------
// Save the speech tasks in a save file
void saveSpeechTasks(SaveFileConstructor &saveGame) {
int32 archiveBufSize;
void *archiveBuffer;
archiveBufSize = speechList.archiveSize();
archiveBuffer = malloc(archiveBufSize);
if (archiveBuffer == NULL)
error("Unable to allocate speech task archive buffer");
speechList.archive(archiveBuffer);
saveGame.writeChunk(
MakeID('S', 'P', 'C', 'H'),
archiveBuffer,
archiveBufSize);
free(archiveBuffer);
}
//-----------------------------------------------------------------------
// Load the speech tasks from a save file
void loadSpeechTasks(SaveFileReader &saveGame) {
// If there is no saved data, simply call the default constructor
if (saveGame.getChunkSize() == 0) {
new (&speechList) SpeechTaskList;
return;
}
void *archiveBuffer;
void *bufferPtr;
archiveBuffer = malloc(saveGame.getChunkSize());
if (archiveBuffer == NULL)
error("Unable to allocate speech task archive buffer");
// Read the archived task stack data
saveGame.read(archiveBuffer, saveGame.getChunkSize());
bufferPtr = archiveBuffer;
// Reconstruct stackList from archived data
new (&speechList) SpeechTaskList(&bufferPtr);
free(archiveBuffer);
}
//-----------------------------------------------------------------------
// Cleanup the speech task list
void cleanupSpeechTasks(void) {
// Call speechList's cleanup() function
speechList.cleanup();
}
} // end of namespace Saga2