Sort includes again.
[gemrb.git] / gemrb / core / DialogHandler.cpp
blob062a0794e08262d07ad84efe51e1443b0a8b2350
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 DialogHandler::DialogHandler(void)
33 dlg = NULL;
34 targetID = 0;
35 originalTargetID = 0;
36 speakerID = 0;
37 targetOB = NULL;
40 DialogHandler::~DialogHandler(void)
42 if (dlg) {
43 delete dlg;
47 //Try to start dialogue between two actors (one of them could be inanimate)
48 int DialogHandler::InitDialog(Scriptable* spk, Scriptable* tgt, const char* dlgref)
50 if (dlg) {
51 delete dlg;
52 dlg = NULL;
55 PluginHolder<DialogMgr> dm(IE_DLG_CLASS_ID);
56 dm->Open( gamedata->GetResource( dlgref, IE_DLG_CLASS_ID ), true );
57 dlg = dm->GetDialog();
59 if (!dlg) {
60 printMessage("GameControl", " ", LIGHT_RED);
61 printf( "Cannot start dialog: %s\n", dlgref );
62 return -1;
65 strnlwrcpy(dlg->ResRef, dlgref, 8); //this isn't handled by GetDialog???
67 //target is here because it could be changed when a dialog runs onto
68 //and external link, we need to find the new target (whose dialog was
69 //linked to)
71 Actor *spe = (Actor *) spk;
72 speakerID = spe->globalID;
73 Actor *oldTarget = GetActorByGlobalID(targetID);
74 if (tgt->Type!=ST_ACTOR) {
75 targetID=0xffff;
76 //most likely this dangling object reference
77 //won't cause problems, because trigger points don't
78 //get destroyed during a dialog
79 targetOB=tgt;
80 spk->LastTalkedTo=0;
81 } else {
82 Actor *tar = (Actor *) tgt;
83 speakerID = spe->globalID;
84 targetID = tar->globalID;
85 if (!originalTargetID) originalTargetID = tar->globalID;
86 spe->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 if (targetID==0xffff) {
144 targetOB->LeaveDialog();
145 } else {
146 tmp=GetTarget();
147 if (tmp) {
148 tmp->LeaveDialog();
151 targetOB = NULL;
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);
167 //translate section values (journal, solved, unsolved, user)
168 static const int sectionMap[4]={4,1,2,0};
170 void DialogHandler::DialogChoose(unsigned int choose)
172 TextArea* ta = core->GetMessageTextArea();
173 if (!ta) {
174 printMessage("GameControl","Dialog aborted???",LIGHT_RED);
175 EndDialog();
176 return;
179 Actor *speaker = GetSpeaker();
180 if (!speaker) {
181 printMessage("GameControl","Speaker gone???",LIGHT_RED);
182 EndDialog();
183 return;
185 Actor *tgt;
186 Scriptable *target;
188 if (targetID!=0xffff) {
189 tgt = GetTarget();
190 target = tgt;
191 } else {
192 //risky!!!
193 target = targetOB;
194 tgt=NULL;
196 if (!target) {
197 printMessage("GameControl","Target gone???",LIGHT_RED);
198 EndDialog();
199 return;
202 Video *video = core->GetVideoDriver();
203 Region vp = video->GetViewport();
204 video->SetMouseEnabled(true);
205 core->timer->SetMoveViewPort( target->Pos.x, target->Pos.y, 0, true );
206 video->MoveViewportTo( target->Pos.x-vp.w/2, target->Pos.y-vp.h/2 );
208 if (choose == (unsigned int) -1) {
209 //increasing talkcount after top level condition was determined
211 int si = dlg->FindFirstState( tgt );
212 if (si<0) {
213 EndDialog();
214 return;
217 if (tgt) {
218 if (core->GetGameControl()->GetDialogueFlags()&DF_TALKCOUNT) {
219 core->GetGameControl()->SetDialogueFlags(DF_TALKCOUNT, BM_NAND);
220 tgt->TalkCount++;
221 } else if (core->GetGameControl()->GetDialogueFlags()&DF_INTERACT) {
222 core->GetGameControl()->SetDialogueFlags(DF_INTERACT, BM_NAND);
223 tgt->InteractCount++;
226 ds = dlg->GetState( si );
227 } else {
228 if (ds->transitionsCount <= choose) {
229 return;
232 DialogTransition* tr = ds->transitions[choose];
234 ta->PopMinRow();
236 if (tr->Flags&IE_DLG_TR_JOURNAL) {
237 int Section = 0;
238 if (tr->Flags&IE_DLG_UNSOLVED) {
239 Section |= 1;
241 if (tr->Flags&IE_DLG_SOLVED) {
242 Section |= 2;
244 if (core->GetGame()->AddJournalEntry(tr->journalStrRef, sectionMap[Section], tr->Flags>>16) ) {
245 displaymsg->DisplayConstantString(STR_JOURNALCHANGE,0xffff00);
246 char *string = core->GetString( tr->journalStrRef );
247 //cutting off the strings at the first crlf
248 char *poi = strchr(string,'\n');
249 if (poi) {
250 *poi='\0';
252 displaymsg->DisplayString( string );
253 free( string );
257 if (tr->textStrRef != 0xffffffff) {
258 //allow_zero is for PST (deionarra's text)
259 displaymsg->DisplayStringName( (int) (tr->textStrRef), 0x8080FF, speaker, IE_STR_SOUND|IE_STR_SPEECH|IE_STR_ALLOW_ZERO);
260 if (core->HasFeature( GF_DIALOGUE_SCROLLS )) {
261 ta->AppendText( "", -1 );
265 if (tr->actions.size()) {
266 // does this belong here? we must clear actions somewhere before
267 // we start executing them (otherwise queued actions interfere)
268 // executing actions directly does not work, because dialog
269 // needs to end before final actions are executed due to
270 // actions making new dialogs!
271 if (target->Type == ST_ACTOR) ((Movable *)target)->ClearPath(); // fuzzie added this
272 target->ClearActions();
274 for (unsigned int i = 0; i < tr->actions.size(); i++) {
275 target->AddAction(tr->actions[i]);
276 //GameScript::ExecuteAction( target, action );
280 int final_dialog = tr->Flags & IE_DLG_TR_FINAL;
282 if (final_dialog) {
283 ta->SetMinRow( false );
284 EndDialog();
287 // *** the commented-out line here should no longer be required, with instant handling ***
288 // all dialog actions must be executed immediately
289 //target->ProcessActions(true);
290 // (do not clear actions - final actions can involve waiting/moving)
292 if (final_dialog) {
293 return;
296 //displaying dialog for selected option
297 int si = tr->stateIndex;
298 //follow external linkage, if required
299 if (tr->Dialog[0] && strnicmp( tr->Dialog, dlg->ResRef, 8 )) {
300 //target should be recalculated!
301 tgt = NULL;
302 if (originalTargetID) {
303 // always try original target first (sometimes there are multiple
304 // actors with the same dialog in an area, we want to pick the one
305 // we were talking to)
306 tgt = GetActorByGlobalID(originalTargetID);
307 if (tgt && strnicmp( tgt->GetDialog(GD_NORMAL), tr->Dialog, 8 ) != 0) {
308 tgt = NULL;
311 if (!tgt) {
312 // then just search the current area for an actor with the dialog
313 tgt = target->GetCurrentArea()->GetActorByDialog(tr->Dialog);
315 if (!tgt) {
316 // try searching for banter dialogue: the original engine seems to
317 // happily let you randomly switch between normal and banter dialogs
319 // TODO: work out if this should go somewhere more central (such
320 // as GetActorByDialog), or if there's a less awful way to do this
321 // (we could cache the entries, for example)
322 // TODO: fix for ToB (see also the Interact action)
323 AutoTable pdtable("interdia");
324 if (pdtable) {
325 int row = pdtable->FindTableValue( pdtable->GetColumnIndex("FILE"), tr->Dialog );
326 tgt = target->GetCurrentArea()->GetActorByScriptName(pdtable->GetRowName(row));
329 target = tgt;
330 if (!target) {
331 printMessage("Dialog","Can't redirect dialog\n",YELLOW);
332 ta->SetMinRow( false );
333 EndDialog();
334 return;
336 Actor *oldTarget = GetActorByGlobalID(targetID);
337 targetID = tgt->globalID;
338 tgt->SetCircleSize();
339 if (oldTarget) oldTarget->SetCircleSize();
340 // we have to make a backup, tr->Dialog is freed
341 ieResRef tmpresref;
342 strnlwrcpy(tmpresref,tr->Dialog, 8);
343 if (target->GetInternalFlag()&IF_NOINT) {
344 // this whole check moved out of InitDialog by fuzzie, see comments
345 // for the IF_NOINT check in BeginDialog
346 displaymsg->DisplayConstantString(STR_TARGETBUSY,0xff0000);
347 ta->SetMinRow( false );
348 EndDialog();
349 return;
351 int ret = InitDialog( speaker, target, tmpresref);
352 if (ret<0) {
353 // error was displayed by InitDialog
354 ta->SetMinRow( false );
355 EndDialog();
356 return;
359 ds = dlg->GetState( si );
360 if (!ds) {
361 printMessage("Dialog","Can't find next dialog\n",YELLOW);
362 ta->SetMinRow( false );
363 EndDialog();
364 return;
367 //displaying npc text
368 displaymsg->DisplayStringName( ds->StrRef, 0x70FF70, target, IE_STR_SOUND|IE_STR_SPEECH);
369 //adding a gap between options and npc text
370 ta->AppendText("",-1);
371 int i;
372 int idx = 0;
373 ta->SetMinRow( true );
374 //first looking for a 'continue' opportunity, the order is descending (a la IE)
375 unsigned int x = ds->transitionsCount;
376 while(x--) {
377 if (ds->transitions[x]->Flags & IE_DLG_TR_FINAL) {
378 continue;
380 if (ds->transitions[x]->textStrRef != 0xffffffff) {
381 continue;
383 if (ds->transitions[x]->Flags & IE_DLG_TR_TRIGGER) {
384 if (ds->transitions[x]->condition &&
385 !ds->transitions[x]->condition->Evaluate(target)) {
386 continue;
389 core->GetDictionary()->SetAt("DialogOption",x);
390 core->GetGameControl()->SetDialogueFlags(DF_OPENCONTINUEWINDOW, BM_OR);
391 goto end_of_choose;
393 for (x = 0; x < ds->transitionsCount; x++) {
394 if (ds->transitions[x]->Flags & IE_DLG_TR_TRIGGER) {
395 if (ds->transitions[x]->condition &&
396 !ds->transitions[x]->condition->Evaluate(target)) {
397 continue;
400 idx++;
401 if (ds->transitions[x]->textStrRef == 0xffffffff) {
402 //dialogchoose should be set to x
403 //it isn't important which END option was chosen, as it ends
404 core->GetDictionary()->SetAt("DialogOption",x);
405 core->GetGameControl()->SetDialogueFlags(DF_OPENENDWINDOW, BM_OR);
406 } else {
407 char *string = ( char * ) malloc( 40 );
408 sprintf( string, "[s=%d,ffffff,ff0000]%d - [p]", x, idx );
409 i = ta->AppendText( string, -1 );
410 free( string );
411 string = core->GetString( ds->transitions[x]->textStrRef );
412 ta->AppendText( string, i );
413 free( string );
414 ta->AppendText( "[/p][/s]", i );
417 // this happens if a trigger isn't implemented or the dialog is wrong
418 if (!idx) {
419 printMessage("Dialog", "There were no valid dialog options!\n", YELLOW);
420 core->GetGameControl()->SetDialogueFlags(DF_OPENENDWINDOW, BM_OR);
422 end_of_choose:
423 //padding the rows so our text will be at the top
424 if (core->HasFeature( GF_DIALOGUE_SCROLLS )) {
425 ta->AppendText( "", -1 );
427 else {
428 ta->PadMinRow();
432 // TODO: duplicate of the one in GameControl
433 Actor *DialogHandler::GetActorByGlobalID(ieWord ID)
435 if (!ID)
436 return NULL;
437 Game* game = core->GetGame();
438 if (!game)
439 return NULL;
441 Map* area = game->GetCurrentArea( );
442 if (!area)
443 return NULL;
444 return
445 area->GetActorByGlobalID(ID);
448 Actor *DialogHandler::GetTarget()
450 return GetActorByGlobalID(targetID);
453 Actor *DialogHandler::GetSpeaker()
455 return GetActorByGlobalID(speakerID);