supertux is now searching for a default.nut file in the same dir as the level and...
[supertux.git] / src / scripting / script_interpreter.cpp
1 #include <config.h>
2
3 #include "script_interpreter.h"
4
5 #include <stdarg.h>
6 #include <stdexcept>
7 #include <sstream>
8 #include <fstream>
9 #include <sqstdio.h>
10 #include <sqstdaux.h>
11 #include <sqstdblob.h>
12 #include <sqstdsystem.h>
13 #include <sqstdmath.h>
14 #include <sqstdstring.h>
15
16 #include "wrapper.h"
17 #include "wrapper_util.h"
18 #include "sector.h"
19 #include "file_system.h"
20 #include "game_session.h"
21 #include "resources.h"
22 #include "physfs/physfs_stream.h"
23 #include "object/text_object.h"
24 #include "object/scripted_object.h"
25 #include "object/display_effect.h"
26 #include "scripting/sound.h"
27 #include "scripting/scripted_object.h"
28 #include "scripting/display_effect.h"
29
30 static void printfunc(HSQUIRRELVM, const char* str, ...)
31 {
32   va_list arglist;
33   va_start(arglist, str);
34   vprintf(str, arglist);
35   va_end(arglist);
36 }
37
38 ScriptInterpreter* ScriptInterpreter::_current = 0;
39
40 ScriptInterpreter::ScriptInterpreter(const std::string& new_working_directory)
41   : working_directory(new_working_directory), sound(0), level(0)
42 {
43   v = sq_open(1024);
44   if(v == 0)
45     throw std::runtime_error("Couldn't initialize squirrel vm");
46
47   // register default error handlers
48   sqstd_seterrorhandlers(v);
49   // register squirrel libs
50   sq_pushroottable(v);
51   if(sqstd_register_bloblib(v) < 0)
52     throw SquirrelError(v, "Couldn't register blob lib");
53   if(sqstd_register_iolib(v) < 0)
54     throw SquirrelError(v, "Couldn't register io lib");
55   if(sqstd_register_systemlib(v) < 0)
56     throw SquirrelError(v, "Couldn't register system lib");
57   if(sqstd_register_mathlib(v) < 0)
58     throw SquirrelError(v, "Couldn't register math lib");
59   if(sqstd_register_stringlib(v) < 0)
60     throw SquirrelError(v, "Couldn't register string lib");
61
62   // register print function
63   sq_setprintfunc(v, printfunc);
64   
65   // register supertux API
66   register_functions(v, supertux_global_functions);
67   register_classes(v, supertux_classes);
68
69   // expose some "global" objects
70   sound = new Scripting::Sound();
71   expose_object(sound, "Sound", "Sound");  
72   
73   level = new Scripting::Level();
74   expose_object(level, "Level", "Level");
75 }
76
77 void
78 ScriptInterpreter::register_sector(Sector* sector)
79 {
80   // expose ScriptedObjects to the script
81   for(Sector::GameObjects::iterator i = sector->gameobjects.begin();
82       i != sector->gameobjects.end(); ++i) {
83     GameObject* object = *i;
84     Scripting::ScriptedObject* scripted_object
85       = dynamic_cast<Scripting::ScriptedObject*> (object);
86     if(!scripted_object)
87       continue;
88     
89     expose_object(scripted_object, scripted_object->get_name(), 
90         "ScriptedObject");
91   }
92   
93   TextObject* text_object = new TextObject();
94   sector->add_object(text_object);
95   Scripting::Text* text = static_cast<Scripting::Text*> (text_object);
96   expose_object(text, "Text", "Text");
97   
98   DisplayEffect* display_effect = new DisplayEffect();
99   sector->add_object(display_effect);
100   Scripting::DisplayEffect* display_effect_api
101     = static_cast<Scripting::DisplayEffect*> (display_effect);
102   expose_object(display_effect_api, "DisplayEffect", "DisplayEffect");
103 }
104
105 ScriptInterpreter::~ScriptInterpreter()
106 {
107   sq_close(v);
108   delete sound;
109   delete level;
110 }
111
112 static SQInteger squirrel_read_char(SQUserPointer file)
113 {
114   std::istream* in = reinterpret_cast<std::istream*> (file);
115   char c = in->get();
116   if(in->eof())
117     return 0;    
118   return c;
119 }
120
121 void
122 ScriptInterpreter::run_script(std::istream& in, const std::string& sourcename,
123         bool remove_when_terminated)
124 {
125   printf("Stackbefore:\n");
126   print_squirrel_stack(v);
127   if(sq_compile(v, squirrel_read_char, &in, sourcename.c_str(), true) < 0)
128     throw SquirrelError(v, "Couldn't parse script");
129  
130   _current = this;
131   sq_push(v, -2);
132   if(sq_call(v, 1, false) < 0)
133     throw SquirrelError(v, "Couldn't start script");
134   _current = 0;
135   if(sq_getvmstate(v) != SQ_VMSTATE_SUSPENDED) {
136     if(remove_when_terminated) {
137       remove_me();
138     }
139     printf("ended.\n");
140     // remove closure from stack
141     sq_pop(v, 1);
142   }
143   printf("After:\n");
144   print_squirrel_stack(v);
145 }
146
147 void
148 ScriptInterpreter::expose_object(void* object, const std::string& name,
149                                  const std::string& type)
150 {
151   // part1 of registration of the instance in the root table
152   sq_pushroottable(v);
153   sq_pushstring(v, name.c_str(), -1);
154
155   // resolve class name
156   sq_pushroottable(v);
157   sq_pushstring(v, type.c_str(), -1);
158   if(sq_get(v, -2) < 0) {
159     std::ostringstream msg;
160     msg << "Couldn't resolve squirrel type '" << type << "'.";
161     throw std::runtime_error(msg.str());
162   }
163   sq_remove(v, -2); // remove roottable
164   
165   // create an instance and set pointer to c++ object
166   if(sq_createinstance(v, -1) < 0 || sq_setinstanceup(v, -1, object)) {
167     std::ostringstream msg;
168     msg << "Couldn't setup squirrel instance for object '"
169         << name << "' of type '" << type << "'.";
170     throw SquirrelError(v, msg.str());
171   }
172   
173   sq_remove(v, -2); // remove class from stack
174   
175   // part2 of registration of the instance in the root table
176   if(sq_createslot(v, -3) < 0)
177     throw SquirrelError(v, "Couldn't register object in squirrel root table");    sq_pop(v, 1);
178 }
179
180 void
181 ScriptInterpreter::set_wakeup_time(float seconds)
182 {
183   wakeup_timer.start(seconds);
184 }
185
186 void
187 ScriptInterpreter::update(float )
188 {
189   if(!wakeup_timer.check())
190     return;
191   
192   _current = this;
193   if(sq_wakeupvm(v, false, false) < 0)
194     throw SquirrelError(v, "Couldn't resume script");
195   _current = 0;
196   if(sq_getvmstate(v) != SQ_VMSTATE_SUSPENDED) {
197     printf("script ended...\n");
198     remove_me();
199   }
200 }
201
202 void
203 ScriptInterpreter::draw(DrawingContext& )
204 {
205 }
206
207 void
208 ScriptInterpreter::add_script_object(Sector* sector, const std::string& name,
209     const std::string& script)
210 {
211   try {
212     std::string workdir = GameSession::current()->get_working_directory();
213     std::auto_ptr<ScriptInterpreter> interpreter(
214                 new ScriptInterpreter(workdir));
215     interpreter->register_sector(sector);
216
217     // load default.nut file if it exists
218     try {
219       std::string filename = workdir + "/default.nut";
220       IFileStream in(filename);
221       interpreter->run_script(in, filename, false);
222     } catch(std::exception& e) {
223       // nothing
224     }
225         
226     std::istringstream in(script);
227     interpreter->run_script(in, name);
228     sector->add_object(interpreter.release());
229   } catch(std::exception& e) {
230     std::cerr << "Couldn't start '" << name << "' script: " << e.what() << "\n";
231   }
232 }
233