commit 8c451c28e7014d194d31cfb381fbf61fbff62c8c Author: hheik <4469778+hheik@users.noreply.github.com> Date: Mon Sep 8 01:06:22 2025 +0300 Init project. diff --git a/godot/.editorconfig b/godot/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/godot/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/godot/.gitattributes b/godot/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/godot/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/godot/.gitignore b/godot/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/godot/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/godot/icon.svg b/godot/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/godot/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/icon.svg.import b/godot/icon.svg.import new file mode 100644 index 0000000..abdae7c --- /dev/null +++ b/godot/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgacydp2nwaus" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/icons/turn_action.svg b/godot/icons/turn_action.svg new file mode 100644 index 0000000..3802a3a --- /dev/null +++ b/godot/icons/turn_action.svg @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/godot/icons/turn_action.svg.import b/godot/icons/turn_action.svg.import new file mode 100644 index 0000000..ad98fb6 --- /dev/null +++ b/godot/icons/turn_action.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6op0q4kbyhwv" +path="res://.godot/imported/turn_action.svg-ba28e62e6dfc65acd8d0077863718b65.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/turn_action.svg" +dest_files=["res://.godot/imported/turn_action.svg-ba28e62e6dfc65acd8d0077863718b65.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/nodes/action.gd b/godot/nodes/action.gd new file mode 100644 index 0000000..170ddd4 --- /dev/null +++ b/godot/nodes/action.gd @@ -0,0 +1,25 @@ +@icon("res://icons/turn_action.svg") +class_name Action +extends Node +# Base class for actions + +# Optional predicate for if the action is allowed +func predicate() -> bool: + return true + +# Called when the action starts +func action_ready(): + pass + +# Called every frame, like _process +func action_process(_delta: float): + pass + +# Must be emitted when an action is successful +@warning_ignore("unused_signal") +signal done() +# Must be emitted if something went wrong and the action can't be performed +# This could be called for example when an action was checked to be valid, but +# failed during execution +@warning_ignore("unused_signal") +signal abort() diff --git a/godot/nodes/action.gd.uid b/godot/nodes/action.gd.uid new file mode 100644 index 0000000..d9b32b4 --- /dev/null +++ b/godot/nodes/action.gd.uid @@ -0,0 +1 @@ +uid://d4ip560ei2w7n diff --git a/godot/nodes/action_decider.gd b/godot/nodes/action_decider.gd new file mode 100644 index 0000000..a20b103 --- /dev/null +++ b/godot/nodes/action_decider.gd @@ -0,0 +1,82 @@ +@icon("res://icons/turn_action.svg") +class_name ActionDecider +extends Node + +var actor: TurnActor +var current_action: Action + +func get_actor(): + var parent = get_parent() + if parent is TurnActor: + return parent + else: + return null + +func connect_to_actor(): + actor.connect("turn_started", handle_turn_start) + actor.connect("deciding_action", handle_decide_action) + actor.connect("performing_action", handle_perform_action) + actor.connect("turn_ended", handle_turn_end) + +func handle_turn_start(): + #print("turn start!") + pass + +func handle_decide_action(): + #print("deciding action...") + pass + +func handle_perform_action(): + #print("performing action: ", actor.get_current_action()) + pass + +func handle_turn_end(): + #print("turn end!") + pass + +func is_deciding() -> bool: + return actor.is_deciding() + +func try_perform(action_node: Action) -> bool: + if current_action: + push_error("Tried to start an action while another one is performing! Current action: ", current_action.name) + return false + add_child(action_node) + if action_node.predicate(): + inner_perform(action_node) + return true + else: + action_node.queue_free() + return false + +func inner_perform(action_node: Action): + current_action = action_node + actor.perform_action(action_node.name) + current_action.done.connect(_on_action_done, CONNECT_ONE_SHOT) + current_action.abort.connect(_on_action_abort, CONNECT_ONE_SHOT) + current_action.action_ready() + +func _on_action_done(): + current_action.abort.disconnect(_on_action_abort) + inner_action_cleanup() + +func _on_action_abort(): + push_error("Action aborted: ", current_action.get_path()) + current_action.done.disconnect(_on_action_done) + inner_action_cleanup() + +func inner_action_cleanup(): + current_action.queue_free() + current_action = null + actor.end_turn() + +func _ready(): + actor = get_actor() + if not actor is TurnActor: + push_error("Couldn't get TurnActor from TurnAction") + else: + connect_to_actor() + +func _process(delta: float): + if current_action: + current_action.action_process(delta) diff --git a/godot/nodes/action_decider.gd.uid b/godot/nodes/action_decider.gd.uid new file mode 100644 index 0000000..fcfe015 --- /dev/null +++ b/godot/nodes/action_decider.gd.uid @@ -0,0 +1 @@ +uid://okxdlbfuvb1b diff --git a/godot/nodes/actions/move_action.gd b/godot/nodes/actions/move_action.gd new file mode 100644 index 0000000..9c6300a --- /dev/null +++ b/godot/nodes/actions/move_action.gd @@ -0,0 +1,22 @@ +@icon("res://icons/turn_action.svg") +class_name MoveAction +extends Action + +var mover: GridPosition +var dir: Vector2i + +func _init(new_mover: GridPosition, new_dir: Vector2i) -> void: + mover = new_mover + dir = new_dir + +func predicate(): + return mover.can_move(dir) + +func action_ready(): + mover.finished_moving.connect(_on_mover_done, CONNECT_ONE_SHOT) + if not mover.try_move(dir): + mover.finished_moving.disconnect(_on_mover_done) + abort.emit() + +func _on_mover_done(_dir: Vector2i): + done.emit() diff --git a/godot/nodes/actions/move_action.gd.uid b/godot/nodes/actions/move_action.gd.uid new file mode 100644 index 0000000..86b587d --- /dev/null +++ b/godot/nodes/actions/move_action.gd.uid @@ -0,0 +1 @@ +uid://bfsdp4v5q2jhg diff --git a/godot/nodes/actions/skip_action.gd b/godot/nodes/actions/skip_action.gd new file mode 100644 index 0000000..9814a02 --- /dev/null +++ b/godot/nodes/actions/skip_action.gd @@ -0,0 +1,6 @@ +@icon("res://icons/turn_action.svg") +class_name SkipAction +extends Action + +func action_ready(): + done.emit() diff --git a/godot/nodes/actions/skip_action.gd.uid b/godot/nodes/actions/skip_action.gd.uid new file mode 100644 index 0000000..f8663bc --- /dev/null +++ b/godot/nodes/actions/skip_action.gd.uid @@ -0,0 +1 @@ +uid://drnhx3nwimloi diff --git a/godot/prefabs/player/player.tscn b/godot/prefabs/player/player.tscn new file mode 100644 index 0000000..e598c04 --- /dev/null +++ b/godot/prefabs/player/player.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=3 format=3 uid="uid://drs6h7ks4r2ta"] + +[ext_resource type="Texture2D" uid="uid://doscvutq8uqmd" path="res://sprites/sheet.png" id="1_72ieh"] +[ext_resource type="Script" uid="uid://sxo578w2yds2" path="res://prefabs/player/player_input.gd" id="2_rdx4y"] + +[node name="Player" type="TurnActor"] + +[node name="Input" type="Node2D" parent="."] +script = ExtResource("2_rdx4y") +metadata/_custom_type_script = "uid://okxdlbfuvb1b" + +[node name="GridPosition" type="GridPosition" parent="."] + +[node name="Sprite2D" type="Sprite2D" parent="GridPosition"] +texture = ExtResource("1_72ieh") +centered = false +hframes = 8 +vframes = 8 +frame = 1 + +[node name="Camera2D" type="Camera2D" parent="GridPosition"] +position = Vector2(16, 16) +zoom = Vector2(2, 2) + +[node name="Gatherer" type="Node" parent="GridPosition"] diff --git a/godot/prefabs/player/player_input.gd b/godot/prefabs/player/player_input.gd new file mode 100644 index 0000000..93fb811 --- /dev/null +++ b/godot/prefabs/player/player_input.gd @@ -0,0 +1,47 @@ +@icon("res://icons/turn_action.svg") +extends ActionDecider + +@onready var grid_position: GridPosition = $"../GridPosition" + +func poll_movement_dir(): + var movement_dir = Vector2i.ZERO + if Input.is_action_pressed("move_up"): + movement_dir = Vector2i.UP + elif Input.is_action_pressed("move_down"): + movement_dir = Vector2i.DOWN + elif Input.is_action_pressed("move_left"): + movement_dir = Vector2i.LEFT + elif Input.is_action_pressed("move_right"): + movement_dir = Vector2i.RIGHT + return movement_dir + +func _input(event: InputEvent) -> void: + if not is_deciding(): + return + if event is InputEventKey: + if event.is_pressed(): + match event.keycode: + KEY_SPACE: + print("skip turn!") + if try_perform(SkipAction.new()): + return + #KEY_UP: + #if try_perform(MoveAction.new(grid_position, Vector2i.UP)): + #return + #KEY_DOWN: + #if try_perform(MoveAction.new(grid_position, Vector2i.DOWN)): + #return + #KEY_LEFT: + #if try_perform(MoveAction.new(grid_position, Vector2i.LEFT)): + #return + #KEY_RIGHT: + #if try_perform(MoveAction.new(grid_position, Vector2i.RIGHT)): + #return + +func _process(_delta: float) -> void: + if not is_deciding(): + return + var movement_dir = poll_movement_dir() + if movement_dir != Vector2i.ZERO: + if try_perform(MoveAction.new(grid_position, movement_dir)): + return diff --git a/godot/prefabs/player/player_input.gd.uid b/godot/prefabs/player/player_input.gd.uid new file mode 100644 index 0000000..e81df58 --- /dev/null +++ b/godot/prefabs/player/player_input.gd.uid @@ -0,0 +1 @@ +uid://sxo578w2yds2 diff --git a/godot/project.godot b/godot/project.godot new file mode 100644 index 0000000..3a5d453 --- /dev/null +++ b/godot/project.godot @@ -0,0 +1,62 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Velho Peli" +run/main_scene="uid://b3odri2wtvke4" +config/features=PackedStringArray("4.4", "Forward Plus") +config/icon="res://icon.svg" + +[display] + +window/size/viewport_width=1216 +window/size/viewport_height=704 +window/vsync/vsync_mode=0 + +[dotnet] + +project/assembly_name="Velho Peli" + +[input] + +move_up={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_down={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +skip_turn={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) +] +} + +[rendering] + +textures/canvas_textures/default_texture_filter=0 diff --git a/godot/scenes/overworld.tscn b/godot/scenes/overworld.tscn new file mode 100644 index 0000000..75f56c0 --- /dev/null +++ b/godot/scenes/overworld.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=4 format=4 uid="uid://b3odri2wtvke4"] + +[ext_resource type="TileSet" uid="uid://b1ps1ww0rtkop" path="res://tilesets/background_tileset.tres" id="1_m1b5j"] +[ext_resource type="TileSet" uid="uid://bxwohuw2p43k1" path="res://tilesets/foreground_tileset.tres" id="2_u2ss0"] +[ext_resource type="PackedScene" uid="uid://drs6h7ks4r2ta" path="res://prefabs/player/player.tscn" id="3_u2ss0"] + +[node name="Root" type="Level" node_paths=PackedStringArray("background", "foreground")] +background = NodePath("Background") +foreground = NodePath("Foreground") + +[node name="Background" type="TileMapLayer" parent="."] +tile_map_data = PackedByteArray("AAD8//z/AQAAAAQAAAD8//3/AQAAAAQAAAD8//7/AQAAAAQAAAD8////AQAAAAQAAAD9//z/AQAAAAQAAAD9//3/AQAAAAQAAAD9//7/AQAAAAQAAAD9////AQAAAAQAAAD+//z/AQAAAAQAAAD+//3/AQAAAAQAAAD+//7/AQAAAAQAAAD+////AQAAAAQAAAD///z/AQAAAAQAAAD///3/AQAAAAQAAAD///7/AQAAAAQAAAD/////AQAAAAQAAAD8/wAAAQAAAAQAAAD8/wEAAQAAAAQAAAD8/wIAAQAAAAQAAAD8/wMAAQAAAAQAAAD9/wAAAQAAAAQAAAD9/wEAAQAAAAQAAAD9/wIAAQAAAAQAAAD9/wMAAQAAAAQAAAD+/wAAAQAAAAQAAAD+/wEAAQAAAAQAAAD+/wIAAQAAAAQAAAD+/wMAAQAAAAQAAAD//wAAAQAAAAQAAAD//wEAAQAAAAQAAAD//wIAAQAAAAQAAAD//wMAAQAAAAQAAAAAAPz/AQAAAAQAAAAAAP3/AQAAAAQAAAAAAP7/AQAAAAQAAAAAAP//AQAAAAQAAAAAAAAAAQAAAAQAAAAAAAEAAQAAAAQAAAAAAAIAAQAAAAQAAAAAAAMAAQAAAAQAAAABAPz/AQAAAAQAAAABAP3/AQAAAAQAAAABAP7/AQAAAAQAAAABAP//AQAAAAQAAAABAAAAAQAAAAQAAAABAAEAAQAAAAQAAAABAAIAAQAAAAQAAAABAAMAAQAAAAQAAAACAPz/AQAAAAQAAAACAP3/AQAAAAQAAAACAP7/AQAAAAQAAAACAP//AQAAAAQAAAACAAAAAQAAAAQAAAACAAEAAQAAAAQAAAACAAIAAQAAAAQAAAACAAMAAQAAAAQAAAADAPz/AQAAAAQAAAADAP3/AQAAAAQAAAADAP7/AQAAAAQAAAADAP//AQAAAAQAAAADAAAAAQAAAAQAAAADAAEAAQAAAAQAAAADAAIAAQAAAAQAAAADAAMAAQAAAAQAAAD6//v/AQAAAAQAAAD6//z/AQAAAAQAAAD6//3/AQAAAAQAAAD6//7/AQAAAAQAAAD6////AQAAAAQAAAD6/wAAAQAAAAQAAAD6/wEAAQAAAAQAAAD6/wIAAQAAAAQAAAD6/wMAAQAAAAQAAAD6/wQAAQAAAAQAAAD6/wUAAQAAAAQAAAD7//v/AQAAAAQAAAD7//z/AQAAAAQAAAD7//3/AQAAAAQAAAD7//7/AQAAAAQAAAD7////AQAAAAQAAAD7/wAAAQAAAAQAAAD7/wEAAQAAAAQAAAD7/wIAAQAAAAQAAAD7/wMAAQAAAAQAAAD7/wQAAQAAAAQAAAD7/wUAAQAAAAQAAAD8//v/AQAAAAQAAAD8/wQAAQAAAAQAAAD8/wUAAQAAAAQAAAD9//v/AQAAAAQAAAD9/wQAAQAAAAQAAAD9/wUAAQAAAAQAAAD+//v/AQAAAAQAAAD+/wQAAQAAAAQAAAD+/wUAAQAAAAQAAAD///v/AQAAAAQAAAD//wQAAQAAAAQAAAD//wUAAQAAAAQAAAAAAPv/AQAAAAQAAAAAAAQAAQAAAAQAAAAAAAUAAQAAAAQAAAABAPv/AQAAAAQAAAABAAQAAQAAAAQAAAABAAUAAQAAAAQAAAACAPv/AQAAAAQAAAACAAQAAQAAAAQAAAACAAUAAQAAAAQAAAADAPr/AQABAAQAAAADAPv/AQAAAAQAAAADAAQAAQAAAAQAAAADAAUAAQAAAAQAAAAEAPr/AQABAAQAAAAEAPv/AQABAAQAAAAEAPz/AQABAAQAAAAEAP3/AQABAAQAAAAEAP7/AQAAAAQAAAAEAP//AQAAAAQAAAAEAAAAAQAAAAQAAAAEAAEAAQAAAAQAAAAEAAIAAQAAAAQAAAAEAAMAAQAAAAQAAAAEAAQAAQAAAAQAAAAEAAUAAQAAAAQAAAAFAPr/AQABAAQAAAAFAPv/AQABAAQAAAAFAPz/AQABAAQAAAAFAP3/AQABAAQAAAAFAP7/AQAAAAQAAAAFAP//AQAAAAQAAAAFAAAAAQAAAAQAAAAFAAEAAQAAAAQAAAAFAAIAAQAAAAQAAAAFAAMAAQAAAAQAAAAFAAQAAQAAAAQAAAAFAAUAAQAAAAQAAAAGAPj/AQABAAQAAAAGAPn/AQABAAQAAAAGAPr/AQABAAQAAAAGAPv/AQABAAQAAAAGAPz/AQABAAQAAAAHAPj/AQABAAQAAAAHAPn/AQABAAQAAAAHAPr/AQABAAQAAAAHAPv/AQABAAQAAAAHAPz/AQABAAQAAAAIAPj/AQABAAQAAAAIAPn/AQABAAQAAAAIAPr/AQABAAQAAAAIAPv/AQABAAQAAAAIAPz/AQABAAQAAAAJAPj/AQABAAQAAAAJAPn/AQABAAQAAAAJAPr/AQABAAQAAAAJAPv/AQABAAQAAAAJAPz/AQABAAQAAAAKAPj/AQABAAQAAAAKAPn/AQABAAQAAAAKAPr/AQABAAQAAAAKAPv/AQABAAQAAAAKAPz/AQABAAQAAAAEAPn/AQABAAQAAAAGAP3/AQABAAQAAAAHAP3/AQABAAQAAAAIAP3/AQABAAQAAAAIAP7/AQABAAQAAAAHAP7/AQABAAQAAAAEAPj/AQABAAQAAAAEAPf/AQABAAQAAAAFAPf/AQABAAQAAAAFAPj/AQABAAQAAAAFAPn/AQABAAQAAAAHAPf/AQABAAQAAAAGAPf/AQABAAQAAAAIAPf/AQABAAQAAAAJAPf/AQABAAQAAAAKAPf/AQABAAQAAAALAPn/AQABAAQAAAALAPr/AQABAAQAAAALAPv/AQABAAQAAAAKAP3/AQABAAQAAAAJAP3/AQABAAQAAAD3//v/AQAAAAQAAAD3//z/AQAAAAQAAAD3//3/AQAAAAQAAAD3//7/AQAAAAQAAAD3////AQAAAAQAAAD3/wAAAQAAAAQAAAD3/wEAAQAAAAQAAAD3/wIAAQAAAAQAAAD3/wMAAQAAAAQAAAD3/wQAAQAAAAQAAAD3/wUAAQAAAAQAAAD4//v/AQAAAAQAAAD4//z/AQAAAAQAAAD4//3/AQAAAAQAAAD4//7/AQAAAAQAAAD4////AQAAAAQAAAD4/wAAAQAAAAQAAAD4/wEAAQAAAAQAAAD4/wIAAQAAAAQAAAD4/wMAAQAAAAQAAAD4/wQAAQAAAAQAAAD4/wUAAQAAAAQAAAD5//v/AQAAAAQAAAD5//z/AQAAAAQAAAD5//3/AQAAAAQAAAD5//7/AQAAAAQAAAD5////AQAAAAQAAAD5/wAAAQAAAAQAAAD5/wEAAQAAAAQAAAD5/wIAAQAAAAQAAAD5/wMAAQAAAAQAAAD5/wQAAQAAAAQAAAD5/wUAAQAAAAQAAAAGAP//AQAAAAQAAAAGAAAAAQAAAAQAAAAGAAEAAQAAAAQAAAAGAAIAAQAAAAQAAAAGAAMAAQAAAAQAAAAGAAQAAQAAAAQAAAAGAAUAAQAAAAQAAAAHAP//AQAAAAQAAAAHAAAAAQAAAAQAAAAHAAEAAQAAAAQAAAAHAAIAAQAAAAQAAAAHAAMAAQAAAAQAAAAHAAQAAQAAAAQAAAAHAAUAAQAAAAQAAAAIAP//AQAAAAQAAAAIAAAAAQAAAAQAAAAIAAEAAQAAAAQAAAAIAAIAAQAAAAQAAAAIAAMAAQAAAAQAAAAIAAQAAQAAAAQAAAAIAAUAAQAAAAQAAAAJAP//AQAAAAQAAAAJAAAAAQAAAAQAAAAJAAEAAQAAAAQAAAAJAAIAAQAAAAQAAAAJAAMAAQAAAAQAAAAJAAQAAQAAAAQAAAAJAAUAAQAAAAQAAAAJAP7/AQABAAQAAAAGAP7/AQABAAQAAAA=") +tile_set = ExtResource("1_m1b5j") + +[node name="Foreground" type="TileMapLayer" parent="."] +tile_map_data = PackedByteArray("AAD/////AQAAAAMAAAAEAAEAAQAAAAIAAAADAAEAAQAAAAIAAAADAAIAAQAAAAIAAAADAAMAAQAAAAIAAAACAAMAAQAAAAIAAAAFAAMAAQABAAIAAAAGAAMAAQABAAIAAAD8//3/AQABAAMAAAD9//3/AQABAAMAAAD9//7/AQABAAMAAAD9/wAAAQABAAMAAAD9/wEAAQABAAMAAAD8/wEAAQABAAMAAAAEAPv/AQACAAMAAAAGAP3/AQACAAMAAAAHAP7/AQACAAMAAAAIAP7/AQACAAMAAAAJAP7/AQACAAMAAAAGAPn/AQACAAMAAAAGAPr/AQACAAMAAAAGAPv/AQACAAMAAAAHAPv/AQACAAMAAAAIAPv/AQACAAMAAAAIAPz/AQACAAMAAAAJAPz/AQACAAMAAAAKAPz/AQACAAMAAAAKAPv/AQACAAMAAAAJAPv/AQACAAMAAAAIAPr/AQACAAMAAAAHAPr/AQACAAMAAAAHAPn/AQACAAMAAAD3//v/AQABAAMAAAD3//z/AQABAAMAAAD3//3/AQABAAMAAAD3//7/AQABAAMAAAD3////AQABAAMAAAD3/wAAAQABAAMAAAD3/wEAAQABAAMAAAD3/wIAAQABAAMAAAD4//v/AQABAAMAAAD4//z/AQABAAMAAAD4//3/AQABAAMAAAD4//7/AQABAAMAAAD4////AQABAAMAAAD4/wAAAQABAAMAAAD4/wEAAQABAAMAAAD4/wIAAQABAAMAAAD3/wMAAQABAAMAAAD3/wQAAQABAAMAAAD3/wUAAQABAAMAAAD4/wMAAQABAAMAAAD4/wQAAQABAAMAAAD4/wUAAQABAAMAAAD5/wUAAQABAAMAAAD6/wUAAQABAAMAAAD7/wUAAQABAAMAAAD8/wUAAQABAAMAAAD9/wUAAQABAAMAAAD+/wUAAQABAAMAAAD//wUAAQABAAMAAAAAAAUAAQABAAMAAAABAAUAAQABAAMAAAACAAUAAQABAAMAAAADAAUAAQABAAMAAAAEAAUAAQABAAMAAAAFAAUAAQABAAMAAAAGAAUAAQABAAMAAAAHAAUAAQABAAMAAAAIAAUAAQABAAMAAAAJAAUAAQABAAMAAAD5//v/AQABAAMAAAD6//v/AQABAAMAAAD7//v/AQABAAMAAAD8//v/AQABAAMAAAD9//v/AQABAAMAAAD+//v/AQABAAMAAAD///v/AQABAAMAAAAAAPv/AQABAAMAAAABAPv/AQABAAMAAAACAPv/AQABAAMAAAADAPv/AQABAAMAAAAJAP//AQABAAMAAAAJAAAAAQABAAMAAAAJAAEAAQABAAMAAAAJAAIAAQABAAMAAAAJAAMAAQABAAMAAAAJAAQAAQABAAMAAAD7/wEAAQABAAMAAAD7//3/AQABAAMAAAD6//3/AQABAAMAAAD6/wEAAQABAAMAAAD6//7/AQABAAMAAAD6/wAAAQABAAMAAAA=") +tile_set = ExtResource("2_u2ss0") + +[node name="Player" parent="." instance=ExtResource("3_u2ss0")] + +[node name="TurnManager" type="TurnManager" parent="."] diff --git a/godot/sprites/sheet.aseprite b/godot/sprites/sheet.aseprite new file mode 100644 index 0000000..e304de2 Binary files /dev/null and b/godot/sprites/sheet.aseprite differ diff --git a/godot/sprites/sheet.png b/godot/sprites/sheet.png new file mode 100644 index 0000000..c0f4ce9 Binary files /dev/null and b/godot/sprites/sheet.png differ diff --git a/godot/sprites/sheet.png.import b/godot/sprites/sheet.png.import new file mode 100644 index 0000000..e88316f --- /dev/null +++ b/godot/sprites/sheet.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://doscvutq8uqmd" +path="res://.godot/imported/sheet.png-611d8b977306b2c8b91271f64fa10ad9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://sprites/sheet.png" +dest_files=["res://.godot/imported/sheet.png-611d8b977306b2c8b91271f64fa10ad9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/godot/tilesets/background_tileset.tres b/godot/tilesets/background_tileset.tres new file mode 100644 index 0000000..e9b12be --- /dev/null +++ b/godot/tilesets/background_tileset.tres @@ -0,0 +1,13 @@ +[gd_resource type="TileSet" load_steps=3 format=3 uid="uid://b1ps1ww0rtkop"] + +[ext_resource type="Texture2D" uid="uid://doscvutq8uqmd" path="res://sprites/sheet.png" id="1_ge1ul"] + +[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_5boi2"] +texture = ExtResource("1_ge1ul") +texture_region_size = Vector2i(32, 32) +0:4/0 = 0 +1:4/0 = 0 + +[resource] +tile_size = Vector2i(32, 32) +sources/1 = SubResource("TileSetAtlasSource_5boi2") diff --git a/godot/tilesets/entity_tileset.tres b/godot/tilesets/entity_tileset.tres new file mode 100644 index 0000000..327f216 --- /dev/null +++ b/godot/tilesets/entity_tileset.tres @@ -0,0 +1,13 @@ +[gd_resource type="TileSet" load_steps=3 format=3 uid="uid://7lfy0ix4n65n"] + +[ext_resource type="Texture2D" uid="uid://doscvutq8uqmd" path="res://sprites/sheet.png" id="1_1tjj6"] + +[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_yenu7"] +texture = ExtResource("1_1tjj6") +texture_region_size = Vector2i(32, 32) +0:0/0 = 0 +1:0/0 = 0 + +[resource] +tile_size = Vector2i(32, 32) +sources/0 = SubResource("TileSetAtlasSource_yenu7") diff --git a/godot/tilesets/foreground_tileset.tres b/godot/tilesets/foreground_tileset.tres new file mode 100644 index 0000000..b8f7a9e --- /dev/null +++ b/godot/tilesets/foreground_tileset.tres @@ -0,0 +1,24 @@ +[gd_resource type="TileSet" load_steps=3 format=3 uid="uid://bxwohuw2p43k1"] + +[ext_resource type="Texture2D" uid="uid://doscvutq8uqmd" path="res://sprites/sheet.png" id="1_oe62d"] + +[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_rc83b"] +texture = ExtResource("1_oe62d") +texture_region_size = Vector2i(32, 32) +0:3/0 = 0 +0:3/0/custom_data_0 = true +0:2/0 = 0 +0:2/0/custom_data_1 = true +1:2/0 = 0 +1:3/0 = 0 +1:3/0/custom_data_0 = true +2:3/0 = 0 +2:3/0/custom_data_0 = true + +[resource] +tile_size = Vector2i(32, 32) +custom_data_layer_0/name = "has_collision" +custom_data_layer_0/type = 1 +custom_data_layer_1/name = "is_berry" +custom_data_layer_1/type = 1 +sources/1 = SubResource("TileSetAtlasSource_rc83b") diff --git a/godot/velholib.gdextension b/godot/velholib.gdextension new file mode 100644 index 0000000..d4ecd23 --- /dev/null +++ b/godot/velholib.gdextension @@ -0,0 +1,14 @@ +[configuration] +entry_symbol = "gdext_rust_init" +compatibility_minimum = 4.1 +reloadable = true + +[libraries] +linux.debug.x86_64 = "res://../rust/target/debug/libvelho.so" +linux.release.x86_64 = "res://../rust/target/release/libvelho.so" +windows.debug.x86_64 = "res://../rust/target/debug/velho.dll" +windows.release.x86_64 = "res://../rust/target/release/velho.dll" +macos.debug = "res://../rust/target/debug/libvelho.dylib" +macos.release = "res://../rust/target/release/libvelho.dylib" +macos.debug.arm64 = "res://../rust/target/debug/libvelho.dylib" +macos.release.arm64 = "res://../rust/target/release/libvelho.dylib" diff --git a/godot/velholib.gdextension.uid b/godot/velholib.gdextension.uid new file mode 100644 index 0000000..d74195d --- /dev/null +++ b/godot/velholib.gdextension.uid @@ -0,0 +1 @@ +uid://ckdiot1it37u5 diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..895516a --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,203 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "gdextension-api" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec0a03c8f9c91e3d8eb7ca56dea81c7248c03826dd3f545f33cd22ef275d4d1" + +[[package]] +name = "glam" +version = "0.30.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d1aab06663bdce00d6ca5e5ed586ec8d18033a771906c993a1e3755b368d85" + +[[package]] +name = "godot" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab462a365a4b8bcfcdaf93afe767a189a183edfb1d1e697bde9fb26c3f9bfbe9" +dependencies = [ + "godot-core", + "godot-macros", +] + +[[package]] +name = "godot-bindings" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597469df0a890b2c7f102b094942e6b184e7ee573adf6ed35fad0421ee86b8fa" +dependencies = [ + "gdextension-api", +] + +[[package]] +name = "godot-cell" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e48bf49cf5d6ef0b64b2a58be980e4854f57e75b822889d639f96c3c098a5ec" + +[[package]] +name = "godot-codegen" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282e80108e93950e66ed3a40f7dcb6f61978ecb0dd2d19ad7028799d6a5537cb" +dependencies = [ + "godot-bindings", + "heck", + "nanoserde", + "proc-macro2", + "quote", + "regex", +] + +[[package]] +name = "godot-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2593ce360312bdec4c45994402dc624c43c3dd8741c8e6391958626dcf635b40" +dependencies = [ + "glam", + "godot-bindings", + "godot-cell", + "godot-codegen", + "godot-ffi", +] + +[[package]] +name = "godot-ffi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce5ae6a7ae29a753f2f0af3d2a2f3194fa482d9900fc167f35a0738fe7d54d2" +dependencies = [ + "godot-bindings", + "godot-codegen", + "godot-macros", + "libc", +] + +[[package]] +name = "godot-macros" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0f3ebf0fe09733267c4631ae0af2d6b1008b322d032832ddcfb4dfc54607ad" +dependencies = [ + "godot-bindings", + "proc-macro2", + "quote", + "venial", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "nanoserde" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a36fb3a748a4c9736ed7aeb5f2dfc99665247f1ce306abbddb2bf0ba2ac530a4" +dependencies = [ + "nanoserde-derive", +] + +[[package]] +name = "nanoserde-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a846cbc04412cf509efcd8f3694b114fc700a035fb5a37f21517f9fb019f1ebc" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "velho" +version = "0.1.0" +dependencies = [ + "godot", +] + +[[package]] +name = "venial" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a42528baceab6c7784446df2a10f4185078c39bf73dc614f154353f1a6b1229" +dependencies = [ + "proc-macro2", + "quote", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..254145c --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "velho" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +godot = "0.3.5" diff --git a/rust/src/common.rs b/rust/src/common.rs new file mode 100644 index 0000000..be314c1 --- /dev/null +++ b/rust/src/common.rs @@ -0,0 +1,136 @@ +use godot::{classes::*, prelude::*}; + +#[derive(GodotConvert, Var, Export, Clone, Copy, Default, Debug, PartialEq, Eq)] +#[godot(via = GString)] +pub enum TurnActorState { + /// The default value in Godot. + /// Turn manager should call `start_turn` to advance. + #[default] + WaitingForTurn, + /// Currently just automatically and immediately advances to `DecidingAction` state. + /// This is reserved for animations and such in the future. + TurnStarted, + /// An action decision script should call `take_control` to advance from this state. + DecidingAction, + /// An action script should call `end_turn` to advance from this state. + PerformingAction, +} + +/// Turn breakdown: +/// +/// Actor can be in 4 different states: +/// 1. Waiting for the turn to start. `TurnActor`s start in this state. (`turn_ended` emitted) +/// 2. Turn has started, waiting for enabled controls (`turn_started` emitted) +/// 3. Deciding what to do (`deciding_action` emitted) +/// 4. Performing the action (`performing_action` emitted) +/// ``` +#[derive(Debug, GodotClass)] +#[class(init, base=Node2D)] +pub struct TurnActor { + state: TurnActorState, + current_action: Option, + base: Base, +} + +#[godot_api] +impl INode2D for TurnActor { + fn ready(&mut self) {} +} + +#[godot_api] +impl TurnActor { + #[func] + pub fn get_state(&self) -> TurnActorState { + self.state + } + + #[func] + pub fn get_current_action(&self) -> Variant { + match self.current_action.clone() { + Some(action) => action.to_variant(), + None => Variant::nil(), + } + } + + #[func] + pub fn is_deciding(&self) -> bool { + self.state == TurnActorState::DecidingAction + } + + #[func] + pub fn is_my_turn(&self) -> bool { + match self.state { + TurnActorState::TurnStarted + | TurnActorState::DecidingAction + | TurnActorState::PerformingAction => true, + TurnActorState::WaitingForTurn => false, + } + } + + /// Should be called by a turn manager + #[func] + pub fn start_turn(&mut self) { + if self.state != TurnActorState::WaitingForTurn { + godot_error!( + "TurnActor: incorrect state transfer. Called 'start_turn' while the actor is in state {:?} (expected WaitingForTurn)", + self.state, + ); + } + self.state = TurnActorState::TurnStarted; + self.signals().turn_started().emit(); + self.start_deciding(); + } + + /// Currently called automatically after the turn is started + #[func] + pub fn start_deciding(&mut self) { + if self.state != TurnActorState::TurnStarted { + godot_error!( + "TurnActor: incorrect state transfer. Called 'start_deciding' while the actor is in state {:?} (expected TurnStarted)", + self.state, + ); + } + self.state = TurnActorState::DecidingAction; + self.signals().deciding_action().emit(); + } + + /// Should be called by an action script. + #[func] + pub fn perform_action(&mut self, action_name: GString) { + if self.state != TurnActorState::DecidingAction { + godot_error!( + "TurnActor: incorrect state transfer. Called 'take_control(\"{action_name}\")' while the actor is in state {:?} (expected DecidingAction)", + self.state, + ); + } + self.state = TurnActorState::PerformingAction; + self.current_action = Some(action_name); + self.signals().performing_action().emit(); + } + + /// Should be called by an action script after it's done. + #[func] + pub fn end_turn(&mut self) { + if self.state != TurnActorState::PerformingAction { + godot_error!( + "TurnActor: incorrect state transfer. Called 'end_turn' while the actor is in state {:?} (expected PerformingAction)", + self.state, + ); + } + self.state = TurnActorState::WaitingForTurn; + self.current_action = None; + self.signals().turn_ended().emit(); + } + + #[signal] + pub fn turn_started(); + + #[signal] + pub fn deciding_action(); + + #[signal] + pub fn performing_action(); + + #[signal] + pub fn turn_ended(); +} diff --git a/rust/src/level.rs b/rust/src/level.rs new file mode 100644 index 0000000..e642594 --- /dev/null +++ b/rust/src/level.rs @@ -0,0 +1,174 @@ +use godot::{classes::*, prelude::*}; + +#[derive(Debug, GodotClass)] +#[class(init, base=Node)] +pub struct Level { + #[export] + background: Option>, + #[export] + foreground: Option>, + + base: Base, +} + +#[godot_api] +impl INode for Level { + fn ready(&mut self) { + if self.background.is_none() { + self.background = self.base().try_get_node_as::("./Background"); + } + if self.foreground.is_none() { + self.foreground = self.base().try_get_node_as::("./Foreground"); + } + } +} + +#[godot_api] +impl Level { + const HAS_COLLISION: &str = "has_collision"; + + #[func] + pub fn find_from_node(node: Gd) -> Option> { + let mut current = node.clone(); + while let Some(parent) = current.get_parent() { + match parent.try_cast::() { + Ok(level) => return Some(level), + Err(other) => current = other, + } + } + None + } + + #[func] + pub fn tile_has_collision(&self, coords: Vector2i) -> bool { + self.foreground + .as_ref() + .is_some_and(|layer| get_custom_data_bool(layer, coords, Self::HAS_COLLISION)) + || self + .background + .as_ref() + .is_some_and(|layer| get_custom_data_bool(layer, coords, Self::HAS_COLLISION)) + } + + #[func] + pub fn get_fg(&self) -> Gd { + self.foreground.clone().unwrap() + } +} + +fn get_custom_data_bool(layer: &Gd, coords: Vector2i, custom_data: &str) -> bool { + layer.get_cell_tile_data(coords).is_some_and(|tile| { + tile.has_custom_data(custom_data) && tile.get_custom_data(custom_data).booleanize() + }) +} + +#[derive(Debug, GodotClass)] +#[class(init, base=Node2D)] +pub struct GridPosition { + #[export] + #[init(val = Vector2::splat(32.0))] + grid_size: Vector2, + + #[export] + #[init(val = 30.0)] + movement_speed: f32, + + #[export_group(name = "Flags")] + #[export] + #[init(val = false)] + ignore_collisions: bool, + + is_moving: bool, + target_coords: Vector2i, + + base: Base, +} + +#[godot_api] +impl INode2D for GridPosition { + fn ready(&mut self) { + self.target_coords = self.get_coords(); + } + + fn process(&mut self, delta: f64) { + if self.is_moving { + let start = self.base().get_global_position(); + let target_coords = self.target_coords; + let movement_speed = self.movement_speed; + let end = self.get_target_pos(); + if start.distance_squared_to(end) <= Self::TILE_SNAP_DIST_SQR { + self.base_mut().set_global_position(end); + self.is_moving = false; + self.signals().finished_moving().emit(target_coords); + } else { + // self.base_mut() + // .set_global_position(start.move_toward(end, movement_speed * delta)); + self.base_mut() + .set_global_position(start.lerp(end, movement_speed * delta as f32)); + } + } + } +} + +#[godot_api] +impl GridPosition { + const TILE_SNAP_DIST_SQR: f32 = 1.0; + + fn level(&self) -> Option> { + Level::find_from_node(self.to_gd().upcast::()) + } + + #[func] + pub fn get_target_pos(&self) -> Vector2 { + self.target_coords.cast_float() * self.grid_size + } + + #[func] + pub fn get_coords(&self) -> Vector2i { + (self.base().get_global_position() / self.grid_size) + .round() + .cast_int() + } + + #[func] + pub fn set_coords(&mut self, pos: Vector2i) { + let grid_size = self.grid_size; + self.base_mut() + .set_global_position(pos.cast_float() * grid_size); + } + + #[func] + pub fn can_move(&self, dir: Vector2i) -> bool { + if self.is_moving { + return false; + } + if self.ignore_collisions { + return true; + } + match self.level() { + Some(level) => !level.bind().tile_has_collision(self.get_coords() + dir), + None => true, + } + } + + #[func] + pub fn try_move(&mut self, dir: Vector2i) -> bool { + let start_coords = self.target_coords; + let target_coords = start_coords + dir; + + if !self.can_move(dir) { + return false; + } + + self.is_moving = true; + self.target_coords = target_coords; + self.signals().started_moving().emit(start_coords, dir); + true + } + + #[signal] + fn started_moving(from_coords: Vector2i, dir: Vector2i); + + #[signal] + fn finished_moving(coords: Vector2i); +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..494cbd2 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,10 @@ +use godot::prelude::*; + +pub struct VelhoExtension; + +mod common; +mod level; +mod turn_manager; + +#[gdextension] +unsafe impl ExtensionLibrary for VelhoExtension {} diff --git a/rust/src/turn_manager.rs b/rust/src/turn_manager.rs new file mode 100644 index 0000000..5ae4eeb --- /dev/null +++ b/rust/src/turn_manager.rs @@ -0,0 +1,58 @@ +use godot::{ + classes::{object::ConnectFlags, *}, + prelude::*, +}; + +use crate::common::TurnActor; + +#[derive(Debug, GodotClass)] +#[class(init, base=Node)] +pub struct TurnManager { + round_queue: Array>, + current_actor: Option>, + + base: Base, +} + +#[godot_api] +impl INode for TurnManager { + fn process(&mut self, _delta: f64) { + if self.current_actor.is_none() && self.round_queue.is_empty() { + self.start_round(); + } + } +} + +#[godot_api] +impl TurnManager { + fn start_round(&mut self) { + self.round_queue = self.find_sibling_actors(); + godot_print!("New round: {:?}", self.round_queue); + self.start_next_turn(); + } + + fn start_next_turn(&mut self) { + self.current_actor = self.round_queue.pop(); + if let Some(mut actor) = self.current_actor.clone() { + actor + .signals() + .turn_ended() + .builder() + .flags(ConnectFlags::ONE_SHOT) + .connect_other_mut(self, |this| this.start_next_turn()); + actor.bind_mut().start_turn(); + } + } + + fn find_sibling_actors(&self) -> Array> { + let mut actors: Array> = Array::new(); + self.base() + .get_parent() + .unwrap() + .get_children() + .iter_shared() + .filter_map(|node| node.try_cast::().ok()) + .for_each(|actor| actors.push(&actor)); + actors + } +}