Adonthell 0.4
dialog.cc
Go to the documentation of this file.
00001 /*
00002    $Id: dialog.cc,v 1.37 2009/03/01 12:26:14 ksterker Exp $
00003 
00004    (C) Copyright 2000/2001/2002 Kai Sterker <kaisterker@linuxgames.com>
00005    Part of the Adonthell Project http://adonthell.linuxgames.com
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details
00013 */
00014 
00015 /**
00016  * @file   dialog.cc
00017  * @author Kai Sterker <kaisterker@linuxgames.com>
00018  * 
00019  * @brief  Declares the dialog class.
00020  * 
00021  * 
00022  */
00023  
00024 #include <algorithm>
00025 #include <iostream>
00026 #include <string.h>
00027 
00028 #include "character.h"
00029 #include "dialog.h"
00030 #include "nls.h"
00031 #include "yarg.h"
00032 
00033 // Constructor
00034 dialog::dialog (character_base *npc)
00035 {
00036     strings = NULL;
00037     npc_portrait_= npc->get_portrait ();
00038     npc_name_ = npc->get_name ();
00039 }
00040 
00041 // Destructor
00042 dialog::~dialog ()
00043 {
00044     clear ();
00045 }
00046 
00047 // Prepare the dialogue for execution
00048 bool dialog::init (string fpath, string name, PyObject *args)
00049 {
00050     // Load and instanciate the dialogue object
00051     if (!dialogue.create_instance (fpath, name, args))
00052         return false;
00053 
00054     // Remaining setup tasks
00055     if (!setup ())
00056         return false;
00057 
00058     return true;
00059 }
00060 
00061 // Misc. initialisation
00062 bool dialog::setup ()
00063 {
00064     PyObject *list, *s;
00065     u_int32 i, size;
00066     
00067     // Get the text that may loop
00068     list = dialogue.get_attribute ("loop");
00069     if (list && PyList_Check (list))
00070     {
00071         size = PyList_Size (list);
00072     
00073         for (i = 0; i < size; i++)
00074         {
00075             s = PyList_GetItem (list, i);
00076             if (s && PyInt_Check (s)) loop.push_back (PyInt_AsLong (s));
00077         }
00078         
00079         Py_DECREF (list);
00080     }
00081     
00082     // Extract the dialogue's strings
00083     list = dialogue.get_attribute ("text");
00084     if (list && PyList_Check (list))
00085     {
00086         size = PyList_Size (list);
00087         strings = new const char*[size];
00088 
00089         for (i = 1; i < size; i++)
00090         {
00091             s = PyList_GetItem (list, i);
00092             if (s && PyString_Check (s)) strings[i] = PyString_AsString (s);
00093             else strings[i] = "*** Error";
00094         }
00095 
00096         Py_DECREF (list);
00097     }
00098     else return false;
00099 
00100     // Init the first answer
00101     answers.push_back (0);
00102         
00103     return true;
00104 }
00105 
00106 // Reload a dialogue script that has changed on disk
00107 bool dialog::reload (string fpath, string name, PyObject *args)
00108 {
00109     // Load and instanciate the dialogue object
00110     if (!dialogue.reload_instance (fpath, name, args))
00111         return false;
00112 
00113     // Remaining setup tasks
00114     if (!setup ())
00115         return false;
00116 
00117     return true;
00118 }
00119 
00120 // Clean up
00121 void dialog::clear ()
00122 {
00123     if (strings) delete[] strings;
00124 }
00125 
00126 // iterate over the dialogue text
00127 string dialog::text ()
00128 {
00129     if (i_text == text_.end ())
00130     {
00131         i_text = text_.begin ();
00132         return "";
00133     }
00134     
00135     return *i_text++;
00136 }
00137 
00138 // Gets the index of either the player or npc array
00139 void dialog::run (u_int32 index)
00140 {
00141     PyObject *arg, *result, *speaker, *speech;
00142     s_int32 s, answer = answers[index];
00143     u_int32 stop, size;
00144     
00145     // empty previous dialogue text
00146     text_.clear ();
00147     answers.clear ();
00148     
00149     // end of dialogue
00150     if (answer == -1)
00151         return;
00152     
00153     // Mark the Player text as used unless loops allowed
00154     if (find (loop.begin (), loop.end (), answer) == loop.end ())
00155         used.push_back (answer);
00156         
00157     do
00158     {
00159         // Execute the next part of the dialogue
00160         arg = Py_BuildValue ("(i)", answer);
00161         
00162          // run next part of dialogue
00163         dialogue.run (arg);
00164 #ifdef PY_DEBUG
00165         python::show_traceback ();
00166 #endif
00167         Py_XDECREF (arg);
00168     
00169         // Now fill in the NPC's and Player's responses:
00170         // 1. Get the neccesary attributes of the dialogue class
00171         speaker = dialogue.get_attribute ("speaker");
00172         speech = dialogue.get_attribute ("speech");
00173 
00174         // 2. Search the NPC part for used text
00175         for (int i = 0; i < PyList_Size (speech); i++)
00176         {
00177             s = PyInt_AsLong (PyList_GetItem (speech, i));
00178 
00179             // Remove text that was already used and isn't allowed to loop
00180             if (find (used.begin (), used.end (), s) != used.end ())
00181             {
00182                 PySequence_DelItem (speaker, i);
00183                 PySequence_DelItem (speech, i--);
00184             }
00185         }
00186 
00187         // check if some text is left at all
00188         size = PyList_Size (speech);
00189         if (size == 0) 
00190         {
00191             i_text = text_.begin ();
00192             return;
00193         }
00194                 
00195         // prepare the random number generator        
00196         yarg::range (0, size - 1);
00197 
00198         // check type of speaker
00199         if (PyList_GetItem (speaker, 0) != Py_None)
00200         {
00201             // got NPC text, so let the engine decide
00202             int rnd = yarg::get ();
00203             
00204             // get the text
00205             answer = PyInt_AsLong (PyList_GetItem (speech, rnd));
00206             text_.push_back (scan_string (nls::translate (strings[answer])));
00207 
00208             // get the NPC color, portrait and name
00209             char *npc = PyString_AsString (PyList_GetItem (speaker, rnd));
00210             if (npc != NULL)
00211             {
00212                 if (strcmp ("Narrator", npc) == 0) npc_color_ = 0;
00213                 else
00214                 {
00215                     // set color and portrait of the NPC
00216                     character_base *mychar = data::characters[npc];
00217 
00218                     npc_color_ = mychar->get_color ();
00219                     npc_portrait_ = mychar->get_portrait ();
00220                     npc_name_ = npc;
00221                 }
00222             }
00223             
00224             // check whether we shall continue or not
00225             arg = Py_BuildValue ("(i)", answer);
00226             result = dialogue.call_method_ret ("stop", arg);
00227             stop = PyInt_AsLong (result);
00228             Py_XDECREF (result);
00229             Py_XDECREF (arg);
00230             
00231             // Mark the NPC text as used unless loops allowed
00232             if (find (loop.begin (), loop.end (), answer) == loop.end ())
00233                 used.push_back (answer);
00234             
00235             answers.push_back (answer);
00236         }
00237         else
00238         {
00239             // got Player text, so let the player decide
00240             for (u_int32 i = 0; i < size; i++)
00241             {
00242                 // simply add all text to let the player select an answer
00243                 answer = PyInt_AsLong (PyList_GetItem (speech, i));
00244                 text_.push_back (scan_string (nls::translate (strings[answer])));
00245                 answers.push_back (answer);
00246             }
00247             
00248             // let the player make his decision
00249             stop = true;
00250         }
00251     
00252         // cleanup
00253         Py_XDECREF (speaker);
00254         Py_XDECREF (speech);
00255     }
00256     while (!stop);            
00257 
00258     // init the iterator for dialogue text retrieval
00259     i_text = text_.begin ();
00260 }
00261 
00262 // execute embedded functions and replace shortcuts
00263 // yeah, the c string library hurts, but at least it's fast ;)
00264 string dialog::scan_string (const char *s)
00265 {
00266     u_int32 begin, end, len;
00267     PyObject *result;
00268     const char *start;
00269     char *tmp, *mid, *str = NULL;
00270     character *the_player = data::the_player;
00271     string newstr (s); 
00272 
00273     // replace $... shortcuts
00274     while (1)
00275     {
00276         // check wether the string contains shortcut code at all
00277         start = strchr (newstr.c_str (), '$');
00278         if (start == NULL) break;
00279 
00280         // replace "$name"
00281         if (strncmp (start, "$name", 5) == 0)
00282         {
00283             begin = newstr.length () - strlen (start);
00284             string t (newstr, 0, begin);
00285             t += the_player->get_name ();
00286             t += (start+5);
00287             
00288             newstr = t;
00289             continue;
00290         }
00291 
00292         // replace "$fm"
00293         if (strncmp (start, "$fm", 3) == 0)
00294         {
00295             // extract the "$fm{.../...} part
00296             end = strcspn (start, "}");
00297             str = new char[end];
00298             str[end-1] = 0;        
00299             strncpy (str, start+3, end);
00300 
00301             if (the_player->storage::get_val ("gender") == FEMALE)
00302                 mid = get_substr (str, "{", "/");
00303             else
00304                 mid = get_substr (str, "/", "}");
00305 
00306             begin = newstr.length () - strlen(start);
00307             tmp = new char[newstr.length () - end + strlen (mid)];
00308             strncpy (tmp, newstr.c_str (), begin);
00309             tmp[begin] = 0;
00310             strcat (tmp, mid);
00311             strcat (tmp, start+end+1);
00312             
00313             delete[] str;
00314             delete[] mid;
00315             newstr = tmp;
00316 
00317             continue;
00318         }
00319 
00320         // Error!
00321         cout << "\n*** Error, unknown macro " << start << flush;
00322         newstr[newstr.length () - strlen (start)] = ' ';
00323     }
00324     
00325     // execute python functions
00326     while (1)
00327     {
00328         // check wether the string contains python code at all
00329         start = strchr (newstr.c_str (), '{');
00330         if (start == NULL) break;
00331 
00332         end = strcspn (start, "}");
00333         mid = NULL;
00334 
00335         str = new char[end];
00336         str[end-1] = 0;        
00337 
00338         // extract the code without the brackets
00339         strncpy (str, start+1, end-1);
00340 
00341         // run the string
00342         result = PyObject_CallMethod (dialogue.get_instance (false), str, NULL);
00343 
00344         if (result)
00345             if (PyString_Check (result))
00346                 mid = (char*) nls::translate (PyString_AS_STRING (result));    
00347         
00348         // Replace existing with new, changed string
00349         // 1. Calculate string's length
00350         len = newstr.length ();
00351         begin = len - strlen (start);
00352         tmp = new char[(mid ? strlen(mid) : 0) + len - strlen(str)];
00353 
00354         // 2. Merge prefix, resulting string and postfix into new string
00355         strncpy (tmp, newstr.c_str (), begin);
00356         tmp[begin] = 0;
00357         if (mid) strcat (tmp, mid);
00358         strcat (tmp, start+end+1);
00359 
00360         // 3. Exchange strings
00361         newstr = tmp;
00362 
00363         // Cleanup
00364         Py_XDECREF (result);
00365         delete[] str;
00366         delete[] tmp;
00367     }
00368 
00369     return newstr;
00370 }
00371 
00372 char *dialog::get_substr (const char* string, const char* begin, const char* end)
00373 {
00374     u_int32 b, e;
00375     b = strcspn (string, begin) + 1;
00376     e = strcspn (string, end) - b;
00377 
00378     char *result = new char[e+1];
00379     strncpy (result, string+b, e);
00380     result[e] = 0;
00381 
00382     return result;
00383 }