diff options
| -rw-r--r-- | assets/definitions/Items/small_healing_potion.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/actor/EntityInspector.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/actor/Player.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/actor/Shadow.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/actor/Skeleton.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/actor/morcegao.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/tiles/floor.tres | 2 | ||||
| -rw-r--r-- | assets/definitions/tiles/wall.tres | 2 | ||||
| -rw-r--r-- | scenes/Game.tscn | 30 | ||||
| -rw-r--r-- | scenes/Inspector.tscn | 2 | ||||
| -rw-r--r-- | scripts/Entities/Actions/Action.cs | 69 | ||||
| -rw-r--r-- | scripts/Entities/Actions/Action.cs.uid (renamed from scripts/entities/actions/Action.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/BumpAction.cs (renamed from scripts/entities/actions/BumpAction.cs) | 14 | ||||
| -rw-r--r-- | scripts/Entities/Actions/BumpAction.cs.uid (renamed from scripts/entities/actions/BumpAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/DirectionalAction.cs | 37 | ||||
| -rw-r--r-- | scripts/Entities/Actions/DirectionalAction.cs.uid (renamed from scripts/entities/actions/DirectionalAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/DropAction.cs | 17 | ||||
| -rw-r--r-- | scripts/Entities/Actions/DropAction.cs.uid (renamed from scripts/entities/actions/DropAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/ItemAction.cs | 39 | ||||
| -rw-r--r-- | scripts/Entities/Actions/ItemAction.cs.uid (renamed from scripts/entities/actions/ItemAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/MeleeAction.cs (renamed from scripts/entities/actions/MeleeAction.cs) | 21 | ||||
| -rw-r--r-- | scripts/Entities/Actions/MeleeAction.cs.uid (renamed from scripts/entities/actions/MeleeAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/MovementAction.cs (renamed from scripts/entities/actions/MovementAction.cs) | 10 | ||||
| -rw-r--r-- | scripts/Entities/Actions/MovementAction.cs.uid (renamed from scripts/entities/actions/MovementAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/PickUpAction.cs | 51 | ||||
| -rw-r--r-- | scripts/Entities/Actions/PickUpAction.cs.uid (renamed from scripts/entities/actions/PickUpAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actions/WaitAction.cs (renamed from scripts/entities/actions/WaitAction.cs) | 8 | ||||
| -rw-r--r-- | scripts/Entities/Actions/WaitAction.cs.uid (renamed from scripts/entities/actions/WaitAction.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/AI/BaseAI.cs | 54 | ||||
| -rw-r--r-- | scripts/Entities/Actors/AI/BaseAI.cs.uid (renamed from scripts/entities/actors/AI/BaseAI.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/AI/HostileEnemyAI.cs (renamed from scripts/entities/actors/AI/HostileEnemyAI.cs) | 46 | ||||
| -rw-r--r-- | scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid (renamed from scripts/entities/actors/AI/HostileEnemyAI.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Actor.cs (renamed from scripts/entities/actors/Actor.cs) | 192 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Actor.cs.uid (renamed from scripts/entities/actors/Actor.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/ActorDefinition.cs (renamed from scripts/entities/actors/ActorDefinition.cs) | 4 | ||||
| -rw-r--r-- | scripts/Entities/Actors/ActorDefinition.cs.uid (renamed from scripts/entities/actors/ActorDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Enemy.cs (renamed from scripts/entities/actors/Enemy.cs) | 41 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Enemy.cs.uid (renamed from scripts/entities/actors/Enemy.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/EnemyDefinition.cs (renamed from scripts/entities/actors/EnemyDefinition.cs) | 6 | ||||
| -rw-r--r-- | scripts/Entities/Actors/EnemyDefinition.cs.uid (renamed from scripts/entities/actors/EnemyDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Inventory.cs | 46 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Inventory.cs.uid (renamed from scripts/entities/actors/Inventory.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Player.cs (renamed from scripts/entities/actors/Player.cs) | 19 | ||||
| -rw-r--r-- | scripts/Entities/Actors/Player.cs.uid (renamed from scripts/entities/actors/Player.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Actors/PlayerDefinition.cs (renamed from scripts/entities/actors/PlayerDefinition.cs) | 5 | ||||
| -rw-r--r-- | scripts/Entities/Actors/PlayerDefinition.cs.uid (renamed from scripts/entities/actors/PlayerDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Entity.cs (renamed from scripts/entities/Entity.cs) | 99 | ||||
| -rw-r--r-- | scripts/Entities/Entity.cs.uid (renamed from scripts/entities/Entity.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/EntityDefinition.cs (renamed from scripts/entities/EntityDefinition.cs) | 5 | ||||
| -rw-r--r-- | scripts/Entities/EntityDefinition.cs.uid (renamed from scripts/entities/EntityDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Items/ConsumableItem.cs (renamed from scripts/entities/items/ConsumableItem.cs) | 15 | ||||
| -rw-r--r-- | scripts/Entities/Items/ConsumableItem.cs.uid (renamed from scripts/entities/items/ConsumableItem.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Items/ConsumableItemDefinition.cs (renamed from scripts/entities/items/ConsumableItemDefinition.cs) | 2 | ||||
| -rw-r--r-- | scripts/Entities/Items/ConsumableItemDefinition.cs.uid (renamed from scripts/entities/items/ConsumableItemDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Items/HealingConsumable.cs | 30 | ||||
| -rw-r--r-- | scripts/Entities/Items/HealingConsumable.cs.uid (renamed from scripts/entities/items/HealingConsumable.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Entities/Items/HealingConsumableDefinition.cs (renamed from scripts/entities/items/HealingConsumableDefinition.cs) | 9 | ||||
| -rw-r--r-- | scripts/Entities/Items/HealingConsumableDefinition.cs.uid (renamed from scripts/entities/items/HealingConsumableDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/GUI/Details.cs | 43 | ||||
| -rw-r--r-- | scripts/GUI/Hud.cs | 17 | ||||
| -rw-r--r-- | scripts/GUI/InventoryMenu.cs | 47 | ||||
| -rw-r--r-- | scripts/GUI/ItemMenuEntry.cs | 61 | ||||
| -rw-r--r-- | scripts/GUI/Message.cs | 45 | ||||
| -rw-r--r-- | scripts/GUI/MessageLog.cs | 12 | ||||
| -rw-r--r-- | scripts/Game.cs | 59 | ||||
| -rw-r--r-- | scripts/InputHandling/BaseInputHandler.cs (renamed from scripts/input/BaseInputHandler.cs) | 19 | ||||
| -rw-r--r-- | scripts/InputHandling/BaseInputHandler.cs.uid (renamed from scripts/input/BaseInputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/InputHandling/GameOverInputHandler.cs (renamed from scripts/input/GameOverInputHandler.cs) | 9 | ||||
| -rw-r--r-- | scripts/InputHandling/GameOverInputHandler.cs.uid (renamed from scripts/input/GameOverInputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/InputHandling/InputHandler.cs | 58 | ||||
| -rw-r--r-- | scripts/InputHandling/InputHandler.cs.uid (renamed from scripts/input/InputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/InputHandling/InspectInputHandler.cs (renamed from scripts/input/InspectInputHandler.cs) | 42 | ||||
| -rw-r--r-- | scripts/InputHandling/InspectInputHandler.cs.uid (renamed from scripts/input/InspectInputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/InputHandling/InventoryInputHandler.cs | 75 | ||||
| -rw-r--r-- | scripts/InputHandling/InventoryInputHandler.cs.uid (renamed from scripts/input/InventoryInputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/InputHandling/MainGameInputHandler.cs | 58 | ||||
| -rw-r--r-- | scripts/InputHandling/MainGameInputHandler.cs.uid (renamed from scripts/input/MainGameInputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/InputHandling/PickupInputHandler.cs (renamed from scripts/input/PickupInputHandler.cs) | 33 | ||||
| -rw-r--r-- | scripts/InputHandling/PickupInputHandler.cs.uid (renamed from scripts/input/PickupInputHandler.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/DungeonGenerator.cs (renamed from scripts/map/DungeonGenerator.cs) | 311 | ||||
| -rw-r--r-- | scripts/Map/DungeonGenerator.cs.uid (renamed from scripts/map/DungeonGenerator.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/FieldOfView.cs (renamed from scripts/map/FieldOfView.cs) | 51 | ||||
| -rw-r--r-- | scripts/Map/FieldOfView.cs.uid (renamed from scripts/map/FieldOfView.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/Map.cs | 98 | ||||
| -rw-r--r-- | scripts/Map/Map.cs.uid (renamed from scripts/map/Map.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/MapData.cs | 318 | ||||
| -rw-r--r-- | scripts/Map/MapData.cs.uid (renamed from scripts/map/MapData.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/MapDivision.cs | 113 | ||||
| -rw-r--r-- | scripts/Map/MapDivision.cs.uid (renamed from scripts/map/MapDivision.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/Tile.cs (renamed from scripts/map/Tile.cs) | 95 | ||||
| -rw-r--r-- | scripts/Map/Tile.cs.uid (renamed from scripts/map/Tile.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Map/TileDefinition.cs (renamed from scripts/map/TileDefinition.cs) | 2 | ||||
| -rw-r--r-- | scripts/Map/TileDefinition.cs.uid (renamed from scripts/map/TileDefinition.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Time/TurnManager.cs | 102 | ||||
| -rw-r--r-- | scripts/Utils/Grid.cs | 12 | ||||
| -rw-r--r-- | scripts/Utils/Inspector.cs (renamed from scripts/entities/actors/Inspector.cs) | 28 | ||||
| -rw-r--r-- | scripts/Utils/Inspector.cs.uid (renamed from scripts/entities/actors/Inspector.cs.uid) | 0 | ||||
| -rw-r--r-- | scripts/Utils/MessageLogData.cs | 59 | ||||
| -rw-r--r-- | scripts/Utils/SignalBus.cs | 19 | ||||
| -rw-r--r-- | scripts/entities/actions/Action.cs | 44 | ||||
| -rw-r--r-- | scripts/entities/actions/DirectionalAction.cs | 30 | ||||
| -rw-r--r-- | scripts/entities/actions/DropAction.cs | 13 | ||||
| -rw-r--r-- | scripts/entities/actions/ItemAction.cs | 17 | ||||
| -rw-r--r-- | scripts/entities/actions/PickUpAction.cs | 34 | ||||
| -rw-r--r-- | scripts/entities/actors/AI/BaseAI.cs | 41 | ||||
| -rw-r--r-- | scripts/entities/actors/Inventory.cs | 39 | ||||
| -rw-r--r-- | scripts/entities/items/HealingConsumable.cs | 28 | ||||
| -rw-r--r-- | scripts/input/InputHandler.cs | 52 | ||||
| -rw-r--r-- | scripts/input/InventoryInputHandler.cs | 65 | ||||
| -rw-r--r-- | scripts/input/MainGameInputHandler.cs | 47 | ||||
| -rw-r--r-- | scripts/map/Map.cs | 86 | ||||
| -rw-r--r-- | scripts/map/MapData.cs | 272 | ||||
| -rw-r--r-- | scripts/map/MapDivision.cs | 98 |
113 files changed, 2011 insertions, 1528 deletions
diff --git a/assets/definitions/Items/small_healing_potion.tres b/assets/definitions/Items/small_healing_potion.tres index 56d1294..4a75d72 100644 --- a/assets/definitions/Items/small_healing_potion.tres +++ b/assets/definitions/Items/small_healing_potion.tres @@ -1,6 +1,6 @@ [gd_resource type="Resource" script_class="HealingConsumableDefinition" load_steps=3 format=3 uid="uid://bm6yx6rwh8bds"] -[ext_resource type="Script" uid="uid://b3qy4gtjfci14" path="res://scripts/entities/items/HealingConsumableDefinition.cs" id="1_4dl2g"] +[ext_resource type="Script" uid="uid://b3qy4gtjfci14" path="res://scripts/Entities/Items/HealingConsumableDefinition.cs" id="1_4dl2g"] [ext_resource type="Texture2D" uid="uid://b7drpdbk4lggb" path="res://assets/sprites/items/small_health_potion.png" id="2_esrbk"] [resource] diff --git a/assets/definitions/actor/EntityInspector.tres b/assets/definitions/actor/EntityInspector.tres index d085b79..8b9b79e 100644 --- a/assets/definitions/actor/EntityInspector.tres +++ b/assets/definitions/actor/EntityInspector.tres @@ -1,6 +1,6 @@ [gd_resource type="Resource" script_class="ActorDefinition" load_steps=2 format=3 uid="uid://bq3mbgtvjgrg3"] -[ext_resource type="Script" uid="uid://crxw1e37xlrrt" path="res://scripts/entities/actors/ActorDefinition.cs" id="1_na4h7"] +[ext_resource type="Script" uid="uid://crxw1e37xlrrt" path="res://scripts/Entities/Actors/ActorDefinition.cs" id="1_na4h7"] [resource] script = ExtResource("1_na4h7") diff --git a/assets/definitions/actor/Player.tres b/assets/definitions/actor/Player.tres index 56aa604..36c24c5 100644 --- a/assets/definitions/actor/Player.tres +++ b/assets/definitions/actor/Player.tres @@ -1,6 +1,6 @@ [gd_resource type="Resource" script_class="PlayerDefinition" load_steps=4 format=3 uid="uid://7pwmjcgbcyoo"] -[ext_resource type="Script" uid="uid://bd78nfh1tsjq6" path="res://scripts/entities/actors/PlayerDefinition.cs" id="1_2k33r"] +[ext_resource type="Script" uid="uid://bd78nfh1tsjq6" path="res://scripts/Entities/Actors/PlayerDefinition.cs" id="1_2k33r"] [ext_resource type="Texture2D" uid="uid://w0808ug4al66" path="res://assets/sprites/actors/generic_grave.png" id="1_m72ac"] [ext_resource type="Texture2D" uid="uid://dwky8qc2y602k" path="res://assets/sprites/actors/player.png" id="3_frwfa"] diff --git a/assets/definitions/actor/Shadow.tres b/assets/definitions/actor/Shadow.tres index cfa0995..8493618 100644 --- a/assets/definitions/actor/Shadow.tres +++ b/assets/definitions/actor/Shadow.tres @@ -1,7 +1,7 @@ [gd_resource type="Resource" script_class="EnemyDefinition" load_steps=4 format=3 uid="uid://dqd714474t4ic"] [ext_resource type="Texture2D" uid="uid://w0808ug4al66" path="res://assets/sprites/actors/generic_grave.png" id="1_3bs08"] -[ext_resource type="Script" uid="uid://dkfdm2m2scyks" path="res://scripts/entities/actors/EnemyDefinition.cs" id="1_4jpld"] +[ext_resource type="Script" uid="uid://dkfdm2m2scyks" path="res://scripts/Entities/Actors/EnemyDefinition.cs" id="1_4jpld"] [ext_resource type="Texture2D" uid="uid://br8cqtbd6xcej" path="res://assets/sprites/actors/shadow.png" id="3_kvxyn"] [resource] diff --git a/assets/definitions/actor/Skeleton.tres b/assets/definitions/actor/Skeleton.tres index f67bf56..44a2893 100644 --- a/assets/definitions/actor/Skeleton.tres +++ b/assets/definitions/actor/Skeleton.tres @@ -1,7 +1,7 @@ [gd_resource type="Resource" script_class="EnemyDefinition" load_steps=4 format=3 uid="uid://gt6xqa737dyt"] [ext_resource type="Texture2D" uid="uid://w0808ug4al66" path="res://assets/sprites/actors/generic_grave.png" id="1_7fof3"] -[ext_resource type="Script" uid="uid://dkfdm2m2scyks" path="res://scripts/entities/actors/EnemyDefinition.cs" id="1_m5x88"] +[ext_resource type="Script" uid="uid://dkfdm2m2scyks" path="res://scripts/Entities/Actors/EnemyDefinition.cs" id="1_m5x88"] [ext_resource type="Texture2D" uid="uid://dh5sgjdwkps88" path="res://assets/sprites/actors/skeleton.png" id="2_hhidw"] [resource] diff --git a/assets/definitions/actor/morcegao.tres b/assets/definitions/actor/morcegao.tres index 937679f..65c9a6e 100644 --- a/assets/definitions/actor/morcegao.tres +++ b/assets/definitions/actor/morcegao.tres @@ -1,7 +1,7 @@ [gd_resource type="Resource" script_class="EnemyDefinition" load_steps=4 format=3 uid="uid://cj0kq4sfft8gh"] [ext_resource type="Texture2D" uid="uid://w0808ug4al66" path="res://assets/sprites/actors/generic_grave.png" id="1_hdleo"] -[ext_resource type="Script" uid="uid://dkfdm2m2scyks" path="res://scripts/entities/actors/EnemyDefinition.cs" id="1_m2lyk"] +[ext_resource type="Script" uid="uid://dkfdm2m2scyks" path="res://scripts/Entities/Actors/EnemyDefinition.cs" id="1_m2lyk"] [ext_resource type="Texture2D" uid="uid://cddn56s7w2lc2" path="res://assets/sprites/actors/morcegao.png" id="3_601km"] [resource] diff --git a/assets/definitions/tiles/floor.tres b/assets/definitions/tiles/floor.tres index 92a8da7..44b0097 100644 --- a/assets/definitions/tiles/floor.tres +++ b/assets/definitions/tiles/floor.tres @@ -1,6 +1,6 @@ [gd_resource type="Resource" script_class="TileDefinition" load_steps=3 format=3 uid="uid://dfrremrf4rn15"] -[ext_resource type="Script" uid="uid://ba82a33ov6uuo" path="res://scripts/map/TileDefinition.cs" id="1_snxyj"] +[ext_resource type="Script" uid="uid://ba82a33ov6uuo" path="res://scripts/Map/TileDefinition.cs" id="1_snxyj"] [ext_resource type="Texture2D" uid="uid://ds13x1dpu0v4s" path="res://assets/sprites/tiles/floor.png" id="1_vvyfi"] [resource] diff --git a/assets/definitions/tiles/wall.tres b/assets/definitions/tiles/wall.tres index a8ba785..a4fa75d 100644 --- a/assets/definitions/tiles/wall.tres +++ b/assets/definitions/tiles/wall.tres @@ -1,7 +1,7 @@ [gd_resource type="Resource" script_class="TileDefinition" load_steps=3 format=3 uid="uid://cdluqrbfcu5ng"] [ext_resource type="Texture2D" uid="uid://s5klxirh5r1w" path="res://assets/sprites/tiles/wall.png" id="1_jkwov"] -[ext_resource type="Script" uid="uid://ba82a33ov6uuo" path="res://scripts/map/TileDefinition.cs" id="1_ugwtv"] +[ext_resource type="Script" uid="uid://ba82a33ov6uuo" path="res://scripts/Map/TileDefinition.cs" id="1_ugwtv"] [resource] script = ExtResource("1_ugwtv") diff --git a/scenes/Game.tscn b/scenes/Game.tscn index 4cb5231..09e9825 100644 --- a/scenes/Game.tscn +++ b/scenes/Game.tscn @@ -1,17 +1,17 @@ [gd_scene load_steps=13 format=3 uid="uid://u5h6iqyi8wd0"] [ext_resource type="Script" uid="uid://dwubb28wt4bhe" path="res://scripts/Game.cs" id="1_cpr0p"] -[ext_resource type="Script" uid="uid://ejqmdbc0524i" path="res://scripts/input/MainGameInputHandler.cs" id="3_400sg"] -[ext_resource type="Script" uid="uid://fe2h4is11tnw" path="res://scripts/map/Map.cs" id="3_cpr0p"] -[ext_resource type="Script" uid="uid://bxt6g7t1uvvia" path="res://scripts/input/InputHandler.cs" id="3_s0nni"] -[ext_resource type="Script" uid="uid://dwyr067lwqcsj" path="res://scripts/map/DungeonGenerator.cs" id="4_78awf"] -[ext_resource type="Script" uid="uid://ogqlb3purl6n" path="res://scripts/input/GameOverInputHandler.cs" id="4_g4kob"] -[ext_resource type="Script" uid="uid://bamlnrj5bm1rd" path="res://scripts/input/InspectInputHandler.cs" id="5_g4kob"] +[ext_resource type="Script" uid="uid://ejqmdbc0524i" path="res://scripts/InputHandling/MainGameInputHandler.cs" id="3_400sg"] +[ext_resource type="Script" uid="uid://fe2h4is11tnw" path="res://scripts/Map/Map.cs" id="3_cpr0p"] +[ext_resource type="Script" uid="uid://bxt6g7t1uvvia" path="res://scripts/InputHandling/InputHandler.cs" id="3_s0nni"] +[ext_resource type="Script" uid="uid://dwyr067lwqcsj" path="res://scripts/Map/DungeonGenerator.cs" id="4_78awf"] +[ext_resource type="Script" uid="uid://ogqlb3purl6n" path="res://scripts/InputHandling/GameOverInputHandler.cs" id="4_g4kob"] +[ext_resource type="Script" uid="uid://bamlnrj5bm1rd" path="res://scripts/InputHandling/InspectInputHandler.cs" id="5_g4kob"] [ext_resource type="PackedScene" uid="uid://bryqrafavmwj4" path="res://scenes/Details.tscn" id="5_qy1jj"] -[ext_resource type="Script" uid="uid://bereyrj1s46y5" path="res://scripts/map/FieldOfView.cs" id="5_s0nni"] +[ext_resource type="Script" uid="uid://bereyrj1s46y5" path="res://scripts/Map/FieldOfView.cs" id="5_s0nni"] [ext_resource type="PackedScene" uid="uid://b4h4xyr1g8o50" path="res://scenes/GUI/hud.tscn" id="6_aug50"] -[ext_resource type="Script" uid="uid://dspqgdxg5jji0" path="res://scripts/input/PickupInputHandler.cs" id="10_3xj3m"] -[ext_resource type="Script" uid="uid://bjcjktvyrdh10" path="res://scripts/input/InventoryInputHandler.cs" id="11_mcffj"] +[ext_resource type="Script" uid="uid://dspqgdxg5jji0" path="res://scripts/InputHandling/PickupInputHandler.cs" id="10_3xj3m"] +[ext_resource type="Script" uid="uid://bjcjktvyrdh10" path="res://scripts/InputHandling/InventoryInputHandler.cs" id="11_mcffj"] [node name="Game" type="Node"] script = ExtResource("1_cpr0p") @@ -21,9 +21,7 @@ script = ExtResource("3_cpr0p") [node name="Generator" type="Node" parent="Map"] script = ExtResource("4_78awf") -seed = 989 -useSeed = false -iterations = 8 +UseSeed = false [node name="FieldOfView" type="Node" parent="Map"] script = ExtResource("5_s0nni") @@ -44,16 +42,16 @@ script = ExtResource("3_400sg") [node name="GameOverInputHandler" type="Node" parent="InputHandler"] script = ExtResource("4_g4kob") -[node name="InspectInputHandler" type="Node" parent="InputHandler" node_paths=PackedStringArray("map")] +[node name="InspectInputHandler" type="Node" parent="InputHandler" node_paths=PackedStringArray("Map")] script = ExtResource("5_g4kob") -map = NodePath("../../Map") +Map = NodePath("../../Map") [node name="PickupInputHandler" type="Node" parent="InputHandler"] script = ExtResource("10_3xj3m") -[node name="InventoryInputHandler" type="Node" parent="InputHandler" node_paths=PackedStringArray("map")] +[node name="InventoryInputHandler" type="Node" parent="InputHandler" node_paths=PackedStringArray("Map")] script = ExtResource("11_mcffj") -map = NodePath("../../Map") +Map = NodePath("../../Map") [node name="Camera2D" type="Camera2D" parent="."] offset = Vector2(8, 8) diff --git a/scenes/Inspector.tscn b/scenes/Inspector.tscn index ad2a062..8b3dcdb 100644 --- a/scenes/Inspector.tscn +++ b/scenes/Inspector.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://dyythuxaio6j4"] [ext_resource type="Texture2D" uid="uid://cjjxf4rbj8gku" path="res://assets/sprites/inspector.png" id="1_psyxb"] -[ext_resource type="Script" uid="uid://dxsrtu4b3pi08" path="res://scripts/entities/actors/Inspector.cs" id="2_g62u7"] +[ext_resource type="Script" uid="uid://dxsrtu4b3pi08" path="res://scripts/Utils/Inspector.cs" id="2_g62u7"] [node name="Sprite2D" type="Sprite2D"] texture = ExtResource("1_psyxb") diff --git a/scripts/Entities/Actions/Action.cs b/scripts/Entities/Actions/Action.cs new file mode 100644 index 0000000..b2d6a4b --- /dev/null +++ b/scripts/Entities/Actions/Action.cs @@ -0,0 +1,69 @@ +using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Actions; + +/// <summary> +/// <c>Action</c> representa uma ação no jogo efetuada por um ator. +/// Ações são geradas pelo jogador e pela IA, elas regem os atores do jogo. +/// </summary> +public abstract partial class Action : RefCounted +{ + private Actor actor; + + private int cost; + + public Action(Actor actor) + { + Actor = actor; + // Custo base, subclasses podem sobreescrever isto se quiserem. + Cost = 10; + } + + /// <summary> + /// O ator que realiza a ação. + /// </summary> + public Actor Actor + { + get => actor; + private set + { + actor = value; + } + } + + /// <summary> + /// O custo da ação. + /// </summary> + protected int Cost + { + get => cost; + set + { + cost = value; + } + } + + /// <summary> + /// É conveniente ter acesso ao mapa dentro de uma ação. + /// </summary> + protected MapData MapData + { + get => actor.MapData; + } + + /// <summary> + /// Método que executa a ação. Subclasses da ação devem implementar este método. + /// <example> + /// Exemplo: + /// <code> + /// Action action = new Action(actor); + /// /* . . . */ + /// action.Perform(); + /// </code> + /// </example> + /// </summary> + /// <returns>Se a ação foi executada ou não.</returns> + public abstract bool Perform(); +} diff --git a/scripts/entities/actions/Action.cs.uid b/scripts/Entities/Actions/Action.cs.uid index 9523b0a..9523b0a 100644 --- a/scripts/entities/actions/Action.cs.uid +++ b/scripts/Entities/Actions/Action.cs.uid diff --git a/scripts/entities/actions/BumpAction.cs b/scripts/Entities/Actions/BumpAction.cs index fa2605f..f0a0047 100644 --- a/scripts/entities/actions/BumpAction.cs +++ b/scripts/Entities/Actions/BumpAction.cs @@ -1,4 +1,7 @@ using Godot; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.Entities.Actions; /// <summary> /// Ação de "Esbarramento", utilizada principalmente pelo jogador. @@ -19,11 +22,14 @@ public partial class BumpAction : DirectionalAction Action action; // Se houver um ator no destino, crie uma ação de ataque. - if (GetTarget() != null) { - action = new MeleeAction(actor, Offset); - } else { + if (GetTarget() != null) + { + action = new MeleeAction(Actor, Offset); + } + else + { // Mas se não houver, crie uma ação de movimento. - action = new MovementAction(actor, Offset); + action = new MovementAction(Actor, Offset); } // Executa a ação. diff --git a/scripts/entities/actions/BumpAction.cs.uid b/scripts/Entities/Actions/BumpAction.cs.uid index f5ce3f8..f5ce3f8 100644 --- a/scripts/entities/actions/BumpAction.cs.uid +++ b/scripts/Entities/Actions/BumpAction.cs.uid diff --git a/scripts/Entities/Actions/DirectionalAction.cs b/scripts/Entities/Actions/DirectionalAction.cs new file mode 100644 index 0000000..e32e9f2 --- /dev/null +++ b/scripts/Entities/Actions/DirectionalAction.cs @@ -0,0 +1,37 @@ +using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Actions; + +/// <summary> +/// Ação direcionada. Esta ação é acompanhada com um vetor que representa uma +/// distância tendo como ponto de partida o ator. +/// </summary> +public abstract partial class DirectionalAction : Action +{ + public DirectionalAction(Actor actor, Vector2I offset) : base(actor) + { + Offset = offset; + } + + /// <summary> + /// Direção/distância do ator da ação. + /// Seu significado depende da ação que implementará esta classe. + /// </summary> + public Vector2I Offset { get; private set; } + + /// <summary> + /// Coordenada do alvo da ação. + /// </summary> + public Vector2I Destination { get => Actor.GridPosition + Offset; } + + /// <summary> + /// Função que obtém o alvo da ação, se houver. + /// </summary> + /// <returns>O ator alvo da ação, nulo se não houver.</returns> + protected Entity GetTarget() + { + return MapData.GetBlockingEntityAtPosition(Destination); + } +} diff --git a/scripts/entities/actions/DirectionalAction.cs.uid b/scripts/Entities/Actions/DirectionalAction.cs.uid index 901756a..901756a 100644 --- a/scripts/entities/actions/DirectionalAction.cs.uid +++ b/scripts/Entities/Actions/DirectionalAction.cs.uid diff --git a/scripts/Entities/Actions/DropAction.cs b/scripts/Entities/Actions/DropAction.cs new file mode 100644 index 0000000..00ddd7e --- /dev/null +++ b/scripts/Entities/Actions/DropAction.cs @@ -0,0 +1,17 @@ +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities.Items; + +namespace TheLegendOfGustav.Entities.Actions; + +public partial class DropAction : ItemAction +{ + public DropAction(Player player, ConsumableItem item) : base(player, item) + { + } + + public override bool Perform() + { + Player.Inventory.Drop(Item); + return true; + } +}
\ No newline at end of file diff --git a/scripts/entities/actions/DropAction.cs.uid b/scripts/Entities/Actions/DropAction.cs.uid index 98ed82f..98ed82f 100644 --- a/scripts/entities/actions/DropAction.cs.uid +++ b/scripts/Entities/Actions/DropAction.cs.uid diff --git a/scripts/Entities/Actions/ItemAction.cs b/scripts/Entities/Actions/ItemAction.cs new file mode 100644 index 0000000..14c5d93 --- /dev/null +++ b/scripts/Entities/Actions/ItemAction.cs @@ -0,0 +1,39 @@ +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities.Items; + +namespace TheLegendOfGustav.Entities.Actions; + +public partial class ItemAction : Action +{ + private ConsumableItem item; + private Player player; + + public ItemAction(Player player, ConsumableItem item) : base(player) + { + Item = item; + Player = player; + } + + public Player Player + { + get => player; + private set + { + player = value; + } + } + + protected ConsumableItem Item + { + get => item; + set + { + item = value; + } + } + + public override bool Perform() + { + return item.Activate(this); + } +}
\ No newline at end of file diff --git a/scripts/entities/actions/ItemAction.cs.uid b/scripts/Entities/Actions/ItemAction.cs.uid index c8c8e23..c8c8e23 100644 --- a/scripts/entities/actions/ItemAction.cs.uid +++ b/scripts/Entities/Actions/ItemAction.cs.uid diff --git a/scripts/entities/actions/MeleeAction.cs b/scripts/Entities/Actions/MeleeAction.cs index 09c5cc7..fcd8368 100644 --- a/scripts/entities/actions/MeleeAction.cs +++ b/scripts/Entities/Actions/MeleeAction.cs @@ -1,4 +1,8 @@ using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.Entities.Actions; /// <summary> /// Ação de ataque físico. Uma ação direcionada que ataca um alvo. @@ -18,31 +22,34 @@ public partial class MeleeAction : DirectionalAction Entity potentialTarget = GetTarget(); // Só podemos atacar atores. - if (potentialTarget is not Actor) { + if (potentialTarget is not TheLegendOfGustav.Entities.Actors.Actor) + { return false; } Actor target = (Actor)potentialTarget; - // Se não houver um ator na direção, não podemos continuar. // Isto é uma ação gratuita. if (target == null) return false; // não podemos ter dano negativo. - int damage = actor.Atk - target.Def; + int damage = Actor.Atk - target.Def; - string attackDesc = $"{actor.DisplayName} ataca {target.DisplayName}"; + string attackDesc = $"{Actor.DisplayName} ataca {target.DisplayName}"; - if (damage > 0) { + if (damage > 0) + { attackDesc += $" e remove {damage} de HP."; target.Hp -= damage; - } else { + } + else + { attackDesc += $" mas {target.DisplayName} tem músculos de aço."; } MessageLogData.Instance.AddMessage(attackDesc); - actor.Energy -= cost; + Actor.Energy -= Cost; return true; } } diff --git a/scripts/entities/actions/MeleeAction.cs.uid b/scripts/Entities/Actions/MeleeAction.cs.uid index bc97619..bc97619 100644 --- a/scripts/entities/actions/MeleeAction.cs.uid +++ b/scripts/Entities/Actions/MeleeAction.cs.uid diff --git a/scripts/entities/actions/MovementAction.cs b/scripts/Entities/Actions/MovementAction.cs index 403ec0a..0ac842c 100644 --- a/scripts/entities/actions/MovementAction.cs +++ b/scripts/Entities/Actions/MovementAction.cs @@ -1,4 +1,8 @@ using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Actions; /// <summary> /// Ação de movimento. Movimenta o ator para a direção de seu Offset. @@ -12,14 +16,14 @@ public partial class MovementAction : DirectionalAction public override bool Perform() { // Não anda se o destino for um tile sólido. - if (!Map_Data.GetTile(Destination).IsWalkable) return true; + if (!MapData.GetTile(Destination).IsWalkable) return true; // Não anda se o destino for oculpado por um ator. // Na maioria dos casos, essa condição nunca é verdadeira. if (GetTarget() != null) return true; - actor.Walk(Offset); - actor.Energy -= cost; + Actor.Walk(Offset); + Actor.Energy -= Cost; return true; } diff --git a/scripts/entities/actions/MovementAction.cs.uid b/scripts/Entities/Actions/MovementAction.cs.uid index 07569ef..07569ef 100644 --- a/scripts/entities/actions/MovementAction.cs.uid +++ b/scripts/Entities/Actions/MovementAction.cs.uid diff --git a/scripts/Entities/Actions/PickUpAction.cs b/scripts/Entities/Actions/PickUpAction.cs new file mode 100644 index 0000000..0dbd672 --- /dev/null +++ b/scripts/Entities/Actions/PickUpAction.cs @@ -0,0 +1,51 @@ +using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Utils; +using TheLegendOfGustav.Entities.Items; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Actions; + +public partial class PickupAction : DirectionalAction +{ + private Player player; + + public PickupAction(Player player, Vector2I offset) : base(player, offset) + { + Player = player; + // Pegar itens requer um tempo menor. + Cost = 2; + } + + protected Player Player + { + get => player; + private set + { + player = value; + } + } + + public override bool Perform() + { + ConsumableItem item = MapData.GetFirstItemAtPosition(Destination); + + if (item == null) + { + MessageLogData.Instance.AddMessage("Não tem item aqui."); + return false; + } + + if (player.Inventory.Items.Count >= player.Inventory.Capacity) + { + MessageLogData.Instance.AddMessage("Seu inventário está cheio"); + return false; + } + + MapData.RemoveEntity(item); + player.Inventory.Add(item); + + player.Energy -= Cost; + return true; + } +}
\ No newline at end of file diff --git a/scripts/entities/actions/PickUpAction.cs.uid b/scripts/Entities/Actions/PickUpAction.cs.uid index 7ca9c72..7ca9c72 100644 --- a/scripts/entities/actions/PickUpAction.cs.uid +++ b/scripts/Entities/Actions/PickUpAction.cs.uid diff --git a/scripts/entities/actions/WaitAction.cs b/scripts/Entities/Actions/WaitAction.cs index c26d884..011703b 100644 --- a/scripts/entities/actions/WaitAction.cs +++ b/scripts/Entities/Actions/WaitAction.cs @@ -1,4 +1,6 @@ -using Godot; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.Entities.Actions; /// <summary> /// Ação da inação. Ação que realiza nada. @@ -96,9 +98,7 @@ public partial class WaitAction : Action //@@@@@@@@@@@@*====+==++=*@. @ @@* .@* @ .*#. @ @* #: -@ @@% @ -+ =*%@@= @@: @ @ +. #+ *@@@@@@@@@@@@@@@@@@@@@@@ //@@@@@@@@@@@======++====+*%@@@@%%%@@@@%*+#@@@@@%#%@@#=+**=. =**+=***==*#*: :%@@@@@@@@@**=+#*==*###*=. -=**+::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //@@@@@@@@@@+==+==++=====++=====++=+++++++++===+++++#% +@@@* :#%= =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - actor.Energy -= cost; + Actor.Energy -= Cost; return true; } - - }
\ No newline at end of file diff --git a/scripts/entities/actions/WaitAction.cs.uid b/scripts/Entities/Actions/WaitAction.cs.uid index 120c8c1..120c8c1 100644 --- a/scripts/entities/actions/WaitAction.cs.uid +++ b/scripts/Entities/Actions/WaitAction.cs.uid diff --git a/scripts/Entities/Actors/AI/BaseAI.cs b/scripts/Entities/Actors/AI/BaseAI.cs new file mode 100644 index 0000000..bdd1e61 --- /dev/null +++ b/scripts/Entities/Actors/AI/BaseAI.cs @@ -0,0 +1,54 @@ +using Godot; + +namespace TheLegendOfGustav.Entities.Actors.AI; + +/// <summary> +/// Enum das diferentes IAs disponíveis. +/// </summary> +public enum AIType +{ + None, + DefaultHostile +}; + +/// <summary> +/// base para as IAs do jogo. +/// </summary> +public abstract partial class BaseAI : Node +{ + /// <summary> + /// Corpo controlado pela IA. + /// O corpo é a marionete da alma. + /// </summary> + protected Actor Body { get; set; } + + public override void _Ready() + { + base._Ready(); + // Por padrão, a IA é filha do nó de seu corpo. + Body = GetParent<Actor>(); + } + + /// <summary> + /// Computa um único turno para o ator controlado. + /// Aviso: NPCs não possuem ações gratuitas. + /// A IA SEMPRE precisa executar uma ação que custe energia. + /// </summary> + public abstract void Perform(); + + /// <summary> + /// Utiliza o pathfinder do mapa para obter um caminho + /// da posição atual do ator para um destino qualquer. + /// </summary> + /// <param name="destination">Destino</param> + /// <returns>Vetor com vetores, passo a passo para chegar no destino.</returns> + public Godot.Collections.Array<Vector2> GetPathTo(Vector2I destination) + { + // Arrays do Godot são muito mais confortáveis de manipular, então + // eu converto o Array do C# em um array do Godot antes de retornar o caminho. + Godot.Collections.Array<Vector2> list = []; + Vector2[] path = Body.MapData.Pathfinder.GetPointPath(Body.GridPosition, destination); + list.AddRange(path); + return list; + } +}
\ No newline at end of file diff --git a/scripts/entities/actors/AI/BaseAI.cs.uid b/scripts/Entities/Actors/AI/BaseAI.cs.uid index b23724c..b23724c 100644 --- a/scripts/entities/actors/AI/BaseAI.cs.uid +++ b/scripts/Entities/Actors/AI/BaseAI.cs.uid diff --git a/scripts/entities/actors/AI/HostileEnemyAI.cs b/scripts/Entities/Actors/AI/HostileEnemyAI.cs index e7efd26..dbcf98d 100644 --- a/scripts/entities/actors/AI/HostileEnemyAI.cs +++ b/scripts/Entities/Actors/AI/HostileEnemyAI.cs @@ -1,4 +1,7 @@ using Godot; +using TheLegendOfGustav.Entities.Actions; + +namespace TheLegendOfGustav.Entities.Actors.AI; /// <summary> /// Uma IA simples. Sempre tentará atacar o jogador com ataques corpo a corpo. @@ -6,16 +9,16 @@ using Godot; public partial class HostileEnemyAI : BaseAI { /// <summary> - /// Caminho até a última posição conhecida do jogador. - /// </summary> - private Godot.Collections.Array<Vector2> path = []; + /// Caminho até a última posição conhecida do jogador. + /// </summary> + private Godot.Collections.Array<Vector2> Path { get; set; } = []; public override void Perform() { // O alvo da IA sempre é o jogador. - Player target = body.Map_Data.Player; + Player target = Body.MapData.Player; // Vetor que parte do inimigo até o jogador. - Vector2I offset = target.GridPosition - body.GridPosition; + Vector2I offset = target.GridPosition - Body.GridPosition; // Distância entre o inimigo e o jogador. Leva em consideração somente // um dos eixos. int distance = int.Max(int.Abs(offset.X), int.Abs(offset.Y)); @@ -24,18 +27,21 @@ public partial class HostileEnemyAI : BaseAI Action action; // Só faz sentido atacar o jogador se o inimigo estiver visível. - if (body.Map_Data.GetTile(body.GridPosition).IsInView) { + if (Body.MapData.GetTile(Body.GridPosition).IsInView) + { // Se o inimigo consegue ver que o jogador está morto, // IT'S OVER. - if (!target.IsAlive) { - action = new WaitAction(body); + if (!target.IsAlive) + { + action = new WaitAction(Body); action.Perform(); return; } // Se estiver do lado do jogador, ataque. - if (distance <= 1) { - action = new MeleeAction(body, offset); + if (distance <= 1) + { + action = new MeleeAction(Body, offset); action.Perform(); // Executada a ação, acabamos nosso turno aqui. return; @@ -44,32 +50,34 @@ public partial class HostileEnemyAI : BaseAI // Se o inimigo estiver visível para o jogador, // consideramos que ele também consiga ver o jogador. // Logo, atualizamos o caminho para a posição atual do jogador. - path = GetPathTo(target.GridPosition); + Path = GetPathTo(target.GridPosition); // O primeiro passo é a posição atual do inimigo, podemos remover. - path.RemoveAt(0); + Path.RemoveAt(0); } // Se existir um caminho conhecido para o jogador. - if (path.Count > 0) { + if (Path.Count > 0) + { // Pegamos o próximo passo para o destino. - Vector2I destination = (Vector2I) path[0]; + Vector2I destination = (Vector2I)Path[0]; // Se tiver o caminho estiver bloqueado, paramos o nosso turno aqui. - if (body.Map_Data.GetBlockingEntityAtPosition(destination) != null) { - action = new WaitAction(body); + if (Body.MapData.GetBlockingEntityAtPosition(destination) != null) + { + action = new WaitAction(Body); action.Perform(); return; } // Caso o contrário, criamos uma nova ação de movimentação e a executamos. - action = new MovementAction(body, destination - body.GridPosition); + action = new MovementAction(Body, destination - Body.GridPosition); action.Perform(); // Podemos remover o passo do caminho. - path.RemoveAt(0); + Path.RemoveAt(0); return; } // Senão, espere. - action = new WaitAction(body); + action = new WaitAction(Body); action.Perform(); return; } diff --git a/scripts/entities/actors/AI/HostileEnemyAI.cs.uid b/scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid index 0fa2c32..0fa2c32 100644 --- a/scripts/entities/actors/AI/HostileEnemyAI.cs.uid +++ b/scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid diff --git a/scripts/entities/actors/Actor.cs b/scripts/Entities/Actors/Actor.cs index 1cb1f37..7e6685f 100644 --- a/scripts/entities/actors/Actor.cs +++ b/scripts/Entities/Actors/Actor.cs @@ -1,4 +1,8 @@ using Godot; +using TheLegendOfGustav.Map; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.Entities.Actors; /// <summary> /// A classe de ator define um personagem no jogo. @@ -6,137 +10,163 @@ using Godot; [GlobalClass] public partial class Actor : Entity { + #region Fields + private int mp; + private int hp; + + private int energy; + #endregion + + #region Constructors + public Actor(Vector2I initialPosition, MapData map, ActorDefinition definition) : base(initialPosition, map, definition) + { + SetDefinition(definition); + } + #endregion + + #region Signals /// <summary> - /// Sinal emitido toda vez que o HP mudar. - /// </summary> - /// <param name="hp">Novo HP</param> - /// <param name="maxHp">Quantidade máxima de HP.</param> + /// Sinal emitido toda vez que o HP mudar. + /// </summary> + /// <param name="hp">Novo HP</param> + /// <param name="maxHp">Quantidade máxima de HP.</param> [Signal] public delegate void HealthChangedEventHandler(int hp, int maxHp); /// <summary> - /// Sinal emitido se o ator morrer. - /// </summary> + /// Sinal emitido se o ator morrer. + /// </summary> [Signal] public delegate void DiedEventHandler(); + #endregion - + #region Properties /// <summary> - /// A definição do ator possui caracterísitcas padrões que definem - /// o ator em questão. + /// Se o ator está vivo. /// </summary> - private ActorDefinition definition; - - /// <summary> - /// Se o ator está vivo. - /// </summary> public bool IsAlive { get => Hp > 0; } - private int energy; /// <summary> /// Utilizado no sistema de turnos. /// Enquanto o ator tiver energia, ele poderá realizar turnos. /// </summary> - public int Energy - { + public int Energy + { get => energy; set { - if (value > Speed) { + if (value > Speed) + { energy = Speed; - } else { + } + else + { energy = value; } } } /// <summary> - /// Taxa de recarga de energia. - /// </summary> - public int Speed { get => definition.Speed; } - - /// <summary> - /// Executado uma vez por turno, - /// </summary> - public void RechargeEnergy() { - Energy += Speed; - } + /// Taxa de recarga de energia. + /// </summary> + public int Speed { get => Definition.Speed; } - private int hp; /// <summary> - /// HP máximo do ator. - /// </summary> + /// HP máximo do ator. + /// </summary> public int MaxHp { get; private set; } /// <summary> - /// HP atual do ator. - /// </summary> - public int Hp { + /// HP atual do ator. + /// </summary> + public int Hp + { get => hp; - set { + set + { // Esta propriedade impede que o HP seja maior que o máximo. hp = int.Clamp(value, 0, MaxHp); EmitSignal(SignalName.HealthChanged, Hp, MaxHp); - if (hp <= 0) { + if (hp <= 0) + { Die(); } } } - private int mp; /// <summary> - /// Máximo de mana do ator. - /// </summary> + /// Máximo de mana do ator. + /// </summary> public int MaxMp { get; private set; } /// <summary> - /// Mana atual do ator. - /// </summary> - public int Mp { + /// Mana atual do ator. + /// </summary> + public int Mp + { get => mp; - set { + set + { mp = int.Clamp(value, 0, MaxMp); } } /// <summary> - /// Estatística de ataque - /// </summary> + /// Estatística de ataque + /// </summary> public int Atk { get; private set; } /// <summary> - /// Estatística de defesa. - /// </summary> + /// Estatística de defesa. + /// </summary> public int Def { get; private set; } /// <summary> - /// Estatística mental. - /// </summary> + /// Estatística mental. + /// </summary> public int Men { get; private set; } /// <summary> - /// Move o ator para uma localização. Veja MovementAction. - /// </summary> - /// <param name="offset">Vetor que parte da posição do ator até o seu destino.</param> - public void Walk(Vector2I offset) { + /// A definição do ator possui caracterísitcas padrões que definem + /// o ator em questão. + /// </summary> + private ActorDefinition Definition + { + get; + set; + } + #endregion + + #region Methods + /// <summary> + /// Executado uma vez por turno, + /// </summary> + public void RechargeEnergy() + { + Energy += Speed; + } + + /// <summary> + /// Move o ator para uma localização. Veja MovementAction. + /// </summary> + /// <param name="offset">Vetor que parte da posição do ator até o seu destino.</param> + public void Walk(Vector2I offset) + { // Cada ator tem um peso no sistema de pathfinding. // Sempre que ele se mover, removemos seu peso da posição antiga - Map_Data.UnregisterBlockingEntity(this); + MapData.UnregisterBlockingEntity(this); GridPosition += offset; // E colocamos na próxima. - Map_Data.RegisterBlockingEntity(this); + MapData.RegisterBlockingEntity(this); // Este peso influencia o algoritmo de pathfinding. // Atores evitam caminhos bloqueados. por outros atores. } - public Actor(Vector2I initialPosition, MapData map, ActorDefinition definition) : base(initialPosition, map, definition) { - - SetDefinition(definition); - } /// <summary> - /// Recupera uma quantidade de HP do ator. - /// </summary> - /// <param name="amount">HP para recuperar</param> - /// <returns>Quanto HP foi realmente recuperado.</returns> - public int Heal(int amount) { + /// Recupera uma quantidade de HP do ator. + /// </summary> + /// <param name="amount">HP para recuperar</param> + /// <returns>Quanto HP foi realmente recuperado.</returns> + public int Heal(int amount) + { int neoHp = Hp + amount; if (neoHp > MaxHp) neoHp = MaxHp; @@ -147,14 +177,15 @@ public partial class Actor : Entity } /// <summary> - /// Aplica uma definição de NPC para o ator. - /// Se o ator for um boneco de barro, este método é como um - /// sopro de vida. - /// </summary> - /// <param name="definition">A definição do ator.</param> - public virtual void SetDefinition(ActorDefinition definition) { + /// Aplica uma definição de NPC para o ator. + /// Se o ator for um boneco de barro, este método é como um + /// sopro de vida. + /// </summary> + /// <param name="definition">A definição do ator.</param> + public virtual void SetDefinition(ActorDefinition definition) + { base.SetDefinition(definition); - this.definition = definition; + Definition = definition; Type = definition.Type; @@ -168,7 +199,8 @@ public partial class Actor : Entity Men = definition.Men; } - public virtual void Die() { + public virtual void Die() + { //⠀⠀⠀⠀⢠⣤⣤⣤⢠⣤⣤⣤⣤⣄⢀⣠⣤⣤⣄⠀⠀⠀⢀⣠⣤⣤⣄⠀⣤⣤⠀⠀⣠⣤⣤⣤⣤⣤⡄⢠⣤⣤⣤⣄⠀⠀ //⠀⠀⠀⠀⠈⢹⣿⠉⠈⠉⣿⣿⠉⠉⢾⣿⣉⣉⠙⠀⠀⢀⣾⡟⠉⠉⣿⣧⢸⣿⡄⢠⣿⠏⣿⣿⣉⣉⡁⢸⣿⡏⢉⣿⡷⠀ //⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⣿⣿⠀⠀⠈⠿⠿⣿⣿⡀⠀⠸⣿⡇⠀⠀⣾⣿⠀⢿⣿⣸⡿⠀⣿⣿⠿⠿⠇⢸⣿⣿⣿⣿⠀⠀ @@ -197,19 +229,23 @@ public partial class Actor : Entity string deathMessage; - if (Map_Data.Player == this) { + if (MapData.Player == this) + { deathMessage = "Você morreu!"; - } else { + } + else + { deathMessage = $"{DisplayName} morreu!"; } MessageLogData.Instance.AddMessage(deathMessage); - Texture = definition.deathTexture; + Texture = Definition.deathTexture; BlocksMovement = false; Type = EntityType.CORPSE; - DisplayName= $"Restos mortais de {DisplayName}"; - Map_Data.UnregisterBlockingEntity(this); + DisplayName = $"Restos mortais de {DisplayName}"; + MapData.UnregisterBlockingEntity(this); EmitSignal(SignalName.Died); } + #endregion }
\ No newline at end of file diff --git a/scripts/entities/actors/Actor.cs.uid b/scripts/Entities/Actors/Actor.cs.uid index cf29b40..cf29b40 100644 --- a/scripts/entities/actors/Actor.cs.uid +++ b/scripts/Entities/Actors/Actor.cs.uid diff --git a/scripts/entities/actors/ActorDefinition.cs b/scripts/Entities/Actors/ActorDefinition.cs index 540ede0..5bd8073 100644 --- a/scripts/entities/actors/ActorDefinition.cs +++ b/scripts/Entities/Actors/ActorDefinition.cs @@ -1,5 +1,7 @@ using Godot; +namespace TheLegendOfGustav.Entities.Actors; + /// <summary> /// Define de forma genérica as características de um ator. /// </summary> @@ -13,7 +15,7 @@ public partial class ActorDefinition : EntityDefinition [ExportCategory("Mechanics")] [Export] - public int Speed { get; set;} = 10; + public int Speed { get; set; } = 10; // Estatísticas padrão do ator. [ExportCategory("Stats")] diff --git a/scripts/entities/actors/ActorDefinition.cs.uid b/scripts/Entities/Actors/ActorDefinition.cs.uid index ddcfe02..ddcfe02 100644 --- a/scripts/entities/actors/ActorDefinition.cs.uid +++ b/scripts/Entities/Actors/ActorDefinition.cs.uid diff --git a/scripts/entities/actors/Enemy.cs b/scripts/Entities/Actors/Enemy.cs index 9c06417..c152a0b 100644 --- a/scripts/entities/actors/Enemy.cs +++ b/scripts/Entities/Actors/Enemy.cs @@ -1,14 +1,8 @@ using Godot; -using System; +using TheLegendOfGustav.Entities.Actors.AI; +using TheLegendOfGustav.Map; -/// <summary> -/// Enum das diferentes IAs disponíveis. -/// </summary> -public enum AIType -{ - None, - DefaultHostile -}; +namespace TheLegendOfGustav.Entities.Actors; /// <summary> /// Um inimigo é uma espécie de ator que é @@ -16,30 +10,32 @@ public enum AIType /// </summary> public partial class Enemy : Actor { - private EnemyDefinition definition; + public Enemy(Vector2I initialPosition, MapData map, EnemyDefinition definition) : base(initialPosition, map, definition) + { + Definition = definition; + SetDefinition(definition); + } + /// <summary> /// A alma do ator. Gera ações que são executadas todo turno. /// </summary> public BaseAI Soul { get; private set; } - public Enemy(Vector2I initialPosition, MapData map, EnemyDefinition definition) : base(initialPosition, map, definition) - { - this.definition = definition; - SetDefinition(definition); - } + private EnemyDefinition Definition { get; set; } /// <summary> - /// Além de definir as características gerais de um ator, - /// também define qual IA utilizar. - /// </summary> - /// <param name="definition">Definição do inimigo.</param> + /// Além de definir as características gerais de um ator, + /// também define qual IA utilizar. + /// </summary> + /// <param name="definition">Definição do inimigo.</param> public void SetDefinition(EnemyDefinition definition) { // Definimos as características do ator. - base.SetDefinition(definition); + base.SetDefinition(Definition); // Definimos qual IA utilizar. - switch(definition.AI) { + switch (definition.AI) + { case AIType.None: break; case AIType.DefaultHostile: @@ -49,7 +45,8 @@ public partial class Enemy : Actor } } - public override void Die() { + public override void Die() + { Soul.QueueFree(); Soul = null; base.Die(); diff --git a/scripts/entities/actors/Enemy.cs.uid b/scripts/Entities/Actors/Enemy.cs.uid index 93255b7..93255b7 100644 --- a/scripts/entities/actors/Enemy.cs.uid +++ b/scripts/Entities/Actors/Enemy.cs.uid diff --git a/scripts/entities/actors/EnemyDefinition.cs b/scripts/Entities/Actors/EnemyDefinition.cs index e372e3a..97f8f13 100644 --- a/scripts/entities/actors/EnemyDefinition.cs +++ b/scripts/Entities/Actors/EnemyDefinition.cs @@ -1,10 +1,14 @@ using Godot; +using TheLegendOfGustav.Entities.Actors.AI; + +namespace TheLegendOfGustav.Entities.Actors; /// <summary> /// Além das configurações do ator, também possui qual IA utilizar. /// </summary> [GlobalClass] -public partial class EnemyDefinition : ActorDefinition { +public partial class EnemyDefinition : ActorDefinition +{ [ExportCategory("AI")] [Export] public AIType AI; diff --git a/scripts/entities/actors/EnemyDefinition.cs.uid b/scripts/Entities/Actors/EnemyDefinition.cs.uid index 1ba03e1..1ba03e1 100644 --- a/scripts/entities/actors/EnemyDefinition.cs.uid +++ b/scripts/Entities/Actors/EnemyDefinition.cs.uid diff --git a/scripts/Entities/Actors/Inventory.cs b/scripts/Entities/Actors/Inventory.cs new file mode 100644 index 0000000..f65dc59 --- /dev/null +++ b/scripts/Entities/Actors/Inventory.cs @@ -0,0 +1,46 @@ +using Godot; +using TheLegendOfGustav.Entities.Items; +using TheLegendOfGustav.Map; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.Entities.Actors; + +public partial class Inventory(int capacity) : Node +{ + private Player Player { get; set; } + public int Capacity { get; private set; } = capacity; + public Godot.Collections.Array<ConsumableItem> Items { get; private set; } = []; + + public override void _Ready() + { + base._Ready(); + Player = GetParent<Player>(); + } + + public void Drop(ConsumableItem item) + { + Items.Remove(item); + + MapData data = Player.MapData; + + data.InsertEntity(item); + data.EmitSignal(MapData.SignalName.EntityPlaced, item); + + item.MapData = data; + item.GridPosition = Player.GridPosition; + + MessageLogData.Instance.AddMessage($"Você descarta {item.DisplayName}."); + } + + public void Add(ConsumableItem item) + { + if (Items.Count >= Capacity) return; + + Items.Add(item); + } + + public void RemoveItem(ConsumableItem item) + { + Items.Remove(item); + } +}
\ No newline at end of file diff --git a/scripts/entities/actors/Inventory.cs.uid b/scripts/Entities/Actors/Inventory.cs.uid index 05c2beb..05c2beb 100644 --- a/scripts/entities/actors/Inventory.cs.uid +++ b/scripts/Entities/Actors/Inventory.cs.uid diff --git a/scripts/entities/actors/Player.cs b/scripts/Entities/Actors/Player.cs index 71812e8..7fd80d4 100644 --- a/scripts/entities/actors/Player.cs +++ b/scripts/Entities/Actors/Player.cs @@ -1,5 +1,7 @@ using Godot; -using System; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Actors; /// <summary> /// Classe do jogador. Por enquanto não é diferente do Ator, mas isso pode mudar. @@ -7,18 +9,19 @@ using System; [GlobalClass] public partial class Player : Actor { - private PlayerDefinition definition; - public Inventory inventory; - public Player(Vector2I initialPosition, MapData map, PlayerDefinition definition) : base(initialPosition, map, definition) { - this.definition = definition; + Definition = definition; SetDefinition(definition); } - public void SetDefinition(PlayerDefinition definition) { - inventory = new(definition.InventoryCapacity); + private PlayerDefinition Definition { get; set; } + public Inventory Inventory { get; private set; } + + public void SetDefinition(PlayerDefinition definition) + { + Inventory = new(definition.InventoryCapacity); - AddChild(inventory); + AddChild(Inventory); } } diff --git a/scripts/entities/actors/Player.cs.uid b/scripts/Entities/Actors/Player.cs.uid index 8229b7f..8229b7f 100644 --- a/scripts/entities/actors/Player.cs.uid +++ b/scripts/Entities/Actors/Player.cs.uid diff --git a/scripts/entities/actors/PlayerDefinition.cs b/scripts/Entities/Actors/PlayerDefinition.cs index aca07e1..58ae6b4 100644 --- a/scripts/entities/actors/PlayerDefinition.cs +++ b/scripts/Entities/Actors/PlayerDefinition.cs @@ -1,7 +1,10 @@ using Godot; +namespace TheLegendOfGustav.Entities.Actors; + [GlobalClass] -public partial class PlayerDefinition : ActorDefinition { +public partial class PlayerDefinition : ActorDefinition +{ [ExportCategory("Player Mechanics")] [Export] public int InventoryCapacity = 0; diff --git a/scripts/entities/actors/PlayerDefinition.cs.uid b/scripts/Entities/Actors/PlayerDefinition.cs.uid index 9d01ab9..9d01ab9 100644 --- a/scripts/entities/actors/PlayerDefinition.cs.uid +++ b/scripts/Entities/Actors/PlayerDefinition.cs.uid diff --git a/scripts/entities/Entity.cs b/scripts/Entities/Entity.cs index 85a3156..412bd7a 100644 --- a/scripts/entities/Entity.cs +++ b/scripts/Entities/Entity.cs @@ -1,4 +1,8 @@ using Godot; +using TheLegendOfGustav.Map; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.Entities; /// <summary> /// Defino aqui que o jogo irá desenhar @@ -14,22 +18,33 @@ public enum EntityType /// <summary> /// Classe para elementos móveis que o jogador pode interagir. /// </summary> -public abstract partial class Entity : Sprite2D { - /// <summary> - /// A definição da entidade possui caracterísitcas padrões que definem - /// a entidade em questão. - /// </summary> - private EntityDefinition definition; +public abstract partial class Entity : Sprite2D +{ + private Vector2I gridPosition = Vector2I.Zero; private EntityType type; + private bool blocksMovement; + private string displayName; + + public Entity(Vector2I initialPosition, MapData map, EntityDefinition definition) + { + GridPosition = initialPosition; + MapData = map; + Centered = false; + + SetDefinition(definition); + } + /// <summary> - /// Usado para definir a camada da entidade no mapa. - /// </summary> - public EntityType Type { + /// Usado para definir a camada da entidade no mapa. + /// </summary> + public EntityType Type + { get => type; - set { + set + { type = value; - ZIndex = (int) type; + ZIndex = (int)type; } } @@ -37,15 +52,16 @@ public abstract partial class Entity : Sprite2D { /// É conveniente ter acesso ao mapa dentro da entidade. Isto porque ela existe dentro /// do mapa, então é necessário ter acesso à algumas informações. /// </summary> - public MapData Map_Data { get; set; } + public MapData MapData { get; set; } - private Vector2I gridPosition = Vector2I.Zero; /// <summary> - /// Posição da entidade no mapa do jogo. Diferentemente de Position, GridPosition tem como formato - /// os tiles do mapa. - /// </summary> - public Vector2I GridPosition { - set { + /// Posição da entidade no mapa do jogo. Diferentemente de Position, GridPosition tem como formato + /// os tiles do mapa. + /// </summary> + public Vector2I GridPosition + { + set + { gridPosition = value; // O sistema de coordenadas do Godot é em pixels, mas faz mais sentido para o jogo utilizar coordenadas em tiles. // Esta propriedade converte um sistema para o outro automaticamente. @@ -54,28 +70,36 @@ public abstract partial class Entity : Sprite2D { get => gridPosition; } - private bool blocksMovement; /// <summary> /// Se a entidade bloqueia movimento (não pode oculpar a mesma célula de outra entidade.) /// </summary> - public bool BlocksMovement { + public bool BlocksMovement + { get => blocksMovement; - protected set { + protected set + { blocksMovement = value; } } - private string displayName; /// <summary> - /// Nome da entidade. - /// </summary> - public string DisplayName { + /// Nome da entidade. + /// </summary> + public string DisplayName + { get => displayName; - protected set { + protected set + { displayName = value; } } + /// <summary> + /// A definição da entidade possui caracterísitcas padrões que definem + /// a entidade em questão. + /// </summary> + private EntityDefinition Definition; + public override void _Ready() { base._Ready(); @@ -84,22 +108,17 @@ public abstract partial class Entity : Sprite2D { GridPosition = Grid.WorldToGrid(Position); } - public Entity(Vector2I initialPosition, MapData map, EntityDefinition definition) { - GridPosition = initialPosition; - Map_Data = map; - Centered = false; - - SetDefinition(definition); - } + /// <summary> - /// Aplica uma definição de NPC para o ator. - /// Se o ator for um boneco de barro, este método é como um - /// sopro de vida. - /// </summary> - /// <param name="definition">A definição do ator.</param> - public virtual void SetDefinition(EntityDefinition definition) { - this.definition = definition; + /// Aplica uma definição de NPC para o ator. + /// Se o ator for um boneco de barro, este método é como um + /// sopro de vida. + /// </summary> + /// <param name="definition">A definição do ator.</param> + public virtual void SetDefinition(EntityDefinition definition) + { + Definition = definition; BlocksMovement = definition.blocksMovement; DisplayName = definition.name; Type = definition.Type; diff --git a/scripts/entities/Entity.cs.uid b/scripts/Entities/Entity.cs.uid index f178d64..f178d64 100644 --- a/scripts/entities/Entity.cs.uid +++ b/scripts/Entities/Entity.cs.uid diff --git a/scripts/entities/EntityDefinition.cs b/scripts/Entities/EntityDefinition.cs index ba7236a..a6080bd 100644 --- a/scripts/entities/EntityDefinition.cs +++ b/scripts/Entities/EntityDefinition.cs @@ -1,7 +1,10 @@ using Godot; +namespace TheLegendOfGustav.Entities; + [GlobalClass] -public partial class EntityDefinition : Resource{ +public partial class EntityDefinition : Resource +{ [ExportCategory("Entity Visuals")] // Nome da entidade. [Export] diff --git a/scripts/entities/EntityDefinition.cs.uid b/scripts/Entities/EntityDefinition.cs.uid index 0aed6ab..0aed6ab 100644 --- a/scripts/entities/EntityDefinition.cs.uid +++ b/scripts/Entities/EntityDefinition.cs.uid diff --git a/scripts/entities/items/ConsumableItem.cs b/scripts/Entities/Items/ConsumableItem.cs index 82fab49..f70983a 100644 --- a/scripts/entities/items/ConsumableItem.cs +++ b/scripts/Entities/Items/ConsumableItem.cs @@ -1,14 +1,16 @@ using Godot; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Items; /// <summary> /// Classe para itens consumíveis. /// Itens consumíveis são itens de uso limitado. /// </summary> -public abstract partial class ConsumableItem : Entity +public abstract partial class ConsumableItem(Vector2I initialPosition, MapData map, EntityDefinition definition) : Entity(initialPosition, map, definition) { - public ConsumableItem(Vector2I initialPosition, MapData map, EntityDefinition definition) : base(initialPosition, map, definition) - { - } /// <summary> /// Gera uma ação onde o ator consome o item. @@ -29,8 +31,9 @@ public abstract partial class ConsumableItem : Entity /// <returns>Se a ação foi realizada ou não.</returns> public abstract bool Activate(ItemAction action); - public void ConsumedBy(Player consumer) { - Inventory inventory = consumer.inventory; + public void ConsumedBy(Player consumer) + { + Inventory inventory = consumer.Inventory; inventory.RemoveItem(this); QueueFree(); } diff --git a/scripts/entities/items/ConsumableItem.cs.uid b/scripts/Entities/Items/ConsumableItem.cs.uid index e6c452a..e6c452a 100644 --- a/scripts/entities/items/ConsumableItem.cs.uid +++ b/scripts/Entities/Items/ConsumableItem.cs.uid diff --git a/scripts/entities/items/ConsumableItemDefinition.cs b/scripts/Entities/Items/ConsumableItemDefinition.cs index 74340d2..9cadc0b 100644 --- a/scripts/entities/items/ConsumableItemDefinition.cs +++ b/scripts/Entities/Items/ConsumableItemDefinition.cs @@ -1,5 +1,7 @@ using Godot; +namespace TheLegendOfGustav.Entities.Items; + /// <summary> /// Esta classe só existe para agrupar seus descendentes. /// </summary> diff --git a/scripts/entities/items/ConsumableItemDefinition.cs.uid b/scripts/Entities/Items/ConsumableItemDefinition.cs.uid index 9ddc0f6..9ddc0f6 100644 --- a/scripts/entities/items/ConsumableItemDefinition.cs.uid +++ b/scripts/Entities/Items/ConsumableItemDefinition.cs.uid diff --git a/scripts/Entities/Items/HealingConsumable.cs b/scripts/Entities/Items/HealingConsumable.cs new file mode 100644 index 0000000..32f76a6 --- /dev/null +++ b/scripts/Entities/Items/HealingConsumable.cs @@ -0,0 +1,30 @@ +using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Utils; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Map; + +namespace TheLegendOfGustav.Entities.Items; + +public partial class HealingConsumable(Vector2I initialPosition, MapData map, HealingConsumableDefinition definition) : ConsumableItem(initialPosition, map, definition) +{ + private HealingConsumableDefinition Definition { get; set; } = definition; + public float HealingPercentage { get; private set; } = definition.healingPercentage; + + public override bool Activate(ItemAction action) + { + Player consumer = action.Player; + int intendedAmount = (int)(HealingPercentage / 100 * consumer.MaxHp); + int recovered = consumer.Heal(intendedAmount); + + // Se não tinha o que curar, a ativação falhou. + if (recovered == 0) + { + MessageLogData.Instance.AddMessage("Você já está saudável."); + return false; + } + MessageLogData.Instance.AddMessage($"Você consome {DisplayName} e recupera {recovered} de HP"); + ConsumedBy(consumer); + return true; + } +}
\ No newline at end of file diff --git a/scripts/entities/items/HealingConsumable.cs.uid b/scripts/Entities/Items/HealingConsumable.cs.uid index 8f8f942..8f8f942 100644 --- a/scripts/entities/items/HealingConsumable.cs.uid +++ b/scripts/Entities/Items/HealingConsumable.cs.uid diff --git a/scripts/entities/items/HealingConsumableDefinition.cs b/scripts/Entities/Items/HealingConsumableDefinition.cs index 2562e9e..d0e5850 100644 --- a/scripts/entities/items/HealingConsumableDefinition.cs +++ b/scripts/Entities/Items/HealingConsumableDefinition.cs @@ -1,10 +1,13 @@ using Godot; +namespace TheLegendOfGustav.Entities.Items; + [GlobalClass] -public partial class HealingConsumableDefinition : ConsumableItemDefinition { +public partial class HealingConsumableDefinition : ConsumableItemDefinition +{ ///<summary> - /// Porcentagem da vida do ator para restaurar. - ///</summary> + /// Porcentagem da vida do ator para restaurar. + ///</summary> [ExportCategory("Item Mechanics")] [Export] public float healingPercentage = 10; diff --git a/scripts/entities/items/HealingConsumableDefinition.cs.uid b/scripts/Entities/Items/HealingConsumableDefinition.cs.uid index 2fd311d..2fd311d 100644 --- a/scripts/entities/items/HealingConsumableDefinition.cs.uid +++ b/scripts/Entities/Items/HealingConsumableDefinition.cs.uid diff --git a/scripts/GUI/Details.cs b/scripts/GUI/Details.cs index 814d2ac..3c64427 100644 --- a/scripts/GUI/Details.cs +++ b/scripts/GUI/Details.cs @@ -1,48 +1,57 @@ using Godot; -using System; +using TheLegendOfGustav.Entities; +using TheLegendOfGustav.Map; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.GUI; public partial class Details : CanvasLayer { private static readonly LabelSettings lblSettings = GD.Load<LabelSettings>("res://assets/definitions/message_label_settings.tres"); - private Map map; - private VBoxContainer entityNames; - private Godot.Collections.Array<Entity> entities = []; + + private Map.Map Map { get; set; } + private VBoxContainer EntityNames { get; set; } + private Godot.Collections.Array<Entity> Entities { get; set; } = []; - private Godot.Collections.Array<Label> actorsLabel = []; + private Godot.Collections.Array<Label> ActorsLabel { get; set; } = []; public override void _Ready() { base._Ready(); - map = GetParent<Map>(); - entityNames = GetNode<VBoxContainer>("HBoxContainer/PanelContainer/ScrollContainer/Entities"); + Map = GetParent<Map.Map>(); + EntityNames = GetNode<VBoxContainer>("HBoxContainer/PanelContainer/ScrollContainer/Entities"); SignalBus.Instance.InspectorMoved += OnInspectorWalk; SignalBus.Instance.EnterInspectionMode += () => Visible = true; SignalBus.Instance.ExitInspectionMode += () => Visible = false; } - public void OnInspectorWalk(Vector2I pos) { - MapData mapData = map.Map_Data; - entities = mapData.GetEntitiesAtPosition(pos); + public void OnInspectorWalk(Vector2I pos) + { + MapData mapData = Map.MapData; + Entities = mapData.GetEntitiesAtPosition(pos); UpdateLabels(); } - private void UpdateLabels() { - foreach(Label label in actorsLabel) { + private void UpdateLabels() + { + foreach (Label label in ActorsLabel) + { label.QueueFree(); } - actorsLabel.Clear(); - foreach (Entity entity in entities) { + ActorsLabel.Clear(); + + foreach (Entity entity in Entities) + { Label label = new() { Text = entity.DisplayName, LabelSettings = lblSettings }; - actorsLabel.Add(label); - entityNames.AddChild(label); + ActorsLabel.Add(label); + EntityNames.AddChild(label); } } } -
\ No newline at end of file diff --git a/scripts/GUI/Hud.cs b/scripts/GUI/Hud.cs index 614fa5e..1149728 100644 --- a/scripts/GUI/Hud.cs +++ b/scripts/GUI/Hud.cs @@ -1,17 +1,20 @@ using Godot; -using System; + +namespace TheLegendOfGustav.GUI; public partial class Hud : Node { - private TextureProgressBar hpBar; + private TextureProgressBar HpBar { get; set; } - public override void _Ready() { + public override void _Ready() + { base._Ready(); - hpBar = GetNode<TextureProgressBar>("VBoxContainer/InfoBar/Stats/MarginContainer/HBoxContainer/AspectRatioContainer/HPbar"); + HpBar = GetNode<TextureProgressBar>("VBoxContainer/InfoBar/Stats/MarginContainer/HBoxContainer/AspectRatioContainer/HPbar"); } - public void OnHealthChanged(int hp, int maxHp) { - hpBar.Value = hp; - hpBar.MaxValue = maxHp; + public void OnHealthChanged(int hp, int maxHp) + { + HpBar.Value = hp; + HpBar.MaxValue = maxHp; } } diff --git a/scripts/GUI/InventoryMenu.cs b/scripts/GUI/InventoryMenu.cs index bfb1795..5bac62b 100644 --- a/scripts/GUI/InventoryMenu.cs +++ b/scripts/GUI/InventoryMenu.cs @@ -1,52 +1,63 @@ using Godot; -using System; +using TheLegendOfGustav.Entities.Items; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.GUI; public partial class InventoryMenu : CanvasLayer { private static readonly PackedScene itemMenuEntryScene = GD.Load<PackedScene>("res://scenes/GUI/item_menu_entry.tscn"); + [Signal] public delegate void ItemSelectedEventHandler(ConsumableItem item); [Signal] public delegate void ItemDropEventHandler(ConsumableItem item); - private VBoxContainer itemsNode; + private VBoxContainer ItemsNode { get; set; } - public override void _Ready() { + public override void _Ready() + { base._Ready(); - itemsNode = GetNode<VBoxContainer>("CenterContainer/PanelContainer/VBoxContainer/Items"); + ItemsNode = GetNode<VBoxContainer>("CenterContainer/PanelContainer/VBoxContainer/Items"); Hide(); } - public void OnActivate(ConsumableItem item) { + public void OnActivate(ConsumableItem item) + { EmitSignal(SignalName.ItemSelected, item); } - - public void OnDrop(ConsumableItem item) { + + public void OnDrop(ConsumableItem item) + { EmitSignal(SignalName.ItemDrop, item); } - private void RegisterItem(int index, ConsumableItem item) { + public void Initialize(Inventory inventory) + { + for (int i = 0; i < inventory.Items.Count; i++) + { + RegisterItem(i, inventory.Items[i]); + } + + Show(); + } + + private void RegisterItem(int index, ConsumableItem item) + { char? shortcut = null; // Só terá atalho para as letras do alfabeto. - if (index < 26) { + if (index < 26) + { shortcut = (char)('a' + index); } ItemMenuEntry itemEntry = itemMenuEntryScene.Instantiate<ItemMenuEntry>(); - itemsNode.AddChild(itemEntry); + ItemsNode.AddChild(itemEntry); itemEntry.Initialize(item, shortcut); itemEntry.Activate += OnActivate; itemEntry.Drop += OnDrop; } - - public void Initialize(Inventory inventory) { - for (int i = 0; i < inventory.Items.Count; i++) { - RegisterItem(i, inventory.Items[i]); - } - - Show(); - } } diff --git a/scripts/GUI/ItemMenuEntry.cs b/scripts/GUI/ItemMenuEntry.cs index 449c97b..7ee3fcd 100644 --- a/scripts/GUI/ItemMenuEntry.cs +++ b/scripts/GUI/ItemMenuEntry.cs @@ -1,41 +1,48 @@ using Godot; +using TheLegendOfGustav.Entities.Items; + +namespace TheLegendOfGustav.GUI; public partial class ItemMenuEntry : HBoxContainer { - private TextureRect icon; - private Label shortcutLabel; - private Label nameLabel; - private Button activateBtn; - private Button dropBtn; - [Signal] public delegate void ActivateEventHandler(ConsumableItem Item); [Signal] - public delegate void DropEventHandler(ConsumableItem item); - - private ConsumableItem item; + public delegate void DropEventHandler(ConsumableItem Item); - public void Initialize(ConsumableItem item, char? shortcut) { - this.item = item; - nameLabel.Text = item.DisplayName; - if (shortcut != null) { - shortcutLabel.Text = $"{shortcut}"; - } else { - shortcutLabel.Text = ""; - } - icon.Texture = item.Texture; - } + private TextureRect Icon { get; set; } + private Label ShortcutLabel { get; set; } + private Label NameLabel { get; set; } + private Button ActivateBtn { get; set; } + private Button DropBtn { get; set; } + private ConsumableItem Item { get; set; } - public override void _Ready() { + public override void _Ready() + { base._Ready(); - icon = GetNode<TextureRect>("Icon"); - shortcutLabel = GetNode<Label>("Shortcut"); - nameLabel = GetNode<Label>("ItemName"); - activateBtn = GetNode<Button>("ActivateBtn"); - dropBtn = GetNode<Button>("DropButton"); + Icon = GetNode<TextureRect>("Icon"); + ShortcutLabel = GetNode<Label>("Shortcut"); + NameLabel = GetNode<Label>("ItemName"); + ActivateBtn = GetNode<Button>("ActivateBtn"); + DropBtn = GetNode<Button>("DropButton"); - activateBtn.Pressed += () => EmitSignal(SignalName.Activate, item); - dropBtn.Pressed += () => EmitSignal(SignalName.Drop, item); + ActivateBtn.Pressed += () => EmitSignal(SignalName.Activate, Item); + DropBtn.Pressed += () => EmitSignal(SignalName.Drop, Item); + } + + public void Initialize(ConsumableItem item, char? shortcut) + { + Item = item; + NameLabel.Text = item.DisplayName; + if (shortcut != null) + { + ShortcutLabel.Text = $"{shortcut}"; + } + else + { + ShortcutLabel.Text = ""; + } + Icon.Texture = item.Texture; } } diff --git a/scripts/GUI/Message.cs b/scripts/GUI/Message.cs index b0472ee..11f3532 100644 --- a/scripts/GUI/Message.cs +++ b/scripts/GUI/Message.cs @@ -1,32 +1,49 @@ using Godot; +namespace TheLegendOfGustav.GUI; + public partial class Message : Label { - private static LabelSettings baseSettings = GD.Load<LabelSettings>("res://assets/definitions/message_label_settings.tres"); + private static readonly LabelSettings baseSettings = GD.Load<LabelSettings>("res://assets/definitions/message_label_settings.tres"); + private string plainText; - public string PlainText { get => plainText; } private int count = 1; - public int Count { + + public Message(string text) + { + PlainText = text; + Text = text; + LabelSettings = (LabelSettings)baseSettings.Duplicate(); + AutowrapMode = TextServer.AutowrapMode.WordSmart; + } + + public string PlainText + { + get => plainText; + private set + { + plainText = value; + } + } + public int Count + { get => count; - set { + set + { count = value; Text = FullText; } } - public string FullText { - get { - if (count > 1) { + public string FullText + { + get + { + if (count > 1) + { return $"{plainText} ({count})"; } return plainText; } } - - public Message(string text) { - plainText = text; - Text = text; - LabelSettings = (LabelSettings) baseSettings.Duplicate(); - AutowrapMode = TextServer.AutowrapMode.WordSmart; - } } diff --git a/scripts/GUI/MessageLog.cs b/scripts/GUI/MessageLog.cs index d481b19..1fc59b6 100644 --- a/scripts/GUI/MessageLog.cs +++ b/scripts/GUI/MessageLog.cs @@ -1,24 +1,28 @@ using Godot; -using System; using System.Threading.Tasks; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.GUI; public partial class MessageLog : ScrollContainer { - private VBoxContainer MessageList; + private VBoxContainer MessageList { get; set; } public override void _Ready() { base._Ready(); MessageList = GetNode<VBoxContainer>("MessageList"); - foreach (Message msg in MessageLogData.Instance.Messages) { + foreach (Message msg in MessageLogData.Instance.Messages) + { _ = AddMessageAsync(msg); } MessageLogData.Instance.messageSent += async (Message msg) => await AddMessageAsync(msg); } - private async Task AddMessageAsync(Message message) { + private async Task AddMessageAsync(Message message) + { MessageList.AddChild(message); await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); EnsureControlVisible(message); diff --git a/scripts/Game.cs b/scripts/Game.cs index b8c6a8b..ff10eea 100644 --- a/scripts/Game.cs +++ b/scripts/Game.cs @@ -1,36 +1,45 @@ using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.InputHandling; +using TheLegendOfGustav.GUI; +using TheLegendOfGustav.Time; +using TheLegendOfGustav.Utils; +namespace TheLegendOfGustav; /// <summary> /// Classe principal do jogo. /// Lar da lógica central do jogo. /// </summary> -public partial class Game : Node { +public partial class Game : Node +{ /// <summary> - /// Definição de um jogador. - /// </summary> + /// Definição de um jogador. + /// </summary> private static readonly PlayerDefinition playerDefinition = GD.Load<PlayerDefinition>("res://assets/definitions/actor/Player.tres"); /// <summary> - /// O jogo possui o mapa. - /// </summary> - private Map Map; + /// O jogo possui o mapa. + /// </summary> + private Map.Map Map { get; set; } /// <summary> - /// Objeto para obter input do usuário. - /// </summary> - private InputHandler inputHandler; + /// Objeto para obter input do usuário. + /// </summary> + private InputHandler InputHandler { get; set; } /// <summary> /// Gerenciador de turnos /// </summary> - private TurnManager turnManager; + private TurnManager TurnManager { get; set; } private Hud hud; - public override void _Ready() { + public override void _Ready() + { base._Ready(); - Map = GetNode<Map>("Map"); + Map = GetNode<Map.Map>("Map"); - inputHandler = GetNode<InputHandler>("InputHandler"); + InputHandler = GetNode<InputHandler>("InputHandler"); hud = GetNode<Hud>("HUD"); // O jogador é criado pelo jogo. @@ -38,7 +47,7 @@ public partial class Game : Node { Camera2D camera = GetNode<Camera2D>("Camera2D"); RemoveChild(camera); player.HealthChanged += (int hp, int maxHp) => hud.OnHealthChanged(hp, maxHp); - player.Died += () => inputHandler.SetInputHandler(InputHandlers.GameOver); + player.Died += () => InputHandler.SetInputHandler(InputHandlers.GameOver); player.AddChild(camera); @@ -46,29 +55,31 @@ public partial class Game : Node { Map.UpdateFOV(player.GridPosition); - turnManager = new(Map); + TurnManager = new(Map); MessageLogData.Instance.AddMessage("Boa sorte!"); } /// <summary> - /// Método executa aproximadamente 60 vezes por segundo. - /// </summary> - /// <param name="delta"></param> - public override void _PhysicsProcess(double delta) { + /// Método executa aproximadamente 60 vezes por segundo. + /// </summary> + /// <param name="delta"></param> + public override void _PhysicsProcess(double delta) + { base._PhysicsProcess(delta); - Player player = Map.Map_Data.Player; + Player player = Map.MapData.Player; // Pegamos uma ação do usuário - Action action = inputHandler.GetAction(player); + Action action = InputHandler.GetAction(player); - if (action != null) { - turnManager.InsertPlayerAction(action); + if (action != null) + { + TurnManager.InsertPlayerAction(action); } // Computamos um turno. - turnManager.Tick(); + TurnManager.Tick(); } } diff --git a/scripts/input/BaseInputHandler.cs b/scripts/InputHandling/BaseInputHandler.cs index 4e389ed..f3527ca 100644 --- a/scripts/input/BaseInputHandler.cs +++ b/scripts/InputHandling/BaseInputHandler.cs @@ -1,4 +1,8 @@ using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities.Actions; + +namespace TheLegendOfGustav.InputHandling; /// <summary> /// Classe base para obter ações do usuário. @@ -8,14 +12,15 @@ using Godot; /// possui somente dois estados: Com jogador vivo e com jogador morto. /// Mas isto pode aumentar. /// </summary> -public abstract partial class BaseInputHandler : Node { - /// <summary> - /// Método executado quando o input handler entra em cena; - /// </summary> +public abstract partial class BaseInputHandler : Node +{ + /// <summary> + /// Método executado quando o input handler entra em cena; + /// </summary> public virtual void Enter() { } - /// <summary> - /// Método executado quando o input handler sai de cena; - /// </summary> + /// <summary> + /// Método executado quando o input handler sai de cena; + /// </summary> public virtual void Exit() { } /// <summary> /// Obtém uma ação do usuári conforme input. diff --git a/scripts/input/BaseInputHandler.cs.uid b/scripts/InputHandling/BaseInputHandler.cs.uid index deae303..deae303 100644 --- a/scripts/input/BaseInputHandler.cs.uid +++ b/scripts/InputHandling/BaseInputHandler.cs.uid diff --git a/scripts/input/GameOverInputHandler.cs b/scripts/InputHandling/GameOverInputHandler.cs index 5e41daf..e11e98a 100644 --- a/scripts/input/GameOverInputHandler.cs +++ b/scripts/InputHandling/GameOverInputHandler.cs @@ -1,10 +1,13 @@ -using Godot; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.InputHandling; /// <summary> /// Esquema de controles para quando o jogador está morto. /// </summary> -public partial class GameOverInputHandler : BaseInputHandler { - +public partial class GameOverInputHandler : BaseInputHandler +{ // Por enquanto não tem nada. public override Action GetAction(Player player) { diff --git a/scripts/input/GameOverInputHandler.cs.uid b/scripts/InputHandling/GameOverInputHandler.cs.uid index 14ddfd8..14ddfd8 100644 --- a/scripts/input/GameOverInputHandler.cs.uid +++ b/scripts/InputHandling/GameOverInputHandler.cs.uid diff --git a/scripts/InputHandling/InputHandler.cs b/scripts/InputHandling/InputHandler.cs new file mode 100644 index 0000000..55a17b4 --- /dev/null +++ b/scripts/InputHandling/InputHandler.cs @@ -0,0 +1,58 @@ +using Godot; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.InputHandling; + +public enum InputHandlers +{ + MainGame, + GameOver, + Inspect, + Pickup, + Inventory +} + +/// <summary> +/// Máquina de estado que obtém ações do usuário conforme o estado atual do jogo. +/// </summary> +public partial class InputHandler : Node +{ + [Export] + private InputHandlers StartingInputHandler { get; set; } + + private Godot.Collections.Dictionary<InputHandlers, BaseInputHandler> InputHandlerDict { get; set; } = []; + + private BaseInputHandler SelectedInputHandler { get; set; } + + public override void _Ready() + { + base._Ready(); + // Controles para quando o jogador está vivo e jogando normalmente. + InputHandlerDict.Add(InputHandlers.MainGame, GetNode<MainGameInputHandler>("MainGameInputHandler")); + // Controles para quando o jogador está morto. + InputHandlerDict.Add(InputHandlers.GameOver, GetNode<GameOverInputHandler>("GameOverInputHandler")); + InputHandlerDict.Add(InputHandlers.Inspect, GetNode<InspectInputHandler>("InspectInputHandler")); + InputHandlerDict.Add(InputHandlers.Pickup, GetNode<PickupInputHandler>("PickupInputHandler")); + InputHandlerDict.Add(InputHandlers.Inventory, GetNode<InventoryInputHandler>("InventoryInputHandler")); + + SetInputHandler(StartingInputHandler); + } + + public Action GetAction(Player player) + { + return SelectedInputHandler.GetAction(player); + } + + /// <summary> + /// Define o esquema de controle atual do jogo + /// para o estado informado. + /// </summary> + /// <param name="inputhandler">Estado do jogo.</param> + public void SetInputHandler(InputHandlers inputhandler) + { + SelectedInputHandler?.Exit(); + SelectedInputHandler = InputHandlerDict[inputhandler]; + SelectedInputHandler.Enter(); + } +} diff --git a/scripts/input/InputHandler.cs.uid b/scripts/InputHandling/InputHandler.cs.uid index f61cdba..f61cdba 100644 --- a/scripts/input/InputHandler.cs.uid +++ b/scripts/InputHandling/InputHandler.cs.uid diff --git a/scripts/input/InspectInputHandler.cs b/scripts/InputHandling/InspectInputHandler.cs index ad76c62..90734f9 100644 --- a/scripts/input/InspectInputHandler.cs +++ b/scripts/InputHandling/InspectInputHandler.cs @@ -1,4 +1,9 @@ using Godot; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.InputHandling; /// <summary> /// TODO: Esta solução é nojenta e precisa ser retrabalhada. @@ -7,7 +12,7 @@ public partial class InspectInputHandler : BaseInputHandler { private static readonly PackedScene InspectorScene = GD.Load<PackedScene>("res://scenes/Inspector.tscn"); - private readonly Godot.Collections.Dictionary<string, Vector2I> directions = new() + private static readonly Godot.Collections.Dictionary<string, Vector2I> directions = new() { {"walk-up", Vector2I.Up}, {"walk-down", Vector2I.Down}, @@ -18,38 +23,45 @@ public partial class InspectInputHandler : BaseInputHandler {"walk-down-right", Vector2I.Down + Vector2I.Right}, {"walk-down-left", Vector2I.Down + Vector2I.Left}, }; + /// <summary> - /// Preciso disso - /// </summary> + /// Preciso disso + /// </summary> [Export] - private Map map; + private Map.Map Map { get; set; } - private Inspector inspector; + private Inspector Inspector { get; set; } - public override void Enter() { + public override void Enter() + { SignalBus.Instance.EmitSignal(SignalBus.SignalName.EnterInspectionMode); - inspector = InspectorScene.Instantiate<Inspector>(); + Inspector = InspectorScene.Instantiate<Inspector>(); - inspector.GridPosition = map.Map_Data.Player.GridPosition; + Inspector.GridPosition = Map.MapData.Player.GridPosition; - map.AddChild(inspector); + Map.AddChild(Inspector); } - public override void Exit() { - inspector.QueueFree(); + public override void Exit() + { + Inspector.QueueFree(); SignalBus.Instance.EmitSignal(SignalBus.SignalName.ExitInspectionMode); } + public override Action GetAction(Player player) { Action action = null; - foreach (var direction in directions) { - if (Input.IsActionJustPressed(direction.Key)) { - inspector.Walk(direction.Value); + foreach (var direction in directions) + { + if (Input.IsActionJustPressed(direction.Key)) + { + Inspector.Walk(direction.Value); } } - if (Input.IsActionJustPressed("quit")) { + if (Input.IsActionJustPressed("quit")) + { GetParent<InputHandler>().SetInputHandler(InputHandlers.MainGame); } diff --git a/scripts/input/InspectInputHandler.cs.uid b/scripts/InputHandling/InspectInputHandler.cs.uid index 20141fa..20141fa 100644 --- a/scripts/input/InspectInputHandler.cs.uid +++ b/scripts/InputHandling/InspectInputHandler.cs.uid diff --git a/scripts/InputHandling/InventoryInputHandler.cs b/scripts/InputHandling/InventoryInputHandler.cs new file mode 100644 index 0000000..390e545 --- /dev/null +++ b/scripts/InputHandling/InventoryInputHandler.cs @@ -0,0 +1,75 @@ +using Godot; +using TheLegendOfGustav.GUI; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities.Items; +using TheLegendOfGustav.Entities.Actions; + +namespace TheLegendOfGustav.InputHandling; + +public partial class InventoryInputHandler : BaseInputHandler +{ + private static readonly PackedScene inventoryScene = GD.Load<PackedScene>("res://scenes/GUI/invetory_menu.tscn"); + + + [Export] + private Map.Map Map { get; set; } + + private InventoryMenu InventoryMenu { get; set; } + private ConsumableItem ActivationItem { get; set; } = null; + private ConsumableItem DropItem { get; set; } = null; + + public override void Enter() + { + InventoryMenu = inventoryScene.Instantiate<InventoryMenu>(); + Map.MapData.Player.AddChild(InventoryMenu); + InventoryMenu.Initialize(Map.MapData.Player.Inventory); + InventoryMenu.ItemSelected += OnItemActivate; + InventoryMenu.ItemDrop += OnItemDrop; + } + + public override void Exit() + { + ActivationItem = null; + DropItem = null; + InventoryMenu.QueueFree(); + } + + public override Action GetAction(Player player) + { + Action action = null; + + if (ActivationItem != null) + { + action = new ItemAction(player, ActivationItem); + Close(); + } + + if (DropItem != null) + { + action = new DropAction(player, DropItem); + Close(); + } + + if (Input.IsActionJustPressed("quit")) + { + Close(); + } + + return action; + } + + private void Close() + { + GetParent<InputHandler>().SetInputHandler(InputHandlers.MainGame); + } + + private void OnItemActivate(ConsumableItem item) + { + ActivationItem = item; + } + + private void OnItemDrop(ConsumableItem item) + { + DropItem = item; + } +}
\ No newline at end of file diff --git a/scripts/input/InventoryInputHandler.cs.uid b/scripts/InputHandling/InventoryInputHandler.cs.uid index b5d0ed9..b5d0ed9 100644 --- a/scripts/input/InventoryInputHandler.cs.uid +++ b/scripts/InputHandling/InventoryInputHandler.cs.uid diff --git a/scripts/InputHandling/MainGameInputHandler.cs b/scripts/InputHandling/MainGameInputHandler.cs new file mode 100644 index 0000000..fe8e573 --- /dev/null +++ b/scripts/InputHandling/MainGameInputHandler.cs @@ -0,0 +1,58 @@ +using Godot; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.InputHandling; + +/// <summary> +/// Esquema de controles principal do jogo. +/// </summary> +public partial class MainGameInputHandler : BaseInputHandler +{ + private static readonly Godot.Collections.Dictionary<string, Vector2I> directions = new() + { + {"walk-up", Vector2I.Up}, + {"walk-down", Vector2I.Down}, + {"walk-left", Vector2I.Left}, + {"walk-right", Vector2I.Right}, + {"walk-up-right", Vector2I.Up + Vector2I.Right}, + {"walk-up-left", Vector2I.Up + Vector2I.Left}, + {"walk-down-right", Vector2I.Down + Vector2I.Right}, + {"walk-down-left", Vector2I.Down + Vector2I.Left}, + }; + + public override Action GetAction(Player player) + { + Action action = null; + + foreach (var direction in directions) + { + if (Input.IsActionJustPressed(direction.Key)) + { + action = new BumpAction(player, direction.Value); + } + } + + if (Input.IsActionJustPressed("open-inventory")) + { + GetParent<InputHandler>().SetInputHandler(InputHandlers.Inventory); + } + + if (Input.IsActionJustPressed("pick-item")) + { + GetParent<InputHandler>().SetInputHandler(InputHandlers.Pickup); + } + + if (Input.IsActionJustPressed("inspect")) + { + GetParent<InputHandler>().SetInputHandler(InputHandlers.Inspect); + } + + if (Input.IsActionJustPressed("skip-turn")) + { + action = new WaitAction(player); + } + + return action; + } +} diff --git a/scripts/input/MainGameInputHandler.cs.uid b/scripts/InputHandling/MainGameInputHandler.cs.uid index 302a3b5..302a3b5 100644 --- a/scripts/input/MainGameInputHandler.cs.uid +++ b/scripts/InputHandling/MainGameInputHandler.cs.uid diff --git a/scripts/input/PickupInputHandler.cs b/scripts/InputHandling/PickupInputHandler.cs index 8f4f9b2..1396b2b 100644 --- a/scripts/input/PickupInputHandler.cs +++ b/scripts/InputHandling/PickupInputHandler.cs @@ -1,9 +1,14 @@ using Godot; +using TheLegendOfGustav.Entities.Actions; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.InputHandling; /// <summary> /// Esquema de controles para pegar um item. /// </summary> -public partial class PickupInputHandler : BaseInputHandler { +public partial class PickupInputHandler : BaseInputHandler +{ private readonly Godot.Collections.Dictionary<string, Vector2I> directions = new() { {"walk-up", Vector2I.Up}, @@ -16,26 +21,30 @@ public partial class PickupInputHandler : BaseInputHandler { {"walk-down-left", Vector2I.Down + Vector2I.Left}, {"skip-turn", Vector2I.Zero} }; - public override Action GetAction(Player player) { + + public override Action GetAction(Player player) + { Action action = null; - if (player.IsAlive) { - foreach (var direction in directions) { - if (Input.IsActionJustPressed(direction.Key)) { - action = new PickupAction(player, direction.Value); - Quit(); - } - } - - if (Input.IsActionJustPressed("quit")) { + foreach (var direction in directions) + { + if (Input.IsActionJustPressed(direction.Key)) + { + action = new PickupAction(player, direction.Value); Quit(); } } + if (Input.IsActionJustPressed("quit")) + { + Quit(); + } + return action; } - private void Quit() { + private void Quit() + { GetParent<InputHandler>().SetInputHandler(InputHandlers.MainGame); } } diff --git a/scripts/input/PickupInputHandler.cs.uid b/scripts/InputHandling/PickupInputHandler.cs.uid index 139dd25..139dd25 100644 --- a/scripts/input/PickupInputHandler.cs.uid +++ b/scripts/InputHandling/PickupInputHandler.cs.uid diff --git a/scripts/map/DungeonGenerator.cs b/scripts/Map/DungeonGenerator.cs index cb89508..9a44d33 100644 --- a/scripts/map/DungeonGenerator.cs +++ b/scripts/Map/DungeonGenerator.cs @@ -1,4 +1,9 @@ using Godot; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities; +using TheLegendOfGustav.Entities.Items; + +namespace TheLegendOfGustav.Map; /// <summary> /// A classe dungeonGenerator cria exatamente um andar da masmorra. @@ -6,9 +11,10 @@ using Godot; /// </summary> public partial class DungeonGenerator : Node { + #region Fields /// <summary> - /// Coleção de todos os inimigos que o gerador tem acesso. - /// </summary> + /// Coleção de todos os inimigos que o gerador tem acesso. + /// </summary> private static readonly Godot.Collections.Array<EnemyDefinition> enemies = [ GD.Load<EnemyDefinition>("res://assets/definitions/actor/Skeleton.tres"), GD.Load<EnemyDefinition>("res://assets/definitions/actor/morcegao.tres"), @@ -18,104 +24,80 @@ public partial class DungeonGenerator : Node private static readonly Godot.Collections.Array<ConsumableItemDefinition> items = [ GD.Load<HealingConsumableDefinition>("res://assets/definitions/Items/small_healing_potion.tres") ]; + #endregion + #region Properties /// <summary> /// Dimensões do mapa a ser criado. /// </summary> [ExportCategory("Dimension")] [Export] - private int width = 80; + private int Width { get; set; } = 80; [Export] - private int height = 60; + private int Height { get; set; } = 60; /// <summary> /// Gerador de números aleatórios /// </summary> [ExportCategory("RNG")] - private RandomNumberGenerator rng = new(); + private RandomNumberGenerator Rng { get; set; } = new(); /// <summary> - /// Qual seed utilizar. - /// </summary> + /// Qual seed utilizar. + /// </summary> [Export] - private ulong seed; + private ulong Seed { get; set; } /// <summary> - /// Se será utilizada a nossa seed ou a seed padrão da classe RandomNumberGenerator. - /// </summary> + /// Se será utilizada a nossa seed ou a seed padrão da classe RandomNumberGenerator. + /// </summary> [Export] - private bool useSeed = true; + private bool UseSeed { get; set; } = true; /// <summary> - /// Quantas iterações do algoritmo chamar. - /// </summary> + /// Quantas iterações do algoritmo chamar. + /// </summary> [Export] - private int iterations = 3; + private int Iterations { get; set; } = 3; /// <summary> - /// Quantidade máxima de inimigos por sala. - /// </summary> + /// Quantidade máxima de inimigos por sala. + /// </summary> [ExportCategory("Monster RNG")] [Export] - private int maxMonsterPerRoom = 2; + private int MaxMonsterPerRoom { get; set; } = 2; /// <summary> - /// Quantidade máxima de itens por sala. - /// </summary> + /// Quantidade máxima de itens por sala. + /// </summary> [ExportCategory("Loot RNG")] [Export] - private int maxItemsPerRoom = 2; + private int MaxItemsPerRoom { get; set; } = 2; + #endregion + #region Methods public override void _Ready() { base._Ready(); - if (useSeed) { - rng.Seed = seed; - } - } - - /// <summary> - /// Transforma o tile da posição especificada em chão. - /// </summary> - /// <param name="data">o mapa</param> - /// <param name="pos">posição para colocar o chão.</param> - private static void CarveTile(MapData data, Vector2I pos) - { - Tile tile = data.GetTile(pos); - if (tile == null) return; - - tile.SetDefinition(MapData.floorDefinition); - } - - /// <summary> - /// Preenche uma área retangular com chão. - /// </summary> - /// <param name="data">O mapa</param> - /// <param name="room">Área para preencher com chão</param> - private static void CarveRoom(MapData data, Rect2I room) - { - for (int y = room.Position.Y; y < room.End.Y; y++) + if (UseSeed) { - for (int x = room.Position.X; x < room.End.X; x++) - { - CarveTile(data, new Vector2I(x, y)); - } + Rng.Seed = Seed; } } /// <summary> - /// Gera um andar da masmorra. - /// Inimigos são colocados conforme configurações. - /// O jogador é colocado na primeira sala gerada. - /// </summary> - /// <param name="player">Jogador.</param> - /// <returns>O mapa gerado.</returns> + /// Gera um andar da masmorra. + /// Inimigos são colocados conforme configurações. + /// O jogador é colocado na primeira sala gerada. + /// </summary> + /// <param name="player">Jogador.</param> + /// <returns>O mapa gerado.</returns> public MapData GenerateDungeon(Player player) { - MapData data = new MapData(width, height, player); + MapData data = new MapData(Width, Height, player); // Divisão mestre que engloba o mapa inteiro. - MapDivision root = new MapDivision(0, 0, width, height); + MapDivision root = new MapDivision(0, 0, Width, Height); // Chama o algoritmo para dividir o mapa. - root.Split(iterations, rng); + root.Split(Iterations, Rng); bool first = true; @@ -123,16 +105,16 @@ public partial class DungeonGenerator : Node TunnelDivisions(data, root); // Cria as salas com base nas divisões geradas. - foreach(MapDivision division in root.GetLeaves()) + foreach (MapDivision division in root.GetLeaves()) { Rect2I room = new(division.Position, division.Size); - + // A sala não pode oculpar a divisão inteira, senão não haveriam paredes. room = room.GrowIndividual( - -rng.RandiRange(1, 2), - -rng.RandiRange(1, 2), - -rng.RandiRange(1, 2), - -rng.RandiRange(1, 2) + -Rng.RandiRange(1, 2), + -Rng.RandiRange(1, 2), + -Rng.RandiRange(1, 2), + -Rng.RandiRange(1, 2) ); // De fato cria a sala. @@ -153,120 +135,167 @@ public partial class DungeonGenerator : Node } /// <summary> - /// Popula uma sala com inimigos. - /// </summary> - /// <param name="data">O mapa</param> - /// <param name="room">A sala.</param> - private void PlaceEntities(MapData data, Rect2I room) { + /// Transforma o tile da posição especificada em chão. + /// </summary> + /// <param name="data">o mapa</param> + /// <param name="pos">posição para colocar o chão.</param> + private static void CarveTile(MapData data, Vector2I pos) + { + Tile tile = data.GetTile(pos); + if (tile == null) return; + + tile.SetDefinition(MapData.floorDefinition); + } + + /// <summary> + /// Preenche uma área retangular com chão. + /// </summary> + /// <param name="data">O mapa</param> + /// <param name="room">Área para preencher com chão</param> + private static void CarveRoom(MapData data, Rect2I room) + { + for (int y = room.Position.Y; y < room.End.Y; y++) + { + for (int x = room.Position.X; x < room.End.X; x++) + { + CarveTile(data, new Vector2I(x, y)); + } + } + } + + /// <summary> + /// Preenche uma linha horizontal com chão. + /// </summary> + /// <param name="data">O mapa</param> + /// <param name="y">Eixo y do corredor.</param> + /// <param name="xBegin">Início do corredor</param> + /// <param name="xEnd">Final do corredor.</param> + private static void HorizontalCorridor(MapData data, int y, int xBegin, int xEnd) + { + int begin = (xBegin < xEnd) ? xBegin : xEnd; + int end = (xEnd > xBegin) ? xEnd : xBegin; + for (int i = begin; i <= end; i++) + { + CarveTile(data, new Vector2I(i, y)); + } + } + + /// <summary> + /// Cria recursivamente corredores entre o centro de cada divisão do mapa. + /// </summary> + /// <param name="data">O mapa</param> + /// <param name="root">Divisão mestre.</param> + private static void TunnelDivisions(MapData data, MapDivision root) + { + if (root.IsLeaf) + { + return; + } + + TunnelBetween(data, root.Right.Center, root.Left.Center); + TunnelDivisions(data, root.Left); + TunnelDivisions(data, root.Right); + } + + /// <summary> + /// Preenche uma linha vertical com chão. + /// </summary> + /// <param name="data">O mapa.</param> + /// <param name="x">Eixo x do corredor.</param> + /// <param name="yBegin">Início do corredor</param> + /// <param name="yEnd">Final do corredor.</param> + private static void VerticalCorridor(MapData data, int x, int yBegin, int yEnd) + { + int begin = (yBegin < yEnd) ? yBegin : yEnd; + int end = (yEnd > yBegin) ? yEnd : yBegin; + for (int i = begin; i <= end; i++) + { + CarveTile(data, new Vector2I(x, i)); + } + } + + /// <summary> + /// Cria corredores vertical e horizontal para unir dois pontos no mapa. + /// </summary> + /// <param name="data">O mapa</param> + /// <param name="start">Ponto inicial</param> + /// <param name="end">Ponto final.</param> + private static void TunnelBetween(MapData data, Vector2I start, Vector2I end) + { + HorizontalCorridor(data, start.Y, start.X, end.X); + VerticalCorridor(data, end.X, start.Y, end.Y); + } + + /// <summary> + /// Popula uma sala com inimigos. + /// </summary> + /// <param name="data">O mapa</param> + /// <param name="room">A sala.</param> + private void PlaceEntities(MapData data, Rect2I room) + { // Define quantos monstros serão colocados na sala - int monsterAmount = rng.RandiRange(0, maxMonsterPerRoom); + int monsterAmount = Rng.RandiRange(0, MaxMonsterPerRoom); // Define quantos itens serão colocados na sala. - int itemAmount = rng.RandiRange(0, maxItemsPerRoom); + int itemAmount = Rng.RandiRange(0, MaxItemsPerRoom); - for (int i = 0; i < monsterAmount; i++) { + for (int i = 0; i < monsterAmount; i++) + { // Escolhe um lugar aleatório na sala. Vector2I position = new( - rng.RandiRange(room.Position.X, room.End.X - 1), - rng.RandiRange(room.Position.Y, room.End.Y - 1) + Rng.RandiRange(room.Position.X, room.End.X - 1), + Rng.RandiRange(room.Position.Y, room.End.Y - 1) ); // Só podemos colocar um ator por ponto no espaço. bool canPlace = true; - foreach (Entity entity in data.Entities) { - if (entity.GridPosition == position) { + foreach (Entity entity in data.Entities) + { + if (entity.GridPosition == position) + { canPlace = false; break; } } // Se possível, criamos um inimigo aleatório na posição escolhida. - if (canPlace) { + if (canPlace) + { EnemyDefinition definition = enemies.PickRandom(); Enemy enemy = new(position, data, definition); data.InsertEntity(enemy); } } - for (int i = 0; i < itemAmount; i++) { + for (int i = 0; i < itemAmount; i++) + { // Escolhe um lugar aleatório na sala. Vector2I position = new( - rng.RandiRange(room.Position.X, room.End.X - 1), - rng.RandiRange(room.Position.Y, room.End.Y - 1) + Rng.RandiRange(room.Position.X, room.End.X - 1), + Rng.RandiRange(room.Position.Y, room.End.Y - 1) ); // Só podemos colocar um ator por ponto no espaço. bool canPlace = true; - foreach (Entity entity in data.Entities) { - if (entity.GridPosition == position) { + foreach (Entity entity in data.Entities) + { + if (entity.GridPosition == position) + { canPlace = false; break; } } // Se possível, criamos um inimigo aleatório na posição escolhida. - if (canPlace) { + if (canPlace) + { ConsumableItemDefinition definition = items.PickRandom(); - if (definition is HealingConsumableDefinition hcDefinition) { + if (definition is HealingConsumableDefinition hcDefinition) + { HealingConsumable item = new(position, data, hcDefinition); data.InsertEntity(item); } } } } - - /// <summary> - /// Preenche uma linha horizontal com chão. - /// </summary> - /// <param name="data">O mapa</param> - /// <param name="y">Eixo y do corredor.</param> - /// <param name="xBegin">Início do corredor</param> - /// <param name="xEnd">Final do corredor.</param> - private static void HorizontalCorridor(MapData data, int y, int xBegin, int xEnd) { - int begin = (xBegin < xEnd) ? xBegin : xEnd; - int end = (xEnd > xBegin) ? xEnd : xBegin; - for (int i = begin; i <= end; i++) { - CarveTile(data, new Vector2I(i, y)); - } - } - - /// <summary> - /// Preenche uma linha vertical com chão. - /// </summary> - /// <param name="data">O mapa.</param> - /// <param name="x">Eixo x do corredor.</param> - /// <param name="yBegin">Início do corredor</param> - /// <param name="yEnd">Final do corredor.</param> - private static void VerticalCorridor(MapData data, int x, int yBegin, int yEnd) { - int begin = (yBegin < yEnd) ? yBegin : yEnd; - int end = (yEnd > yBegin) ? yEnd : yBegin; - for (int i = begin; i <= end; i++) { - CarveTile(data, new Vector2I(x, i)); - } - } - - /// <summary> - /// Cria corredores vertical e horizontal para unir dois pontos no mapa. - /// </summary> - /// <param name="data">O mapa</param> - /// <param name="start">Ponto inicial</param> - /// <param name="end">Ponto final.</param> - private static void TunnelBetween(MapData data, Vector2I start, Vector2I end) { - HorizontalCorridor(data, start.Y, start.X, end.X); - VerticalCorridor(data, end.X, start.Y, end.Y); - } - - /// <summary> - /// Cria recursivamente corredores entre o centro de cada divisão do mapa. - /// </summary> - /// <param name="data">O mapa</param> - /// <param name="root">Divisão mestre.</param> - private static void TunnelDivisions(MapData data, MapDivision root) { - if (root.IsLeaf) { - return; - } - - TunnelBetween(data, root.Right.Center, root.Left.Center); - TunnelDivisions(data, root.Left); - TunnelDivisions(data, root.Right); - } + #endregion } diff --git a/scripts/map/DungeonGenerator.cs.uid b/scripts/Map/DungeonGenerator.cs.uid index 15aeef6..15aeef6 100644 --- a/scripts/map/DungeonGenerator.cs.uid +++ b/scripts/Map/DungeonGenerator.cs.uid diff --git a/scripts/map/FieldOfView.cs b/scripts/Map/FieldOfView.cs index bdcd206..40df320 100644 --- a/scripts/map/FieldOfView.cs +++ b/scripts/Map/FieldOfView.cs @@ -1,21 +1,26 @@ using Godot; +namespace TheLegendOfGustav.Map; + // Copiado e adaptado deste cara aqui: https://www.roguebasin.com/index.php?title=C%2B%2B_shadowcasting_implementation e deste também https://selinadev.github.io/08-rogueliketutorial-04/ // Eu não vou mentir, li como o algoritmo funciona, mas mesmo assim não entendi. -public partial class FieldOfView : Node { +public partial class FieldOfView : Node +{ private Godot.Collections.Array<Tile> fov = []; - private static int[,] multipliers = new int[4,8]{ + private static readonly int[,] multipliers = new int[4, 8]{ {1, 0, 0, -1, -1, 0, 0, 1}, {0, 1, -1, 0, 0, -1, 1, 0}, {0, 1, 1, 0, 0, -1, -1, 0}, {1, 0, 0, 1, -1, 0, 0, -1} }; - private void CastLight(MapData data, Vector2I pos, int radius, int row, float startSlope, float endSlope, int xx, int xy, int yx, int yy) { - if (startSlope < endSlope) { + private void CastLight(MapData data, Vector2I pos, int radius, int row, float startSlope, float endSlope, int xx, int xy, int yx, int yy) + { + if (startSlope < endSlope) + { return; } @@ -40,56 +45,70 @@ public partial class FieldOfView : Node { int sax = dx * xx + dy * xy; int say = dx * yx + dy * yy; - if ((sax < 0 && int.Abs(sax) > pos.X) || (say < 0 && int.Abs(say) > pos.Y)) { + if ((sax < 0 && int.Abs(sax) > pos.X) || (say < 0 && int.Abs(say) > pos.Y)) + { continue; } int ax = pos.X + sax; int ay = pos.Y + say; - if (ax >= data.Width || ay >= data.Height) { + if (ax >= data.Width || ay >= data.Height) + { continue; } Tile currentTile = data.GetTile(ax, ay); int radius2 = radius * radius; - if ((dx * dx + dy * dy) < radius2) { + if ((dx * dx + dy * dy) < radius2) + { currentTile.IsInView = true; fov.Add(currentTile); } - if (blocked) { - if (!currentTile.IsTransparent) { + if (blocked) + { + if (!currentTile.IsTransparent) + { nextStartSlope = rSlope; continue; - } else { + } + else + { blocked = false; startSlope = nextStartSlope; } - } else if (!currentTile.IsTransparent) { + } + else if (!currentTile.IsTransparent) + { blocked = true; nextStartSlope = rSlope; CastLight(data, pos, radius, i + 1, startSlope, lSlope, xx, xy, yx, yy); } } - if (blocked) { + if (blocked) + { break; } } } - private void ClearFOV() { - foreach (Tile tile in fov) { + private void ClearFOV() + { + foreach (Tile tile in fov) + { tile.IsInView = false; } fov.Clear(); } - public void UpdateFOV(MapData data, Vector2I position, int radius) { + public void UpdateFOV(MapData data, Vector2I position, int radius) + { ClearFOV(); Tile start = data.GetTile(position); start.IsInView = true; fov.Add(start); - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; i++) + { CastLight(data, position, radius, 1, 1.0f, 0.0f, multipliers[0, i], multipliers[1, i], multipliers[2, i], multipliers[3, i]); } } diff --git a/scripts/map/FieldOfView.cs.uid b/scripts/Map/FieldOfView.cs.uid index a173ff3..a173ff3 100644 --- a/scripts/map/FieldOfView.cs.uid +++ b/scripts/Map/FieldOfView.cs.uid diff --git a/scripts/Map/Map.cs b/scripts/Map/Map.cs new file mode 100644 index 0000000..9117bc5 --- /dev/null +++ b/scripts/Map/Map.cs @@ -0,0 +1,98 @@ +using Godot; +using TheLegendOfGustav.Entities; +using TheLegendOfGustav.Entities.Actors; + +namespace TheLegendOfGustav.Map; + +/// <summary> +/// A parte visual do mapa. +/// </summary> +public partial class Map : Node2D +{ + /// <summary> + /// Dados do mapa. + /// </summary> + public MapData MapData { get; private set; } + + /// <summary> + /// raio de alcance da visão do jogador. + /// </summary> + [Export] + private int FovRadius { get; set; } = 12; + + /// <summary> + /// Gerador de mapas. + /// </summary> + private DungeonGenerator Generator { get; set; } + + FieldOfView FieldOfView { get; set; } + + private Node2D TilesNode { get; set; } + private Node2D EntitiesNode { get; set; } + + public override void _Ready() + { + base._Ready(); + // Começamos obtendo nós relevantes para o mapa. + Generator = GetNode<DungeonGenerator>("Generator"); + FieldOfView = GetNode<FieldOfView>("FieldOfView"); + TilesNode = GetNode<Node2D>("Tiles"); + EntitiesNode = GetNode<Node2D>("Entities"); + } + + /// <summary> + /// Cria um andar da masmorra utilizando o gerador de mapa. + /// </summary> + /// <param name="player">O gerador de mapas precisa do jogador.</param> + public void Generate(Player player) + { + MapData = Generator.GenerateDungeon(player); + + MapData.EntityPlaced += OnEntityPlaced; + + PlaceTiles(); + PlaceEntities(); + } + + /// <summary> + /// Atualiza o campo de visão do mapa com base em uma coordenada. + /// </summary> + /// <param name="pos">Centro de visão, normalmente é a posição do jogador.</param> + public void UpdateFOV(Vector2I pos) + { + FieldOfView.UpdateFOV(MapData, pos, FovRadius); + // Esconde ou revela entidades com base no campo de visão. + foreach (Entity entity in MapData.Entities) + { + entity.Visible = MapData.GetTile(entity.GridPosition).IsInView; + } + } + + + /// <summary> + /// Coloca todos os tiles do mapa no mundo do jogo. + /// </summary> + private void PlaceTiles() + { + foreach (Tile tile in MapData.Tiles) + { + TilesNode.AddChild(tile); + } + } + + /// <summary> + /// Coloca todas as entidades do mapa no mundo do jogo. + /// </summary> + private void PlaceEntities() + { + foreach (Entity entity in MapData.Entities) + { + EntitiesNode.AddChild(entity); + } + } + + private void OnEntityPlaced(Entity entity) + { + EntitiesNode.AddChild(entity); + } +} diff --git a/scripts/map/Map.cs.uid b/scripts/Map/Map.cs.uid index 7306888..7306888 100644 --- a/scripts/map/Map.cs.uid +++ b/scripts/Map/Map.cs.uid diff --git a/scripts/Map/MapData.cs b/scripts/Map/MapData.cs new file mode 100644 index 0000000..3365380 --- /dev/null +++ b/scripts/Map/MapData.cs @@ -0,0 +1,318 @@ +using Godot; +using TheLegendOfGustav.Entities; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities.Items; + +namespace TheLegendOfGustav.Map; + +/// <summary> +/// Classe que cuida dos dados e da parte lógica do mapa. +/// O mapa é o cenário onde as ações do jogo ocorrem. +/// Mais especificamente, o mapa é um único andar da masmorra. +/// </summary> +public partial class MapData : RefCounted +{ + #region Fields + public static readonly TileDefinition wallDefinition = GD.Load<TileDefinition>("res://assets/definitions/tiles/wall.tres"); + public static readonly TileDefinition floorDefinition = GD.Load<TileDefinition>("res://assets/definitions/tiles/floor.tres"); + /// <summary> + /// Peso do ator no pathfinder. + /// A IA irá evitar de passar por espaços com peso alto. + /// </summary> + private static readonly float EntityWeight = 10.0f; + #endregion + + #region Constructor + public MapData(int width, int height, Player player) + { + Width = width; + Height = height; + + Player = player; + // Como o jogador é criado antes do mapa, precisamos + // atualizá-lo com o novo mapa. + player.MapData = this; + InsertEntity(player); + + SetupTiles(); + } + #endregion + + #region Signals + [Signal] + public delegate void EntityPlacedEventHandler(Entity entity); + #endregion + + #region Properties + /// <summary> + /// Largura do mapa. + /// </summary> + public int Width { get; private set; } + /// <summary> + /// Altura do mapa. + /// </summary> + public int Height { get; private set; } + + /// <summary> + /// Os tiles que compõem o mapa. + /// </summary> + public Godot.Collections.Array<Tile> Tiles { get; private set; } = []; + + /// <summary> + /// O jogador é especial e por isso o mapa faz questão de rastreá-lo. + /// </summary> + public Player Player { get; set; } + /// <summary> + /// Lista de todas as entidades dentro do mapa. + /// </summary> + public Godot.Collections.Array<Entity> Entities { get; private set; } = []; + + /// <summary> + /// Lista de todos os itens dentro do mapa. + /// </summary> + public Godot.Collections.Array<ConsumableItem> Items + { + get + { + Godot.Collections.Array<ConsumableItem> list = []; + foreach (Entity entity in Entities) + { + if (entity is ConsumableItem item) + { + list.Add(item); + } + } + return list; + } + } + /// <summary> + /// Objeto do Godot que utiliza do algoritmo A* para calcular + /// caminhos e rotas. + /// </summary> + public AStarGrid2D Pathfinder { get; private set; } + #endregion + + #region Methods + /// <summary> + /// Inicializa o pathfinder; + /// </summary> + public void SetupPathfinding() + { + Pathfinder = new AStarGrid2D + { + //A região é o mapa inteiro. + Region = new Rect2I(0, 0, Width, Height) + }; + + // Atualiza o pathfinder para a região definida. + Pathfinder.Update(); + + // Define quais pontos do mapa são passáveis ou não. + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + Vector2I pos = new Vector2I(x, y); + Tile tile = GetTile(pos); + // Pontos sólidos são impossíveis de passar. + Pathfinder.SetPointSolid(pos, !tile.IsWalkable); + } + } + + // Registra todos os atores em cena. + foreach (Entity entity in Entities) + { + if (entity.BlocksMovement) + { + RegisterBlockingEntity(entity); + } + } + + } + + /// <summary> + /// Define um peso na posição de uma entidade para que a IA evite de passar por lá. + /// Ênfase em evitar. Se o único caminho para o destino estiver bloqueado + /// por uma entidade, o jogo tentará andar mesmo assim. + /// </summary> + /// <param name="entity">A entidade em questão.</param> + public void RegisterBlockingEntity(Entity entity) + { + Pathfinder.SetPointWeightScale(entity.GridPosition, EntityWeight); + } + + /// <summary> + /// Remove o peso na posição de uma entidade. + /// Quando uma entidade move sua posição, devemos tirar o peso de sua posição anterior. + /// </summary> + /// <param name="entity">A entidade em questão.</param> + public void UnregisterBlockingEntity(Entity entity) + { + Pathfinder.SetPointWeightScale(entity.GridPosition, 0); + } + + /// <summary> + /// Registra uma entidade no mapa. A existência de uma entidade não é considerada se ela não + /// estiver registrada no mapa. + /// </summary> + /// <param name="entity">A entidade em questão</param> + public void InsertEntity(Entity entity) + { + Entities.Add(entity); + } + + /// <summary> + /// Obtém o tile na posição desejada. + /// </summary> + /// <param name="pos">Vetor posição</param> + /// <returns>O tile na posição, nulo se for fora do mapa.</returns> + public Tile GetTile(Vector2I pos) + { + int index = GridToIndex(pos); + + if (index < 0) return null; + + return Tiles[index]; + } + + /// <summary> + /// Obtém o tile na posição desejada. + /// </summary> + /// <param name="x">x da coordenada</param> + /// <param name="y">y da coordenada</param> + /// <returns>O tile na posição, nulo se for fora do mapa.</returns> + public Tile GetTile(int x, int y) + { + return GetTile(new Vector2I(x, y)); + } + /// <summary> + /// Obtém a entidade na posição especificada. + /// </summary> + /// <param name="pos">Vetor posição</param> + /// <returns>A entidade na posição especificada, nulo se não houver.</returns> + public Entity GetBlockingEntityAtPosition(Vector2I pos) + { + foreach (Entity entity in Entities) + { + if (entity.GridPosition == pos && entity.BlocksMovement) + { + return entity; + } + } + return null; + } + + /// <summary> + /// Obtém o primeiro item na posição especificada. + /// </summary> + /// <param name="pos">Posição</param> + /// <returns>O primeiro item na posição, nulo se não houver.</returns> + public ConsumableItem GetFirstItemAtPosition(Vector2I pos) + { + foreach (ConsumableItem item in Items) + { + if (item.GridPosition == pos) + { + return item; + } + } + + return null; + } + + /// <summary> + /// Remove uma entidade do mapa sem dar free. + /// </summary> + /// <param name="entity">A entidade para remover</param> + public void RemoveEntity(Entity entity) + { + // Eu removo a entidade do nó de entidades. + entity.GetParent().RemoveChild(entity); + // Eu removo a entidade da lista de entidades do mapa. + Entities.Remove(entity); + } + + /// <summary> + /// Obtém todas as entidades na posição especificada. + /// É possível haver mais de uma entidade na mesma posição se uma delas não bloquear movimento. + /// </summary> + /// <param name="pos">Vetor posição</param> + /// <returns>Lista com todas as entidades na posição especificada.</returns> + public Godot.Collections.Array<Entity> GetEntitiesAtPosition(Vector2I pos) + { + Godot.Collections.Array<Entity> ZOfZero = []; + Godot.Collections.Array<Entity> ZOfOne = []; + Godot.Collections.Array<Entity> ZOfTwo = []; + + // Pego todos os atores + foreach (Entity entity in Entities) + { + if (entity.GridPosition == pos) + { + switch (entity.ZIndex) + { + case 0: + ZOfZero.Add(entity); + break; + case 1: + ZOfOne.Add(entity); + break; + case 2: + ZOfTwo.Add(entity); + break; + } + } + } + + // Retorno os atores ordenados por ZIndex. + return ZOfZero + ZOfOne + ZOfTwo; + } + + /// <summary> + /// Cria novos Tiles até preencher as dimensões do mapa. + /// É importante que estes tiles sejam paredes, o gerador de mapas + /// não cria paredes por conta própria. + /// </summary> + private void SetupTiles() + { + for (int i = 0; i < Height; i++) + { + for (int j = 0; j < Width; j++) + { + Tiles.Add(new Tile(new Vector2I(j, i), wallDefinition)); + } + } + } + + /// <summary> + /// Converte uma coordenada em um índice para acessar a lista de tiles. + /// </summary> + /// <param name="pos">Vetor posição</param> + /// <returns>Índice na lista de tiles. -1 se estiver fora do mapa.</returns> + private int GridToIndex(Vector2I pos) + { + if (!IsInBounds(pos)) return -1; + + return pos.Y * Width + pos.X; + } + + /// <summary> + /// Se uma coordenada está dentro da área do mapa. + /// </summary> + /// <param name="pos">Vetor posição</param> + /// <returns>Se o vetor está dentro do mapa.</returns> + private bool IsInBounds(Vector2I pos) + { + if (pos.X < 0 || pos.Y < 0) + { + return false; + } + if (pos.X >= Width || pos.Y >= Height) + { + return false; + } + + return true; + } + #endregion +} diff --git a/scripts/map/MapData.cs.uid b/scripts/Map/MapData.cs.uid index 6c226e7..6c226e7 100644 --- a/scripts/map/MapData.cs.uid +++ b/scripts/Map/MapData.cs.uid diff --git a/scripts/Map/MapDivision.cs b/scripts/Map/MapDivision.cs new file mode 100644 index 0000000..e50b331 --- /dev/null +++ b/scripts/Map/MapDivision.cs @@ -0,0 +1,113 @@ +using Godot; + +namespace TheLegendOfGustav.Map; + +/// <summary> +/// Classe utilizada pelo gerador de mapas. +/// Uma divisão é uma região retangular de espaço que pode +/// conter dentro de si duas regiões menores *ou* uma sala. +/// Uma divisão é uma árvore binária que possui espaço para salas em suas folhas. +/// </summary> +public partial class MapDivision : RefCounted +{ + public MapDivision(Vector2I position, Vector2I size) + { + Position = position; + Size = size; + } + + public MapDivision(Vector2I position, int width, int height) + { + Position = position; + Size = new(width, height); + } + + public MapDivision(int x, int y, int width, int height) + { + Position = new(x, y); + Size = new(width, height); + } + + /// <summary> + /// Região retangular da divisão. + /// </summary> + public Vector2I Position { get; set; } + public Vector2I Size { get; set; } + + public Vector2I Center + { + get => new(Position.X + Size.X / 2, Position.Y + Size.Y / 2); + } + + /// <summary> + /// Filhos da árvore + /// </summary> + public MapDivision Left { get; private set; } + public MapDivision Right { get; private set; } + + /// <summary> + /// Se a divisão atual for uma folha. + /// As folhas representam salas. + /// </summary> + public bool IsLeaf + { + get => Left == null && Right == null; + } + + /// <summary> + /// É conveniente ter acesso à todas as folhas da árvore. + /// </summary> + /// <returns>Lista com todas as folhas da árvore.</returns> + public Godot.Collections.Array<MapDivision> GetLeaves() + { + if (IsLeaf) + { + Godot.Collections.Array<MapDivision> list = []; + list.Add(this); + return list; + } + return Left.GetLeaves() + Right.GetLeaves(); + } + + /// <summary> + /// Algoritmo para gerar as divisões. + /// O mapa começa com uma única divisão que oculpa sua extensão completa. + /// Depois disso, ela se dividirá recursivamente n vezes. + /// As divisões nas folhas representam espaços onde pode gerar uma sala. + /// </summary> + /// <param name="iterations">Número de iterações</param> + /// <param name="rng">Gerador de números</param> + public void Split(int iterations, RandomNumberGenerator rng) + { + float SplitRatio = rng.RandfRange(0.35f, 0.65f); + bool horizontalSplit = Size.X <= Size.Y; + + // Eu defini um limite mínimo de 4 de altura e 4 de largura para divisões. + + if (horizontalSplit) + { + int leftHeight = (int)(Size.Y * SplitRatio); + if (leftHeight > 4 && Size.Y - leftHeight > 4) + { + Left = new MapDivision(Position, Size.X, leftHeight); + Right = new MapDivision(Position.X, Position.Y + leftHeight, Size.X, Size.Y - leftHeight); + } + } + else + { + int leftWidth = (int)(Size.Y * SplitRatio); + + if (leftWidth > 4 && Size.Y - leftWidth > 4) + { + Left = new MapDivision(Position, leftWidth, Size.Y); + Right = new MapDivision(Position.X + leftWidth, Position.Y, Size.X - leftWidth, Size.Y); + } + } + + if (iterations > 1) + { + Left?.Split(iterations - 1, rng); + Right?.Split(iterations - 1, rng); + } + } +}
\ No newline at end of file diff --git a/scripts/map/MapDivision.cs.uid b/scripts/Map/MapDivision.cs.uid index fdd53a4..fdd53a4 100644 --- a/scripts/map/MapDivision.cs.uid +++ b/scripts/Map/MapDivision.cs.uid diff --git a/scripts/map/Tile.cs b/scripts/Map/Tile.cs index 39f7486..e721fca 100644 --- a/scripts/map/Tile.cs +++ b/scripts/Map/Tile.cs @@ -1,5 +1,7 @@ using Godot; -using System; +using TheLegendOfGustav.Utils; + +namespace TheLegendOfGustav.Map; /// <summary> /// O mundo do jogo é composto por Tiles. @@ -9,70 +11,77 @@ using System; /// </summary> public partial class Tile : Sprite2D { - /// <summary> - /// A definição do tile carrega seus valores padrão. - /// </summary> - private TileDefinition definition; + private bool isExplored = false; + private bool isInView = false; + + public Tile(Vector2I pos, TileDefinition definition) + { + // Tile herda da classe Sprite2D. + // Por padrão, a posição do Sprite2D é no centro de sua textura. + // Para o jogo, faz mais sentido que a posição seja no + // canto superior esquerdo. + Centered = false; + // Tiles começam invisíveis porque não foram vistos pelo jogador. + Visible = false; + Position = Grid.GridToWorld(pos); + SetDefinition(definition); + } /// <summary> - /// Determina se atores podem andar em cima do Tile. - /// </summary> + /// Determina se atores podem andar em cima do Tile. + /// </summary> public bool IsWalkable { get; private set; } /// <summary> - /// Determina se o tile bloqueia visão. - /// </summary> + /// Determina se o tile bloqueia visão. + /// </summary> public bool IsTransparent { get; private set; } - private bool isExplored = false; /// <summary> - /// Se o jogador já viu este tile antes. - /// Tiles não descobertos são invisíveis. - /// </summary> - public bool IsExplored { - get => this.isExplored; - set { + /// A definição do tile carrega seus valores padrão. + /// </summary> + private TileDefinition Definition { get; set; } + /// <summary> + /// Se o jogador já viu este tile antes. + /// Tiles não descobertos são invisíveis. + /// </summary> + public bool IsExplored + { + get => isExplored; + set + { isExplored = value; - if (IsExplored && !Visible) { + if (IsExplored && !Visible) + { Visible = true; } } } - private bool isInView = false; /// <summary> - /// Se o jogador vê o tile neste exato momento. - /// Elementos neste tile estão dentro do campo de visão do jogador. - /// </summary> - public bool IsInView { + /// Se o jogador vê o tile neste exato momento. + /// Elementos neste tile estão dentro do campo de visão do jogador. + /// </summary> + public bool IsInView + { get => isInView; - set { + set + { isInView = value; - Modulate = isInView ? definition.LitColor : definition.DarkColor; - if (IsInView && !IsExplored) { + Modulate = isInView ? Definition.LitColor : Definition.DarkColor; + if (IsInView && !IsExplored) + { IsExplored = true; } } } - public Tile(Vector2I pos, TileDefinition definition) - { - // Tile herda da classe Sprite2D. - // Por padrão, a posição do Sprite2D é no centro de sua textura. - // Para o jogo, faz mais sentido que a posição seja no - // canto superior esquerdo. - Centered = false; - // Tiles começam invisíveis porque não foram vistos pelo jogador. - Visible = false; - Position = Grid.GridToWorld(pos); - SetDefinition(definition); - } - /// <summary> - /// Define as características do tile. - /// </summary> - /// <param name="definition">Definição do tile.</param> - public void SetDefinition(TileDefinition definition) { - this.definition = definition; + /// Define as características do tile. + /// </summary> + /// <param name="definition">Definição do tile.</param> + public void SetDefinition(TileDefinition definition) + { + Definition = definition; Modulate = definition.DarkColor; Texture = definition.Texture; IsWalkable = definition.IsWalkable; diff --git a/scripts/map/Tile.cs.uid b/scripts/Map/Tile.cs.uid index 9fa3c81..9fa3c81 100644 --- a/scripts/map/Tile.cs.uid +++ b/scripts/Map/Tile.cs.uid diff --git a/scripts/map/TileDefinition.cs b/scripts/Map/TileDefinition.cs index 84e5cc1..e843b52 100644 --- a/scripts/map/TileDefinition.cs +++ b/scripts/Map/TileDefinition.cs @@ -1,5 +1,7 @@ using Godot; +namespace TheLegendOfGustav.Map; + /// <summary> /// Define as características de um tile. /// </summary> diff --git a/scripts/map/TileDefinition.cs.uid b/scripts/Map/TileDefinition.cs.uid index 14f2903..14f2903 100644 --- a/scripts/map/TileDefinition.cs.uid +++ b/scripts/Map/TileDefinition.cs.uid diff --git a/scripts/Time/TurnManager.cs b/scripts/Time/TurnManager.cs index 01efc18..9713fea 100644 --- a/scripts/Time/TurnManager.cs +++ b/scripts/Time/TurnManager.cs @@ -1,86 +1,98 @@ using Godot; +using TheLegendOfGustav.Map; +using TheLegendOfGustav.Entities.Actors; +using TheLegendOfGustav.Entities; +using TheLegendOfGustav.Entities.Actions; + +namespace TheLegendOfGustav.Time; /// <summary> /// Gerenciador de turnos, o senhor do tempo. /// </summary> -public partial class TurnManager : RefCounted +public partial class TurnManager(Map.Map map) : RefCounted { public int TurnCount { get; private set; } = 0; - private Map map; - private MapData Map_Data { get => map.Map_Data; } + private Map.Map Map { get; set; } = map; + private MapData Map_Data { get => Map.MapData; } private Player Player { get => Map_Data.Player; } /// <summary> - /// As ações do jogador ficam em uma fila e são executadas quando - /// possível dentro das regras do senhor do tempo. - /// </summary> - private Godot.Collections.Array<Action> playerActionQueue = []; - - public TurnManager(Map map) { - this.map = map; - } + /// As ações do jogador ficam em uma fila e são executadas quando + /// possível dentro das regras do senhor do tempo. + /// </summary> + private Godot.Collections.Array<Action> PlayerActionQueue { get; set; } = []; /// <summary> - /// Insere uma ação na fila de ações do jogador. - /// </summary> - /// <param name="playerAction"></param> - public void InsertPlayerAction(Action playerAction) { - playerActionQueue.Add(playerAction); + /// Insere uma ação na fila de ações do jogador. + /// </summary> + /// <param name="playerAction"></param> + public void InsertPlayerAction(Action playerAction) + { + PlayerActionQueue.Add(playerAction); } /// <summary> - /// Computa um turno. - /// - /// Um turno segue a seguinte ordem lógica - /// 1. Todos os atores recebem energia com base nas suas velocidades. - /// 2. O jogador performa ações enquanto sua energia permitir - /// 3. Os outros atores performam suas ações enquanto sua energia permitir. - /// </summary> - public void Tick() { + /// Computa um turno. + /// + /// Um turno segue a seguinte ordem lógica + /// 1. Todos os atores recebem energia com base nas suas velocidades. + /// 2. O jogador performa ações enquanto sua energia permitir + /// 3. Os outros atores performam suas ações enquanto sua energia permitir. + /// </summary> + public void Tick() + { // Se o jogador puder agir mas a fila de ações estiver vazia, // não computamos o turno. - if (playerActionQueue.Count == 0 && Player.Energy > 0) { + if (PlayerActionQueue.Count == 0 && Player.Energy > 0) + { return; } Vector2I previousPlayerPos = Player.GridPosition; // Início do turno, o jogador recebe um pouco de energia. - if (Player.Energy <= 0) { + if (Player.Energy <= 0) + { StartTurn(); } - bool actionResult = true;; + bool actionResult = true; ; // Primeiro executamos a ação do jogador, se ele puder. - if (playerActionQueue.Count > 0 && Player.Energy > 0) { - Action action = playerActionQueue[0]; - playerActionQueue.RemoveAt(0); + if (PlayerActionQueue.Count > 0 && Player.Energy > 0) + { + Action action = PlayerActionQueue[0]; + PlayerActionQueue.RemoveAt(0); actionResult = action.Perform(); } // Se a ação do jogador for gratuita ou se o jogador ainda possuir energia, // ele poderá fazer mais um turno sem interrupções. - if (actionResult && Player.Energy <= 0) { + if (actionResult && Player.Energy <= 0) + { // Depois computamos os turnos dos outros atores. HandleEnemyTurns(); - map.UpdateFOV(Player.GridPosition); + Map.UpdateFOV(Player.GridPosition); } // Por fim, se o jogador mudou de lugar, atualizamos seu campo de visão. - if (Player.GridPosition != previousPlayerPos) { - map.UpdateFOV(Player.GridPosition); + if (Player.GridPosition != previousPlayerPos) + { + Map.UpdateFOV(Player.GridPosition); } } /// <summary> - /// Método executado no início do turno. - /// </summary> - private void StartTurn() { + /// Método executado no início do turno. + /// </summary> + private void StartTurn() + { TurnCount++; // Recarregamos a energia de todos os atores. - foreach (Entity entity in Map_Data.Entities) { - if (entity is Actor actor && actor.IsAlive) { + foreach (Entity entity in Map_Data.Entities) + { + if (entity is Actor actor && actor.IsAlive) + { actor.RechargeEnergy(); } } @@ -89,14 +101,18 @@ public partial class TurnManager : RefCounted /// <summary> /// Executa turnos para cada ator no mapa. /// </summary> - private void HandleEnemyTurns() { - foreach (Entity entity in Map_Data.Entities) { + private void HandleEnemyTurns() + { + foreach (Entity entity in Map_Data.Entities) + { if (entity is Player) continue; // Se o ator for um inimigo e estiver vivo, deixamos // que sua IA faça um turno. - if (entity is Enemy enemy && enemy.IsAlive) { + if (entity is Enemy enemy && enemy.IsAlive) + { // O inimigo poderá fazer quantos turnos sua energia deixar. - while (enemy.Energy > 0) { + while (enemy.Energy > 0) + { enemy.Soul.Perform(); } } diff --git a/scripts/Utils/Grid.cs b/scripts/Utils/Grid.cs index 271f559..b744c60 100644 --- a/scripts/Utils/Grid.cs +++ b/scripts/Utils/Grid.cs @@ -1,5 +1,6 @@ using Godot; -using System; + +namespace TheLegendOfGustav.Utils; /// <summary> /// Classe utilitária para converter coordenadas da malha dos tiles @@ -7,14 +8,17 @@ using System; /// Esta classe é necessária porque o Godot trata posições em pixels, /// mas faz mais sentido tratarmos as posições em tiles. /// </summary> -public abstract partial class Grid : GodotObject { +public abstract partial class Grid : GodotObject +{ public static readonly Vector2I tileSize = new(16, 16); - public static Vector2I WorldToGrid(Vector2 coord) { + public static Vector2I WorldToGrid(Vector2 coord) + { return (Vector2I)(coord / tileSize); } - public static Vector2 GridToWorld(Vector2I coord) { + public static Vector2 GridToWorld(Vector2I coord) + { return coord * tileSize; } } diff --git a/scripts/entities/actors/Inspector.cs b/scripts/Utils/Inspector.cs index e340543..7fb12ff 100644 --- a/scripts/entities/actors/Inspector.cs +++ b/scripts/Utils/Inspector.cs @@ -1,17 +1,23 @@ using Godot; +namespace TheLegendOfGustav.Utils; + /// <summary> /// Isto é uma abominação /// </summary> public partial class Inspector : Sprite2D { private Vector2I gridPosition = Vector2I.Zero; + + /// <summary> - /// Posição do inspetor no espaço. Diferentemente de Position, GridPosition tem como formato - /// os tiles do mapa. - /// </summary> - public Vector2I GridPosition { - set { + /// Posição do inspetor no espaço. Diferentemente de Position, GridPosition tem como formato + /// os tiles do mapa. + /// </summary> + public Vector2I GridPosition + { + set + { gridPosition = value; // O sistema de coordenadas do Godot é em pixels, mas faz mais sentido para o jogo utilizar coordenadas em tiles. // Esta propriedade converte um sistema para o outro automaticamente. @@ -20,7 +26,8 @@ public partial class Inspector : Sprite2D get => gridPosition; } - public override void _Ready() { + public override void _Ready() + { base._Ready(); Camera2D camera = GetNode<Camera2D>("Camera2D"); camera.Enabled = true; @@ -30,10 +37,11 @@ public partial class Inspector : Sprite2D } /// <summary> - /// O Inspetor não faz parte do mapa. - /// </summary> - /// <param name="offset"></param> - public void Walk(Vector2I offset) { + /// O Inspetor não faz parte do mapa. + /// </summary> + /// <param name="offset"></param> + public void Walk(Vector2I offset) + { GridPosition += offset; SignalBus.Instance.EmitSignal(SignalBus.SignalName.InspectorMoved, GridPosition); } diff --git a/scripts/entities/actors/Inspector.cs.uid b/scripts/Utils/Inspector.cs.uid index ca411e4..ca411e4 100644 --- a/scripts/entities/actors/Inspector.cs.uid +++ b/scripts/Utils/Inspector.cs.uid diff --git a/scripts/Utils/MessageLogData.cs b/scripts/Utils/MessageLogData.cs index e8f0f09..46d2c3e 100644 --- a/scripts/Utils/MessageLogData.cs +++ b/scripts/Utils/MessageLogData.cs @@ -1,25 +1,30 @@ using Godot; +using TheLegendOfGustav.GUI; + +namespace TheLegendOfGustav.Utils; public partial class MessageLogData : Node { + /// <summary> + /// Acionado sempre que uma mensagem for adicionada para o log. + /// </summary> + /// <param name="text">Mensagem.</param> + [Signal] + public delegate void messageSentEventHandler(Message message); + public static MessageLogData Instance { get; private set; } - private Godot.Collections.Array<Message> messages = []; - public Godot.Collections.Array<Message> Messages {get => messages;} -private Message LastMessage { - get { - if (messages.Count <= 0) { + public Godot.Collections.Array<Message> Messages { get; private set; } = []; + + private Message LastMessage + { + get + { + if (Messages.Count <= 0) + { return null; } - return messages[^1]; - } - } - - public void ClearMessages() { - for (int i = messages.Count - 1; i >= 0; i--) { - Message message = messages[i]; - messages.RemoveAt(i); - message.QueueFree(); + return Messages[^1]; } } @@ -29,22 +34,26 @@ private Message LastMessage { Instance = this; } - public void AddMessage(string text) { - if (LastMessage != null && LastMessage.PlainText == text) { + public void ClearMessages() + { + for (int i = Messages.Count - 1; i >= 0; i--) + { + Message message = Messages[i]; + Messages.RemoveAt(i); + message.QueueFree(); + } + } + + public void AddMessage(string text) + { + if (LastMessage != null && LastMessage.PlainText == text) + { LastMessage.Count++; return; } Message message = new(text); - messages.Add(message); + Messages.Add(message); EmitSignal(SignalName.messageSent, message); } - - /// <summary> - /// Acionado sempre que uma mensagem for adicionada para o log. - /// </summary> - /// <param name="text">Mensagem.</param> - [Signal] - public delegate void messageSentEventHandler(Message message); - }
\ No newline at end of file diff --git a/scripts/Utils/SignalBus.cs b/scripts/Utils/SignalBus.cs index edeb09b..a2aa6ca 100644 --- a/scripts/Utils/SignalBus.cs +++ b/scripts/Utils/SignalBus.cs @@ -1,5 +1,6 @@ using Godot; -using System; + +namespace TheLegendOfGustav.Utils; /// <summary> /// Objeto global com sinais, fortes sinais. @@ -7,16 +8,10 @@ using System; public partial class SignalBus : Node { /// <summary> - /// Pois é. - /// </summary> + /// Pois é. + /// </summary> public static SignalBus Instance { get; private set; } - public override void _Ready() - { - base._Ready(); - Instance = this; - } - [Signal] public delegate void InspectorMovedEventHandler(Vector2I pos); @@ -24,4 +19,10 @@ public partial class SignalBus : Node public delegate void EnterInspectionModeEventHandler(); [Signal] public delegate void ExitInspectionModeEventHandler(); + + public override void _Ready() + { + base._Ready(); + Instance = this; + } } diff --git a/scripts/entities/actions/Action.cs b/scripts/entities/actions/Action.cs deleted file mode 100644 index 9dab5a4..0000000 --- a/scripts/entities/actions/Action.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Godot; - - -/// <summary> -/// <c>Action</c> representa uma ação no jogo efetuada por um ator. -/// Ações são geradas pelo jogador e pela IA, elas regem os atores do jogo. -/// </summary> -public abstract partial class Action : RefCounted { - /// <summary> - /// O ator que realiza a ação. - /// </summary> - protected Actor actor; - public Actor ThisActor { get => actor; } - - // O custo da ação. - protected int cost; - - public Action(Actor actor) { - this.actor = actor; - // Custo base, subclasses podem sobreescrever isto se quiserem. - cost = 10; - } - - /// <summary> - /// Método que executa a ação. Subclasses da ação devem implementar este método. - /// <example> - /// Exemplo: - /// <code> - /// Action action = new Action(actor); - /// /* . . . */ - /// action.Perform(); - /// </code> - /// </example> - /// </summary> - /// <returns>Se a ação foi executada ou não.</returns> - public abstract bool Perform(); - - /// <summary> - /// É conveniente ter acesso ao mapa dentro de uma ação. - /// </summary> - protected MapData Map_Data { - get => actor.Map_Data; - } -} diff --git a/scripts/entities/actions/DirectionalAction.cs b/scripts/entities/actions/DirectionalAction.cs deleted file mode 100644 index 9c7a915..0000000 --- a/scripts/entities/actions/DirectionalAction.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Godot; - -/// <summary> -/// Ação direcionada. Esta ação é acompanhada com um vetor que representa uma -/// distância tendo como ponto de partida o ator. -/// </summary> -public abstract partial class DirectionalAction : Action -{ - /// <summary> - /// Direção/distância do ator da ação. - /// Seu significado depende da ação que implementará esta classe. - /// </summary> - public Vector2I Offset { get; private set; } - /// <summary> - /// Coordenada do alvo da ação. - /// </summary> - public Vector2I Destination { get => actor.GridPosition + Offset; } - public DirectionalAction(Actor actor, Vector2I offset) : base(actor) - { - Offset = offset; - } - - /// <summary> - /// Função que obtém o alvo da ação, se houver. - /// </summary> - /// <returns>O ator alvo da ação, nulo se não houver.</returns> - protected Entity GetTarget() { - return Map_Data.GetBlockingEntityAtPosition(Destination); - } -} diff --git a/scripts/entities/actions/DropAction.cs b/scripts/entities/actions/DropAction.cs deleted file mode 100644 index e5bd929..0000000 --- a/scripts/entities/actions/DropAction.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Godot; - -public partial class DropAction : ItemAction -{ - public DropAction(Player player, ConsumableItem item) : base(player, item) - { - } - - public override bool Perform() { - player.inventory.Drop(item); - return true; - } -}
\ No newline at end of file diff --git a/scripts/entities/actions/ItemAction.cs b/scripts/entities/actions/ItemAction.cs deleted file mode 100644 index d5247df..0000000 --- a/scripts/entities/actions/ItemAction.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Godot; - -public partial class ItemAction : Action -{ - protected ConsumableItem item; - protected Player player; - public ItemAction(Player player, ConsumableItem item) : base(player) - { - this.item = item; - this.player = player; - } - - public override bool Perform() - { - return item.Activate(this); - } -}
\ No newline at end of file diff --git a/scripts/entities/actions/PickUpAction.cs b/scripts/entities/actions/PickUpAction.cs deleted file mode 100644 index b772bb7..0000000 --- a/scripts/entities/actions/PickUpAction.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Godot; - -public partial class PickupAction : DirectionalAction -{ - protected Player player; - - public PickupAction(Player player, Vector2I offset) : base(player, offset) - { - this.player = player; - // Pegar itens requer um tempo menor. - cost = 2; - } - - public override bool Perform() - { - ConsumableItem item = Map_Data.GetFirstItemAtPosition(Destination); - - if (item == null) { - MessageLogData.Instance.AddMessage("Não tem item aqui."); - return false; - } - - if (player.inventory.Items.Count >= player.inventory.Capacity) { - MessageLogData.Instance.AddMessage("Seu inventário está cheio"); - return false; - } - - Map_Data.RemoveEntity(item); - player.inventory.Add(item); - - player.Energy -= cost; - return true; - } -}
\ No newline at end of file diff --git a/scripts/entities/actors/AI/BaseAI.cs b/scripts/entities/actors/AI/BaseAI.cs deleted file mode 100644 index 733a61a..0000000 --- a/scripts/entities/actors/AI/BaseAI.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Godot; - -/// <summary> -/// base para as IAs do jogo. -/// </summary> -public abstract partial class BaseAI : Node { - /// <summary> - /// Corpo controlado pela IA. - /// O corpo é a marionete da alma. - /// </summary> - protected Actor body; - - public override void _Ready() - { - base._Ready(); - // Por padrão, a IA é filha do nó de seu corpo. - body = GetParent<Actor>(); - } - - /// <summary> - /// Computa um único turno para o ator controlado. - /// Aviso: NPCs não possuem ações gratuitas. - /// A IA SEMPRE precisa executar uma ação que custe energia. - /// </summary> - public abstract void Perform(); - - /// <summary> - /// Utiliza o pathfinder do mapa para obter um caminho - /// da posição atual do ator para um destino qualquer. - /// </summary> - /// <param name="destination">Destino</param> - /// <returns>Vetor com vetores, passo a passo para chegar no destino.</returns> - public Godot.Collections.Array<Vector2> GetPathTo(Vector2I destination) { - // Arrays do Godot são muito mais confortáveis de manipular, então - // eu converto o Array do C# em um array do Godot antes de retornar o caminho. - Godot.Collections.Array<Vector2> list = []; - Vector2[] path = body.Map_Data.Pathfinder.GetPointPath(body.GridPosition, destination); - list.AddRange(path); - return list; - } -}
\ No newline at end of file diff --git a/scripts/entities/actors/Inventory.cs b/scripts/entities/actors/Inventory.cs deleted file mode 100644 index f1cff2a..0000000 --- a/scripts/entities/actors/Inventory.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Godot; - -public partial class Inventory : Node { - private Player player; - - public Godot.Collections.Array<ConsumableItem> Items { get; private set; } = []; - - public int Capacity { get; private set; } - - public Inventory(int capacity) { - Capacity = capacity; - } - - public override void _Ready() { - base._Ready(); - player = GetParent<Player>(); - } - - public void Drop(ConsumableItem item) { - Items.Remove(item); - MapData data = player.Map_Data; - data.InsertEntity(item); - data.EmitSignal(MapData.SignalName.EntityPlaced, item); - item.Map_Data = data; - item.GridPosition = player.GridPosition; - - MessageLogData.Instance.AddMessage($"Você descarta {item.DisplayName}."); - } - - public void Add(ConsumableItem item) { - if (Items.Count >= Capacity) return; - - Items.Add(item); - } - - public void RemoveItem(ConsumableItem item) { - Items.Remove(item); - } -}
\ No newline at end of file diff --git a/scripts/entities/items/HealingConsumable.cs b/scripts/entities/items/HealingConsumable.cs deleted file mode 100644 index 2104693..0000000 --- a/scripts/entities/items/HealingConsumable.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Godot; - -public partial class HealingConsumable : ConsumableItem -{ - private HealingConsumableDefinition definition; - public float HealingPercentage { get; private set; } - public HealingConsumable(Vector2I initialPosition, MapData map, HealingConsumableDefinition definition) : base(initialPosition, map, definition) - { - this.definition = definition; - HealingPercentage = definition.healingPercentage; - } - - public override bool Activate(ItemAction action) - { - Player consumer = (Player) action.ThisActor; - int intendedAmount = (int)(HealingPercentage / 100 * consumer.MaxHp); - int recovered = consumer.Heal(intendedAmount); - - // Se não tinha o que curar, a ativação falhou. - if (recovered == 0) { - MessageLogData.Instance.AddMessage("Você já está saudável."); - return false; - } - MessageLogData.Instance.AddMessage($"Você consome {DisplayName} e recupera {recovered} de HP"); - ConsumedBy(consumer); - return true; - } -}
\ No newline at end of file diff --git a/scripts/input/InputHandler.cs b/scripts/input/InputHandler.cs deleted file mode 100644 index 67f4abf..0000000 --- a/scripts/input/InputHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Godot; - -public enum InputHandlers -{ - MainGame, - GameOver, - Inspect, - Pickup, - Inventory -} - -/// <summary> -/// Máquina de estado que obtém ações do usuário conforme o estado atual do jogo. -/// </summary> -public partial class InputHandler : Node -{ - private Godot.Collections.Dictionary<InputHandlers, BaseInputHandler> inputHandlers = []; - - [Export] - private InputHandlers startingInputHandler; - - private BaseInputHandler selectedInputHandler; - - public override void _Ready() - { - base._Ready(); - // Controles para quando o jogador está vivo e jogando normalmente. - inputHandlers.Add(InputHandlers.MainGame, GetNode<MainGameInputHandler>("MainGameInputHandler")); - // Controles para quando o jogador está morto. - inputHandlers.Add(InputHandlers.GameOver, GetNode<GameOverInputHandler>("GameOverInputHandler")); - inputHandlers.Add(InputHandlers.Inspect, GetNode<InspectInputHandler>("InspectInputHandler")); - inputHandlers.Add(InputHandlers.Pickup, GetNode<PickupInputHandler>("PickupInputHandler")); - inputHandlers.Add(InputHandlers.Inventory, GetNode<InventoryInputHandler>("InventoryInputHandler")); - - SetInputHandler(startingInputHandler); - } - - public Action GetAction(Player player) { - return selectedInputHandler.GetAction(player); - } - - /// <summary> - /// Define o esquema de controle atual do jogo - /// para o estado informado. - /// </summary> - /// <param name="inputhandler">Estado do jogo.</param> - public void SetInputHandler(InputHandlers inputhandler) { - selectedInputHandler?.Exit(); - selectedInputHandler = inputHandlers[inputhandler]; - selectedInputHandler.Enter(); - } -} diff --git a/scripts/input/InventoryInputHandler.cs b/scripts/input/InventoryInputHandler.cs deleted file mode 100644 index 98f8576..0000000 --- a/scripts/input/InventoryInputHandler.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Godot; - -public partial class InventoryInputHandler : BaseInputHandler -{ - private static readonly PackedScene inventoryScene = GD.Load<PackedScene>("res://scenes/GUI/invetory_menu.tscn"); - - private InventoryMenu inventoryMenu; - - ConsumableItem activationItem = null; - ConsumableItem dropItem = null; - - [Export] - private Map map; - - public override void Enter() { - inventoryMenu = inventoryScene.Instantiate<InventoryMenu>(); - map.Map_Data.Player.AddChild(inventoryMenu); - inventoryMenu.Initialize(map.Map_Data.Player.inventory); - inventoryMenu.ItemSelected += OnItemActivate; - inventoryMenu.ItemDrop += OnItemDrop; - } - - public override void Exit() { - activationItem = null; - dropItem = null; - inventoryMenu.QueueFree(); - } - - public override Action GetAction(Player player) - { - Action action = null; - - if (activationItem != null) { - action = new ItemAction(player, activationItem); - Close(); - } - - if (dropItem != null) { - action = new DropAction(player, dropItem); - Close(); - } - - if (Input.IsActionJustPressed("quit")) { - Close(); - } - - return action; - } - - private void Close() { - GetParent<InputHandler>().SetInputHandler(InputHandlers.MainGame); - } - - private void ActivateItem() { - - } - - private void OnItemActivate(ConsumableItem item) { - activationItem = item; - } - - private void OnItemDrop(ConsumableItem item) { - dropItem = item; - } -}
\ No newline at end of file diff --git a/scripts/input/MainGameInputHandler.cs b/scripts/input/MainGameInputHandler.cs deleted file mode 100644 index 6bda004..0000000 --- a/scripts/input/MainGameInputHandler.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Godot; - -/// <summary> -/// Esquema de controles principal do jogo. -/// </summary> -public partial class MainGameInputHandler : BaseInputHandler { - private static readonly Godot.Collections.Dictionary<string, Vector2I> directions = new() - { - {"walk-up", Vector2I.Up}, - {"walk-down", Vector2I.Down}, - {"walk-left", Vector2I.Left}, - {"walk-right", Vector2I.Right}, - {"walk-up-right", Vector2I.Up + Vector2I.Right}, - {"walk-up-left", Vector2I.Up + Vector2I.Left}, - {"walk-down-right", Vector2I.Down + Vector2I.Right}, - {"walk-down-left", Vector2I.Down + Vector2I.Left}, - }; - public override Action GetAction(Player player) { - Action action = null; - - if (player.IsAlive) { - foreach (var direction in directions) { - if (Input.IsActionJustPressed(direction.Key)) { - action = new BumpAction(player, direction.Value); - } - } - - if (Input.IsActionJustPressed("open-inventory")) { - GetParent<InputHandler>().SetInputHandler(InputHandlers.Inventory); - } - - if (Input.IsActionJustPressed("pick-item")) { - GetParent<InputHandler>().SetInputHandler(InputHandlers.Pickup); - } - - if (Input.IsActionJustPressed("inspect")) { - GetParent<InputHandler>().SetInputHandler(InputHandlers.Inspect); - } - - if (Input.IsActionJustPressed("skip-turn")) { - action = new WaitAction(player); - } - } - - return action; - } -} diff --git a/scripts/map/Map.cs b/scripts/map/Map.cs deleted file mode 100644 index 148bda6..0000000 --- a/scripts/map/Map.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Godot; - -/// <summary> -/// A parte visual do mapa. -/// </summary> -public partial class Map : Node2D -{ - /// <summary> - /// Dados do mapa. - /// </summary> - public MapData Map_Data { get; private set; } - - /// <summary> - /// raio de alcance da visão do jogador. - /// </summary> - [Export] - private int fovRadius = 12; - - /// <summary> - /// Gerador de mapas. - /// </summary> - private DungeonGenerator generator; - - FieldOfView fieldOfView; - - private Node2D tilesNode; - private Node2D entitiesNode; - - public override void _Ready() - { - base._Ready(); - // Começamos obtendo nós relevantes para o mapa. - generator = GetNode<DungeonGenerator>("Generator"); - fieldOfView = GetNode<FieldOfView>("FieldOfView"); - tilesNode = GetNode<Node2D>("Tiles"); - entitiesNode = GetNode<Node2D>("Entities"); - } - - /// <summary> - /// Coloca todos os tiles do mapa no mundo do jogo. - /// </summary> - private void PlaceTiles() { - foreach (Tile tile in Map_Data.Tiles) { - tilesNode.AddChild(tile); - } - } - - /// <summary> - /// Coloca todas as entidades do mapa no mundo do jogo. - /// </summary> - private void PlaceEntities() { - foreach (Entity entity in Map_Data.Entities) { - entitiesNode.AddChild(entity); - } - } - - /// <summary> - /// Cria um andar da masmorra utilizando o gerador de mapa. - /// </summary> - /// <param name="player">O gerador de mapas precisa do jogador.</param> - public void Generate(Player player) - { - Map_Data = generator.GenerateDungeon(player); - - Map_Data.EntityPlaced += OnEntityPlaced; - - PlaceTiles(); - PlaceEntities(); - } - - /// <summary> - /// Atualiza o campo de visão do mapa com base em uma coordenada. - /// </summary> - /// <param name="pos">Centro de visão, normalmente é a posição do jogador.</param> - public void UpdateFOV(Vector2I pos) { - fieldOfView.UpdateFOV(Map_Data, pos, fovRadius); - // Esconde ou revela entidades com base no campo de visão. - foreach (Entity entity in Map_Data.Entities) { - entity.Visible = Map_Data.GetTile(entity.GridPosition).IsInView; - } - } - - private void OnEntityPlaced(Entity entity) { - entitiesNode.AddChild(entity); - } -} diff --git a/scripts/map/MapData.cs b/scripts/map/MapData.cs deleted file mode 100644 index f3e193e..0000000 --- a/scripts/map/MapData.cs +++ /dev/null @@ -1,272 +0,0 @@ -using Godot; - -/// <summary> -/// Classe que cuida dos dados e da parte lógica do mapa. -/// O mapa é o cenário onde as ações do jogo ocorrem. -/// Mais especificamente, o mapa é um único andar da masmorra. -/// </summary> -public partial class MapData : RefCounted -{ - public static readonly TileDefinition wallDefinition = GD.Load<TileDefinition>("res://assets/definitions/tiles/wall.tres"); - public static readonly TileDefinition floorDefinition = GD.Load<TileDefinition>("res://assets/definitions/tiles/floor.tres"); - - [Signal] - public delegate void EntityPlacedEventHandler(Entity entity); - - /// <summary> - /// Largura do mapa. - /// </summary> - public int Width { get; private set; } - /// <summary> - /// Altura do mapa. - /// </summary> - public int Height { get; private set; } - - /// <summary> - /// Os tiles que compõem o mapa. - /// </summary> - public Godot.Collections.Array<Tile> Tiles { get; private set; } = []; - - /// <summary> - /// O jogador é especial e por isso o mapa faz questão de rastreá-lo. - /// </summary> - public Player Player { get; set; } - /// <summary> - /// Lista de todos os atores dentro do mapa. - /// </summary> - public Godot.Collections.Array<Entity> Entities { get; private set; } = []; - - public Godot.Collections.Array<ConsumableItem> Items { - get { - Godot.Collections.Array<ConsumableItem> list = []; - foreach (Entity entity in Entities) { - if (entity is ConsumableItem item) { - list.Add(item); - } - } - return list; - } - } - - private AStarGrid2D pathfinder; - /// <summary> - /// Objeto do Godot que utiliza do algoritmo A* para calcular - /// caminhos e rotas. - /// </summary> - public AStarGrid2D Pathfinder { get => pathfinder; } - /// <summary> - /// Peso do ator no pathfinder. - /// A IA irá evitar de passar por espaços com peso alto. - /// </summary> - private static readonly float EntityWeight = 10.0f; - - /// <summary> - /// Inicializa o pathfinder; - /// </summary> - public void SetupPathfinding() { - pathfinder = new AStarGrid2D - { - //A região é o mapa inteiro. - Region = new Rect2I(0, 0, Width, Height) - }; - - // Atualiza o pathfinder para a região definida. - pathfinder.Update(); - - // Define quais pontos do mapa são passáveis ou não. - for (int y = 0; y < Height; y++) { - for (int x = 0; x < Width; x++) { - Vector2I pos = new Vector2I(x, y); - Tile tile = GetTile(pos); - // Pontos sólidos são impossíveis de passar. - pathfinder.SetPointSolid(pos, !tile.IsWalkable); - } - } - - // Registra todos os atores em cena. - foreach (Entity entity in Entities) { - if (entity.BlocksMovement) { - RegisterBlockingEntity(entity); - } - } - - } - - /// <summary> - /// Define um peso na posição de uma entidade para que a IA evite de passar por lá. - /// Ênfase em evitar. Se o único caminho para o destino estiver bloqueado - /// por uma entidade, o jogo tentará andar mesmo assim. - /// </summary> - /// <param name="entity">A entidade em questão.</param> - public void RegisterBlockingEntity(Entity entity) { - pathfinder.SetPointWeightScale(entity.GridPosition, EntityWeight); - } - - /// <summary> - /// Remove o peso na posição de uma entidade. - /// Quando uma entidade move sua posição, devemos tirar o peso de sua posição anterior. - /// </summary> - /// <param name="entity">A entidade em questão.</param> - public void UnregisterBlockingEntity(Entity entity) { - pathfinder.SetPointWeightScale(entity.GridPosition, 0); - } - - public MapData(int width, int height, Player player) { - Width = width; - Height = height; - - Player = player; - // Como o jogador é criado antes do mapa, precisamos - // atualizá-lo com o novo mapa. - player.Map_Data = this; - InsertEntity(player); - - SetupTiles(); - } - - /// <summary> - /// Cria novos Tiles até preencher as dimensões do mapa. - /// É importante que estes tiles sejam paredes, o gerador de mapas - /// não cria paredes por conta própria. - /// </summary> - private void SetupTiles() { - for (int i = 0; i < Height; i++) - { - for (int j = 0; j < Width; j++) - { - Tiles.Add(new Tile(new Vector2I(j, i), wallDefinition)); - } - } - } - - /// <summary> - /// Registra uma entidade no mapa. A existência de uma entidade não é considerada se ela não - /// estiver registrada no mapa. - /// </summary> - /// <param name="entity">A entidade em questão</param> - public void InsertEntity(Entity entity) { - Entities.Add(entity); - } - - /// <summary> - /// Converte uma coordenada em um índice para acessar a lista de tiles. - /// </summary> - /// <param name="pos">Vetor posição</param> - /// <returns>Índice na lista de tiles. -1 se estiver fora do mapa.</returns> - private int GridToIndex(Vector2I pos) { - if (!IsInBounds(pos)) return -1; - - return pos.Y * Width + pos.X; - } - - /// <summary> - /// Se uma coordenada está dentro da área do mapa. - /// </summary> - /// <param name="pos">Vetor posição</param> - /// <returns>Se o vetor está dentro do mapa.</returns> - private bool IsInBounds(Vector2I pos) { - if (pos.X < 0 || pos.Y < 0) { - return false; - } - if (pos.X >= Width || pos.Y >= Height) { - return false; - } - - return true; - } - - /// <summary> - /// Obtém o tile na posição desejada. - /// </summary> - /// <param name="pos">Vetor posição</param> - /// <returns>O tile na posição, nulo se for fora do mapa.</returns> - public Tile GetTile(Vector2I pos) { - int index = GridToIndex(pos); - - if (index < 0) return null; - - return Tiles[index]; - } - - /// <summary> - /// Obtém o tile na posição desejada. - /// </summary> - /// <param name="x">x da coordenada</param> - /// <param name="y">y da coordenada</param> - /// <returns>O tile na posição, nulo se for fora do mapa.</returns> - public Tile GetTile(int x, int y) { - return GetTile(new Vector2I(x, y)); - } - - /// <summary> - /// Obtém a entidade na posição especificada. - /// </summary> - /// <param name="pos">Vetor posição</param> - /// <returns>A entidade na posição especificada, nulo se não houver.</returns> - public Entity GetBlockingEntityAtPosition(Vector2I pos) { - foreach (Entity entity in Entities) { - if (entity.GridPosition == pos && entity.BlocksMovement) { - return entity; - } - } - return null; - } - - /// <summary> - /// Obtém o primeiro item na posição especificada. - /// </summary> - /// <param name="pos">Posição</param> - /// <returns>O primeiro item na posição, nulo se não houver.</returns> - public ConsumableItem GetFirstItemAtPosition(Vector2I pos) { - foreach (ConsumableItem item in Items) { - if (item.GridPosition == pos) { - return item; - } - } - - return null; - } - - /// <summary> - /// Remove uma entidade do mapa sem dar free. - /// </summary> - /// <param name="entity">A entidade para remover</param> - public void RemoveEntity(Entity entity) { - // Eu removo a entidade do nó de entidades. - entity.GetParent().RemoveChild(entity); - // Eu removo a entidade da lista de entidades do mapa. - Entities.Remove(entity); - } - - /// <summary> - /// Obtém todas as entidades na posição especificada. - /// É possível haver mais de uma entidade na mesma posição se uma delas não bloquear movimento. - /// </summary> - /// <param name="pos">Vetor posição</param> - /// <returns>Lista com todas as entidades na posição especificada.</returns> - public Godot.Collections.Array<Entity> GetEntitiesAtPosition(Vector2I pos) { - Godot.Collections.Array<Entity> ZOfZero = []; - Godot.Collections.Array<Entity> ZOfOne = []; - Godot.Collections.Array<Entity> ZOfTwo = []; - - // Pego todos os atores - foreach (Entity entity in Entities) { - if (entity.GridPosition == pos) { - switch (entity.ZIndex) { - case 0: - ZOfZero.Add(entity); - break; - case 1: - ZOfOne.Add(entity); - break; - case 2: - ZOfTwo.Add(entity); - break; - } - } - } - - // Retorno os atores ordenados por ZIndex. - return ZOfZero + ZOfOne + ZOfTwo; - } -} diff --git a/scripts/map/MapDivision.cs b/scripts/map/MapDivision.cs deleted file mode 100644 index 3273775..0000000 --- a/scripts/map/MapDivision.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Godot; - -/// <summary> -/// Classe utilizada pelo gerador de mapas. -/// Uma divisão é uma região retangular de espaço que pode -/// conter dentro de si duas regiões menores *ou* uma sala. -/// Uma divisão é uma árvore binária que possui espaço para salas em suas folhas. -/// </summary> -public partial class MapDivision : RefCounted { - /// <summary> - /// Região retangular da divisão. - /// </summary> - public Vector2I Position { get; set; } - public Vector2I Size { get; set; } - - public Vector2I Center { - get => new(Position.X + Size.X/2, Position.Y + Size.Y/2); - } - - /// <summary> - /// Filhos da árvore - /// </summary> - private MapDivision left; - public MapDivision Left { get => this.left; } - private MapDivision right; - public MapDivision Right { get => this.right; } - - /// <summary> - /// Se a divisão atual for uma folha. - /// As folhas representam salas. - /// </summary> - public bool IsLeaf { - get => left == null && right == null; - } - - public MapDivision(Vector2I position, Vector2I size) { - Position = position; - Size = size; - } - - public MapDivision(Vector2I position, int width, int height) { - Position = position; - Size = new(width, height); - } - - public MapDivision(int x, int y, int width, int height) { - Position = new(x, y); - Size = new(width, height); - } - - /// <summary> - /// É conveniente ter acesso à todas as folhas da árvore. - /// </summary> - /// <returns>Lista com todas as folhas da árvore.</returns> - public Godot.Collections.Array<MapDivision> GetLeaves() { - if (IsLeaf) { - Godot.Collections.Array<MapDivision> list = []; - list.Add(this); - return list; - } - return left.GetLeaves() + right.GetLeaves(); - } - - /// <summary> - /// Algoritmo para gerar as divisões. - /// O mapa começa com uma única divisão que oculpa sua extensão completa. - /// Depois disso, ela se dividirá recursivamente n vezes. - /// As divisões nas folhas representam espaços onde pode gerar uma sala. - /// </summary> - /// <param name="iterations">Número de iterações</param> - /// <param name="rng">Gerador de números</param> - public void Split(int iterations, RandomNumberGenerator rng) { - float SplitRatio = rng.RandfRange(0.35f, 0.65f); - bool horizontalSplit = Size.X <= Size.Y; - - // Eu defini um limite mínimo de 4 de altura e 4 de largura para divisões. - - if (horizontalSplit) { - int leftHeight = (int) (Size.Y * SplitRatio); - if (leftHeight > 4 && Size.Y - leftHeight > 4) { - left = new MapDivision(Position, Size.X, leftHeight); - right = new MapDivision(Position.X, Position.Y + leftHeight, Size.X, Size.Y - leftHeight); - } - } else { - int leftWidth = (int) (Size.Y * SplitRatio); - - if (leftWidth > 4 && Size.Y - leftWidth > 4) { - left = new MapDivision(Position, leftWidth, Size.Y); - right = new MapDivision(Position.X + leftWidth, Position.Y, Size.X - leftWidth, Size.Y); - } - } - - if (iterations > 1) { - left?.Split(iterations - 1, rng); - right?.Split(iterations - 1, rng); - } - } -}
\ No newline at end of file |
