1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * aint32 with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 *
22 * Based on the original sources
23 * Faery Tale II -- The Halls of the Dead
24 * (c) 1993-1996 The Wyrmkeep Entertainment Co.
25 */
26
27 #include "saga2/saga2.h"
28 #include "saga2/fta.h"
29 #include "saga2/fontlib.h"
30 #include "saga2/speech.h"
31 #include "saga2/motion.h"
32 #include "saga2/panel.h"
33 #include "saga2/grabinfo.h"
34 #include "saga2/player.h"
35 #include "saga2/annoy.h"
36 #include "saga2/cmisc.h"
37 #include "saga2/tilemode.h"
38
39 namespace Saga2 {
40
41 struct TextSpan {
42 char *text; // pointer to 1st char of span
43 int16 charWidth, // number of characters in span
44 pixelWidth; // number of pixels in span
45 };
46
47 //-----------------------------------------------------------------------
48 // externs
49
50 extern StaticPoint16 fineScroll;
51 int kludgeHeight = 15;
52 extern StaticTilePoint viewCenter; // coordinates of view on map
53
54 //-----------------------------------------------------------------------
55 // constants
56
57 const int maxWidth = 420;
58 const int defaultWidth = 380;
59 const int actorHeight = 80; // Assume 80
60
61 const int lineLeading = 2; // space between lines
62 const int outlineWidth = 2; // width of character outline
63 const int bulletWidth = 13; // width of bullet symbol
64
65 //-----------------------------------------------------------------------
66 // prototypes
67
68 int16 buttonWrap(
69 TextSpan *lineList, // indicates where line breaks are
70 TextSpan *buttonList, // indicates where button breaks are
71 int16 &buttonCount, // returns number of buttons
72 char *text, // text to wrap
73 int16 width, // width of text
74 int16 supressText,
75 gPort &textPort);
76
77 //-----------------------------------------------------------------------
78 // locals
79
80 // Temporary: Alarm which determines when speech finishes
81 Alarm speechFinished;
82
83
84 // The list of active and non-active speech tasks for all actors
85 uint8 speechListBuffer[sizeof(SpeechTaskList)];
86 SpeechTaskList &speechList = *((SpeechTaskList *)speechListBuffer);
87
88 static TextSpan speechLineList[64], // list of speech lines
89 speechButtonList[64]; // list of speech buttons
90 int16 speechLineCount, // count of speech lines
91 speechButtonCount; // count of speech buttons
92
93 static StaticPoint16 initialSpeechPosition = {0, 0}; // inital coords of speech
94
95 // Image data for the little "bullet"
96 static uint8 BulletData[] = {
97 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // Row 0
98 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // Row 1
99 0x00, 0x18, 0x18, 0x4C, 0x4A, 0x4A, 0x18, 0x18, 0x00, // Row 2
100 0x18, 0x18, 0x4E, 0x4C, 0x4A, 0x0A, 0x4A, 0x18, 0x18, // Row 3
101 0x18, 0x18, 0x50, 0x4E, 0x4C, 0x4A, 0x4A, 0x18, 0x18, // Row 4
102 0x18, 0x18, 0x4E, 0x50, 0x50, 0x4E, 0x4E, 0x18, 0x18, // Row 5
103 0x00, 0x18, 0x18, 0x4E, 0x50, 0x50, 0x18, 0x18, 0x00, // Row 6
104 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // Row 7
105 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // Row 8
106 };
107
108 static StaticPixelMap BulletImage = {{9, 9}, BulletData};
109
110 //-----------------------------------------------------------------------
111 // Speech button mode override.
112
113 extern gPanelList *speakButtonControls; // controls for embedded speech button
114
115 //-----------------------------------------------------------------------
116 // Audio resource ID generator
117
118 static char convBuf[5];
119
extendID(int16 smallID)120 inline uint32 extendID(int16 smallID) {
121 sprintf(convBuf, "%4.4d", smallID);
122 return smallID ? MKTAG(convBuf[0] + 'A' - '0', convBuf[1], convBuf[2], convBuf[3]) : 0 ;
123 }
124
125 /* ===================================================================== *
126 Speech member functions
127 * ===================================================================== */
128
read(Common::InSaveFile * in)129 void Speech::read(Common::InSaveFile *in) {
130 // Restore the sample count and character count
131 sampleCount = in->readSint16LE();
132 charCount = in->readSint16LE();
133
134 // Restore the text boundaries
135 bounds.read(in);
136
137 // Restore the pen color and outline color
138 penColor = in->readUint16LE();
139 outlineColor = in->readUint16LE();
140
141 // Restore the object ID
142 objID = in->readUint16LE();
143
144 // Restore the thread ID
145 thread = in->readSint16LE();
146
147 // Restore the flags
148 speechFlags = in->readSint16LE();
149
150 debugC(4, kDebugSaveload, "...... sampleCount = %d", sampleCount);
151 debugC(4, kDebugSaveload, "...... charCount = %d", charCount);
152 debugC(4, kDebugSaveload, "...... penColor = %d", penColor);
153 debugC(4, kDebugSaveload, "...... outlineColor = %d", outlineColor);
154 debugC(4, kDebugSaveload, "...... bounds = (%d, %d, %d, %d)",
155 bounds.x, bounds.y, bounds.width, bounds.height);
156 debugC(4, kDebugSaveload, "...... objID = %d", objID);
157 debugC(4, kDebugSaveload, "...... thread = %d", thread);
158 debugC(4, kDebugSaveload, "...... speechFlags = %d", speechFlags);
159
160 // Restore the sample ID's
161 for (int i = 0; i < sampleCount; i++) {
162 sampleID[i] = in->readUint32BE();
163 debugC(4, kDebugSaveload, "...... sampleID[%d] = %d", i, sampleID[i]);
164 }
165
166 // Restore the text
167 in->read(speechBuffer, charCount);
168 speechBuffer[charCount] = '\0';
169 debugC(4, kDebugSaveload, "...... speechBuffer = %s", speechBuffer);
170
171 // Requeue the speech if needed
172 if (speechFlags & spQueued) {
173 // Add to the active list
174 speechList.remove(this);
175 speechList._list.push_back(this);
176 }
177 }
178
179 //-----------------------------------------------------------------------
180 // Return the number of bytes needed to archive this SpeechTask
181
archiveSize(void)182 int32 Speech::archiveSize(void) {
183 return sizeof(sampleCount)
184 + sizeof(charCount)
185 + sizeof(bounds)
186 + sizeof(penColor)
187 + sizeof(outlineColor)
188 + sizeof(objID)
189 + sizeof(thread)
190 + sizeof(speechFlags)
191 + sizeof(uint32) * sampleCount
192 + sizeof(char) * charCount;
193 }
194
write(Common::MemoryWriteStreamDynamic * out)195 void Speech::write(Common::MemoryWriteStreamDynamic *out) {
196 // Store the sample count and character count
197 out->writeSint16LE(sampleCount);
198 out->writeSint16LE(charCount);
199
200 // Store the text boundaries
201 bounds.write(out);
202
203 // Store the pen color and outline color
204 out->writeUint16LE(penColor);
205 out->writeUint16LE(outlineColor);
206
207 // Store the object's ID
208 out->writeUint16LE(objID);
209
210 // Store the thread ID
211 out->writeSint16LE(thread);
212
213 // Store the flags. NOTE: Make sure this speech is not stored
214 // as being active
215 out->writeSint16LE(speechFlags & ~spActive);
216
217 debugC(4, kDebugSaveload, "...... sampleCount = %d", sampleCount);
218 debugC(4, kDebugSaveload, "...... charCount = %d", charCount);
219 debugC(4, kDebugSaveload, "...... penColor = %d", penColor);
220 debugC(4, kDebugSaveload, "...... outlineColor = %d", outlineColor);
221 debugC(4, kDebugSaveload, "...... bounds = (%d, %d, %d, %d)",
222 bounds.x, bounds.y, bounds.width, bounds.height);
223 debugC(4, kDebugSaveload, "...... objID = %d", objID);
224 debugC(4, kDebugSaveload, "...... thread = %d", thread);
225 debugC(4, kDebugSaveload, "...... speechFlags = %d", speechFlags);
226
227 for (int i = 0; i < sampleCount; i++) {
228 out->writeUint32BE(sampleID[i]);
229 debugC(4, kDebugSaveload, "...... sampleID[%d] = %d", i, sampleID[i]);
230 }
231
232 // Store the text
233 out->write(speechBuffer, charCount);
234 debugC(4, kDebugSaveload, "...... speechBuffer = %s", speechBuffer);
235 }
236
237 //-----------------------------------------------------------------------
238 // Append text and sample to existing speech record
239
append(char * text,int32 sampID)240 bool Speech::append(char *text, int32 sampID) {
241 int16 len = strlen(text);
242
243 // Check to see if there's enough room in the character buffer
244 if (charCount + len >= (long)sizeof(speechBuffer)
245 || sampleCount >= MAX_SAMPLES) return false;
246
247 // Copy text to end of text in buffer, including '\0'
248 memcpy(&speechBuffer[charCount], text, len + 1);
249 charCount += len;
250
251 // Append sample ID to list of samples.
252 // REM: We should translate sample ID's from index to resource
253 // number here.
254 if (sampID)
255 sampleID[sampleCount++] = extendID(sampID);
256
257 return true;
258 }
259
260 //-----------------------------------------------------------------------
261 // Move speech to active list
262
activate(void)263 bool Speech::activate(void) {
264
265 // Remove from existing list
266 speechList.remove(this);
267
268 // Add to the active list
269 speechList._list.push_back(this);
270
271 speechFlags |= spQueued;
272
273 // This routine can't fail
274 return true;
275 }
276
277 //-----------------------------------------------------------------------
278 // Move speech to active list
279
setupActive(void)280 bool Speech::setupActive(void) {
281 int16 x, y;
282 int16 buttonNum = 0,
283 buttonChars;
284
285 speechFlags |= spActive;
286
287 speechFinished.set((charCount * 4 / 2) + ticksPerSecond);
288
289 //Turn Actor Towards Person They Are Talking To
290 // MotionTask::turnObject( *obj, GameObject::objectAddress(32794)->getLocation());
291 // Actor *a = (Actor *)obj;
292 // if(!a->setAction( actionJumpUp, animateRandom ))
293 // throw gError( "Could Not Set Talk Animation");
294
295 // Set up temp gport for blitting to bitmap
296 _textPort.setStyle(textStyleThickOutline); // extra Thick Outline
297 _textPort.setOutlineColor(outlineColor); // outline black
298 _textPort.setFont(&Amber13Font); // speech font
299 _textPort.setColor(penColor); // color of letters
300 _textPort.setMode(drawModeMatte); // insure transparency
301
302 setWidth();
303
304 // If speech position is off-screen, then skip
305 if (!calcPosition(initialSpeechPosition)) return false;
306
307 if (sampleCount) {
308 GameObject *go = GameObject::objectAddress(objID);
309 Location loc = go->notGetWorldLocation();
310 sampleID[sampleCount] = 0;
311
312 //sayVoice(sampleID);
313
314 /// EO SEARCH ///
315 if (sayVoiceAt(sampleID, loc))
316 speechFlags |= spHasVoice;
317 else
318 speechFlags &= ~spHasVoice;
319
320 } else speechFlags &= ~spHasVoice;
321
322 speechLineCount = buttonWrap(speechLineList,
323 speechButtonList,
324 speechButtonCount,
325 speechBuffer,
326 bounds.width,
327 !g_vm->_speechText && (speechFlags & spHasVoice),
328 _textPort);
329
330 // Compute height of bitmap based on number of lines of text.
331 // Include 4 for outline width
332 bounds.height =
333 (speechLineCount * (_textPort.font->height + lineLeading))
334 + outlineWidth * 2;
335
336 // Blit to temp bitmap
337 _speechImage.size.x = bounds.width;
338 _speechImage.size.y = bounds.height;
339 _speechImage.data = new uint8[_speechImage.bytes()]();
340 _textPort.setMap(&_speechImage);
341
342 y = outlineWidth; // Plus 2 for Outlines
343 buttonChars = speechButtonList[buttonNum].charWidth;
344
345 for (int i = 0; i < speechLineCount; i++) {
346 int16 lineChars = speechLineList[i].charWidth;
347 char *lineText = speechLineList[i].text;
348
349 x = (bounds.width - speechLineList[i].pixelWidth) / 2
350 + outlineWidth;
351
352 _textPort.moveTo(x, y);
353
354 // Draw each button on the line in turn.
355 while (lineChars > 0) {
356 int16 dChars;
357
358 // If this is the end of a button
359 if (buttonChars <= 0) {
360 // Incr to next button
361 buttonNum++;
362
363 // If no more buttons, then go nae ferther...
364 // Fer death awaits with nasty, pointy teeth...!
365 if (buttonNum > speechButtonCount) break;
366
367 buttonChars = speechButtonList[buttonNum].charWidth;
368 _textPort.setColor(1 + 9);
369
370 // Blit the little bullet symbol
371 lineChars--;
372 lineText++;
373 buttonChars--;
374
375 _textPort.bltPixels(
376 BulletImage, 0, 0,
377 _textPort.penPos.x, _textPort.penPos.y + 1,
378 BulletImage.size.x, BulletImage.size.y);
379
380 _textPort.move(bulletWidth, 0);
381 }
382
383 // Compute how much of this button is on this line.
384 dChars = MIN(lineChars, buttonChars);
385
386 // Draw however much of this button is on this line.
387 _textPort.drawText(lineText, dChars);
388
389 // Move forward by dChars
390 lineChars -= dChars;
391 buttonChars -= dChars;
392 lineText += dChars;
393 }
394
395 y += _textPort.font->height + lineLeading;
396 }
397
398 if (speechButtonCount > 0) {
399 // REM: Also set pointer to arrow shape.
400 g_vm->_mouseInfo->setIntent(GrabInfo::WalkTo);
401 speakButtonControls->enable(true);
402
403 speechList.SetLock(false);
404 } else {
405 // If there is a lock flag on this speech, then LockUI()
406 speechList.SetLock(speechFlags & spLock);
407 }
408
409 if (!(speechFlags & spNoAnimate) && isActor(objID)) {
410 Actor *a = (Actor *)GameObject::objectAddress(objID);
411
412 if (!a->isDead() && !a->isMoving()) MotionTask::talk(*a);
413 }
414
415 // speechFinished.set( ticksPerSecond*2 );
416 return (true);
417 }
418
419 //This Function Sets Up Width And Height For A Speech
420
setWidth()421 void Speech::setWidth() {
422 TextSpan speechLineList_[32], // list of speech lines
423 speechButtonList_[32]; // list of speech buttons
424 int16 speechLineCount_, // count of speech lines
425 speechButtonCount_; // count of speech buttons
426
427 // How many word-wrapped lines does the speech take up if we word-wrap
428 // it to the default line width?
429
430 speechLineCount_ = buttonWrap(speechLineList_,
431 speechButtonList_,
432 speechButtonCount_,
433 speechBuffer,
434 defaultWidth,
435 !g_vm->_speechText && (speechFlags & spHasVoice),
436 _textPort);
437
438 // If it's more than 3 lines, then use the max line width.
439
440 if (speechLineCount_ > 3) {
441 speechLineCount_ = buttonWrap(speechLineList_,
442 speechButtonList_,
443 speechButtonCount_,
444 speechBuffer,
445 maxWidth,
446 !g_vm->_speechText && (speechFlags & spHasVoice),
447 _textPort);
448 }
449
450
451 // The actual width of the bounds is the widest of the lines.
452
453 bounds.width = 0;
454 for (int i = 0; i < speechLineCount_; i++) {
455 bounds.width = MAX(bounds.width, speechLineList_[i].pixelWidth);
456 }
457 bounds.width += outlineWidth * 2 + 4; // Some padding just in case.
458 }
459
460 //-----------------------------------------------------------------------
461 // Calculate the position of the speech, emanating from the actor.
462
calcPosition(StaticPoint16 & p)463 bool Speech::calcPosition(StaticPoint16 &p) {
464 GameObject *obj = GameObject::objectAddress(objID);
465 TilePoint tp = obj->getWorldLocation();
466
467 if (!isVisible(obj)) return false;
468
469 TileToScreenCoords(tp, p);
470
471 p.x = clamp(8,
472 p.x - bounds.width / 2,
473 8 + maxWidth - bounds.width);
474
475 p.y = clamp(kTileRectY + 8,
476 p.y - (bounds.height + actorHeight),
477 kTileRectHeight - 50 - bounds.height);
478
479 return true;
480 }
481
482 //-----------------------------------------------------------------------
483 // Draw the text on the back buffer
484
displayText(void)485 bool Speech::displayText(void) {
486 StaticPoint16 p;
487
488 // If there are button in the speech, then don't scroll the
489 // speech along with the display. Otherwise, calculate the
490 // position from the actor.
491 if (speechButtonCount > 0)
492 p = initialSpeechPosition;
493 else if (!calcPosition(p))
494 return false;
495
496 // Blit to the port
497 g_vm->_backPort.setMode(drawModeMatte);
498 g_vm->_backPort.bltPixels(_speechImage,
499 0, 0,
500 p.x + fineScroll.x,
501 p.y + fineScroll.y,
502 bounds.width, bounds.height);
503
504 return true;
505 }
506
507 //-----------------------------------------------------------------------
508 // Dispose of this speech object. If this is the one being displayed,
509 // then dealloc the speech image
510
dispose(void)511 void Speech::dispose(void) {
512 if (speechList.currentActive() == this) {
513 // Actor *a = (Actor *)sp->obj;
514 // a->animationFlags |= animateFinished;
515 // a->setAction( actionStand, animateRandom );
516
517 if (!longEnough())
518 playVoice(0);
519 // Wake up the thread, and return the # of the button
520 // that was selected
521 wakeUpThread(thread, selectedButton);
522
523 // De-allocate the speech data
524 delete[] _speechImage.data;
525 _speechImage.data = NULL;
526
527 // Clear the number of active buttons
528 speechLineCount = speechButtonCount = 0;
529 speakButtonControls->enable(false);
530
531 if (!(speechFlags & spNoAnimate) && isActor(objID)) {
532 Actor *a = (Actor *)GameObject::objectAddress(objID);
533
534 if (a->_moveTask)
535 a->_moveTask->finishTalking();
536 }
537 } else wakeUpThread(thread, 0);
538
539 GameObject *obj = GameObject::objectAddress(objID);
540
541 debugC(1, kDebugTasks, "Speech: Disposing %p for %p (%s) (total = %d)'", (void *)this, (void *)obj, obj->objName(), speechList.speechCount());
542
543 remove();
544 }
545
546 //-----------------------------------------------------------------------
547 // Render the speech object at the head of the speech queue.
548
updateSpeech(void)549 void updateSpeech(void) {
550 Speech *sp;
551
552 // if there is a speech object
553 if ((sp = speechList.currentActive()) != NULL) {
554 // If there is no bitmap, then set one up.
555 if (!(sp->speechFlags & Speech::spActive)) {
556 sp->setupActive();
557
558 // If speech failed to set up, then skip it
559 if (sp->_speechImage.data == NULL) {
560 sp->dispose();
561 return;
562 }
563 }
564
565 // Draw the speech bitmap
566 sp->displayText();
567
568 // If this speech has timed-out, then dispose of it.
569
570
571 if (sp->longEnough() &&
572 (speechButtonCount == 0 || sp->selectedButton != 0))
573 sp->dispose();
574 } else speechList.SetLock(false);
575 }
576
longEnough(void)577 bool Speech::longEnough(void) {
578 if (speechFlags & spHasVoice)
579 return (!stillDoingVoice(sampleID));
580 else
581 return (selectedButton != 0 || speechFinished.check());
582 }
583
584 // Gets rid of the current speech
585
abortSpeech(void)586 void Speech::abortSpeech(void) {
587 // Start by displaying first frame straight off, no delay
588 speechFinished.set(0);
589 if (speechFlags & spHasVoice) {
590 PlayVoice(0);
591 }
592 }
593
abortSpeech(void)594 void abortSpeech(void) {
595 if (speechList.currentActive()) speechList.currentActive()->abortSpeech();
596 }
597
598 //-----------------------------------------------------------------------
599 // Delete all speeches relating to a particular actor
600
deleteSpeech(ObjectID id)601 void deleteSpeech(ObjectID id) { // voice sound sample ID
602 Speech *sp;
603
604 while ((sp = speechList.findSpeech(id)) != NULL) sp->dispose();
605 }
606
607 //-----------------------------------------------------------------------
608 // This routine does a word-wrap on the input text, and also checks for
609 // the '@' symbol to see if there are any embedded buttons in the text.
610
buttonWrap(TextSpan * lineList,TextSpan * buttonList,int16 & buttonCount,char * text,int16 width,int16 supressText,gPort & textPort)611 int16 buttonWrap(
612 TextSpan *lineList, // indicates where line breaks are
613 TextSpan *buttonList, // indicates where button breaks are
614 int16 &buttonCount, // returns number of buttons
615 char *text, // text to wrap
616 int16 width, // width of text
617 int16 supressText,
618 gPort &textPort) {
619 int16 i, // loop counter
620 line_start, // start of current line
621 last_space, // last space encountered
622 last_space_pixels = 0, // pixel pos of last space
623 charPixels, // pixel length of character
624 linePixels, // pixels in current line
625 buttonPixels, // pixels in current button
626 buttonChars, // char count of current button
627 lineCount = 0; // number of lines
628
629 // If we are not showing the text of the speech, skip over all text
630 // until we come to the first button definition.
631 if (supressText) {
632 while (*text && *text != '@') text++;
633 }
634
635 lineList->text = text; // set ptr to 1st line
636
637 last_space = -1; // no spaces to word-wrap yet
638 line_start = 0; // start index of 1st line
639 linePixels = 0; // no pixels counted yet
640
641 width -= outlineWidth * 2; // compensate for size of outline
642
643 // For each character in the string, check for word wrap
644
645 for (i = 0; ; i++) {
646 uint8 c = text[i];
647
648 // REM: Translate from foreign character set if needed...
649 // c = TranslationTable[c];
650
651 // If deliberate end of line
652 if (c == '\n' || c == '\r' || c == '\0') {
653 lineList->charWidth = i - line_start;
654 lineList->pixelWidth = linePixels;
655 lineList++;
656 lineCount++;
657
658 line_start = i + 1;
659
660 if (c == '\0') break;
661
662 lineList->text = &text[line_start];
663
664 last_space = -1;
665 linePixels = 0;
666 continue;
667 } else if (c == '@') { // button indicator...
668 // Set width of 'bullet' symbol.
669 charPixels = bulletWidth;
670
671 } else { // Any other character
672 // if it's a space, save the word wrap position.
673 if (c == ' ') {
674 last_space = i;
675 last_space_pixels = linePixels;
676 }
677
678 // Add to pixel length
679 charPixels
680 = textPort.font->charKern[c]
681 + textPort.font->charSpace[c];
682 }
683
684 linePixels += charPixels;
685
686 // If pixel runs off end of line
687 if (linePixels > width && last_space > 0) {
688 lineList->charWidth = last_space - line_start;
689 lineList->pixelWidth = last_space_pixels;
690 lineList++;
691 lineCount++;
692
693 line_start = last_space + 1;
694
695 lineList->text = &text[line_start];
696
697 last_space = -1;
698 linePixels = 0;
699
700 i = line_start - 1;
701 }
702 }
703
704 buttonCount = 0; // assume zero buttons
705 buttonPixels = 0; // no pixels counted yet
706 buttonChars = 0; // no chars counted yet
707 buttonList->text = text; // set ptr to 1st button
708 lineList -= lineCount; // reset line list
709
710 // For each line, look for button markers
711
712 for (int l = 0; l < lineCount; l++, lineList++) {
713 for (i = 0; i < lineList->charWidth; i++) {
714 uint8 c = lineList->text[i];
715
716 // REM: Translate from foreign character set if needed...
717 // c = TranslationTable[c];
718
719 if (c == '@') { // button indicator...
720 // A new button
721 buttonList->charWidth = buttonChars;
722 buttonList->pixelWidth = buttonPixels;
723
724 buttonPixels = 0;
725 buttonChars = 0;
726 buttonList++;
727 buttonCount++;
728 buttonList->text = text; // set ptr to 1st button
729
730 // Set width of 'bullet' symbol.
731 charPixels = bulletWidth;
732
733 } else { // Any other character
734 // Add to pixel length
735 charPixels
736 = textPort.font->charKern[c]
737 + textPort.font->charSpace[c];
738 }
739
740 buttonPixels += charPixels;
741 buttonChars++;
742 }
743 }
744
745 // Clean up the final button
746 buttonList->charWidth = buttonChars;
747 buttonList->pixelWidth = buttonPixels;
748
749 return lineCount;
750 }
751
752 //-----------------------------------------------------------------------
753 // Given the original word-wrap info, determines which button (if any)
754 // was clicked.
755
pickButton(Point16 & pt,TextSpan * lineList,int16 numLines,TextSpan * buttonList,int16 buttonCount,int16 width,gPort textPort)756 int16 pickButton(
757 Point16 &pt,
758 TextSpan *lineList, // indicates where line breaks are
759 int16 numLines, // number of line breaks
760 TextSpan *buttonList, // indicates where button breaks are
761 int16 buttonCount, // number of buttons
762 int16 width,
763 gPort textPort) { // width of rectangle
764 int16 pickLine,
765 pickPixels = 0,
766 centerWidth;
767
768 if (pt.y < 0 // picked off top edge
769 || pt.x < 0 // picked off left edge
770 || buttonCount < 1) // no buttons defined
771 return 0;
772
773 pickLine = pt.y / (textPort.font->height + lineLeading);
774 if (pickLine >= numLines) return 0;
775
776 // Strange algorithm:
777 //
778 // When we first built these data structures, we took a continuous
779 // string of text and broke it up into several lines, and also
780 // broke it up into several buttons. Each of these has a count
781 // of how many pixels wide it was.
782 //
783 // Now, consider if we were to lay each of those lines end
784 // to end as though we were reconstructing the original non-
785 // wrapped text string. And suppose the pick-point were carried
786 // along with the line, so that now our 2-d pixel point is
787 // now a 1-d pixel offset into the line.
788 //
789 // We can do this by adding the length of each previous line
790 // to the pixel offset, and subtracting the margin space used
791 // for centering.
792
793 for (int i = 0; i < pickLine; i++) {
794 pickPixels += lineList[i].pixelWidth;
795 }
796
797 centerWidth = (width - lineList[pickLine].pixelWidth) / 2;
798
799 // Return 0 if mouse off left or right edge of text.
800 if (pt.x < centerWidth || pt.x > width - centerWidth) return 0;
801
802 pickPixels += pt.x - (width - lineList[pickLine].pixelWidth) / 2;
803
804 // Now, we lay all the buttons end to end in a similar fashion,
805 // and determine which button the pick point fell into, in a
806 // simple 1-d comparison.
807
808 for (int j = 0; j <= buttonCount; j++) {
809 pickPixels -= buttonList[j].pixelWidth;
810 if (pickPixels < 0) return j;
811 }
812
813 return 0;
814 }
815
isVisible(GameObject * obj)816 bool isVisible(GameObject *obj) {
817
818 TilePoint tp = obj->getWorldLocation();
819 Point16 p, vp;
820 TileToScreenCoords(tp, p);
821
822 //For Determining If Object Is Being Displayed
823 //Could We Just Check Display List ???
824 int16 distanceX, distanceY;
825 int16 viewSizeY = kTileRectHeight;
826 int16 viewSizeX = kTileRectWidth;
827
828 //I Figure This Differently Than In Dispnode
829 int16 loadDistX = viewSizeX / 2;
830 int16 loadDistY = viewSizeY / 2;
831
832 TileToScreenCoords(viewCenter, vp);
833
834 distanceX = ABS(vp.x - p.x);
835 distanceY = ABS(vp.y - p.y);
836
837 if ((distanceY >= loadDistY) ||
838 (distanceX >= loadDistX))
839 return (false);
840
841 return (true);
842 }
843
844 /* ===================================================================== *
845 SpeechTaskList member functions
846 * ===================================================================== */
847
remove(Speech * p)848 void SpeechTaskList::remove(Speech *p) {
849 for (Common::List<Speech *>::iterator it = _list.begin();
850 it != _list.end(); ++it) {
851 if (p == *it) {
852 _list.remove(p);
853 break;
854 }
855 }
856
857 for (Common::List<Speech *>::iterator it = _inactiveList.begin();
858 it != _inactiveList.end(); ++it) {
859 if (p == *it) {
860 _inactiveList.remove(p);
861 break;
862 }
863 }
864 }
865
866 //-----------------------------------------------------------------------
867 // Initialize the SpeechTaskList
868
SpeechTaskList(void)869 SpeechTaskList::SpeechTaskList(void) {
870 lockFlag = false;
871 }
872
SpeechTaskList(Common::InSaveFile * in)873 SpeechTaskList::SpeechTaskList(Common::InSaveFile *in) {
874 int16 count;
875
876 lockFlag = false;
877
878 // Get the speech count
879 count = in->readSint16LE();
880 debugC(3, kDebugSaveload, "... count = %d", count);
881
882 // Restore the speeches
883 for (int i = 0; i < count; i++) {
884 Speech *sp = new Speech;
885 assert(sp != NULL);
886 debugC(3, kDebugSaveload, "Loading Speech %d", i++);
887
888 _inactiveList.push_back(sp);
889 sp->read(in);
890 }
891 }
892
893 //-----------------------------------------------------------------------
894 // Return the number of bytes needed to archive the speech tasks
895
archiveSize(void)896 int32 SpeechTaskList::archiveSize(void) {
897 int32 size = 0;
898
899 size += sizeof(int16); // Speech count
900
901 for (Common::List<Speech *>::iterator it = _list.begin();
902 it != _list.end(); ++it) {
903 size += (*it)->archiveSize();
904 }
905
906 for (Common::List<Speech *>::iterator it = _inactiveList.begin();
907 it != _inactiveList.end(); ++it) {
908 size += (*it)->archiveSize();
909 }
910
911 return size;
912 }
913
write(Common::MemoryWriteStreamDynamic * out)914 void SpeechTaskList::write(Common::MemoryWriteStreamDynamic *out) {
915 int i = 0;
916 int16 count = 0;
917
918 count += _list.size() + _inactiveList.size();
919
920 // Store speech count
921 out->writeSint16LE(count);
922 debugC(3, kDebugSaveload, "... count = %d", count);
923
924 // Store active speeches
925 for (Common::List<Speech *>::iterator it = _list.begin();
926 it != _list.end(); ++it) {
927 debugC(3, kDebugSaveload, "Saving Speech %d (active)", i++);
928 (*it)->write(out);
929 }
930
931 // Store inactive speeches
932 for (Common::List<Speech *>::iterator it = _inactiveList.begin();
933 it != _inactiveList.end(); ++it) {
934 debugC(3, kDebugSaveload, "Saving Speech %d (inactive)", i++);
935 (*it)->write(out);
936 }
937 }
938
939 //-----------------------------------------------------------------------
940 // Cleanup the speech tasks
941
cleanup(void)942 void SpeechTaskList::cleanup(void) {
943 for (Common::List<Speech *>::iterator it = speechList._list.begin();
944 it != speechList._list.end(); ++it) {
945 delete *it;
946 }
947
948 for (Common::List<Speech *>::iterator it = speechList._inactiveList.begin();
949 it != speechList._inactiveList.end(); ++it) {
950 delete *it;
951 }
952
953 _list.clear();
954 _inactiveList.clear();
955 }
956
957 //-----------------------------------------------------------------------
958 // Search for a speech task associated with a particular GameObject.
959
findSpeech(ObjectID id)960 Speech *SpeechTaskList::findSpeech(ObjectID id) {
961 for (Common::List<Speech *>::iterator it = speechList._inactiveList.begin();
962 it != speechList._inactiveList.end(); ++it) {
963 if ((*it)->objID == id)
964 return *it;
965 }
966
967 return nullptr;
968 }
969
970 //-----------------------------------------------------------------------
971 // Get a new speech task, if there is one available, and initialize it.
972
newTask(ObjectID id,uint16 flags)973 Speech *SpeechTaskList::newTask(ObjectID id, uint16 flags) {
974 Speech *sp;
975 GameObject *obj = GameObject::objectAddress(id);
976
977 // Actors cannot speak if not in the world
978 if (obj->world() != currentWorld) return NULL;
979
980 if (speechCount() >= MAX_SPEECH_PTRS) {
981 warning("Too many speech tasks: > %d", MAX_SPEECH_PTRS);
982 return nullptr;
983 }
984
985 sp = new Speech;
986 #if DEBUG
987 if (sp == NULL) fatal("Ran out of Speech Tasks, Object = %s\n", obj->objName());
988 #endif
989 if (sp == NULL) return NULL;
990
991 debugC(1, kDebugTasks, "Speech: New Task: %p for %p (%s) (flags = %d) (total = %d)", (void *)sp, (void *)obj, obj->objName(), flags, speechCount());
992
993 sp->sampleCount = sp->charCount = 0;
994 sp->objID = id;
995 sp->speechFlags = flags & (Speech::spNoAnimate | Speech::spLock);
996 sp->outlineColor = 15 + 9;
997 sp->thread = NoThread;
998 sp->selectedButton = 0;
999
1000 // Set the pen color of the speech based on the actor
1001 if (isActor(id)) {
1002 Actor *a = (Actor *)obj;
1003
1004 // If actor has color table loaded, then get the speech
1005 // color for this particular color scheme; else use a
1006 // default color.
1007 if (a == getCenterActor()) sp->penColor = 3 + 9 /* 1 */;
1008 else if (a->_appearance
1009 && a->_appearance->schemeList) {
1010 sp->penColor =
1011 a->_appearance->schemeList->_schemes[a->_colorScheme]->speechColor + 9;
1012 } else sp->penColor = 4 + 9;
1013 } else {
1014 sp->penColor = 4 + 9;
1015 }
1016
1017 _inactiveList.push_back(sp);
1018 return sp;
1019 }
1020
SetLock(int newState)1021 void SpeechTaskList::SetLock(int newState) {
1022 if (newState && lockFlag == false) {
1023 noStickyMap();
1024 LockUI(true);
1025 lockFlag = true;
1026 } else if (lockFlag && newState == false) {
1027 LockUI(false);
1028 lockFlag = false;
1029 }
1030 }
1031
1032 //-----------------------------------------------------------------------
1033 // When a speech task is finished, call this function to delete it.
1034
remove(void)1035 void Speech::remove(void) {
1036 speechList.remove(this);
1037 }
1038
1039 //-----------------------------------------------------------------------
1040 // AppFunc for handling clicks on speech
1041
pickSpeechButton(Point16 mouse,int16 size,gPort & textPort)1042 int16 pickSpeechButton(Point16 mouse, int16 size, gPort &textPort) {
1043 Point16 p = mouse - initialSpeechPosition;
1044
1045 p.x -= kTileRectX;
1046 p.y -= kTileRectY;
1047
1048 return pickButton(p,
1049 speechLineList, speechLineCount,
1050 speechButtonList, speechButtonCount,
1051 size,
1052 textPort);
1053 }
1054
APPFUNC(cmdClickSpeech)1055 APPFUNC(cmdClickSpeech) {
1056 Speech *sp;
1057
1058 switch (ev.eventType) {
1059 case gEventMouseMove:
1060 case gEventMouseDrag:
1061
1062 g_vm->_mouseInfo->setDoable(Rect16(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight).ptInside(ev.mouse));
1063 break;
1064
1065 case gEventMouseDown:
1066
1067 if ((sp = speechList.currentActive()) != NULL) {
1068 sp->selectedButton = pickSpeechButton(ev.mouse, sp->_speechImage.size.x, sp->_textPort);
1069 }
1070 break;
1071
1072 default:
1073 break;
1074 }
1075 }
1076
1077 /* ===================================================================== *
1078 SpeechTask management functions
1079 * ===================================================================== */
1080
1081 //-----------------------------------------------------------------------
1082 // Initialize the speech task list
1083
initSpeechTasks(void)1084 void initSpeechTasks(void) {
1085 // Simply call the SpeechTaskList default constructor
1086 new (&speechList) SpeechTaskList;
1087 }
1088
saveSpeechTasks(Common::OutSaveFile * outS)1089 void saveSpeechTasks(Common::OutSaveFile *outS) {
1090 debugC(2, kDebugSaveload, "Saving Speech Tasks");
1091
1092 outS->write("SPCH", 4);
1093 CHUNK_BEGIN;
1094 speechList.write(out);
1095 CHUNK_END;
1096 }
1097
loadSpeechTasks(Common::InSaveFile * in,int32 chunkSize)1098 void loadSpeechTasks(Common::InSaveFile *in, int32 chunkSize) {
1099 debugC(2, kDebugSaveload, "Loading Speech Tasks");
1100
1101 // If there is no saved data, simply call the default constructor
1102 if (chunkSize == 0) {
1103 new (&speechList) SpeechTaskList;
1104 return;
1105 }
1106
1107 // Reconstruct stackList from archived data
1108 new (&speechList) SpeechTaskList(in);
1109 }
1110
1111 //-----------------------------------------------------------------------
1112 // Cleanup the speech task list
1113
cleanupSpeechTasks(void)1114 void cleanupSpeechTasks(void) {
1115 // Call speechList's cleanup() function
1116 speechList.cleanup();
1117 }
1118
1119 } // end of namespace Saga2
1120