1 /*
2  * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3  *
4  * Solarus is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Solarus is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "solarus/core/CurrentQuest.h"
18 #include "solarus/core/Game.h"
19 #include "solarus/core/Geometry.h"
20 #include "solarus/core/MainLoop.h"
21 #include "solarus/core/QuestFiles.h"
22 #include "solarus/core/QuestDatabase.h"
23 #include "solarus/core/QuestProperties.h"
24 #include "solarus/core/Settings.h"
25 #include "solarus/core/System.h"
26 #include "solarus/lua/LuaContext.h"
27 #include "solarus/lua/LuaTools.h"
28 #include <lua.hpp>
29 
30 namespace Solarus {
31 
32 /**
33  * Name of the Lua table representing the main module of Solarus.
34  */
35 const std::string LuaContext::main_module_name = "sol.main";
36 
37 /**
38  * \brief Initializes the main features provided to Lua.
39  */
register_main_module()40 void LuaContext::register_main_module() {
41 
42   std::vector<luaL_Reg> functions = {
43       { "get_solarus_version", main_api_get_solarus_version },
44       { "get_quest_format", main_api_get_quest_format },
45       { "load_file", main_api_load_file },
46       { "do_file", main_api_do_file },
47       { "reset", main_api_reset },
48       { "exit", main_api_exit },
49       { "get_elapsed_time", main_api_get_elapsed_time },
50       { "get_quest_write_dir", main_api_get_quest_write_dir },
51       { "set_quest_write_dir", main_api_set_quest_write_dir },
52       { "load_settings", main_api_load_settings },
53       { "save_settings", main_api_save_settings },
54       { "get_distance", main_api_get_distance },
55       { "get_angle", main_api_get_angle },
56       { "get_type", main_api_get_type },
57       { "get_metatable", main_api_get_metatable },
58       { "get_os", main_api_get_os }
59   };
60   if (CurrentQuest::is_format_at_least({ 1, 6 })) {
61     functions.insert(functions.end(), {
62         { "get_quest_version", main_api_get_quest_version },
63         { "get_resource_ids", main_api_get_resource_ids },
64         { "resource_exists", main_api_resource_exists },
65         { "get_resource_description", main_api_get_resource_description },
66         { "add_resource", main_api_add_resource },
67         { "remove_resource", main_api_remove_resource },
68         { "get_game", main_api_get_game },
69     });
70   }
71   register_functions(main_module_name, functions);
72 
73   // Store sol.main in the registry to access it safely
74   // from C++ (and also slightly faster).
75   // After that, the engine will never rely on the existence of a global
76   // value called "sol". The user can therefore do whatever he wants, including
77   // renaming the sol global table to something else in the unlikely case where
78   // another Lua library called "sol" is required, or if he simply does not
79   // like the name "sol".
80 
81                                   // ...
82   lua_getglobal(current_l, "sol");
83                                   // ... sol
84   lua_getfield(current_l, -1, "main");
85                                   // ... sol main
86   lua_setfield(current_l, LUA_REGISTRYINDEX, main_module_name.c_str());
87                                   // ... sol
88   lua_pop(current_l, 1);
89                                   // ...
90 }
91 
92 /**
93  * \brief Pushes the sol.main table onto the stack.
94  * \param l A Lua state.
95  */
push_main(lua_State * l)96 void LuaContext::push_main(lua_State* l) {
97 
98   lua_getfield(l, LUA_REGISTRYINDEX, main_module_name.c_str());
99 }
100 
101 /**
102  * \brief Returns whether a value is the sol.main table.
103  * \param l A Lua context.
104  * \param index An index in the stack.
105  * \return \c true if the value at this index is sol.main.
106  */
is_main(lua_State * l,int index)107 bool LuaContext::is_main(lua_State* l, int index) {
108   push_main(l);
109   bool result = lua_equal(l, index, -1);
110   lua_pop(l, 1);
111   return result;
112 }
113 
114 /**
115  * \brief Implementation of sol.main.get_solarus_version().
116  * \param l The Lua context that is calling this function.
117  * \return Number of values to return to Lua.
118  */
main_api_get_solarus_version(lua_State * l)119 int LuaContext::main_api_get_solarus_version(lua_State* l) {
120 
121   return state_boundary_handle(l, [&] {
122     const std::string& solarus_version = SOLARUS_VERSION;
123 
124     push_string(l, solarus_version);
125     return 1;
126   });
127 }
128 
129 /**
130  * \brief Implementation of sol.main.get_quest_version().
131  * \param l The Lua context that is calling this function.
132  * \return Number of values to return to Lua.
133  */
main_api_get_quest_version(lua_State * l)134 int LuaContext::main_api_get_quest_version(lua_State* l) {
135 
136   return state_boundary_handle(l, [&] {
137     const std::string& quest_version = CurrentQuest::get_properties().get_quest_version();
138 
139     if (quest_version.empty()) {
140       lua_pushnil(l);
141     }
142     else {
143       push_string(l, quest_version);
144     }
145     return 1;
146   });
147 }
148 
149 /**
150  * \brief Implementation of sol.main.get_quest_format().
151  * \param l The Lua context that is calling this function.
152  * \return Number of values to return to Lua.
153  */
main_api_get_quest_format(lua_State * l)154 int LuaContext::main_api_get_quest_format(lua_State* l) {
155 
156   return state_boundary_handle(l, [&] {
157     const std::string& quest_format = CurrentQuest::get_properties().get_solarus_version();
158 
159     push_string(l, quest_format);
160     return 1;
161   });
162 }
163 
164 /**
165  * \brief Implementation of sol.main.load_file().
166  * \param l The Lua context that is calling this function.
167  * \return Number of values to return to Lua.
168  */
main_api_load_file(lua_State * l)169 int LuaContext::main_api_load_file(lua_State *l) {
170 
171   return state_boundary_handle(l, [&] {
172     const std::string& file_name = LuaTools::check_string(l, 1);
173 
174     if (!get().load_file(file_name)) {
175       lua_pushnil(l);
176     }
177 
178     return 1;
179   });
180 }
181 
182 /**
183  * \brief Implementation of sol.main.do_file().
184  * \param l The Lua context that is calling this function.
185  * \return Number of values to return to Lua.
186  */
main_api_do_file(lua_State * l)187 int LuaContext::main_api_do_file(lua_State *l) {
188 
189   return state_boundary_handle(l, [&] {
190     const std::string& file_name = LuaTools::check_string(l, 1);
191 
192     get().do_file(file_name);
193 
194     return 0;
195   });
196 }
197 
198 /**
199  * \brief Implementation of sol.main.reset().
200  * \param l the Lua context that is calling this function
201  * \return number of values to return to Lua
202  */
main_api_reset(lua_State * l)203 int LuaContext::main_api_reset(lua_State* l) {
204 
205   return state_boundary_handle(l, [&] {
206     get().get_main_loop().set_resetting();
207 
208     return 0;
209   });
210 }
211 
212 /**
213  * \brief Implementation of sol.main.exit().
214  * \param l the Lua context that is calling this function
215  * \return number of values to return to Lua
216  */
main_api_exit(lua_State * l)217 int LuaContext::main_api_exit(lua_State* l) {
218 
219   return state_boundary_handle(l, [&] {
220     get().get_main_loop().set_exiting();
221 
222     return 0;
223   });
224 }
225 
226 /**
227  * \brief Implementation of sol.main.get_elapsed_time().
228  * \param l The Lua context that is calling this function.
229  * \return Number of values to return to Lua.
230  */
main_api_get_elapsed_time(lua_State * l)231 int LuaContext::main_api_get_elapsed_time(lua_State* l) {
232 
233   return state_boundary_handle(l, [&] {
234     uint32_t elapsed_time = System::now();
235 
236     lua_pushinteger(l, elapsed_time);
237     return 1;
238   });
239 }
240 
241 /**
242  * \brief Implementation of sol.main.get_quest_write_dir().
243  * \param l the Lua context that is calling this function
244  * \return number of values to return to Lua
245  */
main_api_get_quest_write_dir(lua_State * l)246 int LuaContext::main_api_get_quest_write_dir(lua_State* l) {
247 
248   return state_boundary_handle(l, [&] {
249     const std::string& quest_write_dir = QuestFiles::get_quest_write_dir();
250 
251     if (quest_write_dir.empty()) {
252       lua_pushnil(l);
253     }
254     else {
255       push_string(l, quest_write_dir);
256     }
257     return 1;
258   });
259 }
260 
261 /**
262  * \brief Implementation of sol.main.set_quest_write_dir().
263  * \param l the Lua context that is calling this function
264  * \return number of values to return to Lua
265  */
main_api_set_quest_write_dir(lua_State * l)266 int LuaContext::main_api_set_quest_write_dir(lua_State* l) {
267 
268   return state_boundary_handle(l, [&] {
269     const std::string& quest_write_dir = LuaTools::opt_string(l, 1, "");
270 
271     QuestFiles::set_quest_write_dir(quest_write_dir);
272 
273     return 0;
274   });
275 }
276 
277 /**
278  * \brief Implementation of sol.main.load_settings().
279  * \param l the Lua context that is calling this function
280  * \return number of values to return to Lua
281  */
main_api_load_settings(lua_State * l)282 int LuaContext::main_api_load_settings(lua_State* l) {
283 
284   return state_boundary_handle(l, [&] {
285     std::string file_name = LuaTools::opt_string(l, 1, "settings.dat");
286 
287     if (QuestFiles::get_quest_write_dir().empty()) {
288       LuaTools::error(l, "Cannot load settings: no write directory was specified in quest.dat");
289     }
290 
291     bool success = false;
292     if (QuestFiles::data_file_exists(file_name) &&
293         !QuestFiles::data_file_is_dir(file_name)) {
294       Settings settings;
295       success = settings.load(file_name);
296       if (success) {
297         settings.apply_to_quest();
298       }
299     }
300 
301     lua_pushboolean(l, success);
302     return 1;
303   });
304 }
305 
306 /**
307  * \brief Implementation of sol.main.save_settings().
308  * \param l the Lua context that is calling this function
309  * \return number of values to return to Lua
310  */
main_api_save_settings(lua_State * l)311 int LuaContext::main_api_save_settings(lua_State* l) {
312 
313   return state_boundary_handle(l, [&] {
314     std::string file_name = LuaTools::opt_string(l, 1, "settings.dat");
315 
316     if (QuestFiles::get_quest_write_dir().empty()) {
317       LuaTools::error(l, "Cannot save settings: no write directory was specified in quest.dat");
318     }
319 
320     Settings settings;
321     settings.set_from_quest();
322     bool success = settings.save(file_name);
323 
324     lua_pushboolean(l, success);
325     return 1;
326   });
327 }
328 
329 /**
330  * \brief Implementation of sol.main.get_distance().
331  * \param l the Lua context that is calling this function
332  * \return number of values to return to Lua
333  */
main_api_get_distance(lua_State * l)334 int LuaContext::main_api_get_distance(lua_State* l) {
335 
336   return state_boundary_handle(l, [&] {
337     int x1 = LuaTools::check_int(l, 1);
338     int y1 = LuaTools::check_int(l, 2);
339     int x2 = LuaTools::check_int(l, 3);
340     int y2 = LuaTools::check_int(l, 4);
341 
342     int distance = (int) Geometry::get_distance(x1, y1, x2, y2);
343 
344     lua_pushinteger(l, distance);
345     return 1;
346   });
347 }
348 
349 /**
350  * \brief Implementation of sol.main.get_angle().
351  * \param l the Lua context that is calling this function
352  * \return number of values to return to Lua
353  */
main_api_get_angle(lua_State * l)354 int LuaContext::main_api_get_angle(lua_State* l) {
355 
356   return state_boundary_handle(l, [&] {
357     int x1 = LuaTools::check_int(l, 1);
358     int y1 = LuaTools::check_int(l, 2);
359     int x2 = LuaTools::check_int(l, 3);
360     int y2 = LuaTools::check_int(l, 4);
361 
362     double angle = Geometry::get_angle(x1, y1, x2, y2);
363 
364     lua_pushnumber(l, angle);
365     return 1;
366   });
367 }
368 
369 /**
370  * \brief Implementation of sol.main.get_resource_ids().
371  * \param l The Lua context that is calling this function.
372  * \return Number of values to return to Lua.
373  */
main_api_get_resource_ids(lua_State * l)374 int LuaContext::main_api_get_resource_ids(lua_State* l) {
375 
376   return state_boundary_handle(l, [&] {
377 
378     ResourceType resource_type = LuaTools::check_enum<ResourceType>(l, 1);
379     const QuestDatabase::ResourceMap& elements = CurrentQuest::get_database().get_resource_elements(resource_type);
380 
381     // Build a Lua array containing the ids.
382     lua_settop(l, 0);
383     lua_newtable(l);
384     int i = 1;
385     for (const std::pair<std::string, std::string>& kvp : elements) {
386       const std::string& id = kvp.first;
387       push_string(l, id);
388       lua_rawseti(l, 1, i);
389       ++i;
390     }
391 
392     return 1;
393   });
394 }
395 
396 /**
397  * \brief Implementation of sol.main.resource_exists().
398  * \param l The Lua context that is calling this function.
399  * \return Number of values to return to Lua.
400  */
main_api_resource_exists(lua_State * l)401 int LuaContext::main_api_resource_exists(lua_State* l) {
402 
403   return state_boundary_handle(l, [&] {
404 
405     ResourceType resource_type = LuaTools::check_enum<ResourceType>(l, 1);
406     const std::string& id = LuaTools::check_string(l, 2);
407 
408     lua_pushboolean(l, CurrentQuest::resource_exists(resource_type, id));
409     return 1;
410   });
411 }
412 
413 /**
414  * \brief Implementation of sol.main.get_resource_description().
415  * \param l The Lua context that is calling this function.
416  * \return Number of values to return to Lua.
417  */
main_api_get_resource_description(lua_State * l)418 int LuaContext::main_api_get_resource_description(lua_State* l) {
419 
420   return state_boundary_handle(l, [&] {
421 
422     ResourceType resource_type = LuaTools::check_enum<ResourceType>(l, 1);
423     const std::string& id = LuaTools::check_string(l, 2);
424 
425     const QuestDatabase& database = CurrentQuest::get_database();
426     if (!database.resource_exists(resource_type, id)) {
427       LuaTools::arg_error(l, 2, "No such resource element: '" + id + "'");
428     }
429 
430     const std::string& description = database.get_description(resource_type, id);
431     if (description.empty()) {
432       lua_pushnil(l);
433     }
434     else {
435       push_string(l, description);
436     }
437     return 1;
438   });
439 }
440 
441 /**
442  * \brief Implementation of sol.main.add_resource().
443  * \param l The Lua context that is calling this function.
444  * \return Number of values to return to Lua.
445  */
main_api_add_resource(lua_State * l)446 int LuaContext::main_api_add_resource(lua_State* l) {
447 
448   return state_boundary_handle(l, [&] {
449 
450     ResourceType resource_type = LuaTools::check_enum<ResourceType>(l, 1);
451     const std::string& id = LuaTools::check_string(l, 2);
452     const std::string& description = LuaTools::opt_string(l, 3, "");
453 
454     QuestDatabase& database = CurrentQuest::get_database();
455     if (database.resource_exists(resource_type, id)) {
456       LuaTools::arg_error(l, 2, "Resource element already exists: '" + id + "'");
457     }
458 
459     database.add(resource_type, id, description);
460 
461     return 0;
462   });
463 }
464 
465 /**
466  * \brief Implementation of sol.main.remove_resource().
467  * \param l The Lua context that is calling this function.
468  * \return Number of values to return to Lua.
469  */
main_api_remove_resource(lua_State * l)470 int LuaContext::main_api_remove_resource(lua_State* l) {
471 
472   return state_boundary_handle(l, [&] {
473 
474     ResourceType resource_type = LuaTools::check_enum<ResourceType>(l, 1);
475     const std::string& id = LuaTools::check_string(l, 2);
476 
477     QuestDatabase& database = CurrentQuest::get_database();
478     if (!database.resource_exists(resource_type, id)) {
479       LuaTools::arg_error(l, 2, "No such resource element: '" + id + "'");
480     }
481 
482     database.remove(resource_type, id);
483 
484     return 0;
485   });
486 }
487 
488 /**
489  * \brief Implementation of sol.main.get_type().
490  * \param l The Lua context that is calling this function.
491  * \return Number of values to return to Lua.
492  */
main_api_get_type(lua_State * l)493 int LuaContext::main_api_get_type(lua_State* l) {
494 
495   return state_boundary_handle(l, [&] {
496 
497     luaL_checkany(l, 1);
498     push_string(l, LuaTools::get_type_name(l, 1));
499     return 1;
500   });
501 }
502 
503 /**
504  * \brief Implementation of sol.main.get_metatable().
505  * \param l The Lua context that is calling this function.
506  * \return Number of values to return to Lua.
507  */
main_api_get_metatable(lua_State * l)508 int LuaContext::main_api_get_metatable(lua_State* l) {
509 
510   return state_boundary_handle(l, [&] {
511     const std::string& type_name = LuaTools::check_string(l, 1);
512 
513     luaL_getmetatable(l, (std::string("sol.") + type_name).c_str());
514     return 1;
515   });
516 }
517 
518 /**
519  * \brief Implementation of sol.main.get_os().
520  * \param l The Lua context that is calling this function.
521  * \return Number of values to return to Lua.
522  */
main_api_get_os(lua_State * l)523 int LuaContext::main_api_get_os(lua_State* l) {
524 
525   return state_boundary_handle(l, [&] {
526     const std::string& os = System::get_os();
527 
528     push_string(l, os);
529     return 1;
530   });
531 }
532 
533 /**
534  * \brief Implementation of sol.main.get_game().
535  * \param l The Lua context that is calling this function.
536  * \return Number of values to return to Lua.
537  */
main_api_get_game(lua_State * l)538 int LuaContext::main_api_get_game(lua_State* l) {
539 
540   return state_boundary_handle(l, [&] {
541     LuaContext& lua_context = get();
542 
543     Game* game = lua_context.get_main_loop().get_game();
544     if (game == nullptr) {
545       lua_pushnil(l);
546     }
547     else {
548       push_game(l, game->get_savegame());
549     }
550     return 1;
551   });
552 }
553 
554 /**
555  * \brief Calls sol.main.on_started() if it exists.
556  *
557  * This function is called when the engine requests Lua to show an
558  * initial screen, i.e. at the beginning of the program
559  * or when the program is reset.
560  */
main_on_started()561 void LuaContext::main_on_started() {
562 
563   push_main(current_l);
564   on_started();
565   lua_pop(current_l, 1);
566 }
567 
568 /**
569  * \brief Calls sol.main.on_finished() if it exists.
570  *
571  * This function is called when the program is reset or stopped.
572  */
main_on_finished()573 void LuaContext::main_on_finished() {
574 
575   push_main(current_l);
576   on_finished();
577   remove_timers(-1);  // Stop timers associated to sol.main.
578   remove_menus(-1);  // Stop menus associated to sol.main.
579   lua_pop(current_l, 1);
580 }
581 
582 /**
583  * \brief Calls sol.main.on_update() if it exists.
584  *
585  * This function is called at each cycle by the main loop.
586  */
main_on_update()587 void LuaContext::main_on_update() {
588   current_l = main_l; //Always execute main on the main thread
589   push_main(current_l);
590   on_update();
591   menus_on_update(-1);
592   lua_pop(current_l, 1);
593 }
594 
595 /**
596  * \brief Calls sol.main.on_draw() if it exists.
597  * \param dst_surface The destination surface.
598  */
main_on_draw(const SurfacePtr & dst_surface)599 void LuaContext::main_on_draw(const SurfacePtr& dst_surface) {
600 
601   push_main(current_l);
602   on_draw(dst_surface);
603   menus_on_draw(-1, dst_surface);
604   lua_pop(current_l, 1);
605 }
606 
607 /**
608  * \brief Notifies Lua that an input event has just occurred.
609  *
610  * The appropriate callback in sol.main is triggered if it exists.
611  *
612  * \param event The input event to handle.
613  * \return \c true if the event was handled and should stop being propagated.
614  */
main_on_input(const InputEvent & event)615 bool LuaContext::main_on_input(const InputEvent& event) {
616 
617   push_main(current_l);
618   bool handled = on_input(event);
619   if (!handled) {
620     handled = menus_on_input(-1, event);
621   }
622   lua_pop(current_l, 1);
623   return handled;
624 }
625 
626 }
627 
628