1 /*
2  * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3  *
4  * This file is part of Arx Libertatis.
5  *
6  * Arx Libertatis is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Arx Libertatis is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /* Based on:
20 ===========================================================================
21 ARX FATALIS GPL Source Code
22 Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
23 
24 This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
25 
26 Arx Fatalis Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
27 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
28 
29 Arx Fatalis Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
30 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
31 
32 You should have received a copy of the GNU General Public License along with Arx Fatalis Source Code.  If not, see
33 <http://www.gnu.org/licenses/>.
34 
35 In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
36 additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
37 Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
38 
39 If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
40 ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
41 ===========================================================================
42 */
43 // Code: Cyril Meynier
44 //
45 // Copyright (c) 1999-2000 ARKANE Studios SA. All rights reserved
46 
47 #include "gui/Speech.h"
48 
49 #include <cstdlib>
50 #include <cstdio>
51 #include <algorithm>
52 
53 #include <boost/lexical_cast.hpp>
54 
55 #include "core/Core.h"
56 #include "core/Localisation.h"
57 #include "core/GameTime.h"
58 
59 #include "game/EntityManager.h"
60 #include "game/NPC.h"
61 #include "game/Player.h"
62 
63 #include "gui/Interface.h"
64 #include "gui/Text.h"
65 #include "gui/TextManager.h"
66 
67 #include "graphics/Draw.h"
68 #include "graphics/Math.h"
69 #include "graphics/font/Font.h"
70 
71 #include "io/resource/ResourcePath.h"
72 #include "io/log/Logger.h"
73 
74 #include "scene/GameSound.h"
75 #include "scene/Interactive.h"
76 
77 #include "script/ScriptEvent.h"
78 
79 using std::min;
80 using std::max;
81 using std::string;
82 using std::transform;
83 
84 extern TextureContainer *	arx_logo_tc;
85 extern long ARX_CONVERSATION;
86 extern long EXTERNALVIEW;
87 extern long REQUEST_SPEECH_SKIP;
88 
89 ARX_SPEECH aspeech[MAX_ASPEECH];
90 Notification speech[MAX_SPEECH];
91 
92 
93 //-----------------------------------------------------------------------------
ARX_SPEECH_Init()94 void ARX_SPEECH_Init()
95 {
96 	for (size_t i = 0 ; i < MAX_SPEECH ; i++ )
97 		speech[i].clear();
98 }
99 
100 //-----------------------------------------------------------------------------
ARX_SPEECH_MoveUp()101 void ARX_SPEECH_MoveUp()
102 {
103 	if (speech[0].timecreation != 0)
104 	{
105 			speech[0].text.clear();
106 	}
107 
108 	for (size_t j = 0; j < MAX_SPEECH - 1; j++)
109 	{
110 		speech[j] = speech[j+1];
111 	}
112 
113 	speech[MAX_SPEECH-1].clear();
114 }
115 
116 //-----------------------------------------------------------------------------
ARX_SPEECH_ClearAll()117 void ARX_SPEECH_ClearAll()
118 {
119 	for (size_t i = 0; i < MAX_SPEECH; i++)
120 	{
121 		if (speech[i].timecreation != 0) {
122 			speech[i].clear();
123 		}
124 	}
125 }
126 
ARX_SPEECH_Add(const string & text,long duration)127 long ARX_SPEECH_Add(const string & text, long duration) {
128 
129 	if(text.empty()) return -1;
130 
131 	unsigned long tim = (unsigned long)(arxtime);
132 	if(tim == 0) {
133 		tim = 1;
134 	}
135 
136 	if(speech[MAX_SPEECH - 1].timecreation != 0) {
137 		ARX_SPEECH_MoveUp();
138 	}
139 
140 	for(size_t i = 0; i < MAX_SPEECH; i++) {
141 		if(speech[i].timecreation != 0) {
142 			continue;
143 		}
144 
145 		// Sets creation time
146 		speech[i].timecreation = tim;
147 
148 		// Sets/computes speech duration
149 		if(duration == -1) {
150 			speech[i].duration = 2000 + text.length() * 60;
151 		} else {
152 			speech[i].duration = duration;
153 		}
154 
155 		speech[i].text = text;
156 
157 		// Successfull allocation
158 		return speech[i].duration;
159 	}
160 
161 	return -1;
162 }
163 
isLastSpeech(size_t index)164 static bool isLastSpeech(size_t index) {
165 
166 	for(size_t i = index + 1; i < MAX_SPEECH; i++) {
167 		if(speech[i].timecreation != 0 && !speech[i].text.empty()) {
168 			return false;
169 		}
170 	}
171 
172 	return true;
173 }
174 
ARX_SPEECH_Render()175 void ARX_SPEECH_Render() {
176 
177 	long igrec = 14;
178 
179 	Vec2i sSize = hFontInBook->getTextSize("p");
180 	sSize.y *= 3;
181 
182 	GRenderer->SetBlendFunc(Renderer::BlendOne, Renderer::BlendOne);
183 	GRenderer->SetRenderState(Renderer::AlphaBlending, true);
184 
185 	int iEnd = igrec + sSize.y;
186 
187 	for(size_t i = 0; i < MAX_SPEECH; i++) {
188 
189 		if(speech[i].timecreation == 0 || speech[i].text.empty()) {
190 			continue;
191 		}
192 
193 		EERIEDrawBitmap(120 * Xratio - 16 * Xratio, static_cast<float>(igrec),
194 		                16 * Xratio, 16 * Xratio, .00001f, arx_logo_tc, Color::white);
195 
196 		igrec += ARX_TEXT_DrawRect(hFontInBook, 120.f * Xratio, (float)igrec, 500 * Xratio,
197 		                           ' ' + speech[i].text, Color::white, NULL);
198 
199 		if(igrec > iEnd && !isLastSpeech(i)) {
200 			ARX_SPEECH_MoveUp();
201 			break;
202 		}
203 	}
204 
205 	GRenderer->SetRenderState(Renderer::AlphaBlending, false);
206 }
207 
ARX_SPEECH_Check()208 void ARX_SPEECH_Check()
209 {
210 	bool bClear = false;
211 	long exist = 0;
212 
213 	for (size_t i = 0; i < MAX_SPEECH; i++)
214 	{
215 		if (speech[i].timecreation != 0)
216 		{
217 			if (float(arxtime) > speech[i].timecreation + speech[i].duration)
218 			{
219 				ARX_SPEECH_MoveUp();
220 				i--;
221 			}
222 			else exist++;
223 
224 			bClear = true;
225 		}
226 	}
227 
228 	if (bClear)
229 	{
230 		if (pTextManage)
231 		{
232 			pTextManage->Clear();
233 		}
234 	}
235 
236 	if (exist) ARX_SPEECH_Render();
237 }
238 
239 //-----------------------------------------------------------------------------
ARX_SPEECH_Launch_No_Unicode_Seek(const string & text,Entity * io_source,long mood)240 void ARX_SPEECH_Launch_No_Unicode_Seek(const string & text, Entity * io_source, long mood)
241 {
242 	mood = ANIM_TALK_NEUTRAL;
243 	long speechnum = ARX_SPEECH_AddSpeech(io_source, text, mood, ARX_SPEECH_FLAG_NOTEXT);
244 
245 	if (speechnum >= 0)
246 	{
247 		aspeech[speechnum].scrpos = -1;
248 		aspeech[speechnum].es = NULL;
249 		aspeech[speechnum].ioscript = io_source;
250 		aspeech[speechnum].flags = 0;
251 		CinematicSpeech acs;
252 		acs.type = ARX_CINE_SPEECH_NONE;
253 		aspeech[speechnum].cine = acs;
254 	}
255 }
256 
257 
258 ARX_CONVERSATION_STRUCT main_conversation;
ARX_CONVERSATION_FirstInit()259 void ARX_CONVERSATION_FirstInit()
260 {
261 	main_conversation.actors_nb = 0;
262 	main_conversation.current = -1;
263 }
ARX_CONVERSATION_Reset()264 void ARX_CONVERSATION_Reset()
265 {
266 	main_conversation.actors_nb = 0;
267 	main_conversation.current = -1;
268 }
269 
ARX_CONVERSATION_CheckAcceleratedSpeech()270 void ARX_CONVERSATION_CheckAcceleratedSpeech() {
271 
272 	if(REQUEST_SPEECH_SKIP) {
273 		for(size_t i = 0; i < MAX_ASPEECH; i++) {
274 			if((aspeech[i].exist) && !(aspeech[i].flags & ARX_SPEECH_FLAG_UNBREAKABLE)) {
275 				aspeech[i].duration = 0;
276 			}
277 		}
278 		REQUEST_SPEECH_SKIP = 0;
279 	}
280 }
281 
282 
ARX_SPEECH_FirstInit()283 void ARX_SPEECH_FirstInit() {
284 	for(size_t i = 0 ; i < MAX_ASPEECH ; i++) {
285 		aspeech[i].clear();
286 	}
287 }
288 
ARX_SPEECH_GetFree()289 long ARX_SPEECH_GetFree() {
290 
291 	for(size_t i = 0; i < MAX_ASPEECH; i++) {
292 		if(!aspeech[i].exist) {
293 			aspeech[i].cine.type = ARX_CINE_SPEECH_NONE;
294 			return i;
295 		}
296 	}
297 
298 	return -1;
299 }
300 
ARX_SPEECH_GetIOSpeech(Entity * io)301 long ARX_SPEECH_GetIOSpeech(Entity * io) {
302 
303 	for(size_t i = 0; i < MAX_ASPEECH; i++) {
304 		if(aspeech[i].exist && aspeech[i].io == io) {
305 			return i;
306 		}
307 	}
308 
309 	return -1;
310 }
311 
ARX_SPEECH_Release(long i)312 void ARX_SPEECH_Release(long i) {
313 
314 	if(aspeech[i].exist) {
315 
316 		ARX_SOUND_Stop(aspeech[i].sample);
317 
318 		if(ValidIOAddress(aspeech[i].io) && aspeech[i].io->animlayer[2].cur_anim) {
319 			AcquireLastAnim(aspeech[i].io);
320 			aspeech[i].io->animlayer[2].cur_anim = NULL;
321 		}
322 
323 		aspeech[i].clear();
324 	}
325 }
326 
ARX_SPEECH_ReleaseIOSpeech(Entity * io)327 void ARX_SPEECH_ReleaseIOSpeech(Entity * io) {
328 
329 	for(size_t i = 0; i < MAX_ASPEECH; i++) {
330 		if(aspeech[i].exist && aspeech[i].io == io) {
331 			ARX_SPEECH_Release(i);
332 		}
333 	}
334 }
335 
ARX_SPEECH_Reset()336 void ARX_SPEECH_Reset() {
337 	for(size_t i = 0; i < MAX_ASPEECH; i++) {
338 		ARX_SPEECH_Release(i);
339 	}
340 }
341 
ARX_SPEECH_ClearIOSpeech(Entity * io)342 void ARX_SPEECH_ClearIOSpeech(Entity * io) {
343 
344 	if(!io) {
345 		return;
346 	}
347 
348 	for(size_t i = 0; i < MAX_ASPEECH; i++) {
349 
350 		if(!aspeech[i].exist || aspeech[i].io != io) {
351 			continue;
352 		}
353 
354 		EERIE_SCRIPT * es = aspeech[i].es;
355 		Entity * io = aspeech[i].ioscript;
356 		long scrpos = aspeech[i].scrpos;
357 		ARX_SPEECH_Release(i);
358 
359 		if(es && ValidIOAddress(io)) {
360 			ScriptEvent::send(es, SM_EXECUTELINE, "", io, "", scrpos);
361 		}
362 	}
363 }
364 
365 
ARX_SPEECH_AddSpeech(Entity * io,const std::string & data,long mood,SpeechFlags flags)366 long ARX_SPEECH_AddSpeech(Entity * io, const std::string & data, long mood,
367                           SpeechFlags flags) {
368 
369 	if(data.empty()) {
370 		return -1;
371 	}
372 
373 	ARX_SPEECH_ClearIOSpeech(io);
374 
375 	long num = ARX_SPEECH_GetFree();
376 	if(num < 0) {
377 		return -1;
378 	}
379 
380 	aspeech[num].exist = 1;
381 	aspeech[num].time_creation = arxtime.get_updated_ul();
382 	aspeech[num].io = io; // can be NULL
383 	aspeech[num].duration = 2000; // Minimum value
384 	aspeech[num].flags = flags;
385 	aspeech[num].sample = -1;
386 	aspeech[num].fDeltaY = 0.f;
387 	aspeech[num].iTimeScroll = 0;
388 	aspeech[num].fPixelScroll = 0.f;
389 	aspeech[num].mood = mood;
390 
391 	LogDebug("speech \"" << data << '"');
392 
393 	res::path sample;
394 
395 	if(flags & ARX_SPEECH_FLAG_NOTEXT) {
396 
397 		// For non-conversation speech choose a random variant
398 
399 		long count = getLocalisedKeyCount(data);
400 		long variant = 1;
401 
402 		// TODO For some samples there are no corresponding entries
403 		// in the localization file  (utext_*.ini) -> count will be 0
404 		// We should probably just count the number of sample files
405 
406 		if(count > 1) {
407 			do {
408 				variant = Random::get(1, count);
409 			} while(io->lastspeechflag == variant);
410 			io->lastspeechflag = checked_range_cast<short>(variant);
411 		}
412 
413 		LogDebug(" -> " << variant << " / " << count);
414 
415 		if(variant > 1) {
416 			sample = data + boost::lexical_cast<std::string>(variant);
417 		} else {
418 			sample = data;
419 		}
420 
421 	} else {
422 
423 		std::string _output = getLocalised(data);
424 
425 		io->lastspeechflag = 0;
426 		aspeech[num].text.clear();
427 		aspeech[num].text = _output;
428 		aspeech[num].duration = max(aspeech[num].duration, (unsigned long)(strlen(_output.c_str()) + 1) * 100);
429 
430 		sample = data;
431 	}
432 
433 	Entity * source = (aspeech[num].flags & ARX_SPEECH_FLAG_OFFVOICE) ? NULL : io;
434 	aspeech[num].sample = ARX_SOUND_PlaySpeech(sample, source);
435 
436 	if(aspeech[num].sample == ARX_SOUND_TOO_FAR) {
437 		aspeech[num].sample = audio::INVALID_ID;
438 	}
439 
440 	//Next lines must be removed (use callback instead)
441 	aspeech[num].duration = (unsigned long)ARX_SOUND_GetDuration(aspeech[num].sample);
442 
443 	if ((io->ioflags & IO_NPC) && !(aspeech[num].flags & ARX_SPEECH_FLAG_OFFVOICE)) {
444 		float fDiv = aspeech[num].duration /= io->_npcdata->speakpitch;
445 		aspeech[num].duration = static_cast<unsigned long>(fDiv);
446 	}
447 
448 	if (aspeech[num].duration < 500) aspeech[num].duration = 2000;
449 
450 	if (ARX_CONVERSATION && io)
451 		for (long j = 0; j < main_conversation.actors_nb; j++)
452 			if (main_conversation.actors[j] >= 0 && io == entities[main_conversation.actors[j]])
453 				main_conversation.current = num;
454 
455 	return num;
456 }
457 
ARX_SPEECH_Update()458 void ARX_SPEECH_Update() {
459 
460 	unsigned long tim = (unsigned long)(arxtime);
461 
462 	if (CINEMASCOPE || BLOCK_PLAYER_CONTROLS) ARX_CONVERSATION_CheckAcceleratedSpeech();
463 
464 	for (size_t i = 0 ; i < MAX_ASPEECH ; i++)
465 	{
466 		if (aspeech[i].exist)
467 		{
468 			Entity * io = aspeech[i].io;
469 
470 			// updates animations
471 			if (io)
472 			{
473 				if (aspeech[i].flags & ARX_SPEECH_FLAG_OFFVOICE)
474 					ARX_SOUND_RefreshSpeechPosition(aspeech[i].sample);
475 				else
476 					ARX_SOUND_RefreshSpeechPosition(aspeech[i].sample, io);
477 
478 				if (((io != entities.player()) || ((io == entities.player())  && (EXTERNALVIEW)))
479 						&&	ValidIOAddress(io))
480 				{
481 					if (io->anims[aspeech[i].mood] == NULL)	aspeech[i].mood = ANIM_TALK_NEUTRAL;
482 
483 					if (io->anims[aspeech[i].mood] != NULL)
484 					{
485 						if ((io->animlayer[2].cur_anim != io->anims[aspeech[i].mood])
486 								||	(io->animlayer[2].flags & EA_ANIMEND))
487 						{
488 							AcquireLastAnim(io);
489 							ANIM_Set(&io->animlayer[2], io->anims[aspeech[i].mood]);
490 						}
491 					}
492 				}
493 			}
494 
495 			// checks finished speech
496 			if (tim >= aspeech[i].time_creation + aspeech[i].duration)
497 			{
498 				EERIE_SCRIPT	*	es		= aspeech[i].es;
499 				Entity	* io		= aspeech[i].ioscript;
500 				long				scrpos	= aspeech[i].scrpos;
501 				ARX_SPEECH_Release(i);
502 
503 				if ((es)
504 						&&	(ValidIOAddress(io)))
505 					ScriptEvent::send(es, SM_EXECUTELINE, "", io, "", scrpos);
506 			}
507 		}
508 	}
509 
510 	for (size_t i = 0 ; i < MAX_ASPEECH ; i++)
511 	{
512 		ARX_SPEECH * speech = &aspeech[i];
513 
514 		if (speech->exist)
515 		{
516 			if (!speech->text.empty())
517 			{
518 				if ((ARX_CONVERSATION) && (speech->io))
519 				{
520 					long ok = 0;
521 
522 					for (long j = 0 ; j < main_conversation.actors_nb ; j++)
523 					{
524 						if (main_conversation.actors[j] >= 0)
525 							if (speech->io == entities[main_conversation.actors[j]])
526 							{
527 								ok = 1;
528 							}
529 					}
530 
531 					if (!ok) goto next;
532 				}
533 
534 				if(CINEMASCOPE) {
535 					if (CINEMA_DECAL >= 100.f)
536 					{
537 						Vec2i sSize = hFontInBook->getTextSize(speech->text);
538 
539 						float fZoneClippHeight	=	static_cast<float>(sSize.y * 3);
540 						float fStartYY			=	100 * Yratio;
541 						float fStartY			=	static_cast<float>(((int)fStartYY - (int)fZoneClippHeight) >> 1);
542 						float fDepY				=	((float)DANAESIZY) - fStartYY + fStartY - speech->fDeltaY + sSize.y;
543 						float fZoneClippY		=	fDepY + speech->fDeltaY;
544 
545 						float fAdd = fZoneClippY + fZoneClippHeight ;
546 
547 						Rect::Num y = checked_range_cast<Rect::Num>(fZoneClippY);
548 						Rect::Num h = checked_range_cast<Rect::Num>(fAdd);
549 						Rect clippingRect(0, y+1, DANAESIZX, h);
550 						float iTaille = (float)ARX_TEXT_DrawRect(
551 						                    hFontInBook,
552 						                    10.f,
553 						                    fDepY + fZoneClippHeight,
554 						                    -10.f + (float)DANAESIZX,
555 						                    speech->text,
556 						                    Color::white,
557 						                    &clippingRect);
558 
559 						GRenderer->SetBlendFunc(Renderer::BlendZero, Renderer::BlendInvSrcColor);
560 						GRenderer->SetRenderState(Renderer::AlphaBlending, true);
561 						GRenderer->SetRenderState(Renderer::DepthTest, false);
562 						EERIEDrawFill2DRectDegrad(0.f, fZoneClippY - 1.f,  static_cast<float>(DANAESIZX),
563 						                          fZoneClippY + (sSize.y * 3 / 4), 0.f, Color::white, Color::black);
564 						EERIEDrawFill2DRectDegrad(0.f, fZoneClippY + fZoneClippHeight - (sSize.y * 3 / 4),
565 						                          static_cast<float>(DANAESIZX), fZoneClippY + fZoneClippHeight,
566 						                          0.f, Color::black, Color::white);
567 						GRenderer->SetBlendFunc(Renderer::BlendOne, Renderer::BlendZero);
568 						GRenderer->SetRenderState(Renderer::DepthTest, true);
569 						GRenderer->SetRenderState(Renderer::AlphaBlending, false);
570 
571 						iTaille += (int)fZoneClippHeight;
572 
573 						if (((int)speech->fDeltaY) <= iTaille)
574 						{
575 							//vitesse du scroll
576 							float fDTime;
577 
578 							if (speech->sample)
579 							{
580 
581 								float duration = ARX_SOUND_GetDuration(speech->sample);
582 								if(duration == 0.0f) {
583 									duration = 4000.0f;
584 								}
585 
586 								fDTime = ((float)iTaille * (float)FrameDiff) / duration; //speech->duration;
587 								float fTimeOneLine = ((float)sSize.y) * fDTime;
588 
589 								if (((float)speech->iTimeScroll) >= fTimeOneLine)
590 								{
591 									float fResteLine = (float)sSize.y - speech->fPixelScroll;
592 									float fTimePlus = ((float)fResteLine * (float)FrameDiff) / duration;
593 									fDTime -= fTimePlus;
594 									speech->fPixelScroll = 0.f;
595 									speech->iTimeScroll = 0;
596 								}
597 
598 								speech->iTimeScroll	+= checked_range_cast<int>(FrameDiff);
599 							}
600 							else
601 							{
602 								fDTime = ((float)iTaille * (float)FrameDiff) / 4000.0f;
603 							}
604 
605 							speech->fDeltaY			+= fDTime;
606 							speech->fPixelScroll	+= fDTime;
607 						}
608 					}
609 				}
610 			}
611 
612 		next:
613 			;
614 		}
615 	}
616 
617 
618 }
619 
ApplySpeechPos(EERIE_CAMERA * conversationcamera,long is)620 bool ApplySpeechPos(EERIE_CAMERA * conversationcamera, long is) {
621 
622 	if(is < 0 || !aspeech[is].io) {
623 		return false;
624 	}
625 
626 	conversationcamera->d_pos = aspeech[is].io->pos + player.baseOffset();
627 	float t = (aspeech[is].io->angle.b);
628 	conversationcamera->pos = conversationcamera->d_pos;
629 	conversationcamera->pos += Vec3f(EEsin(t) * 100.f, 0.f, -EEcos(t) * 100.f);
630 	return true;
631 }
632