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
36static const float BOUNCY_BRICK_MAX_OFFSET = 8;
37static const float BOUNCY_BRICK_SPEED = 90;
38static const float BUMP_ROTATION_ANGLE = 10;
39
40Block::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
56Block::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
84HitResponse
85Block::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
130void
131Block::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
153void
154Block::draw(DrawingContext& context)
155{
156 m_sprite->draw(context.color(), get_pos(), LAYER_OBJECTS+1);
157}
158
159void
160Block::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
177void
178Block::start_break(GameObject* hitter)
179{
180 start_bounce(hitter);
181 m_breaking = true;
182}
183
184void
185Block::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
203ObjectSettings
204Block::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
213void Block::after_editor_set()
214{
215 m_sprite = SpriteManager::current()->create(m_sprite_name);
216}
217
218/* EOF */
219