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 //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)
42 if (core
->HasFeature(GF_JOURNAL_HAS_SECTIONS
) ) {
43 memcpy(sectionMap
, bg2Sections
, sizeof(sectionMap
) );
45 memcpy(sectionMap
, noSections
, sizeof(sectionMap
) );
49 DialogHandler::~DialogHandler(void)
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
)
64 PluginHolder
<DialogMgr
> dm(IE_DLG_CLASS_ID
);
65 dm
->Open( gamedata
->GetResource( dlgref
, IE_DLG_CLASS_ID
), true );
66 dlg
= dm
->GetDialog();
69 printMessage("GameControl", " ", LIGHT_RED
);
70 printf( "Cannot start dialog: %s\n", dlgref
);
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
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
;
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 Scriptable
*tmp2
= GetTarget();
144 if (tmp2
&& tmp2
->Type
== ST_ACTOR
) {
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
);
168 void DialogHandler::DialogChoose(unsigned int choose
)
170 TextArea
* ta
= core
->GetMessageTextArea();
172 printMessage("GameControl","Dialog aborted???",LIGHT_RED
);
177 Actor
*speaker
= GetSpeaker();
179 printMessage("GameControl","Speaker gone???",LIGHT_RED
);
184 Scriptable
*target
= GetTarget();
186 printMessage("GameControl","Target gone???",LIGHT_RED
);
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
);
211 if (core
->GetGameControl()->GetDialogueFlags()&DF_TALKCOUNT
) {
212 core
->GetGameControl()->SetDialogueFlags(DF_TALKCOUNT
, BM_NAND
);
214 } else if (core
->GetGameControl()->GetDialogueFlags()&DF_INTERACT
) {
215 core
->GetGameControl()->SetDialogueFlags(DF_INTERACT
, BM_NAND
);
216 tgt
->InteractCount
++;
219 ds
= dlg
->GetState( si
);
221 if (ds
->transitionsCount
<= choose
) {
225 DialogTransition
* tr
= ds
->transitions
[choose
];
229 if (tr
->Flags
&IE_DLG_TR_JOURNAL
) {
231 if (tr
->Flags
&IE_DLG_UNSOLVED
) {
234 if (tr
->Flags
&IE_DLG_SOLVED
) {
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');
245 displaymsg
->DisplayString( 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
;
276 ta
->SetMinRow( false );
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)
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!
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) {
309 // then just search the current area for an actor with the dialog
310 tgt
= target
->GetCurrentArea()->GetActorByDialog(tr
->Dialog
);
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");
322 int row
= pdtable
->FindTableValue( pdtable
->GetColumnIndex("FILE"), tr
->Dialog
);
323 tgt
= target
->GetCurrentArea()->GetActorByScriptName(pdtable
->GetRowName(row
));
328 printMessage("Dialog","Can't redirect dialog\n",YELLOW
);
329 ta
->SetMinRow( false );
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
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 );
348 int ret
= InitDialog( speaker
, target
, tmpresref
);
350 // error was displayed by InitDialog
351 ta
->SetMinRow( false );
356 ds
= dlg
->GetState( si
);
358 printMessage("Dialog","Can't find next dialog\n",YELLOW
);
359 ta
->SetMinRow( false );
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);
370 ta
->SetMinRow( true );
371 //first looking for a 'continue' opportunity, the order is descending (a la IE)
372 unsigned int x
= ds
->transitionsCount
;
374 if (ds
->transitions
[x
]->Flags
& IE_DLG_TR_FINAL
) {
377 if (ds
->transitions
[x
]->textStrRef
!= 0xffffffff) {
380 if (ds
->transitions
[x
]->Flags
& IE_DLG_TR_TRIGGER
) {
381 if (ds
->transitions
[x
]->condition
&&
382 !ds
->transitions
[x
]->condition
->Evaluate(target
)) {
386 core
->GetDictionary()->SetAt("DialogOption",x
);
387 core
->GetGameControl()->SetDialogueFlags(DF_OPENCONTINUEWINDOW
, BM_OR
);
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
)) {
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
);
404 char *string
= ( char * ) malloc( 40 );
405 sprintf( string
, "[s=%d,ffffff,ff0000]%d - [p]", x
, idx
);
406 i
= ta
->AppendText( string
, -1 );
408 string
= core
->GetString( ds
->transitions
[x
]->textStrRef
);
409 ta
->AppendText( string
, i
);
411 ta
->AppendText( "[/p][/s]", i
);
414 // this happens if a trigger isn't implemented or the dialog is wrong
416 printMessage("Dialog", "There were no valid dialog options!\n", YELLOW
);
417 core
->GetGameControl()->SetDialogueFlags(DF_OPENENDWINDOW
, BM_OR
);
420 //padding the rows so our text will be at the top
421 if (core
->HasFeature( GF_DIALOGUE_SCROLLS
)) {
422 ta
->AppendText( "", -1 );
429 // TODO: duplicate of the one in GameControl
430 Actor
*DialogHandler::GetActorByGlobalID(ieDword ID
)
434 Game
* game
= core
->GetGame();
438 Map
* area
= game
->GetCurrentArea( );
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
);
469 Actor
*DialogHandler::GetSpeaker()
471 return GetActorByGlobalID(speakerID
);