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