From 9b8d84357f48022a8c98465e90c4e9a2b79858c0 Mon Sep 17 00:00:00 2001 From: Elrinth Date: Tue, 3 Feb 2026 02:42:51 +0100 Subject: [PATCH] added fallout tiles, fixed so all enemys can get affected. --- src/assets/gfx/RPG DUNGEON VOL 3.png | Bin 18911 -> 24502 bytes src/assets/gfx/RPG DUNGEON VOL 3.tres | 40 +- src/scenes/boss_room_test.tscn | 4 +- src/scenes/enemy_humanoid.tscn | 98 +++-- src/scenes/fallout.tscn | 3 + src/scenes/game_world.tscn | 108 +++++ src/scenes/player.tscn | 36 +- src/scripts/attack_bomb.gd | 121 ++++-- src/scripts/dungeon_generator.gd | 338 ++++++++++++++-- src/scripts/enemy_base.gd | 95 ++++- src/scripts/enemy_bat.gd | 3 + src/scripts/enemy_hand.gd | 2 + src/scripts/enemy_humanoid.gd | 39 +- src/scripts/floor_switch.gd | 29 +- src/scripts/game_world.gd | 563 ++++++++++++++++++++++---- src/scripts/interactable_object.gd | 21 + src/scripts/item_loot_helper.gd | 9 +- src/scripts/loot.gd | 49 ++- src/scripts/player.gd | 280 ++++++++++--- 19 files changed, 1559 insertions(+), 279 deletions(-) create mode 100644 src/scenes/fallout.tscn diff --git a/src/assets/gfx/RPG DUNGEON VOL 3.png b/src/assets/gfx/RPG DUNGEON VOL 3.png index 67c4fb9be79c32e8f687418c4f429c2aa1f8079a..48eabc76724fc8af359b2f5b5fb0126e13e72571 100644 GIT binary patch literal 24502 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 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