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