Initial commit

This commit is contained in:
gamer147
2026-04-01 17:16:58 -04:00
commit 0233cb6f46
71 changed files with 2376 additions and 0 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

BIN
assets/editor/unit_node.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kg0de6r1lsj0"
path="res://.godot/imported/unit_node.bmp-78f7ae8f67af3742e2be442acdff72e7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/editor/unit_node.bmp"
dest_files=["res://.godot/imported/unit_node.bmp-78f7ae8f67af3742e2be442acdff72e7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

BIN
assets/editor/unit_node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dv8p38jv35ejg"
path="res://.godot/imported/unit_node.png-9b23e77b8d09018da177358cfa433365.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/editor/unit_node.png"
dest_files=["res://.godot/imported/unit_node.png-9b23e77b8d09018da177358cfa433365.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

BIN
assets/fonts/Minecraft.ttf Normal file

Binary file not shown.

View File

@@ -0,0 +1,36 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://eqhm6gv5p05t"
path="res://.godot/imported/Minecraft.ttf-e1845dfc080db589cd29d952a380f38e.fontdata"
[deps]
source_file="res://assets/fonts/Minecraft.ttf"
dest_files=["res://.godot/imported/Minecraft.ttf-e1845dfc080db589cd29d952a380f38e.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=false
hinting=1
subpixel_positioning=4
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dsikulned64qt"
path="res://.godot/imported/combat_bgm_01.OGG-6c9a537751cc7bec8580130b48af399c.oggvorbisstr"
[deps]
source_file="res://assets/music/combat_bgm_01.OGG"
dest_files=["res://.godot/imported/combat_bgm_01.OGG-6c9a537751cc7bec8580130b48af399c.oggvorbisstr"]
[params]
loop=true
loop_offset=0.0
bpm=0.0
beat_count=0
bar_beats=4

BIN
assets/music/menu_theme.OGG Normal file

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://b7dgmblbcm0cj"
path="res://.godot/imported/menu_theme.OGG-70a417a11ba25010cea8e5ee015ba9aa.oggvorbisstr"
[deps]
source_file="res://assets/music/menu_theme.OGG"
dest_files=["res://.godot/imported/menu_theme.OGG-70a417a11ba25010cea8e5ee015ba9aa.oggvorbisstr"]
[params]
loop=true
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c7e4jw4xcti0q"
path="res://.godot/imported/castle_spritesheet.png-6ff90c6355eaddab50addab2e87b1f2c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/castle_spritesheet.png"
dest_files=["res://.godot/imported/castle_spritesheet.png-6ff90c6355eaddab50addab2e87b1f2c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c2se5wyly6gr6"
path="res://.godot/imported/character.bmp-cf65b7adb0d5844b4deb09628dc23b0b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/character.bmp"
dest_files=["res://.godot/imported/character.bmp-cf65b7adb0d5844b4deb09628dc23b0b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

BIN
assets/sprites/flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cw5su6lignryo"
path="res://.godot/imported/flag.png-b8d0210a4bba4a3dc7cd10170df0a728.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/flag.png"
dest_files=["res://.godot/imported/flag.png-b8d0210a4bba4a3dc7cd10170df0a728.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b8td6sv5re6r8"
path="res://.godot/imported/grey_castle_spritesheet_mask.bmp-194d929eb1771eae4ca4719fb40521c6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/grey_castle_spritesheet_mask.bmp"
dest_files=["res://.godot/imported/grey_castle_spritesheet_mask.bmp-194d929eb1771eae4ca4719fb40521c6.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://sjsl8q7tkx8"
path="res://.godot/imported/grid_highlight.png-bbd56dceab9bd3e965927c9144c4c052.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/grid_highlight.png"
dest_files=["res://.godot/imported/grid_highlight.png-bbd56dceab9bd3e965927c9144c4c052.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

BIN
assets/sprites/map1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://udsusbp3o76m"
path="res://.godot/imported/map1.bmp-9e2f42fc155126c194d55961a68898fc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/map1.bmp"
dest_files=["res://.godot/imported/map1.bmp-9e2f42fc155126c194d55961a68898fc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

BIN
assets/sprites/selector.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://h7nfrjxagqmc"
path="res://.godot/imported/selector.png-d0018707617902c3e886c6fbf4977608.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/selector.png"
dest_files=["res://.godot/imported/selector.png-d0018707617902c3e886c6fbf4977608.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

77
code_review_findings.md Normal file
View File

@@ -0,0 +1,77 @@
# Code Review Findings
## Fixed
### 1. Camera and PlayerController input conflict
**Files:** `scripts/camera_controller.gd`, `nodes/player_controller.gd`
Both scripts used `_unhandled_input` with left mouse button. The camera used left click for dragging and never called `set_input_as_handled()`, so every left click also triggered unit selection/movement. Fixed with a drag threshold and click-on-release pattern.
---
## Outstanding
### 2. Unit lookup is fragile
**File:** `nodes/player_controller.gd:75`
`_get_unit_at()` iterates `get_parent().get_children()` to find units. This couples the controller to a specific scene tree layout and will degrade as the scene grows.
**Fix:** Use a group (`"units"`) or a dedicated unit registry so lookup doesn't depend on parent structure.
### 3. No pathfinding — movement can skip walls diagonally
**File:** `nodes/player_controller.gd:45-60`
Step-by-step movement picks one axis per frame. If the goal is diagonally past an L-shaped wall, the unit can path around it depending on approach angle since only the next immediate tile is checked.
**Fix:** Implement BFS/A* on the grid, or at minimum validate the full path before starting movement.
### ~~4. CombatMap extends CanvasItem instead of Node2D~~ (fixed)
**File:** `nodes/combat_map.gd:2`
Changed to extend `Node2D`.
### ~~5. Inconsistent naming convention~~ (fixed)
**File:** `nodes/combat_map.gd:16`
Renamed `tileCoords` to `tile_coords`.
### ~~6. unit_template is a string path, not a PackedScene~~ (fixed)
**File:** `scripts/test_map_generator.gd:7`
Changed to `@export var unit_template: PackedScene` and removed the `load()` call. **Note:** The scene must be assigned in the Godot inspector on the TestMapGenerator node.
### ~~7. No current_hp on units~~ (fixed)
**File:** `nodes/unit.gd`
Added `current_hp` variable, initialized to `max_hp` in `_ready()`.
### ~~8. Debug print left in production code~~ (fixed)
**File:** `scripts/test_map_generator.gd`
Removed the debug `print()` call.
### 9. tile_highlight.gd runs every frame unconditionally
**File:** `scripts/tile_highlight.gd:12-17`
`_process` updates position and alpha every frame even when the highlight is hidden. Could skip processing when invisible via `set_process(false)` on hide.
### 10. GridOverlay is wired but unused
**Files:** `scripts/grid_overlay.gd`, `nodes/combat_map.gd:6`
`highlight_map` reference exists but is never called from any game logic. Dead code at the moment.
---
## Priority
| Priority | Issue |
|----------|-------|
| ~~High~~ | ~~#1 Input conflict~~ (fixed) |
| High | #3 Wall-skipping movement |
| Medium | #2 Fragile unit lookup |
| ~~Medium~~ | ~~#6 String path instead of PackedScene~~ (fixed) |
| ~~Low~~ | ~~#4 CanvasItem base class~~ (fixed) |
| ~~Low~~ | ~~#5 Inconsistent naming~~ (fixed) |
| ~~Low~~ | ~~#7 No current_hp~~ (fixed) |
| ~~Low~~ | ~~#8 Debug print~~ (fixed) |
| Low | #9, #10 |

View File

@@ -0,0 +1,205 @@
# Combat UI Unit Panel Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace the exit-button CombatUI with a bottom-left panel that shows the selected unit's name and HP.
**Architecture:** CombatUI listens to the `unit_selected_changed` signal from units in the `"units"` group. On selection, it populates a name label and HP progress bar and shows the panel. On deselection, it hides the panel.
**Tech Stack:** Godot 4.6, GDScript, `.tscn` scene files
---
## File Structure
| File | Action | Responsibility |
|------|--------|----------------|
| `prefabs/unit.tscn` | Modify | Add Unit node to `"units"` group |
| `scripts/combat_ui.gd` | Create | Script that connects to unit signals, shows/hides panel, populates data |
| `prefabs/combat_ui.tscn` | Modify | Remove exit button, add UnitPanel with NameLabel and HPBar, attach new script |
---
### Task 1: Add Unit to the "units" group
**Files:**
- Modify: `prefabs/unit.tscn`
- [ ] **Step 1: Add the group to the Unit scene**
In `prefabs/unit.tscn`, add a `groups` property to the root `Unit` node so every instantiated unit is automatically in the `"units"` group. Add this line to the root `[node name="Unit" ...]` block:
```
groups=["units"]
```
The full node line becomes:
```
[node name="Unit" type="Node2D" unique_id=1893234933 groups=["units"]]
```
- [ ] **Step 2: Verify in editor**
Open `scenes/combat_test.tscn` in Godot, run the scene (F5), and use the Remote scene tree to confirm the spawned unit node appears under the `"units"` group.
- [ ] **Step 3: Commit**
```bash
git add prefabs/unit.tscn
git commit -m "feat: add Unit to 'units' group for discovery"
```
---
### Task 2: Create combat_ui.gd script
**Files:**
- Create: `scripts/combat_ui.gd`
- [ ] **Step 1: Write the script**
Create `scripts/combat_ui.gd` with the following content:
```gdscript
class_name CombatUI extends CanvasLayer
@onready var unit_panel: PanelContainer = %UnitPanel
@onready var name_label: Label = %NameLabel
@onready var hp_bar: ProgressBar = %HPBar
func _ready() -> void:
unit_panel.visible = false
for unit: Unit in get_tree().get_nodes_in_group("units"):
unit.unit_selected_changed.connect(_on_unit_selected_changed)
func _on_unit_selected_changed(unit: Unit, selected: bool) -> void:
if selected:
name_label.text = unit.current_info.name
hp_bar.max_value = unit.current_stats.max_hp
hp_bar.value = unit.current_stats.current_hp
unit_panel.visible = true
else:
unit_panel.visible = false
```
- [ ] **Step 2: Commit**
```bash
git add scripts/combat_ui.gd
git commit -m "feat: add CombatUI script for unit info panel"
```
---
### Task 3: Rebuild combat_ui.tscn
**Files:**
- Modify: `prefabs/combat_ui.tscn`
- [ ] **Step 1: Replace the scene file contents**
Replace the entire contents of `prefabs/combat_ui.tscn` with:
```
[gd_scene format=3 uid="uid://cy7r0udfcsqbn"]
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_2ro41"]
[ext_resource type="Script" path="res://scripts/combat_ui.gd" id="2_ui_script"]
[node name="CombatUI" type="CanvasLayer" unique_id=1093388037]
script = ExtResource("2_ui_script")
[node name="UnitPanel" type="PanelContainer" parent="." unique_id=2000000001]
unique_name_in_owner = true
visible = false
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = -78.0
offset_right = 208.0
offset_bottom = -8.0
grow_vertical = 0
theme = ExtResource("1_2ro41")
[node name="MarginContainer" type="MarginContainer" parent="UnitPanel"]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="UnitPanel/MarginContainer"]
layout_mode = 2
[node name="NameLabel" type="Label" parent="UnitPanel/MarginContainer/VBoxContainer" unique_id=2000000002]
unique_name_in_owner = true
layout_mode = 2
text = "Unit"
[node name="HPBar" type="ProgressBar" parent="UnitPanel/MarginContainer/VBoxContainer" unique_id=2000000003]
unique_name_in_owner = true
layout_mode = 2
max_value = 100.0
value = 100.0
show_percentage = false
```
Key changes from the old file:
- Removed the exit button, its inline GDScript, the VBoxContainer, and the `[connection]` block.
- Added the `combat_ui.gd` script as an ext_resource and attached it to the root node.
- Added `UnitPanel` (PanelContainer) anchored to bottom-left with 8px margin from edges, 200px wide, ~70px tall.
- `NameLabel` and `HPBar` are marked with `unique_name_in_owner = true` so the script can reference them via `%NameLabel` and `%HPBar`.
- `UnitPanel` is also unique-named for `%UnitPanel` access.
- `show_percentage = false` on HPBar since we just want the bar visual.
- [ ] **Step 2: Open in Godot and verify scene tree**
Open `prefabs/combat_ui.tscn` in the Godot editor. Confirm:
1. No errors in the Scene dock.
2. The node tree shows: `CombatUI > UnitPanel > MarginContainer > VBoxContainer > NameLabel + HPBar`.
3. The UnitPanel is positioned at the bottom-left of the viewport.
- [ ] **Step 3: Commit**
```bash
git add prefabs/combat_ui.tscn
git commit -m "feat: replace exit button with unit info panel in CombatUI"
```
---
### Task 4: Integration test — verify end-to-end behavior
**Files:**
- None (manual verification)
- [ ] **Step 1: Run the combat test scene**
Run `scenes/combat_test.tscn` (F5 or F6 from the scene). The test map generator spawns a unit named "Putit" with 50 HP.
- [ ] **Step 2: Verify panel is hidden on start**
Confirm the bottom-left corner is empty — no panel visible.
- [ ] **Step 3: Click the unit to select it**
Click the "Putit" unit on the map. Confirm:
1. The panel appears in the bottom-left corner.
2. The name label shows "Putit".
3. The HP bar is full (50/50).
- [ ] **Step 4: Click an empty tile to deselect**
Click an empty floor tile (this moves the unit, but the unit stays selected). Currently, clicking a different unit would deselect the first — since there's only one test unit, verify that selecting the same unit again keeps the panel visible and data correct.
- [ ] **Step 5: Verify exit button is gone**
Confirm there is no "Exit" button anywhere in the UI.
- [ ] **Step 6: Commit (if any tweaks were needed)**
```bash
git add -A
git commit -m "fix: adjustments from integration testing"
```

View File

@@ -0,0 +1,68 @@
# Combat UI: Unit Info Panel
## Overview
Replace the current exit-button-only CombatUI with a unit info panel that displays the selected unit's name and HP. The panel appears in the bottom-left corner when a unit is selected and hides when deselected.
## Scene Structure
```
CombatUI (CanvasLayer) — script: scripts/combat_ui.gd
└─ UnitPanel (PanelContainer) — anchored bottom-left, hidden by default
└─ MarginContainer
└─ VBoxContainer
├─ NameLabel (Label) — displays unit name
└─ HPBar (ProgressBar) — displays current_hp / max_hp
```
- The existing exit button and its VBoxContainer are removed entirely.
- The CombatUI CanvasLayer is preserved; its theme reference to `main_ui_theme.tres` stays.
## Layout
- UnitPanel is anchored to the bottom-left of the viewport.
- Margin of 8px from the left and bottom edges.
- Panel width: ~200px (enough for name and HP bar).
- Height: determined by contents.
## Behavior
### Unit Discovery
CombatUI finds units by looking for nodes in the `"units"` group. Units must be added to this group — this is done in `unit.tscn` (scene-level group assignment) or in `unit.gd`'s `_ready()`.
### Signal Connection
On `_ready()`, CombatUI:
1. Calls `get_tree().get_nodes_in_group("units")` to find all current units.
2. Connects each unit's `unit_selected_changed(unit: Unit, selected: bool)` signal to a handler.
### Selection Handling
When `unit_selected_changed` is received:
- If `selected == true`: populate NameLabel with `unit.current_info.name`, set HPBar's `max_value` to `unit.current_stats.max_hp` and `value` to `unit.current_stats.current_hp`, show UnitPanel.
- If `selected == false`: hide UnitPanel.
### Edge Cases
- If no units exist in the group at ready time, the panel simply stays hidden.
- If a unit is removed from the scene (e.g., defeated), the signal disconnects naturally with the node. The panel hides if that unit was selected.
## Files Changed
| File | Change |
|------|--------|
| `prefabs/combat_ui.tscn` | Remove exit button/VBoxContainer. Add UnitPanel with NameLabel and HPBar. Attach new script. |
| `scripts/combat_ui.gd` | New script: connects to unit signals, shows/hides panel, populates data. |
| `prefabs/unit.tscn` | Add the node to the `"units"` group. |
## Styling
Uses the existing `main_ui_theme.tres` applied at the CombatUI or UnitPanel level. No new theme resources needed.
## Out of Scope
- Full stat display (ATK, DEF, etc.) — future expansion.
- Animations (fade/slide) — instant show/hide only.
- Pause menu / exit button — separate future feature.
- HP bar color theming — uses default ProgressBar style from theme.

1
icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

43
icon.svg.import Normal file
View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cpthotfnx5l30"
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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

39
nodes/combat_map.gd Normal file
View File

@@ -0,0 +1,39 @@
class_name CombatMap
extends Node2D
signal tile_hovered(coords: Vector2i)
@export var tile_set: DLTileset
@onready var tile_map: TileMapLayer = %TerrainLayer
@onready var highlight_map: GridOverlay = %OverlayLayer
const TILE_SIZE := 48.0
const SOURCE_ID: int = 0
func snap_to_grid(pos: Vector2) -> Vector2:
return Vector2(floorf(pos.x / TILE_SIZE), floorf(pos.y / TILE_SIZE)) * TILE_SIZE
func world_to_coords(pos: Vector2) -> Vector2i:
return Vector2i(snap_to_grid(pos) / TILE_SIZE)
func coords_to_world(coords: Vector2i) -> Vector2:
return Vector2(coords) * TILE_SIZE
func draw_wall(coords: Vector2i) -> void:
draw_custom(coords, tile_set.wall_tile_coords)
func draw_floor(coords: Vector2i) -> void:
draw_custom(coords, tile_set.floor_tile_coords)
func draw_custom(coords: Vector2i, tile_coords: Vector2i) -> void:
tile_map.set_cell(coords, SOURCE_ID, tile_coords)
func is_wall(coords: Vector2i) -> bool:
return tile_map.get_cell_atlas_coords(coords) == tile_set.wall_tile_coords
func target_tile(coords: Vector2i) -> void:
highlight_map.target_tile(coords)
tile_hovered.emit(coords)

1
nodes/combat_map.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bks7uplgjjdg0

View File

@@ -0,0 +1,80 @@
class_name PlayerController extends Node
const SPEED = 192.0
@export var dl_map: CombatMap
var _selected_unit: Unit = null
var _target_pos: Vector2
var _goal_pos: Vector2
var _moving := false
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and not event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
var world_pos: Vector2 = get_viewport().get_canvas_transform().affine_inverse() * event.position
var clicked_unit := _get_unit_at(world_pos)
if clicked_unit:
_select_unit(clicked_unit)
get_viewport().set_input_as_handled()
elif _selected_unit:
var snapped_pos := dl_map.snap_to_grid(world_pos)
var grid_coords := dl_map.world_to_coords(world_pos)
if dl_map.is_wall(grid_coords):
return
_goal_pos = snapped_pos
get_viewport().set_input_as_handled()
func _physics_process(delta: float) -> void:
if not _selected_unit:
return
if _moving:
var remaining := _target_pos - _selected_unit.position
var step := SPEED * delta
if remaining.length() <= step:
_selected_unit.position = _target_pos
_moving = false
else:
_selected_unit.position += remaining.normalized() * step
return
if _selected_unit.position != _goal_pos:
var diff := _goal_pos - _selected_unit.position
var dir: Vector2
if absf(diff.x) >= absf(diff.y):
dir = Vector2(signf(diff.x), 0)
else:
dir = Vector2(0, signf(diff.y))
var next_pos := _selected_unit.position + dir * dl_map.TILE_SIZE
var grid_coords := dl_map.world_to_coords(next_pos)
if dl_map.is_wall(grid_coords):
_goal_pos = _selected_unit.position
return
_target_pos = next_pos
_moving = true
func _select_unit(unit: Unit) -> void:
if _selected_unit:
_selected_unit.set_selected(false)
_selected_unit = unit
_selected_unit.set_selected(true)
_goal_pos = _selected_unit.position
_target_pos = _selected_unit.position
_moving = false
func _get_unit_at(world_pos: Vector2) -> Unit:
var snapped := dl_map.snap_to_grid(world_pos)
for child in get_parent().get_children():
if child is Unit:
var unit_snapped := dl_map.snap_to_grid(child.global_position)
if unit_snapped == snapped:
return child
return null

View File

@@ -0,0 +1 @@
uid://dfojm3n0em4ef

23
nodes/unit.gd Normal file
View File

@@ -0,0 +1,23 @@
class_name Unit extends Node2D
#region Templates
@export var stat_template: UnitStats
@export var info_template: UnitInfo
@export var allegiance_template: UnitAllegiance
#endregion
var current_stats: UnitStats
var current_info: UnitInfo
var current_allegiance: UnitAllegiance
signal unit_selected_changed(unit: Unit, selected: bool)
signal unit_allegiance_changed(unit: Unit, allegiance: UnitAllegiance)
func _ready() -> void:
current_stats = stat_template.duplicate(true)
current_info = info_template.duplicate(true)
current_allegiance = allegiance_template.duplicate(true)
unit_allegiance_changed.emit(self, current_allegiance)
func set_selected(selected: bool) -> void:
unit_selected_changed.emit(self, selected)

1
nodes/unit.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://c016mxgatcpse

54
prefabs/combat_map.tscn Normal file
View File

@@ -0,0 +1,54 @@
[gd_scene format=3 uid="uid://dkhyh5ce4iuk3"]
[ext_resource type="Script" uid="uid://bks7uplgjjdg0" path="res://nodes/combat_map.gd" id="1_jyv1f"]
[ext_resource type="Script" uid="uid://c6701vy8h5rfx" path="res://resources/resource_definitions/dl_tileset.gd" id="2_8rn0j"]
[ext_resource type="TileSet" uid="uid://dm5wl6d4xkstu" path="res://resources/combat_tileset.tres" id="3_8rn0j"]
[ext_resource type="Texture2D" uid="uid://sjsl8q7tkx8" path="res://assets/sprites/grid_highlight.png" id="3_vcj5e"]
[ext_resource type="Script" uid="uid://cxl38x2m6sj3w" path="res://scripts/grid_overlay.gd" id="4_jelju"]
[ext_resource type="Texture2D" uid="uid://h7nfrjxagqmc" path="res://assets/sprites/selector.png" id="5_muxvo"]
[ext_resource type="Script" uid="uid://b31eyqov8w7bm" path="res://scripts/tile_highlight.gd" id="7_tileh"]
[sub_resource type="Resource" id="Resource_vcj5e"]
script = ExtResource("2_8rn0j")
wall_tile_coords = Vector2i(2, 15)
metadata/_custom_type_script = "uid://c6701vy8h5rfx"
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_jelju"]
texture = ExtResource("3_vcj5e")
texture_region_size = Vector2i(48, 48)
0:0/animation_columns = 6
0:0/animation_frame_0/duration = 1.0
0:0/animation_frame_1/duration = 1.0
0:0/animation_frame_2/duration = 1.0
0:0/animation_frame_3/duration = 1.0
0:0/animation_frame_4/duration = 1.0
0:0/animation_frame_5/duration = 1.0
0:0/0 = 0
[sub_resource type="TileSet" id="TileSet_muxvo"]
tile_size = Vector2i(48, 48)
sources/0 = SubResource("TileSetAtlasSource_jelju")
[node name="CombatMap" type="Node2D" unique_id=546780706]
script = ExtResource("1_jyv1f")
tile_set = SubResource("Resource_vcj5e")
[node name="OverlayLayer" type="TileMapLayer" parent="." unique_id=1335660296]
unique_name_in_owner = true
tile_set = SubResource("TileSet_muxvo")
script = ExtResource("4_jelju")
[node name="TargetingIndicator" type="Sprite2D" parent="OverlayLayer" unique_id=1836328987]
visible = false
z_index = 3
texture = ExtResource("5_muxvo")
offset = Vector2(24, 24)
[node name="TerrainLayer" type="TileMapLayer" parent="." unique_id=1201875024]
unique_name_in_owner = true
tile_set = ExtResource("3_8rn0j")
[node name="TileHighlight" type="ColorRect" parent="." unique_id=211433569]
script = ExtResource("7_tileh")
[connection signal="tile_hovered" from="TileHighlight" to="." method="target_tile"]

42
prefabs/combat_ui.tscn Normal file
View File

@@ -0,0 +1,42 @@
[gd_scene format=3 uid="uid://cy7r0udfcsqbn"]
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_2ro41"]
[ext_resource type="Script" path="res://scripts/combat_ui.gd" id="2_ui_script"]
[node name="CombatUI" type="CanvasLayer" unique_id=1093388037]
script = ExtResource("2_ui_script")
[node name="UnitPanel" type="PanelContainer" parent="." unique_id=2000000001]
unique_name_in_owner = true
visible = false
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = -78.0
offset_right = 208.0
offset_bottom = -8.0
grow_vertical = 0
theme = ExtResource("1_2ro41")
[node name="MarginContainer" type="MarginContainer" parent="UnitPanel"]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="UnitPanel/MarginContainer"]
layout_mode = 2
[node name="NameLabel" type="Label" parent="UnitPanel/MarginContainer/VBoxContainer" unique_id=2000000002]
unique_name_in_owner = true
layout_mode = 2
text = "Unit"
[node name="HPBar" type="ProgressBar" parent="UnitPanel/MarginContainer/VBoxContainer" unique_id=2000000003]
unique_name_in_owner = true
layout_mode = 2
max_value = 100.0
value = 100.0
show_percentage = false

57
prefabs/unit.tscn Normal file
View File

@@ -0,0 +1,57 @@
[gd_scene format=3 uid="uid://b6a7nlnf58mc4"]
[ext_resource type="Script" uid="uid://c016mxgatcpse" path="res://nodes/unit.gd" id="1_cq4v0"]
[ext_resource type="Texture2D" uid="uid://cw5su6lignryo" path="res://assets/sprites/flag.png" id="2_fhs1y"]
[ext_resource type="Texture2D" uid="uid://c2se5wyly6gr6" path="res://assets/sprites/character.bmp" id="2_on614"]
[ext_resource type="Shader" uid="uid://bd8ki8xwym5nc" path="res://shaders/chroma_key.gdshader" id="3_fhs1y"]
[sub_resource type="GDScript" id="GDScript_fhs1y"]
resource_name = "AllegianceIndicatorManager"
script/source = "extends Sprite2D
func _on_unit_unit_allegiance_changed(unit: Unit, allegiance: UnitAllegiance) -> void:
self_modulate = allegiance.color
"
[sub_resource type="ShaderMaterial" id="ShaderMaterial_4j20j"]
shader = ExtResource("3_fhs1y")
shader_parameter/threshold = 0.01
[sub_resource type="AtlasTexture" id="AtlasTexture_on614"]
atlas = ExtResource("2_on614")
region = Rect2(144, 240, 48, 48)
[sub_resource type="GDScript" id="GDScript_on614"]
resource_name = "UnitSelectorHandler"
script/source = "extends ColorRect
func _unit_selected_changed(_unit: Unit, selected: bool) -> void:
visible = selected
"
[node name="Unit" type="Node2D" unique_id=1893234933 groups=["units"]]
script = ExtResource("1_cq4v0")
metadata/_custom_type_script = "uid://c016mxgatcpse"
[node name="AllegianceIndicator" type="Sprite2D" parent="." unique_id=1567439632]
z_index = 2
texture = ExtResource("2_fhs1y")
offset = Vector2(24, 24)
script = SubResource("GDScript_fhs1y")
[node name="UnitSprite" type="Sprite2D" parent="." unique_id=350615297]
z_index = 1
material = SubResource("ShaderMaterial_4j20j")
texture = SubResource("AtlasTexture_on614")
offset = Vector2(24, 24)
[node name="ColorRect" type="ColorRect" parent="." unique_id=1313394023]
visible = false
offset_right = 48.0
offset_bottom = 48.0
color = Color(1, 1, 0.3019608, 0.36078432)
script = SubResource("GDScript_on614")
[connection signal="unit_allegiance_changed" from="." to="AllegianceIndicator" method="_on_unit_unit_allegiance_changed"]
[connection signal="unit_selected_changed" from="." to="ColorRect" method="_unit_selected_changed"]

26
project.godot Normal file
View File

@@ -0,0 +1,26 @@
; 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="Dungeon Lords"
run/main_scene="uid://dlbuo46n6q238"
config/features=PackedStringArray("4.6", "Mobile")
config/icon="res://icon.svg"
[physics]
3d/physics_engine="Jolt Physics"
[rendering]
textures/canvas_textures/default_texture_filter=0
rendering_device/driver.windows="d3d12"
renderer/rendering_method="mobile"

View File

@@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="UnitAllegiance" format=3 uid="uid://cuc7kkknpsr1g"]
[ext_resource type="Script" uid="uid://bhglsexm8dtpj" path="res://resources/resource_definitions/unit_allegiance.gd" id="1_40cg2"]
[resource]
script = ExtResource("1_40cg2")
type = 1
color = Color(0, 0, 1, 1)
metadata/_custom_type_script = "uid://bhglsexm8dtpj"

View File

@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="UnitAllegiance" format=3 uid="uid://dufi2h00j5vrq"]
[ext_resource type="Script" uid="uid://bhglsexm8dtpj" path="res://resources/resource_definitions/unit_allegiance.gd" id="1_4mkdx"]
[resource]
script = ExtResource("1_4mkdx")
color = Color(0.8288101, 2.7193873e-06, 5.7756904e-07, 1)
metadata/_custom_type_script = "uid://bhglsexm8dtpj"

View File

@@ -0,0 +1,836 @@
[gd_resource type="TileSet" format=3 uid="uid://dm5wl6d4xkstu"]
[ext_resource type="Texture2D" uid="uid://udsusbp3o76m" path="res://assets/sprites/map1.bmp" id="1_v1d02"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_5lppa"]
texture = ExtResource("1_v1d02")
texture_region_size = Vector2i(48, 48)
0:0/0 = 0
1:0/0 = 0
2:0/0 = 0
3:0/0 = 0
4:0/0 = 0
5:0/0 = 0
6:0/0 = 0
7:0/0 = 0
8:0/0 = 0
9:0/0 = 0
10:0/0 = 0
11:0/0 = 0
12:0/0 = 0
13:0/0 = 0
14:0/0 = 0
15:0/0 = 0
16:0/0 = 0
17:0/0 = 0
18:0/0 = 0
19:0/0 = 0
20:0/0 = 0
21:0/0 = 0
22:0/0 = 0
23:0/0 = 0
24:0/0 = 0
25:0/0 = 0
26:0/0 = 0
27:0/0 = 0
28:0/0 = 0
29:0/0 = 0
30:0/0 = 0
31:0/0 = 0
32:0/0 = 0
0:1/0 = 0
1:1/0 = 0
2:1/0 = 0
3:1/0 = 0
4:1/0 = 0
5:1/0 = 0
6:1/0 = 0
7:1/0 = 0
8:1/0 = 0
9:1/0 = 0
10:1/0 = 0
11:1/0 = 0
12:1/0 = 0
13:1/0 = 0
14:1/0 = 0
15:1/0 = 0
16:1/0 = 0
17:1/0 = 0
18:1/0 = 0
19:1/0 = 0
20:1/0 = 0
21:1/0 = 0
22:1/0 = 0
23:1/0 = 0
24:1/0 = 0
25:1/0 = 0
26:1/0 = 0
27:1/0 = 0
28:1/0 = 0
29:1/0 = 0
30:1/0 = 0
31:1/0 = 0
32:1/0 = 0
0:2/0 = 0
1:2/0 = 0
2:2/0 = 0
3:2/0 = 0
4:2/0 = 0
5:2/0 = 0
6:2/0 = 0
7:2/0 = 0
8:2/0 = 0
9:2/0 = 0
10:2/0 = 0
11:2/0 = 0
12:2/0 = 0
13:2/0 = 0
14:2/0 = 0
15:2/0 = 0
16:2/0 = 0
17:2/0 = 0
18:2/0 = 0
19:2/0 = 0
20:2/0 = 0
21:2/0 = 0
22:2/0 = 0
23:2/0 = 0
24:2/0 = 0
25:2/0 = 0
26:2/0 = 0
27:2/0 = 0
28:2/0 = 0
29:2/0 = 0
30:2/0 = 0
31:2/0 = 0
32:2/0 = 0
0:3/0 = 0
1:3/0 = 0
2:3/0 = 0
3:3/0 = 0
4:3/0 = 0
5:3/0 = 0
6:3/0 = 0
7:3/0 = 0
8:3/0 = 0
9:3/0 = 0
10:3/0 = 0
11:3/0 = 0
12:3/0 = 0
13:3/0 = 0
14:3/0 = 0
15:3/0 = 0
16:3/0 = 0
17:3/0 = 0
18:3/0 = 0
19:3/0 = 0
20:3/0 = 0
21:3/0 = 0
22:3/0 = 0
23:3/0 = 0
24:3/0 = 0
25:3/0 = 0
26:3/0 = 0
27:3/0 = 0
28:3/0 = 0
29:3/0 = 0
30:3/0 = 0
31:3/0 = 0
32:3/0 = 0
0:4/0 = 0
1:4/0 = 0
2:4/0 = 0
3:4/0 = 0
4:4/0 = 0
5:4/0 = 0
6:4/0 = 0
7:4/0 = 0
8:4/0 = 0
9:4/0 = 0
10:4/0 = 0
11:4/0 = 0
12:4/0 = 0
13:4/0 = 0
14:4/0 = 0
15:4/0 = 0
16:4/0 = 0
17:4/0 = 0
18:4/0 = 0
19:4/0 = 0
20:4/0 = 0
21:4/0 = 0
22:4/0 = 0
23:4/0 = 0
24:4/0 = 0
25:4/0 = 0
26:4/0 = 0
27:4/0 = 0
28:4/0 = 0
29:4/0 = 0
30:4/0 = 0
31:4/0 = 0
32:4/0 = 0
0:5/0 = 0
1:5/0 = 0
2:5/0 = 0
3:5/0 = 0
4:5/0 = 0
5:5/0 = 0
6:5/0 = 0
7:5/0 = 0
8:5/0 = 0
9:5/0 = 0
10:5/0 = 0
11:5/0 = 0
12:5/0 = 0
13:5/0 = 0
14:5/0 = 0
15:5/0 = 0
16:5/0 = 0
17:5/0 = 0
18:5/0 = 0
19:5/0 = 0
20:5/0 = 0
21:5/0 = 0
22:5/0 = 0
23:5/0 = 0
24:5/0 = 0
25:5/0 = 0
26:5/0 = 0
27:5/0 = 0
28:5/0 = 0
29:5/0 = 0
30:5/0 = 0
31:5/0 = 0
32:5/0 = 0
0:6/0 = 0
1:6/0 = 0
2:6/0 = 0
3:6/0 = 0
4:6/0 = 0
5:6/0 = 0
6:6/0 = 0
7:6/0 = 0
8:6/0 = 0
9:6/0 = 0
10:6/0 = 0
11:6/0 = 0
12:6/0 = 0
13:6/0 = 0
14:6/0 = 0
15:6/0 = 0
16:6/0 = 0
17:6/0 = 0
18:6/0 = 0
19:6/0 = 0
20:6/0 = 0
21:6/0 = 0
22:6/0 = 0
23:6/0 = 0
24:6/0 = 0
25:6/0 = 0
26:6/0 = 0
27:6/0 = 0
28:6/0 = 0
29:6/0 = 0
30:6/0 = 0
31:6/0 = 0
32:6/0 = 0
0:7/0 = 0
1:7/0 = 0
2:7/0 = 0
3:7/0 = 0
4:7/0 = 0
5:7/0 = 0
6:7/0 = 0
7:7/0 = 0
8:7/0 = 0
9:7/0 = 0
10:7/0 = 0
11:7/0 = 0
12:7/0 = 0
13:7/0 = 0
14:7/0 = 0
15:7/0 = 0
16:7/0 = 0
17:7/0 = 0
18:7/0 = 0
19:7/0 = 0
20:7/0 = 0
21:7/0 = 0
22:7/0 = 0
23:7/0 = 0
24:7/0 = 0
25:7/0 = 0
26:7/0 = 0
27:7/0 = 0
28:7/0 = 0
29:7/0 = 0
30:7/0 = 0
31:7/0 = 0
32:7/0 = 0
0:8/0 = 0
1:8/0 = 0
2:8/0 = 0
3:8/0 = 0
4:8/0 = 0
5:8/0 = 0
6:8/0 = 0
7:8/0 = 0
8:8/0 = 0
9:8/0 = 0
10:8/0 = 0
11:8/0 = 0
12:8/0 = 0
13:8/0 = 0
14:8/0 = 0
15:8/0 = 0
16:8/0 = 0
17:8/0 = 0
18:8/0 = 0
19:8/0 = 0
20:8/0 = 0
21:8/0 = 0
22:8/0 = 0
23:8/0 = 0
24:8/0 = 0
25:8/0 = 0
26:8/0 = 0
27:8/0 = 0
28:8/0 = 0
29:8/0 = 0
30:8/0 = 0
31:8/0 = 0
32:8/0 = 0
0:9/0 = 0
1:9/0 = 0
2:9/0 = 0
3:9/0 = 0
4:9/0 = 0
5:9/0 = 0
6:9/0 = 0
7:9/0 = 0
8:9/0 = 0
9:9/0 = 0
10:9/0 = 0
11:9/0 = 0
12:9/0 = 0
13:9/0 = 0
14:9/0 = 0
15:9/0 = 0
16:9/0 = 0
17:9/0 = 0
18:9/0 = 0
19:9/0 = 0
20:9/0 = 0
21:9/0 = 0
22:9/0 = 0
23:9/0 = 0
24:9/0 = 0
25:9/0 = 0
26:9/0 = 0
27:9/0 = 0
28:9/0 = 0
29:9/0 = 0
30:9/0 = 0
31:9/0 = 0
32:9/0 = 0
0:10/0 = 0
1:10/0 = 0
2:10/0 = 0
3:10/0 = 0
4:10/0 = 0
5:10/0 = 0
6:10/0 = 0
7:10/0 = 0
8:10/0 = 0
9:10/0 = 0
10:10/0 = 0
11:10/0 = 0
12:10/0 = 0
13:10/0 = 0
14:10/0 = 0
15:10/0 = 0
16:10/0 = 0
17:10/0 = 0
18:10/0 = 0
19:10/0 = 0
20:10/0 = 0
21:10/0 = 0
22:10/0 = 0
23:10/0 = 0
24:10/0 = 0
25:10/0 = 0
26:10/0 = 0
27:10/0 = 0
28:10/0 = 0
29:10/0 = 0
30:10/0 = 0
31:10/0 = 0
32:10/0 = 0
0:11/0 = 0
1:11/0 = 0
2:11/0 = 0
3:11/0 = 0
4:11/0 = 0
5:11/0 = 0
6:11/0 = 0
7:11/0 = 0
8:11/0 = 0
9:11/0 = 0
10:11/0 = 0
11:11/0 = 0
12:11/0 = 0
13:11/0 = 0
14:11/0 = 0
15:11/0 = 0
16:11/0 = 0
17:11/0 = 0
18:11/0 = 0
19:11/0 = 0
20:11/0 = 0
21:11/0 = 0
22:11/0 = 0
23:11/0 = 0
24:11/0 = 0
25:11/0 = 0
26:11/0 = 0
27:11/0 = 0
28:11/0 = 0
29:11/0 = 0
30:11/0 = 0
31:11/0 = 0
32:11/0 = 0
0:12/0 = 0
1:12/0 = 0
2:12/0 = 0
3:12/0 = 0
4:12/0 = 0
5:12/0 = 0
6:12/0 = 0
7:12/0 = 0
8:12/0 = 0
9:12/0 = 0
10:12/0 = 0
11:12/0 = 0
12:12/0 = 0
13:12/0 = 0
14:12/0 = 0
15:12/0 = 0
16:12/0 = 0
17:12/0 = 0
18:12/0 = 0
19:12/0 = 0
20:12/0 = 0
21:12/0 = 0
22:12/0 = 0
23:12/0 = 0
24:12/0 = 0
25:12/0 = 0
26:12/0 = 0
27:12/0 = 0
28:12/0 = 0
29:12/0 = 0
30:12/0 = 0
31:12/0 = 0
32:12/0 = 0
0:13/0 = 0
1:13/0 = 0
2:13/0 = 0
3:13/0 = 0
4:13/0 = 0
5:13/0 = 0
6:13/0 = 0
7:13/0 = 0
8:13/0 = 0
9:13/0 = 0
10:13/0 = 0
11:13/0 = 0
12:13/0 = 0
13:13/0 = 0
14:13/0 = 0
15:13/0 = 0
16:13/0 = 0
17:13/0 = 0
18:13/0 = 0
19:13/0 = 0
20:13/0 = 0
21:13/0 = 0
22:13/0 = 0
23:13/0 = 0
24:13/0 = 0
25:13/0 = 0
26:13/0 = 0
27:13/0 = 0
28:13/0 = 0
29:13/0 = 0
30:13/0 = 0
31:13/0 = 0
32:13/0 = 0
0:14/0 = 0
1:14/0 = 0
2:14/0 = 0
3:14/0 = 0
4:14/0 = 0
5:14/0 = 0
6:14/0 = 0
7:14/0 = 0
8:14/0 = 0
9:14/0 = 0
10:14/0 = 0
11:14/0 = 0
12:14/0 = 0
13:14/0 = 0
14:14/0 = 0
15:14/0 = 0
16:14/0 = 0
17:14/0 = 0
18:14/0 = 0
19:14/0 = 0
20:14/0 = 0
21:14/0 = 0
22:14/0 = 0
23:14/0 = 0
24:14/0 = 0
25:14/0 = 0
26:14/0 = 0
27:14/0 = 0
28:14/0 = 0
29:14/0 = 0
30:14/0 = 0
31:14/0 = 0
32:14/0 = 0
0:15/0 = 0
1:15/0 = 0
2:15/0 = 0
3:15/0 = 0
4:15/0 = 0
5:15/0 = 0
6:15/0 = 0
7:15/0 = 0
8:15/0 = 0
9:15/0 = 0
10:15/0 = 0
11:15/0 = 0
12:15/0 = 0
13:15/0 = 0
14:15/0 = 0
15:15/0 = 0
16:15/0 = 0
17:15/0 = 0
18:15/0 = 0
19:15/0 = 0
20:15/0 = 0
21:15/0 = 0
22:15/0 = 0
23:15/0 = 0
24:15/0 = 0
25:15/0 = 0
26:15/0 = 0
27:15/0 = 0
28:15/0 = 0
29:15/0 = 0
30:15/0 = 0
31:15/0 = 0
32:15/0 = 0
0:16/0 = 0
1:16/0 = 0
2:16/0 = 0
3:16/0 = 0
4:16/0 = 0
5:16/0 = 0
6:16/0 = 0
7:16/0 = 0
8:16/0 = 0
9:16/0 = 0
10:16/0 = 0
11:16/0 = 0
12:16/0 = 0
13:16/0 = 0
14:16/0 = 0
15:16/0 = 0
16:16/0 = 0
17:16/0 = 0
18:16/0 = 0
19:16/0 = 0
20:16/0 = 0
21:16/0 = 0
22:16/0 = 0
23:16/0 = 0
24:16/0 = 0
25:16/0 = 0
26:16/0 = 0
27:16/0 = 0
28:16/0 = 0
29:16/0 = 0
30:16/0 = 0
31:16/0 = 0
32:16/0 = 0
0:17/0 = 0
1:17/0 = 0
2:17/0 = 0
3:17/0 = 0
4:17/0 = 0
5:17/0 = 0
6:17/0 = 0
7:17/0 = 0
8:17/0 = 0
9:17/0 = 0
10:17/0 = 0
11:17/0 = 0
12:17/0 = 0
13:17/0 = 0
14:17/0 = 0
15:17/0 = 0
16:17/0 = 0
17:17/0 = 0
18:17/0 = 0
19:17/0 = 0
20:17/0 = 0
21:17/0 = 0
22:17/0 = 0
23:17/0 = 0
24:17/0 = 0
25:17/0 = 0
26:17/0 = 0
27:17/0 = 0
28:17/0 = 0
29:17/0 = 0
30:17/0 = 0
31:17/0 = 0
32:17/0 = 0
0:18/0 = 0
1:18/0 = 0
2:18/0 = 0
3:18/0 = 0
4:18/0 = 0
5:18/0 = 0
6:18/0 = 0
7:18/0 = 0
8:18/0 = 0
9:18/0 = 0
10:18/0 = 0
11:18/0 = 0
12:18/0 = 0
13:18/0 = 0
14:18/0 = 0
15:18/0 = 0
16:18/0 = 0
17:18/0 = 0
18:18/0 = 0
19:18/0 = 0
20:18/0 = 0
21:18/0 = 0
22:18/0 = 0
23:18/0 = 0
24:18/0 = 0
25:18/0 = 0
26:18/0 = 0
27:18/0 = 0
28:18/0 = 0
29:18/0 = 0
30:18/0 = 0
31:18/0 = 0
32:18/0 = 0
0:19/0 = 0
1:19/0 = 0
2:19/0 = 0
3:19/0 = 0
4:19/0 = 0
5:19/0 = 0
6:19/0 = 0
7:19/0 = 0
8:19/0 = 0
9:19/0 = 0
10:19/0 = 0
11:19/0 = 0
12:19/0 = 0
13:19/0 = 0
14:19/0 = 0
15:19/0 = 0
16:19/0 = 0
17:19/0 = 0
18:19/0 = 0
19:19/0 = 0
20:19/0 = 0
21:19/0 = 0
22:19/0 = 0
23:19/0 = 0
24:19/0 = 0
25:19/0 = 0
26:19/0 = 0
27:19/0 = 0
28:19/0 = 0
29:19/0 = 0
30:19/0 = 0
31:19/0 = 0
32:19/0 = 0
0:20/0 = 0
1:20/0 = 0
2:20/0 = 0
3:20/0 = 0
4:20/0 = 0
5:20/0 = 0
6:20/0 = 0
7:20/0 = 0
8:20/0 = 0
9:20/0 = 0
10:20/0 = 0
11:20/0 = 0
12:20/0 = 0
13:20/0 = 0
14:20/0 = 0
15:20/0 = 0
16:20/0 = 0
17:20/0 = 0
18:20/0 = 0
19:20/0 = 0
20:20/0 = 0
21:20/0 = 0
22:20/0 = 0
23:20/0 = 0
24:20/0 = 0
25:20/0 = 0
26:20/0 = 0
27:20/0 = 0
28:20/0 = 0
29:20/0 = 0
30:20/0 = 0
31:20/0 = 0
32:20/0 = 0
0:21/0 = 0
1:21/0 = 0
2:21/0 = 0
3:21/0 = 0
4:21/0 = 0
5:21/0 = 0
6:21/0 = 0
7:21/0 = 0
8:21/0 = 0
9:21/0 = 0
10:21/0 = 0
11:21/0 = 0
12:21/0 = 0
13:21/0 = 0
14:21/0 = 0
15:21/0 = 0
16:21/0 = 0
17:21/0 = 0
18:21/0 = 0
19:21/0 = 0
20:21/0 = 0
21:21/0 = 0
22:21/0 = 0
23:21/0 = 0
24:21/0 = 0
25:21/0 = 0
26:21/0 = 0
27:21/0 = 0
28:21/0 = 0
29:21/0 = 0
30:21/0 = 0
31:21/0 = 0
32:21/0 = 0
0:22/0 = 0
1:22/0 = 0
2:22/0 = 0
3:22/0 = 0
4:22/0 = 0
5:22/0 = 0
6:22/0 = 0
7:22/0 = 0
8:22/0 = 0
9:22/0 = 0
10:22/0 = 0
11:22/0 = 0
12:22/0 = 0
13:22/0 = 0
14:22/0 = 0
15:22/0 = 0
16:22/0 = 0
17:22/0 = 0
18:22/0 = 0
19:22/0 = 0
20:22/0 = 0
21:22/0 = 0
22:22/0 = 0
23:22/0 = 0
24:22/0 = 0
25:22/0 = 0
26:22/0 = 0
27:22/0 = 0
28:22/0 = 0
29:22/0 = 0
30:22/0 = 0
31:22/0 = 0
32:22/0 = 0
0:23/0 = 0
1:23/0 = 0
2:23/0 = 0
3:23/0 = 0
4:23/0 = 0
5:23/0 = 0
6:23/0 = 0
7:23/0 = 0
8:23/0 = 0
9:23/0 = 0
10:23/0 = 0
11:23/0 = 0
12:23/0 = 0
13:23/0 = 0
14:23/0 = 0
15:23/0 = 0
16:23/0 = 0
17:23/0 = 0
18:23/0 = 0
19:23/0 = 0
20:23/0 = 0
21:23/0 = 0
22:23/0 = 0
23:23/0 = 0
24:23/0 = 0
25:23/0 = 0
26:23/0 = 0
27:23/0 = 0
28:23/0 = 0
29:23/0 = 0
30:23/0 = 0
31:23/0 = 0
32:23/0 = 0
0:24/0 = 0
1:24/0 = 0
2:24/0 = 0
3:24/0 = 0
4:24/0 = 0
5:24/0 = 0
6:24/0 = 0
7:24/0 = 0
8:24/0 = 0
9:24/0 = 0
10:24/0 = 0
11:24/0 = 0
12:24/0 = 0
13:24/0 = 0
14:24/0 = 0
15:24/0 = 0
16:24/0 = 0
17:24/0 = 0
18:24/0 = 0
19:24/0 = 0
20:24/0 = 0
21:24/0 = 0
22:24/0 = 0
23:24/0 = 0
24:24/0 = 0
25:24/0 = 0
26:24/0 = 0
27:24/0 = 0
28:24/0 = 0
29:24/0 = 0
30:24/0 = 0
31:24/0 = 0
32:24/0 = 0
[resource]
tile_size = Vector2i(48, 48)
sources/0 = SubResource("TileSetAtlasSource_5lppa")

View File

@@ -0,0 +1,6 @@
[gd_resource type="Theme" format=3 uid="uid://dx26d6py3n8xi"]
[ext_resource type="FontFile" uid="uid://eqhm6gv5p05t" path="res://assets/fonts/Minecraft.ttf" id="1_nmd6r"]
[resource]
default_font = ExtResource("1_nmd6r")

View File

@@ -0,0 +1,4 @@
class_name DLTileset extends Resource
@export var floor_tile_coords: Vector2i
@export var wall_tile_coords: Vector2i

View File

@@ -0,0 +1 @@
uid://c6701vy8h5rfx

View File

@@ -0,0 +1,12 @@
class_name UnitAllegiance extends Resource
enum AllegianceType {
PLAYER,
ENEMY,
PLAYER_ALLY,
ENEMY_ALLY,
UNAFFILIATED
}
@export var type: AllegianceType
@export var color: Color

View File

@@ -0,0 +1 @@
uid://bhglsexm8dtpj

View File

@@ -0,0 +1,3 @@
class_name UnitInfo extends Resource
@export var name: String = "Unit"

View File

@@ -0,0 +1 @@
uid://d37ulss2k0bq5

View File

@@ -0,0 +1,17 @@
class_name UnitStats extends Resource
@export var max_hp: int = 10
@export var current_hp: int
@export var phys_atk: int = 1
@export var phys_def: int = 1
@export var magic_atk: int = 0
@export var magic_def: int = 0
@export var hit: int = 85
@export var atk_range: int = 1
@export var spd: int = 1
@export var eva: int = 1
@export var lck: int = 1
func _init(max_hp: int = 10) -> void:
self.max_hp = max_hp
current_hp = max_hp

View File

@@ -0,0 +1 @@
uid://cydoey8a8nmb8

36
scenes/combat_test.tscn Normal file
View File

@@ -0,0 +1,36 @@
[gd_scene format=3 uid="uid://wy7ur5r23ek3"]
[ext_resource type="PackedScene" uid="uid://cy7r0udfcsqbn" path="res://prefabs/combat_ui.tscn" id="1_5jbmu"]
[ext_resource type="PackedScene" uid="uid://dkhyh5ce4iuk3" path="res://prefabs/combat_map.tscn" id="1_7abyo"]
[ext_resource type="Script" uid="uid://c8xb86ty7rduf" path="res://scripts/test_map_generator.gd" id="2_ekcfv"]
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/camera_controller.gd" id="3_cam"]
[ext_resource type="Script" uid="uid://dfojm3n0em4ef" path="res://nodes/player_controller.gd" id="4_s5ga2"]
[ext_resource type="AudioStream" uid="uid://dsikulned64qt" path="res://assets/music/combat_bgm_01.OGG" id="6_0yobm"]
[ext_resource type="PackedScene" uid="uid://b6a7nlnf58mc4" path="res://prefabs/unit.tscn" id="6_rfoto"]
[ext_resource type="Resource" uid="uid://dufi2h00j5vrq" path="res://resources/allegiance_types/player_allegiance.tres" id="7_0wg56"]
[ext_resource type="Resource" uid="uid://cuc7kkknpsr1g" path="res://resources/allegiance_types/enemy_allegiance.tres" id="8_w105o"]
[node name="CombatTest" type="Node2D" unique_id=855645983]
[node name="CombatUI" parent="." unique_id=329168107 instance=ExtResource("1_5jbmu")]
[node name="CombatMap" parent="." unique_id=546780706 instance=ExtResource("1_7abyo")]
[node name="PlayerController" type="Node" parent="." unique_id=774568109 node_paths=PackedStringArray("dl_map")]
script = ExtResource("4_s5ga2")
dl_map = NodePath("../CombatMap")
[node name="Camera2D" type="Camera2D" parent="." unique_id=1739569732]
zoom = Vector2(1.5, 1.5)
script = ExtResource("3_cam")
[node name="TestMapGenerator" type="Node" parent="." unique_id=833658301 node_paths=PackedStringArray("dl_map")]
script = ExtResource("2_ekcfv")
dl_map = NodePath("../CombatMap")
unit_template = ExtResource("6_rfoto")
player_allegiance = ExtResource("7_0wg56")
enemy_allegiance = ExtResource("8_w105o")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1057500234]
stream = ExtResource("6_0yobm")
autoplay = true

59
scenes/main_menu.tscn Normal file
View File

@@ -0,0 +1,59 @@
[gd_scene format=3 uid="uid://dlbuo46n6q238"]
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_ekxnf"]
[ext_resource type="AudioStream" uid="uid://b7dgmblbcm0cj" path="res://assets/music/menu_theme.OGG" id="1_yqeox"]
[sub_resource type="GDScript" id="GDScript_bqqt6"]
resource_name = "StartButton"
script/source = "extends Button
func _pressed() -> void:
get_parent().queue_free()
get_tree().change_scene_to_file(\"res://scenes/combat_test.tscn\")
"
[sub_resource type="GDScript" id="GDScript_ekxnf"]
resource_name = "ExitButton"
script/source = "extends Button
func _pressed() -> void:
get_tree().quit(0)
"
[node name="Menu" type="Control" unique_id=528000941]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_ekxnf")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1976575731]
stream = ExtResource("1_yqeox")
autoplay = true
[node name="Buttons" type="VBoxContainer" parent="." unique_id=1869378860]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
alignment = 1
[node name="StartButton" type="Button" parent="Buttons" unique_id=734401781]
layout_mode = 2
text = "Start"
script = SubResource("GDScript_bqqt6")
[node name="Options" type="Button" parent="Buttons" unique_id=1037030570]
layout_mode = 2
text = "Options"
[node name="ExitButton" type="Button" parent="Buttons" unique_id=1205095617]
layout_mode = 2
text = "Exit"
script = SubResource("GDScript_ekxnf")
[connection signal="pressed" from="Buttons/Options" to="Buttons/Options" method="_on_pressed"]

20
scenes/test_scene.tscn Normal file
View File

@@ -0,0 +1,20 @@
[gd_scene format=3 uid="uid://b7hhdysqqmx4y"]
[ext_resource type="Texture2D" uid="uid://c7e4jw4xcti0q" path="res://assets/sprites/castle_spritesheet.png" id="1_g7g4h"]
[ext_resource type="Shader" uid="uid://dakre5usldk6r" path="res://shaders/masked_palette_swap.gdshader" id="1_nd71p"]
[ext_resource type="Texture2D" uid="uid://b8td6sv5re6r8" path="res://assets/sprites/grey_castle_spritesheet_mask.bmp" id="2_7ddre"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_qjeyg"]
shader = ExtResource("1_nd71p")
shader_parameter/flag_mask = ExtResource("2_7ddre")
shader_parameter/team_color = Color(0.84830123, 0.29993045, 0.292207, 1)
[sub_resource type="AtlasTexture" id="AtlasTexture_j8ivh"]
atlas = ExtResource("1_g7g4h")
region = Rect2(0, 100, 100, 100)
[node name="TestScene" type="Node2D" unique_id=1687841395]
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1997336331]
material = SubResource("ShaderMaterial_qjeyg")
texture = SubResource("AtlasTexture_j8ivh")

View File

@@ -0,0 +1,42 @@
class_name CameraController extends Camera2D
const DRAG_THRESHOLD := 8.0
var _dragging := false
var _left_pending := false
var _drag_start := Vector2.ZERO
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
match event.button_index:
MOUSE_BUTTON_LEFT:
if event.pressed:
_left_pending = true
_drag_start = event.position
else:
_left_pending = false
if _dragging:
_dragging = false
Input.set_default_cursor_shape(Input.CURSOR_ARROW)
get_viewport().set_input_as_handled()
MOUSE_BUTTON_MIDDLE:
if event.pressed:
_dragging = true
_drag_start = event.position
Input.set_default_cursor_shape(Input.CURSOR_DRAG)
else:
_dragging = false
Input.set_default_cursor_shape(Input.CURSOR_ARROW)
get_viewport().set_input_as_handled()
elif event is InputEventMouseMotion:
if _left_pending and not _dragging:
if event.position.distance_to(_drag_start) >= DRAG_THRESHOLD:
_dragging = true
_left_pending = false
Input.set_default_cursor_shape(Input.CURSOR_DRAG)
if _dragging:
var delta: Vector2 = _drag_start - event.position
_drag_start = event.position
position += delta / zoom
get_viewport().set_input_as_handled()

View File

@@ -0,0 +1 @@
uid://csdcbi2gtwrly

33
scripts/combat_ui.gd Normal file
View File

@@ -0,0 +1,33 @@
class_name CombatUI extends CanvasLayer
@onready var unit_panel: PanelContainer = %UnitPanel
@onready var name_label: Label = %NameLabel
@onready var hp_bar: ProgressBar = %HPBar
var _selected_unit: Unit
func _ready() -> void:
unit_panel.visible = false
for unit: Unit in get_tree().get_nodes_in_group("units"):
unit.unit_selected_changed.connect(_on_unit_selected_changed)
get_tree().node_added.connect(_on_node_added)
func _on_node_added(node: Node) -> void:
if node is Unit and node.is_in_group("units"):
node.unit_selected_changed.connect(_on_unit_selected_changed)
func _process(_delta: float) -> void:
if _selected_unit:
hp_bar.max_value = _selected_unit.current_stats.max_hp
hp_bar.value = _selected_unit.current_stats.current_hp
func _on_unit_selected_changed(unit: Unit, selected: bool) -> void:
if selected:
_selected_unit = unit
name_label.text = unit.current_info.name
hp_bar.max_value = unit.current_stats.max_hp
hp_bar.value = unit.current_stats.current_hp
unit_panel.visible = true
else:
_selected_unit = null
unit_panel.visible = false

1
scripts/combat_ui.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://w2wh6gtv3u2l

16
scripts/grid_overlay.gd Normal file
View File

@@ -0,0 +1,16 @@
class_name GridOverlay extends TileMapLayer
const SOURCE_ID = 0
const HIGHLIGHT_SPRITE_ID = Vector2i(0,0)
@onready var targeting_selector: Sprite2D = $TargetingIndicator
func highlight_tile(coords: Vector2i) -> void:
set_cell(coords, SOURCE_ID, HIGHLIGHT_SPRITE_ID)
func clear_tile(coords: Vector2i) -> void:
set_cell(coords)
func target_tile(coords: Vector2i) -> void:
targeting_selector.position = coords * tile_set.tile_size
targeting_selector.visible = true

View File

@@ -0,0 +1 @@
uid://cxl38x2m6sj3w

View File

@@ -0,0 +1,38 @@
extends Node
const GRID_SIZE := 5
@export var dl_map: CombatMap
@export var unit_template: PackedScene
@export var player_allegiance: UnitAllegiance
@export var enemy_allegiance: UnitAllegiance
func _ready() -> void:
for x in GRID_SIZE:
for y in GRID_SIZE:
var pos := Vector2i(x, y)
var is_edge := x == 0 or x == GRID_SIZE - 1 or y == 0 or y == GRID_SIZE - 1
if is_edge:
dl_map.draw_wall(pos)
else:
dl_map.draw_floor(pos)
# Create a putit at the center belonging to the player
var center := Vector2i(GRID_SIZE / 2, GRID_SIZE / 2)
var unit: Unit = unit_template.instantiate()
unit.stat_template = UnitStats.new(50)
unit.info_template = UnitInfo.new()
unit.allegiance_template = player_allegiance
unit.info_template.name = "Putit"
unit.position = dl_map.coords_to_world(center)
get_parent().add_child.call_deferred(unit)
# Create a putit at one above the center belonging to the enemy
var center_enemy := Vector2i(GRID_SIZE / 2, (GRID_SIZE / 2) - 1)
var enemy_unit: Unit = unit_template.instantiate()
enemy_unit.stat_template = UnitStats.new(50)
enemy_unit.info_template = UnitInfo.new()
enemy_unit.allegiance_template = enemy_allegiance
enemy_unit.info_template.name = "Putit"
enemy_unit.position = dl_map.coords_to_world(center_enemy)
get_parent().add_child.call_deferred(enemy_unit)

View File

@@ -0,0 +1 @@
uid://c8xb86ty7rduf

37
scripts/tile_highlight.gd Normal file
View File

@@ -0,0 +1,37 @@
extends ColorRect
signal tile_hovered(coords: Vector2i)
@export var tile_size: float = 48.0
var _time: float = 0.0
var _previous_coords := Vector2i(INF, INF)
func _ready() -> void:
size = Vector2(tile_size, tile_size)
color = Color(1.0, 1.0, 1.0, 0.25)
mouse_filter = Control.MOUSE_FILTER_IGNORE
func _process(delta: float) -> void:
_time += delta
color.a = 0.25 + 0.1 * sin(_time * 4.0)
var mouse_pos := get_global_mouse_position()
var snapped_pos := _snap_to_grid(mouse_pos)
global_position = snapped_pos
var coords := Vector2i(snapped_pos / tile_size)
if coords != _previous_coords:
_previous_coords = coords
tile_hovered.emit(coords)
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_MOUSE_EXIT:
hide()
elif what == NOTIFICATION_WM_MOUSE_ENTER:
show()
func _snap_to_grid(pos: Vector2) -> Vector2:
return Vector2(floorf(pos.x / tile_size), floorf(pos.y / tile_size)) * tile_size

View File

@@ -0,0 +1 @@
uid://b31eyqov8w7bm

View File

@@ -0,0 +1,12 @@
shader_type canvas_item;
uniform float threshold : hint_range(0.0, 1.0) = 0.01;
void fragment() {
vec4 tex_color = texture(TEXTURE, UV);
float brightness = max(tex_color.r, max(tex_color.g, tex_color.b));
if (brightness < threshold) {
discard;
}
COLOR = tex_color;
}

View File

@@ -0,0 +1 @@
uid://bd8ki8xwym5nc

View File

@@ -0,0 +1,16 @@
shader_type canvas_item;
uniform sampler2D flag_mask : source_color, hint_default_black;
uniform vec4 team_color : source_color = vec4(1.0, 0.0, 0.0, 1.0); // red by default
void fragment() {
vec4 base = texture(TEXTURE, UV);
float mask = texture(flag_mask, UV).r; // 1.0 on flag pixels, 0.0 on castle
// Use the grey luminance as a brightness multiplier on the team color
float brightness = base.r; // grey means R == G == B
vec4 tinted = vec4(team_color.rgb * brightness * 2.0, base.a);
// Blend: castle pixels stay grey, flag pixels get tinted
COLOR = mix(base, tinted, mask);
}

View File

@@ -0,0 +1 @@
uid://dakre5usldk6r