world_select.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. // SuperTux
  2. // Copyright (C) 2021 A. Semphris <semphris@protonmail.com>
  3. //
  4. // This program 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. // This program 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
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. #include "worldmap/world_select.hpp"
  17. #include <algorithm>
  18. #include "control/controller.hpp"
  19. #include "math/util.hpp"
  20. #include "supertux/fadetoblack.hpp"
  21. #include "supertux/resources.hpp"
  22. #include "supertux/screen_manager.hpp"
  23. #include "util/log.hpp"
  24. #include "util/reader_document.hpp"
  25. #include "util/reader_mapping.hpp"
  26. #include "video/compositor.hpp"
  27. #include "video/drawing_context.hpp"
  28. #include "video/surface.hpp"
  29. #include "worldmap/worldmap.hpp"
  30. namespace worldmap {
  31. const float WorldSelect::s_torque = 0.75f;
  32. WorldSelect::WorldSelect(const std::string& current_world_filename) :
  33. m_enabled(false),
  34. m_worlds(),
  35. m_current_world(),
  36. m_selected_world(),
  37. m_angle(),
  38. m_bkg()
  39. {
  40. std::vector<std::string> worlds;
  41. auto& vm = SquirrelVirtualMachine::current()->get_vm();
  42. SQInteger oldtop = sq_gettop(vm.get_vm());
  43. sq_pushroottable(vm.get_vm());
  44. try {
  45. vm.get_table_entry("state");
  46. vm.get_table_entry("world_select");
  47. worlds = vm.get_table_keys();
  48. } catch(const std::exception&) {}
  49. if (worlds.size() > 0)
  50. std::reverse(worlds.begin(), worlds.end());
  51. // Only worlds with a set prefix, which also are numbered starting with 1, will be ordered properly.
  52. // This is a probably a poor solution, but I can't think of any other. - Daniel
  53. std::string prefix = "";
  54. vm.get_string("prefix", prefix);
  55. if (!prefix.empty())
  56. {
  57. for (int i = 0; unsigned(i) < worlds.size(); i++)
  58. {
  59. worlds[i] = prefix + std::to_string(i+1) + "/worldmap.stwm";
  60. }
  61. }
  62. int i = 0;
  63. for (const auto& world : worlds) {
  64. sq_pushroottable(vm.get_vm());
  65. try {
  66. vm.get_table_entry("state");
  67. vm.get_table_entry("world_select");
  68. vm.get_table_entry(world);
  69. bool unlocked = false;
  70. vm.get_bool("unlocked", unlocked);
  71. WMdata wm;
  72. wm.filename = world;
  73. wm.unlocked = unlocked;
  74. ReaderDocument doc = ReaderDocument::from_file(world);
  75. if (!doc.get_root().get_mapping().get("name", wm.name))
  76. {
  77. log_warning << "No name for worldmap " << world << std::endl;
  78. continue;
  79. }
  80. std::string icon_path = "";
  81. if (!doc.get_root().get_mapping().get(unlocked ? "icon" : "icon-locked", icon_path))
  82. {
  83. log_warning << "No icon (" << (unlocked ? "unlocked" : "locked") << ") for worldmap " << world << std::endl;
  84. continue;
  85. }
  86. wm.icon = Surface::from_file(icon_path);
  87. if (!wm.icon)
  88. {
  89. log_warning << "Icon not found for worldmap " << world << ": "
  90. << icon_path << std::endl;
  91. continue;
  92. }
  93. m_worlds.push_back(wm);
  94. if (current_world_filename == world)
  95. {
  96. m_current_world = i;
  97. std::string bkg_path = "";
  98. if (doc.get_root().get_mapping().get("bkg", bkg_path))
  99. {
  100. m_bkg = Surface::from_file(bkg_path);
  101. }
  102. else
  103. {
  104. m_bkg = Surface::from_file("/images/worlds/background/default.png");
  105. }
  106. }
  107. i++;
  108. } catch(const std::exception& e) {
  109. log_info << "Exception thrown while generating world state: " << e.what() << std::endl;
  110. }
  111. }
  112. sq_settop(vm.get_vm(), oldtop);
  113. m_selected_world = m_current_world;
  114. m_angle = static_cast<float>(m_current_world) / static_cast<float>(i) * math::PI * 2;
  115. if (m_worlds.empty())
  116. {
  117. log_warning << "No maps on world select" << std::endl;
  118. }
  119. }
  120. WorldSelect::~WorldSelect()
  121. {
  122. }
  123. void
  124. WorldSelect::setup()
  125. {
  126. if (m_worlds.empty())
  127. ScreenManager::current()->pop_screen(std::make_unique<FadeToBlack>(FadeToBlack::Direction::FADEOUT, 0.25f));
  128. m_enabled = true;
  129. }
  130. void
  131. WorldSelect::leave()
  132. {
  133. m_enabled = false;
  134. }
  135. void
  136. WorldSelect::draw(Compositor& compositor)
  137. {
  138. auto& context = compositor.make_context();
  139. context.color().draw_filled_rect(context.get_rect(), Color(), -1000);
  140. context.color().draw_surface_scaled(m_bkg, context.get_viewport(), -999);
  141. std::string name_to_display;
  142. float distance = 0.f;
  143. int i = 0;
  144. for (const auto& world : m_worlds)
  145. {
  146. float angle = m_angle - static_cast<float>(i) /
  147. static_cast<float>(m_worlds.size()) * math::PI * 2;
  148. float size = 1.f + (std::cos(angle) - 1.f) / 4.f;
  149. Rectf rect = world.icon->get_region();
  150. rect = Rectf(0, 0, rect.get_width() * size / 2.f, rect.get_height() * size / 2.f);
  151. rect.move(Vector(context.get_width() / 2.f - rect.get_width() / 2.f,
  152. context.get_height() / 2.f - rect.get_height() / 2.f));
  153. rect.move(Vector(std::sin(angle) * -context.get_width() / 4.f, 0.f));
  154. PaintStyle ps;
  155. ps.set_alpha(std::cos(angle) * .5f + .5f);
  156. context.color().draw_surface_scaled(world.icon, rect,
  157. static_cast<int>(rect.get_bottom()), ps);
  158. if (std::cos(angle) > distance)
  159. {
  160. distance = std::cos(angle);
  161. name_to_display = world.unlocked ? world.name : "???";
  162. }
  163. i++;
  164. }
  165. float halfangle = 1.f / static_cast<float>(m_worlds.size()) * math::PI * 2;
  166. float o = distance * (.5f - std::cos(halfangle));
  167. context.color().draw_text(Resources::big_font, name_to_display,
  168. Vector(context.get_width() / 2.f,
  169. context.get_height() * 3.f / 4.f + pow(10.f - o * 10.f, 2.f)),
  170. FontAlignment::ALIGN_CENTER,
  171. 10,
  172. Color(1.f, 1.f, 1.f,static_cast<float>(pow(o, 2.f)) * 4.f));
  173. }
  174. void
  175. WorldSelect::update(float dt_sec, const Controller& controller)
  176. {
  177. float target = static_cast<float>(m_selected_world) /
  178. static_cast<float>(m_worlds.size()) * math::PI * 2;
  179. while (m_angle - target > math::PI)
  180. target += math::PI * 2;
  181. while (m_angle - target < -math::PI)
  182. target -= math::PI * 2;
  183. m_angle = m_angle * s_torque + target * (1.f - s_torque);
  184. if (!m_enabled)
  185. return;
  186. if (controller.pressed_any(Control::ESCAPE, Control::ACTION))
  187. {
  188. m_enabled = false;
  189. ScreenManager::current()->pop_screen(std::make_unique<FadeToBlack>(FadeToBlack::Direction::FADEOUT, 0.25f));
  190. return;
  191. }
  192. if (controller.pressed(Control::LEFT)) {
  193. m_selected_world--;
  194. // Modulo doesn't work for some reason
  195. if (m_selected_world < 0)
  196. {
  197. m_selected_world += static_cast<int>(m_worlds.size());
  198. }
  199. }
  200. if (controller.pressed(Control::RIGHT)) {
  201. m_selected_world++;
  202. m_selected_world %= static_cast<int>(m_worlds.size());
  203. }
  204. if (controller.pressed(Control::JUMP) && m_worlds[m_selected_world].unlocked) {
  205. m_enabled = false;
  206. ScreenManager::current()->pop_screen(std::make_unique<FadeToBlack>(FadeToBlack::Direction::FADEOUT, 0.25f));
  207. worldmap::WorldMap::current()->change(m_worlds[m_selected_world].filename, "", "main");
  208. return;
  209. }
  210. }
  211. IntegrationStatus
  212. WorldSelect::get_status() const
  213. {
  214. IntegrationStatus status;
  215. status.m_details.push_back("In world select");
  216. if (!m_worlds.empty())
  217. {
  218. status.m_details.push_back(m_worlds[m_current_world].name);
  219. }
  220. return status;
  221. }
  222. } // namespace worldmap
  223. /* EOF */