1// SuperTux - "Will-O-Wisp" Badguy
2// Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.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 "badguy/willowisp.hpp"
18
19#include "audio/sound_manager.hpp"
20#include "audio/sound_source.hpp"
21#include "editor/editor.hpp"
22#include "object/lantern.hpp"
23#include "object/player.hpp"
24#include "sprite/sprite.hpp"
25#include "supertux/game_session.hpp"
26#include "supertux/sector.hpp"
27#include "util/reader_mapping.hpp"
28#include "util/writer.hpp"
29
30static const float FLYSPEED = 64.0f; /**< speed in px per second */
31static const float TRACK_RANGE = 384.0f; /**< at what distance to start tracking the player */
32static const float VANISH_RANGE = 512.0f; /**< at what distance to stop tracking and vanish */
33static const std::string SOUNDFILE = "sounds/willowisp.wav";
34
35WillOWisp::WillOWisp(const ReaderMapping& reader) :
36 BadGuy(reader, "images/creatures/willowisp/willowisp.sprite", LAYER_FLOATINGOBJECTS,
37 "images/objects/lightmap_light/lightmap_light-small.sprite"),
38 ExposedObject<WillOWisp, scripting::WillOWisp>(this),
39 PathObject(),
40 m_mystate(STATE_IDLE),
41 m_target_sector(),
42 m_target_spawnpoint(),
43 m_hit_script(),
44 m_sound_source(),
45 m_flyspeed(),
46 m_track_range(),
47 m_vanish_range()
48{
49 if (Editor::is_active()) {
50 reader.get("sector", m_target_sector);
51 reader.get("spawnpoint", m_target_spawnpoint);
52 } else {
53 reader.get("sector", m_target_sector, "main");
54 reader.get("spawnpoint", m_target_spawnpoint, "main");
55 }
56
57 reader.get("flyspeed", m_flyspeed, FLYSPEED);
58 reader.get("track-range", m_track_range, TRACK_RANGE);
59 reader.get("vanish-range", m_vanish_range, VANISH_RANGE);
60 reader.get("hit-script", m_hit_script, "");
61
62 bool running;
63 if ( !reader.get("running", running)) running = false;
64
65 init_path(reader, running);
66
67 m_countMe = false;
68 SoundManager::current()->preload(SOUNDFILE);
69 SoundManager::current()->preload("sounds/warp.wav");
70
71 m_lightsprite->set_color(Color(0.0f, 0.2f, 0.0f));
72 m_glowing = true;
73
74 m_sprite->set_action("idle");
75}
76
77void
78WillOWisp::finish_construction()
79{
80 if (get_walker() && get_walker()->is_running()) {
81 m_mystate = STATE_PATHMOVING_TRACK;
82 }
83}
84
85void
86WillOWisp::active_update(float dt_sec)
87{
88 if (Editor::is_active() && get_path() && get_path()->is_valid()) {
89 get_walker()->update(dt_sec);
90 set_pos(get_walker()->get_pos());
91 return;
92 }
93
94 auto player = get_nearest_player();
95 if (!player) return;
96 Vector p1 = m_col.m_bbox.get_middle();
97 Vector p2 = player->get_bbox().get_middle();
98 Vector dist = (p2 - p1);
99
100 switch (m_mystate) {
101 case STATE_STOPPED:
102 break;
103
104 case STATE_IDLE:
105 if (dist.norm() <= m_track_range) {
106 m_mystate = STATE_TRACKING;
107 }
108 break;
109
110 case STATE_TRACKING:
111 if (dist.norm() > m_vanish_range) {
112 vanish();
113 } else if (dist.norm() >= 1) {
114 Vector dir_ = dist.unit();
115 m_col.m_movement = dir_ * dt_sec * m_flyspeed;
116 } else {
117 /* We somehow landed right on top of the player without colliding.
118 * Sit tight and avoid a division by zero. */
119 }
120 m_sound_source->set_position(get_pos());
121 break;
122
123 case STATE_WARPING:
124 if (m_sprite->animation_done()) {
125 remove_me();
126 }
127 break;
128
129 case STATE_VANISHING: {
130 Vector dir_ = dist.unit();
131 m_col.m_movement = dir_ * dt_sec * m_flyspeed;
132 if (m_sprite->animation_done()) {
133 remove_me();
134 }
135 break;
136 }
137
138 case STATE_PATHMOVING:
139 case STATE_PATHMOVING_TRACK:
140 if (get_walker() == nullptr)
141 return;
142 get_walker()->update(dt_sec);
143 m_col.m_movement = get_walker()->get_pos() - get_pos();
144 if (m_mystate == STATE_PATHMOVING_TRACK && dist.norm() <= m_track_range) {
145 m_mystate = STATE_TRACKING;
146 }
147 break;
148
149 default:
150 assert(false);
151 }
152}
153
154void
155WillOWisp::activate()
156{
157 if (Editor::is_active())
158 return;
159
160 m_sound_source = SoundManager::current()->create_sound_source(SOUNDFILE);
161 m_sound_source->set_position(get_pos());
162 m_sound_source->set_looping(true);
163 m_sound_source->set_gain(1.0f);
164 m_sound_source->set_reference_distance(32);
165 m_sound_source->play();
166}
167
168void
169WillOWisp::deactivate()
170{
171 m_sound_source.reset(nullptr);
172
173 switch (m_mystate) {
174 case STATE_STOPPED:
175 case STATE_IDLE:
176 case STATE_PATHMOVING:
177 case STATE_PATHMOVING_TRACK:
178 break;
179 case STATE_TRACKING:
180 m_mystate = STATE_IDLE;
181 break;
182 case STATE_WARPING:
183 case STATE_VANISHING:
184 remove_me();
185 break;
186 }
187}
188
189void
190WillOWisp::vanish()
191{
192 m_mystate = STATE_VANISHING;
193 m_sprite->set_action("vanishing", 1);
194 set_colgroup_active(COLGROUP_DISABLED);
195}
196
197bool
198WillOWisp::collides(GameObject& other, const CollisionHit& ) const {
199 auto lantern = dynamic_cast<Lantern*>(&other);
200
201 if (lantern && lantern->is_open())
202 return true;
203
204 if (dynamic_cast<Player*>(&other))
205 return true;
206
207 return false;
208}
209
210HitResponse
211WillOWisp::collision_player(Player& player, const CollisionHit& ) {
212 if (player.is_invincible())
213 return ABORT_MOVE;
214
215 if (m_mystate != STATE_TRACKING)
216 return ABORT_MOVE;
217
218 m_mystate = STATE_WARPING;
219 m_sprite->set_action("warping", 1);
220
221 if (!m_hit_script.empty()) {
222 Sector::get().run_script(m_hit_script, "hit-script");
223 } else {
224 GameSession::current()->respawn(m_target_sector, m_target_spawnpoint);
225 }
226 SoundManager::current()->play("sounds/warp.wav");
227
228 return CONTINUE;
229}
230
231void
232WillOWisp::goto_node(int node_no)
233{
234 get_walker()->goto_node(node_no);
235 if (m_mystate != STATE_PATHMOVING && m_mystate != STATE_PATHMOVING_TRACK) {
236 m_mystate = STATE_PATHMOVING;
237 }
238}
239
240void
241WillOWisp::start_moving()
242{
243 get_walker()->start_moving();
244}
245
246void
247WillOWisp::stop_moving()
248{
249 get_walker()->stop_moving();
250}
251
252void
253WillOWisp::set_state(const std::string& new_state)
254{
255 if (new_state == "stopped") {
256 m_mystate = STATE_STOPPED;
257 } else if (new_state == "idle") {
258 m_mystate = STATE_IDLE;
259 } else if (new_state == "move_path") {
260 m_mystate = STATE_PATHMOVING;
261 get_walker()->start_moving();
262 } else if (new_state == "move_path_track") {
263 m_mystate = STATE_PATHMOVING_TRACK;
264 get_walker()->start_moving();
265 } else if (new_state == "normal") {
266 m_mystate = STATE_IDLE;
267 } else if (new_state == "vanish") {
268 vanish();
269 } else {
270 log_warning << "Can't set unknown willowisp state '" << new_state << std::endl;
271 }
272}
273
274ObjectSettings
275WillOWisp::get_settings()
276{
277 ObjectSettings result = BadGuy::get_settings();
278
279 result.add_direction(_("Direction"), &m_dir);
280 result.add_text(_("Sector"), &m_target_sector, "sector");
281 result.add_text(_("Spawnpoint"), &m_target_spawnpoint, "spawnpoint");
282 result.add_text(_("Hit script"), &m_hit_script, "hit-script");
283 result.add_float(_("Track range"), &m_track_range, "track-range", TRACK_RANGE);
284 result.add_float(_("Vanish range"), &m_vanish_range, "vanish-range", VANISH_RANGE);
285 result.add_float(_("Fly speed"), &m_flyspeed, "flyspeed", FLYSPEED);
286 result.add_path_ref(_("Path"), get_path_ref(), "path-ref");
287
288 result.reorder({"sector", "spawnpoint", "flyspeed", "track-range", "hit-script", "vanish-range", "name", "path-ref", "region", "x", "y"});
289
290 return result;
291}
292
293void WillOWisp::stop_looping_sounds()
294{
295 if (m_sound_source) {
296 m_sound_source->stop();
297 }
298}
299
300void WillOWisp::play_looping_sounds()
301{
302 if (m_sound_source) {
303 m_sound_source->play();
304 }
305}
306
307void
308WillOWisp::move_to(const Vector& pos)
309{
310 Vector shift = pos - m_col.m_bbox.p1();
311 if (get_path()) {
312 get_path()->move_by(shift);
313 }
314 set_pos(pos);
315}
316
317/* EOF */
318