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