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