/** * @file * @author Flugente (bears-pit.com) * @brief This file contains definitions of classes and functions used for dynamic dialogue */ #include "DynamicDialogueWidget.h" #include "WCheck.h" #include "renderworld.h" #include "Font Control.h" #include "Utilities.h" #include "WordWrap.h" #include "Soldier Profile.h" #include "Cursors.h" #include "random.h" #include "faces.h" extern FACETYPE *gpCurrentTalkingFace; // whether we are showing the inventory pool graphic extern BOOLEAN fShowMapInventoryPool; extern void Display2Line2ShadowVertical( UINT16 usStartX, UINT16 usStartY, UINT16 EndX, UINT16 usColor1, UINT16 usColor2 ); extern void Display2Line2ShadowHorizontal( UINT16 usStartX, UINT16 usStartY, UINT16 EndY, UINT16 usColor1, UINT16 usColor2 ); #define MYBOX_NUMBER 10 #define MYBOX_FONT_DEF FONT12ARIAL #define IMPDIALOGUECHOOSEBOX_BAR_Y_OFFSET 20 #define IMPDIALOGUECHOOSEBOX_BAR_MAXLENGTH 500 DDBox::DDBox( UINT8 aID ) : WidgetBase( ) { // default settings musHeight = 0; musWidth = 0; // width of text field swprintf( mText, L"" ); musCreationTime = musEndTime = GetJA2Clock( ); musID = aID; mfInit = FALSE; mfDisplayed = FALSE; mfFaceImageExists = FALSE; } void DDBox::Init( UINT16 sX, UINT16 sY ) { SetX( sX ); SetY( sY ); SetX_Text( GetX( ) + MYBOX_FACE_OFFSET ); SetY_Text( WidgetBase::GetY( ) ); musWidth = min( MYBOX_TEXT_MAXWIDTH, StringPixLength( mText, MYBOX_FONT_DEF ) ); musHeight = IanWrappedStringHeight( GetX_Text( ), GetY_Text( ) + 7, musWidth, 2, MYBOX_FONT_DEF, FONT_BLACK, mText, FONT_MCOLOR_BLACK, FALSE, 0 ); musFontHeight = GetFontHeight( MYBOX_FONT_DEF ); // does not work on init of static objects, as the fonts do not yet exist! musEndTime = musCreationTime = GetJA2Clock( ); musFaceImage = 0; mfInit = TRUE; } void DDBox::Create( UINT16 sX, UINT16 sY ) { Destroy( ); Init( sX, sY ); mfDisplayed = TRUE; } void DDBox::Destroy( ) { if ( !IsInit( ) ) return; DeleteVideoObjectFromIndex( musFaceImage ); musFaceImage = 0; mfInit = FALSE; mfDisplayed = FALSE; mfFaceImageExists = FALSE; SetRenderFlags( RENDER_FLAG_FULL ); Refresh( ); RemoveDDBox( GetID( ) ); } void DDBox::Display( ) { if ( !IsDisplayed( ) ) return; // it is possible that we cannot instantly create the video object. Thus we try until we are sucessful if ( !mfFaceImageExists ) { VOBJECT_DESC VObjectDesc; char sTemp[100]; // Load face file VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE; // IMP faces are stored elsewhere if ( gMercProfiles[mEvent.usSpeaker].Type == PROFILETYPE_IMP ) { sprintf( sTemp, "IMPFACES\\%02d.sti", gMercProfiles[mEvent.usSpeaker].ubFaceIndex ); } else { sprintf( sTemp, "FACES\\%02d.sti", gMercProfiles[mEvent.usSpeaker].ubFaceIndex ); } FilenameForBPP( sTemp, VObjectDesc.ImageFile ); if ( AddVideoObject( &VObjectDesc, &musFaceImage ) ) mfFaceImageExists = TRUE; } if ( mfFaceImageExists ) { HVOBJECT hPixHandle; //Get the merc's face if ( GetVideoObject( &hPixHandle, musFaceImage ) ) { //if the merc is dead, shade the face red if ( IsMercDead( mEvent.usSpeaker ) ) { // if the merc is dead, shade the face red, (to signify that he is dead) hPixHandle->pShades[0] = Create16BPPPaletteShaded( hPixHandle->pPaletteEntry, 255, 55, 55, TRUE ); //set the red pallete to the face SetObjectHandleShade( musFaceImage, 0 ); } //Get and display the mercs face BltVideoObject( FRAME_BUFFER, hPixHandle, 0, GetX( ), GetY( ), VO_BLT_SRCTRANSPARENCY, NULL ); } } //Display the background ColorFillVideoSurfaceArea( FRAME_BUFFER, GetX_Text( ), GetY_Text( ), GetX_Text( ) + musWidth, GetY_Text( ) + musFontHeight, Get16BPPColor( FROMRGB( 0, 0, 0 ) ) ); DrawTopEntry( ); SetRenderFlags( RENDER_FLAG_FULL ); } void DDBox::Refresh( ) { // nothing to do if not initialized if ( !IsInit( ) ) { return; } // if still being displayed else if ( IsDisplayed( ) ) { // if not to be shown yet, hide if ( musCreationTime > GetJA2Clock( ) ) { ; } // outdated -> destroy else if ( musEndTime < GetJA2Clock( ) ) { Destroy( ); } else { Display( ); } } else { Destroy( ); } } UINT16 DDBox::GetY( ) { UINT16 sY = WidgetBase::GetY( ); if ( mEvent.usSide == DOST_POSITION_LEFT && gpCurrentTalkingFace ) sY += 130; return sY; } UINT16 DDBox::GetY_Text( ) { UINT16 sY = WidgetBase::GetY_Text( ); if ( mEvent.usSide == DOST_POSITION_LEFT && gpCurrentTalkingFace ) sY += 130; return sY; } void DDBox::DrawTopEntry( ) { //display the name in the list ColorFillVideoSurfaceArea( FRAME_BUFFER, GetX_Text( ) - 4, GetY_Text( ) - 4, GetX_Text( ) + musWidth + 4, GetY_Text( ) + musHeight + 4, GetColorMarked( ) ); SetFontShadow( NO_SHADOW ); DisplayWrappedString( GetX_Text( ), GetY_Text( ) + 4, musWidth, 2, MYBOX_FONT_DEF, FONT_BLACK, mText, FONT_MCOLOR_BLACK, FALSE, 0 ); SetFontShadow( DEFAULT_SHADOW ); } void DDBox::ResetCreationTime( ) { musCreationTime = GetJA2Clock( ); } // the list of boxes DDBox *gDDBoxList[MYBOX_NUMBER]; BOOLEAN InitMyBoxes( ) { // init the pop up box list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { // set ptr to null gDDBoxList[i] = NULL; } return TRUE; } DDBox* GetDDBox( UINT8 aID ) { // now find this box in the list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL && gDDBoxList[i]->GetID( ) == aID ) { return gDDBoxList[i]; } } // create box DDBox* gDDBox = new DDBox( aID ); if ( gDDBox ) { // attempt to add box to list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] == NULL ) { // found a spot, insert gDDBoxList[i] = gDDBox; return gDDBoxList[i]; } } } return NULL; } BOOLEAN RemoveDDBox( UINT8 aID ) { // now find this box in the list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL && gDDBoxList[i]->GetID( ) == aID ) { gDDBoxList[i]->Destroy( ); MemFree( gDDBoxList[i] ); gDDBoxList[i] = NULL; return TRUE; } } return FALSE; } void RefreshBoxes( ) { // if feature is not on, don't bother with this if ( !gGameExternalOptions.fDynamicDialogue ) return; static UINT32 lasttimenhere = GetJA2Clock( ); // dialogue should only happen in GAME_SCREEN or MAP_SCREEN, at other times it would be too distracting // also halt dialogue if sector inventory is open if ( guiCurrentScreen == GAME_SCREEN || guiCurrentScreen == MAP_SCREEN && !fShowMapInventoryPool ) { // if we are scrolling, don't redraw the boxes as often if (gfScrollPending || gfScrollInertia) { UINT32 timepassednotdisplaying = max(0, GetJA2Clock() - lasttimenhere); if (timepassednotdisplaying < 100) { lasttimenhere = GetJA2Clock(); return; } } // refresh all boxes, and while you're at it, check wether there are any BOOLEAN fFound = FALSE; for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL ) { gDDBoxList[i]->Refresh( ); fFound = TRUE; } } if ( IMPDialogueChooseBox_Static::getInstance( ).IsDisplayed( ) ) { IMPDialogueChooseBox_Static::getInstance( ).Refresh( ); fFound = TRUE; } // if no box is active, we are free to start another dialogue if ( !fFound ) HandleDynamicOpinionSpeechEvents( ); else { // boxes are active, thus we have to refresh them if (guiCurrentScreen == MAP_SCREEN) fMapPanelDirty = TRUE; } } else { // Our dynamic dialogue boxes are destroyed as time progresses. As we do not display them at the moment, we have to update their creation and end time. // As a result, no time will be lost for our dialogues if we are in a different screen UINT32 timepassednotdisplaying = max( 0, GetJA2Clock( ) - lasttimenhere ); if ( timepassednotdisplaying ) DelayBoxDestructionBy( timepassednotdisplaying ); } lasttimenhere = GetJA2Clock( ); } void DelayBoxDestruction( UINT32 aVal ) { // now find this box in the list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL && gDDBoxList[i]->IsDisplayed( ) ) { gDDBoxList[i]->SetEndTime( aVal ); } } } void DelayBoxDestructionBy( INT32 aVal ) { // now find this box in the list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL && gDDBoxList[i]->IsDisplayed( ) ) { gDDBoxList[i]->SetStartTime( gDDBoxList[i]->GetStartTime( ) + aVal ); gDDBoxList[i]->SetEndTime( gDDBoxList[i]->GetEndTime( ) + aVal ); } } if ( IMPDialogueChooseBox_Static::getInstance( ).IsDisplayed( ) ) { IMPDialogueChooseBox_Static::getInstance( ).SetStartTime( IMPDialogueChooseBox_Static::getInstance( ).GetStartTime( ) + aVal ); IMPDialogueChooseBox_Static::getInstance( ).SetEndTime( IMPDialogueChooseBox_Static::getInstance( ).GetEndTime( ) + aVal ); } } IMPDialogueChooseBox::IMPDialogueChooseBox( ) { // default settings musWidth = 0; // width of text field mEntryVector.clear( ); swprintf( mText, L"" ); mSelectedEntry = 0; for ( UINT8 i = 0; i < DOST_CHOICE_MAX; ++i ) mChoiceRegionDefined[i] = FALSE; musCreationTime = musEndTime = GetJA2Clock( ); mfInit = FALSE; mfDisplayed = FALSE; } void IMPDialogueChooseBox::Init( UINT16 sX, UINT16 sY ) { SetX( sX ); SetY( sY ); SetX_Text( GetX( ) + MYBOX_FACE_OFFSET ); SetY_Text( WidgetBase::GetY( ) ); mSelectedEntry = 0; musWidth = 0; UINT16 size = mEntryVector.size( ); for ( UINT16 i = 0; i < size; ++i ) { musWidth = max( musWidth, StringPixLength( mEntryVector[i].second, MYBOX_FONT_DEF ) ); } musFontHeight = GetFontHeight( MYBOX_FONT_DEF ); // does not work on init of static objects, as the fonts do not yet exist! musCreationTime = GetJA2Clock( ); musEndTime = musCreationTime; mfInit = TRUE; } void IMPDialogueChooseBox::Create( UINT16 sX, UINT16 sY ) { Destroy( ); Init( sX, sY ); sY += IMPDIALOGUECHOOSEBOX_BAR_Y_OFFSET; UINT16 size = mEntryVector.size( ); for ( UINT16 i = 0; i < size; ++i ) { if ( !mChoiceRegionDefined[i] ) { MSYS_DefineRegion( &mChoiceRegion[i], GetX( ) - 4, sY, GetX( ) + musWidth + 4, sY + musFontHeight, MSYS_PRIORITY_HIGH, CURSOR_WWW, MSYS_NO_CALLBACK, CallBackWrapper( (void*) this, DROPDOWN_REGION, &IMPDialogueChooseBox::Dummyfunc ) ); MSYS_AddRegion( &mChoiceRegion[i] ); MSYS_SetRegionUserData( &mChoiceRegion[i], 0, i ); mChoiceRegionDefined[i] = TRUE; } sY += musFontHeight + 2; } mfDisplayed = TRUE; } void IMPDialogueChooseBox::Destroy( ) { if ( !IsInit( ) ) return; for ( UINT8 i = 0; i < DOST_CHOICE_MAX; ++i ) { if ( mChoiceRegionDefined[i] ) { MSYS_RemoveRegion( &mChoiceRegion[i] ); mChoiceRegionDefined[i] = FALSE; } } mfInit = FALSE; mfDisplayed = FALSE; SetRenderFlags( RENDER_FLAG_FULL ); Refresh( ); } void IMPDialogueChooseBox::Display( ) { if ( !IsDisplayed( ) ) return; // we draw a line that shows us how much time we have for the decision UINT32 totaltime = max( 0, musEndTime - musCreationTime ); if ( totaltime ) { UINT32 timeleft = musEndTime - GetJA2Clock( ); FLOAT factor = (FLOAT)timeleft / (FLOAT)totaltime; UINT32 maxwidth = min( IMPDIALOGUECHOOSEBOX_BAR_MAXLENGTH, SCREEN_WIDTH / 2 ); UINT16 colour = Get16BPPColor( FROMRGB( 255 * (1 - factor), 255 * factor, 0 ) ); UINT16 colourshadow = Get16BPPColor( FROMRGB( 127 * (1 - factor), 127 * factor, 0 ) ); Display2Line2ShadowHorizontal( GetX_Text( ) + maxwidth / 2 - factor * maxwidth / 2, GetY( ), GetX_Text( ) + maxwidth / 2 + factor * maxwidth / 2, colour, colourshadow ); } DrawTopEntry( ); SetRenderFlags( RENDER_FLAG_FULL ); } void IMPDialogueChooseBox::Refresh( ) { // nothing to do if not initialized if ( !IsInit( ) ) { return; } // if still being displayed else if ( IsDisplayed( ) ) { // if not to be shown yet, hide if ( musCreationTime > GetJA2Clock( ) ) { ; } // outdated -> destroy else if ( musEndTime < GetJA2Clock( ) ) { TimeOut( ); Destroy( ); } else { Display( ); } } else { Destroy( ); } } void IMPDialogueChooseBox::DrawTopEntry( ) { // make sure we don't get bogus values mSelectedEntry = min( mSelectedEntry, mEntryVector.size( ) - 1 ); UINT16 sY = GetY( ) + IMPDIALOGUECHOOSEBOX_BAR_Y_OFFSET; UINT16 cnt = 0; std::vector >::iterator itend = mEntryVector.end( ); for ( std::vector >::iterator it = mEntryVector.begin( ); it != itend; ++it ) { //display the name in the list ColorFillVideoSurfaceArea( FRAME_BUFFER, GetX( ) - 4, sY, GetX( ) + musWidth + 4, sY + musFontHeight, cnt == mSelectedEntry ? GetColorHighLight( ) : GetColorMarked( ) ); SetFontShadow( NO_SHADOW ); DrawTextToScreen( (*it).second, GetX( ), sY, 0, MYBOX_FONT_DEF, cnt == mSelectedEntry ? FONT_DKGREEN : FONT_BLACK, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); SetFontShadow( DEFAULT_SHADOW ); sY += musFontHeight + 2; ++cnt; } } void IMPDialogueChooseBox::SelectDropDownRegionCallBack( MOUSE_REGION * pRegion, INT32 iReason ) { if ( iReason & MSYS_CALLBACK_REASON_INIT ) { } else if ( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { mSelectedEntry = (UINT16)MSYS_GetRegionUserData( pRegion, 0 ); } } void IMPDialogueChooseBox::ResetCreationTime( ) { musCreationTime = GetJA2Clock( ); } void IMPDialogueChooseBox::TimeOut( ) { // if we are destroyed, we set up dialogues according to the players choices RemoveDDBox( mEvent.usSpeaker ); DynamicOpinionSpeechEvent event = mEvent; switch ( mSelectedEntry ) { case DOST_CHOICE_NOTHING: event.ubEventType = DOST_INTERJECTOR_NOTHING; break; // if we side with the victim or the cause, we either support them or take a stand against the other // for now, the choice is random case DOST_CHOICE_SIDEWITH_VICTIM: if ( Random( 2 ) ) event.ubEventType = DOST_INTERJECTOR_TO_VICTIM_AGREE; else event.ubEventType = DOST_INTERJECTOR_TO_CAUSE_DENY; event.usSide = DOST_POSITION_LEFT; break; case DOST_CHOICE_SIDEWITH_CAUSE: if ( Random( 2 ) ) event.ubEventType = DOST_INTERJECTOR_TO_CAUSE_AGREE; else event.ubEventType = DOST_INTERJECTOR_TO_VICTIM_DENY; event.usSide = DOST_POSITION_RIGHT; break; case DOST_CHOICE_REASON: event.ubEventType = DOST_INTERJECTOR_SOLVE_REASON; break; case DOST_CHOICE_AGGRESSIVE: event.ubEventType = DOST_INTERJECTOR_SOLVE_AGGRESSIVE; break; } event.usQueueNumber++; event.usStarttime = GetJA2Clock( ); event.usNumonside = (UINT8)GetSidePosition( event.usSide ); AddDynamicOpinionEvent_Continue( event ); } void* IMPDialogueChooseBox_Static::mpSelf = NULL; // are there still dynamic dialogue boxes active? We use this to check wether we can start new dialogue BOOLEAN DynamicDialogueBoxesActive( ) { // now find this box in the list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL ) return TRUE; } if ( IMPDialogueChooseBox_Static::getInstance( ).IsInit( ) ) return TRUE; return FALSE; } // used fo example when we load a game - dialogue has to start anew, so all boxes must be wiped void DestroyAllDynamicDialogueBoxes( ) { // now find this box in the list for ( UINT8 i = 0; i < MYBOX_NUMBER; ++i ) { if ( gDDBoxList[i] != NULL ) { gDDBoxList[i]->Destroy( ); MemFree( gDDBoxList[i] ); gDDBoxList[i] = NULL; } } IMPDialogueChooseBox_Static::getInstance( ).Destroy( ); }