From 8c451c28e7014d194d31cfb381fbf61fbff62c8c Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:06:22 +0300 Subject: [PATCH] Init project. --- godot/.editorconfig | 4 + godot/.gitattributes | 2 + godot/.gitignore | 3 + godot/icon.svg | 1 + godot/icon.svg.import | 37 +++++ godot/icons/turn_action.svg | 46 +++++ godot/icons/turn_action.svg.import | 37 +++++ godot/nodes/action.gd | 25 +++ godot/nodes/action.gd.uid | 1 + godot/nodes/action_decider.gd | 82 +++++++++ godot/nodes/action_decider.gd.uid | 1 + godot/nodes/actions/move_action.gd | 22 +++ godot/nodes/actions/move_action.gd.uid | 1 + godot/nodes/actions/skip_action.gd | 6 + godot/nodes/actions/skip_action.gd.uid | 1 + godot/prefabs/player/player.tscn | 25 +++ godot/prefabs/player/player_input.gd | 47 ++++++ godot/prefabs/player/player_input.gd.uid | 1 + godot/project.godot | 62 +++++++ godot/scenes/overworld.tscn | 21 +++ godot/sprites/sheet.aseprite | Bin 0 -> 2172 bytes godot/sprites/sheet.png | Bin 0 -> 3100 bytes godot/sprites/sheet.png.import | 34 ++++ godot/tilesets/background_tileset.tres | 13 ++ godot/tilesets/entity_tileset.tres | 13 ++ godot/tilesets/foreground_tileset.tres | 24 +++ godot/velholib.gdextension | 14 ++ godot/velholib.gdextension.uid | 1 + rust/.gitignore | 1 + rust/Cargo.lock | 203 +++++++++++++++++++++++ rust/Cargo.toml | 10 ++ rust/src/common.rs | 136 +++++++++++++++ rust/src/level.rs | 174 +++++++++++++++++++ rust/src/lib.rs | 10 ++ rust/src/turn_manager.rs | 58 +++++++ 35 files changed, 1116 insertions(+) create mode 100644 godot/.editorconfig create mode 100644 godot/.gitattributes create mode 100644 godot/.gitignore create mode 100644 godot/icon.svg create mode 100644 godot/icon.svg.import create mode 100644 godot/icons/turn_action.svg create mode 100644 godot/icons/turn_action.svg.import create mode 100644 godot/nodes/action.gd create mode 100644 godot/nodes/action.gd.uid create mode 100644 godot/nodes/action_decider.gd create mode 100644 godot/nodes/action_decider.gd.uid create mode 100644 godot/nodes/actions/move_action.gd create mode 100644 godot/nodes/actions/move_action.gd.uid create mode 100644 godot/nodes/actions/skip_action.gd create mode 100644 godot/nodes/actions/skip_action.gd.uid create mode 100644 godot/prefabs/player/player.tscn create mode 100644 godot/prefabs/player/player_input.gd create mode 100644 godot/prefabs/player/player_input.gd.uid create mode 100644 godot/project.godot create mode 100644 godot/scenes/overworld.tscn create mode 100644 godot/sprites/sheet.aseprite create mode 100644 godot/sprites/sheet.png create mode 100644 godot/sprites/sheet.png.import create mode 100644 godot/tilesets/background_tileset.tres create mode 100644 godot/tilesets/entity_tileset.tres create mode 100644 godot/tilesets/foreground_tileset.tres create mode 100644 godot/velholib.gdextension create mode 100644 godot/velholib.gdextension.uid create mode 100644 rust/.gitignore create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/src/common.rs create mode 100644 rust/src/level.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/turn_manager.rs 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 0000000000000000000000000000000000000000..e304de237bf3443cf258d19a1316e1dc7a21d610 GIT binary patch literal 2172 zcmcIkeNO`7erm4hUbkS8$1ofhsTovV(D8xtdv{|e7{(ft{eb(M*pS|~4zkSYL z>&KacLiJV6LZN1%W?`lc3Uy>!|098@Su;VD`}F#Mh}{{5vc5xqL`6>nESy$n%ty%o zg5yp5=$Y;UryYOHnvE+WjuEl^n04B;0CnXt!{ckV@$j<29mzll{>sU%Nx{3eE(?ty zlF6q-5^@im?5CAG;$OUZ-fed{9ObprAG7n4l9OX&b}_%bP!k%e-b+)loA@PLE}RPa z1RCHoJ7vfGIau^JtYv;P@EE60ITXqV9-!L09lXXHlKAYKRgo_fQ zG6Z9S7k7O(x}T$0(A~W*pFN4*;7*Cjy|8EyvjUC(cH$fK-_RRFFtPb}7Hn{r^wmVZ zo%`fKTlA5E)dh;5e69N7iV?$zp{&~cy}t#t9J_m4G*w>ae#1Xt)uxcOvhb_>Wi0cf zJ5790Qd9+n#kWj7K0k9PJX7#uGJL~_WYSvMrtnG8^|5{B5ozb_U#nmBd_})?|K)&L z(uryOtCWhPCnYqm*5ZC=j%2^FYu<`)-s%+HV`2gH$NRYhtFX!~h4md4TwUi|lZokS zR8JKP_Pf;H))S}fmWwz=qD{HUf~$XJcvc*^wN%#4yBsFDe4?dqH*1szcg|wD05Oi1f zd-E;KiWXd(MA+4mSiv|~gPxA})?M+nSOQ<9f z*zPp>q!Wpu+jLaHOXnZcog5J7uwA^7LiuZ`A8jgOs*w?BQ^!2RCXf|V={~w_zfre= zp9Zk-!vvW(Z)J%IsS-Lq4A6HEF-BRH^Sa~P9@>nZU=`qEa+NRVRJafjIn^|l3RMW! z$GP-ze1H%8CG7gr-S0_(sbV(*@)#0HZ9RWVBTQl6Q5sIT2+Xtzpce&mHxrSL&k3r{ zql;Y#A4^rv0{s!DtF|eJ@u|f%pF5nd1>N$J01$dGGm1x?Pt}$|beqcpT^TiFz0A@- z=CO_Uh~_C@-d&Vt|7xfMz6q2;#qv;15LW3Uq^baCxXP#~>&y{xNu}~yqAF9@v_k`f zYrG~5#*V3{HT2=MXL{E-=@aNsaKVt#1H1`uZdlG1RmVk{TJMnJ_3JW3ZCJ>}&|uzh zf^9iPT%gmO<-0}2$QslcJgS>l!hj}3)1?w-zy6lQG&t!f--G)0R90N zXn!=PuG7^1Ve7_TwSH|N|BAVX6i=O65(zU5(5)NLBb*u!a%G;T=zDr)hA*(UPG|Cf z_he|VUQ^`{lZwb);vRi)8#ebyZ8B$fR-tF?^Ht+f^qU<-`8uMp*7IMM-2hDwL~Kf%WUC~y!cREENvOWj1lcbahx-%vzLfBxbF9TRK=ckCl9(Bgj$Wu08$sxH ziGeK3zhudunE1UCxdeI1eh^7LV#Q?3_``Z-*sn;bu@$-ru$vEDPZURU9Pg#ehd7WL zSdLDt-Hb12)or>L(Bh6UZsrgvKdPe4Ntc7b&twc=07`xL3bVesmBjH>#_Y zje5%okny@>yG*hnFwIvZYXlD>XDv~wX)l6hS;(JD0~OdkLxOrycSrhqx59Vr_LGj9 z8?G&l-t}1cy7oIl{CHk-`8}?~+ISIjxmQ}Umc@%vUlJ8CfrcgHK~MEPUli$8BZ9EJ zyj--QI7y%W&Tjd^D-QNJVY-2ZtFtj(;?CcMw{tad#*v;3FGVqDVX^1wN;otsos^g0 zyAyW)TF+(zAecF!T5sgO!pQxWsLxHWz9! zHjR-Iwe#VlDgD-kawKD;0Rp{;=I+A_A}1=EJEBzX;750x1}xGWteTu<+j)HoKQIgJdeA!@H{KKQ3Sp3 z>mBZwYUkyb^U3?i`C`YMzBs=b_L-p+Cvb8WY|UYC|7axr&tck?Qv|=fYRCCfI!Tqi V=lB^?yj16X(6PYo=0Vib{|$pyN}&J% literal 0 HcmV?d00001 diff --git a/godot/sprites/sheet.png b/godot/sprites/sheet.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f4ce9fd4a2b5a546e967094855c01a22dc35e9 GIT binary patch literal 3100 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 ahxVb@d=0?!vSow=JSa, + 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 + } +}