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