Dialogue Question...

Grab your favourite IDE and tinker with the innards of game engines

Re: Dialogue Question...

Postby zombie@computer on Tue Nov 29, 2011 9:39 pm

Hmm, idea.

I propose the following:

I'll create a dialog system for you (already done it before, so i know what to do). I'll also post it as tutorial on the VERC because the question comes round ever so often. In return, you donate a wee bit of money to interlopers once you get what you want (ill leave the amount up to you).

Deal?

ps: Just this dialog system, im already involved in too many coding projects. Also, i dont do graphic wonders so youll need to provide any markup/images/layout preferences.
When you are up to your neck in shit, keep your head up high
zombie@computer
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Fri Dec 31, 2004 5:58 pm
Location: Lent, Netherlands

Re: Dialogue Question...

Postby Gary on Tue Nov 29, 2011 9:53 pm

Sounds good to me, Z@C will make a far better system than I could.

I'm still gonna make one because I want the experience points. My system won't be released unless someone wants it(though in comparison with Z@C's, I don't see why anyone would).
Have a question related to modding or something I posted? Something that needs staff attention? I haven't been active lately, but feel free to PM me or message me on Steam(link below)

User avatar
Gary
Interlopers Staff
Interlopers Staff
 
Joined: Wed Dec 16, 2009 12:40 am
Location: USA, FL

Re: Dialogue Question...

Postby Armageddon on Tue Nov 29, 2011 10:55 pm

Yay, can't wait to see it, if this guy wants to donate to 'lopers.
User avatar
Armageddon
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Sun Dec 14, 2008 5:53 am

Re: Dialogue Question...

Postby GodAndAirborne on Tue Nov 29, 2011 11:40 pm

Gentlemen, of course I will be more than happy to donate to Interlopers.net. Show me what you got and how it works, and upon completion of my MOD all of you get a shout-out in both the ReadMe & movie credits, and Interlopers.net gets $50, maybe $75 (...My total budget is $150, and I still need a skinner and a mapper).

Edit: Allow me to correct myself: Once the dialogue system is completed and I test it out, then I'll gladly donate the $50-75 to Interlopers. There's no point in waiting for my mod to be completed before payment.
User avatar
GodAndAirborne
Member
Member
 
Joined: Mon Nov 28, 2011 11:17 pm

Re: Dialogue Question...

Postby zombie@computer on Tue Dec 06, 2011 8:02 pm

This post will explain how you can add a dialogue system to your singleplayer modification. Took some time, but i upgraded my pc inbetween and ran into all kinds of semi- and non-related problems. The code below may contain errors, i didn't re-check on a new empty codebase.

Difficulty: Easy (you will need to know how to program though)
ModBase: HL2 episode 2

Acknowledgements:
Inspired by viewtopic.php?f=25&t=35585
Keyvalue setup inspired by ChromeAngel: viewtopic.php?f=25&t=35585#p467236

The setup:
The dialog is set up in a keyvalue script, which goes in your resource folder. Here's an example setup:
Code: Select all
dialogues
{
   TalkWithAlyx //This is a dialog*
   {
      choiceset //This is a set of choices*
      {
         option1 //This is an option name*
         {
            "Text" "Hi, i'm someone" //The text to show in game**
            "wave" "NPC_Antlion.TrappedMetal" //A sound to play when this option is selected ***
            "output" 1 //# output (see text below)
            "next" "alyxLovesU" //The next choiceset to go to (empty means: exit dialog)
         }
         option2
         {
            "Text" "Hi, i'm nobody"
            "wave" "/wav"
            "output" 2
            "next" "alyxKnowsU"
         }
      }
      alyxLovesU
      {
         option1
         {
            "Text" "Marry me!"
            "wave" "/wav"
            "next" ""
         }
         option2
         {
            "Text" "Hi, i'm nobody"
            "wave" "/wav"
            "output" 2
            "next" "alyxKnowsU"
         }
         option3
         {
            "Text" "Bambino???"
         }
      }
      alyxKnowsU
      {
         option1
         {
            "Text" "But, but...!"
            "wave" "/wav"
            "next" ""
         }
         option2
         {
            "Text" "Bye!"
            "wave" "/wav"
            "next" ""
         }
      }
   }
}
*name can be anything, but CANNOT have spaces or the '#' symbol (EVER) and must be < 128 characters long. Names have no other purpose than to create unique identifiers
**Text can contain " by using escape signs ('\"'): Note: use '\\' to display '\'. Newlines/Enters are not supported
***Wave refers to soundscripts (eg npc_alyx.whine). Unfortunately source doesn't really want you to 'just' play wave-files. Use forward slashes as path separator.

The dialog starts with the first optionset and follows through the dialog. If the 'output' value is set, a certain output of the ingame entity will be fired. If the 'wave' value is set, a sound will be played (and the dialog will halt untill that sound has stopped playing)

Begin by creating a dialog like above and save it to [mod]/resources/dialogentries.txt.

Loading the dialogs
Both the server and client need to load the above dialogtree from disk, so we are going to need some shared code. Add the following code to both client.dll and server.dll projects

dialogShared.cpp
Code: Select all
#include "cbase.h"
#include "filesystem.h"
#include "DialogShared.h"
//for ansi-to-unicode
#include "vgui/ILocalize.h"
#include "tier3/tier3.h"
#include <KeyValues.h>

CDialogEntry::CDialogEntry(const char* dialogName, const char* convosetName, KeyValues* source)
{
#if !defined CLIENT_DLL
   //The server needs the data for which sound to play...
   const char *wave = source->GetString("wave");
   if (strlen(wave)==0)
      Wave = NULL;
   else
   {
      Wave = new char[strlen(wave)+1];
      strcpy(Wave, wave);
   }
   //What output to send...
   Output = source->GetInt("output", 0);
   //What the next step in the dialog will be....
   const char *next = source->GetString("next");
   if ( strlen(next)==0 )
      Next = NULL;
   else
   {
      if (strstr(next,"#")!= NULL)
         Warning("The # character is not allowed in dialog entry names");
      Next = new char[strlen(dialogName) + strlen(next) + 2];
      strcpy(Next, dialogName);
      strcat(Next, "#");
      strcat(Next, next);
   }
#else
   //The client just needs to know the text it needs to draw
   const char *text = source->GetString("text");
   Text = new wchar_t[strlen(text)+1];
   g_pVGuiLocalize->ConvertANSIToUnicode( text, Text, sizeof(wchar_t) * (1+strlen(text)));
#endif
   const char *name = source->GetName();
   Name = new char[ strlen(name) + strlen(convosetName) + strlen(dialogName) + 3];
   strcpy(Name, dialogName);
   strcat(Name, "#");
   strcat(Name, convosetName);
   strcat(Name, "#");
   strcat(Name, name);
}
CDialogEntry::~CDialogEntry()
{
#if !defined CLIENT_DLL
   delete [] Name;
   delete [] Wave;
   delete [] Next;
#else
   delete [] Text;
#endif
};

DialogMgr::DialogMgr() : CAutoGameSystem("DialogManager")
{
   g_pDialogMgr = this;
   ActiveDialog = NULL;
}
void DialogMgr::LevelInitPreEntityAllSystems( char const* pMapName )
{
   ActiveDialog = NULL;
}

bool DialogMgr::Init()
{
   //Load the dialog entries from file
   KeyValues* kv = new KeyValues("");
   kv->UsesEscapeSequences(true);
   kv->LoadFromFile(filesystem, "resource/dialogentries.txt");
   if ( !kv )
   {
      Warning( "Unable to load dialogentries.txt\n" );
      return true;
   }
   //loop all subkeys/dialogs
   for(KeyValues *convo = kv->GetFirstSubKey(); convo; convo = convo->GetNextKey() )
   {
      //loop all subkeys/choicesets
      for ( KeyValues *choiceset = convo->GetFirstSubKey(); choiceset; choiceset = choiceset->GetNextKey() )
      {
         //loop entries in these sets
         for( KeyValues * convoentry = choiceset->GetFirstSubKey(); convoentry; convoentry = convoentry->GetNextKey() )
         {
            dialogEntries.AddToTail( new CDialogEntry(convo->GetName(), choiceset->GetName(), convoentry) );
         }
      }
   }
   kv->deleteThis();
   return true;
}

//GetDialogEntryByName: returns an entry by name or NULL of not found (or none present)
//dont call me with NULL...
CDialogEntry* DialogMgr::GetDialogEntriesByName(const char* name, CDialogEntry* previous)
{
   //if there are no entries present, (re)try to load them from the file
   if (dialogEntries.Count()==0)
      return NULL;
   //Iterate all items, starting at 'previous'
   int lengthOfName = strlen(name);
   for(int x=(previous == NULL ? 0 : dialogEntries.Find(previous)+1); x<dialogEntries.Count();x++)
   {
      if (strncmp(name, dialogEntries[x]->Name, lengthOfName) == 0)
      {
         return dialogEntries[x];
      }
   }
   //No entries found with this name!
   return NULL;
}

static DialogMgr s_DialogMgr;
DialogMgr *g_pDialogMgr = &s_DialogMgr;

dialogShared.h
Code: Select all
#include "cbase.h"

class CDialogEntry
{
public:
   char* Name;
#if !defined CLIENT_DLL
   int Output;
   char* Next;
   char* Wave;
#else
   wchar_t* Text;
#endif
   CDialogEntry(const char* dialogName, const char* convosetName, KeyValues* source);
   ~CDialogEntry();
};

class DialogMgr : public CAutoGameSystem
{
   CUtlVector<CDialogEntry*> dialogEntries;
   void LoadDialogEntries();

public:
   CBaseEntity* ActiveDialog;

   bool Init();
   void LevelInitPreEntityAllSystems( char const* pMapName );

   DialogMgr();
   ~DialogMgr() { dialogEntries.PurgeAndDeleteElements(); }

   CDialogEntry* GetDialogEntriesByName(const char* name,  CDialogEntry* previous = NULL);
};

extern DialogMgr* g_pDialogMgr;

Explanation: A CAutoGameSystem is a class that is constructed as the engine starts and is removed when the game ends, which is the ideal location for loading the dialogtree (which doesn't change, so only needs to be loaded once for all dialog entities in your mod). The class simply loads the keyvalues tree and stores it as a single Vector. This class also keeps track of which dialog entity is active.

The entity
Now we got the dialog in memory, but we still can't access it! We need an entity to trigger the dialog, and some kind of panel to show the dialog options and allow us to select one.

The entity is quite simple ( add to server project)
logic_dialog.cpp
Code: Select all
#include "cbase.h"
#include "DialogShared.h"
#include "engine/IEngineSound.h"

class CLogicDialog;

//up this if you want to have more outputs linked from your dialog tree, but mind you also need to alter the DATADESC
#define NUM_OUTPUTS 8

class CLogicDialog : public CLogicalEntity
{
   DECLARE_CLASS (CLogicDialog, CLogicalEntity);
   DECLARE_DATADESC();

   //fired when player exits the dialog manually without selecting a convo
   COutputEvent   m_hOnAbort;
   //A total of eight outputs you can trigger from your dialog :)
   COutputEvent    m_hOutputs [NUM_OUTPUTS];

   //Gonna remember if i'm active, so i can restore the dialog after a save-load
   bool m_bIsActive;
   //dialogentry as set in hammer
   char* m_strDialogEntry;
   //active entry
   char* m_strActiveDialogEntry;

   void ShowDialog()
   {
      CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
      if (!pPlayer) return;
      g_pDialogMgr->ActiveDialog = this;
      m_bIsActive = true;
      CDialogEntry *e = g_pDialogMgr->GetDialogEntriesByName(m_strActiveDialogEntry);
      if (!FStrEq(m_strActiveDialogEntry, "disabled") && !e)
      {
         Warning("Unknown dialog entry '%s', cannot init dialogue\n", m_strActiveDialogEntry);
         HideDialog();//in case it was already open
      }
      else
      {
         CSingleUserRecipientFilter filter(pPlayer);
         UserMessageBegin(filter, "InitDialogue" );
         //the full name of e will be the exact entry, but we want to show the option collection instead
         //the full name is ConversationName#optioncollection#optionname, so we need to write the substring 'ConversationName#optioncollection'
         if (!e)
            WRITE_STRING(m_strActiveDialogEntry);
         else
         {
            char buff [257];//2x MAX_KEY+1
            strcpy(buff, e->Name);
            for(int x = strlen(buff)-1;x>=0;x--)
            {
               if (buff[x] == '#')
               {
                  buff[x] = '\0';
                  break;
               }
            }
            WRITE_STRING(buff);
         }
         MessageEnd();
      }
   }
   void HideDialog()
   {
      CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
      if (!pPlayer) return;
      g_pDialogMgr->ActiveDialog = NULL;
      m_bIsActive = true;
   
      CSingleUserRecipientFilter filter(pPlayer);
      UserMessageBegin(filter, "InitDialogue" );
      WRITE_STRING("");//empty string means: NO dialog
      MessageEnd();
   }
public:
   void OnRestore()
   {
      if (m_bIsActive)
      {
         ShowDialog();
      }
   }
   void ShowDialog( inputdata_t &inputData)
   {
      if (m_strActiveDialogEntry)
      {
         delete [] m_strActiveDialogEntry;
      }
      m_strActiveDialogEntry = new char[strlen(m_strDialogEntry)+1];
      strcpy(m_strActiveDialogEntry, m_strDialogEntry);
      ShowDialog();
   }
   void Precache()
   {
      //Make sure we've precached all sounds we are going to use
      for(CDialogEntry* pEntry = g_pDialogMgr->GetDialogEntriesByName( m_strDialogEntry ); pEntry; pEntry = g_pDialogMgr->GetDialogEntriesByName( m_strDialogEntry, pEntry ) )
      {
         if (pEntry->Wave)
            PrecacheScriptSound(pEntry->Wave);
      }
   }
   void Spawn()
   {
      BaseClass::Spawn();
      Precache();
   }
   //Abort the dialog
   void AbortDialog(inputdata_t &inputData)
   {
      HideDialog();
      m_hOnAbort.FireOutput(this, inputData.pCaller);
   }
   void Think()
   {
      if (m_strActiveDialogEntry == NULL)
      {
         HideDialog();
      }
      else
      {
         ShowDialog();
      }
      BaseClass::Think();
   }
   void PlayerChose(const char* chosenOption)
   {
      if (!chosenOption) //user aborted :)
      {
         HideDialog();
         m_hOnAbort.FireOutput(this, this);
      }
      else
      {
         //the chosen entry MUST be a sub-entry of the active dialog set!
         CDialogEntry *pEntry = g_pDialogMgr->GetDialogEntriesByName(chosenOption);

         if (strncmp(chosenOption, m_strActiveDialogEntry, strlen(m_strActiveDialogEntry)) || !pEntry)
         {
            //invalid choice. Player probably cheated the system!
            DevMsg("Player chose invalid dialog entry");
            return;
         }
         //is the 'output' variable set and valid? Then fire that output
         if (pEntry->Output != -1 && pEntry->Output < NUM_OUTPUTS)
            m_hOutputs[pEntry->Output].FireOutput(this, this);

         if (m_strActiveDialogEntry)
            delete [] m_strActiveDialogEntry;

         //if 'next' was set, play that dialogue. Else, simply exit. But wait untill the sound stopped playing!
         if (pEntry->Next != NULL)
         {
            m_strActiveDialogEntry = new char[strlen(pEntry->Next)+1];
            strcpy(m_strActiveDialogEntry, pEntry->Next);
         }
         else
         {
            m_strActiveDialogEntry = NULL;
         }
         //play the sound if it was set
         if (pEntry->Wave)
         {
            //think as soon as the wave stopped playing, to continue the dialogue
            EmitSound( pEntry->Wave );
            float duration = enginesound->GetSoundDuration(pEntry->Wave);
            //if the sound is invalid, duration is null, so think gets called immediately. otherwise, wait xxx seconds till pEntry->Wave is done playing
            SetNextThink(gpGlobals->curtime + duration);
         }
         else //no sound defined, so just exit/continue
         {
            if (m_strActiveDialogEntry == NULL)
            {
               HideDialog();
            }
            else
            {
               ShowDialog();
            }
         }
      }
   }
};

BEGIN_DATADESC(CLogicDialog)
   DEFINE_KEYFIELD( m_strDialogEntry, FIELD_STRING, "dialogentry"),

   DEFINE_FIELD( m_strActiveDialogEntry, FIELD_STRING ),
   DEFINE_FIELD( m_bIsActive, FIELD_BOOLEAN ),

   DEFINE_OUTPUT( m_hOutputs[0], "Output0" ),
   DEFINE_OUTPUT( m_hOutputs[1], "Output1" ),
   DEFINE_OUTPUT( m_hOutputs[2], "Output2" ),
   DEFINE_OUTPUT( m_hOutputs[3], "Output3" ),
   DEFINE_OUTPUT( m_hOutputs[4], "Output4" ),
   DEFINE_OUTPUT( m_hOutputs[5], "Output5" ),
   DEFINE_OUTPUT( m_hOutputs[6], "Output6" ),
   DEFINE_OUTPUT( m_hOutputs[7], "Output7" ),

   DEFINE_OUTPUT(m_hOnAbort, "OnAbort"),

   DEFINE_INPUTFUNC( FIELD_VOID, "ShowDialog", ShowDialog),
   DEFINE_INPUTFUNC( FIELD_VOID, "AbortDialog", AbortDialog),

END_DATADESC()

LINK_ENTITY_TO_CLASS(logic_dialog, CLogicDialog);

void CC_SelectDialogue(const CCommand& args)
{
   if (g_pDialogMgr->ActiveDialog != NULL)
   {
      ((CLogicDialog*)g_pDialogMgr->ActiveDialog)->PlayerChose(args.ArgC() == 2 ? args.Arg(1) : NULL);
   }
}

static ConCommand SelectDialogue("select_dialogue", CC_SelectDialogue, "Select dialogue option <index>", FCVAR_CHEAT );

The entity uses usermessages to control a panel (see below) to show the dialog options. That dialog panel, in turn, uses the console command above to control the dialog entity (to advance the dialog). Note it's marked as CHEAT, and also it's input is checked so players can't cheat their way through your boring dialogues!

This entity also takes care of precaching all soundscripts. The code may appear confusing, but you can go through it line-by-line if you'd like. It's complexity comes from the facts that 1) there is no direct way to control the (clientside)panel from the (serverside) entity and 2) we need a lot of 'what-if' code (e.g. player saves and loads during a conversation: it shouldn't just restart!)

The dialog panel
The dialog panel is very simple as can be seen below. Through the usermessages sent by logic_dialog it can show a list of options, disappear or appear disabled (which will be the case after a player selected an option and is waiting for the option-linked sound to stop playing)

huddialogue.cpp (client)
Code: Select all
#include "cbase.h"
#include "hud.h"
#include "hud_macros.h"
#include "iclientmode.h"
#include <vgui/ISurface.h>
#include "hudelement.h"
#include "DialogShared.h"
#include <vgui/IInput.h>
#include <vgui_controls/Panel.h>

class CHudDialogue : public CHudElement, public vgui::Panel
{
   DECLARE_CLASS_SIMPLE( CHudDialogue, vgui::Panel  );
   CUtlVector<CDialogEntry*> ActiveEntries;
   bool ResetHeight;
   
   bool DialogueVisible;

   vgui::HFont m_hFont;
   vgui::HScheme scheme;

public:
   void LevelInit()
   {
      SetEnabled(false);
      DialogueVisible = false;//I'd use SetVisible() but Valve's code has a nasty habit of not working correctly. Or at all.
      SetMouseInputEnabled(false);
      SetKeyBoardInputEnabled(false);
   }
   void Paint()
   {
      if (!DialogueVisible) return;
      int fntTall = vgui::surface()->GetFontTall(m_hFont) + 4;
      C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
       if ( !pPlayer )
          return;
      if (ResetHeight)
      {
         SetTall(20 + (fntTall * ActiveEntries.Count()));
         int x, y;
         GetPos(x,y);
         SetPos(x, GetParent()->GetTall() - GetTall());
         ResetHeight = false;
      }
      vgui::surface()->DrawSetColor(0,0,0,100);
      vgui::surface()->DrawFilledRect(0,0, GetWide(), GetTall());

      vgui::surface()->DrawSetTextColor(100,100,100,150);
      vgui::surface()->DrawSetTextFont(m_hFont);
      //get cursor position
      int cx, cy;
      vgui::input()->GetCursorPosition(cx,cy);
      ScreenToLocal(cx,cy);

      for(int x=0;x<ActiveEntries.Count();x++)
      {
         if (IsEnabled() && cy > 10 + (x*fntTall) && cy < 10 + fntTall + (x*fntTall))
         {
            //Draw a highlighting box, but only when the cursor is hovering over me and i'm not disabled
            vgui::surface()->DrawSetColor(0,0,50,120);
            vgui::surface()->DrawFilledRect(15, 10 + (x*fntTall), GetWide()-15, 10 + (x*fntTall)+fntTall);
         }
         //draw a number....
         vgui::surface()->DrawSetTextPos(15, 12+ (x*fntTall));
         vgui::surface()->DrawUnicodeChar('1'+x);
         //draw the text...
         vgui::surface()->DrawSetTextPos(30, 12 + (x*fntTall));
         vgui::surface()->DrawUnicodeString(ActiveEntries[x]->Text);
      }
   }
   void MsgFunc_InitDialogue(bf_read &msg)
   {
      ResetHeight = true;//force resize of this control
      char dialogentry[257];//2x MAX_KEY+1
      msg.ReadString(dialogentry, 257);
      if (FStrEq(dialogentry, "disabled"))//disabled mode; waits for sounds to finish playing :)
      {
         SetEnabled(false);
         return;
      }
      SetEnabled(true);
      ActiveEntries.RemoveAll();
      
      if (strlen(dialogentry)>0)
      {
         for(CDialogEntry *e = g_pDialogMgr->GetDialogEntriesByName(dialogentry); e; e = g_pDialogMgr->GetDialogEntriesByName(dialogentry, e))
         {
            ActiveEntries.AddToTail(e);
         }
         DialogueVisible = true;
         SetMouseInputEnabled(true);
         SetKeyBoardInputEnabled(true);
      }
      if (ActiveEntries.Count()==0)
      {
         DialogueVisible =false;
         SetKeyBoardInputEnabled(false);
         SetMouseInputEnabled(false);
      }
   }
   void OnMousePressed(vgui::MouseCode code)
   {
      if (code == MOUSE_LEFT && IsEnabled())
      {
         int cx, cy;
         vgui::input()->GetCursorPosition(cx,cy);
         ScreenToLocal(cx,cy);
         int fntTall = vgui::surface()->GetFontTall(m_hFont) + 4;
         int index = (cy - 10) / fntTall;
         if (index >=0 && index < ActiveEntries.Count())
            SelectDialogue(index);
      }
   }
   void Init( void );
   void ApplySchemeSettings( vgui::IScheme *pScheme )
    {
       m_hFont = pScheme->GetFont( "HudHintTextLarge", true );
      int fntTall = vgui::surface()->GetFontTall(m_hFont);
      SetTall(20 + (fntTall * ActiveEntries.Count()));
      int x, y;
      GetPos(x,y);
      SetPos(x, GetParent()->GetTall() - GetTall());
      ResetHeight = false;
       BaseClass::ApplySchemeSettings( pScheme );
    }
 
   void SelectDialogue(int id)
   {
      char cmd[128];
      strcpy(cmd, "select_dialogue ");
      if (id>=0)
         strcat(cmd, ActiveEntries[id]->Name);
      engine->ServerCmd(cmd);
      //server decides if we continue the dialog. Not me
      SetEnabled(false);
   }
   void CHudDialogue::OnKeyCodeReleased(vgui::KeyCode code)
   {
      if (IsEnabled())
      {
         if (code > KEY_0 && code <= KEY_9)
            SelectDialogue(code - KEY_1);//select 1-9
         else if (code == KEY_SPACE)//key_escape doesnt work here, its reserved for the menu/console
            SelectDialogue(-1);//cancel   
      }
   }
   CHudDialogue(const char *pElementName) : CHudElement(pElementName), BaseClass(NULL, pElementName)
   {
      SetParent( g_pClientMode->GetViewport() );//attacht it to the main screen
      scheme = vgui::scheme()->LoadSchemeFromFile("resource/ClientScheme.res", "ClientScheme");//load scheme
      SetPaintBackgroundEnabled( false );//we draw in paint
      SetHiddenBits( HIDEHUD_PLAYERDEAD );//dont show if we're dead.
      MakePopup(true, true);//make it a popup, yet do not draw it. Just hoover invisible over the screen till needed
      SetMouseInputEnabled(false);//dont swallow mouse input
      SetKeyBoardInputEnabled(false);//dont swallow keyboard input
   }
};
//I AM HUD.
DECLARE_HUDELEMENT ( CHudDialogue );
//I listen to InitDialog usermessage
DECLARE_HUD_MESSAGE( CHudDialogue, InitDialogue )

void CHudDialogue::Init()
{
   //I'm ready to recieve InitDialog messages now.
    HOOK_HUD_MESSAGE( CHudDialogue, InitDialogue );
}

Markup is done by code in the Paint() event, so modifications in how the panel looks should be made there (see e.g. https://developer.valvesoftware.com/wik ... dification). The panel still needs to be declared in hudlayout.res, so go to scripts/hudlayout.res and add an entry like this
Code: Select all
   CHudDialogue
   {
      "fieldname" "CHudDialogue"
      "visible" "0"
      "wide" "500"
      "tall" "100"
      "xpos" "c-250"
      "ypos" "380"
   }

which positions the panel to the middle of the screen (c) with a width of 500 and initial height of 100 pixels. The hud paint() code resizes the panel if more or less options are required, so the height of the panel doesn't really matter.

Now that we have the panel, entity and scripts loaded, we are almost done! Al we need now is to register our usermessage, so go to hl2_usermessages.cpp and add a new entry like so:
Code: Select all
usermessages->Register( "InitDialogue", -1);

Note: -1 means infinite size, we cannot predict the size of the usermessage because it sends the name of the dialog entry, and these can vary. Doesn't really matter for singleplayer games anyway.

And finally, the hammer fgd code:
Code: Select all
@PointClass base(Targetname) = logic_dialog : "Dialog entity"
[
   dialogentry(string) : "Dialog Entry" : "" : "The dialog to start in dialogentries.txt."
   output Output1(void) : "Fired when this Output is triggered in the dialogue."
   output Output2(void) : "Fired when this Output is triggered in the dialogue."
   output Output3(void) : "Fired when this Output is triggered in the dialogue."
   output Output4(void) : "Fired when this Output is triggered in the dialogue."
   output Output5(void) : "Fired when this Output is triggered in the dialogue."
   output Output6(void) : "Fired when this Output is triggered in the dialogue."
   output Output7(void) : "Fired when this Output is triggered in the dialogue."
   output Output8(void) : "Fired when this Output is triggered in the dialogue."
   output Output9(void) : "Fired when this Output is triggered in the dialogue."
   output OnAbort(void) : "Fired when the dialogue was cancelled by user or input."
   input ShowDialog(void) : "Show the dialog (cancels any active dialog)"
   input AbortDialog(void) : "Abort the dialog (has no effect if no dialog is active, but will call OnAbort)."
]


Additions
  • make the sounds originate from the talking npc. Use the Emitsound() of your npc instead of the one of the logic_dialog.
  • you may want to play .vcd's and/or npc animations instead of just sounds. For this, check the code of the corresponding entities and mimic that (e.g. by making your dialog entity derive from logic_choreographed_scene)
  • you'll probably want to encode the dialogue keyvalue file if you don't want people to nose or mess around in these files, but this is beyond the scope of this tutorial.

What about multiplayer? Adding dialogues to MP is possible, but the skills required are of a level which you don't have if you needed this tutorial in the first place :p Be prepared for running into problems concerning lag, script consistencies and much more. I'd advise against it!
When you are up to your neck in shit, keep your head up high
zombie@computer
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Fri Dec 31, 2004 5:58 pm
Location: Lent, Netherlands

Re: Dialogue Question...

Postby GodAndAirborne on Tue Dec 06, 2011 9:15 pm

It's brilliant, simply brilliant. Granted, it's Greek to me, so now I'm off to learn how to interpret and implement it. First off, thank you Zombie@computer. Not only do I believe this will be invaluable to my MOD, but for all of those who're wanting to create more story-driven or even RPG mods. And thanks to all of you at Interlopers.net-- truly you guys were the only ones who stepped up and offered any assistance. Oh, I know I'm going to need much more assistance with my mod in the next coming year, so I'm relieved and grateful there are professionals here who will lend an ear and some of their experience.

Lastly, can anyone provide a link of where I can go to learn basic HL2/Source programming? I mean a "For Dummies" type assistance for those of us who know not the first thing about the subject? I'm really a noob of the highest order, but I'm willing to learn. [Edit: In light of Armageddon's suggestion, does anyone know of a site for basic C++ instruction? And I do mean basic (re: for idiots).]

As soon as I successfully try this out, I'm as good as my word-- I'll deposit around $60 to Interlopers.net. I hope that's acceptable. Really, flattery aside, you guys have the finest community out there for beginners in HL2 modding. Thanks again.

Joshua.
Last edited by GodAndAirborne on Tue Dec 06, 2011 11:47 pm, edited 1 time in total.
User avatar
GodAndAirborne
Member
Member
 
Joined: Mon Nov 28, 2011 11:17 pm

Re: Dialogue Question...

Postby Armageddon on Tue Dec 06, 2011 9:59 pm

Instead of trying to learn how to code just one thing, or edit one engine I would suggest learning C++ (which the Source engine uses) it will make things so much simpler and it's a good skill to have.
User avatar
Armageddon
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Sun Dec 14, 2008 5:53 am

Re: Dialogue Question...

Postby GodAndAirborne on Wed Dec 07, 2011 4:31 pm

Spent a few hours checking out C++. Wish I could report even a rudimentary understanding, rather it was a harrowing look into a unknowable land fraught with head-scratching and self-doubt confessions of, "Dear God, what have I gotten myself into?".

So, here's my solution-- gentlemen, which one of you 'Lopers would like to earn $40 to do (...what I hope is) some light scripting? Granted, zombie@computer took care of the actual code-script, what I need is for someone to set it up during crucial scenes in my levels. I would provide the text and the sound files, all you would need to do is link it to a trigger or a function with zomb's code-script. Having reviewed my own notes, I'm thinking I just need 5 to possibly 6 instances during the entire game where the player would need to engage in a lengthy conversation where the dialogue options would play a part.

If you're interested, please post a reply or contact me here: GodandAirborne@gmail.com

And by the way, Interlopers.net, per request, will be donated $60. If you want, I can pay them $60 and you (...the prospective employee) the $40, or I can give a grand total of $100 to Interlopers. Your choice. Think on it...and thanks.

Joshua.
User avatar
GodAndAirborne
Member
Member
 
Joined: Mon Nov 28, 2011 11:17 pm

Re: Dialogue Question...

Postby stoopdapoop on Wed Dec 07, 2011 5:44 pm

http://www.cprogramming.com/tutorial.html

This is very good. Concise but informative.

Also, good luck with the project.
I'm Brown
Image
User avatar
stoopdapoop
Veteran
Veteran
 
Joined: Sun Aug 21, 2005 2:14 am
Location: Ann Arbor, MI

Re: Dialogue Question...

Postby GodAndAirborne on Wed Dec 07, 2011 6:29 pm

Stoop, my man, thank you for the link. I gave it a once-over and, to a degree, understand what { & } mean now. However...it's still Greek to me, especially when trying to figure how to use it within the confines of the Source Engine. I re-read zombie@computer's code and, while I'm sure it's brilliant, still can't make heads nor tails of how to code it with a trigger or a function, nor how to set up the dialogue or sound files to be triggered. So, C++ isn't a strength for me, but if someone is willing to help I will financially compensate them. And I need to stress that again: I'm not asking for a volunteer or a hand-out, but rather a mutually beneficially work partnership in which "Omnichronia" sees the light of day and those who help me are paid for it.

I understand if there's some hesitation, so next week (Dec 12-16) I will deposit $10 out of the $60 toward Interlopers.net as a token of good faith. If no one still wants to assist, then all I can ask is that maybe you could pass the word to other prospective modders/coders. I assure you, gentlemen, I'm that determined to see this mod published. I want to look back past the Army, the novels, comic books, etc., and be able to say, "...oh, and I also wrote a mod for Half Life 2."

Thanks again.
User avatar
GodAndAirborne
Member
Member
 
Joined: Mon Nov 28, 2011 11:17 pm

Re: Dialogue Question...

Postby zombie@computer on Wed Dec 07, 2011 7:55 pm

You dont need to know c++ to add the posted code, though it will help massively in case there are errors somewhere.

https://developer.valvesoftware.com/wik ... er_Choices
https://developer.valvesoftware.com/wik ... ource_Code
https://developer.valvesoftware.com/wiki/My_First_Mod

those are essential
When you are up to your neck in shit, keep your head up high
zombie@computer
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Fri Dec 31, 2004 5:58 pm
Location: Lent, Netherlands

Re: Dialogue Question...

Postby ChromeAngel on Wed Dec 07, 2011 10:07 pm

This looks interesting, so I think i'll have a go with zombie's in an otherwise un-modded SP code base . I'll share the results if/when I get it working.

IMHO you can't say you've "written" a HL2 mod unless you did the coding yourself, you just "modded" it.
/coder snobbery
User avatar
ChromeAngel
Member
Member
 
Joined: Fri Oct 21, 2011 7:28 am
Location: England

Re: Dialogue Question...

Postby Armageddon on Sat Dec 10, 2011 6:50 am

Okay so I added and compiled your code, at first there was an error with including the dialogShared.h in the logic_dialog code but I worked it out, now I'm getting these errors which I can't read, Gary told me to recompile but that didn't help.

Code: Select all
2>logic_dialog.obj : error LNK2001: unresolved external symbol "class DialogMgr * g_pDialogMgr" (?g_pDialogMgr@@3PAVDialogMgr@@A)
2>logic_dialog.obj : error LNK2019: unresolved external symbol "public: class CDialogEntry * __thiscall DialogMgr::GetDialogEntriesByName(char const *,class CDialogEntry *)" (?GetDialogEntriesByName@DialogMgr@@QAEPAVCDialogEntry@@PBDPAV2@@Z) referenced in function "private: void __thiscall CLogicDialog::ShowDialog(void)" (?ShowDialog@CLogicDialog@@AAEXXZ)
2>.\Release_episodic\Server.dll : fatal error LNK1120: 2 unresolved externals
========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
User avatar
Armageddon
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Sun Dec 14, 2008 5:53 am

Re: Dialogue Question...

Postby zombie@computer on Sat Dec 10, 2011 6:01 pm

have you added the shared code (cpp AND h) to both projects (client AND server)?
When you are up to your neck in shit, keep your head up high
zombie@computer
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Fri Dec 31, 2004 5:58 pm
Location: Lent, Netherlands

Re: Dialogue Question...

Postby Armageddon on Sat Dec 10, 2011 9:28 pm

Yes. I created them under the shared folder.
User avatar
Armageddon
Forum Goer Elite™
Forum Goer Elite™
 
Joined: Sun Dec 14, 2008 5:53 am
PreviousNext

Return to Programming

Who is online

Users browsing this forum: No registered users