Adonthell  0.4
dialog.cc
Go to the documentation of this file.
1 /*
2  $Id: dialog.cc,v 1.37 2009/03/01 12:26:14 ksterker Exp $
3 
4  (C) Copyright 2000/2001/2002 Kai Sterker <kaisterker@linuxgames.com>
5  Part of the Adonthell Project http://adonthell.linuxgames.com
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details
13 */
14 
15 /**
16  * @file dialog.cc
17  * @author Kai Sterker <kaisterker@linuxgames.com>
18  *
19  * @brief Declares the dialog class.
20  *
21  *
22  */
23 
24 #include <algorithm>
25 #include <iostream>
26 #include <string.h>
27 
28 #include "character.h"
29 #include "dialog.h"
30 #include "nls.h"
31 #include "yarg.h"
32 
33 // Constructor
35 {
36  strings = NULL;
37  npc_portrait_= npc->get_portrait ();
38  npc_name_ = npc->get_name ();
39 }
40 
41 // Destructor
43 {
44  clear ();
45 }
46 
47 // Prepare the dialogue for execution
48 bool dialog::init (string fpath, string name, PyObject *args)
49 {
50  // Load and instanciate the dialogue object
51  if (!dialogue.create_instance (fpath, name, args))
52  return false;
53 
54  // Remaining setup tasks
55  if (!setup ())
56  return false;
57 
58  return true;
59 }
60 
61 // Misc. initialisation
62 bool dialog::setup ()
63 {
64  PyObject *list, *s;
65  u_int32 i, size;
66 
67  // Get the text that may loop
68  list = dialogue.get_attribute ("loop");
69  if (list && PyList_Check (list))
70  {
71  size = PyList_Size (list);
72 
73  for (i = 0; i < size; i++)
74  {
75  s = PyList_GetItem (list, i);
76  if (s && PyInt_Check (s)) loop.push_back (PyInt_AsLong (s));
77  }
78 
79  Py_DECREF (list);
80  }
81 
82  // Extract the dialogue's strings
83  list = dialogue.get_attribute ("text");
84  if (list && PyList_Check (list))
85  {
86  size = PyList_Size (list);
87  strings = new const char*[size];
88 
89  for (i = 1; i < size; i++)
90  {
91  s = PyList_GetItem (list, i);
92  if (s && PyString_Check (s)) strings[i] = PyString_AsString (s);
93  else strings[i] = "*** Error";
94  }
95 
96  Py_DECREF (list);
97  }
98  else return false;
99 
100  // Init the first answer
101  answers.push_back (0);
102 
103  return true;
104 }
105 
106 // Reload a dialogue script that has changed on disk
107 bool dialog::reload (string fpath, string name, PyObject *args)
108 {
109  // Load and instanciate the dialogue object
110  if (!dialogue.reload_instance (fpath, name, args))
111  return false;
112 
113  // Remaining setup tasks
114  if (!setup ())
115  return false;
116 
117  return true;
118 }
119 
120 // Clean up
121 void dialog::clear ()
122 {
123  if (strings) delete[] strings;
124 }
125 
126 // iterate over the dialogue text
127 string dialog::text ()
128 {
129  if (i_text == text_.end ())
130  {
131  i_text = text_.begin ();
132  return "";
133  }
134 
135  return *i_text++;
136 }
137 
138 // Gets the index of either the player or npc array
139 void dialog::run (u_int32 index)
140 {
141  PyObject *arg, *result, *speaker, *speech;
142  s_int32 s, answer = answers[index];
143  u_int32 stop, size;
144 
145  // empty previous dialogue text
146  text_.clear ();
147  answers.clear ();
148 
149  // end of dialogue
150  if (answer == -1)
151  return;
152 
153  // Mark the Player text as used unless loops allowed
154  if (find (loop.begin (), loop.end (), answer) == loop.end ())
155  used.push_back (answer);
156 
157  do
158  {
159  // Execute the next part of the dialogue
160  arg = Py_BuildValue ("(i)", answer);
161 
162  // run next part of dialogue
163  dialogue.run (arg);
164 #ifdef PY_DEBUG
166 #endif
167  Py_XDECREF (arg);
168 
169  // Now fill in the NPC's and Player's responses:
170  // 1. Get the neccesary attributes of the dialogue class
171  speaker = dialogue.get_attribute ("speaker");
172  speech = dialogue.get_attribute ("speech");
173 
174  // 2. Search the NPC part for used text
175  for (int i = 0; i < PyList_Size (speech); i++)
176  {
177  s = PyInt_AsLong (PyList_GetItem (speech, i));
178 
179  // Remove text that was already used and isn't allowed to loop
180  if (find (used.begin (), used.end (), s) != used.end ())
181  {
182  PySequence_DelItem (speaker, i);
183  PySequence_DelItem (speech, i--);
184  }
185  }
186 
187  // check if some text is left at all
188  size = PyList_Size (speech);
189  if (size == 0)
190  {
191  i_text = text_.begin ();
192  return;
193  }
194 
195  // prepare the random number generator
196  yarg::range (0, size - 1);
197 
198  // check type of speaker
199  if (PyList_GetItem (speaker, 0) != Py_None)
200  {
201  // got NPC text, so let the engine decide
202  int rnd = yarg::get ();
203 
204  // get the text
205  answer = PyInt_AsLong (PyList_GetItem (speech, rnd));
206  text_.push_back (scan_string (nls::translate (strings[answer])));
207 
208  // get the NPC color, portrait and name
209  char *npc = PyString_AsString (PyList_GetItem (speaker, rnd));
210  if (npc != NULL)
211  {
212  if (strcmp ("Narrator", npc) == 0) npc_color_ = 0;
213  else
214  {
215  // set color and portrait of the NPC
216  character_base *mychar = data::characters[npc];
217 
218  npc_color_ = mychar->get_color ();
219  npc_portrait_ = mychar->get_portrait ();
220  npc_name_ = npc;
221  }
222  }
223 
224  // check whether we shall continue or not
225  arg = Py_BuildValue ("(i)", answer);
226  result = dialogue.call_method_ret ("stop", arg);
227  stop = PyInt_AsLong (result);
228  Py_XDECREF (result);
229  Py_XDECREF (arg);
230 
231  // Mark the NPC text as used unless loops allowed
232  if (find (loop.begin (), loop.end (), answer) == loop.end ())
233  used.push_back (answer);
234 
235  answers.push_back (answer);
236  }
237  else
238  {
239  // got Player text, so let the player decide
240  for (u_int32 i = 0; i < size; i++)
241  {
242  // simply add all text to let the player select an answer
243  answer = PyInt_AsLong (PyList_GetItem (speech, i));
244  text_.push_back (scan_string (nls::translate (strings[answer])));
245  answers.push_back (answer);
246  }
247 
248  // let the player make his decision
249  stop = true;
250  }
251 
252  // cleanup
253  Py_XDECREF (speaker);
254  Py_XDECREF (speech);
255  }
256  while (!stop);
257 
258  // init the iterator for dialogue text retrieval
259  i_text = text_.begin ();
260 }
261 
262 // execute embedded functions and replace shortcuts
263 // yeah, the c string library hurts, but at least it's fast ;)
264 string dialog::scan_string (const char *s)
265 {
266  u_int32 begin, end, len;
267  PyObject *result;
268  const char *start;
269  char *tmp, *mid, *str = NULL;
270  character *the_player = data::the_player;
271  string newstr (s);
272 
273  // replace $... shortcuts
274  while (1)
275  {
276  // check wether the string contains shortcut code at all
277  start = strchr (newstr.c_str (), '$');
278  if (start == NULL) break;
279 
280  // replace "$name"
281  if (strncmp (start, "$name", 5) == 0)
282  {
283  begin = newstr.length () - strlen (start);
284  string t (newstr, 0, begin);
285  t += the_player->get_name ();
286  t += (start+5);
287 
288  newstr = t;
289  continue;
290  }
291 
292  // replace "$fm"
293  if (strncmp (start, "$fm", 3) == 0)
294  {
295  // extract the "$fm{.../...} part
296  end = strcspn (start, "}");
297  str = new char[end];
298  str[end-1] = 0;
299  strncpy (str, start+3, end);
300 
301  if (the_player->storage::get_val ("gender") == FEMALE)
302  mid = get_substr (str, "{", "/");
303  else
304  mid = get_substr (str, "/", "}");
305 
306  begin = newstr.length () - strlen(start);
307  tmp = new char[newstr.length () - end + strlen (mid)];
308  strncpy (tmp, newstr.c_str (), begin);
309  tmp[begin] = 0;
310  strcat (tmp, mid);
311  strcat (tmp, start+end+1);
312 
313  delete[] str;
314  delete[] mid;
315  newstr = tmp;
316 
317  continue;
318  }
319 
320  // Error!
321  cout << "\n*** Error, unknown macro " << start << flush;
322  newstr[newstr.length () - strlen (start)] = ' ';
323  }
324 
325  // execute python functions
326  while (1)
327  {
328  // check wether the string contains python code at all
329  start = strchr (newstr.c_str (), '{');
330  if (start == NULL) break;
331 
332  end = strcspn (start, "}");
333  mid = NULL;
334 
335  str = new char[end];
336  str[end-1] = 0;
337 
338  // extract the code without the brackets
339  strncpy (str, start+1, end-1);
340 
341  // run the string
342  result = PyObject_CallMethod (dialogue.get_instance (false), str, NULL);
343 
344  if (result)
345  if (PyString_Check (result))
346  mid = (char*) nls::translate (PyString_AS_STRING (result));
347 
348  // Replace existing with new, changed string
349  // 1. Calculate string's length
350  len = newstr.length ();
351  begin = len - strlen (start);
352  tmp = new char[(mid ? strlen(mid) : 0) + len - strlen(str)];
353 
354  // 2. Merge prefix, resulting string and postfix into new string
355  strncpy (tmp, newstr.c_str (), begin);
356  tmp[begin] = 0;
357  if (mid) strcat (tmp, mid);
358  strcat (tmp, start+end+1);
359 
360  // 3. Exchange strings
361  newstr = tmp;
362 
363  // Cleanup
364  Py_XDECREF (result);
365  delete[] str;
366  delete[] tmp;
367  }
368 
369  return newstr;
370 }
371 
372 char *dialog::get_substr (const char* string, const char* begin, const char* end)
373 {
374  u_int32 b, e;
375  b = strcspn (string, begin) + 1;
376  e = strcspn (string, end) - b;
377 
378  char *result = new char[e+1];
379  strncpy (result, string+b, e);
380  result[e] = 0;
381 
382  return result;
383 }
Declares the character class.
bool reload(string fpath, string name, PyObject *args)
This method is similar to init.
Definition: dialog.cc:107
#define s_int32
32 bits long signed integer
Definition: types.h:44
PyObject * call_method_ret(const string &name, PyObject *args=NULL) const
Call a method of this object.
Definition: py_object.cc:108
void run(u_int32 index)
Run the dialogue.
Definition: dialog.cc:139
string text()
Iterates over the dialogue&#39;s text.
Definition: dialog.cc:127
Class holding game characters.
Definition: character.h:35
#define u_int32
32 bits long unsigned integer
Definition: types.h:35
National Language Support.
dialog(character_base *npc)
Default constructor.
Definition: dialog.cc:34
string get_portrait() const
Returns the current portrait of the character.
static void show_traceback(void)
Dumps any error information to stderr.
Base character class containing attributes and dialog stuff.
bool create_instance(string file, string classname, PyObject *args=NULL)
Creates an instance of a Python class.
Definition: py_object.cc:53
void run(PyObject *args=NULL)
Calls the run () method of this object.
Definition: py_object.h:121
PyObject * get_instance(const bool &incref=true) const
Direct access to the instance object.
Definition: py_object.h:204
PyObject * get_attribute(const string &name) const
Returns a new reference to an attribute of this object.
Definition: py_object.cc:139
static const char * translate(const string &text)
Translate the given string if it&#39;s found in the message catalogue.
Definition: nls.cc:73
Defines the dialog class.
~dialog()
Destructor.
Definition: dialog.cc:42
bool reload_instance(string file, string classname, PyObject *args=NULL)
Similar to create_instance, except that it will reload the module from disk, in case it has been chan...
Definition: py_object.cc:64
string get_name() const
Returns the name of the character.
u_int32 get_color() const
Returns the color representing the character.
bool init(string fpath, string name, PyObject *args)
Load and instanciate the dialog object.
Definition: dialog.cc:48