diff --git a/src/assets/gfx/RPG DUNGEON VOL 3.png b/src/assets/gfx/RPG DUNGEON VOL 3.png index 67c4fb9..48eabc7 100644 Binary files a/src/assets/gfx/RPG DUNGEON VOL 3.png and b/src/assets/gfx/RPG DUNGEON VOL 3.png differ diff --git a/src/assets/gfx/RPG DUNGEON VOL 3.tres b/src/assets/gfx/RPG DUNGEON VOL 3.tres index a88f6d3..fd0129a 100644 --- a/src/assets/gfx/RPG DUNGEON VOL 3.tres +++ b/src/assets/gfx/RPG DUNGEON VOL 3.tres @@ -297,12 +297,16 @@ separation = Vector2i(1, 1) 9:9/0 = 0 10:9/0 = 0 11:9/0 = 0 +11:9/0/custom_data_0 = 1 12:9/0 = 0 +12:9/0/custom_data_0 = 1 13:9/0 = 0 14:9/0 = 0 15:9/0 = 0 16:9/0 = 0 +16:9/0/custom_data_0 = 2 17:9/0 = 0 +17:9/0/custom_data_0 = 2 18:9/0 = 0 19:9/0 = 0 0:10/0 = 0 @@ -321,9 +325,11 @@ separation = Vector2i(1, 1) 11:10/0 = 0 12:10/0 = 0 13:10/0 = 0 +13:10/0/custom_data_0 = -1 14:10/0 = 0 15:10/0 = 0 16:10/0 = 0 +16:10/0/custom_data_0 = -1 17:10/0 = 0 1:11/0 = 0 2:11/0 = 0 @@ -400,9 +406,11 @@ separation = Vector2i(1, 1) 11:13/0/custom_data_0 = -1 12:13/0 = 0 13:13/0 = 0 +13:13/0/custom_data_0 = -1 14:13/0 = 0 15:13/0 = 0 16:13/0 = 0 +16:13/0/custom_data_0 = -1 17:13/0 = 0 0:14/0 = 0 0:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(8, 2.66667, 8, 8, -8, 8, -8, 2.66667) @@ -411,13 +419,10 @@ separation = Vector2i(1, 1) 1:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(8, 2.66667, 8, 8, -8, 8, -8, 2.66667) 1:14/0/custom_data_0 = 8 2:14/0 = 0 -2:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, -2.66667, -8, -2.66667, 8, -8, 8) 2:14/0/custom_data_0 = 8 3:14/0 = 0 -3:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(2.66667, -8, 8, -8, 8, 8, 2.66667, 8) 3:14/0/custom_data_0 = 8 4:14/0 = 0 -4:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(8, 2.66667, 8, 8, -8, 8, -8, 2.66667) 4:14/0/custom_data_0 = 8 5:14/0 = 0 5:14/0/physics_layer_0/polygon_0/points = PackedVector2Array(8, 2.66667, 8, 8, -8, 8, -8, 2.66667) @@ -465,6 +470,35 @@ separation = Vector2i(1, 1) 14:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 14:1/0 = 0 14:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) +14:15/0 = 0 +15:15/0 = 0 +16:15/0 = 0 +17:15/0 = 0 +18:15/0 = 0 +19:15/0 = 0 +20:15/0 = 0 +20:16/0 = 0 +21:16/0 = 0 +21:15/0 = 0 +16:16/0 = 0 +15:16/0 = 0 +15:16/0/custom_data_0 = -2 +14:16/0 = 0 +13:16/0 = 0 +12:16/0 = 0 +11:16/0 = 0 +10:16/0 = 0 +9:16/0 = 0 +7:15/0 = 0 +1:16/0 = 0 +0:16/0 = 0 +2:16/0 = 0 +3:16/0 = 0 +4:16/0 = 0 +5:16/0 = 0 +6:16/0 = 0 +7:16/0 = 0 +8:16/0 = 0 [resource] occlusion_layer_0/light_mask = 1 diff --git a/src/scenes/boss_room_test.tscn b/src/scenes/boss_room_test.tscn index 6c2b332..3b9a957 100644 --- a/src/scenes/boss_room_test.tscn +++ b/src/scenes/boss_room_test.tscn @@ -28,11 +28,11 @@ zoom = Vector2(3, 3) [node name="DungeonLayer0" type="TileMapLayer" parent="Environment" unique_id=747504971] z_index = -2 -tile_map_data = PackedByteArray("AAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAEAAAAAAAIAAAAAAAIAAAAAAAMAAAAAAAIAAAAAAAQAAAAAAAIAAAABAAAAAAABAAAAAAABAAEAAAABAAEAAAABAAIAAAABAAIAAAABAAMAAAABAAIAAAABAAQAAAABAAIAAAACAAAAAAACAAAAAAACAAEAAAACAAEAAAACAAIAAAAOAAgAAAACAAMAAAAOAAgAAAACAAQAAAAOAAgAAAADAAAAAAACAAAAAAADAAEAAAACAAEAAAADAAIAAAAOAAgAAAADAAMAAAAOAAgAAAADAAQAAAAOAAgAAAAEAAAAAAACAAAAAAAEAAEAAAACAAEAAAAEAAIAAAAOAAgAAAAEAAMAAAAOAAgAAAAEAAQAAAAOAAgAAAAFAAAAAAACAAAAAAAFAAEAAAACAAEAAAAGAAAAAAACAAAAAAAGAAEAAAACAAEAAAAHAAAAAAACAAAAAAAHAAEAAAACAAEAAAAIAAAAAAACAAAAAAAIAAEAAAACAAEAAAAJAAAAAAACAAAAAAAJAAEAAAACAAEAAAAKAAAAAAACAAAAAAAKAAEAAAACAAEAAAALAAAAAAACAAAAAAALAAEAAAACAAEAAAAMAAAAAAACAAAAAAAMAAEAAAACAAEAAAANAAAAAAACAAAAAAANAAEAAAACAAEAAAAOAAAAAAACAAAAAAAOAAEAAAACAAEAAAAPAAAAAAACAAAAAAAPAAEAAAACAAEAAAAQAAAAAAACAAAAAAAQAAEAAAACAAEAAAARAAAAAAACAAAAAAARAAEAAAACAAEAAAASAAAAAAACAAAAAAASAAEAAAACAAEAAAATAAAAAAACAAAAAAATAAEAAAACAAEAAAAUAAAAAAACAAAAAAAUAAEAAAACAAEAAAAVAAAAAAACAAAAAAAVAAEAAAACAAEAAAAWAAAAAAACAAAAAAAWAAEAAAACAAEAAAAXAAAAAAACAAAAAAAXAAEAAAACAAEAAAAYAAAAAAACAAAAAAAYAAEAAAACAAEAAAAZAAAAAAACAAAAAAAZAAEAAAACAAEAAAAaAAAAAAACAAAAAAAaAAEAAAACAAEAAAAbAAAAAAACAAAAAAAbAAEAAAACAAEAAAAcAAAAAAACAAAAAAAcAAEAAAACAAEAAAAdAAAAAAACAAAAAAAdAAEAAAACAAEAAAAeAAAAAAACAAAAAAAeAAEAAAACAAEAAAAfAAAAAAACAAAAAAAfAAEAAAACAAEAAAAgAAAAAAACAAAAAAAgAAEAAAACAAEAAAAhAAAAAAACAAAAAAAhAAEAAAACAAEAAAAAAAUAAAAAAAIAAAABAAUAAAABAAIAAAAAAAYAAAAAAAIAAAABAAYAAAABAAIAAAAAAAcAAAAAAAIAAAABAAcAAAABAAIAAAAAAAgAAAAAAAIAAAABAAgAAAABAAIAAAAAAAkAAAAAAAIAAAABAAkAAAABAAIAAAAAAAoAAAAAAAIAAAABAAoAAAABAAIAAAAAAAsAAAAAAAIAAAABAAsAAAABAAIAAAAAAAwAAAAAAAIAAAABAAwAAAABAAIAAAAAAA0AAAAAAAIAAAABAA0AAAABAAIAAAAAAA4AAAAAAAIAAAABAA4AAAABAAIAAAAAAA8AAAAAAAIAAAABAA8AAAABAAIAAAAAABAAAAAAAAIAAAABABAAAAABAAIAAAAAABEAAAAAAAIAAAABABEAAAABAAIAAAAAABUAAAAAAAMAAAAAABYAAAAAAAAAAAABABUAAAABAAMAAAABABYAAAABAAQAAAAAABIAAAAAAAIAAAABABIAAAABAAIAAAAAABMAAAAAAAIAAAABABMAAAABAAIAAAAAABQAAAAAAAIAAAABABQAAAABAAIAAAACABUAAAACAAMAAAACABYAAAACAAQAAAADABUAAAACAAMAAAADABYAAAACAAQAAAAEABUAAAACAAMAAAAEABYAAAACAAQAAAAFABUAAAACAAMAAAAFABYAAAACAAQAAAAGABUAAAACAAMAAAAGABYAAAACAAQAAAAHABUAAAACAAMAAAAHABYAAAACAAQAAAAIABUAAAACAAMAAAAIABYAAAACAAQAAAAJABUAAAACAAMAAAAJABYAAAACAAQAAAAKABUAAAACAAMAAAAKABYAAAACAAQAAAALABUAAAACAAMAAAALABYAAAACAAQAAAAMABUAAAACAAMAAAAMABYAAAACAAQAAAANABUAAAACAAMAAAANABYAAAACAAQAAAAOABUAAAACAAMAAAAOABYAAAACAAQAAAAPABUAAAACAAMAAAAPABYAAAACAAQAAAAQABUAAAACAAMAAAAQABYAAAACAAQAAAARABUAAAACAAMAAAARABYAAAACAAQAAAASABUAAAACAAMAAAASABYAAAACAAQAAAATABUAAAACAAMAAAATABYAAAACAAQAAAAUABUAAAACAAMAAAAUABYAAAACAAQAAAAVABUAAAAHAAUAAAAVABYAAAAHAAYAAAAWABUAAAAJAAgAAAAWABYAAAAIAAYAAAAXABUAAAAJAAUAAAAXABYAAAAJAAYAAAAYABUAAAACAAMAAAAYABYAAAACAAQAAAAZABUAAAACAAMAAAAZABYAAAACAAQAAAAaABUAAAACAAMAAAAaABYAAAACAAQAAAAbABUAAAACAAMAAAAbABYAAAACAAQAAAAcABUAAAACAAMAAAAcABYAAAACAAQAAAAdABUAAAACAAMAAAAdABYAAAACAAQAAAAeABUAAAACAAMAAAAeABYAAAACAAQAAAAfABUAAAACAAMAAAAfABYAAAACAAQAAAAgABUAAAACAAMAAAAgABYAAAACAAQAAAAhABUAAAACAAMAAAAhABYAAAACAAQAAAAiABUAAAACAAMAAAAiABYAAAACAAQAAAAjABUAAAACAAMAAAAjABYAAAACAAQAAAAkABUAAAACAAMAAAAkABYAAAACAAQAAAAlABUAAAACAAMAAAAlABYAAAACAAQAAAAmABUAAAACAAMAAAAmABYAAAACAAQAAAAnABUAAAACAAMAAAAnABYAAAACAAQAAAAoABUAAAACAAMAAAAoABYAAAACAAQAAAApABUAAAACAAMAAAApABYAAAACAAQAAAAqABUAAAADAAMAAAAqABYAAAADAAQAAAArABUAAAAEAAMAAAArABYAAAAAAAAAAAAqABMAAAADAAIAAAArABMAAAAEAAIAAAAqABQAAAADAAIAAAArABQAAAAEAAIAAAAqABIAAAADAAIAAAArABIAAAAEAAIAAAAqABEAAAADAAIAAAArABEAAAAEAAIAAAAqABAAAAADAAIAAAArABAAAAAEAAIAAAAqAA8AAAADAAIAAAArAA8AAAAEAAIAAAAqAA4AAAADAAIAAAArAA4AAAAEAAIAAAAqAA0AAAADAAIAAAArAA0AAAAEAAIAAAAqAAwAAAADAAIAAAArAAwAAAAEAAIAAAAqAAsAAAADAAIAAAArAAsAAAAEAAIAAAAqAAoAAAADAAIAAAArAAoAAAAEAAIAAAAqAAkAAAADAAIAAAArAAkAAAAEAAIAAAAqAAgAAAADAAIAAAArAAgAAAAEAAIAAAAqAAcAAAADAAIAAAArAAcAAAAEAAIAAAAqAAYAAAADAAIAAAArAAYAAAAEAAIAAAAqAAUAAAADAAIAAAArAAUAAAAEAAIAAAAqAAQAAAADAAIAAAArAAQAAAAEAAIAAAAqAAMAAAADAAIAAAArAAMAAAAEAAIAAAAqAAIAAAADAAIAAAArAAIAAAAEAAIAAAAqAAAAAAADAAAAAAAqAAEAAAADAAEAAAArAAAAAAAAAAAAAAArAAEAAAAEAAEAAAApAAAAAAACAAAAAAApAAEAAAACAAEAAAAoAAAAAAACAAAAAAAoAAEAAAACAAEAAAAnAAAAAAACAAAAAAAnAAEAAAACAAEAAAAmAAAAAAACAAAAAAAmAAEAAAACAAEAAAAlAAAAAAACAAAAAAAlAAEAAAACAAEAAAAkAAAAAAACAAAAAAAkAAEAAAACAAEAAAAjAAAAAAACAAAAAAAjAAEAAAACAAEAAAAiAAAAAAACAAAAAAAiAAEAAAACAAEAAAAFAAMAAAAOAAgAAAAFAAQAAAAOAAgAAAAGAAMAAAAOAAgAAAAHAAMAAAAOAAgAAAAFAAIAAAAOAAgAAAAGAAIAAAAOAAgAAAAHAAIAAAAOAAgAAAAIAAIAAAAOAAgAAAAJAAMAAAAOAAgAAAAKAAMAAAAOAAgAAAALAAMAAAAOAAgAAAAIAAMAAAAOAAgAAAAMAAMAAAAOAAgAAAALAAIAAAAOAAgAAAAKAAIAAAAOAAgAAAAJAAIAAAAOAAgAAAAHAAQAAAAOAAgAAAAHAAUAAAAOAAgAAAAIAAYAAAAGAAsAAAAJAAYAAAAGAAsAAAAKAAYAAAAGAAsAAAALAAYAAAAOAAgAAAALAAUAAAAJAAgAAAAMAAUAAAAOAAgAAAANAAUAAAAOAAgAAAAOAAUAAAAOAAgAAAAOAAQAAAAOAAgAAAAPAAQAAAAOAAgAAAANAAQAAAAOAAgAAAAMAAQAAAAOAAgAAAALAAQAAAAOAAgAAAAKAAQAAAAOAAgAAAAJAAQAAAAOAAgAAAAIAAQAAAAOAAgAAAAGAAQAAAAOAAgAAAAQAAQAAAAOAAgAAAARAAQAAAAOAAgAAAARAAUAAAAGAAsAAAAQAAUAAAAOAAgAAAAPAAUAAAAOAAgAAAANAAMAAAAOAAgAAAAOAAMAAAAOAAgAAAAPAAMAAAAOAAgAAAAQAAMAAAAOAAgAAAARAAMAAAAOAAgAAAASAAMAAAAOAAgAAAATAAMAAAAOAAgAAAASAAQAAAAOAAgAAAAIAAUAAAAOAAgAAAAJAAUAAAAOAAgAAAATAAQAAAAGAAsAAAAMAAIAAAAOAAgAAAAUAAMAAAAOAAgAAAAUAAIAAAAOAAgAAAATAAIAAAAOAAgAAAASAAIAAAAOAAgAAAARAAIAAAAOAAgAAAAQAAIAAAAOAAgAAAAPAAIAAAAOAAgAAAAOAAIAAAAOAAgAAAANAAIAAAAOAAgAAAAKAAUAAAAOAAgAAAAGAAUAAAAOAAgAAAAFAAUAAAAOAAgAAAAEAAUAAAAOAAgAAAADAAUAAAAOAAgAAAACAAUAAAAOAAgAAAACAAYAAAAOAAgAAAADAAYAAAAOAAgAAAAEAAYAAAAOAAgAAAAFAAYAAAAOAAgAAAAGAAYAAAAOAAgAAAAHAAYAAAAOAAgAAAAVAAgAAAAOAAgAAAAVAAcAAAAJAAgAAAAVAAYAAAAOAAgAAAAVAAUAAAAGAAsAAAAVAAQAAAAOAAgAAAAVAAMAAAAOAAgAAAAVAAIAAAAOAAgAAAAWAAIAAAAOAAgAAAAWAAMAAAAOAAgAAAAWAAQAAAAOAAgAAAAWAAUAAAAJAAsAAAAWAAYAAAAGAAsAAAAWAAcAAAAJAA0AAAAWAAgAAAAJAAgAAAAWAAkAAAAJAAgAAAAVAAkAAAAJAAgAAAAUAAkAAAAJAAgAAAAUAAgAAAAJAAgAAAAUAAcAAAAJAAgAAAAUAAYAAAAOAAgAAAAUAAUAAAAGAAsAAAAUAAQAAAAGAAsAAAATAAUAAAAGAAsAAAASAAUAAAAGAAsAAAASAAYAAAAGAAsAAAARAAYAAAAGAAsAAAAQAAYAAAAGAAsAAAAPAAYAAAAGAAsAAAAOAAYAAAAOAAgAAAANAAYAAAAOAAgAAAAMAAYAAAAOAAgAAAAMAAcAAAAGAAsAAAALAAcAAAAGAAsAAAAKAAcAAAAGAAsAAAAJAAcAAAAGAAsAAAAIAAcAAAAGAAsAAAAHAAcAAAAGAAsAAAAGAAcAAAAGAAsAAAAFAAcAAAAOAAgAAAAEAAcAAAAOAAgAAAADAAcAAAAOAAgAAAACAAcAAAAOAAgAAAACAAgAAAAOAAgAAAACAAkAAAAOAAgAAAACAAoAAAAOAAgAAAACAAsAAAAOAAgAAAACAAwAAAAOAAgAAAACAA0AAAAOAAgAAAACAA4AAAAOAAgAAAACAA8AAAAOAAgAAAACABAAAAAOAAgAAAACABEAAAAOAAgAAAACABIAAAAOAAgAAAACABMAAAAOAAgAAAACABQAAAAOAAgAAAADABQAAAAOAAgAAAADABMAAAAOAAgAAAADABIAAAAOAAgAAAADABEAAAAOAAgAAAADABAAAAAOAAgAAAADAA8AAAAOAAgAAAADAA4AAAAOAAgAAAADAA0AAAAOAAgAAAADAAwAAAAOAAgAAAADAAsAAAAOAAgAAAADAAoAAAAOAAgAAAADAAkAAAAOAAgAAAADAAgAAAAOAAgAAAAEAAgAAAAOAAgAAAAEAAkAAAAOAAgAAAAEAAoAAAAOAAgAAAAEAAsAAAAOAAgAAAAEAAwAAAAOAAgAAAAEAA0AAAAOAAgAAAAEAA4AAAAOAAgAAAAEAA8AAAAOAAgAAAAEABAAAAAOAAgAAAAEABEAAAAOAAgAAAAEABIAAAAOAAgAAAAEABMAAAAOAAgAAAAEABQAAAAOAAgAAAAFABQAAAAOAAgAAAAFABMAAAAOAAgAAAAFABIAAAAOAAgAAAAFABEAAAAOAAgAAAAFABAAAAAGAAsAAAAFAA8AAAAGAAsAAAAFAA4AAAAGAAsAAAAFAA0AAAAGAAsAAAAFAAwAAAAGAAsAAAAFAAsAAAAGAAsAAAAFAAoAAAAGAAsAAAAFAAkAAAAGAAsAAAAFAAgAAAAOAAgAAAAGAAgAAAAGAAsAAAAGAAkAAAAGAAsAAAAGAAoAAAAGAAsAAAAGAAsAAAAOAAgAAAAGAAwAAAAOAAgAAAAGAA0AAAAOAAgAAAAGAA4AAAAOAAgAAAAGAA8AAAAGAAsAAAAGABAAAAAGAAsAAAAGABEAAAAOAAgAAAAGABIAAAAOAAgAAAAGABMAAAAOAAgAAAAGABQAAAAOAAgAAAAHABQAAAAOAAgAAAAHABMAAAAOAAgAAAAHABIAAAAGAAsAAAAHABEAAAAGAAsAAAAHABAAAAAGAAsAAAAHAA8AAAAGAAsAAAAHAA4AAAAGAAsAAAAHAA0AAAAGAAsAAAAHAAwAAAAGAAsAAAAHAAsAAAAGAAsAAAAHAAoAAAAGAAsAAAAHAAkAAAAOAAgAAAAHAAgAAAAOAAgAAAAIAAgAAAAOAAgAAAAIAAkAAAAOAAgAAAAIAAoAAAAGAAsAAAAIAAsAAAAGAAsAAAAIAAwAAAAOAAgAAAAIAA0AAAAOAAgAAAAIAA4AAAAJAAgAAAAIAA8AAAAJAAgAAAAIABAAAAAOAAgAAAAIABEAAAAOAAgAAAAIABIAAAAGAAsAAAAIABMAAAAOAAgAAAAIABQAAAAOAAgAAAAJABQAAAAOAAgAAAAJABMAAAAOAAgAAAAJABIAAAAGAAsAAAAJABEAAAAOAAgAAAAJABAAAAAOAAgAAAAJAA8AAAAJAAgAAAAJAA4AAAAOAAgAAAAJAA0AAAAJAAgAAAAJAAwAAAAGAAsAAAAJAAsAAAAGAAsAAAAJAAoAAAAOAAgAAAAJAAkAAAAGAAsAAAAJAAgAAAAGAAsAAAAKAAgAAAAGAAsAAAAKAAkAAAAGAAsAAAAKAAoAAAAJAAgAAAAKAAsAAAAJAAgAAAAKAAwAAAAGAAsAAAAKAA0AAAAGAAsAAAAKAA4AAAAOAAgAAAAKAA8AAAAOAAgAAAAKABAAAAAJAAgAAAAKABEAAAAOAAgAAAAKABIAAAAGAAsAAAAKABMAAAAOAAgAAAAKABQAAAAOAAgAAAALABQAAAAOAAgAAAALABMAAAAOAAgAAAALABIAAAAGAAsAAAALABEAAAAGAAsAAAALABAAAAAJAAgAAAALAA8AAAAOAAgAAAALAA4AAAAOAAgAAAALAA0AAAAGAAsAAAALAAwAAAAGAAsAAAALAAsAAAAJAAgAAAALAAoAAAAOAAgAAAALAAkAAAAGAAsAAAALAAgAAAAGAAsAAAAMAAgAAAAGAAsAAAAMAAkAAAAJAAgAAAAMAAoAAAAOAAgAAAAMAAsAAAAJAAgAAAAMAAwAAAAGAAsAAAAMAA0AAAAOAAgAAAAMAA4AAAAGAAsAAAAMAA8AAAAOAAgAAAAMABAAAAAOAAgAAAAMABEAAAAGAAsAAAAMABIAAAAOAAgAAAAMABMAAAAOAAgAAAAMABQAAAAOAAgAAAANABQAAAAOAAgAAAANABMAAAAOAAgAAAANABIAAAAOAAgAAAANABEAAAAGAAsAAAANABAAAAAGAAsAAAANAA8AAAAJAAgAAAANAA4AAAAGAAsAAAANAA0AAAAOAAgAAAANAAwAAAAGAAsAAAANAAsAAAAGAAsAAAANAAoAAAAGAAsAAAANAAkAAAAJAAgAAAANAAgAAAAGAAsAAAANAAcAAAAGAAsAAAAOAAcAAAAGAAsAAAAOAAgAAAAGAAsAAAAOAAkAAAAGAAsAAAAOAAoAAAAJAAgAAAAOAAsAAAAJAAgAAAAOAAwAAAAJAAgAAAAOAA0AAAAGAAsAAAAOAA4AAAAJAAgAAAAOAA8AAAAGAAsAAAAOABAAAAAGAAsAAAAOABEAAAAGAAsAAAAOABIAAAAOAAgAAAAOABMAAAAOAAgAAAAOABQAAAAOAAgAAAAPABQAAAAOAAgAAAAPABMAAAAOAAgAAAAPABIAAAAOAAgAAAAPABEAAAAGAAsAAAAPABAAAAAGAAsAAAAPAA8AAAAGAAsAAAAPAA4AAAAGAAsAAAAPAA0AAAAGAAsAAAAPAAwAAAAJAAgAAAAPAAsAAAAJAAgAAAAPAAoAAAAJAAgAAAAPAAkAAAAJAAgAAAAPAAgAAAAGAAsAAAAPAAcAAAAGAAsAAAAQAAcAAAAGAAsAAAAQAAgAAAAJAAgAAAAQAAkAAAAJAAgAAAAQAAoAAAAJAAgAAAAQAAsAAAAOAAgAAAAQAAwAAAAJAAgAAAAQAA0AAAAGAAsAAAAQAA4AAAAOAAgAAAAQAA8AAAAGAAsAAAAQABAAAAAGAAsAAAAQABEAAAAGAAsAAAAQABIAAAAOAAgAAAAQABMAAAAOAAgAAAAQABQAAAAOAAgAAAARABQAAAAOAAgAAAARABMAAAAOAAgAAAARABIAAAAOAAgAAAARABEAAAAGAAsAAAARABAAAAAGAAsAAAARAA8AAAAGAAsAAAARAA4AAAAJAAgAAAARAA0AAAAGAAsAAAARAAwAAAAJAAgAAAARAAsAAAAOAAgAAAARAAoAAAAOAAgAAAARAAkAAAAJAAgAAAARAAgAAAAJAAgAAAARAAcAAAAGAAsAAAASAAcAAAAJAAgAAAASAAgAAAAJAAgAAAASAAkAAAAOAAgAAAASAAoAAAAOAAgAAAASAAsAAAAJAAgAAAASAAwAAAAGAAsAAAASAA0AAAAGAAsAAAASAA4AAAAGAAsAAAASAA8AAAAGAAsAAAASABAAAAAGAAsAAAASABEAAAAGAAsAAAASABIAAAAOAAgAAAASABMAAAAOAAgAAAASABQAAAAOAAgAAAATABQAAAAOAAgAAAATABMAAAAOAAgAAAATABIAAAAOAAgAAAATABEAAAAGAAsAAAATABAAAAAGAAsAAAATAA8AAAAJAAgAAAATAA4AAAAGAAsAAAATAA0AAAAGAAsAAAATAAwAAAAJAAgAAAATAAsAAAAGAAsAAAATAAoAAAAJAAgAAAATAAkAAAAJAAgAAAATAAgAAAAJAAgAAAATAAcAAAAJAAgAAAATAAYAAAAGAAsAAAAUAAoAAAAJAAgAAAAUAAsAAAAGAAsAAAAUAAwAAAAGAAsAAAAUAA0AAAAGAAsAAAAUAA4AAAAGAAsAAAAUAA8AAAAJAAgAAAAUABAAAAAGAAsAAAAUABEAAAAGAAsAAAAUABIAAAAOAAgAAAAUABMAAAAOAAgAAAAUABQAAAAOAAgAAAAVABQAAAAOAAgAAAAVABMAAAAOAAgAAAAVABIAAAAOAAgAAAAVABEAAAAGAAsAAAAVABAAAAAGAAsAAAAVAA8AAAAJAAgAAAAVAA4AAAAGAAsAAAAVAA0AAAAGAAsAAAAVAAwAAAAGAAsAAAAVAAsAAAAOAAgAAAAVAAoAAAAGAAsAAAAWAAoAAAAGAAsAAAAWAAsAAAAGAAsAAAAWAAwAAAAGAAsAAAAWAA0AAAAGAAsAAAAWAA4AAAAOAAgAAAAWAA8AAAAJAAgAAAAWABAAAAAGAAsAAAAWABEAAAAGAAsAAAAWABIAAAAOAAgAAAAWABMAAAAOAAgAAAAWABQAAAAOAAgAAAAXABQAAAAOAAkAAAAXABMAAAAOAAgAAAAXABIAAAAOAAgAAAAXABEAAAAGAAsAAAAXABAAAAAGAAsAAAAXAA8AAAAJAAgAAAAXAA4AAAAOAAgAAAAXAA0AAAAGAAsAAAAXAAwAAAAGAAsAAAAXAAsAAAAGAAsAAAAXAAoAAAAOAAgAAAAXAAkAAAAGAAsAAAAXAAgAAAAOAAgAAAAXAAcAAAAKAA0AAAAXAAYAAAAHAAsAAAAXAAUAAAAKAAsAAAAXAAQAAAAOAAgAAAAXAAMAAAAOAAgAAAAXAAIAAAAOAAcAAAAYAAIAAAAOAAcAAAAYAAMAAAAJAAsAAAAYAAQAAAAJAAwAAAAYAAUAAAAQAA0AAAAYAAYAAAAKAAwAAAAYAAcAAAAKAA0AAAAYAAgAAAAOAAgAAAAYAAkAAAAGAAsAAAAYAAoAAAAOAAgAAAAYAAsAAAAGAAsAAAAYAAwAAAAGAAsAAAAYAA0AAAAGAAsAAAAYAA4AAAAOAAgAAAAYAA8AAAAJAAgAAAAYABAAAAAGAAsAAAAYABEAAAAGAAsAAAAYABIAAAAOAAgAAAAYABMAAAAOAAgAAAAYABQAAAAOAAkAAAAZABQAAAAOAAkAAAAZABMAAAAOAAgAAAAZABIAAAAOAAgAAAAZABEAAAAGAAsAAAAZABAAAAAGAAsAAAAZAA8AAAAJAAgAAAAZAA4AAAAJAAgAAAAZAA0AAAAGAAsAAAAZAAwAAAAGAAsAAAAZAAsAAAAOAAgAAAAZAAoAAAAGAAsAAAAZAAkAAAAGAAsAAAAZAAgAAAAGAAsAAAAZAAcAAAALAA0AAAAZAAYAAAALAAwAAAAZAAUAAAANAAoAAAAZAAQAAAAKAAwAAAAZAAMAAAAKAAsAAAAZAAIAAAAOAAcAAAAaAAIAAAAOAAcAAAAaAAMAAAALAAsAAAAaAAQAAAALAAwAAAAaAAUAAAALAA0AAAAaAAYAAAAOAAgAAAAaAAcAAAAJAAgAAAAaAAgAAAAGAAsAAAAaAAkAAAAGAAsAAAAaAAoAAAAGAAsAAAAaAAsAAAAOAAgAAAAaAAwAAAAJAAgAAAAaAA0AAAAGAAsAAAAaAA4AAAAJAAgAAAAaAA8AAAAGAAsAAAAaABAAAAAGAAsAAAAaABEAAAAGAAsAAAAaABIAAAAOAAgAAAAaABMAAAAOAAgAAAAaABQAAAAOAAkAAAAbABQAAAAOAAkAAAAbABMAAAAOAAgAAAAbABIAAAAOAAgAAAAbABEAAAAGAAsAAAAbABAAAAAGAAsAAAAbAA8AAAAGAAsAAAAbAA4AAAAJAAgAAAAbAA0AAAAGAAsAAAAbAAwAAAAJAAgAAAAbAAsAAAAOAAgAAAAbAAoAAAAGAAsAAAAbAAkAAAAJAAgAAAAbAAgAAAAJAAgAAAAbAAcAAAAJAAgAAAAbAAYAAAAGAAsAAAAbAAUAAAAOAAgAAAAbAAQAAAAOAAgAAAAbAAMAAAAOAAgAAAAbAAIAAAAOAAcAAAAcAAIAAAAOAAcAAAAcAAMAAAAOAAgAAAAcAAQAAAAOAAgAAAAcAAUAAAAOAAgAAAAcAAYAAAAGAAsAAAAcAAcAAAAJAAgAAAAcAAgAAAAJAAgAAAAcAAkAAAAJAAgAAAAcAAoAAAAGAAsAAAAcAAsAAAAGAAsAAAAcAAwAAAAGAAsAAAAcAA0AAAAJAAgAAAAcAA4AAAAJAAgAAAAcAA8AAAAGAAsAAAAcABAAAAAGAAsAAAAcABEAAAAGAAsAAAAcABIAAAAOAAgAAAAcABMAAAAOAAgAAAAcABQAAAAOAAkAAAAdABQAAAAOAAkAAAAdABMAAAAOAAgAAAAdABIAAAAOAAgAAAAdABEAAAAGAAsAAAAdABAAAAAGAAsAAAAdAA8AAAAGAAsAAAAdAA4AAAAOAAgAAAAdAA0AAAAOAAgAAAAdAAwAAAAAAAMAAAAdAAsAAAAAAAIAAAAdAAoAAAAAAAIAAAAdAAkAAAAAAAIAAAAdAAgAAAAAAAEAAAAdAAcAAAAOAAgAAAAdAAYAAAAGAAsAAAAdAAUAAAAOAAgAAAAdAAQAAAAOAAgAAAAdAAMAAAAOAAgAAAAdAAIAAAAOAAcAAAAeAAIAAAAOAAcAAAAeAAMAAAAOAAgAAAAeAAQAAAAOAAgAAAAeAAUAAAAOAAgAAAAeAAYAAAAGAAsAAAAeAAcAAAABAAAAAAAeAAgAAAABAAEAAAAeAAkAAAABAAIAAAAeAAoAAAABAAIAAAAeAAsAAAABAAIAAAAeAAwAAAABAAMAAAAeAA0AAAABAAQAAAAeAA4AAAAGAAsAAAAeAA8AAAAGAAsAAAAeABAAAAAGAAsAAAAeABEAAAAOAAgAAAAeABIAAAAOAAgAAAAeABMAAAAOAAgAAAAeABQAAAAOAAkAAAAfABQAAAAOAAkAAAAfABIAAAAOAAgAAAAfABEAAAAOAAgAAAAfABAAAAAGAAsAAAAfAA8AAAAOAAgAAAAfAA4AAAAGAAsAAAAfAA0AAAACAAQAAAAfAAwAAAACAAMAAAAfAAsAAAAJAA0AAAAfAAoAAAAJAAwAAAAfAAkAAAAJAAsAAAAfAAgAAAACAAEAAAAfAAcAAAACAAAAAAAfAAYAAAAGAAsAAAAfAAUAAAAOAAgAAAAfAAQAAAAOAAgAAAAfAAMAAAAOAAgAAAAfAAIAAAAOAAcAAAAgAAIAAAAOAAcAAAAgAAMAAAAOAAgAAAAgAAQAAAAOAAgAAAAgAAUAAAAOAAgAAAAgAAYAAAAJAAgAAAAgAAcAAAACAAAAAAAgAAgAAAACAAEAAAAgAAkAAAAKAAsAAAAgAAoAAAAKAAwAAAAgAAsAAAAKAA0AAAAgAAwAAAACAAMAAAAgAA0AAAACAAQAAAAgAA4AAAAGAAsAAAAgAA8AAAAGAAsAAAAgABAAAAAOAAgAAAAgABEAAAAOAAgAAAAgABIAAAAOAAgAAAAgABQAAAAOAAkAAAAhABQAAAAOAAkAAAAhABMAAAAOAAgAAAAhABIAAAAGAAsAAAAhABEAAAAOAAgAAAAhABAAAAAOAAgAAAAhAA8AAAAGAAsAAAAhAA4AAAAOAAgAAAAhAA0AAAACAAQAAAAhAAwAAAACAAMAAAAhAAsAAAAKAA0AAAAhAAoAAAAKAAwAAAAhAAkAAAAKAAsAAAAhAAgAAAACAAwAAAAhAAcAAAACAAsAAAAhAAYAAAAOAAgAAAAhAAUAAAAOAAgAAAAhAAQAAAAOAAgAAAAhAAMAAAAOAAgAAAAhAAIAAAAOAAcAAAAiAAIAAAAOAAcAAAAiAAMAAAAOAAgAAAAiAAQAAAAGAAsAAAAiAAUAAAAGAAsAAAAiAAYAAAAGAAsAAAAiAAcAAAADAAsAAAAiAAgAAAADAAwAAAAiAAkAAAAKAAsAAAAiAAoAAAAKAAwAAAAiAAsAAAAKAA0AAAAiAAwAAAACAAMAAAAiAA0AAAACAAQAAAAiAA4AAAAGAAsAAAAiAA8AAAAOAAgAAAAiABAAAAAOAAgAAAAiABEAAAAGAAsAAAAiABIAAAAGAAsAAAAiABMAAAAOAAgAAAAiABQAAAAOAAkAAAAjABQAAAAOAAkAAAAjABMAAAAOAAgAAAAjABIAAAAOAAgAAAAjABEAAAAGAAsAAAAjABAAAAAOAAgAAAAjAA8AAAAOAAgAAAAjAA4AAAAGAAsAAAAjAA0AAAACAAQAAAAjAAwAAAACAAMAAAAjAAsAAAAKAA0AAAAjAAoAAAAKAAwAAAAjAAkAAAAKAAsAAAAjAAgAAAACAAEAAAAjAAcAAAACAAAAAAAjAAYAAAAOAAgAAAAjAAUAAAAGAAsAAAAjAAQAAAAGAAsAAAAjAAMAAAAOAAgAAAAjAAIAAAAOAAcAAAAkAAIAAAAOAAcAAAAkAAMAAAAOAAgAAAAkAAQAAAAOAAgAAAAkAAUAAAAOAAgAAAAkAAYAAAAGAAsAAAAkAAcAAAACAAAAAAAkAAgAAAACAAEAAAAkAAkAAAAKAAsAAAAkAAoAAAAKAAwAAAAkAAsAAAAKAA0AAAAkAAwAAAACAAMAAAAkAA0AAAACAAQAAAAkAA4AAAAOAAgAAAAkAA8AAAAOAAgAAAAkABAAAAAGAAsAAAAkABEAAAAGAAsAAAAkABIAAAAOAAgAAAAkABMAAAAOAAgAAAAkABQAAAAOAAkAAAAlABQAAAAOAAkAAAAlABMAAAAOAAgAAAAlABIAAAAOAAgAAAAlABEAAAAOAAgAAAAlABAAAAAGAAsAAAAlAA8AAAAGAAsAAAAlAA4AAAAGAAsAAAAlAA0AAAACAAQAAAAlAAwAAAACAAMAAAAlAAsAAAAKAA0AAAAlAAoAAAAKAAwAAAAlAAkAAAAKAAsAAAAlAAgAAAACAAEAAAAlAAcAAAACAAAAAAAlAAYAAAAOAAgAAAAlAAUAAAAOAAgAAAAlAAQAAAAOAAgAAAAlAAMAAAAOAAgAAAAlAAIAAAAOAAcAAAAmAAIAAAAOAAcAAAAmAAMAAAAOAAgAAAAmAAQAAAAOAAgAAAAmAAUAAAAOAAgAAAAmAAYAAAAOAAgAAAAmAAcAAAACAAAAAAAmAAgAAAACAAEAAAAmAAkAAAAKAAsAAAAmAAoAAAAKAAwAAAAmAAsAAAAKAA0AAAAmAAwAAAACAAMAAAAmAA0AAAACAAQAAAAmAA4AAAAOAAgAAAAmAA8AAAAOAAgAAAAmABAAAAAOAAgAAAAmABEAAAAOAAgAAAAmABIAAAAOAAgAAAAmABMAAAAOAAgAAAAmABQAAAAJAAkAAAAnABQAAAAJAAkAAAAnABMAAAAOAAgAAAAnABIAAAAOAAgAAAAnABEAAAAOAAgAAAAnABAAAAAOAAgAAAAnAA8AAAAOAAgAAAAnAA4AAAAOAAgAAAAnAA0AAAACAA4AAAAnAAwAAAACAA0AAAAnAAsAAAAKAA0AAAAnAAoAAAAKAAwAAAAnAAkAAAAKAAsAAAAnAAgAAAACAAEAAAAnAAcAAAACAAAAAAAnAAYAAAAOAAgAAAAnAAUAAAAOAAgAAAAnAAQAAAAOAAgAAAAnAAMAAAAOAAgAAAAnAAIAAAAOAAcAAAAoAAIAAAAOAAcAAAAoAAMAAAAOAAgAAAAoAAQAAAAOAAgAAAAoAAUAAAAOAAgAAAAoAAYAAAAOAAgAAAAoAAcAAAACAAAAAAAoAAgAAAACAAEAAAAoAAkAAAAKAAsAAAAoAAoAAAAKAAwAAAAoAAsAAAAKAA0AAAAoAAwAAAADAA0AAAAoAA0AAAADAA4AAAAoAA4AAAAOAAgAAAAoAA8AAAAOAAgAAAAoABAAAAAOAAgAAAAoABEAAAAOAAgAAAAoABIAAAAOAAgAAAAoABMAAAAOAAgAAAAoABQAAAAJAAkAAAApABQAAAAKAAkAAAApABMAAAAKAAgAAAApABIAAAAKAAgAAAApABEAAAAKAAgAAAApABAAAAAPAAgAAAApAA8AAAAPAAgAAAApAA4AAAAPAAgAAAApAA0AAAACAAQAAAApAAwAAAACAAMAAAApAAsAAAAKAA0AAAApAAoAAAAKAAwAAAApAAkAAAAKAAsAAAApAAgAAAACAAEAAAApAAcAAAACAAAAAAApAAYAAAAPAAgAAAApAAUAAAAPAAgAAAApAAQAAAAPAAgAAAApAAMAAAAPAAgAAAApAAIAAAAPAAcAAAAWABcAAAAJAAgAAAAWABgAAAAOAAgAAAAWABkAAAAOAAgAAAAWABoAAAAOAAgAAAAWAB4AAAAOAAgAAAAWAB0AAAAOAAgAAAAWABwAAAAOAAgAAAAWABsAAAAJAAgAAAAWACAAAAAOAAgAAAAWAB8AAAAOAAgAAAAgABMAAAAOAAgAAAAfABMAAAAOAAgAAAA1AAAAAAAAAAAAAAA1AAEAAAAAAAEAAAA1AAIAAAAAAAIAAAA1AAMAAAAAAAIAAAA1AAQAAAAAAAIAAAA2AAAAAAABAAAAAAA2AAEAAAABAAEAAAA2AAIAAAABAAIAAAA2AAMAAAABAAIAAAA2AAQAAAABAAIAAAA3AAAAAAACAAAAAAA3AAEAAAACAAEAAAA3AAIAAAAJAAgAAAA3AAMAAAAJAAgAAAA3AAQAAAAJAAgAAAA4AAAAAAACAAAAAAA4AAEAAAACAAEAAAA5AAAAAAACAAAAAAA5AAEAAAACAAEAAAA6AAAAAAACAAAAAAA6AAEAAAACAAEAAAA7AAAAAAACAAAAAAA7AAEAAAACAAEAAAA8AAAAAAACAAAAAAA8AAEAAAACAAEAAAA9AAAAAAACAAAAAAA9AAEAAAACAAEAAAA+AAAAAAACAAAAAAA+AAEAAAACAAEAAAA/AAAAAAACAAAAAAA/AAEAAAACAAEAAABAAAAAAAACAAAAAABAAAEAAAACAAEAAABBAAAAAAACAAAAAABBAAEAAAACAAEAAABCAAAAAAACAAAAAABCAAEAAAACAAEAAABDAAAAAAACAAAAAABDAAEAAAACAAEAAABEAAAAAAACAAAAAABEAAEAAAACAAEAAABFAAAAAAADAAAAAABFAAEAAAADAAEAAABGAAAAAAAAAAAAAABGAAEAAAAEAAEAAABFAAIAAAADAAIAAABGAAIAAAAEAAIAAABFAAMAAAADAAIAAABGAAMAAAAEAAIAAABFAAQAAAADAAIAAABGAAQAAAAEAAIAAABFAAoAAAADAAIAAABGAAoAAAAEAAIAAABFAAkAAAADAAIAAABGAAkAAAAEAAIAAABFAAgAAAADAAIAAABGAAgAAAAEAAIAAABFAAcAAAADAAIAAABGAAcAAAAEAAIAAABFAAYAAAADAAIAAABGAAYAAAAEAAIAAABFAAUAAAADAAIAAABGAAUAAAAEAAIAAABEAA0AAAAEAAMAAABEAA4AAAAAAAAAAABDAA0AAAADAAMAAABDAA4AAAADAAQAAABCAA0AAAACAAMAAABCAA4AAAACAAQAAABBAA0AAAACAAMAAABBAA4AAAACAAQAAABAAA0AAAACAAMAAABAAA4AAAACAAQAAAA/AA0AAAACAAMAAAA/AA4AAAACAAQAAAA+AA0AAAABAAMAAAA+AA4AAAABAAQAAAA9AA0AAAAAAAMAAAA9AA4AAAAAAAAAAAA8AA0AAAAEAAMAAAA8AA4AAAAAAAAAAAA7AA0AAAADAAMAAAA7AA4AAAADAAQAAAA6AA0AAAACAAMAAAA6AA4AAAACAAQAAAA5AA0AAAACAAMAAAA5AA4AAAACAAQAAAA4AA0AAAACAAMAAAA4AA4AAAACAAQAAAA3AA0AAAACAAMAAAA3AA4AAAACAAQAAAA1AA0AAAAAAAMAAAA1AA4AAAAAAAAAAAA2AA0AAAABAAMAAAA2AA4AAAABAAQAAAA1AAwAAAAAAAIAAAA2AAwAAAABAAIAAAA1AAsAAAAFAAQAAAA2AAsAAAAGAAQAAAA1AAoAAAAFAAMAAAA2AAoAAAAGAAMAAAA1AAkAAAAFAAIAAAA2AAkAAAAGAAIAAAA1AAgAAAAAAAIAAAA2AAgAAAABAAIAAAA1AAcAAAAAAAIAAAA2AAcAAAABAAIAAAA1AAYAAAAAAAIAAAA2AAYAAAABAAIAAAA1AAUAAAAAAAIAAAA2AAUAAAABAAIAAAA3AAUAAAAJAAgAAAA4AAIAAAAJAAgAAAA5AAIAAAAJAAgAAAA6AAIAAAAJAAgAAAA7AAIAAAAJAAgAAAA8AAIAAAAJAAgAAAA9AAIAAAAJAAgAAAA+AAIAAAAJAAgAAAA/AAIAAAAJAAgAAAA/AAMAAAAGAAsAAABAAAMAAAAGAAsAAABBAAMAAAAOAAgAAABEAAwAAAAEAAkAAABEAAsAAAAEAAgAAAA9AAcAAAACAAAAAAA9AAYAAAAFAAAAAAA9AAUAAAACAAQAAAA9AAQAAAACAAMAAAA9AAMAAAAGAAsAAAA8AAMAAAAGAAsAAAA7AAMAAAAGAAsAAAA6AAMAAAAGAAsAAAA5AAMAAAAGAAsAAAA4AAMAAAAJAAgAAAA4AAQAAAAOAAgAAAA4AAUAAAAOAAgAAAA4AAYAAAAOAAgAAAA3AAYAAAAOAAgAAAA3AAcAAAAOAAgAAAA3AAgAAAAOAAgAAAA3AAkAAAAOAAgAAAA3AAoAAAAOAAgAAAA3AAsAAAAJAAgAAAA3AAwAAAAJAAgAAAA4AAwAAAAOAAgAAAA4AAsAAAAOAAgAAAA4AAoAAAAOAAgAAAA4AAkAAAAOAAgAAAA4AAgAAAAOAAgAAAA4AAcAAAAOAAgAAAA5AAcAAAAOAAgAAAA5AAYAAAAOAAgAAAA5AAUAAAAOAAgAAAA5AAQAAAAGAAsAAAA6AAQAAAAGAAsAAAA6AAUAAAAGAAsAAAA6AAYAAAAGAAsAAAA6AAcAAAAGAAsAAAA6AAgAAAAGAAsAAAA5AAgAAAAOAAgAAAA5AAkAAAAOAAgAAAA5AAoAAAAOAAgAAAA5AAsAAAAOAAgAAAA5AAwAAAAOAAgAAAA6AAwAAAAOAAgAAAA6AAsAAAAOAAgAAAA6AAoAAAAOAAgAAAA6AAkAAAAGAAsAAAA7AAkAAAAGAAsAAAA7AAgAAAADAAcAAAA7AAcAAAADAAYAAAA7AAYAAAADAAIAAAA7AAUAAAADAAkAAAA7AAQAAAADAAgAAAA8AAQAAAAEAAgAAAA8AAUAAAAEAAkAAAA8AAYAAAAEAAIAAAA8AAcAAAAEAAYAAAA8AAgAAAAEAAcAAAA8AAkAAAAGAAsAAAA8AAoAAAAOAAgAAAA7AAoAAAAOAAgAAAA7AAsAAAADAAgAAAA7AAwAAAADAAkAAAA8AAwAAAAEAAkAAAA8AAsAAAAEAAgAAAA9AAsAAAABAAgAAAA9AAoAAAAOAAgAAAA9AAkAAAAGAAsAAAA9AAgAAAACAAEAAAA+AAgAAAABAAcAAAA+AAcAAAABAAYAAAA+AAYAAAAAAAIAAAA+AAUAAAABAAkAAAA+AAQAAAABAAgAAAA+AAMAAAAGAAsAAAA/AAQAAAACAAgAAAA/AAUAAAACAAkAAAA/AAYAAAABAAIAAAA/AAcAAAACAAYAAAA/AAgAAAACAAcAAAA/AAkAAAAGAAsAAAA+AAkAAAAGAAsAAAA+AAoAAAAOAAgAAAA+AAsAAAACAAgAAAA+AAwAAAACAAkAAAA9AAwAAAABAAkAAAA/AAwAAAAOAAgAAAA/AAsAAAAOAAgAAAA/AAoAAAAGAAsAAABAAAoAAAAOAAgAAABAAAkAAAAGAAsAAABAAAgAAAAGAAsAAABAAAcAAAAGAAsAAABAAAYAAAAGAAsAAABAAAUAAAAGAAsAAABAAAQAAAAGAAsAAABBAAQAAAAGAAsAAABBAAUAAAAGAAsAAABBAAYAAAAOAAgAAABBAAcAAAAOAAgAAABBAAgAAAAOAAgAAABBAAkAAAAOAAgAAABBAAoAAAAOAAgAAABBAAsAAAAOAAgAAABAAAsAAAAOAAgAAABAAAwAAAAJAAgAAABBAAwAAAAJAAgAAABCAAwAAAAJAAgAAABCAAsAAAAJAAgAAABCAAoAAAAOAAgAAABCAAkAAAAOAAgAAABCAAgAAAAOAAgAAABCAAcAAAAOAAgAAABCAAYAAAAOAAgAAABCAAUAAAAOAAgAAABCAAQAAAAOAAgAAABCAAMAAAAOAAgAAABCAAIAAAAOAAgAAABBAAIAAAAOAAgAAABAAAIAAAAOAAgAAABDAAIAAAAJAAgAAABDAAMAAAAOAAgAAABDAAQAAAAOAAgAAABDAAUAAAAOAAgAAABDAAYAAAAOAAgAAABDAAcAAAAOAAgAAABDAAgAAAAOAAgAAABDAAkAAAAOAAgAAABDAAoAAAAJAAgAAABDAAsAAAADAAgAAABDAAwAAAADAAkAAABEAAoAAAAJAAgAAABEAAkAAAAJAAgAAABEAAgAAAAJAAgAAABEAAcAAAAJAAgAAABEAAYAAAAJAAgAAABEAAUAAAAJAAgAAABEAAQAAAAJAAgAAABEAAMAAAAJAAgAAABEAAIAAAAJAAgAAABFAAsAAAADAAMAAABFAAwAAAADAAQAAABGAAsAAAAEAAMAAABGAAwAAAAAAAAAAABGAA0AAAAAAAAAAABFAA0AAAAAAAAAAABFAA4AAAAAAAAAAABGAA4AAAAAAAAAAAAsAAkAAAAKAAsAAAAsAAoAAAAKAAwAAAAsAAsAAAAKAAwAAAAtAAkAAAAKAAsAAAAtAAoAAAAKAAwAAAAtAAsAAAAKAAwAAAAuAAkAAAAKAAsAAAAuAAoAAAAKAAwAAAAuAAsAAAAKAAwAAAAvAAkAAAAKAAsAAAAvAAoAAAAKAAwAAAAvAAsAAAADAAgAAAAwAAkAAAAKAAsAAAAwAAoAAAAKAAwAAAAwAAsAAAAEAAgAAAAxAAkAAAAKAAsAAAAxAAoAAAAKAAwAAAAxAAsAAAACAAMAAAAyAAkAAAAKAAsAAAAyAAoAAAAKAAwAAAAyAAsAAAACAAMAAAAzAAkAAAAKAAsAAAAzAAoAAAAKAAwAAAAzAAsAAAACAAMAAAA0AAkAAAAKAAsAAAA0AAoAAAAKAAwAAAA0AAsAAAACAAMAAAAsAAcAAAACAAAAAAAsAAgAAAACAAEAAAAtAAcAAAACAAAAAAAtAAgAAAACAAEAAAAuAAcAAAACAAAAAAAuAAgAAAACAAEAAAAvAAcAAAACAAAAAAAvAAgAAAACAAEAAAAwAAcAAAACAAAAAAAwAAgAAAACAAEAAAAxAAcAAAACAAAAAAAxAAgAAAACAAEAAAAyAAcAAAACAAAAAAAyAAgAAAACAAEAAAAzAAcAAAACAAAAAAAzAAgAAAACAAEAAAA0AAcAAAACAAAAAAA0AAgAAAACAAEAAAAsAAwAAAAKAA0AAAAsAA0AAAACAAMAAAAtAAwAAAAKAA0AAAAtAA0AAAACAAMAAAAuAAwAAAAKAA0AAAAuAA0AAAACAAMAAAAvAAwAAAADAAkAAAAvAA0AAAADAAMAAAAwAAwAAAAEAAQAAAAwAA0AAAAEAAMAAAAxAAwAAAACAAQAAAAxAA0AAAAAAAAAAAAyAAwAAAACAAQAAAAyAA0AAAAAAAAAAAAzAAwAAAACAAQAAAAzAA0AAAAAAAAAAAA0AAwAAAACAAQAAAA0AA0AAAAAAAAAAAAuAA4AAAACAAQAAAAtAA4AAAACAAQAAAAvAA4AAAADAAQAAAAwAA4AAAAAAAAAAAAsAA4AAAACAAQAAAA0AA4AAAAAAAAAAAAzAA4AAAAAAAAAAAAyAA4AAAAAAAAAAAAxAA4AAAAAAAAAAAAsAA8AAAAAAAAAAAAtAA8AAAAAAAAAAAAuAA8AAAAAAAAAAAAvAA8AAAAAAAAAAAAwAA8AAAAAAAAAAAAxAA8AAAAAAAAAAAAxABAAAAAAAAAAAAAyABAAAAAAAAAAAAAzABAAAAAAAAAAAAA0ABAAAAAAAAAAAAA1AA8AAAAAAAAAAAA0AA8AAAAAAAAAAAAzAA8AAAAAAAAAAAAyAA8AAAAAAAAAAAA1ABAAAAAAAAAAAAAwABAAAAAAAAAAAAAvABAAAAAAAAAAAAAtABAAAAAAAAAAAAAsABAAAAAAAAAAAAAuABAAAAAAAAAAAAA0AAYAAAAAAAAAAAAzAAYAAAAAAAAAAAAyAAYAAAAAAAAAAAAxAAYAAAAAAAAAAAAwAAYAAAAAAAAAAAAvAAYAAAAAAAAAAAAuAAYAAAAAAAAAAAAtAAYAAAAAAAAAAAAsAAYAAAAAAAAAAAAsAAUAAAAAAAAAAAAtAAUAAAAAAAAAAAAuAAUAAAAAAAAAAAAvAAUAAAAAAAAAAAAwAAUAAAAAAAAAAAAxAAUAAAAAAAAAAAAyAAUAAAAAAAAAAAAzAAUAAAAAAAAAAAA0AAUAAAAAAAAAAAA0AAQAAAAAAAAAAAA0AAMAAAAAAAAAAAA0AAIAAAAAAAAAAAA0AAEAAAAAAAAAAAA0AAAAAAAAAAAAAAAzAAAAAAAAAAAAAAAyAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAvAAAAAAAAAAAAAAAuAAAAAAAAAAAAAAAtAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAAsAAEAAAAAAAAAAAAsAAIAAAAAAAAAAAAsAAMAAAAAAAAAAAAsAAQAAAAAAAAAAAAtAAQAAAAAAAAAAAAuAAQAAAAAAAAAAAAvAAQAAAAAAAAAAAAwAAQAAAAAAAAAAAAxAAQAAAAAAAAAAAAyAAQAAAAAAAAAAAAzAAQAAAAAAAAAAAAzAAMAAAAAAAAAAAAzAAIAAAAAAAAAAAAzAAEAAAAAAAAAAAAyAAEAAAAAAAAAAAAxAAEAAAAAAAAAAAAwAAEAAAAAAAAAAAAvAAEAAAAAAAAAAAAuAAEAAAAAAAAAAAAtAAEAAAAAAAAAAAAtAAIAAAAAAAAAAAAtAAMAAAAAAAAAAAAuAAMAAAAAAAAAAAAvAAMAAAAAAAAAAAAwAAMAAAAAAAAAAAAxAAMAAAAAAAAAAAAyAAMAAAAAAAAAAAAyAAIAAAAAAAAAAAAxAAIAAAAAAAAAAAAwAAIAAAAAAAAAAAAvAAIAAAAAAAAAAAAuAAIAAAAAAAAAAAA=") +tile_map_data = PackedByteArray("AAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAEAAAAAAAIAAAAAAAIAAAAAAAMAAAAAAAIAAAAAAAQAAAAAAAIAAAABAAAAAAABAAAAAAABAAEAAAABAAEAAAABAAIAAAABAAIAAAABAAMAAAABAAIAAAABAAQAAAABAAIAAAACAAAAAAACAAAAAAACAAEAAAACAAEAAAACAAIAAAAOAAgAAAACAAMAAAAOAAgAAAACAAQAAAAOAAgAAAADAAAAAAACAAAAAAADAAEAAAACAAEAAAADAAIAAAAOAAgAAAADAAMAAAAOAAgAAAADAAQAAAAOAAgAAAAEAAAAAAACAAAAAAAEAAEAAAACAAEAAAAEAAIAAAAOAAgAAAAEAAMAAAAOAAgAAAAEAAQAAAAOAAgAAAAFAAAAAAACAAAAAAAFAAEAAAACAAEAAAAGAAAAAAACAAAAAAAGAAEAAAACAAEAAAAHAAAAAAACAAAAAAAHAAEAAAACAAEAAAAIAAAAAAACAAAAAAAIAAEAAAACAAEAAAAJAAAAAAACAAAAAAAJAAEAAAACAAEAAAAKAAAAAAACAAAAAAAKAAEAAAACAAEAAAALAAAAAAACAAAAAAALAAEAAAACAAEAAAAMAAAAAAACAAAAAAAMAAEAAAACAAEAAAANAAAAAAACAAAAAAANAAEAAAACAAEAAAAOAAAAAAACAAAAAAAOAAEAAAACAAEAAAAPAAAAAAACAAAAAAAPAAEAAAACAAEAAAAQAAAAAAACAAAAAAAQAAEAAAACAAEAAAARAAAAAAACAAAAAAARAAEAAAACAAEAAAASAAAAAAACAAAAAAASAAEAAAACAAEAAAATAAAAAAACAAAAAAATAAEAAAACAAEAAAAUAAAAAAACAAAAAAAUAAEAAAACAAEAAAAVAAAAAAACAAAAAAAVAAEAAAACAAEAAAAWAAAAAAACAAAAAAAWAAEAAAACAAEAAAAXAAAAAAACAAAAAAAXAAEAAAACAAEAAAAYAAAAAAACAAAAAAAYAAEAAAACAAEAAAAZAAAAAAACAAAAAAAZAAEAAAACAAEAAAAaAAAAAAACAAAAAAAaAAEAAAACAAEAAAAbAAAAAAACAAAAAAAbAAEAAAACAAEAAAAcAAAAAAACAAAAAAAcAAEAAAACAAEAAAAdAAAAAAACAAAAAAAdAAEAAAACAAEAAAAeAAAAAAACAAAAAAAeAAEAAAACAAEAAAAfAAAAAAACAAAAAAAfAAEAAAACAAEAAAAgAAAAAAACAAAAAAAgAAEAAAACAAEAAAAhAAAAAAACAAAAAAAhAAEAAAACAAEAAAAAAAUAAAAAAAIAAAABAAUAAAABAAIAAAAAAAYAAAAAAAIAAAABAAYAAAABAAIAAAAAAAcAAAAAAAIAAAABAAcAAAABAAIAAAAAAAgAAAAAAAIAAAABAAgAAAABAAIAAAAAAAkAAAAAAAIAAAABAAkAAAABAAIAAAAAAAoAAAAAAAIAAAABAAoAAAABAAIAAAAAAAsAAAAAAAIAAAABAAsAAAABAAIAAAAAAAwAAAAAAAIAAAABAAwAAAABAAIAAAAAAA0AAAAAAAIAAAABAA0AAAABAAIAAAAAAA4AAAAAAAIAAAABAA4AAAABAAIAAAAAAA8AAAAAAAIAAAABAA8AAAABAAIAAAAAABAAAAAAAAIAAAABABAAAAABAAIAAAAAABEAAAAAAAIAAAABABEAAAABAAIAAAAAABUAAAAAAAMAAAAAABYAAAAAAAAAAAABABUAAAABAAMAAAABABYAAAABAAQAAAAAABIAAAAAAAIAAAABABIAAAABAAIAAAAAABMAAAAAAAIAAAABABMAAAABAAIAAAAAABQAAAAAAAIAAAABABQAAAABAAIAAAACABUAAAACAAMAAAACABYAAAACAAQAAAADABUAAAACAAMAAAADABYAAAACAAQAAAAEABUAAAACAAMAAAAEABYAAAACAAQAAAAFABUAAAACAAMAAAAFABYAAAACAAQAAAAGABUAAAACAAMAAAAGABYAAAACAAQAAAAHABUAAAACAAMAAAAHABYAAAACAAQAAAAIABUAAAACAAMAAAAIABYAAAACAAQAAAAJABUAAAACAAMAAAAJABYAAAACAAQAAAAKABUAAAACAAMAAAAKABYAAAACAAQAAAALABUAAAACAAMAAAALABYAAAACAAQAAAAMABUAAAACAAMAAAAMABYAAAACAAQAAAANABUAAAACAAMAAAANABYAAAACAAQAAAAOABUAAAACAAMAAAAOABYAAAACAAQAAAAPABUAAAACAAMAAAAPABYAAAACAAQAAAAQABUAAAACAAMAAAAQABYAAAACAAQAAAARABUAAAACAAMAAAARABYAAAACAAQAAAASABUAAAACAAMAAAASABYAAAACAAQAAAATABUAAAACAAMAAAATABYAAAACAAQAAAAUABUAAAACAAMAAAAUABYAAAACAAQAAAAVABUAAAAHAAUAAAAVABYAAAAHAAYAAAAWABUAAAAJAAgAAAAWABYAAAAIAAYAAAAXABUAAAAJAAUAAAAXABYAAAAJAAYAAAAYABUAAAACAAMAAAAYABYAAAACAAQAAAAZABUAAAACAAMAAAAZABYAAAACAAQAAAAaABUAAAACAAMAAAAaABYAAAACAAQAAAAbABUAAAACAAMAAAAbABYAAAACAAQAAAAcABUAAAACAAMAAAAcABYAAAACAAQAAAAdABUAAAACAAMAAAAdABYAAAACAAQAAAAeABUAAAACAAMAAAAeABYAAAACAAQAAAAfABUAAAACAAMAAAAfABYAAAACAAQAAAAgABUAAAACAAMAAAAgABYAAAACAAQAAAAhABUAAAACAAMAAAAhABYAAAACAAQAAAAiABUAAAACAAMAAAAiABYAAAACAAQAAAAjABUAAAACAAMAAAAjABYAAAACAAQAAAAkABUAAAACAAMAAAAkABYAAAACAAQAAAAlABUAAAACAAMAAAAlABYAAAACAAQAAAAmABUAAAACAAMAAAAmABYAAAACAAQAAAAnABUAAAACAAMAAAAnABYAAAACAAQAAAAoABUAAAACAAMAAAAoABYAAAACAAQAAAApABUAAAACAAMAAAApABYAAAACAAQAAAAqABUAAAADAAMAAAAqABYAAAADAAQAAAArABUAAAAEAAMAAAArABYAAAAAAAAAAAAqABMAAAADAAIAAAArABMAAAAEAAIAAAAqABQAAAADAAIAAAArABQAAAAEAAIAAAAqABIAAAADAAIAAAArABIAAAAEAAIAAAAqABEAAAADAAIAAAArABEAAAAEAAIAAAAqABAAAAADAAIAAAArABAAAAAEAAIAAAAqAA8AAAADAAIAAAArAA8AAAAEAAIAAAAqAA4AAAADAAIAAAArAA4AAAAEAAIAAAAqAA0AAAADAAIAAAArAA0AAAAEAAIAAAAqAAwAAAADAAIAAAArAAwAAAAEAAIAAAAqAAsAAAADAAIAAAArAAsAAAAEAAIAAAAqAAoAAAADAAIAAAArAAoAAAAEAAIAAAAqAAkAAAADAAIAAAArAAkAAAAEAAIAAAAqAAgAAAADAAIAAAArAAgAAAAEAAIAAAAqAAcAAAADAAIAAAArAAcAAAAEAAIAAAAqAAYAAAADAAIAAAArAAYAAAAEAAIAAAAqAAUAAAADAAIAAAArAAUAAAAEAAIAAAAqAAQAAAADAAIAAAArAAQAAAAEAAIAAAAqAAMAAAADAAIAAAArAAMAAAAEAAIAAAAqAAIAAAADAAIAAAArAAIAAAAEAAIAAAAqAAAAAAADAAAAAAAqAAEAAAADAAEAAAArAAAAAAAAAAAAAAArAAEAAAAEAAEAAAApAAAAAAACAAAAAAApAAEAAAACAAEAAAAoAAAAAAACAAAAAAAoAAEAAAACAAEAAAAnAAAAAAACAAAAAAAnAAEAAAACAAEAAAAmAAAAAAACAAAAAAAmAAEAAAACAAEAAAAlAAAAAAACAAAAAAAlAAEAAAACAAEAAAAkAAAAAAACAAAAAAAkAAEAAAACAAEAAAAjAAAAAAACAAAAAAAjAAEAAAACAAEAAAAiAAAAAAACAAAAAAAiAAEAAAACAAEAAAAFAAMAAAAOAAgAAAAFAAQAAAAOAAgAAAAGAAMAAAAOAAgAAAAHAAMAAAAOAAgAAAAFAAIAAAAOAAgAAAAGAAIAAAAOAAgAAAAHAAIAAAAOAAgAAAAIAAIAAAAOAAgAAAAJAAMAAAAOAAgAAAAKAAMAAAAOAAgAAAALAAMAAAAOAAgAAAAIAAMAAAAOAAgAAAAMAAMAAAAOAAgAAAALAAIAAAAOAAgAAAAKAAIAAAAOAAgAAAAJAAIAAAAOAAgAAAAHAAQAAAAOAAgAAAAHAAUAAAAOAAgAAAAIAAYAAAAGAAsAAAAJAAYAAAAGAAsAAAAKAAYAAAAGAAsAAAALAAYAAAAOAAgAAAALAAUAAAAJAAgAAAAMAAUAAAAOAAgAAAANAAUAAAAOAAgAAAAOAAUAAAAOAAgAAAAOAAQAAAAOAAgAAAAPAAQAAAAOAAgAAAANAAQAAAAOAAgAAAAMAAQAAAAOAAgAAAALAAQAAAAOAAgAAAAKAAQAAAAOAAgAAAAJAAQAAAAOAAgAAAAIAAQAAAAOAAgAAAAGAAQAAAAOAAgAAAAQAAQAAAAOAAgAAAARAAQAAAAOAAgAAAARAAUAAAAGAAsAAAAQAAUAAAAOAAgAAAAPAAUAAAAOAAgAAAANAAMAAAAOAAgAAAAOAAMAAAAOAAgAAAAPAAMAAAAOAAgAAAAQAAMAAAAOAAgAAAARAAMAAAAOAAgAAAASAAMAAAAOAAgAAAATAAMAAAAOAAgAAAASAAQAAAAOAAgAAAAIAAUAAAAOAAgAAAAJAAUAAAAOAAgAAAATAAQAAAAGAAsAAAAMAAIAAAAOAAgAAAAUAAMAAAAOAAgAAAAUAAIAAAAOAAgAAAATAAIAAAAOAAgAAAASAAIAAAAOAAgAAAARAAIAAAAOAAgAAAAQAAIAAAAOAAgAAAAPAAIAAAAOAAgAAAAOAAIAAAAOAAgAAAANAAIAAAAOAAgAAAAKAAUAAAAOAAgAAAAGAAUAAAAOAAgAAAAFAAUAAAAOAAgAAAAEAAUAAAAOAAgAAAADAAUAAAAOAAgAAAACAAUAAAAOAAgAAAACAAYAAAAOAAgAAAADAAYAAAAOAAgAAAAEAAYAAAAOAAgAAAAFAAYAAAAOAAgAAAAGAAYAAAAOAAgAAAAHAAYAAAAOAAgAAAAVAAgAAAAOAAgAAAAVAAcAAAAJAAgAAAAVAAYAAAAOAAgAAAAVAAUAAAAGAAsAAAAVAAQAAAAOAAgAAAAVAAMAAAAOAAgAAAAVAAIAAAAOAAgAAAAWAAIAAAAOAAgAAAAWAAMAAAAOAAgAAAAWAAQAAAAOAAgAAAAWAAUAAAAJAAsAAAAWAAYAAAAGAAsAAAAWAAcAAAAJAA0AAAAWAAgAAAAJAAgAAAAWAAkAAAAJAAgAAAAVAAkAAAAJAAgAAAAUAAkAAAAJAAgAAAAUAAgAAAAJAAgAAAAUAAcAAAAJAAgAAAAUAAYAAAAOAAgAAAAUAAUAAAAGAAsAAAAUAAQAAAAGAAsAAAATAAUAAAAGAAsAAAASAAUAAAAGAAsAAAASAAYAAAAGAAsAAAARAAYAAAAGAAsAAAAQAAYAAAAGAAsAAAAPAAYAAAAGAAsAAAAOAAYAAAAOAAgAAAANAAYAAAAOAAgAAAAMAAYAAAAOAAgAAAAMAAcAAAAGAAsAAAALAAcAAAAGAAsAAAAKAAcAAAAGAAsAAAAJAAcAAAAGAAsAAAAIAAcAAAAGAAsAAAAHAAcAAAAGAAsAAAAGAAcAAAAGAAsAAAAFAAcAAAAOAAgAAAAEAAcAAAAOAAgAAAADAAcAAAAOAAgAAAACAAcAAAAOAAgAAAACAAgAAAAOAAgAAAACAAkAAAAOAAgAAAACAAoAAAAOAAgAAAACAAsAAAAOAAgAAAACAAwAAAAOAAgAAAACAA0AAAAOAAgAAAACAA4AAAAOAAgAAAACAA8AAAAOAAgAAAACABAAAAAOAAgAAAACABEAAAAOAAgAAAACABIAAAAOAAgAAAACABMAAAAOAAgAAAACABQAAAAOAAgAAAADABQAAAAOAAgAAAADABMAAAAOAAgAAAADABIAAAAOAAgAAAADABEAAAAOAAgAAAADABAAAAAOAAgAAAADAA8AAAAOAAgAAAADAA4AAAAOAAgAAAADAA0AAAAOAAgAAAADAAwAAAAOAAgAAAADAAsAAAAOAAgAAAADAAoAAAAOAAgAAAADAAkAAAAOAAgAAAADAAgAAAAOAAgAAAAEAAgAAAAOAAgAAAAEAAkAAAAOAAgAAAAEAAoAAAAOAAgAAAAEAAsAAAAOAAgAAAAEAAwAAAAOAAgAAAAEAA0AAAAOAAgAAAAEAA4AAAAOAAgAAAAEAA8AAAAOAAgAAAAEABAAAAAOAAgAAAAEABEAAAAOAAgAAAAEABIAAAAOAAgAAAAEABMAAAAOAAgAAAAEABQAAAAOAAgAAAAFABQAAAAOAAgAAAAFABMAAAAOAAgAAAAFABIAAAAOAAgAAAAFABEAAAAOAAgAAAAFABAAAAAGAAsAAAAFAA8AAAAGAAsAAAAFAA4AAAAGAAsAAAAFAA0AAAAGAAsAAAAFAAwAAAAGAAsAAAAFAAsAAAAGAAsAAAAFAAoAAAAGAAsAAAAFAAkAAAAGAAsAAAAFAAgAAAAOAAgAAAAGAAgAAAAGAAsAAAAGAAkAAAAGAAsAAAAGAAoAAAAGAAsAAAAGAAsAAAAOAAgAAAAGAAwAAAAOAAgAAAAGAA0AAAAOAAgAAAAGAA4AAAAOAAgAAAAGAA8AAAAGAAsAAAAGABAAAAAGAAsAAAAGABEAAAAOAAgAAAAGABIAAAAOAAgAAAAGABMAAAAOAAgAAAAGABQAAAAOAAgAAAAHABQAAAAOAAgAAAAHABMAAAAOAAgAAAAHABIAAAAGAAsAAAAHABEAAAAGAAsAAAAHABAAAAAGAAsAAAAHAA8AAAAGAAsAAAAHAA4AAAAGAAsAAAAHAA0AAAAGAAsAAAAHAAwAAAAGAAsAAAAHAAsAAAAGAAsAAAAHAAoAAAAGAAsAAAAHAAkAAAAOAAgAAAAHAAgAAAAOAAgAAAAIAAgAAAAOAAgAAAAIAAkAAAAOAAgAAAAIAAoAAAAGAAsAAAAIAAsAAAAGAAsAAAAIAAwAAAAOAAgAAAAIAA0AAAAOAAgAAAAIAA4AAAAJAAgAAAAIAA8AAAAJAAgAAAAIABAAAAAOAAgAAAAIABEAAAAOAAgAAAAIABIAAAAGAAsAAAAIABMAAAAOAAgAAAAIABQAAAAOAAgAAAAJABQAAAAOAAgAAAAJABMAAAAOAAgAAAAJABIAAAAGAAsAAAAJABEAAAAOAAgAAAAJABAAAAAOAAgAAAAJAA8AAAAJAAgAAAAJAA4AAAAOAAgAAAAJAA0AAAAJAAgAAAAJAAwAAAAGAAsAAAAJAAsAAAAGAAsAAAAJAAoAAAAOAAgAAAAJAAkAAAAGAAsAAAAJAAgAAAAGAAsAAAAKAAgAAAAGAAsAAAAKAAkAAAAGAAsAAAAKAAoAAAAJAAgAAAAKAAsAAAAJAAgAAAAKAAwAAAAGAAsAAAAKAA0AAAAGAAsAAAAKAA4AAAAOAAgAAAAKAA8AAAAOAAgAAAAKABAAAAAJAAgAAAAKABEAAAAOAAgAAAAKABIAAAAGAAsAAAAKABMAAAAOAAgAAAAKABQAAAAOAAgAAAALABQAAAAOAAgAAAALABMAAAAOAAgAAAALABIAAAAGAAsAAAALABEAAAAGAAsAAAALABAAAAAJAAgAAAALAA8AAAAOAAgAAAALAA4AAAAOAAgAAAALAA0AAAAGAAsAAAALAAwAAAAGAAsAAAALAAsAAAAJAAgAAAALAAoAAAAOAAgAAAALAAkAAAAGAAsAAAALAAgAAAAGAAsAAAAMAAgAAAAGAAsAAAAMAAkAAAAJAAgAAAAMAAoAAAAOAAgAAAAMAAsAAAAJAAgAAAAMAAwAAAAGAAsAAAAMAA0AAAAOAAgAAAAMAA4AAAAGAAsAAAAMAA8AAAAOAAgAAAAMABAAAAAOAAgAAAAMABEAAAAGAAsAAAAMABIAAAAOAAgAAAAMABMAAAAOAAgAAAAMABQAAAAOAAgAAAANABQAAAAOAAgAAAANABMAAAAOAAgAAAANABIAAAAOAAgAAAANABEAAAAGAAsAAAANABAAAAAGAAsAAAANAA8AAAAJAAgAAAANAA4AAAAGAAsAAAANAA0AAAAOAAgAAAANAAwAAAAGAAsAAAANAAsAAAAGAAsAAAANAAoAAAAGAAsAAAANAAkAAAAJAAgAAAANAAgAAAAGAAsAAAANAAcAAAAGAAsAAAAOAAcAAAAGAAsAAAAOAAgAAAAGAAsAAAAOAAkAAAAGAAsAAAAOAAoAAAAJAAgAAAAOAAsAAAAJAAgAAAAOAAwAAAAJAAgAAAAOAA0AAAAGAAsAAAAOAA4AAAAJAAgAAAAOAA8AAAAGAAsAAAAOABAAAAAGAAsAAAAOABEAAAAGAAsAAAAOABIAAAAOAAgAAAAOABMAAAAOAAgAAAAOABQAAAAOAAgAAAAPABQAAAAOAAgAAAAPABMAAAAOAAgAAAAPABIAAAAOAAgAAAAPABEAAAAGAAsAAAAPABAAAAAGAAsAAAAPAA8AAAAGAAsAAAAPAA4AAAAGAAsAAAAPAA0AAAAGAAsAAAAPAAwAAAAJAAgAAAAPAAsAAAAJAAgAAAAPAAoAAAAJAAgAAAAPAAkAAAAJAAgAAAAPAAgAAAAGAAsAAAAPAAcAAAAGAAsAAAAQAAcAAAAGAAsAAAAQAAgAAAAJAAgAAAAQAAkAAAAJAAgAAAAQAAoAAAAJAAgAAAAQAAsAAAAOAAgAAAAQAAwAAAAJAAgAAAAQAA0AAAAGAAsAAAAQAA4AAAAOAAgAAAAQAA8AAAAGAAsAAAAQABAAAAAGAAsAAAAQABEAAAAGAAsAAAAQABIAAAAOAAgAAAAQABMAAAAOAAgAAAAQABQAAAAOAAgAAAARABQAAAAOAAgAAAARABMAAAAOAAgAAAARABIAAAAOAAgAAAARABEAAAAGAAsAAAARABAAAAAGAAsAAAARAA8AAAAGAAsAAAARAA4AAAAJAAgAAAARAA0AAAAGAAsAAAARAAwAAAAJAAgAAAARAAsAAAAOAAgAAAARAAoAAAAOAAgAAAARAAkAAAAJAAgAAAARAAgAAAAJAAgAAAARAAcAAAAGAAsAAAASAAcAAAAJAAgAAAASAAgAAAAJAAgAAAASAAkAAAAOAAgAAAASAAoAAAAOAAgAAAASAAsAAAAJAAgAAAASAAwAAAAGAAsAAAASAA0AAAAGAAsAAAASAA4AAAAGAAsAAAASAA8AAAAGAAsAAAASABAAAAAGAAsAAAASABEAAAAGAAsAAAASABIAAAAOAAgAAAASABMAAAAOAAgAAAASABQAAAAOAAgAAAATABQAAAAOAAgAAAATABMAAAAOAAgAAAATABIAAAAOAAgAAAATABEAAAAGAAsAAAATABAAAAAGAAsAAAATAA8AAAAJAAgAAAATAA4AAAAGAAsAAAATAA0AAAAGAAsAAAATAAwAAAAJAAgAAAATAAsAAAAGAAsAAAATAAoAAAAJAAgAAAATAAkAAAAJAAgAAAATAAgAAAAJAAgAAAATAAcAAAAJAAgAAAATAAYAAAAGAAsAAAAUAAoAAAAJAAgAAAAUAAsAAAAGAAsAAAAUAAwAAAAGAAsAAAAUAA0AAAAGAAsAAAAUAA4AAAAGAAsAAAAUAA8AAAAJAAgAAAAUABAAAAAGAAsAAAAUABEAAAAGAAsAAAAUABIAAAAOAAgAAAAUABMAAAAOAAgAAAAUABQAAAAOAAgAAAAVABQAAAAOAAgAAAAVABMAAAAOAAgAAAAVABIAAAAOAAgAAAAVABEAAAAGAAsAAAAVABAAAAAGAAsAAAAVAA8AAAAJAAgAAAAVAA4AAAAGAAsAAAAVAA0AAAAGAAsAAAAVAAwAAAAGAAsAAAAVAAsAAAAOAAgAAAAVAAoAAAAGAAsAAAAWAAoAAAAGAAsAAAAWAAsAAAAGAAsAAAAWAAwAAAAGAAsAAAAWAA0AAAAGAAsAAAAWAA4AAAAOAAgAAAAWAA8AAAAJAAgAAAAWABAAAAAGAAsAAAAWABEAAAAGAAsAAAAWABIAAAAOAAgAAAAWABMAAAAOAAgAAAAWABQAAAAOAAgAAAAXABQAAAAOAAkAAAAXABMAAAAOAAgAAAAXABIAAAAOAAgAAAAXABEAAAAGAAsAAAAXABAAAAAGAAsAAAAXAA8AAAAJAAgAAAAXAA4AAAAOAAgAAAAXAA0AAAAGAAsAAAAXAAwAAAAGAAsAAAAXAAsAAAAGAAsAAAAXAAoAAAAOAAgAAAAXAAkAAAAGAAsAAAAXAAgAAAAOAAgAAAAXAAcAAAAKAA0AAAAXAAYAAAAHAAsAAAAXAAUAAAAKAAsAAAAXAAQAAAAOAAgAAAAXAAMAAAAOAAgAAAAXAAIAAAAOAAcAAAAYAAIAAAAOAAcAAAAYAAMAAAAJAAsAAAAYAAQAAAAJAAwAAAAYAAUAAAAQAA0AAAAYAAYAAAAKAAwAAAAYAAcAAAAKAA0AAAAYAAgAAAAOAAgAAAAYAAkAAAAGAAsAAAAYAAoAAAAOAAgAAAAYAAsAAAAGAAsAAAAYAAwAAAAGAAsAAAAYAA0AAAAGAAsAAAAYAA4AAAAOAAgAAAAYAA8AAAAJAAgAAAAYABAAAAAGAAsAAAAYABEAAAAGAAsAAAAYABIAAAAOAAgAAAAYABMAAAAOAAgAAAAYABQAAAAOAAkAAAAZABQAAAAOAAkAAAAZABMAAAAOAAgAAAAZABIAAAAOAAgAAAAZABEAAAAGAAsAAAAZABAAAAAGAAsAAAAZAA8AAAAJAAgAAAAZAA4AAAAJAAgAAAAZAA0AAAAGAAsAAAAZAAwAAAAGAAsAAAAZAAsAAAAOAAgAAAAZAAoAAAAGAAsAAAAZAAkAAAAGAAsAAAAZAAgAAAAGAAsAAAAZAAcAAAALAA0AAAAZAAYAAAALAAwAAAAZAAUAAAANAAoAAAAZAAQAAAAKAAwAAAAZAAMAAAAKAAsAAAAZAAIAAAAOAAcAAAAaAAIAAAAOAAcAAAAaAAMAAAALAAsAAAAaAAQAAAALAAwAAAAaAAUAAAALAA0AAAAaAAYAAAAOAAgAAAAaAAcAAAAJAAgAAAAaAAgAAAAGAAsAAAAaAAkAAAAGAAsAAAAaAAoAAAAGAAsAAAAaAAsAAAAOAAgAAAAaAAwAAAAJAAgAAAAaAA0AAAAGAAsAAAAaAA4AAAAJAAgAAAAaAA8AAAAGAAsAAAAaABAAAAAGAAsAAAAaABEAAAAGAAsAAAAaABIAAAAOAAgAAAAaABMAAAAOAAgAAAAaABQAAAAOAAkAAAAbABQAAAAOAAkAAAAbABMAAAAOAAgAAAAbABIAAAAOAAgAAAAbABEAAAAGAAsAAAAbABAAAAAGAAsAAAAbAA8AAAAGAAsAAAAbAA4AAAAJAAgAAAAbAA0AAAAGAAsAAAAbAAwAAAAJAAgAAAAbAAsAAAAOAAgAAAAbAAoAAAAGAAsAAAAbAAkAAAAJAAgAAAAbAAgAAAAJAAgAAAAbAAcAAAAJAAgAAAAbAAYAAAAGAAsAAAAbAAUAAAAOAAgAAAAbAAQAAAAOAAgAAAAbAAMAAAAOAAgAAAAbAAIAAAAOAAcAAAAcAAIAAAAOAAcAAAAcAAMAAAAOAAgAAAAcAAQAAAAOAAgAAAAcAAUAAAAOAAgAAAAcAAYAAAAGAAsAAAAcAAcAAAAJAAgAAAAcAAgAAAAJAAgAAAAcAAkAAAAJAAgAAAAcAAoAAAAGAAsAAAAcAAsAAAAGAAsAAAAcAAwAAAAGAAsAAAAcAA0AAAAJAAgAAAAcAA4AAAAJAAgAAAAcAA8AAAAGAAsAAAAcABAAAAAGAAsAAAAcABEAAAAGAAsAAAAcABIAAAAOAAgAAAAcABMAAAAOAAgAAAAcABQAAAAOAAkAAAAdABQAAAAOAAkAAAAdABMAAAAOAAgAAAAdABIAAAAOAAgAAAAdABEAAAAGAAsAAAAdABAAAAAGAAsAAAAdAA8AAAAGAAsAAAAdAA4AAAAOAAgAAAAdAA0AAAAOAAgAAAAdAAwAAAAAAAMAAAAdAAsAAAAAAAIAAAAdAAoAAAAAAAIAAAAdAAkAAAAAAAIAAAAdAAgAAAAAAAEAAAAdAAcAAAAOAAgAAAAdAAYAAAAGAAsAAAAdAAUAAAAOAAgAAAAdAAQAAAAOAAgAAAAdAAMAAAAOAAgAAAAdAAIAAAAOAAcAAAAeAAIAAAAOAAcAAAAeAAMAAAAOAAgAAAAeAAQAAAAOAAgAAAAeAAUAAAAOAAgAAAAeAAYAAAAGAAsAAAAeAAcAAAABAAAAAAAeAAgAAAABAAEAAAAeAAkAAAABAAIAAAAeAAoAAAABAAIAAAAeAAsAAAABAAIAAAAeAAwAAAABAAMAAAAeAA0AAAABAAQAAAAeAA4AAAAGAAsAAAAeAA8AAAAGAAsAAAAeABAAAAAGAAsAAAAeABEAAAAOAAgAAAAeABIAAAAOAAgAAAAeABMAAAAOAAgAAAAeABQAAAAOAAkAAAAfABQAAAAOAAkAAAAfABIAAAAOAAgAAAAfABEAAAAOAAgAAAAfABAAAAAGAAsAAAAfAA8AAAAOAAgAAAAfAA4AAAAGAAsAAAAfAA0AAAACAAQAAAAfAAwAAAACAAMAAAAfAAsAAAAJAA0AAAAfAAoAAAAJAAwAAAAfAAkAAAAJAAsAAAAfAAgAAAACAAEAAAAfAAcAAAACAAAAAAAfAAYAAAAGAAsAAAAfAAUAAAAOAAgAAAAfAAQAAAAOAAgAAAAfAAMAAAAOAAgAAAAfAAIAAAAOAAcAAAAgAAIAAAAOAAcAAAAgAAMAAAAOAAgAAAAgAAQAAAAOAAgAAAAgAAUAAAAOAAgAAAAgAAYAAAAJAAgAAAAgAAcAAAACAAAAAAAgAAgAAAACAAEAAAAgAAkAAAAKAAsAAAAgAAoAAAAKAAwAAAAgAAsAAAAKAA0AAAAgAAwAAAACAAMAAAAgAA0AAAACAAQAAAAgAA4AAAAGAAsAAAAgAA8AAAAGAAsAAAAgABAAAAAOAAgAAAAgABEAAAAOAAgAAAAgABIAAAAOAAgAAAAgABQAAAAOAAkAAAAhABQAAAAOAAkAAAAhABMAAAAOAAgAAAAhABIAAAAGAAsAAAAhABEAAAAOAAgAAAAhABAAAAAOAAgAAAAhAA8AAAAGAAsAAAAhAA4AAAAOAAgAAAAhAA0AAAACAAQAAAAhAAwAAAACAAMAAAAhAAsAAAAKAA0AAAAhAAoAAAAKAAwAAAAhAAkAAAAKAAsAAAAhAAgAAAACAAwAAAAhAAcAAAACAAsAAAAhAAYAAAAOAAgAAAAhAAUAAAAOAAgAAAAhAAQAAAAOAAgAAAAhAAMAAAAOAAgAAAAhAAIAAAAOAAcAAAAiAAIAAAAOAAcAAAAiAAMAAAAOAAgAAAAiAAQAAAAGAAsAAAAiAAUAAAAGAAsAAAAiAAYAAAAGAAsAAAAiAAcAAAADAAsAAAAiAAgAAAADAAwAAAAiAAkAAAAKAAsAAAAiAAoAAAAKAAwAAAAiAAsAAAAKAA0AAAAiAAwAAAACAAMAAAAiAA0AAAACAAQAAAAiAA4AAAAGAAsAAAAiAA8AAAAOAAgAAAAiABAAAAAOAAgAAAAiABEAAAAGAAsAAAAiABIAAAAGAAsAAAAiABMAAAAOAAgAAAAiABQAAAAOAAkAAAAjABQAAAAOAAkAAAAjABMAAAAOAAgAAAAjABIAAAAOAAgAAAAjABEAAAAGAAsAAAAjABAAAAAOAAgAAAAjAA8AAAAOAAgAAAAjAA4AAAAGAAsAAAAjAA0AAAACAAQAAAAjAAwAAAACAAMAAAAjAAsAAAAKAA0AAAAjAAoAAAAKAAwAAAAjAAkAAAAKAAsAAAAjAAgAAAACAAEAAAAjAAcAAAACAAAAAAAjAAYAAAAOAAgAAAAjAAUAAAAGAAsAAAAjAAQAAAAGAAsAAAAjAAMAAAAOAAgAAAAjAAIAAAAOAAcAAAAkAAIAAAAOAAcAAAAkAAMAAAAOAAgAAAAkAAQAAAAOAAgAAAAkAAUAAAAOAAgAAAAkAAYAAAAGAAsAAAAkAAcAAAACAAAAAAAkAAgAAAACAAEAAAAkAAkAAAAKAAsAAAAkAAoAAAAKAAwAAAAkAAsAAAAKAA0AAAAkAAwAAAACAAMAAAAkAA0AAAACAAQAAAAkAA4AAAAOAAgAAAAkAA8AAAAOAAgAAAAkABAAAAAGAAsAAAAkABEAAAAGAAsAAAAkABIAAAAOAAgAAAAkABMAAAAOAAgAAAAkABQAAAAOAAkAAAAlABQAAAAOAAkAAAAlABMAAAAOAAgAAAAlABIAAAAOAAgAAAAlABEAAAAOAAgAAAAlABAAAAAGAAsAAAAlAA8AAAAGAAsAAAAlAA4AAAAGAAsAAAAlAA0AAAACAAQAAAAlAAwAAAACAAMAAAAlAAsAAAAKAA0AAAAlAAoAAAAKAAwAAAAlAAkAAAAKAAsAAAAlAAgAAAACAAEAAAAlAAcAAAACAAAAAAAlAAYAAAAOAAgAAAAlAAUAAAAOAAgAAAAlAAQAAAAOAAgAAAAlAAMAAAAOAAgAAAAlAAIAAAAOAAcAAAAmAAIAAAAOAAcAAAAmAAMAAAAOAAgAAAAmAAQAAAAOAAgAAAAmAAUAAAAOAAgAAAAmAAYAAAAOAAgAAAAmAAcAAAACAAAAAAAmAAgAAAACAAEAAAAmAAkAAAAKAAsAAAAmAAoAAAAKAAwAAAAmAAsAAAAKAA0AAAAmAAwAAAACAAMAAAAmAA0AAAACAAQAAAAmAA4AAAAOAAgAAAAmAA8AAAAOAAgAAAAmABAAAAAOAAgAAAAmABEAAAAOAAgAAAAmABIAAAAOAAgAAAAmABMAAAAOAAgAAAAmABQAAAAJAAkAAAAnABQAAAAJAAkAAAAnABMAAAAOAAgAAAAnABIAAAAOAAgAAAAnABEAAAAOAAgAAAAnABAAAAAOAAgAAAAnAA8AAAAOAAgAAAAnAA4AAAAOAAgAAAAnAA0AAAACAA4AAAAnAAwAAAACAA0AAAAnAAsAAAAKAA0AAAAnAAoAAAAKAAwAAAAnAAkAAAAKAAsAAAAnAAgAAAACAAEAAAAnAAcAAAACAAAAAAAnAAYAAAAOAAgAAAAnAAUAAAAOAAgAAAAnAAQAAAAOAAgAAAAnAAMAAAAOAAgAAAAnAAIAAAAOAAcAAAAoAAIAAAAOAAcAAAAoAAMAAAAOAAgAAAAoAAQAAAAOAAgAAAAoAAUAAAAOAAgAAAAoAAYAAAAOAAgAAAAoAAcAAAACAAAAAAAoAAgAAAACAAEAAAAoAAkAAAAKAAsAAAAoAAoAAAAKAAwAAAAoAAsAAAAKAA0AAAAoAAwAAAADAA0AAAAoAA0AAAADAA4AAAAoAA4AAAAOAAgAAAAoAA8AAAAOAAgAAAAoABAAAAAOAAgAAAAoABEAAAAOAAgAAAAoABIAAAAOAAgAAAAoABMAAAAOAAgAAAAoABQAAAAJAAkAAAApABQAAAAKAAkAAAApABMAAAAKAAgAAAApABIAAAAKAAgAAAApABEAAAAKAAgAAAApABAAAAAPAAgAAAApAA8AAAAPAAgAAAApAA4AAAAPAAgAAAApAA0AAAACAAQAAAApAAwAAAACAAMAAAApAAsAAAAKAA0AAAApAAoAAAAKAAwAAAApAAkAAAAKAAsAAAApAAgAAAACAAEAAAApAAcAAAACAAAAAAApAAYAAAAPAAgAAAApAAUAAAAPAAgAAAApAAQAAAAPAAgAAAApAAMAAAAPAAgAAAApAAIAAAAPAAcAAAAWABcAAAAJAAgAAAAWABgAAAAOAAgAAAAWABkAAAAOAAgAAAAWABoAAAAOAAgAAAAWAB4AAAAOAAgAAAAWAB0AAAAOAAgAAAAWABwAAAAOAAgAAAAWABsAAAAJAAgAAAAWACAAAAAOAAgAAAAWAB8AAAAOAAgAAAAgABMAAAAOAAgAAAAfABMAAAAOAAgAAAA1AAAAAAAAAAAAAAA1AAEAAAAAAAEAAAA1AAIAAAAAAAIAAAA1AAMAAAAAAAIAAAA1AAQAAAAAAAIAAAA2AAAAAAABAAAAAAA2AAEAAAABAAEAAAA2AAIAAAABAAIAAAA2AAMAAAABAAIAAAA2AAQAAAABAAIAAAA3AAAAAAACAAAAAAA3AAEAAAACAAEAAAA3AAIAAAAJAAsAAAA3AAMAAAAJAAwAAAA3AAQAAAAJAAwAAAA4AAAAAAACAAAAAAA4AAEAAAACAAEAAAA5AAAAAAACAAAAAAA5AAEAAAACAAEAAAA6AAAAAAACAAAAAAA6AAEAAAACAAEAAAA7AAAAAAACAAAAAAA7AAEAAAACAAEAAAA8AAAAAAACAAAAAAA8AAEAAAACAAEAAAA9AAAAAAACAAAAAAA9AAEAAAACAAEAAAA+AAAAAAACAAAAAAA+AAEAAAACAAEAAAA/AAAAAAACAAAAAAA/AAEAAAACAAEAAABAAAAAAAACAAAAAABAAAEAAAACAAEAAABBAAAAAAACAAAAAABBAAEAAAACAAEAAABCAAAAAAACAAAAAABCAAEAAAACAAEAAABDAAAAAAACAAAAAABDAAEAAAACAAEAAABEAAAAAAACAAAAAABEAAEAAAACAAEAAABFAAAAAAADAAAAAABFAAEAAAADAAEAAABGAAAAAAAAAAAAAABGAAEAAAAEAAEAAABFAAIAAAADAAIAAABGAAIAAAAEAAIAAABFAAMAAAADAAIAAABGAAMAAAAEAAIAAABFAAQAAAADAAIAAABGAAQAAAAEAAIAAABFAAoAAAADAAIAAABGAAoAAAAEAAIAAABFAAkAAAADAAIAAABGAAkAAAAEAAIAAABFAAgAAAADAAIAAABGAAgAAAAEAAIAAABFAAcAAAADAAIAAABGAAcAAAAEAAIAAABFAAYAAAADAAIAAABGAAYAAAAEAAIAAABFAAUAAAADAAIAAABGAAUAAAAEAAIAAABEAA0AAAAEAAIAAABEAA4AAAAEAAIAAABDAA0AAAADAAIAAABDAA4AAAADAAIAAABCAA0AAAALAAwAAABCAA4AAAALAA0AAABBAA0AAAAKAAwAAABBAA4AAAAKAA0AAABAAA0AAAAJAAwAAABAAA4AAAAJAA0AAAA/AA0AAAAJAAgAAAA/AA4AAAAJAAgAAAA+AA0AAAABAAIAAAA+AA4AAAABAAIAAAA9AA0AAAAAAAIAAAA9AA4AAAAAAAIAAAA8AA0AAAAEAAMAAAA8AA4AAAAAAAAAAAA7AA0AAAADAAMAAAA7AA4AAAADAAQAAAA6AA0AAAACAAMAAAA6AA4AAAACAAQAAAA5AA0AAAACAAMAAAA5AA4AAAACAAQAAAA4AA0AAAACAAMAAAA4AA4AAAACAAQAAAA3AA0AAAACAAMAAAA3AA4AAAACAAQAAAA1AA0AAAAAAAMAAAA1AA4AAAAAAAAAAAA2AA0AAAABAAMAAAA2AA4AAAABAAQAAAA1AAwAAAAAAAIAAAA2AAwAAAABAAIAAAA1AAsAAAAFAAQAAAA2AAsAAAAGAAQAAAA1AAoAAAAFAAMAAAA2AAoAAAAGAAMAAAA1AAkAAAAFAAIAAAA2AAkAAAAGAAIAAAA1AAgAAAAAAAIAAAA2AAgAAAABAAIAAAA1AAcAAAAAAAIAAAA2AAcAAAABAAIAAAA1AAYAAAAAAAIAAAA2AAYAAAABAAIAAAA1AAUAAAAAAAIAAAA2AAUAAAABAAIAAAA3AAUAAAAJAAwAAAA4AAIAAAAKAAsAAAA5AAIAAAAKAAsAAAA6AAIAAAAKAAsAAAA7AAIAAAALAAsAAAA8AAIAAAAJAAgAAAA9AAIAAAAJAAgAAAA+AAIAAAAJAAgAAAA/AAIAAAAJAAgAAAA/AAMAAAAGAAsAAABAAAMAAAAKAAwAAABBAAMAAAAKAAwAAABEAAwAAAAEAAkAAABEAAsAAAAEAAgAAAA9AAcAAAACAAAAAAA9AAYAAAAFAAAAAAA9AAUAAAACAAQAAAA9AAQAAAACAAMAAAA9AAMAAAAGAAsAAAA8AAMAAAAGAAsAAAA7AAMAAAALAA0AAAA6AAMAAAANAAoAAAA4AAQAAAAKAAwAAAA4AAUAAAAKAAwAAAA4AAYAAAAKAA0AAAA3AAYAAAAJAA0AAAA3AAcAAAAOAAgAAAA3AAgAAAAOAAgAAAA3AAkAAAAOAAgAAAA3AAoAAAAOAAgAAAA3AAsAAAAJAAgAAAA3AAwAAAAJAAgAAAA4AAwAAAAOAAgAAAA4AAsAAAAOAAgAAAA4AAoAAAAOAAgAAAA4AAkAAAAOAAgAAAA4AAgAAAAOAAgAAAA4AAcAAAAOAAgAAAA5AAcAAAAOAAgAAAA5AAYAAAAKAA0AAAA5AAUAAAAKAAwAAAA6AAQAAAALAAwAAAA6AAUAAAALAAwAAAA6AAYAAAALAA0AAAA6AAcAAAAGAAsAAAA6AAgAAAAGAAsAAAA5AAgAAAAOAAgAAAA5AAkAAAAOAAgAAAA5AAoAAAAOAAgAAAA5AAsAAAAOAAgAAAA5AAwAAAAOAAgAAAA6AAwAAAAOAAgAAAA6AAsAAAAOAAgAAAA6AAoAAAAOAAgAAAA6AAkAAAAGAAsAAAA7AAkAAAAGAAsAAAA7AAgAAAADAAcAAAA7AAcAAAADAAYAAAA7AAYAAAADAAIAAAA7AAUAAAADAAkAAAA7AAQAAAADAAgAAAA8AAQAAAAEAAgAAAA8AAUAAAAEAAkAAAA8AAYAAAAEAAIAAAA8AAcAAAAEAAYAAAA8AAgAAAAEAAcAAAA8AAkAAAAGAAsAAAA8AAoAAAAOAAgAAAA7AAoAAAAOAAgAAAA7AAsAAAADAAgAAAA7AAwAAAADAAkAAAA8AAwAAAAEAAkAAAA8AAsAAAAEAAgAAAA9AAsAAAABAAgAAAA9AAoAAAAOAAgAAAA9AAkAAAAGAAsAAAA9AAgAAAACAAEAAAA+AAgAAAABAAcAAAA+AAcAAAABAAYAAAA+AAYAAAAAAAIAAAA+AAUAAAABAAkAAAA+AAQAAAABAAgAAAA+AAMAAAAGAAsAAAA/AAQAAAACAAgAAAA/AAUAAAACAAkAAAA/AAYAAAABAAIAAAA/AAcAAAACAAYAAAA/AAgAAAACAAcAAAA/AAkAAAAGAAsAAAA+AAkAAAAGAAsAAAA+AAoAAAAOAAgAAAA+AAsAAAACAAgAAAA+AAwAAAACAAkAAAA9AAwAAAABAAkAAAA/AAwAAAAOAAgAAAA/AAsAAAAOAAgAAAA/AAoAAAAGAAsAAABAAAoAAAAOAAgAAABAAAkAAAAGAAsAAABAAAgAAAAGAAsAAABAAAcAAAAGAAsAAABAAAYAAAAGAAsAAABAAAUAAAAQAAoAAABAAAQAAAAKAAwAAABBAAQAAAAKAAwAAABBAAUAAAAKAAwAAABBAAYAAAAOAAgAAABBAAcAAAAOAAgAAABBAAgAAAAOAAgAAABBAAkAAAAOAAgAAABBAAoAAAAOAAgAAABBAAsAAAAOAAgAAABAAAsAAAAOAAgAAABAAAwAAAAJAAsAAABBAAwAAAAKAAsAAABCAAwAAAALAAsAAABCAAsAAAAJAAgAAABCAAoAAAAOAAgAAABCAAkAAAAOAAgAAABCAAgAAAAQAAoAAABCAAcAAAAKAAwAAABCAAYAAAAKAAwAAABCAAUAAAAKAAwAAABCAAQAAAAKAAwAAABCAAMAAAAKAAwAAABCAAIAAAAKAAsAAABBAAIAAAAKAAsAAABAAAIAAAAKAAsAAABDAAIAAAAKAAsAAABDAAMAAAAKAAwAAABDAAQAAAAKAAwAAABDAAUAAAAKAAwAAABDAAYAAAAKAAwAAABDAAcAAAAKAAwAAABDAAgAAAAKAAwAAABDAAkAAAAOAAgAAABDAAoAAAAJAAgAAABDAAsAAAADAAgAAABDAAwAAAADAAkAAABEAAoAAAAJAAgAAABEAAkAAAAJAAgAAABEAAgAAAALAAwAAABEAAcAAAALAAwAAABEAAYAAAALAAwAAABEAAUAAAALAAwAAABEAAQAAAALAAwAAABEAAMAAAALAAwAAABEAAIAAAALAAsAAABFAAsAAAADAAMAAABFAAwAAAADAAQAAABGAAsAAAAEAAMAAABGAAwAAAAAAAAAAABGAA0AAAAAAAAAAABFAA0AAAAAAAAAAABFAA4AAAAAAAAAAABGAA4AAAAAAAAAAAAsAAkAAAAKAAsAAAAsAAoAAAAKAAwAAAAsAAsAAAAKAAwAAAAtAAkAAAAKAAsAAAAtAAoAAAAKAAwAAAAtAAsAAAAKAAwAAAAuAAkAAAAKAAsAAAAuAAoAAAAKAAwAAAAuAAsAAAAKAAwAAAAvAAkAAAAKAAsAAAAvAAoAAAAKAAwAAAAvAAsAAAADAAgAAAAwAAkAAAAKAAsAAAAwAAoAAAAKAAwAAAAwAAsAAAAEAAgAAAAxAAkAAAAKAAsAAAAxAAoAAAAKAAwAAAAxAAsAAAACAAMAAAAyAAkAAAAKAAsAAAAyAAoAAAAKAAwAAAAyAAsAAAACAAMAAAAzAAkAAAAKAAsAAAAzAAoAAAAKAAwAAAAzAAsAAAACAAMAAAA0AAkAAAAKAAsAAAA0AAoAAAAKAAwAAAA0AAsAAAACAAMAAAAsAAcAAAACAAAAAAAsAAgAAAACAAEAAAAtAAcAAAACAAAAAAAtAAgAAAACAAEAAAAuAAcAAAACAAAAAAAuAAgAAAACAAEAAAAvAAcAAAACAAAAAAAvAAgAAAACAAEAAAAwAAcAAAACAAAAAAAwAAgAAAACAAEAAAAxAAcAAAACAAAAAAAxAAgAAAACAAEAAAAyAAcAAAACAAAAAAAyAAgAAAACAAEAAAAzAAcAAAACAAAAAAAzAAgAAAACAAEAAAA0AAcAAAACAAAAAAA0AAgAAAACAAEAAAAsAAwAAAAKAA0AAAAsAA0AAAACAAMAAAAtAAwAAAAKAA0AAAAtAA0AAAACAAMAAAAuAAwAAAAKAA0AAAAuAA0AAAACAAMAAAAvAAwAAAADAAkAAAAvAA0AAAADAAMAAAAwAAwAAAAEAAQAAAAwAA0AAAAEAAMAAAAxAAwAAAACAAQAAAAxAA0AAAAAAAAAAAAyAAwAAAACAAQAAAAyAA0AAAAAAAAAAAAzAAwAAAACAAQAAAAzAA0AAAAAAAAAAAA0AAwAAAACAAQAAAA0AA0AAAAAAAAAAAAuAA4AAAACAAQAAAAtAA4AAAACAAQAAAAvAA4AAAADAAQAAAAwAA4AAAAAAAAAAAAsAA4AAAACAAQAAAA0AA4AAAAAAAAAAAAzAA4AAAAAAAAAAAAyAA4AAAAAAAAAAAAxAA4AAAAAAAAAAAAsAA8AAAAAAAAAAAAtAA8AAAAAAAAAAAAuAA8AAAAAAAAAAAAvAA8AAAAAAAAAAAAwAA8AAAAAAAAAAAAxAA8AAAAAAAAAAAAxABAAAAAAAAAAAAAyABAAAAAAAAAAAAAzABAAAAAAAAAAAAA0ABAAAAAAAAAAAAA1AA8AAAAAAAAAAAA0AA8AAAAAAAAAAAAzAA8AAAAAAAAAAAAyAA8AAAAAAAAAAAA1ABAAAAAAAAAAAAAwABAAAAAAAAAAAAAvABAAAAAAAAAAAAAtABAAAAAAAAAAAAAsABAAAAAAAAAAAAAuABAAAAAAAAAAAAA0AAYAAAAAAAAAAAAzAAYAAAAAAAAAAAAyAAYAAAAAAAAAAAAxAAYAAAAAAAAAAAAwAAYAAAAAAAAAAAAvAAYAAAAAAAAAAAAuAAYAAAAAAAAAAAAtAAYAAAAAAAAAAAAsAAYAAAAAAAAAAAAsAAUAAAAAAAAAAAAtAAUAAAAAAAAAAAAuAAUAAAAAAAAAAAAvAAUAAAAAAAAAAAAwAAUAAAAAAAAAAAAxAAUAAAAAAAAAAAAyAAUAAAAAAAAAAAAzAAUAAAAAAAAAAAA0AAUAAAAAAAAAAAA0AAQAAAAAAAAAAAA0AAMAAAAAAAAAAAA0AAIAAAAAAAAAAAA0AAEAAAAAAAAAAAA0AAAAAAAAAAAAAAAzAAAAAAAAAAAAAAAyAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAvAAAAAAAAAAAAAAAuAAAAAAAAAAAAAAAtAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAAsAAEAAAAAAAAAAAAsAAIAAAAAAAAAAAAsAAMAAAAAAAAAAAAsAAQAAAAAAAAAAAAtAAQAAAAAAAAAAAAuAAQAAAAAAAAAAAAvAAQAAAAAAAAAAAAwAAQAAAAAAAAAAAAxAAQAAAAAAAAAAAAyAAQAAAAAAAAAAAAzAAQAAAAAAAAAAAAzAAMAAAAAAAAAAAAzAAIAAAAAAAAAAAAzAAEAAAAAAAAAAAAyAAEAAAAAAAAAAAAxAAEAAAAAAAAAAAAwAAEAAAAAAAAAAAAvAAEAAAAAAAAAAAAuAAEAAAAAAAAAAAAtAAEAAAAAAAAAAAAtAAIAAAAAAAAAAAAtAAMAAAAAAAAAAAAuAAMAAAAAAAAAAAAvAAMAAAAAAAAAAAAwAAMAAAAAAAAAAAAxAAMAAAAAAAAAAAAyAAMAAAAAAAAAAAAyAAIAAAAAAAAAAAAxAAIAAAAAAAAAAAAwAAIAAAAAAAAAAAAvAAIAAAAAAAAAAAAuAAIAAAAAAAAAAAA4AAMAAAAKAAwAAAA5AAMAAAAKAAwAAAA5AAQAAAAKAAwAAABDAA8AAAADAAMAAABDABAAAAADAAQAAABEAA8AAAAEAAMAAABEABAAAAAEAAQAAABCAA8AAAACAAMAAABCABAAAAACAAQAAABBAA8AAAACAAMAAABBABAAAAACAAQAAABAAA8AAAACAAMAAABAABAAAAACAAQAAAA/AA8AAAACAAMAAAA/ABAAAAACAAQAAAA9AA8AAAAAAAMAAAA9ABAAAAAAAAQAAAA+AA8AAAABAAMAAAA+ABAAAAABAAQAAAA=") tile_set = ExtResource("1_cvtbc") [node name="TileMapLayer" type="TileMapLayer" parent="Environment" unique_id=349941462] -tile_map_data = PackedByteArray("AABBAAoAAAAOAAMAAAA5AAoAAAAMAAMAAAA6AAoAAAANAAMAAAA7AAoAAAANAAMAAAA8AAoAAAANAAMAAAA9AAoAAAANAAMAAAA+AAoAAAANAAMAAAA/AAoAAAANAAMAAABAAAoAAAANAAMAAABBAAgAAAAOAAEAAABBAAkAAAAOAAIAAAA8AAgAAAAOAAQAAAA+AAgAAAAPAAQAAAA=") +tile_map_data = PackedByteArray("AABBAAoAAAAOAAMAAAA5AAoAAAAMAAMAAAA6AAoAAAANAAMAAAA7AAoAAAANAAMAAAA8AAoAAAANAAMAAAA9AAoAAAANAAMAAAA+AAoAAAANAAMAAAA/AAoAAAANAAMAAABAAAoAAAANAAMAAABBAAgAAAAOAAEAAABBAAkAAAAOAAIAAAA8AAgAAAAOAAQAAAA+AAgAAAAPAAQAAAA4AAwAAAARAAUAAAA6AAwAAAARAAUAAAA8AAwAAAARAAUAAAA=") tile_set = ExtResource("1_cvtbc") [node name="TileMapLayerAbove" type="TileMapLayer" parent="Environment" unique_id=2036754042] diff --git a/src/scenes/enemy_humanoid.tscn b/src/scenes/enemy_humanoid.tscn index d7add3d..df92145 100644 --- a/src/scenes/enemy_humanoid.tscn +++ b/src/scenes/enemy_humanoid.tscn @@ -204,6 +204,24 @@ shader_parameter/replace_5 = Color(0, 0, 0, 1) shader_parameter/replace_6 = Color(0, 0, 0, 1) shader_parameter/tint = Color(1, 1, 1, 1) +[sub_resource type="ShaderMaterial" id="ShaderMaterial_shield"] +shader = ExtResource("4_r7ul0") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) + [sub_resource type="CircleShape2D" id="CircleShape2D_1"] radius = 5.0 @@ -227,23 +245,13 @@ stream_2/stream = ExtResource("11_5x2ph") stream_3/stream = ExtResource("12_oynfq") stream_4/stream = ExtResource("13_b0veo") -[sub_resource type="ShaderMaterial" id="ShaderMaterial_shield"] -shader = ExtResource("4_r7ul0") -shader_parameter/original_0 = Color(0, 0, 0, 1) -shader_parameter/original_1 = Color(0, 0, 0, 1) -shader_parameter/original_2 = Color(0, 0, 0, 1) -shader_parameter/original_3 = Color(0, 0, 0, 1) -shader_parameter/original_4 = Color(0, 0, 0, 1) -shader_parameter/original_5 = Color(0, 0, 0, 1) -shader_parameter/original_6 = Color(0, 0, 0, 1) -shader_parameter/replace_0 = Color(0, 0, 0, 1) -shader_parameter/replace_1 = Color(0, 0, 0, 1) -shader_parameter/replace_2 = Color(0, 0, 0, 1) -shader_parameter/replace_3 = Color(0, 0, 0, 1) -shader_parameter/replace_4 = Color(0, 0, 0, 1) -shader_parameter/replace_5 = Color(0, 0, 0, 1) -shader_parameter/replace_6 = Color(0, 0, 0, 1) -shader_parameter/tint = Color(1, 1, 1, 1) +[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_block"] +playback_mode = 1 +streams_count = 4 +stream_0/stream = ExtResource("18_sfx") +stream_1/stream = ExtResource("19_sfx") +stream_2/stream = ExtResource("20_sfx") +stream_3/stream = ExtResource("21_sfx") [sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_bow"] playback_mode = 1 @@ -253,14 +261,6 @@ stream_0/stream = ExtResource("22_sfx") stream_1/stream = ExtResource("23_sfx") stream_2/stream = ExtResource("24_sfx") -[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_block"] -playback_mode = 1 -streams_count = 4 -stream_0/stream = ExtResource("18_sfx") -stream_1/stream = ExtResource("19_sfx") -stream_2/stream = ExtResource("20_sfx") -stream_3/stream = ExtResource("21_sfx") - [node name="EnemyHumanoid" type="CharacterBody2D" unique_id=285357386] collision_layer = 2 collision_mask = 65 @@ -269,13 +269,14 @@ script = ExtResource("1") [node name="Shadow" type="Sprite2D" parent="." unique_id=468462304] z_index = -1 -position = Vector2(0, 7) +position = Vector2(0, 3) texture = SubResource("GradientTexture2D_1") script = ExtResource("2") [node name="Sprite2DBody" type="Sprite2D" parent="." unique_id=855871821] y_sort_enabled = true material = SubResource("ShaderMaterial_uedn7") +position = Vector2(0, -4) texture = ExtResource("3") hframes = 35 vframes = 8 @@ -283,59 +284,67 @@ vframes = 8 [node name="Sprite2DBoots" type="Sprite2D" parent="." unique_id=460958943] y_sort_enabled = true material = SubResource("ShaderMaterial_5x2ph") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DArmour" type="Sprite2D" parent="." unique_id=6790482] y_sort_enabled = true material = SubResource("ShaderMaterial_r7ul0") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DFacialHair" type="Sprite2D" parent="." unique_id=31110906] y_sort_enabled = true material = SubResource("ShaderMaterial_oynfq") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DHair" type="Sprite2D" parent="." unique_id=425592986] y_sort_enabled = true material = SubResource("ShaderMaterial_b0veo") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DEyes" type="Sprite2D" parent="." unique_id=496437887] y_sort_enabled = true material = SubResource("ShaderMaterial_of8l8") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DEyeLashes" type="Sprite2D" parent="." unique_id=1799398723] y_sort_enabled = true material = SubResource("ShaderMaterial_ofeay") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DAddons" type="Sprite2D" parent="." unique_id=1702763725] y_sort_enabled = true material = SubResource("ShaderMaterial_5a33a") +position = Vector2(0, -4) hframes = 35 vframes = 8 [node name="Sprite2DHeadgear" type="Sprite2D" parent="." unique_id=164186416] y_sort_enabled = true material = SubResource("ShaderMaterial_i1636") +position = Vector2(0, -4) hframes = 35 vframes = 8 -[node name="Sprite2DShield" type="Sprite2D" parent="."] +[node name="Sprite2DShield" type="Sprite2D" parent="." unique_id=470468744] visible = false material = SubResource("ShaderMaterial_shield") texture = ExtResource("14_shield") hframes = 35 vframes = 8 -[node name="Sprite2DShieldHolding" type="Sprite2D" parent="."] +[node name="Sprite2DShieldHolding" type="Sprite2D" parent="." unique_id=1318098286] visible = false material = SubResource("ShaderMaterial_shield") texture = ExtResource("15_shieldh") @@ -344,11 +353,13 @@ vframes = 8 [node name="Sprite2DWeapon" type="Sprite2D" parent="." unique_id=1718282928] y_sort_enabled = true +position = Vector2(0, -4) texture = ExtResource("4") hframes = 35 vframes = 8 -[node name="Incantation" parent="." instance=ExtResource("16_inc")] +[node name="Incantation" parent="." unique_id=441417699 instance=ExtResource("16_inc")] +position = Vector2(0, -4) [node name="AlertIndicator" type="Sprite2D" parent="." unique_id=1697001148] visible = false @@ -365,10 +376,10 @@ texture = ExtResource("6") hframes = 3 [node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=189217716] -position = Vector2(0, 4) shape = SubResource("CircleShape2D_1") [node name="AttackArea" type="Area2D" parent="." unique_id=1923132385] +position = Vector2(0, -4) collision_layer = 0 [node name="CollisionShape2D" type="CollisionShape2D" parent="AttackArea" unique_id=1597070641] @@ -376,12 +387,14 @@ position = Vector2(0, 4) shape = SubResource("CircleShape2D_1") [node name="AggroArea" type="Area2D" parent="." unique_id=1234567890] +position = Vector2(0, -4) collision_layer = 0 [node name="CollisionShape2D" type="CollisionShape2D" parent="AggroArea" unique_id=1286608618] shape = SubResource("CircleShape2D_2") [node name="SfxDie" type="AudioStreamPlayer2D" parent="." unique_id=693933783] +position = Vector2(0, -4) stream = SubResource("AudioStreamRandomizer_fikv0") max_distance = 930.0 attenuation = 8.282114 @@ -390,6 +403,7 @@ panning_strength = 1.3 bus = &"Sfx" [node name="SfxAlertFoundPlayer" type="AudioStreamPlayer2D" parent="." unique_id=815591859] +position = Vector2(0, -4) stream = SubResource("AudioStreamRandomizer_37mja") max_distance = 1146.0 attenuation = 8.57418 @@ -397,45 +411,53 @@ max_polyphony = 4 panning_strength = 1.04 bus = &"Sfx" -[node name="SfxActivateShield" type="AudioStreamPlayer2D" parent="."] +[node name="SfxActivateShield" type="AudioStreamPlayer2D" parent="." unique_id=626374525] +position = Vector2(0, -4) stream = ExtResource("17_sfx") volume_db = 9.695 attenuation = 1.3660401 panning_strength = 1.78 -[node name="SfxBlockWithShield" type="AudioStreamPlayer2D" parent="."] +[node name="SfxBlockWithShield" type="AudioStreamPlayer2D" parent="." unique_id=4928520] +position = Vector2(0, -4) stream = SubResource("AudioStreamRandomizer_block") volume_db = 7.254 attenuation = 1.3195078 panning_strength = 1.06 bus = &"Sfx" -[node name="SfxBowShoot" type="AudioStreamPlayer2D" parent="."] +[node name="SfxBowShoot" type="AudioStreamPlayer2D" parent="." unique_id=495272024] +position = Vector2(0, -4) stream = SubResource("AudioStreamRandomizer_bow") pitch_scale = 1.33 attenuation = 6.7271657 -[node name="SfxBowWithoutArrow" type="AudioStreamPlayer2D" parent="."] +[node name="SfxBowWithoutArrow" type="AudioStreamPlayer2D" parent="." unique_id=343560488] +position = Vector2(0, -4) stream = ExtResource("25_sfx") max_distance = 1455.0 attenuation = 7.4642572 -[node name="SfxBuckleBow" type="AudioStreamPlayer2D" parent="."] +[node name="SfxBuckleBow" type="AudioStreamPlayer2D" parent="." unique_id=1114353313] +position = Vector2(0, -4) stream = ExtResource("26_sfx") attenuation = 7.727478 panning_strength = 1.03 -[node name="SfxSpellCharge" type="AudioStreamPlayer2D" parent="."] +[node name="SfxSpellCharge" type="AudioStreamPlayer2D" parent="." unique_id=671028964] +position = Vector2(0, -4) stream = ExtResource("27_sfx") -[node name="SfxThrow" type="AudioStreamPlayer2D" parent="."] +[node name="SfxThrow" type="AudioStreamPlayer2D" parent="." unique_id=92763809] +position = Vector2(0, -4) stream = ExtResource("28_sfx") pitch_scale = 0.61 max_distance = 983.0 attenuation = 8.876549 panning_strength = 1.04 -[node name="SfxLift" type="AudioStreamPlayer2D" parent="."] +[node name="SfxLift" type="AudioStreamPlayer2D" parent="." unique_id=722569138] +position = Vector2(0, -4) stream = ExtResource("29_sfx") max_distance = 1246.0 attenuation = 1.9999994 diff --git a/src/scenes/fallout.tscn b/src/scenes/fallout.tscn new file mode 100644 index 0000000..2e47429 --- /dev/null +++ b/src/scenes/fallout.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://cm4f7w8ohdi7r"] + +[node name="Fallout" type="Node2D" unique_id=587574934] diff --git a/src/scenes/game_world.tscn b/src/scenes/game_world.tscn index 915256d..1499ea0 100644 --- a/src/scenes/game_world.tscn +++ b/src/scenes/game_world.tscn @@ -42,6 +42,105 @@ shader_parameter/replace_13 = Color(0, 0, 0, 1) shader_parameter/tint = Color(1, 1, 1, 1) shader_parameter/ambient = Color(1, 1, 1, 1) +[sub_resource type="ShaderMaterial" id="ShaderMaterial_u1jpj"] +shader = ExtResource("4_bhwwd") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/original_7 = Color(0, 0, 0, 1) +shader_parameter/original_8 = Color(0, 0, 0, 1) +shader_parameter/original_9 = Color(0, 0, 0, 1) +shader_parameter/original_10 = Color(0, 0, 0, 1) +shader_parameter/original_11 = Color(0, 0, 0, 1) +shader_parameter/original_12 = Color(0, 0, 0, 1) +shader_parameter/original_13 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/replace_7 = Color(0, 0, 0, 1) +shader_parameter/replace_8 = Color(0, 0, 0, 1) +shader_parameter/replace_9 = Color(0, 0, 0, 1) +shader_parameter/replace_10 = Color(0, 0, 0, 1) +shader_parameter/replace_11 = Color(0, 0, 0, 1) +shader_parameter/replace_12 = Color(0, 0, 0, 1) +shader_parameter/replace_13 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) +shader_parameter/ambient = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_uh34q"] +shader = ExtResource("4_bhwwd") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/original_7 = Color(0, 0, 0, 1) +shader_parameter/original_8 = Color(0, 0, 0, 1) +shader_parameter/original_9 = Color(0, 0, 0, 1) +shader_parameter/original_10 = Color(0, 0, 0, 1) +shader_parameter/original_11 = Color(0, 0, 0, 1) +shader_parameter/original_12 = Color(0, 0, 0, 1) +shader_parameter/original_13 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/replace_7 = Color(0, 0, 0, 1) +shader_parameter/replace_8 = Color(0, 0, 0, 1) +shader_parameter/replace_9 = Color(0, 0, 0, 1) +shader_parameter/replace_10 = Color(0, 0, 0, 1) +shader_parameter/replace_11 = Color(0, 0, 0, 1) +shader_parameter/replace_12 = Color(0, 0, 0, 1) +shader_parameter/replace_13 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) +shader_parameter/ambient = Color(1, 1, 1, 1) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_ph1f2"] +shader = ExtResource("4_bhwwd") +shader_parameter/original_0 = Color(0, 0, 0, 1) +shader_parameter/original_1 = Color(0, 0, 0, 1) +shader_parameter/original_2 = Color(0, 0, 0, 1) +shader_parameter/original_3 = Color(0, 0, 0, 1) +shader_parameter/original_4 = Color(0, 0, 0, 1) +shader_parameter/original_5 = Color(0, 0, 0, 1) +shader_parameter/original_6 = Color(0, 0, 0, 1) +shader_parameter/original_7 = Color(0, 0, 0, 1) +shader_parameter/original_8 = Color(0, 0, 0, 1) +shader_parameter/original_9 = Color(0, 0, 0, 1) +shader_parameter/original_10 = Color(0, 0, 0, 1) +shader_parameter/original_11 = Color(0, 0, 0, 1) +shader_parameter/original_12 = Color(0, 0, 0, 1) +shader_parameter/original_13 = Color(0, 0, 0, 1) +shader_parameter/replace_0 = Color(0, 0, 0, 1) +shader_parameter/replace_1 = Color(0, 0, 0, 1) +shader_parameter/replace_2 = Color(0, 0, 0, 1) +shader_parameter/replace_3 = Color(0, 0, 0, 1) +shader_parameter/replace_4 = Color(0, 0, 0, 1) +shader_parameter/replace_5 = Color(0, 0, 0, 1) +shader_parameter/replace_6 = Color(0, 0, 0, 1) +shader_parameter/replace_7 = Color(0, 0, 0, 1) +shader_parameter/replace_8 = Color(0, 0, 0, 1) +shader_parameter/replace_9 = Color(0, 0, 0, 1) +shader_parameter/replace_10 = Color(0, 0, 0, 1) +shader_parameter/replace_11 = Color(0, 0, 0, 1) +shader_parameter/replace_12 = Color(0, 0, 0, 1) +shader_parameter/replace_13 = Color(0, 0, 0, 1) +shader_parameter/tint = Color(1, 1, 1, 1) +shader_parameter/ambient = Color(1, 1, 1, 1) + [sub_resource type="ShaderMaterial" id="ShaderMaterial_bhwwd"] shader = ExtResource("4_bhwwd") shader_parameter/original_0 = Color(0, 0, 0, 1) @@ -92,7 +191,16 @@ z_index = -2 material = SubResource("ShaderMaterial_pdbwf") tile_set = ExtResource("9") +[node name="TileMapLayerDecoratedGround" type="TileMapLayer" parent="Environment" unique_id=1839647666] +material = SubResource("ShaderMaterial_u1jpj") +tile_set = ExtResource("9") + +[node name="TileMapLayerCrackedGround" type="TileMapLayer" parent="Environment" unique_id=556112467] +material = SubResource("ShaderMaterial_uh34q") +tile_set = ExtResource("9") + [node name="TileMapLayerMiddle" type="TileMapLayer" parent="Environment" unique_id=1063124770] +material = SubResource("ShaderMaterial_ph1f2") tile_set = ExtResource("9") [node name="TileMapLayerAbove" type="TileMapLayer" parent="Environment" unique_id=1234567892] diff --git a/src/scenes/player.tscn b/src/scenes/player.tscn index fc49e80..a3e2c8f 100644 --- a/src/scenes/player.tscn +++ b/src/scenes/player.tscn @@ -289,6 +289,9 @@ radius = 4.0 [sub_resource type="CircleShape2D" id="CircleShape2D_2"] radius = 8.0 +[sub_resource type="RectangleShape2D" id="RectangleShape2D_quicksand"] +size = Vector2(8, 8) + [sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_l71n6"] playback_mode = 1 random_pitch = 1.0118532 @@ -340,6 +343,21 @@ tracks/0/keys = { "values": [2037] } +[sub_resource type="Animation" id="Animation_6e8lb"] +resource_name = "fallout" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("IncantationSprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(-0.029999994), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [2037] +} + [sub_resource type="Animation" id="Animation_j2b1d"] resource_name = "fire_charging" length = 0.4 @@ -459,6 +477,7 @@ tracks/0/keys = { [sub_resource type="AnimationLibrary" id="AnimationLibrary_2dvfe"] _data = { &"RESET": SubResource("Animation_t4otl"), +&"fallout": SubResource("Animation_6e8lb"), &"fire_charging": SubResource("Animation_j2b1d"), &"fire_ready": SubResource("Animation_cs1tg"), &"frost_charging": SubResource("Animation_frost_ch"), @@ -702,11 +721,20 @@ collision_mask = 3 shape = SubResource("CircleShape2D_2") debug_color = Color(0.70196074, 0.6126261, 0.19635464, 0.41960785) +[node name="QuicksandArea" type="Area2D" parent="." unique_id=600000001] +position = Vector2(0, 4) +collision_layer = 0 +collision_mask = 0 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="QuicksandArea" unique_id=600000002] +shape = SubResource("RectangleShape2D_quicksand") +debug_color = Color(0.48027503, 0, 0.70196074, 0.41960785) + [node name="Label" type="Label" parent="." unique_id=227628720] offset_left = -10.0 offset_top = -15.0 offset_right = 10.0 -offset_bottom = -9.0 +offset_bottom = 8.0 horizontal_alignment = 1 [node name="InteractionIndicator" type="Sprite2D" parent="." unique_id=1661043470] @@ -732,6 +760,12 @@ volume_db = -2.537 attenuation = 8.876548 bus = &"Sfx" +[node name="SfxFallout" type="AudioStreamPlayer2D" parent="." unique_id=600000003] +stream = ExtResource("19_4r5pv") +volume_db = -2.537 +attenuation = 8.876548 +bus = &"Sfx" + [node name="SfxTakeDamage" type="AudioStreamPlayer2D" parent="." unique_id=322150091] stream = SubResource("AudioStreamRandomizer_487ah") volume_db = -6.092 diff --git a/src/scripts/attack_bomb.gd b/src/scripts/attack_bomb.gd index 859bfd5..dc4a5d2 100644 --- a/src/scripts/attack_bomb.gd +++ b/src/scripts/attack_bomb.gd @@ -2,15 +2,15 @@ extends CharacterBody2D # Bomb - Explosive projectile that can be thrown or placed -@export var fuse_duration: float = 3.0 # Time until explosion -@export var base_damage: float = 50.0 # Base damage (increased from 30) -@export var damage_radius: float = 48.0 # Area of effect radius (48x48) -@export var screenshake_strength: float = 18.0 # Base screenshake strength (stronger) +@export var fuse_duration: float = 3.0 # Time until explosion +@export var base_damage: float = 50.0 # Base damage (increased from 30) +@export var damage_radius: float = 48.0 # Area of effect radius (48x48) +@export var screenshake_strength: float = 18.0 # Base screenshake strength (stronger) var player_owner: Node = null var is_fused: bool = false var fuse_timer: float = 0.0 -var is_thrown: bool = false # True if thrown by Dwarf, false if placed +var is_thrown: bool = false # True if thrown by Dwarf, false if placed var is_exploding: bool = false var explosion_frame: int = 0 var explosion_timer: float = 0.0 @@ -21,16 +21,21 @@ var velocity_z: float = 0.0 var gravity_z: float = 500.0 var is_airborne: bool = false var throw_velocity: Vector2 = Vector2.ZERO -var rotation_speed: float = 0.0 # Angular velocity when thrown +var rotation_speed: float = 0.0 # Angular velocity when thrown # Blinking animation var blink_timer: float = 0.0 -var bomb_visible: bool = true # Renamed to avoid shadowing CanvasItem.is_visible -var blink_start_time: float = 1.0 # Start blinking 1 second before explosion +var bomb_visible: bool = true # Renamed to avoid shadowing CanvasItem.is_visible +var blink_start_time: float = 1.0 # Start blinking 1 second before explosion # Collection var can_be_collected: bool = false -var collection_delay: float = 0.2 # Can be collected after 0.2 seconds +var collection_delay: float = 0.2 # Can be collected after 0.2 seconds + +# Fallout (landed in quicksand): sink and explode with no visual/damage, but sound + screenshake +var fell_in_fallout: bool = false +var fallout_sink_progress: float = 1.0 +const FALLOUT_SINK_DURATION: float = 0.5 @onready var sprite = $Sprite2D @onready var explosion_sprite = $ExplosionSprite @@ -45,13 +50,13 @@ var collection_delay: float = 0.2 # Can be collected after 0.2 seconds var damage_area_shape: CircleShape2D = null const TILE_SIZE: int = 16 -const TILE_STRIDE: int = 17 # 16 + separation 1 +const TILE_STRIDE: int = 17 # 16 + separation 1 var _explosion_tile_particle_scene: PackedScene = null func _ready(): # Set collision layer to 2 (interactable objects) so it can be grabbed collision_layer = 2 - collision_mask = 1 | 2 | 64 # Collide with players, objects, and walls + collision_mask = 1 | 2 | 64 # Collide with players, objects, and walls # Connect area signals if bomb_area and not bomb_area.body_entered.is_connected(_on_bomb_area_body_entered): @@ -85,7 +90,7 @@ func _deferred_ready(): var collision_shape = bomb_area.get_node_or_null("CollisionShape2D") if collision_shape: damage_area_shape = CircleShape2D.new() - damage_area_shape.radius = damage_radius / 2.0 # 24 pixel radius for 48x48 + damage_area_shape.radius = damage_radius / 2.0 # 24 pixel radius for 48x48 collision_shape.shape = damage_area_shape # Start fuse if not thrown (placed bomb starts fusing immediately; thrown bombs start fuse on land) @@ -143,11 +148,14 @@ func _start_fuse(): func _physics_process(delta): if is_exploding: - # Handle explosion animation + # Handle explosion animation (or immediate free if fell in fallout) + if fell_in_fallout: + queue_free() + return explosion_timer += delta if explosion_sprite: # Play 9 frames of explosion animation at ~15 FPS - if explosion_timer >= 0.06666667: # ~15 FPS + if explosion_timer >= 0.06666667: # ~15 FPS explosion_timer = 0.0 explosion_frame += 1 if explosion_frame < 9: @@ -157,6 +165,21 @@ func _physics_process(delta): queue_free() return + # Fallout sink: scale down over time (no respawn) + if fell_in_fallout: + fallout_sink_progress -= delta / FALLOUT_SINK_DURATION + if fallout_sink_progress < 0.0: + fallout_sink_progress = 0.0 + scale = Vector2.ONE * max(0.0, fallout_sink_progress) + # Fuse still runs; when it hits, _explode() will do sound + screenshake only + if is_fused: + fuse_timer += delta + if fuse_timer >= fuse_duration: + _explode() + return + move_and_slide() + return + # Update fuse timer if is_fused: fuse_timer += delta @@ -164,7 +187,7 @@ func _physics_process(delta): # Start blinking when close to explosion if fuse_timer >= (fuse_duration - blink_start_time): blink_timer += delta - if blink_timer >= 0.1: # Blink every 0.1 seconds + if blink_timer >= 0.1: # Blink every 0.1 seconds blink_timer = 0.0 bomb_visible = not bomb_visible if sprite: @@ -183,7 +206,7 @@ func _physics_process(delta): # Update sprite position and rotation based on height if sprite: - sprite.position.y = -position_z * 0.5 + sprite.position.y = - position_z * 0.5 var height_scale = 1.0 - (position_z / 50.0) * 0.2 sprite.scale = Vector2.ONE * max(0.8, height_scale) sprite.rotation += rotation_speed * delta @@ -246,6 +269,15 @@ func _land(): position_z = 0.0 velocity_z = 0.0 + # If landed on fallout tile: sink and will explode with no visual/damage (sound + screenshake only) + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(global_position): + fell_in_fallout = true + fallout_sink_progress = 1.0 + can_be_collected = false + if collection_area: + collection_area.set_deferred("monitoring", false) + # Start fuse when landing if not is_fused: _start_fuse() @@ -256,7 +288,27 @@ func _explode(): is_exploding = true - # Hide bomb sprite and shadow, show explosion + # Stop fuse sound and particles + if has_node("SfxFuse"): + $SfxFuse.stop() + if fuse_particles: + fuse_particles.emitting = false + if fuse_light: + fuse_light.enabled = false + + # Fell in fallout: no explosion visual, no damage, but sound + screenshake + if fell_in_fallout: + if has_node("SfxExplosion"): + $SfxExplosion.play() + _cause_screenshake() + if bomb_area: + bomb_area.set_deferred("monitoring", false) + if collection_area: + collection_area.set_deferred("monitoring", false) + queue_free() + return + + # Normal explosion if sprite: sprite.visible = false if shadow: @@ -267,38 +319,27 @@ func _explode(): explosion_frame = 0 explosion_timer = 0.0 - # Stop fuse sound and particles - if has_node("SfxFuse"): - $SfxFuse.stop() - if fuse_particles: - fuse_particles.emitting = false - - # Disable fuse light, enable explosion light - if fuse_light: - fuse_light.enabled = false if explosion_light: explosion_light.enabled = true - # Fade out explosion light over time var tween = create_tween() tween.tween_property(explosion_light, "energy", 0.0, 0.3) tween.tween_callback(func(): if explosion_light: explosion_light.enabled = false) - # Play explosion sound if has_node("SfxExplosion"): $SfxExplosion.play() - # Deal area damage _deal_explosion_damage() - # Cause screenshake + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("break_cracked_tiles_in_radius"): + gw.break_cracked_tiles_in_radius(global_position, damage_radius / 2.0) + _cause_screenshake() - # Spawn tile debris particles (4 pieces per affected tile, bounce, fade) _spawn_explosion_tile_particles() if has_node("SfxDebrisFromParticles"): $SfxDebrisFromParticles.play() - # Disable collision if bomb_area: bomb_area.set_deferred("monitoring", false) if collection_area: @@ -333,8 +374,8 @@ func _deal_explosion_damage(): # Calculate distance for damage falloff var distance = global_position.distance_to(body.global_position) - var damage_multiplier = 1.0 - (distance / damage_radius) # Linear falloff - damage_multiplier = max(0.1, damage_multiplier) # Minimum 10% damage + var damage_multiplier = 1.0 - (distance / damage_radius) # Linear falloff + damage_multiplier = max(0.1, damage_multiplier) # Minimum 10% damage var final_damage = total_damage * damage_multiplier # Deal damage to players @@ -414,7 +455,7 @@ func _spawn_explosion_tile_particles(): continue var bx = atlas.x * TILE_STRIDE var by = atlas.y * TILE_STRIDE - var h = 8.0 # TILE_SIZE / 2 + var h = 8.0 # TILE_SIZE / 2 var regions = [ Rect2(bx, by, h, h), Rect2(bx + h, by, h, h), @@ -444,12 +485,12 @@ func _spawn_explosion_tile_particles(): spr.material = particle_material p.global_position = world - var speed = randf_range(280.0, 420.0) # Much faster - fly around more + var speed = randf_range(280.0, 420.0) # Much faster - fly around more var d = outward + Vector2(randf_range(-0.4, 0.4), randf_range(-0.4, 0.4)) p.velocity = d.normalized() * speed p.angular_velocity = randf_range(-14.0, 14.0) p.position_z = 0.0 - p.velocity_z = randf_range(100.0, 180.0) # Upward burst, then gravity brings them down + p.velocity_z = randf_range(100.0, 180.0) # Upward burst, then gravity brings them down parent.add_child(p) func _cause_screenshake(): @@ -477,11 +518,11 @@ func _cause_screenshake(): # Calculate screenshake strength (inverse distance, capped) var shake_strength = screenshake_strength / max(1.0, min_distance / 50.0) - shake_strength = min(shake_strength, screenshake_strength * 2.0) # Cap at 2x base + shake_strength = min(shake_strength, screenshake_strength * 2.0) # Cap at 2x base # Apply screenshake (longer duration for bigger boom) if game_world.has_method("add_screenshake"): - game_world.add_screenshake(shake_strength, 0.5) # 0.5 second duration + game_world.add_screenshake(shake_strength, 0.5) # 0.5 second duration func _on_bomb_area_body_entered(_body): # This is for explosion damage (handled in _deal_explosion_damage) @@ -540,7 +581,7 @@ func on_grabbed(by_player): if parent: parent.add_child(ft) ft.global_position = Vector2(by_player.global_position.x, by_player.global_position.y - 20) - ft.setup("+1 Bomb", Color(0.9, 0.5, 0.2), 0.5, 0.5) # Orange-ish + ft.setup("+1 Bomb", Color(0.9, 0.5, 0.2), 0.5, 0.5) # Orange-ish # Play pickup sound if has_node("SfxPickup"): diff --git a/src/scripts/dungeon_generator.gd b/src/scripts/dungeon_generator.gd index 8069e79..3f57960 100644 --- a/src/scripts/dungeon_generator.gd +++ b/src/scripts/dungeon_generator.gd @@ -53,12 +53,6 @@ const WALL_BOTTOM_RIGHT_BOTTOM_RIGHT = Vector2i(4, 4) # Bottom-right corner, bot const WALL_BOTTOM_UPPER = Vector2i(2, 3) # Bottom wall, upper part const WALL_BOTTOM_LOWER = Vector2i(2, 4) # Bottom wall, lower part -# Inner wall tiles (for non-rectangular rooms) -const INNER_WALL_TOP_LEFT = Vector2i(1, 6) -const INNER_WALL_TOP_RIGHT = Vector2i(3, 6) -const INNER_WALL_BOTTOM_LEFT = Vector2i(1, 8) -const INNER_WALL_BOTTOM_RIGHT = Vector2i(3, 8) - # Door tiles const DOOR_UP_START = Vector2i(7, 0) # 3x2 large const DOOR_LEFT_START = Vector2i(5, 2) # 2x3 large @@ -71,8 +65,35 @@ const STAIRS_LEFT_START = Vector2i(5, 2) # 2x3 large, middle tile is (5,1) inste const STAIRS_RIGHT_START = Vector2i(10, 2) # 2x3 large, middle tile is (11,1) instead of (11,3) const STAIRS_DOWN_START = Vector2i(7, 5) # 3x2 large, middle tile is (6,6) instead of (8,6) -# Ground/floor tiles (random selection) -const FLOOR_TILES = [Vector2i(9, 8), Vector2i(14, 8), Vector2i(6, 11)] +# Base floor tile always on DungeonLayer0 (standard floor) +const FLOOR_BASE = Vector2i(0, 15) +# Decorated ground tiles (TileMapLayerDecoratedGround): (16,16) = most common (blank), then (1,15)-(21,15), (0,16)-(14,16) +const FLOOR_DECORATED_PLAIN = Vector2i(16, 16) # Blank/non-decorated (0,0 has collision) +const FLOOR_DECORATED_RANGE_1_START = Vector2i(1, 15) +const FLOOR_DECORATED_RANGE_1_END = Vector2i(21, 15) +const FLOOR_DECORATED_RANGE_2_START = Vector2i(0, 16) +const FLOOR_DECORATED_RANGE_2_END = Vector2i(14, 16) +# Cracked ground tile (TileMapLayerCrackedGround) - terrain -2 +const CRACKED_TILE = Vector2i(15, 16) + +# Fallout: solo floor tiles (cracked/worn) - use center for all single fallout floor tiles +const FALLOUT_CENTER = Vector2i(10, 12) # 274 + +# Fallout inner wall edges (for larger holes - boundary between hole wall and room floor) +const FALLOUT_INNER_UP_LEFT = Vector2i(9, 11) # 251 +const FALLOUT_INNER_UP = Vector2i(10, 11) # 252 +const FALLOUT_INNER_UP_RIGHT = Vector2i(11, 11) # 253 +const FALLOUT_INNER_RIGHT = Vector2i(11, 12) # 275 +const FALLOUT_INNER_DOWN_RIGHT = Vector2i(11, 13) # 297 +const FALLOUT_INNER_DOWN = Vector2i(10, 13) # 296 +const FALLOUT_INNER_DOWN_LEFT = Vector2i(9, 13) # 295 +const FALLOUT_INNER_LEFT = Vector2i(9, 12) # 273 + +# Fallout inner corners (for larger holes) +const FALLOUT_CORNER_INNER_UP_LEFT = Vector2i(13, 10) # 233 +const FALLOUT_CORNER_INNER_UP_RIGHT = Vector2i(16, 10) # 236 +const FALLOUT_CORNER_INNER_DOWN_LEFT = Vector2i(13, 13) # 299 +const FALLOUT_CORNER_INNER_DOWN_RIGHT = Vector2i(16, 13) # 302 # Room generation parameters # Minimum room size is 3x3 floor tiles, but rooms need 2-tile walls on each side @@ -100,13 +121,19 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) - # Initialize grid (0 = wall, 1 = floor, 2 = door, 3 = corridor) var grid = [] - var tile_grid = [] # Actual tile coordinates (Vector2i) for rendering + var tile_grid = [] # Actual tile coordinates (Vector2i) for DungeonLayer0 + var decorated_tile_grid = [] # TileMapLayerDecoratedGround: random (0,0) or (1,15)-(21,15), (0,16)-(14,16) + var cracked_tile_grid = [] # TileMapLayerCrackedGround: (15,16) where true; never in doors/corridors/front for x in range(map_size.x): grid.append([]) tile_grid.append([]) + decorated_tile_grid.append([]) + cracked_tile_grid.append([]) for y in range(map_size.y): grid[x].append(0) # Start with all walls tile_grid[x].append(Vector2i(0, 0)) # Default wall tile (will be set properly later) + decorated_tile_grid[x].append(null) # Filled for floor/corridor only + cracked_tile_grid[x].append(false) # Filled for some floor only (not doors/corridors/front) var all_rooms = [] var all_doors = [] @@ -271,6 +298,18 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) - # 7. Render walls around rooms _render_room_walls(all_rooms, grid, tile_grid, map_size, rng) + # 7.4. Fix inner walls and corners (holes / compound rooms) + _cleanup_compound_room_corners_and_walls(all_rooms, grid, tile_grid, map_size) + + # 7.43. Fill decorated ground layer (random per floor/corridor); (0,0) most common + _fill_decorated_tile_grid(grid, decorated_tile_grid, map_size, rng) + # 7.44. Sprinkle cracked floor (15,16) on some floor; never in doors, corridors, in front of doors, or in start room + _fill_cracked_tile_grid(grid, cracked_tile_grid, map_size, all_doors, all_rooms[start_room_index], rng) + # 7.45. Sprinkle fallout tiles on some floor (cracked/worn look); never in doors, corridors, in front of doors, or in start room + _render_fallout_tiles(grid, tile_grid, map_size, all_doors, all_rooms[start_room_index], rng) + # 7.46. Keep fallout tiles free from decorated layer (no decorated tiles on top of fallout) + _clear_decorated_on_fallout(tile_grid, decorated_tile_grid, map_size) + # 7.5. Place stairs in exit room BEFORE placing torches (so torches don't overlap stairs) var stairs_data = _place_stairs_in_exit_room(all_rooms[exit_room_index], grid, tile_grid, map_size, all_doors, rng) if stairs_data.is_empty(): @@ -305,9 +344,25 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) - var blocking_doors = blocking_doors_result.doors if blocking_doors_result.has("doors") else blocking_doors_result var room_puzzle_data = blocking_doors_result.puzzle_data if blocking_doors_result.has("puzzle_data") else {} + # Floor switches must NEVER have cracked or fallout: clear cracked_tile_grid and replace fallout with normal floor at all switch positions + var blocking_doors_array = blocking_doors if blocking_doors is Array else blocking_doors.doors + var switch_tiles_seen = {} + for door_data in blocking_doors_array: + if "switch_tile_x" in door_data and "switch_tile_y" in door_data: + var sx = door_data.switch_tile_x + var sy = door_data.switch_tile_y + var key = str(sx) + "," + str(sy) + if not key in switch_tiles_seen: + switch_tiles_seen[key] = true + if sx >= 0 and sx < map_size.x and sy >= 0 and sy < map_size.y: + cracked_tile_grid[sx][sy] = false + if _is_fallout_atlas(tile_grid[sx][sy]): + tile_grid[sx][sy] = FLOOR_BASE + LogManager.log("DungeonGenerator: Replaced fallout with normal floor at switch tile (" + str(sx) + "," + str(sy) + ")", LogManager.CATEGORY_DUNGEON) + LogManager.log("DungeonGenerator: Cleared cracked at switch tile (" + str(sx) + "," + str(sy) + ") - switches cannot have cracked ground", LogManager.CATEGORY_DUNGEON) + # Extract rooms with monster spawner puzzles (these should NOT have pre-spawned enemies) var rooms_with_spawner_puzzles = [] - var blocking_doors_array = blocking_doors if blocking_doors is Array else blocking_doors.doors for door_data in blocking_doors_array: if "puzzle_type" in door_data and door_data.puzzle_type == "enemy": if "blocking_room" in door_data and not door_data.blocking_room.is_empty(): @@ -348,7 +403,7 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) - var room = all_rooms[i] # Skip start room and exit room if i != start_room_index and i != exit_room_index: - var room_objects = _place_interactable_objects_in_room(room, grid, map_size, all_doors, all_enemies, rng, room_puzzle_data) + var room_objects = _place_interactable_objects_in_room(room, grid, tile_grid, map_size, all_doors, all_enemies, rng, room_puzzle_data) all_interactable_objects.append_array(room_objects) # 9.6. Place traps (1-2 per level, excluding start and exit rooms) @@ -369,21 +424,20 @@ func generate_dungeon(map_size: Vector2i, seed_value: int = 0, level: int = 1) - "blocking_doors": blocking_doors, "grid": grid, "tile_grid": tile_grid, + "decorated_tile_grid": decorated_tile_grid, + "cracked_tile_grid": cracked_tile_grid, "map_size": map_size, "start_room": all_rooms[start_room_index], "exit_room": all_rooms[exit_room_index] } -func _set_floor(room: Dictionary, grid: Array, tile_grid: Array, map_size: Vector2i, rng: RandomNumberGenerator): - # Set floor tiles for the room (interior only, walls will be set separately) - # Leave 2 tile border for walls (walls are 2 tiles tall) +func _set_floor(room: Dictionary, grid: Array, tile_grid: Array, map_size: Vector2i, _rng: RandomNumberGenerator): + # Set floor tiles for the room (interior only); base layer always (0,15) for x in range(room.x + 2, room.x + room.w - 2): for y in range(room.y + 2, room.y + room.h - 2): if x >= 0 and x < map_size.x and y >= 0 and y < map_size.y: grid[x][y] = 1 # Floor - # Random floor tile variation - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[x][y] = floor_tile + tile_grid[x][y] = FLOOR_BASE func _try_place_room_near(source_room: Dictionary, grid: Array, map_size: Vector2i, rng: RandomNumberGenerator) -> Dictionary: var attempts = 20 @@ -547,13 +601,11 @@ func _create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, grid: if corridor_intersects_other_room.call(corridor_start_x, corridor_y, corridor_end_x, corridor_y, true): return {} # Corridor would pass through another room, skip this connection - # Create corridor (1 tile wide) - use floor tiles - # Corridor is between the rooms, after the door + # Create corridor (1 tile wide) - base floor (0,15) for x in range(wall_x + 1, wall_x + corridor_length + 1): # Corridor starts after the wall if x >= 0 and x < map_size.x and door_y + 1 >= 0 and door_y + 1 < map_size.y: grid[x][door_y + 1] = 3 # Corridor (middle row of door) - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[x][door_y + 1] = floor_tile + tile_grid[x][door_y + 1] = FLOOR_BASE # Create door on RIGHT wall of left room (2x3 tiles - 2 wide, 3 tall) # Door is placed ON the wall, replacing the 2-tile wide wall @@ -572,8 +624,7 @@ func _create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, grid: tile_grid[x][y] = door_tile_start + Vector2i(door_dx, door_dy) if door_dx == 0 and door_dy == 1: grid[x][y] = 1 # Floor - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[x][y] = floor_tile + tile_grid[x][y] = FLOOR_BASE # Also create door on LEFT wall of right room (if there's a gap) @@ -591,8 +642,7 @@ func _create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, grid: tile_grid[x][y] = right_door_tile_start + Vector2i(door_dx, door_dy) if door_dx == 1 and door_dy == 1: grid[x][y] = 1 # Floor - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[x][y] = floor_tile + tile_grid[x][y] = FLOOR_BASE # CRITICAL: room1 = room the door is ON (left room for horizontal doors) # room2 = room the door leads TO (right room for horizontal doors) @@ -639,13 +689,11 @@ func _create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, grid: if corridor_intersects_other_room.call(corridor_x, corridor_start_y, corridor_x, corridor_end_y, false): return {} # Corridor would pass through another room, skip this connection - # Create corridor (1 tile wide) - use floor tiles - # Corridor is between the rooms, after the door + # Create corridor (1 tile wide) - base floor (0,15) for y in range(wall_y + 1, wall_y + corridor_length + 1): # Corridor starts after the wall if door_x + 1 >= 0 and door_x + 1 < map_size.x and y >= 0 and y < map_size.y: grid[door_x + 1][y] = 3 # Corridor (middle column of door) - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[door_x + 1][y] = floor_tile + tile_grid[door_x + 1][y] = FLOOR_BASE # Create door on BOTTOM wall of top room (3x2 tiles - 3 wide, 2 tall) # Door is placed ON the wall, replacing the 2-tile tall wall @@ -664,8 +712,7 @@ func _create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, grid: tile_grid[x][y] = door_tile_start + Vector2i(door_dx, door_dy) if door_dx == 1 and door_dy == 0: grid[x][y] = 1 # Floor - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[x][y] = floor_tile + tile_grid[x][y] = FLOOR_BASE # Also create door on TOP wall of bottom room (if there's a gap) if corridor_length > 0: @@ -682,8 +729,7 @@ func _create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, grid: tile_grid[x][y] = bottom_door_tile_start + Vector2i(door_dx, door_dy) if door_dx == 1 and door_dy == 1: grid[x][y] = 1 # Floor - var floor_tile = FLOOR_TILES[rng.randi() % FLOOR_TILES.size()] - tile_grid[x][y] = floor_tile + tile_grid[x][y] = FLOOR_BASE # CRITICAL: room1 = room the door is ON (top room for vertical doors) # room2 = room the door leads TO (bottom room for vertical doors) @@ -1037,7 +1083,7 @@ func _add_hole_to_room(room: Dictionary, grid: Array, tile_grid: Array, map_size var hole_x = rng.randi_range(floor_min_x, max_x) var hole_y = rng.randi_range(floor_min_y, max_y) - # Create hole (back to wall) - use inner wall tiles + # Create hole (back to wall) - use fallout inner corner/edge tiles; cleanup pass will refine edges # Only create hole if the position is currently a floor tile for x in range(hole_x, hole_x + hole_size): for y in range(hole_y, hole_y + hole_size): @@ -1045,18 +1091,17 @@ func _add_hole_to_room(room: Dictionary, grid: Array, tile_grid: Array, map_size # Only create hole if it's currently a floor tile if grid[x][y] == 1: # Floor grid[x][y] = 0 # Wall - # Use inner wall tiles for holes + # Fallout corner tiles for hole corners; rest get center (cleanup will set edges) if x == hole_x and y == hole_y: - tile_grid[x][y] = INNER_WALL_TOP_LEFT + tile_grid[x][y] = FALLOUT_CORNER_INNER_UP_LEFT elif x == hole_x + hole_size - 1 and y == hole_y: - tile_grid[x][y] = INNER_WALL_TOP_RIGHT + tile_grid[x][y] = FALLOUT_CORNER_INNER_UP_RIGHT elif x == hole_x and y == hole_y + hole_size - 1: - tile_grid[x][y] = INNER_WALL_BOTTOM_LEFT + tile_grid[x][y] = FALLOUT_CORNER_INNER_DOWN_LEFT elif x == hole_x + hole_size - 1 and y == hole_y + hole_size - 1: - tile_grid[x][y] = INNER_WALL_BOTTOM_RIGHT + tile_grid[x][y] = FALLOUT_CORNER_INNER_DOWN_RIGHT else: - # Use default wall tile for interior of hole - tile_grid[x][y] = WALL_TOP_UPPER + tile_grid[x][y] = FALLOUT_CENTER func _find_farthest_room(all_rooms: Array, start_index: int) -> int: var start_room = all_rooms[start_index] @@ -1250,6 +1295,200 @@ func _render_room_walls(all_rooms: Array, grid: Array, tile_grid: Array, map_siz if grid[right_x_right][y] != 2: # Not a door (don't overwrite door tiles) tile_grid[right_x_right][y] = WALL_RIGHT_RIGHT +func _cleanup_compound_room_corners_and_walls(all_rooms: Array, grid: Array, tile_grid: Array, map_size: Vector2i): + # Fix inner walls and corners for rooms that have holes (compound shapes). + # Use fallout inner/corner tiles for hole boundaries (larger holes get proper corners and edges). + for room in all_rooms: + var rx = room.x + var ry = room.y + var rw = room.w + var rh = room.h + # Inner area: skip outer 2-tile perimeter (walls); only consider wall cells inside the room + for x in range(rx + 2, rx + rw - 2): + for y in range(ry + 2, ry + rh - 2): + if x < 0 or x >= map_size.x or y < 0 or y >= map_size.y: + continue + if grid[x][y] != 0: + continue # Not a wall (e.g. floor, door, corridor) + # Wall cell inside room - check 4-neighbors for floor (1) or corridor (3) + var floor_left = (x - 1 >= 0 and (grid[x - 1][y] == 1 or grid[x - 1][y] == 3)) + var floor_right = (x + 1 < map_size.x and (grid[x + 1][y] == 1 or grid[x + 1][y] == 3)) + var floor_up = (y - 1 >= 0 and (grid[x][y - 1] == 1 or grid[x][y - 1] == 3)) + var floor_down = (y + 1 < map_size.y and (grid[x][y + 1] == 1 or grid[x][y + 1] == 3)) + # Inner corner: two perpendicular floor neighbors → fallout corner tiles + if floor_right and floor_down: + tile_grid[x][y] = FALLOUT_CORNER_INNER_UP_LEFT + elif floor_left and floor_down: + tile_grid[x][y] = FALLOUT_CORNER_INNER_UP_RIGHT + elif floor_right and floor_up: + tile_grid[x][y] = FALLOUT_CORNER_INNER_DOWN_LEFT + elif floor_left and floor_up: + tile_grid[x][y] = FALLOUT_CORNER_INNER_DOWN_RIGHT + # Inner edge: single floor neighbor → fallout inner edge tiles + elif floor_down: + tile_grid[x][y] = FALLOUT_INNER_UP + elif floor_up: + tile_grid[x][y] = FALLOUT_INNER_DOWN + elif floor_right: + tile_grid[x][y] = FALLOUT_INNER_LEFT + elif floor_left: + tile_grid[x][y] = FALLOUT_INNER_RIGHT + # Wall with no floor neighbor (hole interior) → fallout center + else: + tile_grid[x][y] = FALLOUT_CENTER + +func _is_wall_at(grid: Array, map_size: Vector2i, x: int, y: int) -> bool: + if x < 0 or x >= map_size.x or y < 0 or y >= map_size.y: + return false + return grid[x][y] == 0 or grid[x][y] == 2 # wall or door + +func _get_fallout_tile_for_floor(grid: Array, map_size: Vector2i, x: int, y: int) -> Vector2i: + # Choose fallout tile based on adjacent walls (up/right/down/left). Default: fallout_center. + var w_up = _is_wall_at(grid, map_size, x, y - 1) + var w_right = _is_wall_at(grid, map_size, x + 1, y) + var w_down = _is_wall_at(grid, map_size, x, y + 1) + var w_left = _is_wall_at(grid, map_size, x - 1, y) + # Corner tiles: exactly 2 walls (numpad 1/3/7/9) — check first so they take precedence + if w_down and w_left and not w_up and not w_right: + return FALLOUT_CORNER_INNER_UP_RIGHT # numpad 1: down+left + if w_down and w_right and not w_up and not w_left: + return FALLOUT_CORNER_INNER_UP_LEFT # numpad 3: down+right + if w_up and w_left and not w_down and not w_right: + return FALLOUT_CORNER_INNER_DOWN_RIGHT # numpad 7: up+left + if w_up and w_right and not w_down and not w_left: + return FALLOUT_CORNER_INNER_DOWN_LEFT # numpad 9: up+right + # Inner two-wall (wall up+left, up+right, down+right, down+left) — 3 or 4 walls + if w_up and w_left: + return FALLOUT_INNER_UP_LEFT + if w_up and w_right: + return FALLOUT_INNER_UP_RIGHT + if w_down and w_right: + return FALLOUT_INNER_DOWN_RIGHT + if w_down and w_left: + return FALLOUT_INNER_DOWN_LEFT + # Single wall (edge only) + if w_up and not w_right and not w_down and not w_left: + return FALLOUT_INNER_UP + if w_right and not w_up and not w_down and not w_left: + return FALLOUT_INNER_RIGHT + if w_down and not w_up and not w_left and not w_right: + return FALLOUT_INNER_DOWN + if w_left and not w_up and not w_right and not w_down: + return FALLOUT_INNER_LEFT + return FALLOUT_CENTER + +func _pick_decorated_tile(rng: RandomNumberGenerator) -> Vector2i: + # (16,16) = most common (plain/non-decorated); then (1,15)-(21,15), (0,16)-(14,16) + if rng.randf() < 0.5: + return FLOOR_DECORATED_PLAIN + var roll = rng.randi() % 36 # 21 + 15 = 36 other options + if roll < 21: + return FLOOR_DECORATED_RANGE_1_START + Vector2i(roll, 0) + return FLOOR_DECORATED_RANGE_2_START + Vector2i(roll - 21, 0) + +func _fill_decorated_tile_grid(grid: Array, decorated_tile_grid: Array, map_size: Vector2i, rng: RandomNumberGenerator): + for x in range(map_size.x): + for y in range(map_size.y): + if grid[x][y] == 1 or grid[x][y] == 3: # Floor or corridor only (not doors) + decorated_tile_grid[x][y] = _pick_decorated_tile(rng) + +func _is_tile_in_room_interior(tx: int, ty: int, room: Dictionary) -> bool: + # Room interior excludes 2-tile walls: room.x+2..room.x+room.w-2, room.y+2..room.y+room.h-2 + if room.is_empty(): + return false + return tx >= room.x + 2 and tx < room.x + room.w - 2 and ty >= room.y + 2 and ty < room.y + room.h - 2 + +func _fill_cracked_tile_grid(grid: Array, cracked_tile_grid: Array, map_size: Vector2i, all_doors: Array, start_room: Dictionary, rng: RandomNumberGenerator): + const CHANCE = 0.05 # Not so common + for x in range(map_size.x): + for y in range(map_size.y): + if grid[x][y] != 1: + continue # Only floor (never corridors, doors) + if _is_tile_blocked_for_fallout(x, y, grid, map_size, all_doors): + continue + if not start_room.is_empty() and _is_tile_in_room_interior(x, y, start_room): + continue # No cracked tiles in start room + if rng.randf() >= CHANCE: + continue + cracked_tile_grid[x][y] = true + +func _is_tile_blocked_for_fallout(x: int, y: int, _grid: Array, _map_size: Vector2i, all_doors: Array) -> bool: + # True if (x,y) must NOT get a fallout tile: inside a door (either side), or directly in front of a door + # Each connection has TWO door frames (room1 side and room2 side); we must exclude both + for door in all_doors: + var dx: int = door.x + var dy: int = door.y + var room2 = door.get("room2", null) + if door.get("dir", "E") == "E": + # Horizontal: door frame is 2 wide, 3 tall + # Room1 (left) door: (dx, dy) to (dx+2, dy+3) + if x >= dx and x < dx + 2 and y >= dy and y < dy + 3: + return true + # Room2 (right) door: (room2.x, dy) to (room2.x+2, dy+3) + if room2 is Dictionary: + var r2x: int = room2.x + if x >= r2x and x < r2x + 2 and y >= dy and y < dy + 3: + return true + # Directly in front of door (one tile into room from door) + if x == dx - 1 and y >= dy and y < dy + 3: + return true + if x == dx + 2 and y >= dy and y < dy + 3: + return true + if room2 is Dictionary: + var r2x: int = room2.x + if x == r2x - 1 and y >= dy and y < dy + 3: + return true + if x == r2x + 2 and y >= dy and y < dy + 3: + return true + else: + # Vertical (S): door frame is 3 wide, 2 tall + # Room1 (top) door: (dx, dy) to (dx+3, dy+2) + if x >= dx and x < dx + 3 and y >= dy and y < dy + 2: + return true + # Room2 (bottom) door: (dx, room2.y) to (dx+3, room2.y+2) + if room2 is Dictionary: + var r2y: int = room2.y + if x >= dx and x < dx + 3 and y >= r2y and y < r2y + 2: + return true + if x >= dx and x < dx + 3 and y == dy - 1: + return true + if x >= dx and x < dx + 3 and y == dy + 2: + return true + if room2 is Dictionary: + var r2y: int = room2.y + if x >= dx and x < dx + 3 and y == r2y - 1: + return true + if x >= dx and x < dx + 3 and y == r2y + 2: + return true + return false + +func _render_fallout_tiles(grid: Array, tile_grid: Array, map_size: Vector2i, all_doors: Array, start_room: Dictionary, rng: RandomNumberGenerator): + # Replace a small fraction of floor tiles with fallout; never in doors, corridors, in front of doors, or in start room + const CHANCE = 0.08 + for x in range(map_size.x): + for y in range(map_size.y): + if grid[x][y] != 1: + continue # Only floor (corridors are 3, door frame is 2) + if _is_tile_blocked_for_fallout(x, y, grid, map_size, all_doors): + continue + if not start_room.is_empty() and _is_tile_in_room_interior(x, y, start_room): + continue # No fallout tiles in start room + if rng.randf() >= CHANCE: + continue + tile_grid[x][y] = _get_fallout_tile_for_floor(grid, map_size, x, y) + +func _is_fallout_atlas(tile: Vector2i) -> bool: + return tile == FALLOUT_CENTER \ + or tile == FALLOUT_INNER_UP_LEFT or tile == FALLOUT_INNER_UP or tile == FALLOUT_INNER_UP_RIGHT \ + or tile == FALLOUT_INNER_RIGHT or tile == FALLOUT_INNER_DOWN_RIGHT or tile == FALLOUT_INNER_DOWN or tile == FALLOUT_INNER_DOWN_LEFT or tile == FALLOUT_INNER_LEFT \ + or tile == FALLOUT_CORNER_INNER_UP_LEFT or tile == FALLOUT_CORNER_INNER_UP_RIGHT or tile == FALLOUT_CORNER_INNER_DOWN_LEFT or tile == FALLOUT_CORNER_INNER_DOWN_RIGHT + +func _clear_decorated_on_fallout(tile_grid: Array, decorated_tile_grid: Array, map_size: Vector2i): + for x in range(map_size.x): + for y in range(map_size.y): + if _is_fallout_atlas(tile_grid[x][y]): + decorated_tile_grid[x][y] = null + func _place_enemies_in_room(room: Dictionary, grid: Array, map_size: Vector2i, rng: RandomNumberGenerator, level: int = 1) -> Array: # Place enemies in a room, scaled by level # Level 1: 0-2 enemies per room (fewer) @@ -1857,7 +2096,7 @@ func _force_place_stairs(exit_room: Dictionary, grid: Array, tile_grid: Array, m LogManager.log("DungeonGenerator: Force placed " + str(stairs_dir) + " stairs at tile (" + str(stairs_data.x) + "," + str(stairs_data.y) + ") world pos: " + str(stairs_data.world_pos), LogManager.CATEGORY_DUNGEON) return stairs_data -func _place_interactable_objects_in_room(room: Dictionary, grid: Array, map_size: Vector2i, all_doors: Array, all_enemies: Array, rng: RandomNumberGenerator, room_puzzle_data: Dictionary = {}) -> Array: +func _place_interactable_objects_in_room(room: Dictionary, grid: Array, tile_grid: Array, map_size: Vector2i, all_doors: Array, all_enemies: Array, rng: RandomNumberGenerator, room_puzzle_data: Dictionary = {}) -> Array: # Place interactable objects in a room # Small rooms (7-8 tiles): 0-1 objects # Medium rooms (9-10 tiles): 0-3 objects @@ -2034,6 +2273,17 @@ func _place_interactable_objects_in_room(room: Dictionary, grid: Array, map_size "room": room }) + # If an interactable spawns on a fallout tile, replace that tile with normal floor + for obj in objects: + if obj.has("position"): + var pos = obj.position + var tx = int(pos.x / tile_size) + var ty = int(pos.y / tile_size) + if tx >= 0 and tx < map_size.x and ty >= 0 and ty < map_size.y: + if _is_fallout_atlas(tile_grid[tx][ty]): + tile_grid[tx][ty] = FLOOR_BASE + LogManager.log("DungeonGenerator: Replaced fallout with normal floor at interactable spawn (" + str(tx) + "," + str(ty) + ")", LogManager.CATEGORY_DUNGEON) + return objects func _is_valid_interactable_position(world_pos: Vector2, all_doors: Array, all_enemies: Array, room: Dictionary) -> bool: @@ -2837,7 +3087,7 @@ func _place_traps_in_dungeon(all_rooms: Array, start_room_index: int, exit_room_ while attempts > 0 and not trap_placed: # Random position in room floor (excluding 2-tile wall border, plus extra safety margin) - var floor_margin = 3 # Extra margin from walls for safety + var floor_margin = 3 # Extra margin from walls for safety var local_x = rng.randi_range(floor_margin, room.w - floor_margin - 1) var local_y = rng.randi_range(floor_margin, room.h - floor_margin - 1) var world_x = room.x + local_x @@ -2845,7 +3095,7 @@ func _place_traps_in_dungeon(all_rooms: Array, start_room_index: int, exit_room_ # Check if position is valid (floor tile, not blocked) if world_x >= 0 and world_x < map_size.x and world_y >= 0 and world_y < map_size.y: - if grid[world_x][world_y] == 1: # Floor tile + if grid[world_x][world_y] == 1: # Floor tile # Check if position is not too close to door (avoid blocking doorways) var too_close_to_door = false # Simplified check - just ensure we're not right at door position diff --git a/src/scripts/enemy_base.gd b/src/scripts/enemy_base.gd index ec2823b..e159ed6 100644 --- a/src/scripts/enemy_base.gd +++ b/src/scripts/enemy_base.gd @@ -33,6 +33,15 @@ var burn_damage_timer: float = 0.0 # Timer for burn damage ticks var position_z: float = 0.0 var velocity_z: float = 0.0 +# Fallout (quicksand): humanoids sink like player then die; slimes rotate 45 + scale then die +var fallout_state: bool = false +var fallout_scale_progress: float = 1.0 +var fallout_defeat_started: bool = false # Slime-like: simple scale/rotate then die +var died_from_fallout: bool = false # True when _die() was triggered by fallout (delay loot 0.3s) +const FALLOUT_SINK_DURATION: float = 0.5 +const FALLOUT_CENTER_THRESHOLD: float = 2.0 +const FALLOUT_LOOT_DELAY: float = 0.3 # Seconds after fallout death before spawning loot + # Animation enum Direction {DOWN = 0, LEFT = 1, RIGHT = 2, UP = 3, DOWN_LEFT = 4, DOWN_RIGHT = 5, UP_LEFT = 6, UP_RIGHT = 7} var current_direction: Direction = Direction.DOWN @@ -118,6 +127,71 @@ func _physics_process(delta): burn_sprite.set_meta("burn_animation_timer", anim_timer) return + # Fallout: humanoid sinks like player (FALL anim) then dies; slime rotates 45 + scale then dies + if fallout_state: + velocity = Vector2.ZERO + fallout_scale_progress -= delta / FALLOUT_SINK_DURATION + if fallout_scale_progress <= 0.0: + died_from_fallout = true + call_deferred("_die") + return + scale = Vector2.ONE * max(0.0, fallout_scale_progress) + if has_method("_set_animation"): + _set_animation("FALL") + move_and_slide() + return + if fallout_defeat_started: + velocity = Vector2.ZERO + fallout_scale_progress -= delta / FALLOUT_SINK_DURATION + if fallout_scale_progress <= 0.0: + died_from_fallout = true + call_deferred("_die") + return + scale = Vector2.ONE * max(0.0, fallout_scale_progress) + rotation = deg_to_rad(45.0) + move_and_slide() + return + + # Only ground enemies (position_z <= 0) can fall into fallout; bat has position_z 1 and ignores it + # Humanoid: use 16x16 box check (like player) so any part on fallout triggers; drag toward center then sink + var gw = get_tree().get_first_node_in_group("game_world") + var on_fallout = false + if position_z <= 0.0 and gw: + if "humanoid_type" in self and gw.has_method("_is_player_box_on_fallout_tile"): + on_fallout = gw._is_player_box_on_fallout_tile(global_position, 8.0) + elif gw.has_method("_is_position_on_fallout_tile"): + on_fallout = gw._is_position_on_fallout_tile(global_position) + if on_fallout: + if "humanoid_type" in self: + # Humanoid: drag toward tile center (quicksand pull) then sink when at center + var tile_center = gw._get_closest_fallout_tile_center(global_position) if gw.has_method("_get_closest_fallout_tile_center") else (gw._get_tile_center_at(global_position) if gw.has_method("_get_tile_center_at") else global_position) + var dist_to_center = global_position.distance_to(tile_center) + if dist_to_center < FALLOUT_CENTER_THRESHOLD: + global_position = tile_center + fallout_state = true + fallout_scale_progress = 1.0 + velocity = Vector2.ZERO + if has_method("_set_animation"): + _set_animation("FALL") + if has_node("SfxFallout"): + $SfxFallout.play() + else: + # Drag toward center (quicksand pull) + var dir = (tile_center - global_position).normalized() + const FALLOUT_DRAG_STRENGTH: float = 820.0 + velocity = dir * FALLOUT_DRAG_STRENGTH * get_process_delta_time() + if has_method("_set_animation"): + _set_animation("RUN") + move_and_slide() + return + else: + # Slime-like: rotate 45 and scale down then die + fallout_defeat_started = true + fallout_scale_progress = 1.0 + rotation = deg_to_rad(45.0) + move_and_slide() + return + # Update attack timer if attack_timer > 0: attack_timer -= delta @@ -137,6 +211,14 @@ func _physics_process(delta): if not is_knocked_back: _ai_behavior(delta) + # Slime, rat, humanoid: try to avoid stepping onto fallout (position_z <= 0 = ground enemies only; bat has position_z 1) + if position_z <= 0.0 and not fallout_state and not fallout_defeat_started and velocity.length_squared() > 1.0: + var game_world = get_tree().get_first_node_in_group("game_world") + if game_world and game_world.has_method("_is_position_on_fallout_tile"): + var step = velocity.normalized() * 18.0 + if game_world._is_position_on_fallout_tile(global_position + step): + velocity = Vector2.ZERO + # Move move_and_slide() @@ -752,8 +834,14 @@ func _die(): if game_world and game_world.has_method("_sync_exp_text_at_position") and multiplayer.has_multiplayer_peer(): game_world._sync_exp_text_at_position.rpc(exp_per_player, global_position) - # Spawn loot immediately (before death animation) - _spawn_loot() + # Spawn loot (immediately, or after 0.3s if died from fallout so it appears after sink) + if died_from_fallout: + get_tree().create_timer(FALLOUT_LOOT_DELAY).timeout.connect(func(): + if is_instance_valid(self): + _spawn_loot() + ) + else: + _spawn_loot() # Sync death to all clients (only server sends RPC) # Use game_world to route death sync instead of direct RPC to avoid node path issues @@ -932,10 +1020,11 @@ func _spawn_loot(): ItemLootHelper.spawn_item_loot(item, safe_spawn_pos, entities_node, game_world) LogManager.log(str(name) + " ✓ dropped item #" + str(i + 1) + ": " + str(item.item_name) + " at " + str(safe_spawn_pos) + " (LCK boost: " + str(item_rarity_boost) + ")", LogManager.CATEGORY_ENEMY) else: - # Spawn regular loot (coin or food) + # Spawn regular loot (coin or food) - start at position_z 1 to avoid falling into fallout var loot = loot_scene.instantiate() entities_node.add_child(loot) loot.global_position = safe_spawn_pos + loot.position_z = 1.0 if "position_z" in loot else 0.0 loot.loot_type = loot_type # Set initial velocity before _ready() processes loot.velocity = initial_velocity diff --git a/src/scripts/enemy_bat.gd b/src/scripts/enemy_bat.gd index ae5ba85..5901b84 100644 --- a/src/scripts/enemy_bat.gd +++ b/src/scripts/enemy_bat.gd @@ -28,6 +28,9 @@ func _ready(): state_timer = idle_duration + # Bats fly: permanent position_z 1 so they ignore fallout tiles + position_z = 1.0 + # CRITICAL: Ensure collision mask is set correctly (walls are on layer 7 = bit 6 = 64) collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64) diff --git a/src/scripts/enemy_hand.gd b/src/scripts/enemy_hand.gd index fd20e47..743bfa8 100644 --- a/src/scripts/enemy_hand.gd +++ b/src/scripts/enemy_hand.gd @@ -34,6 +34,8 @@ func _ready() -> void: move_speed = 16.8 # 60% of 28.0 - slower chase/random movement damage = SNATCH_DAMAGE exp_reward = 8.0 + # Enemy hand is a special enemy: always at position_z 1 so it never falls into fallout + position_z = 1.0 collision_layer = 2 collision_mask = 1 | 2 | 64 diff --git a/src/scripts/enemy_humanoid.gd b/src/scripts/enemy_humanoid.gd index 5c100b4..7fceb48 100644 --- a/src/scripts/enemy_humanoid.gd +++ b/src/scripts/enemy_humanoid.gd @@ -173,6 +173,12 @@ const ANIMATIONS = { "loop": false, "nextAnimation": null }, + "FALL": { + "frames": [21], + "frameDurations": [500], + "loop": true, + "nextAnimation": null + }, "IDLE_HOLD": { "frames": [25], "frameDurations": [500], @@ -246,8 +252,8 @@ func _ready(): seed_value = hash(str(spawn_position) + str(humanoid_type) + str(random_component)) LogManager.log(str(name) + " appearance seed (randomized): " + str(seed_value) + " at spawn position: " + str(spawn_position) + " type: " + str(humanoid_type), LogManager.CATEGORY_ENEMY) else: - # Deterministic based on position and type only - seed_value = hash(str(spawn_position) + str(humanoid_type)) + # Deterministic based on position, type, and name so each enemy has unique look (name differs per spawn index) + seed_value = hash(str(spawn_position) + str(humanoid_type) + str(name)) LogManager.log(str(name) + " appearance seed (deterministic): " + str(seed_value) + " at spawn position: " + str(spawn_position) + " type: " + str(humanoid_type), LogManager.CATEGORY_ENEMY) appearance_rng.seed = seed_value @@ -747,8 +753,8 @@ func _load_random_headgear(): # Available headgears organized by category (using actual files found) var headgear_categories = { "": ["Headband.png"], # Direct files in Layer 6 - Headgears - "Basic Assasin": [ - "AssasinBandanaBlack.png", "StalkerHoodBlack.png", "ThiefBandanaGreen.png" + "Basic Assassin": [ + "AssassinBandanaBlack.png", "StalkerHoodBlack.png", "ThiefBandanaGreen.png" ], "Basic Mage": [ "EsperHatBlue.png", "HighMageHatCyan.png", "MageHatRed.png", "SorcererHoodCyan.png" @@ -1138,6 +1144,15 @@ func _ai_behavior(delta): else: lost_target_timer = 0.0 +func _would_move_into_fallout(move_dir: Vector2, step: float = 24.0) -> bool: + if move_dir.length_squared() < 0.01: + return false + var gw = get_tree().get_first_node_in_group("game_world") + if not gw or not gw.has_method("_is_position_on_fallout_tile"): + return false + var check_pos = global_position + move_dir.normalized() * step + return gw._is_position_on_fallout_tile(check_pos) + func _idle_behavior(_delta): velocity = Vector2.ZERO @@ -1166,13 +1181,17 @@ func _idle_behavior(_delta): func _wandering_behavior(_delta): # Patrolling at slower pace - # Pick a random direction at the start of wandering + # Pick a random direction at the start of wandering (avoid fallout) if state_timer >= 1.9: # Pick direction at start var random_angle = randf() * PI * 2 var random_dir = Vector2(cos(random_angle), sin(random_angle)) - velocity = random_dir * move_speed * patrol_speed_multiplier # Slower when patrolling + if _would_move_into_fallout(random_dir): + velocity = Vector2.ZERO + _set_animation("IDLE") + else: + velocity = random_dir * move_speed * patrol_speed_multiplier # Slower when patrolling + _set_animation("RUN") current_direction = _get_direction_from_vector(random_dir) - _set_animation("RUN") # Check if player enters vision while patrolling if target_player and _is_player_in_vision(target_player): @@ -1315,6 +1334,12 @@ func _chasing_behavior(delta_arg): var desired_distance = 45.0 + # Avoid walking into fallout (quicksand) + if _would_move_into_fallout(to_player): + velocity = Vector2.ZERO + current_direction = _get_direction_from_vector(to_player) + return + # Apply speed multiplier if blocking var speed_mult = 1.0 if is_blocking: diff --git a/src/scripts/floor_switch.gd b/src/scripts/floor_switch.gd index 1d1013c..ba7701a 100644 --- a/src/scripts/floor_switch.gd +++ b/src/scripts/floor_switch.gd @@ -2,28 +2,28 @@ extends Area2D # Floor Switch - Activates when enough weight is placed on it -@export_enum("walk", "pillar") var switch_type: String = "walk" # "walk" = walk-on switch (weight 1), "pillar" = requires pillar (weight 5) -@export var required_weight: float = 1.0 # Required weight to activate (automatically set based on switch_type) +@export_enum("walk", "pillar") var switch_type: String = "walk" # "walk" = walk-on switch (weight 1), "pillar" = requires pillar (weight 5) +@export var required_weight: float = 1.0 # Required weight to activate (automatically set based on switch_type) var is_activated: bool = false var current_weight: float = 0.0 -var objects_on_switch: Array = [] # Track objects currently on the switch +var objects_on_switch: Array = [] # Track objects currently on the switch var tilemap_layer: TileMapLayer = null -var switch_tile_position: Vector2i = Vector2i.ZERO # Tile position in the tilemap -var check_timer: float = 0.0 # Timer for periodic checks (pillar switches only) -var check_interval: float = 0.2 # Check every 0.2 seconds +var switch_tile_position: Vector2i = Vector2i.ZERO # Tile position in the tilemap +var check_timer: float = 0.0 # Timer for periodic checks (pillar switches only) +var check_interval: float = 0.2 # Check every 0.2 seconds func _ready(): # Set required weight based on switch type if switch_type == "walk": - required_weight = 1.0 # Player weight only + required_weight = 1.0 # Player weight only elif switch_type == "pillar": - required_weight = 5.0 # Requires pillar (weight 5) + required_weight = 5.0 # Requires pillar (weight 5) # Set collision mask to detect players and objects collision_layer = 0 - collision_mask = 1 | 2 # Detect players (layer 1) and objects (layer 2) + collision_mask = 1 | 2 # Detect players (layer 1) and objects (layer 2) # Connect signals body_entered.connect(_on_body_entered) @@ -42,16 +42,15 @@ func _ready(): set_process(false) func _find_tilemap_layer(): - # Find tilemap layer to update switch visual + # Find TileMapLayerDecoratedGround to update switch visual (switches are drawn on decorated ground) var game_world = get_tree().get_first_node_in_group("game_world") if game_world: - if "dungeon_tilemap_layer" in game_world: - tilemap_layer = game_world.dungeon_tilemap_layer + if "dungeon_tilemap_layer_decorated" in game_world: + tilemap_layer = game_world.dungeon_tilemap_layer_decorated else: - # Try to find it in Environment node var environment = game_world.get_node_or_null("Environment") if environment: - tilemap_layer = environment.get_node_or_null("DungeonLayer0") + tilemap_layer = environment.get_node_or_null("TileMapLayerDecoratedGround") func _on_body_entered(body): # Object entered the switch @@ -67,7 +66,7 @@ func _on_body_entered(body): # Only count pillars that are placed (not being held) if object_type == "Pillar" and not is_being_held: var weight = _get_object_weight(body) - if weight >= required_weight: # Pillar must have weight >= 5.0 + if weight >= required_weight: # Pillar must have weight >= 5.0 $PressSwitch.play() objects_on_switch.append(body) current_weight += weight diff --git a/src/scripts/game_world.gd b/src/scripts/game_world.gd index 7e6d877..bd1ae19 100644 --- a/src/scripts/game_world.gd +++ b/src/scripts/game_world.gd @@ -88,6 +88,8 @@ var fog_debug_lines: Array = [] var dungeon_data: Dictionary = {} var dungeon_tilemap_layer: TileMapLayer = null var dungeon_tilemap_layer_above: TileMapLayer = null +var dungeon_tilemap_layer_decorated: TileMapLayer = null +var dungeon_tilemap_layer_cracked: TileMapLayer = null var current_level: int = 1 var dungeon_seed: int = 0 @@ -111,6 +113,28 @@ var clients_ready: Dictionary = {} # peer_id -> bool # Track dungeon syncs in progress (server only) - prevent multiple simultaneous syncs var dungeon_sync_in_progress: Dictionary = {} # peer_id -> bool +# Fallout tiles (quicksand): last safe tile center per player (for respawn after falling in) +var last_safe_position_by_player: Dictionary = {} # player node path or name -> Vector2 + +# Cracked floor: stand too long -> tile breaks and becomes fallout +var cracked_stand_timers: Dictionary = {} # "player_key|tx|ty" -> float (seconds on that tile) +const CRACKED_STAND_DURATION: float = 0.9 # Seconds standing on cracked tile before it breaks +const CRACKED_TILE_ATLAS: Vector2i = Vector2i(15, 16) +# Fallout tile atlas coords (for replacing floor when cracked tile breaks) - match dungeon_generator +const _FALLOUT_CENTER = Vector2i(10, 12) +const _FALLOUT_INNER_UP_LEFT = Vector2i(9, 11) +const _FALLOUT_INNER_UP = Vector2i(10, 11) +const _FALLOUT_INNER_UP_RIGHT = Vector2i(11, 11) +const _FALLOUT_INNER_RIGHT = Vector2i(11, 12) +const _FALLOUT_INNER_DOWN_RIGHT = Vector2i(11, 13) +const _FALLOUT_INNER_DOWN = Vector2i(10, 13) +const _FALLOUT_INNER_DOWN_LEFT = Vector2i(9, 13) +const _FALLOUT_INNER_LEFT = Vector2i(9, 12) +const _FALLOUT_CORNER_INNER_UP_LEFT = Vector2i(13, 10) +const _FALLOUT_CORNER_INNER_UP_RIGHT = Vector2i(16, 10) +const _FALLOUT_CORNER_INNER_DOWN_LEFT = Vector2i(13, 13) +const _FALLOUT_CORNER_INNER_DOWN_RIGHT = Vector2i(16, 13) + # Track chunk acknowledgments (server only) - for flow control var dungeon_chunk_acks: Dictionary = {} # peer_id -> {chunk_idx -> bool, next_chunk_to_send -> int, chunks_data -> Array} @@ -449,9 +473,12 @@ func _send_initial_client_sync_with_retry(peer_id: int, local_count: int, retry_ _push_existing_players_state_to_client(peer_id) ) - # Sync broken interactable objects to the new client - # Wait a bit after dungeon sync to ensure objects are spawned first + # Sync broken interactable objects to the new client (immediate + delayed retry so joiner always gets it after objects exist) call_deferred("_sync_broken_objects_to_client", peer_id) + get_tree().create_timer(2.0).timeout.connect(func(): + if is_inside_tree() and multiplayer.is_server(): + _sync_broken_objects_to_client(peer_id) + ) # Sync existing enemies (from spawners) to the new client # Wait a bit after dungeon sync to ensure spawners are spawned first @@ -1029,6 +1056,7 @@ func _sync_loot_spawn(spawn_position: Vector2, loot_type: int, initial_velocity: loot.set_meta("loot_id", loot_id) entities_node.add_child(loot) loot.global_position = spawn_position + loot.position_z = 1.0 if "position_z" in loot else 0.0 loot.loot_type = loot_type # Ensure key loot has a deterministic name for any legacy RPCs if loot_type == 4: # LootType.KEY @@ -1064,6 +1092,7 @@ func _sync_item_loot_spawn(spawn_position: Vector2, item_data: Dictionary, initi loot.set_meta("loot_id", loot_id) entities_node.add_child(loot) loot.global_position = spawn_position + loot.position_z = 1.0 if "position_z" in loot else 0.0 loot.loot_type = loot.LootType.ITEM loot.item = item # Set the item instance # Set initial velocity before _ready() processes @@ -1900,6 +1929,67 @@ func _process(delta): peer_cleanup_timer = 0.0 _cleanup_disconnected_peers() + # Cracked floor: only server (or single-player) checks stand time and breaks tiles. + # On server, check ALL players (host + joiners) so joiners can break cracked tiles too. + if dungeon_tilemap_layer_cracked and (multiplayer.is_server() or not multiplayer.has_multiplayer_peer()): + var players_to_check: Array = get_tree().get_nodes_in_group("player") if (multiplayer.is_server() and multiplayer.has_multiplayer_peer()) else (player_manager.get_local_players() if player_manager else []) + if players_to_check.is_empty() and player_manager: + players_to_check = player_manager.get_local_players() + for player in players_to_check: + if not is_instance_valid(player): + continue + var pos = player.global_position + if player.has_node("QuicksandArea"): + var qa = player.get_node("QuicksandArea") + if is_instance_valid(qa): + pos = qa.global_position + if not _is_position_on_cracked_tile(pos): + # Clear any timer for this player's other tiles + var to_remove = [] + var player_key = str(player.get_path()) if player.is_inside_tree() else str(player.name) + for k in cracked_stand_timers.keys(): + if k.begins_with(player_key + "|"): + to_remove.append(k) + for k in to_remove: + cracked_stand_timers.erase(k) + continue + var tile = _get_tile_coords_at_world(pos) + if tile.x < 0 or tile.y < 0: + continue + var key = (str(player.get_path()) if player.is_inside_tree() else str(player.name)) + "|" + str(tile.x) + "|" + str(tile.y) + cracked_stand_timers[key] = cracked_stand_timers.get(key, 0.0) + delta + if cracked_stand_timers[key] >= CRACKED_STAND_DURATION: + cracked_stand_timers.erase(key) + _break_cracked_tile(tile.x, tile.y) + # Slime, rat, humanoid: standing on cracked tiles can break them (they don't dodge cracked) + var enemies_to_check = get_tree().get_nodes_in_group("enemy") + for enemy in enemies_to_check: + if not is_instance_valid(enemy): + continue + if "is_dead" in enemy and enemy.is_dead: + continue + var ez = enemy.position_z if "position_z" in enemy else 0.0 + if ez > 0.0: + continue + var pos = enemy.global_position + if not _is_position_on_cracked_tile(pos): + var enemy_key = (str(enemy.get_path()) if enemy.is_inside_tree() else str(enemy.name)) + "|" + var to_remove = [] + for k in cracked_stand_timers.keys(): + if k.begins_with(enemy_key): + to_remove.append(k) + for k in to_remove: + cracked_stand_timers.erase(k) + continue + var tile = _get_tile_coords_at_world(pos) + if tile.x < 0 or tile.y < 0: + continue + var key = (str(enemy.get_path()) if enemy.is_inside_tree() else str(enemy.name)) + "|" + str(tile.x) + "|" + str(tile.y) + cracked_stand_timers[key] = cracked_stand_timers.get(key, 0.0) + delta + if cracked_stand_timers[key] >= CRACKED_STAND_DURATION: + cracked_stand_timers.erase(key) + _break_cracked_tile(tile.x, tile.y) + func _check_client_buffers(current_time: float): # Check all client buffers and mark which ones should be skipped if not multiplayer.multiplayer_peer is WebRTCMultiplayerPeer: @@ -2251,8 +2341,8 @@ func _update_mouse_cursor(delta: float): var mouse_direction = (target_world_pos - player_pos).normalized() - # Only update facing if mouse is far enough from player - if mouse_direction.length() > 0.1: + # Only update facing if mouse is far enough from player (lock to down during fallout) + if mouse_direction.length() > 0.1 and not (("fallout_state" in player) and player.fallout_state): player._update_facing_from_mouse(mouse_direction) else: # Mouse is outside window - disable mouse control (use WASD/movement for direction) @@ -2408,6 +2498,267 @@ func _is_walkable_tile(tile_center: Vector2) -> bool: var v = grid[tile_x][tile_y] return v == 1 or v == 2 or v == 3 +# Fallout (quicksand) tiles: CustomDataLayer "terrain" int value -1 +const FALLOUT_TERRAIN_VALUE: int = -1 + +func _is_position_on_fallout_tile(world_pos: Vector2) -> bool: + if not dungeon_tilemap_layer: + return false + var tile_pos = dungeon_tilemap_layer.local_to_map(world_pos - dungeon_tilemap_layer.global_position) + var td = dungeon_tilemap_layer.get_cell_tile_data(tile_pos) + if not td: + return false + if not td.get_custom_data("terrain") is int: + return false + return td.get_custom_data("terrain") == FALLOUT_TERRAIN_VALUE + +func _is_player_box_on_fallout_tile(player_center: Vector2, box_half_size: float = 8.0) -> bool: + # True if any part of the player's 16x16 box (center ± 8) is on a fallout tile (Link's Awakening style) + var corners = [ + player_center, + player_center + Vector2(-box_half_size, -box_half_size), + player_center + Vector2(box_half_size, -box_half_size), + player_center + Vector2(-box_half_size, box_half_size), + player_center + Vector2(box_half_size, box_half_size), + ] + for p in corners: + if _is_position_on_fallout_tile(p): + return true + return false + +func _get_tile_center_at(world_pos: Vector2) -> Vector2: + if not dungeon_tilemap_layer: + return world_pos + var tile_pos = dungeon_tilemap_layer.local_to_map(world_pos - dungeon_tilemap_layer.global_position) + return dungeon_tilemap_layer.map_to_local(tile_pos) + dungeon_tilemap_layer.global_position + +func _get_closest_fallout_tile_center(world_pos: Vector2) -> Vector2: + if not dungeon_tilemap_layer: + return world_pos + var center_tile = dungeon_tilemap_layer.local_to_map(world_pos - dungeon_tilemap_layer.global_position) + var best_center: Vector2 = world_pos + var best_dist: float = 1e9 + var search_radius = 3 + for dx in range(-search_radius, search_radius + 1): + for dy in range(-search_radius, search_radius + 1): + var t = center_tile + Vector2i(dx, dy) + var tile_center = dungeon_tilemap_layer.map_to_local(t) + dungeon_tilemap_layer.global_position + var td = dungeon_tilemap_layer.get_cell_tile_data(t) + if not td: + continue + if not td.get_custom_data("terrain") is int: + continue + if td.get_custom_data("terrain") != FALLOUT_TERRAIN_VALUE: + continue + var d = world_pos.distance_squared_to(tile_center) + if d < best_dist: + best_dist = d + best_center = tile_center + return best_center + +# Cracked floor: terrain -2 on TileMapLayerCrackedGround +func _is_position_on_cracked_tile(world_pos: Vector2) -> bool: + if not dungeon_tilemap_layer_cracked: + return false + var tile_pos = dungeon_tilemap_layer_cracked.local_to_map(world_pos - dungeon_tilemap_layer_cracked.global_position) + var td = dungeon_tilemap_layer_cracked.get_cell_tile_data(tile_pos) + if not td: + return false + if not td.get_custom_data("terrain") is int: + return false + return td.get_custom_data("terrain") == -2 + +func _get_tile_coords_at_world(world_pos: Vector2) -> Vector2i: + if not dungeon_tilemap_layer: + return Vector2i(-1, -1) + return dungeon_tilemap_layer.local_to_map(world_pos - dungeon_tilemap_layer.global_position) + +func _is_wall_at_tile(tx: int, ty: int) -> bool: + if dungeon_data.is_empty() or not dungeon_data.has("grid") or not dungeon_data.has("map_size"): + return false + var grid = dungeon_data.grid + var map_size: Vector2i = dungeon_data.map_size + if tx < 0 or tx >= map_size.x or ty < 0 or ty >= map_size.y: + return false + var v = grid[tx][ty] + return v == 0 or v == 2 + +func _get_fallout_tile_for_floor_at(tx: int, ty: int) -> Vector2i: + var w_up = _is_wall_at_tile(tx, ty - 1) + var w_right = _is_wall_at_tile(tx + 1, ty) + var w_down = _is_wall_at_tile(tx, ty + 1) + var w_left = _is_wall_at_tile(tx - 1, ty) + if w_down and w_left and not w_up and not w_right: + return _FALLOUT_CORNER_INNER_UP_RIGHT + if w_down and w_right and not w_up and not w_left: + return _FALLOUT_CORNER_INNER_UP_LEFT + if w_up and w_left and not w_down and not w_right: + return _FALLOUT_CORNER_INNER_DOWN_RIGHT + if w_up and w_right and not w_down and not w_left: + return _FALLOUT_CORNER_INNER_DOWN_LEFT + if w_up and w_left: + return _FALLOUT_INNER_UP_LEFT + if w_up and w_right: + return _FALLOUT_INNER_UP_RIGHT + if w_down and w_right: + return _FALLOUT_INNER_DOWN_RIGHT + if w_down and w_left: + return _FALLOUT_INNER_DOWN_LEFT + if w_up and not w_right and not w_down and not w_left: + return _FALLOUT_INNER_UP + if w_right and not w_up and not w_down and not w_left: + return _FALLOUT_INNER_RIGHT + if w_down and not w_up and not w_left and not w_right: + return _FALLOUT_INNER_DOWN + if w_left and not w_up and not w_right and not w_down: + return _FALLOUT_INNER_LEFT + return _FALLOUT_CENTER + +func _play_whoosh_at(world_pos: Vector2) -> void: + var whoosh = load("res://assets/audio/sfx/wizard/animevox/whoosh_1769364646131.wav") as AudioStream + if not whoosh: + return + var player = AudioStreamPlayer2D.new() + player.stream = whoosh + player.global_position = world_pos + player.bus = "Sfx" + add_child(player) + player.play() + player.finished.connect(player.queue_free) + +func break_cracked_tiles_in_radius(world_center: Vector2, radius: float) -> void: + # Break any cracked tiles inside the given world-space circle. Only server performs the break. + # Clients (e.g. joiner's bomb) request the server to do it via RPC. + if not dungeon_tilemap_layer or not dungeon_tilemap_layer_cracked: + return + if multiplayer.has_multiplayer_peer() and not multiplayer.is_server(): + _request_break_cracked_tiles_in_radius.rpc_id(1, world_center, radius) + return + var center_tile = _get_tile_coords_at_world(world_center) + if center_tile.x < 0 or center_tile.y < 0: + return + var tile_radius = int(ceil(radius / 16.0)) + 1 + for dx in range(-tile_radius, tile_radius + 1): + for dy in range(-tile_radius, tile_radius + 1): + var tx = center_tile.x + dx + var ty = center_tile.y + dy + var tile_center = dungeon_tilemap_layer.map_to_local(Vector2i(tx, ty)) + dungeon_tilemap_layer.global_position + if world_center.distance_to(tile_center) > radius: + continue + if not _is_position_on_cracked_tile(tile_center): + continue + _break_cracked_tile(tx, ty) + +@rpc("any_peer", "reliable") +func _request_break_cracked_tiles_in_radius(world_center: Vector2, radius: float) -> void: + if not multiplayer.is_server(): + return + break_cracked_tiles_in_radius(world_center, radius) + +func _break_cracked_tile(tile_x: int, tile_y: int) -> void: + if not dungeon_tilemap_layer or dungeon_data.is_empty(): + return + # Replace floor with fallout on main layer + var fallout_tile = _get_fallout_tile_for_floor_at(tile_x, tile_y) + dungeon_tilemap_layer.set_cell(Vector2i(tile_x, tile_y), 0, fallout_tile) + if dungeon_tilemap_layer_decorated: + dungeon_tilemap_layer_decorated.erase_cell(Vector2i(tile_x, tile_y)) + if dungeon_tilemap_layer_cracked: + dungeon_tilemap_layer_cracked.erase_cell(Vector2i(tile_x, tile_y)) + var tile_center = dungeon_tilemap_layer.map_to_local(Vector2i(tile_x, tile_y)) + dungeon_tilemap_layer.global_position + _play_whoosh_at(tile_center) + # Update dungeon_data so re-packed blob for joiners has correct floor (no separate broken list needed) + if multiplayer.is_server() and not dungeon_data.is_empty(): + if dungeon_data.has("tile_grid") and tile_x >= 0 and tile_y >= 0: + var tg = dungeon_data.tile_grid + if tile_x < tg.size() and tile_y < tg[tile_x].size(): + tg[tile_x][tile_y] = fallout_tile + if dungeon_data.has("decorated_tile_grid"): + var dg = dungeon_data.decorated_tile_grid + if tile_x < dg.size() and tile_y < dg[tile_x].size(): + dg[tile_x][tile_y] = null + if dungeon_data.has("cracked_tile_grid"): + var cg = dungeon_data.cracked_tile_grid + if tile_x < cg.size() and tile_y < cg[tile_x].size(): + cg[tile_x][tile_y] = false + if multiplayer.is_server() and multiplayer.has_multiplayer_peer(): + _sync_cracked_tile_broke.rpc(tile_x, tile_y, fallout_tile.x, fallout_tile.y) + +@rpc("authority", "reliable", "call_remote") +func _sync_cracked_tile_broke(tile_x: int, tile_y: int, fallout_atlas_x: int, fallout_atlas_y: int) -> void: + if multiplayer.is_server(): + return + var fallout_tile = Vector2i(fallout_atlas_x, fallout_atlas_y) + if dungeon_tilemap_layer: + dungeon_tilemap_layer.set_cell(Vector2i(tile_x, tile_y), 0, fallout_tile) + if dungeon_tilemap_layer_decorated: + dungeon_tilemap_layer_decorated.erase_cell(Vector2i(tile_x, tile_y)) + if dungeon_tilemap_layer_cracked: + dungeon_tilemap_layer_cracked.erase_cell(Vector2i(tile_x, tile_y)) + var tile_center = dungeon_tilemap_layer.map_to_local(Vector2i(tile_x, tile_y)) + dungeon_tilemap_layer.global_position if dungeon_tilemap_layer else Vector2(tile_x * 16 + 8, tile_y * 16 + 8) + _play_whoosh_at(tile_center) + +func update_last_safe_position_for_player(player: Node, world_pos: Vector2) -> void: + # Only store when position is on a non-fallout tile so we never remember a pit as safe + if _is_position_on_fallout_tile(world_pos): + return + var key: String = str(player.get_path()) if player.is_inside_tree() else str(player.name) + last_safe_position_by_player[key] = _get_tile_center_at(world_pos) + +func _get_nearest_safe_tile_center(from_world_pos: Vector2) -> Vector2: + # Search outward from the tile at from_world_pos; return nearest safe tile (walkable, not fallout). + # Prioritize previously visited (explored) tiles so we never place the player in a wall or in unseen area. + if not dungeon_tilemap_layer or dungeon_data.is_empty() or not dungeon_data.has("map_size"): + return from_world_pos + var map_size: Vector2i = dungeon_data.map_size + var center_tile = dungeon_tilemap_layer.local_to_map(from_world_pos - dungeon_tilemap_layer.global_position) + # Pass 1: prefer explored tiles that are walkable and not fallout + if not explored_map.is_empty(): + for r in range(0, 10): + for dx in range(-r, r + 1): + for dy in range(-r, r + 1): + if abs(dx) != r and abs(dy) != r: + continue + var t = center_tile + Vector2i(dx, dy) + if t.x < 0 or t.x >= map_size.x or t.y < 0 or t.y >= map_size.y: + continue + var tile_center = dungeon_tilemap_layer.map_to_local(t) + dungeon_tilemap_layer.global_position + if not _is_walkable_tile(tile_center): + continue + if _is_position_on_fallout_tile(tile_center): + continue + var idx = t.x + t.y * map_size.x + if idx >= 0 and idx < explored_map.size() and explored_map[idx] == 1: + return tile_center + # Pass 2: any walkable, non-fallout tile (never return a wall) + for r in range(0, 10): + for dx in range(-r, r + 1): + for dy in range(-r, r + 1): + if abs(dx) != r and abs(dy) != r: + continue + var t = center_tile + Vector2i(dx, dy) + if t.x < 0 or t.x >= map_size.x or t.y < 0 or t.y >= map_size.y: + continue + var tile_center = dungeon_tilemap_layer.map_to_local(t) + dungeon_tilemap_layer.global_position + if not _is_walkable_tile(tile_center): + continue + if _is_position_on_fallout_tile(tile_center): + continue + return tile_center + return from_world_pos + +func get_last_safe_position_for_player(player: Node) -> Vector2: + var key: String = str(player.get_path()) if player.is_inside_tree() else str(player.name) + var stored: Vector2 + if last_safe_position_by_player.has(key): + stored = last_safe_position_by_player[key] + else: + stored = player.global_position + # If stored position is on a fallout tile (e.g. never updated or room changed), use nearest safe tile + if _is_position_on_fallout_tile(stored): + return _get_nearest_safe_tile_center(stored) + return stored + func _get_adjacent_valid_spell_tile_centers(center_world_pos: Vector2, _player_pos: Vector2) -> Array: var out: Array = [] if not dungeon_tilemap_layer or dungeon_data.is_empty() or not dungeon_data.has("grid"): @@ -3151,7 +3502,7 @@ func _generate_dungeon(): LogManager.log("GameWorld: Dungeon generation completed successfully", LogManager.CATEGORY_DUNGEON) -# Dungeon shader color replacement: 13 original colors (wall x6, ground x5, fallout x2) +# Dungeon shader color replacement: 14 original colors (wall x6, ground x5, fallout x3) const _DUNGEON_ORIGINALS: Array = [ Color(24 / 255.0, 59 / 255.0, 255 / 255.0), # 0 wall Color(33 / 255.0, 50 / 255.0, 195 / 255.0), # 1 wall @@ -3164,8 +3515,9 @@ const _DUNGEON_ORIGINALS: Array = [ Color(48 / 255.0, 38 / 255.0, 20 / 255.0), # 8 ground Color(143 / 255.0, 71 / 255.0, 112 / 255.0), # 9 ground Color(106 / 255.0, 62 / 255.0, 57 / 255.0), # 10 ground - Color(69 / 255.0, 42 / 255.0, 31 / 255.0), # 11 fallout - Color(53 / 255.0, 46 / 255.0, 26 / 255.0), # 12 fallout + Color(109 / 255.0, 33 / 255.0, 24 / 255.0), # 11 fallout + Color(62 / 255.0, 29 / 255.0, 15 / 255.0), # 12 fallout + Color(2 / 255.0, 0 / 255.0, 4 / 255.0), # 13 fallout (near-black) ] # Original wall slots ordered light→dark (by luminance). Tile art uses these for highlight/mid/shadow. @@ -3209,7 +3561,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.78, 0.48, 0.24), Color(0.84, 0.54, 0.30), Color(0.58, 0.36, 0.16), Color(0.72, 0.44, 0.22), Color(0.66, 0.40, 0.20), Color(0.38, 0.30, 0.22), - Color(0.32, 0.28, 0.20), + Color(0.32, 0.28, 0.20), Color(0.28, 0.24, 0.18), ] 1: # 2️⃣ Crimson Void (blood / corruption / danger) walls = [ @@ -3219,7 +3571,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.22, 0.58, 0.62), Color(0.28, 0.64, 0.66), Color(0.18, 0.48, 0.52), Color(0.20, 0.54, 0.58), Color(0.20, 0.50, 0.54), Color(0.26, 0.38, 0.34), - Color(0.22, 0.34, 0.30), + Color(0.22, 0.34, 0.30), Color(0.20, 0.30, 0.26), ] 2: # 3️⃣ Toxic Green (poison / nature / alchemy) walls = [ @@ -3229,7 +3581,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.64, 0.36, 0.72), Color(0.70, 0.42, 0.78), Color(0.48, 0.26, 0.56), Color(0.58, 0.32, 0.66), Color(0.54, 0.30, 0.62), Color(0.34, 0.26, 0.38), - Color(0.28, 0.22, 0.32), + Color(0.28, 0.22, 0.32), Color(0.24, 0.18, 0.28), ] 3: # 4️⃣ Stone Grey (industrial / ruins / UI neutral) — brightened for visibility walls = [ @@ -3239,7 +3591,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.58, 0.58, 0.60), Color(0.62, 0.62, 0.64), Color(0.46, 0.46, 0.48), Color(0.55, 0.55, 0.57), Color(0.50, 0.50, 0.52), Color(0.40, 0.40, 0.42), - Color(0.36, 0.36, 0.38), + Color(0.36, 0.36, 0.38), Color(0.32, 0.32, 0.34), ] 4: # 5️⃣ Royal Purple (arcane royalty / bosses) walls = [ @@ -3249,7 +3601,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.90, 0.68, 0.22), Color(0.94, 0.74, 0.28), Color(0.72, 0.52, 0.14), Color(0.84, 0.60, 0.18), Color(0.78, 0.56, 0.16), Color(0.46, 0.36, 0.20), - Color(0.38, 0.30, 0.18), + Color(0.38, 0.30, 0.18), Color(0.32, 0.26, 0.16), ] 5: # 6️⃣ Desert Gold (sand / temples / sunlight) walls = [ @@ -3259,7 +3611,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.22, 0.58, 0.62), Color(0.28, 0.64, 0.66), Color(0.18, 0.48, 0.52), Color(0.20, 0.54, 0.58), Color(0.20, 0.50, 0.54), Color(0.26, 0.38, 0.34), - Color(0.22, 0.34, 0.30), + Color(0.22, 0.34, 0.30), Color(0.20, 0.30, 0.26), ] 6: # 7️⃣ Ancient Stone (medieval / castles / ruins) walls = [ @@ -3269,7 +3621,7 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.35, 0.28, 0.22), Color(0.40, 0.32, 0.26), Color(0.28, 0.22, 0.18), Color(0.38, 0.30, 0.24), Color(0.32, 0.26, 0.20), Color(0.24, 0.20, 0.16), - Color(0.20, 0.16, 0.14), + Color(0.20, 0.16, 0.14), Color(0.18, 0.14, 0.12), ] 7: # 8️⃣ Infernal Lava (hell / bosses / damage) walls = [ @@ -3279,11 +3631,11 @@ func _get_dungeon_color_scheme(scheme_index: int) -> Array: ground_fallout = [ Color(0.32, 0.68, 0.48), Color(0.38, 0.74, 0.54), Color(0.22, 0.52, 0.36), Color(0.28, 0.62, 0.44), Color(0.26, 0.58, 0.40), Color(0.26, 0.34, 0.28), - Color(0.22, 0.30, 0.24), + Color(0.22, 0.30, 0.24), Color(0.20, 0.26, 0.22), ] _: return o.duplicate() - if walls.size() == 6 and ground_fallout.size() == 7: + if walls.size() == 6 and ground_fallout.size() == 8: walls = _reorder_wall_colors_by_luminance(walls) var out: Array = [] out.append_array(walls) @@ -3297,26 +3649,27 @@ func _apply_dungeon_color_scheme() -> void: var shader_res = load("res://shaders/game_world.gdshader") as Shader if not shader_res: return - for layer in [dungeon_tilemap_layer, dungeon_tilemap_layer_above]: - if not layer or not is_instance_valid(layer): + var env_node = get_node_or_null("Environment") + if not env_node: + return + for child in env_node.get_children(): + if not child is TileMapLayer or not is_instance_valid(child): continue + var layer = child as TileMapLayer var mat = layer.material if not mat or not (mat is ShaderMaterial): mat = ShaderMaterial.new() mat.shader = shader_res layer.material = mat var sm = mat as ShaderMaterial - for i in range(13): + for i in range(14): var orig = _DUNGEON_ORIGINALS[i] as Color - var rpl = replace_colors[i] as Color + # Index 13: darkest fallout (2,0,4) — keep as-is, don't replace with scheme color + var rpl = (_DUNGEON_ORIGINALS[13] as Color) if i == 13 else (replace_colors[i] as Color) sm.set_shader_parameter("original_" + str(i), orig) sm.set_shader_parameter("replace_" + str(i), rpl) - # Index 13 unused; set to no-op (original same as replace, distinct from tile colors) - var neutral = Color(0.0, 0.0, 0.0, 1.0) - sm.set_shader_parameter("original_13", neutral) - sm.set_shader_parameter("replace_13", neutral) # TileMapLayerAbove: tint ffffff77 for slight transparency - if layer == dungeon_tilemap_layer_above: + if layer.name == "TileMapLayerAbove": sm.set_shader_parameter("tint", Color(1.0, 1.0, 1.0, 0x77 / 255.0)) else: sm.set_shader_parameter("tint", Color(1.0, 1.0, 1.0, 1.0)) @@ -3339,6 +3692,8 @@ func _render_dungeon(): if env_node: dungeon_tilemap_layer = env_node.get_node_or_null("DungeonLayer0") dungeon_tilemap_layer_above = env_node.get_node_or_null("TileMapLayerAbove") + dungeon_tilemap_layer_decorated = env_node.get_node_or_null("TileMapLayerDecoratedGround") + dungeon_tilemap_layer_cracked = env_node.get_node_or_null("TileMapLayerCrackedGround") if not dungeon_tilemap_layer: # Create new TileMapLayer @@ -3506,6 +3861,45 @@ func _render_dungeon(): dungeon_tilemap_layer_above.set_cell(Vector2i(x, y), 0, BLACK_TILE) above_tiles_placed += 1 + # Render decorated ground (on TileMapLayerDecoratedGround) and cracked ground (on TileMapLayerCrackedGround) + if dungeon_tilemap_layer_decorated or dungeon_tilemap_layer_cracked: + for x in range(map_size.x): + for y in range(map_size.y): + if dungeon_tilemap_layer_decorated: + dungeon_tilemap_layer_decorated.erase_cell(Vector2i(x, y)) + if dungeon_tilemap_layer_cracked: + dungeon_tilemap_layer_cracked.erase_cell(Vector2i(x, y)) + if dungeon_data.has("decorated_tile_grid") and dungeon_tilemap_layer_decorated: + var decorated_tile_grid = dungeon_data.decorated_tile_grid + for x in range(map_size.x): + for y in range(map_size.y): + if grid[x][y] != 1 and grid[x][y] != 3: + continue + if x >= decorated_tile_grid.size() or y >= decorated_tile_grid[x].size(): + continue + var dt = decorated_tile_grid[x][y] + if dt != null and dt is Vector2i: + dungeon_tilemap_layer_decorated.set_cell(Vector2i(x, y), 0, dt) + if dungeon_data.has("cracked_tile_grid") and dungeon_tilemap_layer_cracked: + var cracked_tile_grid = dungeon_data.cracked_tile_grid + # Floor switch positions must NEVER show cracked ground + var switch_tiles = {} + if dungeon_data.has("blocking_doors"): + var bd = dungeon_data.blocking_doors + var bd_array = bd if bd is Array else (bd.doors if "doors" in bd else []) + for door_data in bd_array: + if "switch_tile_x" in door_data and "switch_tile_y" in door_data: + var k = str(door_data.switch_tile_x) + "," + str(door_data.switch_tile_y) + switch_tiles[k] = true + for x in range(map_size.x): + for y in range(map_size.y): + if x >= cracked_tile_grid.size() or y >= cracked_tile_grid[x].size(): + continue + if switch_tiles.has(str(x) + "," + str(y)): + continue + if cracked_tile_grid[x][y]: + dungeon_tilemap_layer_cracked.set_cell(Vector2i(x, y), 0, CRACKED_TILE_ATLAS) + LogManager.log("GameWorld: Placed " + str(tiles_placed) + " tiles on main layer", LogManager.CATEGORY_DUNGEON) LogManager.log("GameWorld: Placed " + str(above_tiles_placed) + " tiles on above layer", LogManager.CATEGORY_DUNGEON) LogManager.log("GameWorld: Dungeon rendered on TileMapLayer", LogManager.CATEGORY_DUNGEON) @@ -3543,25 +3937,48 @@ func _update_spawn_points(target_room: Dictionary = {}, clear_existing: bool = t # Find free floor tiles in the room (excluding already assigned positions) var free_tiles = _find_free_floor_tiles_in_room(room, exclude_positions) + var room_center_x = (room.x + room.w / 2.0) * tile_size + var room_center_y = (room.y + room.h / 2.0) * tile_size + + # Helper: only add spawn point if NOT on a fallout tile (players must never start on fallout) + var try_add_spawn = func(world_pos: Vector2) -> void: + if not _is_position_on_fallout_tile(world_pos): + player_manager.spawn_points.append(world_pos) # Update player manager spawn points if free_tiles.size() > 0: - # Use free floor tiles as spawn points + # Use free floor tiles as spawn points (exclude fallout tiles) for tile_pos in free_tiles: var world_x = tile_pos.x * tile_size + tile_size / 2.0 # Center of tile var world_y = tile_pos.y * tile_size + tile_size / 2.0 # Center of tile - player_manager.spawn_points.append(Vector2(world_x, world_y)) - LogManager.log("GameWorld: Updated spawn points with " + str(free_tiles.size()) + " free floor tiles in room", LogManager.CATEGORY_DUNGEON) + try_add_spawn.call(Vector2(world_x, world_y)) + LogManager.log("GameWorld: Updated spawn points with " + str(player_manager.spawn_points.size()) + " safe (non-fallout) floor tiles in room", LogManager.CATEGORY_DUNGEON) else: - # Fallback: Create spawn points in a circle around the room center - var room_center_x = (room.x + room.w / 2.0) * tile_size - var room_center_y = (room.y + room.h / 2.0) * tile_size + # Fallback: Create spawn points in a circle around the room center (exclude fallout) var num_spawn_points = 8 for i in range(num_spawn_points): var angle = i * PI * 2 / num_spawn_points var offset = Vector2(cos(angle), sin(angle)) * 30 # 30 pixel radius - player_manager.spawn_points.append(Vector2(room_center_x, room_center_y) + offset) - LogManager.log("GameWorld: Updated spawn points in circle around room center (no free tiles found)", LogManager.CATEGORY_DUNGEON) + try_add_spawn.call(Vector2(room_center_x, room_center_y) + offset) + LogManager.log("GameWorld: Updated spawn points in circle (safe count: " + str(player_manager.spawn_points.size()) + ")", LogManager.CATEGORY_DUNGEON) + + # If no safe spawn points (e.g. room is all fallout), add room center only if safe; else first non-fallout tile in room + if player_manager.spawn_points.size() == 0: + var room_center = Vector2(room_center_x, room_center_y) + if not _is_position_on_fallout_tile(room_center): + player_manager.spawn_points.append(room_center) + LogManager.log("GameWorld: Fallback spawn at room center (safe)", LogManager.CATEGORY_DUNGEON) + else: + # Search room for any non-fallout floor tile + for tx in range(room.x + 2, room.x + room.w - 2): + for ty in range(room.y + 2, room.y + room.h - 2): + var wp = Vector2(tx * tile_size + tile_size / 2.0, ty * tile_size + tile_size / 2.0) + if not _is_position_on_fallout_tile(wp): + player_manager.spawn_points.append(wp) + LogManager.log("GameWorld: Fallback spawn at first non-fallout tile in room", LogManager.CATEGORY_DUNGEON) + break + if player_manager.spawn_points.size() > 0: + break func _find_room_at_position(world_pos: Vector2) -> Dictionary: # Find which room contains the given world position @@ -3959,10 +4376,12 @@ func _collect_current_world_metadata() -> Dictionary: } func _send_dungeon_blob_sync(client_peer_id: int): - # Send pre-packed dungeon blob chunks with acknowledgment-based flow control + # Send dungeon blob chunks with acknowledgment-based flow control. + # Re-pack from current dungeon_data so joiners get up-to-date floor (including broken cracked tiles). if not is_inside_tree() or not multiplayer.is_server(): return + _pack_dungeon_blob() if dungeon_blob_chunks.is_empty(): print("GameWorld: HOST - ERROR: No dungeon blob chunks available!") return @@ -4235,6 +4654,8 @@ func _pack_dungeon_blob(): var full_dungeon_data = { "tile_grid": dungeon_data.get("tile_grid", []), "grid": dungeon_data.get("grid", []), + "decorated_tile_grid": dungeon_data.get("decorated_tile_grid", []), + "cracked_tile_grid": dungeon_data.get("cracked_tile_grid", []), "map_size": dungeon_data.get("map_size", Vector2i(72, 72)), "rooms": dungeon_data.get("rooms", []), "start_room": dungeon_data.get("start_room", {}), @@ -4399,17 +4820,17 @@ func _sync_dungeon(dungeon_data_sync: Dictionary, seed_value: int, level: int, h await get_tree().process_frame await get_tree().process_frame # Wait an extra frame to ensure enemies are fully ready - # Update spawn points - use host's room if available, otherwise use start room + # Update spawn points - always use start room for joiners so they never end up in a corridor or outside the world print("GameWorld: Client - Updating spawn points...") - if not host_room.is_empty(): - print("GameWorld: Client - Using host's room for spawn points: ", host_room) - LogManager.log("GameWorld: Using host's room for spawn points", LogManager.CATEGORY_DUNGEON) - _update_spawn_points(host_room) - # Move any existing players to spawn near host - _move_players_to_host_room(host_room) + var spawn_room = dungeon_data.start_room if dungeon_data.has("start_room") and not dungeon_data.start_room.is_empty() else host_room + if not spawn_room.is_empty(): + print("GameWorld: Client - Using start room for joiner spawn points (avoid corridor/outside world)") + LogManager.log("GameWorld: Using start room for joiner spawn points", LogManager.CATEGORY_DUNGEON) + _update_spawn_points(spawn_room) + _move_players_to_host_room(spawn_room) else: - print("GameWorld: Client - Host room not available, using start room") - LogManager.log("GameWorld: Host room not available, using start room", LogManager.CATEGORY_DUNGEON) + print("GameWorld: Client - No start room, using default spawn points") + LogManager.log("GameWorld: No start room for joiner, using default spawn points", LogManager.CATEGORY_DUNGEON) _update_spawn_points() # Move all players to start room print("GameWorld: Client - Moving all players to start room...") @@ -4466,8 +4887,7 @@ func _sync_dungeon_blob_metadata(seed_value: int, level: int, map_size_sync: Vec print("GameWorld: Client - defeated_enemies dictionary now has ", defeated_enemies.size(), " entries") LogManager.log("GameWorld: Client received " + str(defeated_enemies_list.size()) + " defeated enemy indices", LogManager.CATEGORY_NETWORK) - # Store broken objects BEFORE objects are spawned - broken_objects.clear() + # Store broken objects BEFORE objects are spawned (merge, don't replace - so _sync_broken_objects isn't overwritten if it arrived first) for obj_index in broken_objects_list: broken_objects[obj_index] = true if broken_objects_list.size() > 0: @@ -4571,7 +4991,7 @@ func _sync_dungeon_blob_metadata(seed_value: int, level: int, map_size_sync: Vec "seed": seed_value, "level": level, "host_room": host_room, - "existing_loot": existing_loot_list # Store for spawning after dungeon is ready + "existing_loot": existing_loot_list } dungeon_sync_chunks.clear() dungeon_sync_received_chunks = 0 @@ -4889,15 +5309,16 @@ func _reassemble_dungeon_blob(): _sync_loot_spawn(loot_data.position, loot_data.get("loot_type", 0), loot_data.get("velocity", Vector2.ZERO), loot_data.get("velocity_z", 0.0), loot_data.get("loot_id", -1)) print("GameWorld: Client - Existing loot spawned") - # Update spawn points + # Update spawn points - always use start room for joiners so they never end up in a corridor or outside the world print("GameWorld: Client - Updating spawn points...") var host_room = dungeon_sync_metadata.host_room - if not host_room.is_empty(): - print("GameWorld: Client - Using host's room for spawn points: ", host_room) - _update_spawn_points(host_room) - _move_players_to_host_room(host_room) + var spawn_room = dungeon_data.start_room if dungeon_data.has("start_room") and not dungeon_data.start_room.is_empty() else host_room + if not spawn_room.is_empty(): + print("GameWorld: Client - Using start room for joiner spawn points") + _update_spawn_points(spawn_room) + _move_players_to_host_room(spawn_room) else: - print("GameWorld: Client - Host room not available, using start room") + print("GameWorld: Client - No start room, using default spawn points") _update_spawn_points() # Move all players to start room @@ -5026,15 +5447,16 @@ func _check_and_render_dungeon(): _render_dungeon() print("GameWorld: Client - Dungeon rendered") - # Update spawn points + # Update spawn points - always use start room for joiners so they never end up in a corridor or outside the world print("GameWorld: Client - Updating spawn points...") - var host_room = dungeon_sync_metadata.host_room - if not host_room.is_empty(): - print("GameWorld: Client - Using host's room for spawn points: ", host_room) - _update_spawn_points(host_room) - _move_players_to_host_room(host_room) + var host_room_chunks = dungeon_sync_metadata.host_room + var spawn_room_chunks = dungeon_data.start_room if dungeon_data.has("start_room") and not dungeon_data.start_room.is_empty() else host_room_chunks + if not spawn_room_chunks.is_empty(): + print("GameWorld: Client - Using start room for joiner spawn points") + _update_spawn_points(spawn_room_chunks) + _move_players_to_host_room(spawn_room_chunks) else: - print("GameWorld: Client - Host room not available, using start room") + print("GameWorld: Client - No start room, using default spawn points") _update_spawn_points() # Move all players to start room @@ -5272,15 +5694,20 @@ func _spawn_enemies(): if "damage" in enemy_data: enemy.damage = enemy_data.damage - # If it's a humanoid enemy, set the humanoid_type - if enemy_type.ends_with("enemy_humanoid.tscn") and "humanoid_type" in enemy_data: - enemy.humanoid_type = enemy_data.humanoid_type + # If it's a humanoid enemy, set the humanoid_type and spawn_position for unique appearance seed + if enemy_type.ends_with("enemy_humanoid.tscn"): + if "humanoid_type" in enemy_data: + enemy.humanoid_type = enemy_data.humanoid_type + if "spawn_position" in enemy: + enemy.spawn_position = enemy_data.position # CRITICAL: Set collision mask BEFORE adding to scene to ensure enemies collide with walls (layer 7 = bit 6 = 64) # This overrides any collision_mask set in the scene file enemy.collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64) - # Add to scene tree AFTER setting authority and stats + # Set position BEFORE add_child so humanoid _ready() sees correct global_position for unique appearance seed + enemy.position = enemy_data.position + # Add to scene tree AFTER setting authority, stats, and position entities_node.add_child(enemy) enemy.global_position = enemy_data.position @@ -9255,14 +9682,14 @@ func _spawn_floor_switch(i_position: Vector2, required_weight: float, tile_x: in entities_node.add_child(switch) switch.global_position = i_position - # Update tilemap to show switch tile (initial inactive state) - if dungeon_tilemap_layer: + # Draw switch on TileMapLayerDecoratedGround (terrain 1 = walk, 2 = pillar set in tileset) + if dungeon_tilemap_layer_decorated: var initial_tile: Vector2i if switch_type == "pillar": - initial_tile = Vector2i(16, 9) # Pillar switch inactive + initial_tile = Vector2i(16, 9) # Pillar switch inactive (terrain 2) else: - initial_tile = Vector2i(11, 9) # Walk-on switch inactive - dungeon_tilemap_layer.set_cell(Vector2i(tile_x, tile_y), 0, initial_tile) + initial_tile = Vector2i(11, 9) # Walk-on switch inactive (terrain 1) + dungeon_tilemap_layer_decorated.set_cell(Vector2i(tile_x, tile_y), 0, initial_tile) var room_x_str = str(switch_room.get("x", "?")) if switch_room and not switch_room.is_empty() else "?" var room_y_str = str(switch_room.get("y", "?")) if switch_room and not switch_room.is_empty() else "?" diff --git a/src/scripts/interactable_object.gd b/src/scripts/interactable_object.gd index e7c3cc2..45e358f 100644 --- a/src/scripts/interactable_object.gd +++ b/src/scripts/interactable_object.gd @@ -44,6 +44,11 @@ var is_chest_opened: bool = false var sync_timer: float = 0.0 var sync_interval: float = 0.05 # Sync 20 times per second +# Fallout: sink and disappear (like loot) +var falling_into_fallout: bool = false +var fallout_sink_progress: float = 1.0 +const FALLOUT_SINK_DURATION: float = 0.4 + func _ready(): # Make sure it's on the interactable layer collision_layer = 2 # Layer 2 for objects @@ -76,6 +81,22 @@ func _physics_process(delta): return if not is_frozen: + # Fallout: sink and disappear when on ground (not held, not airborne) + if not is_airborne and position_z <= 0.0: + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(global_position): + if not falling_into_fallout: + falling_into_fallout = true + fallout_sink_progress = 1.0 + fallout_sink_progress -= delta / FALLOUT_SINK_DURATION + if fallout_sink_progress <= 0.0: + queue_free() + return + if sprite: + sprite.scale = Vector2.ONE * max(0.0, fallout_sink_progress) + move_and_slide() + return + falling_into_fallout = false # Z-axis physics for airborne boxes if is_airborne: # Apply gravity to Z velocity diff --git a/src/scripts/item_loot_helper.gd b/src/scripts/item_loot_helper.gd index 40bf2a4..fca73d8 100644 --- a/src/scripts/item_loot_helper.gd +++ b/src/scripts/item_loot_helper.gd @@ -23,12 +23,12 @@ static func spawn_item_loot(item: Item, position: Vector2, entities_node: Node, # Create unique seed for this loot item: dungeon_seed + position hash + counter # Use position hash to make seed unique per spawn location var pos_hash = hash(str(int(position.x)) + "_" + str(int(position.y))) - var loot_seed = base_seed + pos_hash + 20000 # Offset to avoid collisions with enemy loot + var loot_seed = base_seed + pos_hash + 20000 # Offset to avoid collisions with enemy loot loot_rng.seed = loot_seed var random_angle = loot_rng.randf() * PI * 2 - var random_force = loot_rng.randf_range(25.0, 50.0) # Reduced to half speed - var random_velocity_z = loot_rng.randf_range(40.0, 60.0) # Reduced to half speed + var random_force = loot_rng.randf_range(25.0, 50.0) # Reduced to half speed + var random_velocity_z = loot_rng.randf_range(40.0, 60.0) # Reduced to half speed var initial_velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force # Find safe spawn position if game_world is provided @@ -44,8 +44,9 @@ static func spawn_item_loot(item: Item, position: Vector2, entities_node: Node, # Set properties before adding to scene tree (to avoid physics state change errors) loot.global_position = safe_spawn_pos + loot.position_z = 1.0 if "position_z" in loot else 0.0 loot.loot_type = loot.LootType.ITEM - loot.item = item # Set the item instance + loot.item = item # Set the item instance # Set initial velocity before _ready() processes loot.velocity = initial_velocity loot.velocity_z = random_velocity_z diff --git a/src/scripts/loot.gd b/src/scripts/loot.gd index 5ef2352..1e28dac 100644 --- a/src/scripts/loot.gd +++ b/src/scripts/loot.gd @@ -13,8 +13,9 @@ enum LootType { @export var loot_type: LootType = LootType.COIN -# Z-axis physics (like boxes and players) -var position_z: float = 0.0 +# Z-axis physics (like boxes and players) - start at 1 to avoid instantly falling into fallout +const SPAWN_POSITION_Z: float = 1.0 +var position_z: float = 1.0 var velocity_z: float = 0.0 var acceleration_z: float = 0.0 var is_airborne: bool = true @@ -57,8 +58,15 @@ var item: Item = null # Item instance (for LootType.ITEM) # Quantity badge for items with quantity > 1 var quantity_badge: Label = null +# Fallout: sink and disappear (no recovery) +var falling_into_fallout: bool = false +var fallout_sink_progress: float = 1.0 +const FALLOUT_SINK_DURATION: float = 0.4 + func _ready(): add_to_group("loot") + # Always start slightly above ground to prevent instantly falling into fallout + position_z = SPAWN_POSITION_Z # Setup shadow if shadow: @@ -219,6 +227,36 @@ func _physics_process(delta): # Server (authority): Run physics normally if is_server: + # Fallout: lock to tile center, stop x/y movement, not collectible, sink then remove + if not is_airborne and position_z <= 0.0: + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(global_position): + if not falling_into_fallout: + falling_into_fallout = true + fallout_sink_progress = 1.0 + # Lock to center of fallout tile and stop all x/y movement + if gw.has_method("_get_tile_center_at"): + global_position = gw._get_tile_center_at(global_position) + velocity = Vector2.ZERO + # Not collectible while falling + if pickup_area: + pickup_area.monitoring = false + pickup_area.monitorable = false + fallout_sink_progress -= delta / FALLOUT_SINK_DURATION + if fallout_sink_progress <= 0.0: + # Sync removal to clients so joiner sees loot disappear (same as pickup) + if multiplayer.has_multiplayer_peer() and is_inside_tree(): + var loot_id = get_meta("loot_id") if has_meta("loot_id") else -1 + var game_world = get_tree().get_first_node_in_group("game_world") + if game_world and game_world.has_method("_sync_loot_remove"): + game_world._rpc_to_ready_peers("_sync_loot_remove", [loot_id, global_position]) + queue_free() + return + scale = Vector2.ONE * max(0.0, fallout_sink_progress) + move_and_slide() + _update_visuals() + return + # Update bounce timer if bounce_timer > 0.0: bounce_timer -= delta @@ -410,6 +448,8 @@ func _animate_coin(delta): sprite.frame = frame func _on_pickup_area_body_entered(body): + if falling_into_fallout: + return if body and body.is_in_group("player") and not body.is_dead: # Check if this item was dropped by this player recently (5 second cooldown) if has_meta("dropped_by_peer_id") and has_meta("drop_time"): @@ -430,6 +470,8 @@ func _on_pickup_area_body_entered(body): _pickup(body) func _pickup(player: Node): + if falling_into_fallout: + return # Prevent multiple pickups if collected: print("Loot: Already collected, ignoring pickup") @@ -774,6 +816,9 @@ func _request_pickup(player_peer_id: int): print("Loot: Already collected (collected=", collected, "), ignoring pickup request") return + if falling_into_fallout: + return + # Set mutex and mark as collected IMMEDIATELY to prevent any race conditions processing_pickup = true collected = true diff --git a/src/scripts/player.gd b/src/scripts/player.gd index d2b98c2..ebdca95 100644 --- a/src/scripts/player.gd +++ b/src/scripts/player.gd @@ -59,6 +59,7 @@ var controls_disabled: bool = false # True when player has reached exit and cont # Being held state var being_held_by: Node = null +var is_being_held: bool = false # Set by set_being_held(); reliable on all clients for fallout immunity var grabbed_by_enemy_hand: Node = null # Set when enemy hand grabs (snatch); locks movement until release var enemy_hand_grab_knockback_time: float = 0.0 # Timer for initial knockback when grabbed const ENEMY_HAND_GRAB_KNOCKBACK_DURATION: float = 0.15 # Short knockback duration before moving to hand @@ -143,6 +144,27 @@ var spawn_landing_bounced: bool = false var spawn_landing_visible_shown: bool = false # One-shot: set true when we show right before falling var has_seen_exit_this_level: bool = false # Track if player has seen exit notification for current level +# Fallout (quicksand) state: sink into tile, then respawn at last safe +var fallout_state: bool = false +var fallout_scale_progress: float = 1.0 # 1.0 -> 0.0 during sink +var fallout_respawn_delay_timer: float = 0.0 # After scale hits 0, wait this long before respawn +var fallout_respawn_stun_timer: float = 0.0 # After respawn from fallout, stun for this long (no control) +var on_fallout_tile_near_sink: bool = false # True when on fallout tile but not yet at center (fast walk plays) +var animation_speed_multiplier: float = 1.0 # 1.0 = normal; >1 when on fallout tile so run anim plays faster +const FALLOUT_CENTER_THRESHOLD: float = 2.0 # Player center must be almost exactly at tile center to sink (Zelda Link's Awakening style) +const FALLOUT_DRAG_STRENGTH: float = 820.0 # Base pull toward fallout center (strong enough to prevent running over) +const FALLOUT_CENTER_PULL_BOOST: float = 1.8 # Pull is stronger near center: at center (1+BOOST)x, at edge 1x +const FALLOUT_DRAG_EDGE_FACTOR: float = 0.45 # At tile edge drag is 45% strength; ramps to 100% toward center +const FALLOUT_MOVEMENT_FACTOR: float = 0.3 # Movement speed on fallout tile (30%) so player cannot run over it +const FALLOUT_TILE_HALF_SIZE: float = 8.0 # Half of tile size (16) for distance-based strength +const FALLOUT_PLAYER_BOX_HALF: float = 8.0 # Player treated as 16x16 box for quicksand (center ± 8) +const FALLOUT_TILE_ANIMATION_SPEED: float = 3.0 # Run animation plays this many times faster when on fallout tile (warning to player) +const FALLOUT_SINK_DURATION: float = 0.5 # Seconds to scale from 1 to 0 (faster sink) +const FALLOUT_RESPAWN_DELAY: float = 0.3 # Seconds after scale reaches 0 before respawning at safe tile +const FALLOUT_RESPAWN_STUN_DURATION: float = 0.3 # Seconds of stun after respawn from fallout +const FALLOUT_RESPAWN_HP_PENALTY: float = 1.0 # HP lost when respawning from fallout +const HELD_POSITION_Z: float = 12.0 # Z height when held/lifted (above ground; immune to fallout) + # Components # @onready var sprite = $Sprite2D # REMOVED: Now using layered sprites @onready var shadow = $Shadow @@ -150,6 +172,7 @@ var has_seen_exit_this_level: bool = false # Track if player has seen exit notif @onready var point_light = $PointLight2D @onready var collision_shape = $CollisionShape2D @onready var grab_area = $GrabArea +@onready var quicksand_area = $QuicksandArea @onready var interaction_indicator = $InteractionIndicator # Audio @@ -1584,9 +1607,10 @@ func _get_collision_extent(node: Node) -> float: return 8.0 func _update_animation(delta): - # Update animation frame timing + # Update animation frame timing (faster when on fallout tile to warn player) + var frame_duration_sec = ANIMATIONS[current_animation]["frameDurations"][current_frame] / 1000.0 / animation_speed_multiplier time_since_last_frame += delta - if time_since_last_frame >= ANIMATIONS[current_animation]["frameDurations"][current_frame] / 1000.0: + if time_since_last_frame >= frame_duration_sec: current_frame += 1 if current_frame >= len(ANIMATIONS[current_animation]["frames"]): current_frame -= 1 # Prevent out of bounds @@ -1972,7 +1996,26 @@ func _physics_process(delta): was_reviving_last_frame = false _stop_spell_charge_incantation() - if is_local_player and is_multiplayer_authority(): + # Fallout (quicksand) sink: run for ALL players so remote see scale/rotation/FALL animation + if fallout_state: + current_direction = Direction.DOWN + facing_direction_vector = Vector2.DOWN + _set_animation("FALL") + scale = Vector2.ONE * max(0.0, fallout_scale_progress) + rotation = deg_to_rad(45.0) + velocity = Vector2.ZERO + if fallout_respawn_delay_timer > 0.0: + fallout_respawn_delay_timer -= delta + if fallout_respawn_delay_timer <= 0.0: + fallout_respawn_delay_timer = 0.0 + if is_multiplayer_authority(): + _respawn_from_fallout() + else: + fallout_scale_progress -= delta / FALLOUT_SINK_DURATION + if fallout_scale_progress <= 0.0: + fallout_scale_progress = 0.0 + fallout_respawn_delay_timer = FALLOUT_RESPAWN_DELAY + elif is_local_player and is_multiplayer_authority(): # When dead: only corpse knockback friction + sync; no input or other logic if is_dead: if is_knocked_back: @@ -1983,6 +2026,42 @@ func _physics_process(delta): else: velocity = velocity.lerp(Vector2.ZERO, delta * 8.0) else: + # Reset fallout-tile animation state each frame (set when on fallout tile below) + animation_speed_multiplier = 1.0 + on_fallout_tile_near_sink = false + # Held players cannot fallout (use is_being_held so it works on all clients; position can put held player over a tile) + var is_held = is_being_held or (being_held_by != null and is_instance_valid(being_held_by)) + if position_z == 0.0 and not is_held: + # Quicksand: only when player CENTER is on a fallout tile (avoids vortex pull from adjacent ground) + var gw = get_tree().get_first_node_in_group("game_world") + var area_center = quicksand_area.global_position if quicksand_area else global_position + if gw and gw.has_method("_is_position_on_fallout_tile"): + if not gw._is_position_on_fallout_tile(area_center): + gw.update_last_safe_position_for_player(self, global_position) + else: + # Center is on fallout: use this tile's center for drag/sink (symmetric) + var tile_center = gw._get_tile_center_at(area_center) + var dist_to_center = area_center.distance_to(tile_center) + if dist_to_center < FALLOUT_CENTER_THRESHOLD: + # If carrying something, throw it in the direction we were looking before falling + if held_object and is_lifting: + _force_throw_held_object(facing_direction_vector) + # Snap player center exactly to fallout tile center so sink looks correct + global_position = tile_center + fallout_state = true + fallout_scale_progress = 1.0 + velocity = Vector2.ZERO + current_direction = Direction.DOWN + facing_direction_vector = Vector2.DOWN + _set_animation("FALL") + if has_node("SfxFallout"): + $SfxFallout.play() + if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): + _rpc_to_ready_peers("_sync_fallout_start", [tile_center]) + else: + on_fallout_tile_near_sink = true + animation_speed_multiplier = FALLOUT_TILE_ANIMATION_SPEED + _set_animation("RUN") # Handle knockback timer (always handle knockback, even when controls are disabled) if is_knocked_back: knockback_time += delta @@ -1994,6 +2073,12 @@ func _physics_process(delta): if grabbed_by_enemy_hand: enemy_hand_grab_knockback_time += delta + # Update fallout respawn stun timer (no control for 0.3s after respawn from fallout) + if fallout_respawn_stun_timer > 0.0: + fallout_respawn_stun_timer -= delta + if fallout_respawn_stun_timer <= 0.0: + fallout_respawn_stun_timer = 0.0 + # Update movement lock timer (for bow release) if movement_lock_timer > 0.0: movement_lock_timer -= delta @@ -2084,18 +2169,18 @@ func _physics_process(delta): burn_damage_timer = 0.0 _remove_burn_debuff() - # Skip input if controls are disabled (e.g., when inventory is open) or spawn landing (fall → DIE → stand up) + # Skip input if controls are disabled (e.g., when inventory is open) or spawn landing (fall → DIE → stand up) or fallout (sinking) or post-fallout stun # But still allow knockback to continue (handled above) # CRITICAL: During entrance walk-out cut-scene, game_world sets velocity; do NOT zero it here var entrance_walk_out = controls_disabled and has_meta("entrance_walk_target") - var skip_input = controls_disabled or spawn_landing - if controls_disabled or spawn_landing: + var skip_input = controls_disabled or spawn_landing or fallout_state or (fallout_respawn_stun_timer > 0.0) + if controls_disabled or spawn_landing or fallout_state or (fallout_respawn_stun_timer > 0.0): if not is_knocked_back and not entrance_walk_out: # Immediately stop movement when controls are disabled (e.g., inventory opened) # Exception: entrance walk-out - velocity is driven by game_world for cut-scene velocity = Vector2.ZERO # Reset animation to IDLE if not in a special state (skip when spawn_landing: we use DIE until stand up) - if not spawn_landing and current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF" and current_animation != "AXE" and current_animation != "PUNCH": + if not spawn_landing and not fallout_state and current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF" and current_animation != "AXE" and current_animation != "PUNCH": if is_lifting: _set_animation("IDLE_HOLD") elif is_pushing: @@ -2152,6 +2237,24 @@ func _physics_process(delta): struggle_time = 0.0 # Reset struggle timer struggle_direction = Vector2.ZERO _handle_input() + # Apply quicksand only when player CENTER is on a fallout tile (no vortex pull from adjacent tiles) + if position_z == 0.0 and not (is_being_held or (being_held_by != null and is_instance_valid(being_held_by))): + var gw = get_tree().get_first_node_in_group("game_world") + var area_center = quicksand_area.global_position if quicksand_area else global_position + if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(area_center): + # Heavy movement penalty so running over the tile is not possible + velocity *= FALLOUT_MOVEMENT_FACTOR + var tile_center = gw._get_tile_center_at(area_center) + var area_center_dist = area_center.distance_to(tile_center) + if area_center_dist >= FALLOUT_CENTER_THRESHOLD: + # Drag toward this tile's center (same strength from all directions) + var dir = (tile_center - area_center).normalized() + # Softer at edge: drag ramps from FALLOUT_DRAG_EDGE_FACTOR at tile edge to 1.0 toward center + var edge_t = clamp(area_center_dist / FALLOUT_TILE_HALF_SIZE, 0.0, 1.0) + var edge_drag_factor = lerp(1.0, FALLOUT_DRAG_EDGE_FACTOR, edge_t) + # Strength: stronger when player center is closer to fallout tile center (distance-based only, no direction bias) + var strength_mult = 1.0 + FALLOUT_CENTER_PULL_BOOST * (1.0 - clamp(area_center_dist / FALLOUT_TILE_HALF_SIZE, 0.0, 1.0)) + velocity += dir * FALLOUT_DRAG_STRENGTH * strength_mult * edge_drag_factor * delta _handle_movement(delta) _handle_interactions() else: @@ -3301,10 +3404,9 @@ func _try_grab(): closest_body.set_collision_mask_value(2, false) closest_body.set_collision_mask_value(7, true) # Keep wall collision elif _is_player(closest_body): - # Players: remove from layer fully when lifted – no collision with anything - closest_body.set_collision_layer_value(1, false) - closest_body.set_collision_mask_value(1, false) - closest_body.set_collision_mask_value(7, false) + # Players: no collision layer at all while held + closest_body.collision_layer = 0 + closest_body.collision_mask = 0 # When grabbing, immediately try to lift if possible _set_animation("IDLE") @@ -3363,10 +3465,14 @@ func _lift_object(): if "is_frozen" in held_object: held_object.is_frozen = true elif _is_player(held_object): - # Player: use set_being_held + # Player: use set_being_held (also sets position_z = HELD_POSITION_Z) if held_object.has_method("set_being_held"): held_object.set_being_held(true) + # Any held object with position_z gets lifted above ground + if "position_z" in held_object: + held_object.position_z = HELD_POSITION_Z + if held_object.has_method("on_lifted"): held_object.on_lifted(self) @@ -3453,11 +3559,12 @@ func reset_grab_state(): if "held_by_player" in held_object: held_object.held_by_player = null elif _is_player(held_object): - held_object.set_collision_layer_value(1, true) - held_object.set_collision_mask_value(1, true) - held_object.set_collision_mask_value(7, true) + held_object.collision_layer = 1 + held_object.collision_mask = 1 | 2 | 64 # players, objects, walls if held_object.has_method("set_being_held"): held_object.set_being_held(false) + if "position_z" in held_object: + held_object.position_z = 0.0 # Stop drag sound if playing if held_object.has_method("stop_drag_sound"): @@ -3515,14 +3622,16 @@ func _stop_pushing(): released_obj.set_collision_mask_value(2, true) released_obj.set_collision_mask_value(7, true) # Re-enable wall collision! elif _is_player(released_obj): - # Players: back on layer 1 - released_obj.set_collision_layer_value(1, true) - released_obj.set_collision_mask_value(1, true) - released_obj.set_collision_mask_value(7, true) # Re-enable wall collision! + # Players: restore collision layer and mask (layer 1, mask 1|2|64 so we collide with players, objects, walls) + released_obj.collision_layer = 1 + released_obj.collision_mask = 1 | 2 | 64 if released_obj is CharacterBody2D and released_obj.has_method("set_being_held"): released_obj.set_being_held(false) + if "position_z" in released_obj: + released_obj.position_z = 0.0 + # Ensure position stays exactly where it is - no movement on release! # Do this AFTER calling on_released in case it tries to change position if released_obj.has_method("on_released"): @@ -3640,10 +3749,11 @@ func _throw_object(): if thrown_obj.has_method("set_being_held"): thrown_obj.set_being_held(false) - # Re-add to layer DIRECTLY when thrown (no delay) + # Re-add to layer DIRECTLY when thrown (no delay); restore full mask 1|2|64 if thrown_obj and is_instance_valid(thrown_obj): thrown_obj.set_collision_layer_value(1, true) thrown_obj.set_collision_mask_value(1, true) + thrown_obj.set_collision_mask_value(2, true) thrown_obj.set_collision_mask_value(7, true) elif thrown_obj and thrown_obj.has_method("can_be_grabbed") and thrown_obj.can_be_grabbed(): # Bomb or other grabbable object - handle like box @@ -3785,9 +3895,8 @@ func _force_throw_held_object(direction: Vector2): # Re-add to layer DIRECTLY when thrown (no delay) if thrown_obj and is_instance_valid(thrown_obj): - thrown_obj.set_collision_layer_value(1, true) - thrown_obj.set_collision_mask_value(1, true) - thrown_obj.set_collision_mask_value(7, true) + thrown_obj.collision_layer = 1 + thrown_obj.collision_mask = 1 | 2 | 64 # players, objects, walls elif thrown_obj and thrown_obj.has_method("can_be_grabbed") and thrown_obj.can_be_grabbed(): # Other grabbable object - handle like box thrown_obj.global_position = throw_start_pos @@ -3917,10 +4026,9 @@ func _place_down_object(): if "velocity_z" in placed_obj: placed_obj.velocity_z = 0.0 elif _is_player(placed_obj): - # Player: back on layer 1 - placed_obj.set_collision_layer_value(1, true) - placed_obj.set_collision_mask_value(1, true) - placed_obj.set_collision_mask_value(7, true) # Re-enable wall collision! + # Player: restore collision layer and mask (1|2|64 so we collide with players, objects, walls) + placed_obj.collision_layer = 1 + placed_obj.collision_mask = 1 | 2 | 64 placed_obj.global_position = place_pos placed_obj.velocity = Vector2.ZERO if placed_obj.has_method("set_being_held"): @@ -5223,7 +5331,7 @@ func _update_lifted_object(): held_object = null return - # Object floats above player's head + # Object floats above player's head (XY) and at HELD_POSITION_Z (above ground, immune to fallout) var target_pos = position + Vector2(0, -12) # Above head # Instant follow for local player, smooth for network sync @@ -5232,6 +5340,10 @@ func _update_lifted_object(): else: held_object.global_position = held_object.global_position.lerp(target_pos, 0.3) + # Keep held object at Z height so it's "above" ground (no fallout under it) + if "position_z" in held_object: + held_object.position_z = HELD_POSITION_Z + # Sync held object position over network if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree(): var obj_name = _get_object_name_for_sync(held_object) @@ -5301,6 +5413,12 @@ func _update_pushed_object(): var results = space_state.intersect_point(query) was_blocked = results.size() > 0 + # StonePillar must NOT be pushed onto fallout - treat fallout as solid + if not was_blocked and held_object.get("object_type") == "Pillar": + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(target_pos): + was_blocked = true + # Update the flag for next frame's input handling object_blocked_by_wall = was_blocked @@ -5777,10 +5895,11 @@ func _sync_throw(obj_name: String, throw_pos: Vector2, force: Vector2, thrower_n print("Player is now airborne on client!") - # Re-add to layer DIRECTLY when thrown (no delay) + # Re-add to layer DIRECTLY when thrown (no delay); full mask 1|2|64 if obj and is_instance_valid(obj): obj.set_collision_layer_value(1, true) obj.set_collision_mask_value(1, true) + obj.set_collision_mask_value(2, true) obj.set_collision_mask_value(7, true) @rpc("any_peer", "reliable") @@ -5818,9 +5937,8 @@ func _sync_initial_grab(obj_name: String, _offset: Vector2): obj.set_collision_mask_value(1, false) obj.set_collision_mask_value(2, false) elif _is_player(obj): - obj.set_collision_layer_value(1, false) - obj.set_collision_mask_value(1, false) - obj.set_collision_mask_value(7, false) + obj.collision_layer = 0 + obj.collision_mask = 0 print("Synced initial grab on client: ", obj_name) @@ -5868,9 +5986,8 @@ func _sync_grab(obj_name: String, is_lift: bool, axis: Vector2 = Vector2.ZERO): if "throw_velocity" in obj: obj.throw_velocity = Vector2.ZERO elif _is_player(obj): - obj.set_collision_layer_value(1, false) - obj.set_collision_mask_value(1, false) - obj.set_collision_mask_value(7, false) + obj.collision_layer = 0 + obj.collision_mask = 0 if obj.has_method("set_being_held"): obj.set_being_held(true) else: @@ -5887,9 +6004,8 @@ func _sync_grab(obj_name: String, is_lift: bool, axis: Vector2 = Vector2.ZERO): if "throw_velocity" in obj: obj.throw_velocity = Vector2.ZERO elif _is_player(obj): - obj.set_collision_layer_value(1, false) - obj.set_collision_mask_value(1, false) - obj.set_collision_mask_value(7, false) + obj.collision_layer = 0 + obj.collision_mask = 0 if obj.has_method("set_being_held"): obj.set_being_held(true) @@ -5940,6 +6056,7 @@ func _sync_release(obj_name: String): elif _is_player(obj): obj.set_collision_layer_value(1, true) obj.set_collision_mask_value(1, true) + obj.set_collision_mask_value(2, true) obj.set_collision_mask_value(7, true) # Re-enable wall collision! if obj.has_method("set_being_held"): obj.set_being_held(false) @@ -6000,6 +6117,7 @@ func _sync_place_down(obj_name: String, place_pos: Vector2): elif _is_player(obj): obj.set_collision_layer_value(1, true) obj.set_collision_mask_value(1, true) + obj.set_collision_mask_value(2, true) obj.set_collision_mask_value(7, true) # Re-enable wall collision! obj.velocity = Vector2.ZERO if obj.has_method("set_being_held"): @@ -6126,6 +6244,7 @@ func _break_free_from_holder(): struggle_time = 0.0 struggle_direction = Vector2.ZERO being_held_by = null + is_being_held = false @rpc("any_peer", "reliable") func _sync_break_free(holder_name: String, direction: Vector2): @@ -6182,6 +6301,7 @@ func _force_place_down(direction: Vector2): elif _is_player(placed_obj): placed_obj.set_collision_layer_value(1, true) placed_obj.set_collision_mask_value(1, true) + placed_obj.set_collision_mask_value(2, true) placed_obj.set_collision_mask_value(7, true) # Re-enable wall collision! placed_obj.velocity = Vector2.ZERO if placed_obj.has_method("set_being_held"): @@ -6193,19 +6313,22 @@ func _force_place_down(direction: Vector2): print("Forced to place down ", placed_obj.name) func set_being_held(held: bool): - # When being held by another player, disable movement - # But keep physics_process running for network sync + # When being held by another player, disable movement and collision; use HELD_POSITION_Z so we're "above" ground (immune to fallout) + is_being_held = held if held: - # Just prevent input handling, don't disable physics velocity = Vector2.ZERO is_airborne = false - position_z = 0.0 + position_z = HELD_POSITION_Z velocity_z = 0.0 + collision_layer = 0 + collision_mask = 0 else: - # Released - reset struggle state struggle_time = 0.0 struggle_direction = Vector2.ZERO being_held_by = null + position_z = 0.0 + collision_layer = 1 + collision_mask = 1 | 2 | 64 # layer 1 players, 2 objects, 7 walls @rpc("any_peer", "reliable") func rpc_grabbed_by_enemy_hand(enemy_name: String) -> void: @@ -6257,10 +6380,13 @@ func rpc_take_damage(amount: float, attacker_position: Vector2, is_burn_damage: if is_multiplayer_authority(): take_damage(amount, attacker_position, is_burn_damage, apply_burn_debuff) -func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool = false, apply_burn_debuff: bool = false): +func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool = false, apply_burn_debuff: bool = false, no_knockback: bool = false): # Don't take damage if already dead if is_dead: return + # Invulnerable during fallout sink (can't take damage from anything while falling) + if fallout_state: + return # Cancel bow charging when taking damage if is_charging_bow: @@ -6391,8 +6517,8 @@ func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool # Lock facing direction briefly so player can't change it while taking damage damage_direction_lock_timer = damage_direction_lock_duration - # Only apply knockback if not burn damage - if not is_burn_damage: + # Only apply knockback if not burn damage and not suppressed (e.g. fallout respawn) + if not is_burn_damage and not no_knockback: # Calculate direction FROM attacker TO victim var direction_from_attacker = (global_position - attacker_position).normalized() @@ -6476,6 +6602,7 @@ func _die(): elif _is_player(released_obj): released_obj.set_collision_layer_value(1, true) released_obj.set_collision_mask_value(1, true) + released_obj.set_collision_mask_value(2, true) released_obj.set_collision_mask_value(7, true) # Re-enable wall collision! if released_obj.has_method("set_being_held"): released_obj.set_being_held(false) @@ -6539,10 +6666,9 @@ func _die(): other_player.grab_offset = Vector2.ZERO other_player.push_axis = Vector2.ZERO - # Re-enable our collision - set_collision_layer_value(1, true) - set_collision_mask_value(1, true) - set_collision_mask_value(7, true) # Re-enable wall collision! + # Re-enable our collision (layer 1, mask 1|2|64) + collision_layer = 1 + collision_mask = 1 | 2 | 64 # THEN sync to other clients if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): @@ -6555,6 +6681,7 @@ func _die(): print(name, " is NOT being held by anyone") being_held_by = null + is_being_held = false # Replicas: no wait loop; we get _sync_respawn from authority. if not is_multiplayer_authority(): @@ -6666,6 +6793,24 @@ func _spawn_landing_stand_up(): if game_world and game_world.has_method("_start_bg_music"): game_world._start_bg_music() +func _respawn_from_fallout(): + # Teleport to last safe tile, reset fallout state, then apply 1 HP damage via take_damage (no knockback) + var gw = get_tree().get_first_node_in_group("game_world") + if gw and gw.has_method("get_last_safe_position_for_player"): + global_position = gw.get_last_safe_position_for_player(self) + fallout_state = false + fallout_scale_progress = 1.0 + fallout_respawn_delay_timer = 0.0 + scale = Vector2.ONE + rotation = 0.0 + velocity = Vector2.ZERO + fallout_respawn_stun_timer = FALLOUT_RESPAWN_STUN_DURATION + _set_animation("IDLE") + # Apply damage via take_damage (shows damage number, sound, etc.) but with no knockback + take_damage(FALLOUT_RESPAWN_HP_PENALTY, global_position, false, false, true) + if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree(): + _rpc_to_ready_peers("_sync_respawn_from_fallout", [global_position]) + func _respawn(): print(name, " respawning!") was_revived = false @@ -6684,6 +6829,7 @@ func _respawn(): # Re-enable collision in case it was disabled while being carried set_collision_layer_value(1, true) set_collision_mask_value(1, true) + set_collision_mask_value(2, true) set_collision_mask_value(7, true) # Re-enable wall collision! # Reset health and state @@ -6815,9 +6961,10 @@ func _force_holder_to_drop_local(holder_name: String): holder.grab_offset = Vector2.ZERO holder.push_axis = Vector2.ZERO - # Re-enable collision on dropped player + # Re-enable collision on dropped player (layer 1, mask 1|2|64) set_collision_layer_value(1, true) set_collision_mask_value(1, true) + set_collision_mask_value(2, true) set_collision_mask_value(7, true) # Re-enable wall collision! else: print(" ✗ held_object doesn't match self") @@ -6936,14 +7083,43 @@ func _sync_death(): _apply_death_visual() return +@rpc("any_peer", "reliable") +func _sync_fallout_start(tile_center_pos: Vector2): + # Other clients: start fallout sink visuals; ignore if this player is being held (immune to fallout) + if not is_multiplayer_authority(): + if is_being_held: + return + global_position = tile_center_pos + fallout_state = true + fallout_scale_progress = 1.0 + fallout_respawn_delay_timer = 0.0 + velocity = Vector2.ZERO + current_direction = Direction.DOWN + facing_direction_vector = Vector2.DOWN + _set_animation("FALL") + +@rpc("any_peer", "reliable") +func _sync_respawn_from_fallout(safe_pos: Vector2): + if not is_multiplayer_authority(): + global_position = safe_pos + fallout_state = false + fallout_scale_progress = 1.0 + fallout_respawn_delay_timer = 0.0 + scale = Vector2.ONE + rotation = 0.0 + velocity = Vector2.ZERO + fallout_respawn_stun_timer = FALLOUT_RESPAWN_STUN_DURATION + _set_animation("IDLE") + @rpc("any_peer", "reliable") func _sync_respawn(spawn_pos: Vector2): if not is_multiplayer_authority(): # being_held_by already cleared via RPC in _die() # Holder already dropped us via _force_holder_to_drop RPC - # Re-enable collision in case it was disabled while being carried + # Re-enable collision in case it was disabled while being carried (layer 1, mask 1|2|64) set_collision_layer_value(1, true) set_collision_mask_value(1, true) + set_collision_mask_value(2, true) set_collision_mask_value(7, true) # Re-enable wall collision! # Reset health and state