key.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // SuperTux
  2. // Copyright (C) 2022 Daniel Ward <weluvgoatz@gmail.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 "object/key.hpp"
  17. #include "audio/sound_manager.hpp"
  18. #include "math/random.hpp"
  19. #include "math/util.hpp"
  20. #include "object/player.hpp"
  21. #include "object/sprite_particle.hpp"
  22. #include "sprite/sprite.hpp"
  23. #include "sprite/sprite_manager.hpp"
  24. #include "supertux/sector.hpp"
  25. #include "trigger/door.hpp"
  26. #include "util/reader_mapping.hpp"
  27. Key::Key(const ReaderMapping& reader) :
  28. MovingSprite(reader, "images/objects/keys/key.sprite", LAYER_OBJECTS, COLGROUP_TOUCHABLE),
  29. m_pos_list(),
  30. m_collected(),
  31. m_state(KeyState::NORMAL),
  32. m_wait_timer(),
  33. m_unlock_timer(),
  34. m_physic(),
  35. m_chain_pos(1),
  36. m_my_door_pos(0.f, 0.f),
  37. m_color(Color::WHITE),
  38. m_owner(),
  39. m_lightsprite(SpriteManager::current()->create("images/objects/lightmap_light/lightmap_light-small.sprite")),
  40. m_total_time_elapsed(),
  41. m_target_speed()
  42. {
  43. std::vector<float> vColor;
  44. if (reader.get("color", vColor)) {
  45. m_color = Color(vColor);
  46. }
  47. m_lightsprite->set_blend(Blend::ADD);
  48. m_lightsprite->set_color(m_color);
  49. // TODO: Add proper sound
  50. SoundManager::current()->preload("sounds/metal_hit.ogg");
  51. m_sprite->set_color(m_color);
  52. m_physic.enable_gravity(false);
  53. }
  54. void
  55. Key::update(float dt_sec)
  56. {
  57. m_target_speed = 12.f * std::cos(m_total_time_elapsed * 2.f);
  58. if (m_state == KeyState::NORMAL)
  59. {
  60. m_total_time_elapsed += dt_sec;
  61. m_col.set_movement(Vector(0.f, m_target_speed*dt_sec));
  62. }
  63. float px = graphicsRandom.randf(get_bbox().get_left(), get_bbox().get_right());
  64. float py = graphicsRandom.randf(get_bbox().get_top(), get_bbox().get_bottom());
  65. Vector ppos = Vector(px, py);
  66. int upper_limit = m_state == KeyState::NORMAL ? 20 : 50;
  67. bool spawn_particle_now = (graphicsRandom.rand(0, upper_limit) == 5);
  68. if (spawn_particle_now)
  69. {
  70. Sector::get().add<SpriteParticle>(
  71. "images/particles/sparkle.sprite", "small",
  72. ppos, ANCHOR_MIDDLE, Vector(0, 0), Vector(0, 0), LAYER_OBJECTS + 6, false, m_color);
  73. }
  74. if (!m_owner)
  75. return;
  76. float distance = glm::length(get_pos() - m_owner->get_pos());
  77. if (m_state != KeyState::NORMAL)
  78. {
  79. m_pos_list.push_front((m_owner->get_bbox().get_middle())-
  80. Vector(get_bbox().get_width()/2.f, get_bbox().get_height()/2.f)-
  81. Vector(0.f, 10.f));
  82. }
  83. Vector m_goal_pos = m_pos_list.back();
  84. while (m_pos_list.size() > unsigned(20 * m_chain_pos))
  85. m_pos_list.pop_back();
  86. // use
  87. for (auto& door : Sector::get().get_objects_by_type<Door>()) {
  88. if (m_state == KeyState::FOLLOW && door.is_locked() && glm::length(get_pos() - door.get_pos()) < 100.f &&
  89. // color matches
  90. std::abs(door.get_lock_color().red - m_color.red) <= 0.1f &&
  91. std::abs(door.get_lock_color().green - m_color.green) <= 0.1f &&
  92. std::abs(door.get_lock_color().blue - m_color.blue) <= 0.1f) {
  93. m_owner->remove_collected_key(this);
  94. for (auto& key : Sector::get().get_objects_by_type<Key>()) {
  95. if (key.m_chain_pos > m_chain_pos)
  96. key.m_chain_pos -= 1;
  97. }
  98. door.unlock();
  99. m_my_door_pos = door.get_bbox().get_middle();
  100. m_unlock_timer.start(1.f);
  101. m_state = KeyState::FOUND;
  102. }
  103. }
  104. switch (m_state) {
  105. case NORMAL:
  106. break;
  107. case COLLECT:
  108. m_col.set_movement(Vector(0, 0));
  109. if (m_wait_timer.check())
  110. {
  111. m_wait_timer.stop();
  112. m_state = KeyState::FOLLOW;
  113. }
  114. break;
  115. case FOLLOW:
  116. m_total_time_elapsed += dt_sec;
  117. m_col.set_movement(((m_goal_pos - get_pos()) * (std::max(0.f, distance-(50.f*float(m_chain_pos)))/300.f))+
  118. Vector(0.f, m_target_speed * dt_sec));
  119. if (m_owner->is_dying())
  120. m_state = KeyState::FOUND;
  121. break;
  122. case FOUND:
  123. m_col.set_movement((m_my_door_pos - Vector(get_bbox().get_width() / 2.f, get_bbox().get_height() / 2.f) -
  124. (get_pos()))
  125. *glm::length((m_my_door_pos - get_bbox().get_middle())*0.003f));
  126. if (m_unlock_timer.check() || m_owner->is_dying())
  127. {
  128. m_unlock_timer.stop();
  129. m_physic.enable_gravity(true);
  130. m_physic.set_velocity_y(-300.f);
  131. m_physic.set_acceleration_y(500.f);
  132. spawn_use_particles();
  133. m_state = KeyState::USE;
  134. }
  135. break;
  136. case USE:
  137. m_col.set_movement(m_physic.get_movement(dt_sec));
  138. if (get_pos().y > Sector::get().get_height())
  139. remove_me();
  140. break;
  141. }
  142. GameObject::update(dt_sec);
  143. }
  144. HitResponse
  145. Key::collision(GameObject& other, const CollisionHit& hit_)
  146. {
  147. if (m_state == KeyState::NORMAL)
  148. {
  149. auto player = dynamic_cast<Player*>(&other);
  150. if (player)
  151. {
  152. spawn_use_particles();
  153. m_total_time_elapsed = 0.f;
  154. SoundManager::current()->play("sounds/metal_hit.ogg", get_pos());
  155. m_chain_pos = static_cast<int>(player->get_collected_keys().size()) + 1;
  156. player->add_collected_key(this);
  157. m_collected = true;
  158. m_wait_timer.start(0.5f);
  159. m_owner = player;
  160. m_state = KeyState::COLLECT;
  161. }
  162. }
  163. return FORCE_MOVE;
  164. }
  165. void
  166. Key::draw(DrawingContext& context)
  167. {
  168. m_sprite->draw(context.color(), get_pos(), m_layer, m_flip);
  169. m_lightsprite->draw(context.light(), m_col.m_bbox.get_middle(), m_layer+1);
  170. }
  171. ObjectSettings
  172. Key::get_settings()
  173. {
  174. ObjectSettings result = MovingSprite::get_settings();
  175. result.add_color(_("Color"), &m_color, "color", Color::WHITE);
  176. result.reorder({ "color", "name", "x", "y" });
  177. return result;
  178. }
  179. void
  180. Key::after_editor_set()
  181. {
  182. MovingSprite::after_editor_set();
  183. m_lightsprite->set_blend(Blend::ADD);
  184. m_lightsprite->set_color(m_color);
  185. m_sprite->set_color(m_color);
  186. }
  187. void
  188. Key::update_pos()
  189. {
  190. m_col.m_bbox.set_pos(m_owner->get_bbox().get_middle() -
  191. Vector(m_col.m_bbox.get_width() / 2.f, m_col.m_bbox.get_height() / 2.f - 10.f));
  192. }
  193. void
  194. Key::spawn_use_particles()
  195. {
  196. for (int i = 1; i < 9; i++)
  197. {
  198. Vector direction = glm::normalize(Vector(std::cos(float(i) * math::PI_4), std::sin(float(i) * math::PI_4)));
  199. Sector::get().add<SpriteParticle>("images/particles/sparkle.sprite", "small-key-collect",
  200. get_bbox().get_middle(),
  201. ANCHOR_MIDDLE, Vector(400.f * direction), -Vector(400.f * direction) * 2.8f, LAYER_OBJECTS + 6, false, m_color);
  202. }
  203. }
  204. /* EOF */