1 | // SuperTux |
2 | // Copyright (C) 2006 Matthias Braun <matze@braunis.de> |
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 | |
17 | #include <physfs.h> |
18 | |
19 | #include "object/block.hpp" |
20 | |
21 | #include "audio/sound_manager.hpp" |
22 | #include "badguy/badguy.hpp" |
23 | #include "badguy/bomb.hpp" |
24 | #include "math/random.hpp" |
25 | #include "object/coin.hpp" |
26 | #include "object/growup.hpp" |
27 | #include "object/player.hpp" |
28 | #include "object/sprite_particle.hpp" |
29 | #include "sprite/sprite.hpp" |
30 | #include "sprite/sprite_manager.hpp" |
31 | #include "supertux/constants.hpp" |
32 | #include "supertux/sector.hpp" |
33 | #include "util/reader_mapping.hpp" |
34 | #include "util/writer.hpp" |
35 | |
36 | static const float BOUNCY_BRICK_MAX_OFFSET = 8; |
37 | static const float BOUNCY_BRICK_SPEED = 90; |
38 | static const float BUMP_ROTATION_ANGLE = 10; |
39 | |
40 | Block::Block(SpritePtr newsprite) : |
41 | m_sprite(std::move(newsprite)), |
42 | m_sprite_name(), |
43 | m_default_sprite_name(), |
44 | m_bouncing(false), |
45 | m_breaking(false), |
46 | m_bounce_dir(0), |
47 | m_bounce_offset(0), |
48 | m_original_y(-1) |
49 | { |
50 | m_col.m_bbox.set_size(32, 32.1f); |
51 | set_group(COLGROUP_STATIC); |
52 | SoundManager::current()->preload("sounds/upgrade.wav" ); |
53 | SoundManager::current()->preload("sounds/brick.wav" ); |
54 | } |
55 | |
56 | Block::Block(const ReaderMapping& mapping, const std::string& sprite_file) : |
57 | m_sprite(), |
58 | m_sprite_name(), |
59 | m_default_sprite_name(), |
60 | m_bouncing(false), |
61 | m_breaking(false), |
62 | m_bounce_dir(0), |
63 | m_bounce_offset(0), |
64 | m_original_y(-1) |
65 | { |
66 | mapping.get("x" , m_col.m_bbox.get_left()); |
67 | mapping.get("y" , m_col.m_bbox.get_top()); |
68 | |
69 | std::string sf; |
70 | mapping.get("sprite" , sf); |
71 | if (sf.empty() || !PHYSFS_exists(sf.c_str())) { |
72 | sf = sprite_file; |
73 | } |
74 | m_sprite = SpriteManager::current()->create(sf); |
75 | m_sprite_name = sf; |
76 | m_default_sprite_name = m_sprite_name; |
77 | |
78 | m_col.m_bbox.set_size(32, 32.1f); |
79 | set_group(COLGROUP_STATIC); |
80 | SoundManager::current()->preload("sounds/upgrade.wav" ); |
81 | SoundManager::current()->preload("sounds/brick.wav" ); |
82 | } |
83 | |
84 | HitResponse |
85 | Block::collision(GameObject& other, const CollisionHit& ) |
86 | { |
87 | auto player = dynamic_cast<Player*> (&other); |
88 | if (player) { |
89 | if (player->get_bbox().get_top() > m_col.m_bbox.get_bottom() - SHIFT_DELTA) { |
90 | hit(*player); |
91 | } |
92 | } |
93 | |
94 | // only interact with other objects if... |
95 | // 1) we are bouncing |
96 | // 2) the object is not portable (either never or not currently); |
97 | // make an exception if it's a portable badguy - those get killed |
98 | // 3) the object is being hit from below (baguys don't get killed for activating boxes) |
99 | auto badguy = dynamic_cast<BadGuy*> (&other); |
100 | auto portable = dynamic_cast<Portable*> (&other); |
101 | auto moving_object = dynamic_cast<MovingObject*> (&other); |
102 | auto bomb = dynamic_cast<Bomb*> (&other); |
103 | bool is_portable = ((portable != nullptr) && portable->is_portable()); |
104 | bool is_bomb = (bomb != nullptr); // bombs need to explode, although they are considered portable |
105 | bool hit_mo_from_below = ((moving_object == nullptr) || (moving_object->get_bbox().get_bottom() < (m_col.m_bbox.get_top() + SHIFT_DELTA))); |
106 | if (m_bouncing && (!is_portable || badguy || is_bomb) && hit_mo_from_below) { |
107 | |
108 | // Badguys get killed |
109 | if (badguy) { |
110 | badguy->kill_fall(); |
111 | } |
112 | |
113 | // Coins get collected |
114 | auto coin = dynamic_cast<Coin*> (&other); |
115 | if (coin) { |
116 | coin->collect(); |
117 | } |
118 | |
119 | //Eggs get jumped |
120 | auto growup = dynamic_cast<GrowUp*> (&other); |
121 | if (growup) { |
122 | growup->do_jump(); |
123 | } |
124 | |
125 | } |
126 | |
127 | return FORCE_MOVE; |
128 | } |
129 | |
130 | void |
131 | Block::update(float dt_sec) |
132 | { |
133 | if (!m_bouncing) |
134 | return; |
135 | |
136 | float offset = m_original_y - get_pos().y; |
137 | if (offset > BOUNCY_BRICK_MAX_OFFSET) { |
138 | m_bounce_dir = BOUNCY_BRICK_SPEED; |
139 | m_col.m_movement = Vector(0, m_bounce_dir * dt_sec); |
140 | if (m_breaking){ |
141 | break_me(); |
142 | } |
143 | } else if (offset < BOUNCY_BRICK_SPEED * dt_sec && m_bounce_dir > 0) { |
144 | m_col.m_movement = Vector(0, offset); |
145 | m_bounce_dir = 0; |
146 | m_bouncing = false; |
147 | m_sprite->set_angle(0); |
148 | } else { |
149 | m_col.m_movement = Vector(0, m_bounce_dir * dt_sec); |
150 | } |
151 | } |
152 | |
153 | void |
154 | Block::draw(DrawingContext& context) |
155 | { |
156 | m_sprite->draw(context.color(), get_pos(), LAYER_OBJECTS+1); |
157 | } |
158 | |
159 | void |
160 | Block::start_bounce(GameObject* hitter) |
161 | { |
162 | if (m_original_y == -1){ |
163 | m_original_y = m_col.m_bbox.get_top(); |
164 | } |
165 | m_bouncing = true; |
166 | m_bounce_dir = -BOUNCY_BRICK_SPEED; |
167 | m_bounce_offset = 0; |
168 | |
169 | MovingObject* hitter_mo = dynamic_cast<MovingObject*>(hitter); |
170 | if (hitter_mo) { |
171 | float center_of_hitter = hitter_mo->get_bbox().get_middle().x; |
172 | float offset = (m_col.m_bbox.get_middle().x - center_of_hitter)*2 / m_col.m_bbox.get_width(); |
173 | m_sprite->set_angle(BUMP_ROTATION_ANGLE*offset); |
174 | } |
175 | } |
176 | |
177 | void |
178 | Block::start_break(GameObject* hitter) |
179 | { |
180 | start_bounce(hitter); |
181 | m_breaking = true; |
182 | } |
183 | |
184 | void |
185 | Block::break_me() |
186 | { |
187 | const auto gravity = Sector::get().get_gravity() * 100; |
188 | Vector pos = get_pos() + Vector(16.0f, 16.0f); |
189 | |
190 | for (const char* action : {"piece1" , "piece2" , "piece3" , "piece4" , "piece5" , "piece6" }) |
191 | { |
192 | Vector velocity(graphicsRandom.randf(-100, 100), |
193 | graphicsRandom.randf(-400, -300)); |
194 | Sector::get().add<SpriteParticle>(m_sprite->clone(), action, |
195 | pos, ANCHOR_MIDDLE, |
196 | velocity, Vector(0, gravity), |
197 | LAYER_OBJECTS + 1); |
198 | } |
199 | |
200 | remove_me(); |
201 | } |
202 | |
203 | ObjectSettings |
204 | Block::get_settings() |
205 | { |
206 | ObjectSettings result = MovingObject::get_settings(); |
207 | |
208 | result.add_sprite(_("Sprite" ), &m_sprite_name, "sprite" , m_default_sprite_name); |
209 | |
210 | return result; |
211 | } |
212 | |
213 | void Block::after_editor_set() |
214 | { |
215 | m_sprite = SpriteManager::current()->create(m_sprite_name); |
216 | } |
217 | |
218 | /* EOF */ |
219 | |