world_select.cpp 7.7 KB

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