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"
24 #include "DialogMgr.h"
25 #include "DisplayMessage.h"
29 #include "GUI/GameControl.h"
31 DialogHandler::DialogHandler(void)
40 DialogHandler::~DialogHandler(void)
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
)
55 PluginHolder
<DialogMgr
> dm(IE_DLG_CLASS_ID
);
56 dm
->Open( gamedata
->GetResource( dlgref
, IE_DLG_CLASS_ID
), true );
57 dlg
= dm
->GetDialog();
60 printMessage("GameControl", " ", LIGHT_RED
);
61 printf( "Cannot start dialog: %s\n", dlgref
);
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
71 Actor
*spe
= (Actor
*) spk
;
72 speakerID
= spe
->globalID
;
73 Actor
*oldTarget
= GetActorByGlobalID(targetID
);
74 if (tgt
->Type
!=ST_ACTOR
) {
76 //most likely this dangling object reference
77 //won't cause problems, because trigger points don't
78 //get destroyed during a dialog
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
;
90 if (oldTarget
) oldTarget
->SetCircleSize();
92 //check if we are already in dialog
93 if (core
->GetGameControl()->GetDialogueFlags()&DF_IN_DIALOG
) {
97 int si
= dlg
->FindFirstState( tgt
);
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
);
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
) ) {
138 Actor
*tmp
= GetSpeaker();
143 if (targetID
==0xffff) {
144 targetOB
->LeaveDialog();
153 if (tmp
) tmp
->SetCircleSize();
154 originalTargetID
= 0;
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();
174 printMessage("GameControl","Dialog aborted???",LIGHT_RED
);
179 Actor
*speaker
= GetSpeaker();
181 printMessage("GameControl","Speaker gone???",LIGHT_RED
);
188 if (targetID
!=0xffff) {
197 printMessage("GameControl","Target gone???",LIGHT_RED
);
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
);
218 if (core
->GetGameControl()->GetDialogueFlags()&DF_TALKCOUNT
) {
219 core
->GetGameControl()->SetDialogueFlags(DF_TALKCOUNT
, BM_NAND
);
221 } else if (core
->GetGameControl()->GetDialogueFlags()&DF_INTERACT
) {
222 core
->GetGameControl()->SetDialogueFlags(DF_INTERACT
, BM_NAND
);
223 tgt
->InteractCount
++;
226 ds
= dlg
->GetState( si
);
228 if (ds
->transitionsCount
<= choose
) {
232 DialogTransition
* tr
= ds
->transitions
[choose
];
236 if (tr
->Flags
&IE_DLG_TR_JOURNAL
) {
238 if (tr
->Flags
&IE_DLG_UNSOLVED
) {
241 if (tr
->Flags
&IE_DLG_SOLVED
) {
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');
252 displaymsg
->DisplayString( 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
;
283 ta
->SetMinRow( false );
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)
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!
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) {
312 // then just search the current area for an actor with the dialog
313 tgt
= target
->GetCurrentArea()->GetActorByDialog(tr
->Dialog
);
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");
325 int row
= pdtable
->FindTableValue( pdtable
->GetColumnIndex("FILE"), tr
->Dialog
);
326 tgt
= target
->GetCurrentArea()->GetActorByScriptName(pdtable
->GetRowName(row
));
331 printMessage("Dialog","Can't redirect dialog\n",YELLOW
);
332 ta
->SetMinRow( false );
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
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 );
351 int ret
= InitDialog( speaker
, target
, tmpresref
);
353 // error was displayed by InitDialog
354 ta
->SetMinRow( false );
359 ds
= dlg
->GetState( si
);
361 printMessage("Dialog","Can't find next dialog\n",YELLOW
);
362 ta
->SetMinRow( false );
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);
373 ta
->SetMinRow( true );
374 //first looking for a 'continue' opportunity, the order is descending (a la IE)
375 unsigned int x
= ds
->transitionsCount
;
377 if (ds
->transitions
[x
]->Flags
& IE_DLG_TR_FINAL
) {
380 if (ds
->transitions
[x
]->textStrRef
!= 0xffffffff) {
383 if (ds
->transitions
[x
]->Flags
& IE_DLG_TR_TRIGGER
) {
384 if (ds
->transitions
[x
]->condition
&&
385 !ds
->transitions
[x
]->condition
->Evaluate(target
)) {
389 core
->GetDictionary()->SetAt("DialogOption",x
);
390 core
->GetGameControl()->SetDialogueFlags(DF_OPENCONTINUEWINDOW
, BM_OR
);
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
)) {
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
);
407 char *string
= ( char * ) malloc( 40 );
408 sprintf( string
, "[s=%d,ffffff,ff0000]%d - [p]", x
, idx
);
409 i
= ta
->AppendText( string
, -1 );
411 string
= core
->GetString( ds
->transitions
[x
]->textStrRef
);
412 ta
->AppendText( string
, i
);
414 ta
->AppendText( "[/p][/s]", i
);
417 // this happens if a trigger isn't implemented or the dialog is wrong
419 printMessage("Dialog", "There were no valid dialog options!\n", YELLOW
);
420 core
->GetGameControl()->SetDialogueFlags(DF_OPENENDWINDOW
, BM_OR
);
423 //padding the rows so our text will be at the top
424 if (core
->HasFeature( GF_DIALOGUE_SCROLLS
)) {
425 ta
->AppendText( "", -1 );
432 // TODO: duplicate of the one in GameControl
433 Actor
*DialogHandler::GetActorByGlobalID(ieWord ID
)
437 Game
* game
= core
->GetGame();
441 Map
* area
= game
->GetCurrentArea( );
445 area
->GetActorByGlobalID(ID
);
448 Actor
*DialogHandler::GetTarget()
450 return GetActorByGlobalID(targetID
);
453 Actor
*DialogHandler::GetSpeaker()
455 return GetActorByGlobalID(speakerID
);