Moved some console commands' implementations nearer to target classes
[supertux.git] / src / scripting / script_interpreter.cpp
1 #include <config.h>
2
3 #include "script_interpreter.hpp"
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 "msg.hpp"
17 #include "wrapper.hpp"
18 #include "wrapper_util.hpp"
19 #include "sector.hpp"
20 #include "file_system.hpp"
21 #include "game_session.hpp"
22 #include "resources.hpp"
23 #include "physfs/physfs_stream.hpp"
24 #include "object/text_object.hpp"
25 #include "object/scripted_object.hpp"
26 #include "object/display_effect.hpp"
27 #include "object/player.hpp"
28 #include "scripting/sound.hpp"
29 #include "scripting/scripted_object.hpp"
30 #include "scripting/display_effect.hpp"
31 #include "scripting/squirrel_error.hpp"
32
33 static void printfunc(HSQUIRRELVM, const char* str, ...)
34 {
35   va_list arglist;
36   va_start(arglist, str);
37   vprintf(str, arglist);
38   va_end(arglist);
39 }
40
41 ScriptInterpreter* ScriptInterpreter::_current = 0;
42
43 ScriptInterpreter::ScriptInterpreter(const std::string& new_working_directory)
44   : working_directory(new_working_directory), sound(0), level(0), camera(0)
45 {
46   v = sq_open(1024);
47   if(v == 0)
48     throw std::runtime_error("Couldn't initialize squirrel vm");
49
50   // register default error handlers
51   sqstd_seterrorhandlers(v);
52   // register squirrel libs
53   sq_pushroottable(v);
54   if(sqstd_register_bloblib(v) < 0)
55     throw Scripting::SquirrelError(v, "Couldn't register blob lib");
56   if(sqstd_register_iolib(v) < 0)
57     throw Scripting::SquirrelError(v, "Couldn't register io lib");
58   if(sqstd_register_systemlib(v) < 0)
59     throw Scripting::SquirrelError(v, "Couldn't register system lib");
60   if(sqstd_register_mathlib(v) < 0)
61     throw Scripting::SquirrelError(v, "Couldn't register math lib");
62   if(sqstd_register_stringlib(v) < 0)
63     throw Scripting::SquirrelError(v, "Couldn't register string lib");
64
65   // register print function
66   sq_setprintfunc(v, printfunc);
67   
68   // register supertux API
69   Scripting::register_supertux_wrapper(v);
70
71   // expose some "global" objects
72   sound = new Scripting::Sound();
73   expose_object(sound, "Sound");
74   
75   level = new Scripting::Level();
76   expose_object(level, "Level");
77 }
78
79 void
80 ScriptInterpreter::register_sector(Sector* sector)
81 {
82   // expose ScriptedObjects to the script
83   for(Sector::GameObjects::iterator i = sector->gameobjects.begin();
84       i != sector->gameobjects.end(); ++i) {
85     GameObject* object = *i;
86     Scripting::ScriptedObject* scripted_object
87       = dynamic_cast<Scripting::ScriptedObject*> (object);
88     if(!scripted_object)
89       continue;
90     
91     expose_object(scripted_object, scripted_object->get_name());
92   }
93   
94   expose_object(static_cast<Scripting::Player*> (sector->player), "Tux");
95   TextObject* text_object = new TextObject();
96   sector->add_object(text_object);
97   Scripting::Text* text = static_cast<Scripting::Text*> (text_object);
98   expose_object(text, "Text");
99   
100   DisplayEffect* display_effect = new DisplayEffect();
101   sector->add_object(display_effect);
102   Scripting::DisplayEffect* display_effect_api
103     = static_cast<Scripting::DisplayEffect*> (display_effect);
104   expose_object(display_effect_api, "DisplayEffect");
105
106   Scripting::Camera* camera = new Scripting::Camera(sector->camera);
107   expose_object(camera, "Camera");
108 }
109
110 ScriptInterpreter::~ScriptInterpreter()
111 {
112   sq_close(v);
113   delete sound;
114   delete level;
115   delete camera;
116 }
117
118 static SQInteger squirrel_read_char(SQUserPointer file)
119 {
120   std::istream* in = reinterpret_cast<std::istream*> (file);
121   char c = in->get();
122   if(in->eof())
123     return 0;    
124   return c;
125 }
126
127 void
128 ScriptInterpreter::run_script(std::istream& in, const std::string& sourcename,
129         bool remove_when_terminated)
130 {
131   if(sq_compile(v, squirrel_read_char, &in, sourcename.c_str(), true) < 0)
132     throw Scripting::SquirrelError(v, "Couldn't parse script");
133  
134   _current = this;
135   sq_push(v, -2);
136   if(sq_call(v, 1, false) < 0)
137     throw Scripting::SquirrelError(v, "Couldn't start script");
138   _current = 0;
139   if(sq_getvmstate(v) != SQ_VMSTATE_SUSPENDED) {
140     if(remove_when_terminated) {
141       remove_me();
142     }
143     // remove closure from stack
144     sq_pop(v, 1);
145   }
146 }
147
148 void
149 ScriptInterpreter::set_wakeup_time(float seconds)
150 {
151   wakeup_timer.start(seconds);
152 }
153
154 void
155 ScriptInterpreter::update(float )
156 {
157   if(!wakeup_timer.check())
158     return;
159   
160   _current = this;
161   if(sq_wakeupvm(v, false, false) < 0)
162     throw Scripting::SquirrelError(v, "Couldn't resume script");
163   _current = 0;
164   if(sq_getvmstate(v) != SQ_VMSTATE_SUSPENDED) {
165     printf("script ended...\n");
166     remove_me();
167   }
168 }
169
170 void
171 ScriptInterpreter::draw(DrawingContext& )
172 {
173 }
174
175 void
176 ScriptInterpreter::add_script_object(Sector* sector, const std::string& name,
177     const std::string& script)
178 {
179   try {
180     std::string workdir = GameSession::current()->get_working_directory();
181     std::auto_ptr<ScriptInterpreter> interpreter(
182                 new ScriptInterpreter(workdir));
183     interpreter->register_sector(sector);
184     
185     // load global default.nut file if it exists
186     //TODO: Load all .nut files from that directory
187     try {
188       std::string filename = "script/default.nut";
189       IFileStream in(filename);
190       interpreter->run_script(in, filename, false);
191     } catch(std::exception& e) {
192       // nothing
193     }
194
195     // load world-specific default.nut file if it exists
196     try {
197       std::string filename = workdir + "/default.nut";
198       IFileStream in(filename);
199       interpreter->run_script(in, filename, false);
200     } catch(std::exception& e) {
201       // nothing
202     }
203         
204     std::istringstream in(script);
205     interpreter->run_script(in, name);
206     sector->add_object(interpreter.release());
207   } catch(std::exception& e) {
208     msg_warning << "Couldn't start '" << name << "' script: " << e.what() << std::endl;
209   }
210 }
211