Adonthell
0.4
|
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 }