1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software; you can redistribute it and/or
5// modify it under the terms of the GNU General Public License
6// as published by the Free Software Foundation; either version 2
7// of the License, or (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, write to the Free Software
16// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18#include "object/player.hpp"
19
20#include "audio/sound_manager.hpp"
21#include "badguy/badguy.hpp"
22#include "control/codecontroller.hpp"
23#include "control/input_manager.hpp"
24#include "editor/editor.hpp"
25#include "math/random.hpp"
26#include "object/bullet.hpp"
27#include "object/camera.hpp"
28#include "object/display_effect.hpp"
29#include "object/falling_coin.hpp"
30#include "object/music_object.hpp"
31#include "object/particles.hpp"
32#include "object/portable.hpp"
33#include "object/sprite_particle.hpp"
34#include "sprite/sprite.hpp"
35#include "sprite/sprite_manager.hpp"
36#include "supertux/game_session.hpp"
37#include "supertux/gameconfig.hpp"
38#include "supertux/sector.hpp"
39#include "supertux/tile.hpp"
40#include "trigger/trigger_base.hpp"
41#include "video/surface.hpp"
42
43//#define SWIMMING
44
45const float TUX_INVINCIBLE_TIME_WARNING = 2.0f;
46
47namespace {
48
49/* Times: */
50const float TUX_SAFE_TIME = 1.8f;
51const float TUX_INVINCIBLE_TIME = 14.0f;
52const float TUX_BACKFLIP_TIME = 2.1f; // minimum air time that backflip results in a loss of control
53
54const float BUTTJUMP_MIN_VELOCITY_Y = 400.0f;
55const float SHOOTING_TIME = .150f;
56const float GLIDE_TIME_PER_FLOWER = 0.5f;
57const float STONE_TIME_PER_FLOWER = 2.0f;
58
59/** number of idle stages, including standing */
60const unsigned int IDLE_STAGE_COUNT = 5;
61/**
62 * how long to play each idle animation in milliseconds
63 * '0' means the sprite action is played once before moving onto the next
64 * animation
65 */
66const int IDLE_TIME[] = { 5000, 0, 2500, 0, 2500 };
67/** idle stages */
68const std::string IDLE_STAGES[] =
69{ "stand",
70 "idle",
71 "stand",
72 "idle",
73 "stand" };
74
75/** acceleration in horizontal direction when walking
76 * (all accelerations are in pixel/s^2) */
77const float WALK_ACCELERATION_X = 300;
78/** acceleration in horizontal direction when running */
79const float RUN_ACCELERATION_X = 400;
80/** acceleration when skidding */
81const float SKID_XM = 200;
82/** time of skidding in seconds */
83const float SKID_TIME = .3f;
84/** maximum walk velocity (pixel/s) */
85const float MAX_WALK_XM = 230;
86/** maximum run velocity (pixel/s) */
87const float MAX_RUN_XM = 320;
88/** bonus run velocity addition (pixel/s) */
89const float BONUS_RUN_XM = 80;
90/** maximum horizontal climb velocity */
91const float MAX_CLIMB_XM = 96;
92/** maximum vertical climb velocity */
93const float MAX_CLIMB_YM = 128;
94/** maximum vertical glide velocity */
95const float MAX_GLIDE_YM = 128;
96/** instant velocity when tux starts to walk */
97const float WALK_SPEED = 100;
98
99/** multiplied by WALK_ACCELERATION to give friction */
100const float NORMAL_FRICTION_MULTIPLIER = 1.5f;
101/** multiplied by WALK_ACCELERATION to give friction */
102const float ICE_FRICTION_MULTIPLIER = 0.1f;
103const float ICE_ACCELERATION_MULTIPLIER = 0.25f;
104
105/** time of the kick (kicking mriceblock) animation */
106const float KICK_TIME = .3f;
107
108/** if Tux cannot unduck for this long, he will get hurt */
109const float UNDUCK_HURT_TIME = 0.25f;
110/** gravity is higher after the jump key is released before
111 the apex of the jump is reached */
112const float JUMP_EARLY_APEX_FACTOR = 3.0;
113
114const float JUMP_GRACE_TIME = 0.25f; /**< time before hitting the ground that the jump button may be pressed (and still trigger a jump) */
115
116/* Tux's collision rectangle */
117const float TUX_WIDTH = 31.8f;
118const float RUNNING_TUX_WIDTH = 34;
119const float SMALL_TUX_HEIGHT = 30.8f;
120const float BIG_TUX_HEIGHT = 62.8f;
121const float DUCKED_TUX_HEIGHT = 31.8f;
122
123bool no_water = true;
124
125} // namespace
126
127Player::Player(PlayerStatus& player_status, const std::string& name_) :
128 ExposedObject<Player, scripting::Player>(this),
129 m_deactivated(false),
130 m_controller(&InputManager::current()->get_controller()),
131 m_scripting_controller(new CodeController()),
132 m_player_status(player_status),
133 m_duck(false),
134 m_dead(false),
135 m_dying(false),
136 m_winning(false),
137 m_backflipping(false),
138 m_backflip_direction(0),
139 m_peekingX(Direction::AUTO),
140 m_peekingY(Direction::AUTO),
141 m_ability_time(),
142 m_stone(false),
143 m_swimming(false),
144 m_speedlimit(0), //no special limit
145 m_scripting_controller_old(nullptr),
146 m_jump_early_apex(false),
147 m_on_ice(false),
148 m_ice_this_frame(false),
149 m_lightsprite(SpriteManager::current()->create("images/creatures/tux/light.sprite")),
150 m_powersprite(SpriteManager::current()->create("images/creatures/tux/powerups.sprite")),
151 m_dir(Direction::RIGHT),
152 m_old_dir(m_dir),
153 m_last_ground_y(0),
154 m_fall_mode(ON_GROUND),
155 m_on_ground_flag(false),
156 m_jumping(false),
157 m_can_jump(true),
158 m_jump_button_timer(),
159 m_wants_buttjump(false),
160 m_does_buttjump(false),
161 m_invincible_timer(),
162 m_skidding_timer(),
163 m_safe_timer(),
164 m_kick_timer(),
165 m_shooting_timer(),
166 m_ability_timer(),
167 m_cooldown_timer(),
168 m_dying_timer(),
169 m_second_growup_sound_timer(),
170 m_growing(false),
171 m_backflip_timer(),
172 m_physic(),
173 m_visible(true),
174 m_grabbed_object(nullptr),
175 // if/when we have complete penny gfx, we can
176 // load those instead of Tux's sprite in the
177 // constructor
178 m_sprite(SpriteManager::current()->create("images/creatures/tux/tux.sprite")),
179 m_airarrow(Surface::from_file("images/engine/hud/airarrow.png")),
180 m_floor_normal(),
181 m_ghost_mode(false),
182 m_edit_mode(false),
183 m_unduck_hurt_timer(),
184 m_idle_timer(),
185 m_idle_stage(0),
186 m_climbing(nullptr)
187{
188 m_name = name_;
189 m_idle_timer.start(static_cast<float>(IDLE_TIME[0]) / 1000.0f);
190
191 SoundManager::current()->preload("sounds/bigjump.wav");
192 SoundManager::current()->preload("sounds/jump.wav");
193 SoundManager::current()->preload("sounds/hurt.wav");
194 SoundManager::current()->preload("sounds/kill.wav");
195 SoundManager::current()->preload("sounds/skid.wav");
196 SoundManager::current()->preload("sounds/flip.wav");
197 SoundManager::current()->preload("sounds/invincible_start.ogg");
198 SoundManager::current()->preload("sounds/splash.wav");
199 SoundManager::current()->preload("sounds/grow.wav");
200 m_col.set_size(TUX_WIDTH, is_big() ? BIG_TUX_HEIGHT : SMALL_TUX_HEIGHT);
201
202 m_sprite->set_angle(0.0f);
203 m_powersprite->set_angle(0.0f);
204 m_lightsprite->set_angle(0.0f);
205 m_lightsprite->set_blend(Blend::ADD);
206
207 m_physic.reset();
208}
209
210Player::~Player()
211{
212 if (m_climbing) stop_climbing(*m_climbing);
213}
214
215float
216Player::get_speedlimit() const
217{
218 return m_speedlimit;
219}
220
221void
222Player::set_speedlimit(float newlimit)
223{
224 m_speedlimit=newlimit;
225}
226
227void
228Player::set_controller(const Controller* controller_)
229{
230 m_controller = controller_;
231}
232
233void
234Player::set_winning()
235{
236 if ( ! is_winning() ){
237 m_winning = true;
238 m_invincible_timer.start(10000.0f);
239 }
240}
241
242void
243Player::use_scripting_controller(bool use_or_release)
244{
245 if ((use_or_release == true) && (m_controller != m_scripting_controller.get())) {
246 m_scripting_controller_old = &get_controller();
247 set_controller(m_scripting_controller.get());
248 }
249 if ((use_or_release == false) && (m_controller == m_scripting_controller.get())) {
250 set_controller(m_scripting_controller_old);
251 m_scripting_controller_old = nullptr;
252 }
253}
254
255void
256Player::do_scripting_controller(const std::string& control_text, bool pressed)
257{
258 if (const auto maybe_control = Control_from_string(control_text)) {
259 m_scripting_controller->press(*maybe_control, pressed);
260 }
261}
262
263bool
264Player::adjust_height(float new_height)
265{
266 Rectf bbox2 = m_col.m_bbox;
267 bbox2.move(Vector(0, m_col.m_bbox.get_height() - new_height));
268 bbox2.set_height(new_height);
269
270
271 if (new_height > m_col.m_bbox.get_height()) {
272 Rectf additional_space = bbox2;
273 additional_space.set_height(new_height - m_col.m_bbox.get_height());
274 if (!Sector::get().is_free_of_statics(additional_space, this, true))
275 return false;
276 }
277
278 // adjust bbox accordingly
279 // note that we use members of moving_object for this, so we can run this during CD, too
280 set_pos(bbox2.p1());
281 m_col.set_size(bbox2.get_width(), bbox2.get_height());
282 return true;
283}
284
285void
286Player::trigger_sequence(const std::string& sequence_name, const SequenceData* data)
287{
288 trigger_sequence(string_to_sequence(sequence_name), data);
289}
290
291void
292Player::trigger_sequence(Sequence seq, const SequenceData* data)
293{
294 if (m_climbing) stop_climbing(*m_climbing);
295 stop_backflipping();
296 GameSession::current()->start_sequence(seq, data);
297}
298
299void
300Player::update(float dt_sec)
301{
302 check_bounds();
303
304 if ( no_water ){
305 m_swimming = false;
306 }
307 no_water = true;
308
309 if (m_dying && m_dying_timer.check()) {
310 Sector::get().stop_looping_sounds();
311 set_bonus(NO_BONUS, true);
312 m_dead = true;
313 return;
314 }
315
316 if (!m_dying && !m_deactivated)
317 handle_input();
318
319 /*
320 // handle_input() calls apply_friction() when Tux is not walking, so we'll have to do this ourselves
321 if (deactivated)
322 apply_friction();
323 */
324
325 // extend/shrink tux collision rectangle so that we fall through/walk over 1
326 // tile holes
327 if (fabsf(m_physic.get_velocity_x()) > MAX_WALK_XM) {
328 m_col.set_width(RUNNING_TUX_WIDTH);
329 } else {
330 m_col.set_width(TUX_WIDTH);
331 }
332
333 // on downward slopes, adjust vertical velocity so tux walks smoothly down
334 if (on_ground() && !m_dying) {
335 if (m_floor_normal.y != 0) {
336 if ((m_floor_normal.x * m_physic.get_velocity_x()) >= 0) {
337 m_physic.set_velocity_y(250);
338 }
339 }
340 }
341
342 // handle backflipping
343 if (m_backflipping && !m_dying) {
344 //prevent player from changing direction when backflipping
345 m_dir = (m_backflip_direction == 1) ? Direction::LEFT : Direction::RIGHT;
346 if (m_backflip_timer.started()) m_physic.set_velocity_x(100.0f * static_cast<float>(m_backflip_direction));
347 //rotate sprite during flip
348 m_sprite->set_angle(m_sprite->get_angle() + (m_dir==Direction::LEFT?1:-1) * dt_sec * (360.0f / 0.5f));
349 if (m_player_status.has_hat_sprite()) {
350 m_powersprite->set_angle(m_sprite->get_angle());
351 if (m_player_status.bonus == EARTH_BONUS)
352 m_lightsprite->set_angle(m_sprite->get_angle());
353 }
354 }
355
356 // set fall mode...
357 if (on_ground()) {
358 m_fall_mode = ON_GROUND;
359 m_last_ground_y = get_pos().y;
360 } else {
361 if (get_pos().y > m_last_ground_y)
362 m_fall_mode = FALLING;
363 else if (m_fall_mode == ON_GROUND)
364 m_fall_mode = JUMPING;
365 }
366
367 // check if we landed
368 if (on_ground()) {
369 m_jumping = false;
370 if (m_backflipping && (m_backflip_timer.get_timegone() > 0.15f)) {
371 m_backflipping = false;
372 m_backflip_direction = 0;
373 m_physic.set_velocity_x(0);
374 if (!m_stone) {
375 m_sprite->set_angle(0.0f);
376 m_powersprite->set_angle(0.0f);
377 m_lightsprite->set_angle(0.0f);
378 }
379
380 // if controls are currently deactivated, we take care of standing up ourselves
381 if (m_deactivated)
382 do_standup();
383 }
384 if (m_player_status.bonus == AIR_BONUS)
385 m_ability_time = static_cast<float>(m_player_status.max_air_time) * GLIDE_TIME_PER_FLOWER;
386
387 if (m_second_growup_sound_timer.check())
388 {
389 SoundManager::current()->play("sounds/grow.wav");
390 m_second_growup_sound_timer.stop();
391 }
392 }
393
394 // calculate movement for this frame
395 m_col.m_movement = m_physic.get_movement(dt_sec);
396
397 if (m_grabbed_object != nullptr && !m_dying) {
398 position_grabbed_object();
399 }
400
401 if (m_grabbed_object != nullptr && m_dying){
402 m_grabbed_object->ungrab(*this, m_dir);
403 m_grabbed_object = nullptr;
404 }
405
406 if (!m_ice_this_frame && on_ground())
407 m_on_ice = false;
408
409 m_on_ground_flag = false;
410 m_ice_this_frame = false;
411
412 // when invincible, spawn particles
413 if (m_invincible_timer.started())
414 {
415 if (graphicsRandom.rand(0, 2) == 0) {
416 float px = graphicsRandom.randf(m_col.m_bbox.get_left()+0, m_col.m_bbox.get_right()-0);
417 float py = graphicsRandom.randf(m_col.m_bbox.get_top()+0, m_col.m_bbox.get_bottom()-0);
418 Vector ppos = Vector(px, py);
419 Vector pspeed = Vector(0, 0);
420 Vector paccel = Vector(0, 0);
421 Sector::get().add<SpriteParticle>(
422 "images/objects/particles/sparkle.sprite",
423 // draw bright sparkle when there is lots of time left,
424 // dark sparkle when invincibility is about to end
425 (m_invincible_timer.get_timeleft() > TUX_INVINCIBLE_TIME_WARNING) ?
426 // make every other a longer sparkle to make trail a bit fuzzy
427 (size_t(g_game_time*20)%2) ? "small" : "medium"
428 :
429 "dark", ppos, ANCHOR_MIDDLE, pspeed, paccel, LAYER_OBJECTS + 1 + 5);
430 }
431 }
432
433 if (m_growing) {
434 if (m_sprite->animation_done()) m_growing = false;
435 }
436
437 // when climbing animate only while moving
438 if (m_climbing){
439 if ((m_physic.get_velocity_x()==0)&&(m_physic.get_velocity_y()==0))
440 m_sprite->stop_animation();
441 else
442 m_sprite->set_animation_loops(-1);
443 }
444
445}
446
447bool
448Player::slightly_above_ground() const
449{
450 float abs_vy = std::abs(m_physic.get_velocity_y());
451 float ground_y_delta = std::abs(m_last_ground_y - get_pos().y);
452 return (abs_vy == 15.625f || abs_vy == 31.25f) && ground_y_delta < 0.85f;
453}
454
455bool
456Player::on_ground() const
457{
458 return m_on_ground_flag || slightly_above_ground();
459}
460
461bool
462Player::is_big() const
463{
464 if (m_player_status.bonus == NO_BONUS)
465 return false;
466
467 return true;
468}
469
470void
471Player::apply_friction()
472{
473 if ((on_ground()) && (fabsf(m_physic.get_velocity_x()) < WALK_SPEED)) {
474 m_physic.set_velocity_x(0);
475 m_physic.set_acceleration_x(0);
476 } else {
477 float friction = WALK_ACCELERATION_X * (m_on_ice ? ICE_FRICTION_MULTIPLIER : NORMAL_FRICTION_MULTIPLIER);
478 if (m_physic.get_velocity_x() < 0) {
479 m_physic.set_acceleration_x(friction);
480 } else if (m_physic.get_velocity_x() > 0) {
481 m_physic.set_acceleration_x(-friction);
482 } // no friction for physic.get_velocity_x() == 0
483 }
484}
485
486void
487Player::handle_horizontal_input()
488{
489 float vx = m_physic.get_velocity_x();
490 float vy = m_physic.get_velocity_y();
491 float ax = m_physic.get_acceleration_x(); // NOLINT
492 float ay = m_physic.get_acceleration_y();
493
494 float dirsign = 0;
495 if (!m_duck || m_physic.get_velocity_y() != 0) {
496 if (m_controller->hold(Control::LEFT) && !m_controller->hold(Control::RIGHT)) {
497 m_old_dir = m_dir;
498 m_dir = Direction::LEFT;
499 dirsign = -1;
500 } else if (!m_controller->hold(Control::LEFT)
501 && m_controller->hold(Control::RIGHT)) {
502 m_old_dir = m_dir;
503 m_dir = Direction::RIGHT;
504 dirsign = 1;
505 }
506 }
507
508 // do not run if we're holding something which slows us down
509 if ( m_grabbed_object && m_grabbed_object->is_hampering() ) {
510 ax = dirsign * WALK_ACCELERATION_X;
511 // limit speed
512 if (vx >= MAX_WALK_XM && dirsign > 0) {
513 vx = MAX_WALK_XM;
514 ax = 0;
515 } else if (vx <= -MAX_WALK_XM && dirsign < 0) {
516 vx = -MAX_WALK_XM;
517 ax = 0;
518 }
519 } else {
520 if ( vx * dirsign < MAX_WALK_XM ) {
521 ax = dirsign * WALK_ACCELERATION_X;
522 } else {
523 ax = dirsign * RUN_ACCELERATION_X;
524 }
525 // limit speed
526 if (vx >= MAX_RUN_XM + BONUS_RUN_XM *((m_player_status.bonus == AIR_BONUS) ? 1 : 0) && dirsign > 0) {
527 vx = MAX_RUN_XM + BONUS_RUN_XM *((m_player_status.bonus == AIR_BONUS) ? 1 : 0);
528 ax = 0;
529 } else if (vx <= -MAX_RUN_XM - BONUS_RUN_XM *((m_player_status.bonus == AIR_BONUS) ? 1 : 0) && dirsign < 0) {
530 vx = -MAX_RUN_XM - BONUS_RUN_XM *((m_player_status.bonus == AIR_BONUS) ? 1 : 0);
531 ax = 0;
532 }
533 }
534
535 // we can reach WALK_SPEED without any acceleration
536 if (dirsign != 0 && fabsf(vx) < WALK_SPEED) {
537 vx = dirsign * WALK_SPEED;
538 }
539
540 //Check speedlimit.
541 if ( m_speedlimit > 0 && vx * dirsign >= m_speedlimit ) {
542 vx = dirsign * m_speedlimit;
543 ax = 0;
544 }
545
546 // changing directions?
547 if ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0)) {
548 if (on_ground()) {
549 // let's skid!
550 if (fabsf(vx)>SKID_XM && !m_skidding_timer.started()) {
551 m_skidding_timer.start(SKID_TIME);
552 SoundManager::current()->play("sounds/skid.wav");
553 // dust some particles
554 Sector::get().add<Particles>(
555 Vector(m_dir == Direction::LEFT ? m_col.m_bbox.get_right() : m_col.m_bbox.get_left(), m_col.m_bbox.get_bottom()),
556 m_dir == Direction::LEFT ? 50 : -70, m_dir == Direction::LEFT ? 70 : -50, 260, 280,
557 Vector(0, 300), 3, Color(.4f, .4f, .4f), 3, .8f, LAYER_OBJECTS+1);
558
559 ax *= 2.5f;
560 } else {
561 ax *= 2;
562 }
563 }
564 else {
565 // give Tux tighter air control
566 ax *= 2.f;
567 }
568 }
569
570 if (m_on_ice) {
571 ax *= ICE_ACCELERATION_MULTIPLIER;
572 }
573
574 m_physic.set_velocity(vx, vy);
575 m_physic.set_acceleration(ax, ay);
576
577 // we get slower when not pressing any keys
578 if (dirsign == 0) {
579 apply_friction();
580 }
581
582}
583
584void
585Player::do_cheer()
586{
587 do_duck();
588 do_backflip();
589 do_standup();
590}
591
592void
593Player::do_duck() {
594 if (m_duck)
595 return;
596 if (!is_big())
597 return;
598
599 if (m_physic.get_velocity_y() != 0)
600 return;
601 if (!on_ground())
602 return;
603 if (m_does_buttjump)
604 return;
605
606 if (adjust_height(DUCKED_TUX_HEIGHT)) {
607 m_duck = true;
608 m_growing = false;
609 m_unduck_hurt_timer.stop();
610 } else {
611 // FIXME: what now?
612 }
613}
614
615void
616Player::do_standup() {
617 if (!m_duck)
618 return;
619 if (!is_big())
620 return;
621 if (m_backflipping)
622 return;
623 if (m_stone)
624 return;
625
626 if (adjust_height(BIG_TUX_HEIGHT)) {
627 m_duck = false;
628 m_unduck_hurt_timer.stop();
629 } else {
630 // if timer is not already running, start it.
631 if (m_unduck_hurt_timer.get_period() == 0) {
632 m_unduck_hurt_timer.start(UNDUCK_HURT_TIME);
633 }
634 else if (m_unduck_hurt_timer.check()) {
635 kill(false);
636 }
637 }
638
639}
640
641void
642Player::do_backflip() {
643 if (!m_duck)
644 return;
645 if (!on_ground())
646 return;
647
648 m_backflip_direction = (m_dir == Direction::LEFT)?(+1):(-1);
649 m_backflipping = true;
650 do_jump((m_player_status.bonus == AIR_BONUS) ? -720.0f : -580.0f);
651 SoundManager::current()->play("sounds/flip.wav");
652 m_backflip_timer.start(TUX_BACKFLIP_TIME);
653}
654
655void
656Player::do_jump(float yspeed) {
657 if (!on_ground())
658 return;
659
660 m_physic.set_velocity_y(yspeed);
661 //bbox.move(Vector(0, -1));
662 m_jumping = true;
663 m_on_ground_flag = false;
664 m_can_jump = false;
665
666 // play sound
667 if (is_big()) {
668 SoundManager::current()->play("sounds/bigjump.wav");
669 } else {
670 SoundManager::current()->play("sounds/jump.wav");
671 }
672}
673
674void
675Player::early_jump_apex()
676{
677 if (!m_jump_early_apex)
678 {
679 m_jump_early_apex = true;
680 m_physic.set_gravity_modifier(JUMP_EARLY_APEX_FACTOR);
681 }
682}
683
684void
685Player::do_jump_apex()
686{
687 if (m_jump_early_apex)
688 {
689 m_jump_early_apex = false;
690 m_physic.set_gravity_modifier(1.0f);
691 }
692}
693
694void
695Player::handle_vertical_input()
696{
697 // Press jump key
698 if (m_controller->pressed(Control::JUMP)) m_jump_button_timer.start(JUMP_GRACE_TIME);
699 if (m_controller->hold(Control::JUMP) && m_jump_button_timer.started() && m_can_jump) {
700 m_jump_button_timer.stop();
701 if (m_duck) {
702 // when running, only jump a little bit; else do a backflip
703 if ((m_physic.get_velocity_x() != 0) ||
704 (m_controller->hold(Control::LEFT)) ||
705 (m_controller->hold(Control::RIGHT)))
706 {
707 do_jump(-300);
708 }
709 else
710 {
711 do_backflip();
712 }
713 } else {
714 // airflower allows for higher jumps-
715 // jump a bit higher if we are running; else do a normal jump
716 if (m_player_status.bonus == AIR_BONUS)
717 do_jump((fabsf(m_physic.get_velocity_x()) > MAX_WALK_XM) ? -620.0f : -580.0f);
718 else
719 do_jump((fabsf(m_physic.get_velocity_x()) > MAX_WALK_XM) ? -580.0f : -520.0f);
720 }
721 // airflower glide only when holding jump key
722 } else if (m_controller->hold(Control::JUMP) && m_player_status.bonus == AIR_BONUS && m_physic.get_velocity_y() > MAX_GLIDE_YM) {
723 if (m_ability_time > 0 && !m_ability_timer.started())
724 m_ability_timer.start(m_ability_time);
725 else if (m_ability_timer.started()) {
726 // glide stops after some duration or if buttjump is initiated
727 if ((m_ability_timer.get_timeleft() <= 0.05f) || m_controller->hold(Control::DOWN)) {
728 m_ability_time = 0;
729 m_ability_timer.stop();
730 } else {
731 m_physic.set_velocity_y(MAX_GLIDE_YM);
732 m_physic.set_acceleration_y(0);
733 }
734 }
735 }
736
737
738 // Let go of jump key
739 else if (!m_controller->hold(Control::JUMP)) {
740 if (!m_backflipping && m_jumping && m_physic.get_velocity_y() < 0) {
741 m_jumping = false;
742 early_jump_apex();
743 }
744 if (m_player_status.bonus == AIR_BONUS && m_ability_timer.started()){
745 m_ability_time = m_ability_timer.get_timeleft();
746 m_ability_timer.stop();
747 }
748 }
749
750 if (m_jump_early_apex && m_physic.get_velocity_y() >= 0) {
751 do_jump_apex();
752 }
753
754 /* In case the player has pressed Down while in a certain range of air,
755 enable butt jump action */
756 if (m_controller->hold(Control::DOWN) && !m_duck && is_big() && !on_ground()) {
757 m_wants_buttjump = true;
758 if (m_physic.get_velocity_y() >= BUTTJUMP_MIN_VELOCITY_Y) m_does_buttjump = true;
759 }
760
761 /* When Down is not held anymore, disable butt jump */
762 if (!m_controller->hold(Control::DOWN)) {
763 m_wants_buttjump = false;
764 m_does_buttjump = false;
765 }
766
767 // swimming
768 m_physic.set_acceleration_y(0);
769#ifdef SWIMMING
770 if (swimming) {
771 if (controller->hold(Control::UP) || controller->hold(Control::JUMP))
772 physic.set_acceleration_y(-2000);
773 physic.set_velocity_y(physic.get_velocity_y() * 0.94);
774 }
775#endif
776}
777
778void
779Player::handle_input()
780{
781 if (m_ghost_mode) {
782 handle_input_ghost();
783 return;
784 }
785 if (m_climbing) {
786 handle_input_climbing();
787 return;
788 }
789
790 /* Peeking */
791 if ( m_controller->released( Control::PEEK_LEFT ) || m_controller->released( Control::PEEK_RIGHT ) ) {
792 m_peekingX = Direction::AUTO;
793 }
794 if ( m_controller->released( Control::PEEK_UP ) || m_controller->released( Control::PEEK_DOWN ) ) {
795 m_peekingY = Direction::AUTO;
796 }
797 if ( m_controller->pressed( Control::PEEK_LEFT ) ) {
798 m_peekingX = Direction::LEFT;
799 }
800 if ( m_controller->pressed( Control::PEEK_RIGHT ) ) {
801 m_peekingX = Direction::RIGHT;
802 }
803 if (!m_backflipping && !m_jumping && on_ground()) {
804 if ( m_controller->pressed( Control::PEEK_UP ) ) {
805 m_peekingY = Direction::UP;
806 } else if ( m_controller->pressed( Control::PEEK_DOWN ) ) {
807 m_peekingY = Direction::DOWN;
808 }
809 }
810
811 /* Handle horizontal movement: */
812 if (!m_backflipping && !m_stone) handle_horizontal_input();
813
814 /* Jump/jumping? */
815 if (on_ground())
816 m_can_jump = true;
817
818 /* Handle vertical movement: */
819 if (!m_stone) handle_vertical_input();
820
821 /* Shoot! */
822 auto active_bullets = Sector::get().get_object_count<Bullet>();
823 if (m_controller->pressed(Control::ACTION) && (m_player_status.bonus == FIRE_BONUS || m_player_status.bonus == ICE_BONUS)) {
824 if ((m_player_status.bonus == FIRE_BONUS &&
825 active_bullets < m_player_status.max_fire_bullets) ||
826 (m_player_status.bonus == ICE_BONUS &&
827 active_bullets < m_player_status.max_ice_bullets))
828 {
829 Vector pos = get_pos() + ((m_dir == Direction::LEFT)? Vector(0, m_col.m_bbox.get_height()/2) : Vector(32, m_col.m_bbox.get_height()/2));
830 Sector::get().add<Bullet>(pos, m_physic.get_velocity_x(), m_dir, m_player_status.bonus);
831 SoundManager::current()->play("sounds/shoot.wav");
832 m_shooting_timer.start(SHOOTING_TIME);
833 }
834 }
835
836 /* Turn to Stone */
837 if (m_controller->pressed(Control::DOWN) && m_player_status.bonus == EARTH_BONUS && !m_cooldown_timer.started() && on_ground()) {
838 if (m_controller->hold(Control::ACTION) && !m_ability_timer.started()) {
839 m_ability_timer.start(static_cast<float>(m_player_status.max_earth_time) * STONE_TIME_PER_FLOWER);
840 m_powersprite->stop_animation();
841 m_stone = true;
842 m_physic.set_gravity_modifier(1.0f); // Undo jump_early_apex
843 }
844 }
845
846 if (m_stone)
847 apply_friction();
848
849 /* Revert from Stone */
850 if (m_stone && (!m_controller->hold(Control::ACTION) || m_ability_timer.get_timeleft() <= 0.5f)) {
851 m_cooldown_timer.start(m_ability_timer.get_timegone()/2.0f); //The longer stone form is used, the longer until it can be used again
852 m_ability_timer.stop();
853 m_sprite->set_angle(0.0f);
854 m_powersprite->set_angle(0.0f);
855 m_lightsprite->set_angle(0.0f);
856 m_stone = false;
857 for (int i = 0; i < 8; i++)
858 {
859 Vector ppos = Vector(m_col.m_bbox.get_left() + 8.0f + 16.0f * static_cast<float>(static_cast<int>(i / 4)),
860 m_col.m_bbox.get_top() + 16.0f * static_cast<float>(i % 4));
861 float grey = graphicsRandom.randf(.4f, .8f);
862 Color pcolor = Color(grey, grey, grey);
863 Sector::get().add<Particles>(ppos, -60, 240, 42, 81, Vector(0.0f, 500.0f),
864 8, pcolor, 4 + graphicsRandom.randf(-0.4f, 0.4f),
865 0.8f + graphicsRandom.randf(0.0f, 0.4f), LAYER_OBJECTS + 2);
866 }
867 }
868
869 /* Duck or Standup! */
870 if (m_controller->hold(Control::DOWN) && !m_stone) {
871 do_duck();
872 } else {
873 do_standup();
874 }
875
876 /* grabbing */
877 try_grab();
878
879 if (!m_controller->hold(Control::ACTION) && m_grabbed_object) {
880 auto moving_object = dynamic_cast<MovingObject*> (m_grabbed_object);
881 if (moving_object) {
882 // move the grabbed object a bit away from tux
883 Rectf grabbed_bbox = moving_object->get_bbox();
884 Rectf dest_;
885 dest_.set_bottom(m_col.m_bbox.get_top() + m_col.m_bbox.get_height() * 0.66666f);
886 dest_.set_top(dest_.get_bottom() - grabbed_bbox.get_height());
887 if (m_dir == Direction::LEFT) {
888 dest_.set_right(m_col.m_bbox.get_left() - 1);
889 dest_.set_left(dest_.get_right() - grabbed_bbox.get_width());
890 } else {
891 dest_.set_left(m_col.m_bbox.get_right() + 1);
892 dest_.set_right(dest_.get_left() + grabbed_bbox.get_width());
893 }
894 if (Sector::get().is_free_of_tiles(dest_, true) &&
895 Sector::get().is_free_of_statics(dest_, moving_object, true)) {
896 moving_object->set_pos(dest_.p1());
897 if (m_controller->hold(Control::UP)) {
898 m_grabbed_object->ungrab(*this, Direction::UP);
899 } else if (m_controller->hold(Control::DOWN)) {
900 m_grabbed_object->ungrab(*this, Direction::DOWN);
901 } else {
902 m_grabbed_object->ungrab(*this, m_dir);
903 }
904 m_grabbed_object = nullptr;
905 }
906 } else {
907 log_debug << "Non MovingObject grabbed?!?" << std::endl;
908 }
909 }
910
911 /* stop backflipping at will */
912 if ( m_backflipping && ( !m_controller->hold(Control::JUMP) && !m_backflip_timer.started()) ){
913 stop_backflipping();
914 }
915}
916
917void
918Player::position_grabbed_object()
919{
920 auto moving_object = dynamic_cast<MovingObject*>(m_grabbed_object);
921 assert(moving_object);
922 const auto& object_bbox = moving_object->get_bbox();
923
924 // Position where we will hold the lower-inner corner
925 Vector pos(m_col.m_bbox.get_left() + m_col.m_bbox.get_width()/2,
926 m_col.m_bbox.get_top() + m_col.m_bbox.get_height()*0.66666f);
927
928 // Adjust to find the grabbed object's upper-left corner
929 if (m_dir == Direction::LEFT)
930 pos.x -= object_bbox.get_width();
931 pos.y -= object_bbox.get_height();
932
933 m_grabbed_object->grab(*this, pos, m_dir);
934}
935
936void
937Player::try_grab()
938{
939 if (m_controller->hold(Control::ACTION) && !m_grabbed_object
940 && !m_duck) {
941
942 Vector pos;
943 if (m_dir == Direction::LEFT) {
944 pos = Vector(m_col.m_bbox.get_left() - 5, m_col.m_bbox.get_bottom() - 16);
945 } else {
946 pos = Vector(m_col.m_bbox.get_right() + 5, m_col.m_bbox.get_bottom() - 16);
947 }
948
949 for (auto& moving_object : Sector::get().get_objects_by_type<MovingObject>()) {
950 Portable* portable = dynamic_cast<Portable*>(&moving_object);
951 if (portable && portable->is_portable())
952 {
953 // make sure the Portable isn't currently non-solid
954 if (moving_object.get_group() == COLGROUP_DISABLED) continue;
955
956 // check if we are within reach
957 if (moving_object.get_bbox().contains(pos)) {
958 if (m_climbing)
959 stop_climbing(*m_climbing);
960 m_grabbed_object = portable;
961 position_grabbed_object();
962 break;
963 }
964 }
965 }
966 }
967}
968
969void
970Player::handle_input_ghost()
971{
972 float vx = 0;
973 float vy = 0;
974 if (m_controller->hold(Control::LEFT)) {
975 m_dir = Direction::LEFT;
976 vx -= MAX_RUN_XM * 2;
977 }
978 if (m_controller->hold(Control::RIGHT)) {
979 m_dir = Direction::RIGHT;
980 vx += MAX_RUN_XM * 2;
981 }
982 if ((m_controller->hold(Control::UP)) || (m_controller->hold(Control::JUMP))) {
983 vy -= MAX_RUN_XM * 2;
984 }
985 if (m_controller->hold(Control::DOWN)) {
986 vy += MAX_RUN_XM * 2;
987 }
988 if (m_controller->hold(Control::ACTION)) {
989 set_ghost_mode(false);
990 }
991 m_physic.set_velocity(vx, vy);
992 m_physic.set_acceleration(0, 0);
993}
994
995void
996Player::add_coins(int count)
997{
998 m_player_status.add_coins(count);
999}
1000
1001int
1002Player::get_coins() const
1003{
1004 return m_player_status.coins;
1005}
1006
1007BonusType
1008Player::string_to_bonus(const std::string& bonus) const {
1009 BonusType type = NO_BONUS;
1010
1011 if (bonus == "grow") {
1012 type = GROWUP_BONUS;
1013 } else if (bonus == "fireflower") {
1014 type = FIRE_BONUS;
1015 } else if (bonus == "iceflower") {
1016 type = ICE_BONUS;
1017 } else if (bonus == "airflower") {
1018 type = AIR_BONUS;
1019 } else if (bonus == "earthflower") {
1020 type = EARTH_BONUS;
1021 } else if (bonus == "none") {
1022 type = NO_BONUS;
1023 } else {
1024 std::ostringstream msg;
1025 msg << "Unknown bonus type " << bonus;
1026 throw std::runtime_error(msg.str());
1027 }
1028
1029 return type;
1030}
1031
1032bool
1033Player::add_bonus(const std::string& bonustype)
1034{
1035 return add_bonus( string_to_bonus(bonustype) );
1036}
1037
1038bool
1039Player::set_bonus(const std::string& bonustype)
1040{
1041 return set_bonus( string_to_bonus(bonustype) );
1042}
1043
1044bool
1045Player::add_bonus(BonusType type, bool animate)
1046{
1047 // always ignore NO_BONUS
1048 if (type == NO_BONUS) {
1049 return true;
1050 }
1051
1052 // ignore GROWUP_BONUS if we're already big
1053 if (type == GROWUP_BONUS) {
1054 if (m_player_status.bonus != NO_BONUS)
1055 return true;
1056 }
1057
1058 return set_bonus(type, animate);
1059}
1060
1061bool
1062Player::set_bonus(BonusType type, bool animate)
1063{
1064 if (m_dying) {
1065 return false;
1066 }
1067
1068 if ((m_player_status.bonus == NO_BONUS) && (type != NO_BONUS)) {
1069 if (!adjust_height(BIG_TUX_HEIGHT)) {
1070 log_debug << "Can't adjust Tux height" << std::endl;
1071 return false;
1072 }
1073 if (animate) {
1074 m_growing = true;
1075 m_sprite->set_action((m_dir == Direction::LEFT)?"grow-left":"grow-right", 1);
1076 }
1077 if (m_climbing) stop_climbing(*m_climbing);
1078 }
1079
1080 if (type == NO_BONUS) {
1081 if (!adjust_height(SMALL_TUX_HEIGHT)) {
1082 log_debug << "Can't adjust Tux height" << std::endl;
1083 return false;
1084 }
1085 if (m_does_buttjump) m_does_buttjump = false;
1086 }
1087
1088 if ((type == NO_BONUS) || (type == GROWUP_BONUS)) {
1089 Vector ppos = Vector((m_col.m_bbox.get_left() + m_col.m_bbox.get_right()) / 2, m_col.m_bbox.get_top());
1090 Vector pspeed = Vector(((m_dir == Direction::LEFT) ? 100.0f : -100.0f), -300.0f);
1091 Vector paccel = Vector(0, 1000);
1092 std::string action = (m_dir == Direction::LEFT) ? "left" : "right";
1093 std::string particle_name = "";
1094
1095 if ((m_player_status.bonus == FIRE_BONUS) && (animate)) {
1096 // visually lose helmet
1097 if (g_config->christmas_mode) {
1098 particle_name = "santatux-hat";
1099 }
1100 else {
1101 particle_name = "firetux-helmet";
1102 }
1103 }
1104 if ((m_player_status.bonus == ICE_BONUS) && (animate)) {
1105 // visually lose cap
1106 particle_name = "icetux-cap";
1107 }
1108 if ((m_player_status.bonus == AIR_BONUS) && (animate)) {
1109 // visually lose hat
1110 particle_name = "airtux-hat";
1111 }
1112 if ((m_player_status.bonus == EARTH_BONUS) && (animate)) {
1113 // visually lose hard-hat
1114 particle_name = "earthtux-hardhat";
1115 }
1116 if (!particle_name.empty() && animate) {
1117 Sector::get().add<SpriteParticle>("images/objects/particles/" + particle_name + ".sprite",
1118 action, ppos, ANCHOR_TOP, pspeed, paccel, LAYER_OBJECTS - 1);
1119 }
1120 if (m_climbing) stop_climbing(*m_climbing);
1121
1122 m_player_status.max_fire_bullets = 0;
1123 m_player_status.max_ice_bullets = 0;
1124 m_player_status.max_air_time = 0;
1125 m_player_status.max_earth_time = 0;
1126 }
1127 if (type == FIRE_BONUS) m_player_status.max_fire_bullets++;
1128 if (type == ICE_BONUS) m_player_status.max_ice_bullets++;
1129 if (type == AIR_BONUS) m_player_status.max_air_time++;
1130 if (type == EARTH_BONUS) m_player_status.max_earth_time++;
1131
1132 if (!m_second_growup_sound_timer.started() &&
1133 type > GROWUP_BONUS && type != m_player_status.bonus)
1134 {
1135 m_second_growup_sound_timer.start(0.5);
1136 }
1137
1138 m_player_status.bonus = type;
1139 return true;
1140}
1141
1142void
1143Player::set_visible(bool visible_)
1144{
1145 m_visible = visible_;
1146}
1147
1148bool
1149Player::get_visible() const
1150{
1151 return m_visible;
1152}
1153
1154void
1155Player::kick()
1156{
1157 m_kick_timer.start(KICK_TIME);
1158}
1159
1160void
1161Player::draw(DrawingContext& context)
1162{
1163 if (Editor::is_active()) {
1164 return;
1165 }
1166
1167 if (!m_visible)
1168 return;
1169
1170 // if Tux is above camera, draw little "air arrow" to show where he is x-wise
1171 if (m_col.m_bbox.get_bottom() - 16 < Sector::get().get_camera().get_translation().y) {
1172 float px = m_col.m_bbox.get_left() + (m_col.m_bbox.get_right() - m_col.m_bbox.get_left() - static_cast<float>(m_airarrow.get()->get_width())) / 2.0f;
1173 float py = Sector::get().get_camera().get_translation().y;
1174 py += std::min(((py - (m_col.m_bbox.get_bottom() + 16)) / 4), 16.0f);
1175 context.color().draw_surface(m_airarrow, Vector(px, py), LAYER_HUD - 1);
1176 }
1177
1178 std::string sa_prefix = "";
1179 std::string sa_postfix = "";
1180
1181 if (m_player_status.bonus == GROWUP_BONUS)
1182 sa_prefix = "big";
1183 else if (m_player_status.bonus == FIRE_BONUS)
1184 if (g_config->christmas_mode)
1185 sa_prefix = "santa";
1186 else
1187 sa_prefix = "fire";
1188 else if (m_player_status.bonus == ICE_BONUS)
1189 sa_prefix = "ice";
1190 else if (m_player_status.bonus == AIR_BONUS)
1191 sa_prefix = "air";
1192 else if (m_player_status.bonus == EARTH_BONUS)
1193 sa_prefix = "earth";
1194 else
1195 sa_prefix = "small";
1196
1197 if (m_dir == Direction::LEFT)
1198 sa_postfix = "-left";
1199 else
1200 sa_postfix = "-right";
1201
1202 /* Set Tux sprite action */
1203 if (m_dying) {
1204 m_sprite->set_action("gameover");
1205 }
1206 else if (m_growing) {
1207 m_sprite->set_action_continued("grow"+sa_postfix);
1208 // while growing, do not change action
1209 // do_duck() will take care of cancelling growing manually
1210 // update() will take care of cancelling when growing completed
1211 }
1212 else if (m_stone) {
1213 m_sprite->set_action(m_sprite->get_action()+"-stone");
1214 }
1215 else if (m_climbing) {
1216 m_sprite->set_action(sa_prefix+"-climbing"+sa_postfix);
1217 }
1218 else if (m_backflipping) {
1219 m_sprite->set_action(sa_prefix+"-backflip"+sa_postfix);
1220 }
1221 else if (m_duck && is_big()) {
1222 m_sprite->set_action(sa_prefix+"-duck"+sa_postfix);
1223 }
1224 else if (m_skidding_timer.started() && !m_skidding_timer.check()) {
1225 m_sprite->set_action(sa_prefix+"-skid"+sa_postfix);
1226 }
1227 else if (m_kick_timer.started() && !m_kick_timer.check()) {
1228 m_sprite->set_action(sa_prefix+"-kick"+sa_postfix);
1229 }
1230 else if ((m_wants_buttjump || m_does_buttjump) && is_big()) {
1231 m_sprite->set_action(sa_prefix+"-buttjump"+sa_postfix, 1);
1232 }
1233 else if (!on_ground() || m_fall_mode != ON_GROUND) {
1234 if (m_physic.get_velocity_x() != 0 || m_fall_mode != ON_GROUND) {
1235 m_sprite->set_action(sa_prefix+"-jump"+sa_postfix);
1236 }
1237 }
1238 else {
1239 if (fabsf(m_physic.get_velocity_x()) < 1.0f) {
1240 // Determine which idle stage we're at
1241 if (m_sprite->get_action().find("-stand-") == std::string::npos && m_sprite->get_action().find("-idle-") == std::string::npos) {
1242 m_idle_stage = 0;
1243 m_idle_timer.start(static_cast<float>(IDLE_TIME[m_idle_stage]) / 1000.0f);
1244
1245 m_sprite->set_action_continued(sa_prefix+("-" + IDLE_STAGES[m_idle_stage])+sa_postfix);
1246 }
1247 else if (m_idle_timer.check() || (IDLE_TIME[m_idle_stage] == 0 && m_sprite->animation_done())) {
1248 m_idle_stage++;
1249 if (m_idle_stage >= IDLE_STAGE_COUNT)
1250 m_idle_stage = 1;
1251
1252 m_idle_timer.start(static_cast<float>(IDLE_TIME[m_idle_stage]) / 1000.0f);
1253
1254 if (IDLE_TIME[m_idle_stage] == 0)
1255 m_sprite->set_action(sa_prefix+("-" + IDLE_STAGES[m_idle_stage])+sa_postfix, 1);
1256 else
1257 m_sprite->set_action(sa_prefix+("-" + IDLE_STAGES[m_idle_stage])+sa_postfix);
1258 }
1259 else {
1260 m_sprite->set_action_continued(sa_prefix+("-" + IDLE_STAGES[m_idle_stage])+sa_postfix);
1261 }
1262 }
1263 else {
1264 if (fabsf(m_physic.get_velocity_x()) > MAX_WALK_XM && !is_big()) {
1265 m_sprite->set_action(sa_prefix+"-run"+sa_postfix);
1266 } else {
1267 m_sprite->set_action(sa_prefix+"-walk"+sa_postfix);
1268 }
1269 }
1270 }
1271
1272 /* Set Tux powerup sprite action */
1273 if (m_player_status.has_hat_sprite()) {
1274 m_powersprite->set_action(m_sprite->get_action());
1275 if (m_player_status.bonus == EARTH_BONUS)
1276 m_lightsprite->set_action(m_sprite->get_action());
1277 }
1278
1279 /*
1280 // Tux is holding something
1281 if ((grabbed_object != 0 && physic.get_velocity_y() == 0) ||
1282 (shooting_timer.get_timeleft() > 0 && !shooting_timer.check())) {
1283 if (duck) {
1284 } else {
1285 }
1286 }
1287 */
1288
1289 /* Draw Tux */
1290 if (m_safe_timer.started() && size_t(g_game_time*40)%2)
1291 ; // don't draw Tux
1292 else if (m_player_status.bonus == EARTH_BONUS){ // draw special effects with earthflower bonus
1293 // shake at end of maximum stone duration
1294 Vector shake_delta = (m_stone && m_ability_timer.get_timeleft() < 1.0f) ? Vector(graphicsRandom.randf(-3.0f, 3.0f) * 1.0f, 0) : Vector(0,0);
1295 m_sprite->draw(context.color(), get_pos() + shake_delta, LAYER_OBJECTS + 1);
1296 // draw hardhat
1297 m_powersprite->draw(context.color(), get_pos() + shake_delta, LAYER_OBJECTS + 1);
1298 // light
1299 m_lightsprite->draw(context.light(), get_pos(), 0);
1300
1301 // give an indicator that stone form cannot be used for a while
1302 if (m_cooldown_timer.started() && graphicsRandom.rand(0, 4) == 0) {
1303 float px = graphicsRandom.randf(m_col.m_bbox.get_left(), m_col.m_bbox.get_right());
1304 float py = m_col.m_bbox.get_bottom()+8;
1305 Vector ppos = Vector(px, py);
1306 Sector::get().add<SpriteParticle>(
1307 "images/objects/particles/sparkle.sprite", "dark",
1308 ppos, ANCHOR_MIDDLE, Vector(0, 0), Vector(0, 0), LAYER_OBJECTS+1+5);
1309 }
1310 }
1311 else {
1312 if (m_dying)
1313 m_sprite->draw(context.color(), get_pos(), Sector::get().get_foremost_layer());
1314 else
1315 m_sprite->draw(context.color(), get_pos(), LAYER_OBJECTS + 1);
1316
1317 if (m_player_status.has_hat_sprite())
1318 m_powersprite->draw(context.color(), get_pos(), LAYER_OBJECTS + 1);
1319 }
1320
1321}
1322
1323void
1324Player::collision_tile(uint32_t tile_attributes)
1325{
1326 if (tile_attributes & Tile::HURTS)
1327 kill(false);
1328
1329#ifdef SWIMMING
1330 if ( swimming ){
1331 if ( tile_attributes & Tile::WATER ){
1332 no_water = false;
1333 } else {
1334 swimming = false;
1335 }
1336 } else {
1337 if ( tile_attributes & Tile::WATER ){
1338 swimming = true;
1339 no_water = false;
1340 SoundManager::current()->play( "sounds/splash.wav" );
1341 }
1342 }
1343#endif
1344
1345 if (tile_attributes & Tile::ICE) {
1346 m_ice_this_frame = true;
1347 m_on_ice = true;
1348 }
1349}
1350
1351void
1352Player::collision_solid(const CollisionHit& hit)
1353{
1354 if (hit.bottom) {
1355 if (m_physic.get_velocity_y() > 0)
1356 m_physic.set_velocity_y(0);
1357
1358 m_on_ground_flag = true;
1359 m_floor_normal = hit.slope_normal;
1360
1361 // Butt Jump landed
1362 if (m_does_buttjump) {
1363 m_does_buttjump = false;
1364 m_physic.set_velocity_y(-300);
1365 m_on_ground_flag = false;
1366 Sector::get().add<Particles>(
1367 m_col.m_bbox.p2(),
1368 50, 70, 260, 280, Vector(0, 300), 3,
1369 Color(.4f, .4f, .4f), 3, .8f, LAYER_OBJECTS+1);
1370 Sector::get().add<Particles>(
1371 Vector(m_col.m_bbox.get_left(), m_col.m_bbox.get_bottom()),
1372 -70, -50, 260, 280, Vector(0, 300), 3,
1373 Color(.4f, .4f, .4f), 3, .8f, LAYER_OBJECTS+1);
1374 Sector::get().get_camera().shake(.1f, 0, 5);
1375 }
1376
1377 } else if (hit.top) {
1378 if (m_physic.get_velocity_y() < 0)
1379 m_physic.set_velocity_y(.2f);
1380 }
1381
1382 if ((hit.left || hit.right) && hit.slope_normal.x == 0) {
1383 m_physic.set_velocity_x(0);
1384 }
1385
1386 // crushed?
1387 if (hit.crush) {
1388 if (hit.left || hit.right) {
1389 kill(true);
1390 } else if (hit.top || hit.bottom) {
1391 kill(false);
1392 }
1393 }
1394}
1395
1396HitResponse
1397Player::collision(GameObject& other, const CollisionHit& hit)
1398{
1399 auto bullet = dynamic_cast<Bullet*> (&other);
1400 if (bullet) {
1401 return FORCE_MOVE;
1402 }
1403
1404 auto player = dynamic_cast<Player*> (&other);
1405 if (player) {
1406 return ABORT_MOVE;
1407 }
1408
1409 if (hit.left || hit.right) {
1410 try_grab(); //grab objects right now, in update it will be too late
1411 }
1412 assert(dynamic_cast<MovingObject*> (&other) != nullptr);
1413 auto moving_object = static_cast<MovingObject*> (&other);
1414 if (moving_object->get_group() == COLGROUP_TOUCHABLE) {
1415 auto trigger = dynamic_cast<TriggerBase*> (&other);
1416 if (trigger && !m_deactivated) {
1417 if (m_controller->pressed(Control::UP))
1418 trigger->event(*this, TriggerBase::EVENT_ACTIVATE);
1419 }
1420
1421 return FORCE_MOVE;
1422 }
1423
1424 auto badguy = dynamic_cast<BadGuy*> (&other);
1425 if (badguy != nullptr) {
1426 if (m_safe_timer.started() || m_invincible_timer.started())
1427 return FORCE_MOVE;
1428 if (m_stone)
1429 return ABORT_MOVE;
1430 }
1431
1432 return CONTINUE;
1433}
1434
1435void
1436Player::make_invincible()
1437{
1438 SoundManager::current()->play("sounds/invincible_start.ogg");
1439 m_invincible_timer.start(TUX_INVINCIBLE_TIME);
1440 Sector::get().get_singleton_by_type<MusicObject>().play_music(HERRING_MUSIC);
1441}
1442
1443void
1444Player::kill(bool completely)
1445{
1446 if (m_dying || m_deactivated || is_winning() )
1447 return;
1448
1449 if (!completely && (m_safe_timer.started() || m_invincible_timer.started() || m_stone))
1450 return;
1451
1452 m_growing = false;
1453
1454 if (m_climbing) stop_climbing(*m_climbing);
1455
1456 m_physic.set_velocity_x(0);
1457
1458 m_sprite->set_angle(0.0f);
1459 m_powersprite->set_angle(0.0f);
1460 m_lightsprite->set_angle(0.0f);
1461
1462 if (!completely && is_big()) {
1463 SoundManager::current()->play("sounds/hurt.wav");
1464
1465 if (m_player_status.bonus == FIRE_BONUS
1466 || m_player_status.bonus == ICE_BONUS
1467 || m_player_status.bonus == AIR_BONUS
1468 || m_player_status.bonus == EARTH_BONUS) {
1469 m_safe_timer.start(TUX_SAFE_TIME);
1470 set_bonus(GROWUP_BONUS, true);
1471 } else if (m_player_status.bonus == GROWUP_BONUS) {
1472 m_safe_timer.start(TUX_SAFE_TIME /* + GROWING_TIME */);
1473 m_duck = false;
1474 stop_backflipping();
1475 set_bonus(NO_BONUS, true);
1476 }
1477 } else {
1478 SoundManager::current()->play("sounds/kill.wav");
1479
1480 // do not die when in edit mode
1481 if (m_edit_mode) {
1482 set_ghost_mode(true);
1483 return;
1484 }
1485
1486 if (m_player_status.can_reach_checkpoint())
1487 {
1488 for (int i = 0; i < 5; i++)
1489 {
1490 // the numbers: starting x, starting y, velocity y
1491 Sector::get().add<FallingCoin>(get_pos() +
1492 Vector(graphicsRandom.randf(5.0f), graphicsRandom.randf(-32.0f, 18.0f)),
1493 graphicsRandom.randf(-100.0f, 100.0f));
1494 }
1495 m_player_status.take_checkpoint_coins();
1496 }
1497 else
1498 {
1499 GameSession::current()->set_reset_point("", Vector());
1500 }
1501 m_physic.enable_gravity(true);
1502 m_physic.set_gravity_modifier(1.0f); // Undo jump_early_apex
1503 m_safe_timer.stop();
1504 m_invincible_timer.stop();
1505 m_physic.set_acceleration(0, 0);
1506 m_physic.set_velocity(0, -700);
1507 set_bonus(NO_BONUS, true);
1508 m_dying = true;
1509 m_dying_timer.start(3.0);
1510 set_group(COLGROUP_DISABLED);
1511
1512 // TODO: need nice way to handle players dying in co-op mode
1513 Sector::get().get_effect().fade_out(3.0);
1514 SoundManager::current()->pause_music(3.0);
1515 }
1516}
1517
1518void
1519Player::move(const Vector& vector)
1520{
1521 set_pos(vector);
1522
1523 // Reset size to get correct hitbox if Tux was eg. ducked before moving
1524 if (is_big())
1525 m_col.set_size(TUX_WIDTH, BIG_TUX_HEIGHT);
1526 else
1527 m_col.set_size(TUX_WIDTH, SMALL_TUX_HEIGHT);
1528 m_duck = false;
1529 stop_backflipping();
1530 m_last_ground_y = vector.y;
1531 if (m_climbing) stop_climbing(*m_climbing);
1532
1533 m_physic.reset();
1534}
1535
1536void
1537Player::check_bounds()
1538{
1539 /* Keep tux in sector bounds: */
1540 if (get_pos().x < 0) {
1541 // Lock Tux to the size of the level, so that he doesn't fall off
1542 // the left side
1543 set_pos(Vector(0, get_pos().y));
1544 }
1545
1546 if (m_col.m_bbox.get_right() > Sector::get().get_width()) {
1547 // Lock Tux to the size of the level, so that he doesn't fall off
1548 // the right side
1549 set_pos(Vector(Sector::get().get_width() - m_col.m_bbox.get_width(), m_col.m_bbox.get_top()));
1550 }
1551
1552 /* fallen out of the level? */
1553 if ((get_pos().y > Sector::get().get_height()) && (!m_ghost_mode)) {
1554 kill(true);
1555 return;
1556 }
1557}
1558
1559void
1560Player::add_velocity(const Vector& velocity)
1561{
1562 m_physic.set_velocity(m_physic.get_velocity() + velocity);
1563}
1564
1565void
1566Player::add_velocity(const Vector& velocity, const Vector& end_speed)
1567{
1568 if (end_speed.x > 0)
1569 m_physic.set_velocity_x(std::min(m_physic.get_velocity_x() + velocity.x, end_speed.x));
1570 if (end_speed.x < 0)
1571 m_physic.set_velocity_x(std::max(m_physic.get_velocity_x() + velocity.x, end_speed.x));
1572 if (end_speed.y > 0)
1573 m_physic.set_velocity_y(std::min(m_physic.get_velocity_y() + velocity.y, end_speed.y));
1574 if (end_speed.y < 0)
1575 m_physic.set_velocity_y(std::max(m_physic.get_velocity_y() + velocity.y, end_speed.y));
1576}
1577
1578Vector
1579Player::get_velocity() const
1580{
1581 return m_physic.get_velocity();
1582}
1583
1584void
1585Player::bounce(BadGuy& )
1586{
1587 if (!(m_player_status.bonus == AIR_BONUS))
1588 m_physic.set_velocity_y(m_controller->hold(Control::JUMP) ? -520.0f : -300.0f);
1589 else {
1590 m_physic.set_velocity_y(m_controller->hold(Control::JUMP) ? -580.0f : -340.0f);
1591 m_ability_time = static_cast<float>(m_player_status.max_air_time) * GLIDE_TIME_PER_FLOWER;
1592 }
1593}
1594
1595//scripting Functions Below
1596
1597void
1598Player::deactivate()
1599{
1600 if (m_deactivated)
1601 return;
1602 m_deactivated = true;
1603 m_physic.set_velocity_x(0);
1604 m_physic.set_velocity_y(0);
1605 m_physic.set_acceleration_x(0);
1606 m_physic.set_acceleration_y(0);
1607 if (m_climbing) stop_climbing(*m_climbing);
1608}
1609
1610void
1611Player::activate()
1612{
1613 if (!m_deactivated)
1614 return;
1615 m_deactivated = false;
1616}
1617
1618void Player::walk(float speed)
1619{
1620 m_physic.set_velocity_x(speed);
1621}
1622
1623void Player::set_dir(bool right)
1624{
1625 m_dir = right ? Direction::RIGHT : Direction::LEFT;
1626}
1627
1628void
1629Player::set_ghost_mode(bool enable)
1630{
1631 if (m_ghost_mode == enable)
1632 return;
1633
1634 if (m_climbing) stop_climbing(*m_climbing);
1635
1636 if (m_grabbed_object) {
1637 m_grabbed_object->ungrab(*this, m_dir);
1638 m_grabbed_object = nullptr;
1639 }
1640
1641 if (enable) {
1642 m_ghost_mode = true;
1643 set_group(COLGROUP_DISABLED);
1644 m_physic.enable_gravity(false);
1645 log_debug << "You feel lightheaded. Use movement controls to float around, press ACTION to scare badguys." << std::endl;
1646 } else {
1647 m_ghost_mode = false;
1648 set_group(COLGROUP_MOVING);
1649 m_physic.enable_gravity(true);
1650 log_debug << "You feel solid again." << std::endl;
1651 }
1652}
1653
1654void
1655Player::set_edit_mode(bool enable)
1656{
1657 m_edit_mode = enable;
1658}
1659
1660void
1661Player::start_climbing(Climbable& climbable)
1662{
1663 if (m_climbing) return;
1664
1665 m_climbing = &climbable;
1666 m_physic.enable_gravity(false);
1667 m_physic.set_velocity(0, 0);
1668 m_physic.set_acceleration(0, 0);
1669 if (m_backflipping) {
1670 stop_backflipping();
1671 do_standup();
1672 }
1673}
1674
1675void
1676Player::stop_climbing(Climbable& /*climbable*/)
1677{
1678 if (!m_climbing) return;
1679
1680 m_climbing = nullptr;
1681
1682 if (m_grabbed_object) {
1683 m_grabbed_object->ungrab(*this, m_dir);
1684 m_grabbed_object = nullptr;
1685 }
1686
1687 m_physic.enable_gravity(true);
1688 m_physic.set_velocity(0, 0);
1689 m_physic.set_acceleration(0, 0);
1690
1691 if ((m_controller->hold(Control::JUMP)) || (m_controller->hold(Control::UP))) {
1692 m_on_ground_flag = true;
1693 // TODO: This won't help. Why?
1694 do_jump(-300);
1695 }
1696}
1697
1698void
1699Player::handle_input_climbing()
1700{
1701 if (!m_climbing) {
1702 log_warning << "handle_input_climbing called with climbing set to 0. Input handling skipped" << std::endl;
1703 return;
1704 }
1705
1706 float vx = 0;
1707 float vy = 0;
1708 if (m_controller->hold(Control::LEFT)) {
1709 m_dir = Direction::LEFT;
1710 vx -= MAX_CLIMB_XM;
1711 }
1712 if (m_controller->hold(Control::RIGHT)) {
1713 m_dir = Direction::RIGHT;
1714 vx += MAX_CLIMB_XM;
1715 }
1716 if (m_controller->hold(Control::UP)) {
1717 vy -= MAX_CLIMB_YM;
1718 }
1719 if (m_controller->hold(Control::DOWN)) {
1720 vy += MAX_CLIMB_YM;
1721 }
1722 if (m_controller->hold(Control::JUMP)) {
1723 if (m_can_jump) {
1724 stop_climbing(*m_climbing);
1725 return;
1726 }
1727 } else {
1728 m_can_jump = true;
1729 }
1730 if (m_controller->hold(Control::ACTION)) {
1731 stop_climbing(*m_climbing);
1732 return;
1733 }
1734 m_physic.set_velocity(vx, vy);
1735 m_physic.set_acceleration(0, 0);
1736}
1737
1738void
1739Player::stop_backflipping()
1740{
1741 m_backflipping = false;
1742 m_backflip_direction = 0;
1743 m_sprite->set_angle(0.0f);
1744 m_powersprite->set_angle(0.0f);
1745 m_lightsprite->set_angle(0.0f);
1746}
1747
1748bool
1749Player::has_grabbed(const std::string& object_name) const
1750{
1751 if (object_name.empty())
1752 {
1753 return false;
1754 }
1755 if (auto object = dynamic_cast<GameObject*>(m_grabbed_object))
1756 {
1757 return object->get_name() == object_name;
1758 }
1759 return false;
1760}
1761
1762/* EOF */
1763