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 "object/coin.hpp"
18
19#include "audio/sound_manager.hpp"
20#include "audio/sound_source.hpp"
21#include "editor/editor.hpp"
22#include "object/bouncy_coin.hpp"
23#include "object/player.hpp"
24#include "object/tilemap.hpp"
25#include "supertux/level.hpp"
26#include "supertux/sector.hpp"
27#include "util/reader_mapping.hpp"
28#include "util/writer.hpp"
29
30Coin::Coin(const Vector& pos) :
31 MovingSprite(pos, "images/objects/coin/coin.sprite", LAYER_OBJECTS - 1, COLGROUP_TOUCHABLE),
32 PathObject(),
33 m_offset(),
34 m_from_tilemap(false),
35 m_add_path(false),
36 m_physic(),
37 m_collect_script()
38{
39 SoundManager::current()->preload("sounds/coin.wav");
40}
41
42Coin::Coin(const ReaderMapping& reader) :
43 MovingSprite(reader, "images/objects/coin/coin.sprite", LAYER_OBJECTS - 1, COLGROUP_TOUCHABLE),
44 PathObject(),
45 m_offset(),
46 m_from_tilemap(false),
47 m_add_path(false),
48 m_physic(),
49 m_collect_script()
50{
51 init_path(reader, true);
52
53 reader.get("collect-script", m_collect_script, "");
54
55 SoundManager::current()->preload("sounds/coin.wav");
56}
57
58void
59Coin::finish_construction()
60{
61 if (get_path())
62 {
63 Vector v = get_path()->get_base();
64 set_pos(v);
65 }
66
67 m_add_path = get_walker() && get_path() && get_path()->is_valid();
68}
69
70void
71Coin::update(float dt_sec)
72{
73 // if we have a path to follow, follow it
74 if (get_walker()) {
75 Vector v;
76 if (m_from_tilemap)
77 {
78 v = m_offset + get_walker()->get_pos();
79 }
80 else
81 {
82 get_walker()->update(dt_sec);
83 v = get_walker()->get_pos();
84 }
85
86 if (get_path()->is_valid()) {
87 m_col.m_movement = v - get_pos();
88 }
89 }
90}
91
92void
93Coin::editor_update()
94{
95 if (get_walker()) {
96 if (m_from_tilemap) {
97 set_pos(m_offset + get_walker()->get_pos());
98 } else {
99 set_pos(get_walker()->get_pos());
100 }
101 }
102}
103
104void
105Coin::collect()
106{
107 static Timer sound_timer;
108 static int pitch_one = 128;
109 static float last_pitch = 1;
110 float pitch = 1;
111
112 int tile = static_cast<int>(get_pos().y / 32);
113
114 if (!sound_timer.started()) {
115 pitch_one = tile;
116 pitch = 1;
117 last_pitch = 1;
118 } else if (sound_timer.get_timegone() < 0.02f) {
119 pitch = last_pitch;
120 } else {
121 switch ((pitch_one - tile) % 7) {
122 case -6:
123 pitch = 1.f/2; // C
124 break;
125 case -5:
126 pitch = 5.f/8; // E
127 break;
128 case -4:
129 pitch = 4.f/6; // F
130 break;
131 case -3:
132 pitch = 3.f/4; // G
133 break;
134 case -2:
135 pitch = 5.f/6; // A
136 break;
137 case -1:
138 pitch = 9.f/10; // Bb
139 break;
140 case 0:
141 pitch = 1.f; // c
142 break;
143 case 1:
144 pitch = 9.f/8; // d
145 break;
146 case 2:
147 pitch = 5.f/4; // e
148 break;
149 case 3:
150 pitch = 4.f/3; // f
151 break;
152 case 4:
153 pitch = 3.f/2; // g
154 break;
155 case 5:
156 pitch = 5.f/3; // a
157 break;
158 case 6:
159 pitch = 9.f/5; // bb
160 break;
161 }
162 last_pitch = pitch;
163 }
164 sound_timer.start(1);
165
166 std::unique_ptr<SoundSource> soundSource = SoundManager::current()->create_sound_source("sounds/coin.wav");
167 soundSource->set_position(get_pos());
168 soundSource->set_pitch(pitch);
169 soundSource->play();
170 SoundManager::current()->manage_source(std::move(soundSource));
171
172 Sector::get().get_player().get_status().add_coins(1, false);
173 Sector::get().add<BouncyCoin>(get_pos(), false, get_sprite_name());
174 Sector::get().get_level().m_stats.m_coins++;
175 remove_me();
176
177 if (!m_collect_script.empty()) {
178 Sector::get().run_script(m_collect_script, "collect-script");
179 }
180}
181
182HitResponse
183Coin::collision(GameObject& other, const CollisionHit& )
184{
185 auto player = dynamic_cast<Player*>(&other);
186 if (player == nullptr)
187 return ABORT_MOVE;
188
189 collect();
190 return ABORT_MOVE;
191}
192
193/* The following defines a coin subject to gravity */
194HeavyCoin::HeavyCoin(const Vector& pos, const Vector& init_velocity) :
195 Coin(pos),
196 m_physic()
197{
198 m_physic.enable_gravity(true);
199 SoundManager::current()->preload("sounds/coin2.ogg");
200 set_group(COLGROUP_MOVING);
201 m_physic.set_velocity(init_velocity);
202}
203
204HeavyCoin::HeavyCoin(const ReaderMapping& reader) :
205 Coin(reader),
206 m_physic()
207{
208 m_physic.enable_gravity(true);
209 SoundManager::current()->preload("sounds/coin2.ogg");
210 set_group(COLGROUP_MOVING);
211}
212
213void
214HeavyCoin::update(float dt_sec)
215{
216 // enable physics
217 m_col.m_movement = m_physic.get_movement(dt_sec);
218}
219
220void
221HeavyCoin::collision_solid(const CollisionHit& hit)
222{
223 float clink_threshold = 100.0f; // sets the minimum speed needed to result in collision noise
224 //TODO: colliding HeavyCoins should have their own unique sound
225 if (hit.bottom) {
226 if (m_physic.get_velocity_y() > clink_threshold)
227 SoundManager::current()->play("sounds/coin2.ogg");
228 if (m_physic.get_velocity_y() > 200) {// lets some coins bounce
229 m_physic.set_velocity_y(-99);
230 } else {
231 m_physic.set_velocity_y(0);
232 m_physic.set_velocity_x(0);
233 }
234 }
235 if (hit.right || hit.left) {
236 if (m_physic.get_velocity_x() > clink_threshold || m_physic.get_velocity_x() < clink_threshold)
237 SoundManager::current()->play("sounds/coin2.ogg");
238 m_physic.set_velocity_x(-m_physic.get_velocity_x());
239 }
240 if (hit.top) {
241 if (m_physic.get_velocity_y() < clink_threshold)
242 SoundManager::current()->play("sounds/coin2.ogg");
243 m_physic.set_velocity_y(-m_physic.get_velocity_y());
244 }
245}
246
247void
248Coin::move_to(const Vector& pos)
249{
250 Vector shift = pos - m_col.m_bbox.p1();
251 if (get_path()) {
252 get_path()->move_by(shift);
253 }
254 set_pos(pos);
255}
256
257ObjectSettings
258Coin::get_settings()
259{
260 ObjectSettings result = MovingSprite::get_settings();
261
262 result.add_path_ref(_("Path"), get_path_ref(), "path-ref");
263 m_add_path = get_walker() && get_path() && get_path()->is_valid();
264 result.add_bool(_("Following path"), &m_add_path);
265
266 if (get_walker() && get_path()->is_valid()) {
267 result.add_walk_mode(_("Path Mode"), &get_path()->m_mode, {}, {});
268 }
269
270 result.add_script(_("Collect script"), &m_collect_script, "collect-script");
271
272 result.reorder({"collect-script", "path-ref"});
273
274 return result;
275}
276
277void
278Coin::after_editor_set()
279{
280 MovingSprite::after_editor_set();
281
282 if (get_walker() && get_path()->is_valid()) {
283 if (!m_add_path) {
284 get_path()->m_nodes.clear();
285 }
286 } else {
287 if (m_add_path) {
288 init_path_pos(m_col.m_bbox.p1());
289 }
290 }
291}
292
293ObjectSettings
294HeavyCoin::get_settings()
295{
296 auto result = MovingSprite::get_settings();
297
298 result.add_script(_("Collect script"), &m_collect_script, "collect-script");
299
300 result.reorder({"collect-script", "sprite", "x", "y"});
301
302 return result;
303}
304
305void
306HeavyCoin::after_editor_set()
307{
308 MovingSprite::after_editor_set();
309}
310
311/* EOF */
312