TickHook: Fix crash when TickHook isn't set.
[gemrb.git] / gemrb / core / DialogHandler.cpp
blob5653a80969b94efa4a3008c388968fc9c89821aa
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "DialogHandler.h"
22 #include "strrefs.h"
24 #include "DialogMgr.h"
25 #include "DisplayMessage.h"
26 #include "Game.h"
27 #include "GameData.h"
28 #include "Video.h"
29 #include "GUI/GameControl.h"
31 //translate section values (journal, solved, unsolved, user)
32 static int sectionMap[4]={4,1,2,0};
33 static const int bg2Sections[4]={4,1,2,0};
34 static const int noSections[4]={0,0,0,0};
36 DialogHandler::DialogHandler(void)
38 dlg = NULL;
39 targetID = 0;
40 originalTargetID = 0;
41 speakerID = 0;
42 if (core->HasFeature(GF_JOURNAL_HAS_SECTIONS) ) {
43 memcpy(sectionMap, bg2Sections, sizeof(sectionMap) );
44 } else {
45 memcpy(sectionMap, noSections, sizeof(sectionMap) );
49 DialogHandler::~DialogHandler(void)
51 if (dlg) {
52 delete dlg;
56 //Try to start dialogue between two actors (one of them could be inanimate)
57 int DialogHandler::InitDialog(Scriptable* spk, Scriptable* tgt, const char* dlgref)
59 if (dlg) {
60 delete dlg;
61 dlg = NULL;
64 PluginHolder<DialogMgr> dm(IE_DLG_CLASS_ID);
65 dm->Open( gamedata->GetResource( dlgref, IE_DLG_CLASS_ID ), true );
66 dlg = dm->GetDialog();
68 if (!dlg) {
69 printMessage("GameControl", " ", LIGHT_RED);
70 printf( "Cannot start dialog: %s\n", dlgref );
71 return -1;
74 strnlwrcpy(dlg->ResRef, dlgref, 8); //this isn't handled by GetDialog???
76 //target is here because it could be changed when a dialog runs onto
77 //and external link, we need to find the new target (whose dialog was
78 //linked to)
80 Actor *oldTarget = GetActorByGlobalID(targetID);
81 speakerID = spk->GetGlobalID();
82 targetID = tgt->GetGlobalID();
83 if (!originalTargetID) originalTargetID = tgt->GetGlobalID();
84 if (tgt->Type==ST_ACTOR) {
85 Actor *tar = (Actor *) tgt;
86 spk->LastTalkedTo=targetID;
87 tar->LastTalkedTo=speakerID;
88 tar->SetCircleSize();
90 if (oldTarget) oldTarget->SetCircleSize();
92 //check if we are already in dialog
93 if (core->GetGameControl()->GetDialogueFlags()&DF_IN_DIALOG) {
94 return 0;
97 int si = dlg->FindFirstState( tgt );
98 if (si < 0) {
99 return -1;
102 //we need GUI for dialogs
103 core->GetGameControl()->UnhideGUI();
105 //no exploring while in dialogue
106 core->GetGameControl()->SetScreenFlags(SF_GUIENABLED|SF_DISABLEMOUSE|SF_LOCKSCROLL, BM_OR);
107 core->GetGameControl()->SetDialogueFlags(DF_IN_DIALOG, BM_OR);
109 if (tgt->Type==ST_ACTOR) {
110 Actor *tar = (Actor *) tgt;
111 tar->DialogInterrupt();
114 //allow mouse selection from dialog (even though screen is locked)
115 Video *video = core->GetVideoDriver();
116 Region vp = video->GetViewport();
117 video->SetMouseEnabled(true);
118 core->timer->SetMoveViewPort( tgt->Pos.x, tgt->Pos.y, 0, true );
119 video->MoveViewportTo( tgt->Pos.x-vp.w/2, tgt->Pos.y-vp.h/2 );
120 //there are 3 bits, if they are all unset, the dialog freezes scripts
121 if (!(dlg->Flags&7) ) {
122 core->GetGameControl()->SetDialogueFlags(DF_FREEZE_SCRIPTS, BM_OR);
124 //opening control size to maximum, enabling dialog window
125 core->GetGame()->SetControlStatus(CS_HIDEGUI, BM_NAND);
126 core->GetGame()->SetControlStatus(CS_DIALOG, BM_OR);
127 core->SetEventFlag(EF_PORTRAIT);
128 return 0;
131 /*try to break will only try to break it, false means unconditional stop*/
132 void DialogHandler::EndDialog(bool try_to_break)
134 if (try_to_break && (core->GetGameControl()->GetDialogueFlags()&DF_UNBREAKABLE) ) {
135 return;
138 Actor *tmp = GetSpeaker();
139 if (tmp) {
140 tmp->LeaveDialog();
142 speakerID = 0;
143 Scriptable *tmp2 = GetTarget();
144 if (tmp2 && tmp2->Type == ST_ACTOR) {
145 tmp = (Actor *)tmp2;
146 } else {
147 tmp = NULL;
149 if (tmp) {
150 tmp->LeaveDialog();
152 targetID = 0;
153 if (tmp) tmp->SetCircleSize();
154 originalTargetID = 0;
155 ds = NULL;
156 if (dlg) {
157 delete dlg;
158 dlg = NULL;
160 //restoring original size
161 core->GetGame()->SetControlStatus(CS_DIALOG, BM_NAND);
162 core->GetGameControl()->SetScreenFlags(SF_DISABLEMOUSE|SF_LOCKSCROLL, BM_NAND);
163 core->GetGameControl()->SetDialogueFlags(0, BM_SET);
164 core->SetEventFlag(EF_PORTRAIT);
168 void DialogHandler::DialogChoose(unsigned int choose)
170 TextArea* ta = core->GetMessageTextArea();
171 if (!ta) {
172 printMessage("GameControl","Dialog aborted???",LIGHT_RED);
173 EndDialog();
174 return;
177 Actor *speaker = GetSpeaker();
178 if (!speaker) {
179 printMessage("GameControl","Speaker gone???",LIGHT_RED);
180 EndDialog();
181 return;
184 Scriptable *target = GetTarget();
185 if (!target) {
186 printMessage("GameControl","Target gone???",LIGHT_RED);
187 EndDialog();
188 return;
190 Actor *tgt = NULL;
191 if (target->Type == ST_ACTOR) {
192 tgt = (Actor *)target;
195 Video *video = core->GetVideoDriver();
196 Region vp = video->GetViewport();
197 video->SetMouseEnabled(true);
198 core->timer->SetMoveViewPort( target->Pos.x, target->Pos.y, 0, true );
199 video->MoveViewportTo( target->Pos.x-vp.w/2, target->Pos.y-vp.h/2 );
201 if (choose == (unsigned int) -1) {
202 //increasing talkcount after top level condition was determined
204 int si = dlg->FindFirstState( tgt );
205 if (si<0) {
206 EndDialog();
207 return;
210 if (tgt) {
211 if (core->GetGameControl()->GetDialogueFlags()&DF_TALKCOUNT) {
212 core->GetGameControl()->SetDialogueFlags(DF_TALKCOUNT, BM_NAND);
213 tgt->TalkCount++;
214 } else if (core->GetGameControl()->GetDialogueFlags()&DF_INTERACT) {
215 core->GetGameControl()->SetDialogueFlags(DF_INTERACT, BM_NAND);
216 tgt->InteractCount++;
219 ds = dlg->GetState( si );
220 } else {
221 if (ds->transitionsCount <= choose) {
222 return;
225 DialogTransition* tr = ds->transitions[choose];
227 ta->PopMinRow();
229 if (tr->Flags&IE_DLG_TR_JOURNAL) {
230 int Section = 0;
231 if (tr->Flags&IE_DLG_UNSOLVED) {
232 Section |= 1;
234 if (tr->Flags&IE_DLG_SOLVED) {
235 Section |= 2;
237 if (core->GetGame()->AddJournalEntry(tr->journalStrRef, sectionMap[Section], tr->Flags>>16) ) {
238 displaymsg->DisplayConstantString(STR_JOURNALCHANGE,0xffff00);
239 char *string = core->GetString( tr->journalStrRef );
240 //cutting off the strings at the first crlf
241 char *poi = strchr(string,'\n');
242 if (poi) {
243 *poi='\0';
245 displaymsg->DisplayString( string );
246 free( string );
250 if (tr->textStrRef != 0xffffffff) {
251 //allow_zero is for PST (deionarra's text)
252 displaymsg->DisplayStringName( (int) (tr->textStrRef), 0x8080FF, speaker, IE_STR_SOUND|IE_STR_SPEECH|IE_STR_ALLOW_ZERO);
253 if (core->HasFeature( GF_DIALOGUE_SCROLLS )) {
254 ta->AppendText( "", -1 );
258 if (tr->actions.size()) {
259 // does this belong here? we must clear actions somewhere before
260 // we start executing them (otherwise queued actions interfere)
261 // executing actions directly does not work, because dialog
262 // needs to end before final actions are executed due to
263 // actions making new dialogs!
264 if (target->Type == ST_ACTOR) ((Movable *)target)->ClearPath(); // fuzzie added this
265 target->ClearActions();
267 for (unsigned int i = 0; i < tr->actions.size(); i++) {
268 target->AddAction(tr->actions[i]);
269 //GameScript::ExecuteAction( target, action );
273 int final_dialog = tr->Flags & IE_DLG_TR_FINAL;
275 if (final_dialog) {
276 ta->SetMinRow( false );
277 EndDialog();
280 // *** the commented-out line here should no longer be required, with instant handling ***
281 // all dialog actions must be executed immediately
282 //target->ProcessActions(true);
283 // (do not clear actions - final actions can involve waiting/moving)
285 if (final_dialog) {
286 return;
289 // avoid problems when dhjollde.dlg tries starting a cutscene in the middle of a dialog
290 // (it seems harmless doing it in non-HoW too, since other versions would just break in such a situation)
291 core->SetCutSceneMode( false );
293 //displaying dialog for selected option
294 int si = tr->stateIndex;
295 //follow external linkage, if required
296 if (tr->Dialog[0] && strnicmp( tr->Dialog, dlg->ResRef, 8 )) {
297 //target should be recalculated!
298 tgt = NULL;
299 if (originalTargetID) {
300 // always try original target first (sometimes there are multiple
301 // actors with the same dialog in an area, we want to pick the one
302 // we were talking to)
303 tgt = GetActorByGlobalID(originalTargetID);
304 if (tgt && strnicmp( tgt->GetDialog(GD_NORMAL), tr->Dialog, 8 ) != 0) {
305 tgt = NULL;
308 if (!tgt) {
309 // then just search the current area for an actor with the dialog
310 tgt = target->GetCurrentArea()->GetActorByDialog(tr->Dialog);
312 if (!tgt) {
313 // try searching for banter dialogue: the original engine seems to
314 // happily let you randomly switch between normal and banter dialogs
316 // TODO: work out if this should go somewhere more central (such
317 // as GetActorByDialog), or if there's a less awful way to do this
318 // (we could cache the entries, for example)
319 // TODO: fix for ToB (see also the Interact action)
320 AutoTable pdtable("interdia");
321 if (pdtable) {
322 int row = pdtable->FindTableValue( pdtable->GetColumnIndex("FILE"), tr->Dialog );
323 tgt = target->GetCurrentArea()->GetActorByScriptName(pdtable->GetRowName(row));
326 target = tgt;
327 if (!target) {
328 printMessage("Dialog","Can't redirect dialog\n",YELLOW);
329 ta->SetMinRow( false );
330 EndDialog();
331 return;
333 Actor *oldTarget = GetActorByGlobalID(targetID);
334 targetID = tgt->GetGlobalID();
335 tgt->SetCircleSize();
336 if (oldTarget) oldTarget->SetCircleSize();
337 // we have to make a backup, tr->Dialog is freed
338 ieResRef tmpresref;
339 strnlwrcpy(tmpresref,tr->Dialog, 8);
340 if (target->GetInternalFlag()&IF_NOINT) {
341 // this whole check moved out of InitDialog by fuzzie, see comments
342 // for the IF_NOINT check in BeginDialog
343 displaymsg->DisplayConstantString(STR_TARGETBUSY,0xff0000);
344 ta->SetMinRow( false );
345 EndDialog();
346 return;
348 int ret = InitDialog( speaker, target, tmpresref);
349 if (ret<0) {
350 // error was displayed by InitDialog
351 ta->SetMinRow( false );
352 EndDialog();
353 return;
356 ds = dlg->GetState( si );
357 if (!ds) {
358 printMessage("Dialog","Can't find next dialog\n",YELLOW);
359 ta->SetMinRow( false );
360 EndDialog();
361 return;
364 //displaying npc text
365 displaymsg->DisplayStringName( ds->StrRef, 0x70FF70, target, IE_STR_SOUND|IE_STR_SPEECH);
366 //adding a gap between options and npc text
367 ta->AppendText("",-1);
368 int i;
369 int idx = 0;
370 ta->SetMinRow( true );
371 //first looking for a 'continue' opportunity, the order is descending (a la IE)
372 unsigned int x = ds->transitionsCount;
373 while(x--) {
374 if (ds->transitions[x]->Flags & IE_DLG_TR_FINAL) {
375 continue;
377 if (ds->transitions[x]->textStrRef != 0xffffffff) {
378 continue;
380 if (ds->transitions[x]->Flags & IE_DLG_TR_TRIGGER) {
381 if (ds->transitions[x]->condition &&
382 !ds->transitions[x]->condition->Evaluate(target)) {
383 continue;
386 core->GetDictionary()->SetAt("DialogOption",x);
387 core->GetGameControl()->SetDialogueFlags(DF_OPENCONTINUEWINDOW, BM_OR);
388 goto end_of_choose;
390 for (x = 0; x < ds->transitionsCount; x++) {
391 if (ds->transitions[x]->Flags & IE_DLG_TR_TRIGGER) {
392 if (ds->transitions[x]->condition &&
393 !ds->transitions[x]->condition->Evaluate(target)) {
394 continue;
397 idx++;
398 if (ds->transitions[x]->textStrRef == 0xffffffff) {
399 //dialogchoose should be set to x
400 //it isn't important which END option was chosen, as it ends
401 core->GetDictionary()->SetAt("DialogOption",x);
402 core->GetGameControl()->SetDialogueFlags(DF_OPENENDWINDOW, BM_OR);
403 } else {
404 char *string = ( char * ) malloc( 40 );
405 sprintf( string, "[s=%d,ffffff,ff0000]%d - [p]", x, idx );
406 i = ta->AppendText( string, -1 );
407 free( string );
408 string = core->GetString( ds->transitions[x]->textStrRef );
409 ta->AppendText( string, i );
410 free( string );
411 ta->AppendText( "[/p][/s]", i );
414 // this happens if a trigger isn't implemented or the dialog is wrong
415 if (!idx) {
416 printMessage("Dialog", "There were no valid dialog options!\n", YELLOW);
417 core->GetGameControl()->SetDialogueFlags(DF_OPENENDWINDOW, BM_OR);
419 end_of_choose:
420 //padding the rows so our text will be at the top
421 if (core->HasFeature( GF_DIALOGUE_SCROLLS )) {
422 ta->AppendText( "", -1 );
424 else {
425 ta->PadMinRow();
429 // TODO: duplicate of the one in GameControl
430 Actor *DialogHandler::GetActorByGlobalID(ieDword ID)
432 if (!ID)
433 return NULL;
434 Game* game = core->GetGame();
435 if (!game)
436 return NULL;
438 Map* area = game->GetCurrentArea( );
439 if (!area)
440 return NULL;
441 return area->GetActorByGlobalID(ID);
444 Scriptable *DialogHandler::GetTarget()
446 // TODO: area GetScriptableByGlobalID?
448 if (!targetID) return NULL;
450 Game *game = core->GetGame();
451 if (!game) return NULL;
453 Map *area = game->GetCurrentArea();
454 if (!area) return NULL;
456 Actor *actor = area->GetActorByGlobalID(targetID);
457 if (actor) return actor;
459 Door *door = area->GetDoorByGlobalID(targetID);
460 if (door) return door;
461 Container *container = area->GetContainerByGlobalID(targetID);
462 if (container) return container;
463 InfoPoint *ip = area->GetInfoPointByGlobalID(targetID);
464 if (ip) return ip;
466 return NULL;
469 Actor *DialogHandler::GetSpeaker()
471 return GetActorByGlobalID(speakerID);