41
41
#include " util/reader_mapping.hpp"
42
42
#include " video/surface.hpp"
43
43
44
- namespace
45
- {
46
- /* Maximum movement speed in pixels per LOGICAL_FPS. */
47
- constexpr float RECOVER_SPEED_NORMAL = -3 .125f ;
48
- constexpr float RECOVER_SPEED_LARGE = -2 .0f ;
49
- constexpr float PAUSE_TIME_NORMAL = 0 .5f ;
50
- constexpr float PAUSE_TIME_LARGE = 1 .0f ;
51
- constexpr float DETECT_RANGE = 1000 .f;
44
+ /* Maximum movement speed in pixels per LOGICAL_FPS. */
45
+ constexpr float RECOVER_SPEED_NORMAL = -3 .125f ;
46
+ constexpr float RECOVER_SPEED_LARGE = -2 .0f ;
47
+ constexpr float PAUSE_TIME_NORMAL = 0 .5f ;
48
+ constexpr float PAUSE_TIME_LARGE = 1 .0f ;
49
+ constexpr float DETECT_RANGE = 1000 .f;
52
50
53
- constexpr float MAX_CRUSH_SPEED = 700 .f;
51
+ constexpr float MAX_CRUSH_SPEED = 700 .f;
54
52
55
- constexpr float RECOVER_SPEED_MULTIPLIER_NORMAL = 1 .125f ;
56
- constexpr float RECOVER_SPEED_MULTIPLIER_LARGE = 1 .0f ;
53
+ constexpr float RECOVER_SPEED_MULTIPLIER_NORMAL = 1 .125f ;
54
+ constexpr float RECOVER_SPEED_MULTIPLIER_LARGE = 1 .0f ;
57
55
58
- constexpr float BRICK_BREAK_PROBE_DISTANCE = 12 .f;
59
- constexpr float RECOVERY_PATH_PROBE_DISTANCE = 1 .0f ;
60
- }
56
+ constexpr float BRICK_BREAK_PROBE_DISTANCE = 12 .f;
57
+ constexpr float RECOVERY_PATH_PROBE_DISTANCE = 1 .0f ;
58
+
59
+ // Visual offset of the roots from the crusher.
60
+ constexpr float ROOT_OFFSET_X = 2 .5f ;
61
+ constexpr float ROOT_OFFSET_Y = 5 .5f ;
61
62
62
63
Crusher::CrusherDirection
63
64
Crusher::CrusherDirection_from_string (std::string_view str)
@@ -507,83 +508,136 @@ Crusher::direction_from_vector(const Vector& vec)
507
508
}
508
509
509
510
void
510
- Crusher::spawn_roots ()
511
+ Crusher::spawn_roots (const CollisionHit& hit_info )
511
512
{
513
+ constexpr float TILE_SIZE = 32 .0f ;
514
+
512
515
Vector pos (0 .f , 0 .f );
513
516
const Direction dir = direction_from_vector (m_dir_vector);
514
517
float * axis{};
515
518
float origin{}, pos1{}, pos2{};
516
519
517
- switch (dir)
520
+ // Snap the impact origin to the tile grid.
521
+ if (hit_info.bottom || hit_info.top )
518
522
{
519
- case Direction::UP:
520
- origin = get_bbox ().get_top ();
521
- axis = &pos.x ;
522
- pos1 = get_bbox ().get_left ();
523
- pos2 = get_bbox ().get_right ();
524
- break ;
525
-
526
- case Direction::DOWN:
527
- origin = get_bbox ().get_bottom ();
528
- axis = &pos.x ;
529
- pos1 = get_bbox ().get_left ();
530
- pos2 = get_bbox ().get_right ();
531
- break ;
532
-
533
- case Direction::LEFT:
534
- origin = get_bbox ().get_left ();
535
- axis = &pos.y ;
536
- pos1 = get_bbox ().get_top ();
537
- pos2 = get_bbox ().get_bottom ();
538
- break ;
539
-
540
- case Direction::RIGHT:
541
- origin = get_bbox ().get_right ();
542
- axis = &pos.y ;
543
- pos1 = get_bbox ().get_top ();
544
- pos2 = get_bbox ().get_bottom ();
545
- break ;
546
- case Direction::AUTO:
547
- case Direction::NONE:
548
- default :
549
- return ;
523
+ origin = round ((hit_info.bottom ? get_bbox ().get_bottom () : get_bbox ().get_top ()) / TILE_SIZE) * TILE_SIZE;
524
+ axis = &pos.x ;
525
+ pos1 = get_bbox ().get_left ();
526
+ pos2 = get_bbox ().get_right ();
527
+ }
528
+ else if (hit_info.left || hit_info.right )
529
+ {
530
+ origin = round ((hit_info.left ? get_bbox ().get_left () : get_bbox ().get_right ()) / TILE_SIZE) * TILE_SIZE;
531
+ axis = &pos.y ;
532
+ pos1 = get_bbox ().get_top ();
533
+ pos2 = get_bbox ().get_bottom ();
534
+ }
535
+ else
536
+ {
537
+ return ;
550
538
}
551
539
552
540
*(axis == &pos.y ? &pos.x : &pos.y ) = origin;
553
541
554
- for (int i = 0 ; i < 3 ; ++i)
555
- {
556
- const float delay = (static_cast <float >(i) + 1 .f ) * 0 .6f ;
557
- Root& root = Sector::get ().add <Root>(Vector (0 .f , 0 .f ), invert_dir (dir),
558
- " images/creatures/mole/corrupted/root.sprite" ,
559
- delay, false , false );
560
- const float dimension = (axis == &pos.y ? root.get_height () : root.get_width ());
542
+ const Direction root_direction = invert_dir (dir);
543
+
544
+ auto spawn_roots_on_side = [&](float start, float sign) {
545
+ for (int i = 0 ; i < 3 ; ++i)
546
+ {
547
+ const float hatch_delay = 0 .05f ;
548
+ const float delay = (static_cast <float >(i) + 1 .f ) * 0 .22f ;
549
+ Root& root = Sector::get ().add <Root>(Vector (0 .f , 0 .f ),
550
+ root_direction,
551
+ " images/creatures/mole/corrupted/root.sprite" ,
552
+ hatch_delay, false , false , delay);
553
+
554
+ const float dimension = (axis == &pos.y ? root.get_height () : root.get_width ());
555
+
556
+ if (axis != nullptr )
557
+ *axis = start + sign * (10 .f + ((dimension / 2 .f ) + 50 .f ) * static_cast <float >(i));
558
+
559
+ // Ensure the root's base is on a solid surface.
560
+ Rectf base_check_box (pos - Vector (2 .0f , 2 .0f ), Sizef (4 .0f , 4 .0f ));
561
+ if (hit_info.bottom )
562
+ base_check_box.move (Vector (0 .f , 2 .0f ));
563
+ else if (hit_info.top )
564
+ base_check_box.move (Vector (0 .f , -2 .0f ));
565
+ else if (hit_info.left )
566
+ base_check_box.move (Vector (-2 .0f , 0 .f ));
567
+ else if (hit_info.right )
568
+ base_check_box.move (Vector (2 .0f , 0 .f ));
569
+
570
+ if (Sector::get ().is_free_of_tiles (base_check_box, false , Tile::SOLID))
571
+ {
572
+ root.remove_me ();
573
+ continue ;
574
+ }
561
575
562
- if (axis != nullptr )
563
- *axis = pos1 - 10 .f - (((dimension / 2 .f ) + 50 .f ) * static_cast <float >(i));
576
+ // Ensure the root's exit path is clear.
577
+ Rectf exit_path_box;
578
+ const Sizef root_size = root.get_bbox ().get_size ();
579
+ const float clearance = 1 .0f ;
564
580
565
- root.set_pos (pos);
566
- root.construct ();
567
- }
581
+ switch (root_direction)
582
+ {
583
+ case Direction::UP:
584
+ exit_path_box = Rectf (pos.x - root_size.width / 2 .0f , pos.y - root_size.height - clearance,
585
+ pos.x + root_size.width / 2 .0f , pos.y - clearance);
586
+ break ;
587
+ case Direction::DOWN:
588
+ exit_path_box = Rectf (pos.x - root_size.width / 2 .0f , pos.y + clearance,
589
+ pos.x + root_size.width / 2 .0f , pos.y + root_size.height + clearance);
590
+ break ;
591
+ case Direction::LEFT:
592
+ exit_path_box = Rectf (pos.x - root_size.width - clearance, pos.y - root_size.height / 2 .0f ,
593
+ pos.x - clearance, pos.y + root_size.height / 2 .0f );
594
+ break ;
595
+ case Direction::RIGHT:
596
+ exit_path_box = Rectf (pos.x + clearance, pos.y - root_size.height / 2 .0f ,
597
+ pos.x + root_size.width + clearance, pos.y + root_size.height / 2 .0f );
598
+ break ;
599
+ default :
600
+ break ;
601
+ }
568
602
569
- for (int i = 0 ; i < 3 ; ++i)
570
- {
571
- const float delay = (static_cast <float >(i) + 1 .f ) * 0 .6f ;
572
- Root& root = Sector::get ().add <Root>(Vector (0 .f , 0 .f ), invert_dir (dir),
573
- " images/creatures/mole/corrupted/root.sprite" ,
574
- delay, false , false );
575
- const float dimension = (axis == &pos.y ? root.get_height () : root.get_width ());
603
+ if (!exit_path_box.empty () && !Sector::get ().is_free_of_tiles (exit_path_box, false , Tile::SOLID))
604
+ {
605
+ root.remove_me ();
606
+ continue ;
607
+ }
608
+
609
+ Vector spawn_pos = pos;
610
+ switch (root_direction)
611
+ {
612
+ case Direction::UP:
613
+ // Addtional offset to account for grass.
614
+ spawn_pos.y += ROOT_OFFSET_Y - 4 .8f ;
615
+ break ;
616
+ case Direction::DOWN:
617
+ spawn_pos.y -= ROOT_OFFSET_Y;
618
+ break ;
619
+ case Direction::LEFT:
620
+ spawn_pos.x += ROOT_OFFSET_X;
621
+ break ;
622
+ case Direction::RIGHT:
623
+ spawn_pos.x -= ROOT_OFFSET_X;
624
+ break ;
625
+ default :
626
+ break ;
627
+ }
576
628
577
- if (axis != nullptr )
578
- *axis = pos2 + 10 .f + (((dimension / 2 .f ) + 50 .f ) * static_cast <float >(i));
629
+ root.set_pos (spawn_pos);
630
+ root.construct ();
631
+ root.initialize ();
632
+ }
633
+ };
579
634
580
- root.set_pos (pos);
581
- root.construct ();
582
- }
635
+ spawn_roots_on_side (pos1, -1 .f );
636
+ spawn_roots_on_side (pos2, +1 .f );
583
637
}
584
638
585
639
void
586
- Crusher::crushed (const CollisionHit& hit_info)
640
+ Crusher::crushed (const CollisionHit& hit_info, bool allow_root_spawn )
587
641
{
588
642
m_state = DELAY;
589
643
m_state_timer.start (m_ic_size == NORMAL ? PAUSE_TIME_NORMAL : PAUSE_TIME_LARGE, true );
@@ -599,8 +653,8 @@ Crusher::crushed(const CollisionHit& hit_info)
599
653
600
654
spawn_particles (hit_info);
601
655
602
- if (m_ic_type == CORRUPTED)
603
- spawn_roots ();
656
+ if (m_ic_type == CORRUPTED && allow_root_spawn )
657
+ spawn_roots (hit_info );
604
658
605
659
run_crush_script ();
606
660
}
@@ -807,7 +861,7 @@ Crusher::collision(MovingObject& other, const CollisionHit& hit)
807
861
if (m_dir_vector.y > 0 .5f && hit.bottom ) // Down, hit bottom
808
862
is_crushing_hit = true ;
809
863
else if (m_dir_vector.y < -0 .5f && hit.top ) // Up, hit top
810
- is_crushing_hit = true ;
864
+ is_crushing_hit = true ;
811
865
else if (m_dir_vector.x < -0 .5f && hit.left ) // Left, hit left
812
866
is_crushing_hit = true ;
813
867
else if (m_dir_vector.x > 0 .5f && hit.right ) // Right, hit right
@@ -828,7 +882,7 @@ Crusher::collision(MovingObject& other, const CollisionHit& hit)
828
882
if (player_vulnerable)
829
883
{
830
884
SoundManager::current ()->play (" sounds/brick.wav" , get_pos ());
831
- crushed (hit);
885
+ crushed (hit, false );
832
886
833
887
if (!player->is_invincible ())
834
888
{
@@ -841,7 +895,7 @@ Crusher::collision(MovingObject& other, const CollisionHit& hit)
841
895
842
896
if (other_crusher)
843
897
{
844
- crushed (hit);
898
+ crushed (hit, false );
845
899
return ABORT_MOVE;
846
900
}
847
901
@@ -858,7 +912,7 @@ Crusher::collision(MovingObject& other, const CollisionHit& hit)
858
912
if (m_dir != CrusherDirection::HORIZONTAL && m_dir != CrusherDirection::UP && m_dir != CrusherDirection::ALL)
859
913
{
860
914
SoundManager::current ()->play (" sounds/brick.wav" , get_pos ());
861
- crushed (hit);
915
+ crushed (hit, true );
862
916
return ABORT_MOVE;
863
917
}
864
918
else // Ensure the rock does not get stuck in a wall when pushed by the crusher.
@@ -904,7 +958,7 @@ Crusher::collision(MovingObject& other, const CollisionHit& hit)
904
958
905
959
if (rock_would_hit_wall)
906
960
{
907
- crushed (hit);
961
+ crushed (hit, true );
908
962
return ABORT_MOVE;
909
963
}
910
964
else
@@ -928,7 +982,7 @@ Crusher::collision_solid(const CollisionHit& hit)
928
982
{
929
983
if (m_state == CRUSHING && should_finish_crushing (hit))
930
984
{
931
- crushed (hit);
985
+ crushed (hit, true );
932
986
}
933
987
else if (m_state == RECOVERING)
934
988
{
@@ -1009,7 +1063,7 @@ Crusher::update(float dt_sec)
1009
1063
}
1010
1064
break ;
1011
1065
}
1012
-
1066
+
1013
1067
case DELAY:
1014
1068
if (m_state_timer.check ())
1015
1069
{
0 commit comments