Compare commits
37 Commits
595e389033
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2891fb6248 | ||
|
|
31a787a7f0 | ||
|
|
f332771153 | ||
|
|
2d74e15006 | ||
|
|
6d9d08d78c | ||
|
|
834d6a3a83 | ||
|
|
676c82c4e5 | ||
|
|
e2d23bec48 | ||
|
|
b01d8c6648 | ||
|
|
f6ac31b52e | ||
|
|
9b1d6e8c8f | ||
|
|
664c9694de | ||
|
|
e356078a9f | ||
|
|
6b46d1c274 | ||
|
|
b807e9897d | ||
|
|
c192d48bc4 | ||
|
|
24134cfa33 | ||
|
|
767df71975 | ||
|
|
92a0bb1d58 | ||
|
|
e42a98fece | ||
|
|
0882908e4c | ||
|
|
880d4ecc77 | ||
|
|
39d2222546 | ||
|
|
97909235ff | ||
|
|
344efee7b4 | ||
|
|
b086c7d181 | ||
|
|
7f6fd7a0d4 | ||
|
|
1f87df8149 | ||
|
|
85f593cf56 | ||
|
|
612e88579d | ||
|
|
3a8e3edc03 | ||
|
|
b485e11a5a | ||
|
|
1973d93b16 | ||
|
|
eb5bf32bb8 | ||
|
|
89fd4210e3 | ||
|
|
407b982710 | ||
|
|
68d1406632 |
BIN
assets/fonts/MS Gothic.ttf
Normal file
36
assets/fonts/MS Gothic.ttf.import
Normal file
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://1a55lafcbss"
|
||||
path="res://.godot/imported/MS Gothic.ttf-fe31b0559366ecce2136a33451d351df.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/fonts/MS Gothic.ttf"
|
||||
dest_files=["res://.godot/imported/MS Gothic.ttf-fe31b0559366ecce2136a33451d351df.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={}
|
||||
BIN
assets/sprites/CP002AA.BMP
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
@@ -2,16 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2se5wyly6gr6"
|
||||
path="res://.godot/imported/character.bmp-cf65b7adb0d5844b4deb09628dc23b0b.ctex"
|
||||
uid="uid://dyutp4m5d53gd"
|
||||
path="res://.godot/imported/CP002AA.BMP-458508fd9812b1212a2b0485c80b722b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/character.bmp"
|
||||
dest_files=["res://.godot/imported/character.bmp-cf65b7adb0d5844b4deb09628dc23b0b.ctex"]
|
||||
source_file="res://assets/sprites/CP002AA.BMP"
|
||||
dest_files=["res://.godot/imported/CP002AA.BMP-458508fd9812b1212a2b0485c80b722b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
BIN
assets/sprites/CP002AB.BMP
Normal file
|
After Width: | Height: | Size: 32 KiB |
40
assets/sprites/CP002AB.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6smsdyydtiv4"
|
||||
path="res://.godot/imported/CP002AB.BMP-83c77e61ea705a94bd90d268ad08d826.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/CP002AB.BMP"
|
||||
dest_files=["res://.godot/imported/CP002AB.BMP-83c77e61ea705a94bd90d268ad08d826.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/MP000A.BMP
Normal file
|
After Width: | Height: | Size: 245 KiB |
40
assets/sprites/MP000A.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://65rmoynep5hy"
|
||||
path="res://.godot/imported/MP000A.BMP-368d2040af5711fa204ca3af7a278f42.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/MP000A.BMP"
|
||||
dest_files=["res://.godot/imported/MP000A.BMP-368d2040af5711fa204ca3af7a278f42.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/aux_terrain.BMP
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
40
assets/sprites/aux_terrain.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b20mhn7ca5xyo"
|
||||
path="res://.godot/imported/aux_terrain.BMP-15c2f0fd910deee8ff95cb1125e18906.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/aux_terrain.BMP"
|
||||
dest_files=["res://.godot/imported/aux_terrain.BMP-15c2f0fd910deee8ff95cb1125e18906.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/combat_map_ui.BMP
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
40
assets/sprites/combat_map_ui.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b1ks72fiesfrm"
|
||||
path="res://.godot/imported/combat_map_ui.BMP-d85bc460987d9320be9eb9133d575ddc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/combat_map_ui.BMP"
|
||||
dest_files=["res://.godot/imported/combat_map_ui.BMP-d85bc460987d9320be9eb9133d575ddc.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/dialogue_continue.BMP
Normal file
|
After Width: | Height: | Size: 38 KiB |
40
assets/sprites/dialogue_continue.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dj621xih5cam7"
|
||||
path="res://.godot/imported/dialogue_continue.BMP-dabd4279feb14083304f5e16e0d8a0bd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/dialogue_continue.BMP"
|
||||
dest_files=["res://.godot/imported/dialogue_continue.BMP-dabd4279feb14083304f5e16e0d8a0bd.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/dialogue_ui.BMP
Normal file
|
After Width: | Height: | Size: 938 KiB |
40
assets/sprites/dialogue_ui.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cyl18yt5hxyb5"
|
||||
path="res://.godot/imported/dialogue_ui.BMP-e3af05c48e6befb926caa8e760ab9c22.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/dialogue_ui.BMP"
|
||||
dest_files=["res://.godot/imported/dialogue_ui.BMP-e3af05c48e6befb926caa8e760ab9c22.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
|
||||
|
Before Width: | Height: | Size: 5.4 MiB |
|
Before Width: | Height: | Size: 5.4 MiB After Width: | Height: | Size: 3.7 MiB |
@@ -2,16 +2,16 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://udsusbp3o76m"
|
||||
path="res://.godot/imported/map1.bmp-9e2f42fc155126c194d55961a68898fc.ctex"
|
||||
uid="uid://cau61m1755dkn"
|
||||
path="res://.godot/imported/SO008A.BMP-dedc5f46caf0f66b50eed190cef6a3a9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/map1.bmp"
|
||||
dest_files=["res://.godot/imported/map1.bmp-9e2f42fc155126c194d55961a68898fc.ctex"]
|
||||
source_file="res://assets/ui/SO008A.BMP"
|
||||
dest_files=["res://.godot/imported/SO008A.BMP-dedc5f46caf0f66b50eed190cef6a3a9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
BIN
assets/ui/SO008B.BMP
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
40
assets/ui/SO008B.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cavpqnd0qqoou"
|
||||
path="res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/ui/SO008B.BMP"
|
||||
dest_files=["res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.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/ui/unit_faces.BMP
Normal file
|
After Width: | Height: | Size: 173 KiB |
40
assets/ui/unit_faces.BMP.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c7coajdu61crq"
|
||||
path="res://.godot/imported/unit_faces.BMP-4eb539ec6753a0109183a7c01fbbbf92.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/ui/unit_faces.BMP"
|
||||
dest_files=["res://.godot/imported/unit_faces.BMP-4eb539ec6753a0109183a7c01fbbbf92.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
|
||||
278
docs/review/2026-04-07-architecture-review.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Architecture & Code Quality Review — Dungeon Lords
|
||||
|
||||
Reviewed: 2026-04-07
|
||||
Previous review: `architecture_review.md` (2026-04-02)
|
||||
|
||||
This pass re-checks the 15 issues from the prior review and surfaces new findings introduced by recent work (fog of war, camera fixes, map sizing, corners, room/wall renderer).
|
||||
|
||||
---
|
||||
|
||||
## Status of Prior Review (2026-04-02)
|
||||
|
||||
| # | Issue | Status | Notes |
|
||||
|---|-------|--------|-------|
|
||||
| 1 | No allegiance check on combat | **Open** | `nodes/player_controller.gd:135` |
|
||||
| 2 | No pathfinding | **Partially mitigated** | `is_tile_passable()` now blocks the next step (`player_controller.gd:122`), but units still walk around L-walls one axis at a time and have no movement budget |
|
||||
| 3 | Stale combat proposal | **Open** | `apply_proposal()` still has no `is_instance_valid()` guards (`combat_system.gd:129-148`) |
|
||||
| 4 | `queue_free()` dangling refs | **Open** | `nodes/unit.gd` still queue_frees immediately on death |
|
||||
| 5 | Duplicated unit-tracking | **Open** | `player_controller.gd:26-35` and `scripts/battle/combat_ui.gd:40-50` |
|
||||
| 6 | Inline scripts in unit.tscn | **Open** | Two `SubResource` GDScript blocks remain in `prefabs/unit.tscn` |
|
||||
| 7 | Two combat code paths | **Open** | `combat_system.gd:114` `process_combat()` is dead (no callers in code, only docs/plans) |
|
||||
| 8 | O(n) unit lookup | **Open** | `player_controller.gd:159-167` |
|
||||
| 9 | No turn / movement system | **Open** | `spd` still unused |
|
||||
| 10 | `atk_range` not enforced | **Open** | Tactic-range is checked when *building* a proposal (`combat_system.gd:25`), but `_handle_left_click` emits `combat_requested` without any pre-check |
|
||||
| 11 | tile_highlight always processing | **Resolved** | `tile_highlight.gd` is gone; replaced by `scripts/battle/grid_overlay.gd` which only updates on `target_tile()` calls |
|
||||
| 12 | Unused GridOverlay | **Resolved** | Now wired through `StrategyPhase._on_mouse_grid_changed → CombatMap.target_tile()` (`combat_map.gd:85`) |
|
||||
| 13 | Jolt 3D physics configured | **Open** | Still in `project.godot` |
|
||||
| 14 | `current_hp` default fragile | **Resolved-ish** | `UnitStats._init` sets it; works but `@export` still has no default |
|
||||
| 15 | `UnitInfo.name` shadows `Object.name` | **Open** | `resources/resource_definitions/unit_info.gd:3` |
|
||||
|
||||
**Tally:** 11 still open, 1 partially mitigated, 3 resolved.
|
||||
|
||||
---
|
||||
|
||||
## High Priority
|
||||
|
||||
### A. `apply_proposal` does not validate unit references — **Resolved 2026-04-07**
|
||||
|
||||
`apply_proposal` now early-returns if either unit reference is invalid, and re-checks both before the counterattack branch.
|
||||
|
||||
**File:** `nodes/combat_system.gd:129-148`
|
||||
|
||||
`process_combat()` was given an `is_instance_valid()` guard at the top, but the canonical path used by the UI — `apply_proposal()` — calls `def_unit.take_damage(...)` and `atk_unit.take_damage(...)` directly without checking either reference. Today this is harmless because `Unit._die()` only happens through this same code path, but the moment a second damage source exists (poison, AoE, traps), a unit can be freed mid-proposal and `apply_proposal` will hit a freed reference.
|
||||
|
||||
**Fix:** Guard both unit accesses with `is_instance_valid()`. Better, snapshot the unit references into the proposal at creation time and treat them as nullable from then on.
|
||||
|
||||
---
|
||||
|
||||
### B. No allegiance check on combat targeting
|
||||
|
||||
**File:** `nodes/player_controller.gd:135`
|
||||
|
||||
```gdscript
|
||||
if _selected_unit and clicked_unit != _selected_unit \
|
||||
and _selected_unit.is_alive() and clicked_unit.is_alive():
|
||||
combat_requested.emit(_selected_unit, clicked_unit)
|
||||
```
|
||||
|
||||
A player unit can attack a friendly unit. Carried over unchanged from the prior review.
|
||||
|
||||
**Fix:** Compare `clicked_unit.current_allegiance.type` against the selected unit, ideally in `StrategyPhase` so `PlayerController` stays allegiance-agnostic.
|
||||
|
||||
---
|
||||
|
||||
### C. `Unit.queue_free()` on death leaves dangling references in proposals
|
||||
|
||||
**File:** `nodes/unit.gd` (`_die`)
|
||||
|
||||
When a unit dies it emits `unit_died` and immediately calls `queue_free()`. `CombatUI._on_unit_died` (`scripts/battle/combat_ui.gd:52-58`) compares the dying unit against `_current_proposal.attacker.unit` — that comparison happens *this* frame, so it's fine, but `CombatProposal` still holds the same reference and will be invalid for any code that runs after the deferred free.
|
||||
|
||||
**Fix:** Either delay the `queue_free()` until after combat resolution finishes (hide-then-free), or null out unit references in any active proposal as part of the death handler.
|
||||
|
||||
---
|
||||
|
||||
### D. `attack_range` is never enforced before requesting combat
|
||||
|
||||
**Files:** `nodes/player_controller.gd:135`, `nodes/combat_system.gd:22-27`
|
||||
|
||||
Tactic-range filtering happens *inside* `create_proposal`, so a click that targets an unreachable enemy will produce a proposal with no valid tactics — and from there the behavior depends on `_find_default_attack` returning `null` and `_snapshot` happily writing zeros. The user-visible bug is that you can click any enemy on the map and a proposal opens regardless of distance.
|
||||
|
||||
**Fix:** Check distance against the attacker's max tactic range (or `current_stats.atk_range`) inside `_handle_left_click` before emitting `combat_requested`. Reject the click otherwise.
|
||||
|
||||
---
|
||||
|
||||
## Medium Priority
|
||||
|
||||
### E. Pathfinding still missing; movement is greedy axis-stepping
|
||||
|
||||
**File:** `nodes/player_controller.gd:96-127`
|
||||
|
||||
`is_tile_passable` now blocks the next single step, which is an improvement, but the mover is still a greedy "pick the larger axis" loop with no global plan. Going around an L-shaped wall succeeds; going around a U-shaped wall doesn't. There's also still no movement budget — a unit can walk to the far corner of the map with a single click.
|
||||
|
||||
**Fix:** Compute the full path with BFS/A* over `MapLayout.is_passable` at click time, store it as a list of tiles, and step along it. Add a per-turn movement budget once a turn system exists.
|
||||
|
||||
---
|
||||
|
||||
### F. Duplicated unit-tracking boilerplate
|
||||
|
||||
**Files:** `nodes/player_controller.gd:26-35`, `scripts/battle/combat_ui.gd:40-50`
|
||||
|
||||
Both systems independently iterate `get_nodes_in_group("units")`, hook `node_added`, and guard against duplicate connections. Any third system that cares about units will copy this again.
|
||||
|
||||
**Fix:** Promote a small `UnitRegistry` autoload that emits `unit_registered(unit)` / `unit_deregistered(unit)`. This ties into finding G below.
|
||||
|
||||
---
|
||||
|
||||
### G. `CombatMap.remove_unit` doesn't disconnect signals
|
||||
|
||||
**File:** `nodes/combat_map.gd:80-82`
|
||||
|
||||
`remove_unit` only calls `remove_child`. PlayerController and CombatUI both connected to `unit_died` (and `unit_selected_changed` for PlayerController via the AllegianceIndicator path). If a unit is removed and re-added — or destroyed without going through `_die` — the connections leak. Combined with finding F, an event-bus / registry would solve both.
|
||||
|
||||
---
|
||||
|
||||
### H. Two combat entry points; `process_combat` is dead — **Resolved 2026-04-07**
|
||||
|
||||
`process_combat()` deleted from `combat_system.gd`. Only `create_proposal` → `apply_proposal` remains.
|
||||
|
||||
**File:** `nodes/combat_system.gd:114-127`
|
||||
|
||||
`process_combat()` has no callers in `nodes/` or `scripts/`; the only references are in plan/spec docs under `docs/superpowers/`. It mirrors the proposal-application path and is liable to drift.
|
||||
|
||||
**Fix:** Delete `process_combat()` and let any future AI/auto-combat go through `create_proposal` → `apply_proposal` directly.
|
||||
|
||||
---
|
||||
|
||||
### I. O(n) unit lookup on every click
|
||||
|
||||
**File:** `nodes/player_controller.gd:159-167`
|
||||
|
||||
`_get_unit_at` snaps every unit in the group to the grid and compares. Fine for tens of units, slow for hundreds.
|
||||
|
||||
**Fix:** Maintain a `Dictionary[Vector2i, Unit]` in `CombatMap` that updates on deploy/move/death. Lookups become O(1) and you get tile-occupancy collision detection for free.
|
||||
|
||||
---
|
||||
|
||||
### J. Sprite spam in `FogRenderer` and `WallRenderer` — **Resolved 2026-04-07**
|
||||
|
||||
Both renderers now override `_draw()` and use `draw_texture_rect_region()` directly. Zero child sprites; layout changes call `queue_redraw()` instead of thrashing the scene tree. Findings **O** (redundant `match` arms) and **P** (undocumented `FOG_RECT`) were folded into the same change.
|
||||
|
||||
**Files:** `nodes/fog_renderer.gd:13-43`, `nodes/wall_renderer.gd:44-176`
|
||||
|
||||
Both renderers create one `Sprite2D` per fog tile / per wall half-segment / per inner corner, with no pooling, and `_clear()` `queue_free`s every child on the next layout change. For a modest 30×30 map this is already in the high hundreds of nodes; for larger maps it scales linearly with `width × height` plus wall surface area, and every map reload thrashes the scene tree.
|
||||
|
||||
**Fix:** Override `_draw()` and use `draw_texture_rect_region()` for both renderers. You get the same visual result with one draw call per region instead of N nodes, and `queue_redraw()` on layout change replaces the whole `_clear` + recreate cycle.
|
||||
|
||||
---
|
||||
|
||||
### K. `MapLayout` requires `initialize()` but exposes no guard
|
||||
|
||||
**Files:** `nodes/combat_map.gd:89-101`, `resources/resource_definitions/map_layout.gd`
|
||||
|
||||
`apply_layout()` is the only place that calls `map_layout.initialize()`. Anything that constructs a `MapLayout` and calls `is_passable()` / `is_tile_valid()` without going through `apply_layout` will silently see empty internal state.
|
||||
|
||||
**Fix:** Either lazy-initialize inside `is_passable` / `is_tile_valid`, or assert that `initialize()` has been called.
|
||||
|
||||
---
|
||||
|
||||
### L. `target_tile` highlight updates while combat UI is open — **Not a bug (2026-04-07)**
|
||||
|
||||
On re-reading `player_controller.gd:44-46`, `_process` already early-returns when `input_disabled` is true. Original finding was based on a misread; no code change needed.
|
||||
|
||||
**File:** `nodes/player_controller.gd:44-52`
|
||||
|
||||
`input_disabled` short-circuits `_unhandled_input` but `_process` still runs and emits `mouse_grid_changed`, so the cursor highlight keeps tracking the mouse during the combat proposal panel. May be intentional, but looks like a bug — the player just told the game "I'm interacting with the modal", not "keep showing me where my next click would land on the map."
|
||||
|
||||
**Fix:** Bail out of `_process` (or at least suppress the emit) when `input_disabled` is true.
|
||||
|
||||
---
|
||||
|
||||
### M. Inline scripts in `prefabs/unit.tscn`
|
||||
|
||||
The AllegianceIndicator and selection-highlight scripts are still embedded as `SubResource` GDScript blocks in the scene file. Invisible to grep, painful to refactor.
|
||||
|
||||
**Fix:** Extract to `scripts/units/allegiance_indicator.gd` and `scripts/units/unit_selection_highlight.gd`.
|
||||
|
||||
---
|
||||
|
||||
## Low Priority
|
||||
|
||||
### N. `WallRenderer._draw_outer_corners` is a stub
|
||||
|
||||
**File:** `nodes/wall_renderer.gd:114-115, 179-183`
|
||||
|
||||
The function is called but no-ops. The TODO is fine for now, but the matching atlas constants for outer corners aren't defined either, so when you implement it you'll need to add both. Worth a tracker entry.
|
||||
|
||||
---
|
||||
|
||||
### O. `WallRenderer._draw_tile_walls` match arms are identical — **Resolved 2026-04-07**
|
||||
|
||||
Collapsed to a single `for edge in edges: _build_edge_segments(...)` loop as part of finding J's refactor.
|
||||
|
||||
**File:** `nodes/wall_renderer.gd:103-112`
|
||||
|
||||
```gdscript
|
||||
match edge:
|
||||
&"left": _draw_edge_segments(tile_origin, edge)
|
||||
&"right": _draw_edge_segments(tile_origin, edge)
|
||||
&"top": _draw_edge_segments(tile_origin, edge)
|
||||
&"bottom":_draw_edge_segments(tile_origin, edge)
|
||||
```
|
||||
|
||||
The whole `match` collapses to `for edge in edges: _draw_edge_segments(tile_origin, edge)`.
|
||||
|
||||
---
|
||||
|
||||
### P. `FogRenderer.FOG_RECT` is `(53, 53, 100, 100)` — tile-aligned magic constant
|
||||
|
||||
**File:** `nodes/fog_renderer.gd:8`
|
||||
|
||||
Hardcoded atlas region with no comment about why those coordinates. Same kind of magic numbers that the wall renderer uses, but at least the wall constants have section comments. Add a one-line comment naming the source asset.
|
||||
|
||||
---
|
||||
|
||||
### Q. `Jolt Physics` configured for a 2D project
|
||||
|
||||
**File:** `project.godot`
|
||||
|
||||
Carried over from prior review. `3d/physics_engine="Jolt Physics"` is harmless but unused — the game has no 3D nodes. Removing it removes a "huh, why?" for any new contributor.
|
||||
|
||||
---
|
||||
|
||||
### R. `UnitInfo.name` still shadows `Object.name`
|
||||
|
||||
**File:** `resources/resource_definitions/unit_info.gd:3`
|
||||
|
||||
Rename to `display_name` or `unit_name` to disambiguate from `Node.name` / `Object.name`.
|
||||
|
||||
---
|
||||
|
||||
### S. `CombatMap.is_wall` and `is_tile_valid` have legacy fallbacks — **Resolved 2026-04-07**
|
||||
|
||||
Fallback branches removed from `is_tile_passable` and `is_tile_valid`; both now `assert(map_layout != null)`. The now-dead `is_wall()` helper was deleted.
|
||||
|
||||
**File:** `nodes/combat_map.gd:97-108`
|
||||
|
||||
Both methods have a "no room system" fallback path. Now that every map goes through `MapLayout`, the fallback is dead — but it's also the kind of dead code that quietly hides bugs (a missing `map_layout` won't crash, it'll silently fall through to wall-tile checks). Either delete the fallbacks and assert, or document the contract.
|
||||
|
||||
---
|
||||
|
||||
## Architectural Notes
|
||||
|
||||
### Renderer pattern is the right shape, wrong implementation
|
||||
`WallRenderer` and `FogRenderer` correctly isolate "draw the map's decoration layer" from `CombatMap`. The split is good. The execution — instantiating Sprite2D children — is the part to revisit. A `_draw()`-based implementation would let both classes keep the same public API (`draw_walls_for_layout(MapLayout)` / `draw_fog_for_layout(MapLayout)`) and their `_clear()` calls would become `queue_redraw()`.
|
||||
|
||||
### Combat data flow is solid
|
||||
`PlayerController → StrategyPhase → CombatSystem ← CombatUI` is still clean. The remaining issues in this area (A, B, C, D) are all edge cases at the boundaries, not architectural problems.
|
||||
|
||||
### The missing piece is still turn/movement
|
||||
Findings 2 and 9 from the prior review are the largest gameplay gap. Until that exists, "combat" is "pick two units anywhere on the map and click." Worth scoping a small turn manager (`turn_started(unit)`, `turn_ended(unit)`, `action_points_remaining`) before adding more combat features on top of the current freeform model.
|
||||
|
||||
### No tests
|
||||
`CombatSystem` is pure logic that takes resources and returns a proposal — it's the easiest thing in the codebase to test, and the proposal/apply path is the most fragile. A few GdUnit4 tests on combat math would catch most of A/B/C/D as regressions.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| # | Finding | Priority | Effort |
|
||||
|---|---------|----------|--------|
|
||||
| A | `apply_proposal` no `is_instance_valid` | ~~High~~ | **Done** |
|
||||
| B | No allegiance check | High | Small |
|
||||
| C | `queue_free` dangling refs | High | Small |
|
||||
| D | `atk_range` not enforced pre-request | High | Small |
|
||||
| E | Greedy axis movement, no pathfinding | Medium | Medium |
|
||||
| F | Duplicated unit-tracking | Medium | Medium |
|
||||
| G | `remove_unit` leaks signal connections | Medium | Small |
|
||||
| H | Dead `process_combat` | ~~Medium~~ | **Done** |
|
||||
| I | O(n) unit lookup | Medium | Small |
|
||||
| J | Sprite-per-tile renderers | ~~Medium~~ | **Done** |
|
||||
| K | `MapLayout.initialize` not guarded | Medium | Small |
|
||||
| L | Highlight updates while modal open | ~~Medium~~ | **Not a bug** |
|
||||
| M | Inline scripts in `unit.tscn` | Medium | Small |
|
||||
| N | `_draw_outer_corners` stub | Low | Medium |
|
||||
| O | Redundant `match` in wall renderer | ~~Low~~ | **Done** |
|
||||
| P | Magic `FOG_RECT` constant | Low | Trivial |
|
||||
| Q | Jolt 3D in 2D project | Low | Trivial |
|
||||
| R | `UnitInfo.name` shadows | Low | Trivial |
|
||||
| S | Dead fallback paths in `CombatMap` | ~~Low~~ | **Done** |
|
||||
667
docs/superpowers/plans/2026-04-04-combat-tactics.md
Normal file
@@ -0,0 +1,667 @@
|
||||
# Combat Tactics 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:** Add selectable combat tactics to the combat proposal system so units can choose between different attacks (and Defend) during combat.
|
||||
|
||||
**Architecture:** Polymorphic resource hierarchy for both tactics and range checking. Tactics encapsulate their own stat logic via virtual methods. The combat system delegates stat resolution to tactics rather than interpreting configuration. The UI adds an OptionButton per combatant for tactic selection.
|
||||
|
||||
**Tech Stack:** Godot 4.6, GDScript
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-04-04-combat-tactics-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
### New Files
|
||||
| File | Responsibility |
|
||||
|------|---------------|
|
||||
| `resources/resource_definitions/combat_tactic_range.gd` | Base range class with `is_valid_range()` virtual method |
|
||||
| `resources/resource_definitions/fixed_combat_tactic_range.gd` | Range check against a fixed tile distance |
|
||||
| `resources/resource_definitions/any_combat_tactic_range.gd` | Always-valid range (for Defend) |
|
||||
| `resources/resource_definitions/unit_matching_combat_tactic_range.gd` | Range check against the unit's `atk_range` stat |
|
||||
| `resources/resource_definitions/combat_tactic.gd` | Base tactic class with `get_offensive_stats()`, `get_relevant_defense()`, `deals_damage()` |
|
||||
| `resources/resource_definitions/attack_combat_tactic.gd` | Physical attack tactic (uses phys_atk/phys_def) |
|
||||
| `resources/resource_definitions/defend_combat_tactic.gd` | No-attack tactic |
|
||||
|
||||
### Modified Files
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `nodes/unit.gd` | Add `@export var tactics` array, append built-in Attack/Defend at `_ready()` |
|
||||
| `resources/resource_definitions/combat_proposal.gd` | Add `available_tactics`, `selected_tactic` to `CombatantStats` |
|
||||
| `nodes/combat_system.gd` | Tactic-aware `create_proposal()`, new `update_tactic()`, AI selection, tactic-aware `apply_proposal()` |
|
||||
| `scripts/combat_ui.gd` | Add OptionButton per side, wire tactic changes to `CombatSystem.update_tactic()`, refresh stats |
|
||||
| `prefabs/combat_ui.tscn` | Add OptionButton nodes to attacker/defender stat columns |
|
||||
| `nodes/strategy_phase.gd` | Pass `combat_system` reference to `combat_ui`, pass distance to `create_proposal()` |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: CombatTacticRange Hierarchy
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/resource_definitions/combat_tactic_range.gd`
|
||||
- Create: `resources/resource_definitions/fixed_combat_tactic_range.gd`
|
||||
- Create: `resources/resource_definitions/any_combat_tactic_range.gd`
|
||||
- Create: `resources/resource_definitions/unit_matching_combat_tactic_range.gd`
|
||||
|
||||
- [ ] **Step 1: Create the base CombatTacticRange class**
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/combat_tactic_range.gd
|
||||
class_name CombatTacticRange extends Resource
|
||||
|
||||
func is_valid_range(distance: int, unit: Unit) -> bool:
|
||||
return false
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create FixedCombatTacticRange**
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/fixed_combat_tactic_range.gd
|
||||
class_name FixedCombatTacticRange extends CombatTacticRange
|
||||
|
||||
@export var tactic_range: int = 1
|
||||
|
||||
func is_valid_range(distance: int, unit: Unit) -> bool:
|
||||
return distance <= tactic_range
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create AnyCombatTacticRange**
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/any_combat_tactic_range.gd
|
||||
class_name AnyCombatTacticRange extends CombatTacticRange
|
||||
|
||||
func is_valid_range(distance: int, unit: Unit) -> bool:
|
||||
return true
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Create UnitMatchingCombatTacticRange**
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/unit_matching_combat_tactic_range.gd
|
||||
class_name UnitMatchingCombatTacticRange extends CombatTacticRange
|
||||
|
||||
func is_valid_range(distance: int, unit: Unit) -> bool:
|
||||
return distance <= unit.current_stats.atk_range
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify in Godot**
|
||||
|
||||
Open the project in the Godot editor and confirm all four scripts load without errors in the Output panel.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add resources/resource_definitions/combat_tactic_range.gd resources/resource_definitions/fixed_combat_tactic_range.gd resources/resource_definitions/any_combat_tactic_range.gd resources/resource_definitions/unit_matching_combat_tactic_range.gd
|
||||
git commit -m "feat: add CombatTacticRange hierarchy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: CombatTactic Base Class and Subclasses
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/resource_definitions/combat_tactic.gd`
|
||||
- Create: `resources/resource_definitions/attack_combat_tactic.gd`
|
||||
- Create: `resources/resource_definitions/defend_combat_tactic.gd`
|
||||
|
||||
- [ ] **Step 1: Create the base CombatTactic class**
|
||||
|
||||
Note: `tactic_name` is used instead of `name` to avoid shadowing `Resource.name`. Subclasses override all three virtual methods.
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/combat_tactic.gd
|
||||
class_name CombatTactic extends Resource
|
||||
|
||||
@export var tactic_name: String = ""
|
||||
@export var tactic_range: CombatTacticRange
|
||||
|
||||
func get_offensive_stats(unit: Unit) -> Variant:
|
||||
return null
|
||||
|
||||
func get_relevant_defense(unit: Unit) -> int:
|
||||
return unit.current_stats.phys_def
|
||||
|
||||
func deals_damage() -> bool:
|
||||
return false
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create AttackCombatTactic**
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/attack_combat_tactic.gd
|
||||
class_name AttackCombatTactic extends CombatTactic
|
||||
|
||||
func get_offensive_stats(unit: Unit) -> Variant:
|
||||
return {"atk": unit.current_stats.phys_atk, "hit": unit.current_stats.hit}
|
||||
|
||||
func get_relevant_defense(unit: Unit) -> int:
|
||||
return unit.current_stats.phys_def
|
||||
|
||||
func deals_damage() -> bool:
|
||||
return true
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create DefendCombatTactic**
|
||||
|
||||
```gdscript
|
||||
# resources/resource_definitions/defend_combat_tactic.gd
|
||||
class_name DefendCombatTactic extends CombatTactic
|
||||
|
||||
func get_offensive_stats(unit: Unit) -> Variant:
|
||||
return null
|
||||
|
||||
func get_relevant_defense(unit: Unit) -> int:
|
||||
return unit.current_stats.phys_def
|
||||
|
||||
func deals_damage() -> bool:
|
||||
return false
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify in Godot**
|
||||
|
||||
Open the Godot editor and confirm all three scripts load without errors. In the inspector, verify that creating a new `AttackCombatTactic` resource shows `tactic_name` and `tactic_range` as exported properties.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add resources/resource_definitions/combat_tactic.gd resources/resource_definitions/attack_combat_tactic.gd resources/resource_definitions/defend_combat_tactic.gd
|
||||
git commit -m "feat: add CombatTactic base class with Attack and Defend subclasses"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Unit Tactic Assignment
|
||||
|
||||
**Files:**
|
||||
- Modify: `nodes/unit.gd`
|
||||
|
||||
- [ ] **Step 1: Add tactics export and built-in appending**
|
||||
|
||||
In `nodes/unit.gd`, add the export var after the existing template exports (after line 8), and modify `_ready()` to append built-in tactics:
|
||||
|
||||
```gdscript
|
||||
# After the existing @export vars (line 8):
|
||||
@export var tactics: Array[CombatTactic] = []
|
||||
```
|
||||
|
||||
Replace the existing `_ready()` function (lines 16-19) with:
|
||||
|
||||
```gdscript
|
||||
func _ready() -> void:
|
||||
current_stats = stat_template.duplicate(true)
|
||||
current_info = info_template.duplicate(true)
|
||||
current_allegiance = allegiance_template.duplicate(true)
|
||||
_append_builtin_tactics()
|
||||
unit_allegiance_changed.emit(self, current_allegiance)
|
||||
```
|
||||
|
||||
Add the new helper method after `_ready()`:
|
||||
|
||||
```gdscript
|
||||
func _append_builtin_tactics() -> void:
|
||||
var attack := AttackCombatTactic.new()
|
||||
attack.tactic_name = "Attack"
|
||||
attack.tactic_range = UnitMatchingCombatTacticRange.new()
|
||||
tactics.append(attack)
|
||||
|
||||
var defend := DefendCombatTactic.new()
|
||||
defend.tactic_name = "Defend"
|
||||
defend.tactic_range = AnyCombatTacticRange.new()
|
||||
tactics.append(defend)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in Godot**
|
||||
|
||||
Run the game. Units should initialize without errors. Add a temporary `print(tactics)` at the end of `_ready()` to confirm each unit has Attack and Defend in their tactics list. Remove the print after verifying.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add nodes/unit.gd
|
||||
git commit -m "feat: add tactics list to Unit with built-in Attack and Defend"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: CombatProposal Tactic Support
|
||||
|
||||
**Files:**
|
||||
- Modify: `resources/resource_definitions/combat_proposal.gd`
|
||||
|
||||
- [ ] **Step 1: Add tactic fields to CombatantStats**
|
||||
|
||||
Replace the entire file content with:
|
||||
|
||||
```gdscript
|
||||
class_name CombatProposal extends Resource
|
||||
|
||||
class CombatantStats:
|
||||
var unit: Unit
|
||||
var max_hp: int
|
||||
var hp: int
|
||||
var sp: int
|
||||
var hit: int
|
||||
var atk: int
|
||||
var def: int
|
||||
var spd: int
|
||||
var available_tactics: Array[CombatTactic] = []
|
||||
var selected_tactic: CombatTactic
|
||||
|
||||
var attacker: CombatantStats
|
||||
var defender: CombatantStats
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in Godot**
|
||||
|
||||
Open the Godot editor and confirm no errors. The existing `combat_ui.gd` and `combat_system.gd` still reference the same fields (`atk`, `def`, `hit`, etc.) so nothing should break yet.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add resources/resource_definitions/combat_proposal.gd
|
||||
git commit -m "feat: add available_tactics and selected_tactic to CombatantStats"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: CombatSystem — Tactic-Aware Proposal Creation
|
||||
|
||||
**Files:**
|
||||
- Modify: `nodes/combat_system.gd`
|
||||
|
||||
This task rewrites the combat system to use tactics for stat resolution. The `create_proposal` method gains a `distance` parameter and filters/selects tactics.
|
||||
|
||||
- [ ] **Step 1: Rewrite create_proposal and _snapshot**
|
||||
|
||||
Replace the entire content of `nodes/combat_system.gd` with:
|
||||
|
||||
```gdscript
|
||||
class_name CombatSystem extends Node
|
||||
|
||||
func create_proposal(attacker: Unit, defender: Unit, distance: int) -> CombatProposal:
|
||||
var proposal := CombatProposal.new()
|
||||
|
||||
var atk_tactics := _filter_tactics(attacker, distance)
|
||||
var def_tactics := _filter_tactics(defender, distance)
|
||||
|
||||
var atk_tactic := _find_default_attack(atk_tactics)
|
||||
var def_tactic := _find_default_attack(def_tactics)
|
||||
|
||||
proposal.attacker = _snapshot(attacker, defender, atk_tactics, atk_tactic, def_tactic)
|
||||
proposal.defender = _snapshot(defender, attacker, def_tactics, def_tactic, atk_tactic)
|
||||
|
||||
return proposal
|
||||
|
||||
|
||||
func _filter_tactics(unit: Unit, distance: int) -> Array[CombatTactic]:
|
||||
var valid: Array[CombatTactic] = []
|
||||
for tactic in unit.tactics:
|
||||
if tactic.tactic_range and tactic.tactic_range.is_valid_range(distance, unit):
|
||||
valid.append(tactic)
|
||||
return valid
|
||||
|
||||
|
||||
func _find_default_attack(tactics: Array[CombatTactic]) -> CombatTactic:
|
||||
for tactic in tactics:
|
||||
if tactic is AttackCombatTactic:
|
||||
return tactic
|
||||
return tactics[0] if tactics.size() > 0 else null
|
||||
|
||||
|
||||
func _snapshot(unit: Unit, opponent: Unit, available: Array[CombatTactic], selected: CombatTactic, opponent_selected: CombatTactic) -> CombatProposal.CombatantStats:
|
||||
var stats := CombatProposal.CombatantStats.new()
|
||||
stats.unit = unit
|
||||
stats.max_hp = unit.current_stats.max_hp
|
||||
stats.hp = unit.current_stats.current_hp
|
||||
stats.sp = unit.current_stats.current_sp
|
||||
stats.spd = unit.current_stats.spd
|
||||
stats.available_tactics = available
|
||||
stats.selected_tactic = selected
|
||||
|
||||
if selected and selected.deals_damage():
|
||||
var offensive := selected.get_offensive_stats(unit)
|
||||
stats.atk = offensive["atk"]
|
||||
stats.hit = offensive["hit"] - opponent.current_stats.eva
|
||||
else:
|
||||
stats.atk = 0
|
||||
stats.hit = 0
|
||||
|
||||
if opponent_selected and opponent_selected.deals_damage():
|
||||
stats.def = opponent_selected.get_relevant_defense(unit)
|
||||
else:
|
||||
stats.def = unit.current_stats.phys_def
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
func update_tactic(proposal: CombatProposal, is_attacker: bool, tactic: CombatTactic) -> void:
|
||||
var self_stats: CombatProposal.CombatantStats
|
||||
var opp_stats: CombatProposal.CombatantStats
|
||||
if is_attacker:
|
||||
self_stats = proposal.attacker
|
||||
opp_stats = proposal.defender
|
||||
else:
|
||||
self_stats = proposal.defender
|
||||
opp_stats = proposal.attacker
|
||||
|
||||
self_stats.selected_tactic = tactic
|
||||
|
||||
# Recalculate this side's offensive stats
|
||||
if tactic and tactic.deals_damage():
|
||||
var offensive := tactic.get_offensive_stats(self_stats.unit)
|
||||
self_stats.atk = offensive["atk"]
|
||||
self_stats.hit = offensive["hit"] - opp_stats.unit.current_stats.eva
|
||||
else:
|
||||
self_stats.atk = 0
|
||||
self_stats.hit = 0
|
||||
|
||||
# Recalculate opponent's def based on this side's new tactic
|
||||
if tactic and tactic.deals_damage():
|
||||
opp_stats.def = tactic.get_relevant_defense(opp_stats.unit)
|
||||
else:
|
||||
opp_stats.def = opp_stats.unit.current_stats.phys_def
|
||||
|
||||
|
||||
func select_ai_tactic(unit: Unit, opponent: Unit, available_tactics: Array[CombatTactic]) -> CombatTactic:
|
||||
var best_tactic: CombatTactic = null
|
||||
var best_damage := -1
|
||||
|
||||
for tactic in available_tactics:
|
||||
if not tactic.deals_damage():
|
||||
continue
|
||||
var offensive := tactic.get_offensive_stats(unit)
|
||||
var defense := tactic.get_relevant_defense(opponent)
|
||||
var damage := maxi(offensive["atk"] - defense, 0)
|
||||
if damage > best_damage:
|
||||
best_damage = damage
|
||||
best_tactic = tactic
|
||||
|
||||
if best_tactic == null or best_damage <= 0:
|
||||
for tactic in available_tactics:
|
||||
if tactic is DefendCombatTactic:
|
||||
return tactic
|
||||
return available_tactics[0] if available_tactics.size() > 0 else null
|
||||
|
||||
return best_tactic
|
||||
|
||||
|
||||
func process_combat(attacker: Unit, defender: Unit, distance: int) -> void:
|
||||
if not attacker.is_alive() or not defender.is_alive():
|
||||
return
|
||||
var proposal := create_proposal(attacker, defender, distance)
|
||||
var atk_name := attacker.current_info.name
|
||||
var def_name := defender.current_info.name
|
||||
print("=== Combat: %s vs %s ===" % [atk_name, def_name])
|
||||
print(" %s — HP:%d ATK:%d DEF:%d HIT:%d" % [atk_name, proposal.attacker.hp, proposal.attacker.atk, proposal.attacker.def, proposal.attacker.hit])
|
||||
print(" %s — HP:%d ATK:%d DEF:%d HIT:%d" % [def_name, proposal.defender.hp, proposal.defender.atk, proposal.defender.def, proposal.defender.hit])
|
||||
apply_proposal(proposal)
|
||||
var atk_hp := attacker.current_stats.current_hp if is_instance_valid(attacker) else 0
|
||||
var def_hp := defender.current_stats.current_hp if is_instance_valid(defender) else 0
|
||||
print(" Result: %s HP=%d, %s HP=%d" % [atk_name, atk_hp, def_name, def_hp])
|
||||
|
||||
|
||||
func apply_proposal(proposal: CombatProposal) -> void:
|
||||
var atk_stats := proposal.attacker
|
||||
var def_stats := proposal.defender
|
||||
var atk_unit := atk_stats.unit
|
||||
var def_unit := def_stats.unit
|
||||
|
||||
# Attacker strikes (if their tactic deals damage)
|
||||
if atk_stats.selected_tactic and atk_stats.selected_tactic.deals_damage():
|
||||
var atk_roll := randi_range(1, 100)
|
||||
if atk_roll <= atk_stats.hit:
|
||||
var damage := maxi(atk_stats.atk - def_stats.def, 0)
|
||||
def_unit.take_damage(damage)
|
||||
|
||||
# Counterattack if defender survives and their tactic deals damage
|
||||
if def_unit.is_alive() and def_stats.selected_tactic and def_stats.selected_tactic.deals_damage():
|
||||
var def_roll := randi_range(1, 100)
|
||||
if def_roll <= def_stats.hit:
|
||||
var damage := maxi(def_stats.atk - atk_stats.def, 0)
|
||||
atk_unit.take_damage(damage)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update StrategyPhase to pass distance**
|
||||
|
||||
In `nodes/strategy_phase.gd`, replace `_on_combat_requested` (lines 22-25) with:
|
||||
|
||||
```gdscript
|
||||
func _on_combat_requested(attacker: Unit, defender: Unit) -> void:
|
||||
var atk_coords := combat_map.world_to_coords(attacker.position)
|
||||
var def_coords := combat_map.world_to_coords(defender.position)
|
||||
var distance := absi(atk_coords.x - def_coords.x) + absi(atk_coords.y - def_coords.y)
|
||||
var proposal := combat_system.create_proposal(attacker, defender, distance)
|
||||
_set_input_disabled(true)
|
||||
combat_ui.show_proposal(proposal)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify in Godot**
|
||||
|
||||
Run the game. Select a unit, click an enemy to trigger combat proposal. The proposal panel should appear with stats displayed as before (Attack is auto-selected, same phys_atk/phys_def values). Confirm and verify combat resolves normally. Check the Output panel for the combat log prints.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add nodes/combat_system.gd nodes/strategy_phase.gd
|
||||
git commit -m "feat: tactic-aware combat proposal creation and resolution"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: AI Tactic Selection Wiring
|
||||
|
||||
**Files:**
|
||||
- Modify: `nodes/combat_system.gd`
|
||||
- Modify: `nodes/strategy_phase.gd`
|
||||
|
||||
- [ ] **Step 1: Wire AI tactic selection into proposal creation**
|
||||
|
||||
In `nodes/combat_system.gd`, modify `create_proposal` to auto-select for non-player units. Replace the `create_proposal` method with:
|
||||
|
||||
```gdscript
|
||||
func create_proposal(attacker: Unit, defender: Unit, distance: int) -> CombatProposal:
|
||||
var proposal := CombatProposal.new()
|
||||
|
||||
var atk_tactics := _filter_tactics(attacker, distance)
|
||||
var def_tactics := _filter_tactics(defender, distance)
|
||||
|
||||
var atk_tactic := _find_default_attack(atk_tactics)
|
||||
var def_tactic := _find_default_attack(def_tactics)
|
||||
|
||||
# AI auto-selects for non-player units
|
||||
if not _is_player_controlled(defender):
|
||||
def_tactic = select_ai_tactic(defender, attacker, def_tactics)
|
||||
|
||||
proposal.attacker = _snapshot(attacker, defender, atk_tactics, atk_tactic, def_tactic)
|
||||
proposal.defender = _snapshot(defender, attacker, def_tactics, def_tactic, atk_tactic)
|
||||
|
||||
return proposal
|
||||
```
|
||||
|
||||
Add the `_is_player_controlled` helper at the bottom of the file:
|
||||
|
||||
```gdscript
|
||||
func _is_player_controlled(unit: Unit) -> bool:
|
||||
return unit.current_allegiance.type == UnitAllegiance.AllegianceType.PLAYER
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in Godot**
|
||||
|
||||
Run the game and initiate combat against an enemy unit. The defender should auto-select their best tactic (Attack with base stats, since no custom tactics exist yet). Add a temporary print in `select_ai_tactic` to confirm it's being called for enemy units: `print("AI selected: ", best_tactic.tactic_name)`. Remove after verifying.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add nodes/combat_system.gd
|
||||
git commit -m "feat: wire AI tactic auto-selection for non-player defenders"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Combat UI — Tactic Selector
|
||||
|
||||
**Files:**
|
||||
- Modify: `prefabs/combat_ui.tscn`
|
||||
- Modify: `scripts/combat_ui.gd`
|
||||
- Modify: `nodes/strategy_phase.gd`
|
||||
|
||||
- [ ] **Step 1: Add OptionButton nodes to the .tscn**
|
||||
|
||||
In `prefabs/combat_ui.tscn`, add an OptionButton node to each side's stat column. Insert after the AttackerNameLabel node (after line 93) and after the DefenderNameLabel node (after line 127).
|
||||
|
||||
Add these nodes to the .tscn file:
|
||||
|
||||
After the `AttackerNameLabel` node (line 93), insert:
|
||||
|
||||
```
|
||||
[node name="AttackerTacticSelect" type="OptionButton" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
```
|
||||
|
||||
After the `DefenderNameLabel` node (line 127, which shifts due to insertion above), insert:
|
||||
|
||||
```
|
||||
[node name="DefenderTacticSelect" type="OptionButton" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update combat_ui.gd with tactic selector references and logic**
|
||||
|
||||
In `scripts/combat_ui.gd`, add the new @onready vars after the existing ones (after line 25):
|
||||
|
||||
```gdscript
|
||||
@onready var atk_tactic_select: OptionButton = %AttackerTacticSelect
|
||||
@onready var def_tactic_select: OptionButton = %DefenderTacticSelect
|
||||
```
|
||||
|
||||
Add a `combat_system` variable that will be set by StrategyPhase (after line 28):
|
||||
|
||||
```gdscript
|
||||
var combat_system: CombatSystem
|
||||
```
|
||||
|
||||
In `_ready()`, connect the OptionButton signals. Add after the `cancel_button.pressed.connect` line (after line 34):
|
||||
|
||||
```gdscript
|
||||
atk_tactic_select.item_selected.connect(_on_atk_tactic_selected)
|
||||
def_tactic_select.item_selected.connect(_on_def_tactic_selected)
|
||||
```
|
||||
|
||||
Replace the `show_proposal` method (lines 77-96) with:
|
||||
|
||||
```gdscript
|
||||
func show_proposal(proposal: CombatProposal) -> void:
|
||||
_current_proposal = proposal
|
||||
_populate_tactic_select(atk_tactic_select, proposal.attacker)
|
||||
_populate_tactic_select(def_tactic_select, proposal.defender)
|
||||
_refresh_stats()
|
||||
background_tint.visible = true
|
||||
proposal_panel.visible = true
|
||||
```
|
||||
|
||||
Add the new helper methods at the bottom of the file:
|
||||
|
||||
```gdscript
|
||||
func _populate_tactic_select(button: OptionButton, combatant: CombatProposal.CombatantStats) -> void:
|
||||
button.clear()
|
||||
var selected_idx := 0
|
||||
for i in combatant.available_tactics.size():
|
||||
var tactic := combatant.available_tactics[i]
|
||||
button.add_item(tactic.tactic_name)
|
||||
if tactic == combatant.selected_tactic:
|
||||
selected_idx = i
|
||||
button.selected = selected_idx
|
||||
# Disable dropdown for AI-controlled units (read-only display)
|
||||
var is_player := combatant.unit.current_allegiance.type == UnitAllegiance.AllegianceType.PLAYER
|
||||
button.disabled = not is_player
|
||||
|
||||
|
||||
func _refresh_stats() -> void:
|
||||
var atk := _current_proposal.attacker
|
||||
var def := _current_proposal.defender
|
||||
atk_name_label.text = atk.unit.current_info.name
|
||||
atk_hp_bar.max_value = atk.max_hp
|
||||
atk_hp_bar.value = atk.hp
|
||||
atk_atk_label.text = "ATK: %d" % atk.atk
|
||||
atk_def_label.text = "DEF: %d" % atk.def
|
||||
atk_hit_label.text = "HIT: %d%%" % atk.hit
|
||||
atk_spd_label.text = "SPD: %d" % atk.spd
|
||||
def_name_label.text = def.unit.current_info.name
|
||||
def_hp_bar.max_value = def.max_hp
|
||||
def_hp_bar.value = def.hp
|
||||
def_atk_label.text = "ATK: %d" % def.atk
|
||||
def_def_label.text = "DEF: %d" % def.def
|
||||
def_hit_label.text = "HIT: %d%%" % def.hit
|
||||
def_spd_label.text = "SPD: %d" % def.spd
|
||||
|
||||
|
||||
func _on_atk_tactic_selected(index: int) -> void:
|
||||
if not _current_proposal or not combat_system:
|
||||
return
|
||||
var tactic := _current_proposal.attacker.available_tactics[index]
|
||||
combat_system.update_tactic(_current_proposal, true, tactic)
|
||||
_refresh_stats()
|
||||
|
||||
|
||||
func _on_def_tactic_selected(index: int) -> void:
|
||||
if not _current_proposal or not combat_system:
|
||||
return
|
||||
var tactic := _current_proposal.defender.available_tactics[index]
|
||||
combat_system.update_tactic(_current_proposal, false, tactic)
|
||||
_refresh_stats()
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Wire combat_system reference in StrategyPhase**
|
||||
|
||||
In `nodes/strategy_phase.gd`, add this line at the end of `_ready()` (after line 14):
|
||||
|
||||
```gdscript
|
||||
combat_ui.combat_system = combat_system
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify in Godot**
|
||||
|
||||
Run the game and trigger a combat proposal:
|
||||
1. Confirm the OptionButton appears on each side showing "Attack" selected
|
||||
2. Open the dropdown — should show "Attack" and "Defend"
|
||||
3. Select "Defend" on the attacker side — ATK and HIT should drop to 0
|
||||
4. Select "Attack" again — stats should restore
|
||||
5. Confirm the defender's dropdown is disabled (greyed out) for enemy units
|
||||
6. Click Fight — combat should resolve correctly using the selected tactics
|
||||
7. Click Cancel — proposal should dismiss normally
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add prefabs/combat_ui.tscn scripts/combat_ui.gd nodes/strategy_phase.gd
|
||||
git commit -m "feat: add tactic selector UI to combat proposal panel"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: End-to-End Verification
|
||||
|
||||
- [ ] **Step 1: Test basic Attack vs Attack**
|
||||
|
||||
Run the game. Select a player unit, click an enemy. Both should default to Attack. Stats should match the pre-tactic behavior (phys_atk, phys_def, hit - eva). Confirm fight, verify damage is applied correctly via the combat log prints.
|
||||
|
||||
- [ ] **Step 2: Test Defend selection**
|
||||
|
||||
Trigger a combat proposal. Switch the attacker to Defend. ATK and HIT should show 0. Confirm fight — the attacker should deal no damage, defender should still counterattack.
|
||||
|
||||
- [ ] **Step 3: Test defender Defend (for player vs player scenario)**
|
||||
|
||||
If possible, set up two player-allegiance units (or temporarily change an enemy's allegiance to PLAYER in the editor). Trigger combat between them. Both dropdowns should be enabled. Set defender to Defend — defender should deal no damage on counterattack.
|
||||
|
||||
- [ ] **Step 4: Test AI auto-selection**
|
||||
|
||||
Trigger combat against an enemy. The enemy's tactic dropdown should show their auto-selected tactic (Attack, since it's the only damage-dealing option) and be disabled/greyed out.
|
||||
|
||||
- [ ] **Step 5: Verify no regressions**
|
||||
|
||||
Move units around the map, select/deselect, trigger multiple combats in sequence. Verify no errors in the Output panel. Verify unit death still works (HP drops to 0, unit removed).
|
||||
389
docs/superpowers/plans/2026-04-05-f1-debug-menu.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# F1 Debug Menu 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:** Add an F1-toggled debug overlay with scene swapping and a command console that works regardless of the active scene.
|
||||
|
||||
**Architecture:** A new `game.tscn` root scene hosts a `DebugMenu` CanvasLayer and an `ActiveSceneContainer` node. Scenes are loaded as children of the container. The command console uses a command pattern with explicit registration and an Expression eval fallback.
|
||||
|
||||
**Tech Stack:** Godot 4.6, GDScript
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: ConsoleCommand Base Class
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/resource_definitions/console_command.gd`
|
||||
|
||||
- [ ] **Step 1: Create the base class**
|
||||
|
||||
```gdscript
|
||||
class_name ConsoleCommand extends RefCounted
|
||||
|
||||
func get_command_name() -> String:
|
||||
return ""
|
||||
|
||||
func get_help_text() -> String:
|
||||
return ""
|
||||
|
||||
func run(args: Array, context: Dictionary) -> String:
|
||||
return ""
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 2: HelpCommand
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/console_commands/help_command.gd`
|
||||
|
||||
- [ ] **Step 1: Create the command**
|
||||
|
||||
`context["commands"]` will be the `Array[ConsoleCommand]` from DebugMenu.
|
||||
|
||||
```gdscript
|
||||
class_name HelpCommand extends ConsoleCommand
|
||||
|
||||
func get_command_name() -> String:
|
||||
return "help"
|
||||
|
||||
func get_help_text() -> String:
|
||||
return "Lists all available commands"
|
||||
|
||||
func run(args: Array, context: Dictionary) -> String:
|
||||
var commands: Array = context["commands"]
|
||||
var lines: PackedStringArray = []
|
||||
for command: ConsoleCommand in commands:
|
||||
lines.append("%s - %s" % [command.get_command_name(), command.get_help_text()])
|
||||
lines.append("Any other input is evaluated as a GDScript expression.")
|
||||
return "\n".join(lines)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 3: ListScenesCommand
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/console_commands/list_scenes_command.gd`
|
||||
|
||||
- [ ] **Step 1: Create the command**
|
||||
|
||||
`context["scene_registry"]` will be the scene registry array from DebugMenu.
|
||||
|
||||
```gdscript
|
||||
class_name ListScenesCommand extends ConsoleCommand
|
||||
|
||||
func get_command_name() -> String:
|
||||
return "list_scenes"
|
||||
|
||||
func get_help_text() -> String:
|
||||
return "Lists available scenes for swapping"
|
||||
|
||||
func run(args: Array, context: Dictionary) -> String:
|
||||
var registry: Array = context["scene_registry"]
|
||||
var lines: PackedStringArray = []
|
||||
for entry: Dictionary in registry:
|
||||
lines.append(entry["name"])
|
||||
return "\n".join(lines)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 4: SwapCommand
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/console_commands/swap_command.gd`
|
||||
|
||||
- [ ] **Step 1: Create the command**
|
||||
|
||||
`context["debug_menu"]` will be the DebugMenu node reference, which has the `swap_scene()` method.
|
||||
|
||||
```gdscript
|
||||
class_name SwapCommand extends ConsoleCommand
|
||||
|
||||
func get_command_name() -> String:
|
||||
return "swap"
|
||||
|
||||
func get_help_text() -> String:
|
||||
return "swap <name> - Swap to a scene by name (use list_scenes to see options)"
|
||||
|
||||
func run(args: Array, context: Dictionary) -> String:
|
||||
if args.size() == 0:
|
||||
return "Usage: swap <scene name>"
|
||||
|
||||
var search_name := " ".join(args).to_lower()
|
||||
var registry: Array = context["scene_registry"]
|
||||
for entry: Dictionary in registry:
|
||||
if entry["name"].to_lower() == search_name:
|
||||
var debug_menu: Node = context["debug_menu"]
|
||||
debug_menu.call_deferred("swap_scene", entry)
|
||||
return "Swapping to: %s" % entry["name"]
|
||||
|
||||
return "Scene not found: %s" % search_name
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 5: DebugMenu Script
|
||||
|
||||
**Files:**
|
||||
- Create: `nodes/debug_menu.gd`
|
||||
|
||||
- [ ] **Step 1: Create the script**
|
||||
|
||||
```gdscript
|
||||
class_name DebugMenu extends CanvasLayer
|
||||
|
||||
signal scene_swapped
|
||||
signal close_requested
|
||||
|
||||
var active_scene_container: Node
|
||||
|
||||
var scene_registry: Array = [
|
||||
{ "name": "Strategy Phase", "path": "res://scenes/strategy_phase.tscn" },
|
||||
{ "name": "Main Menu", "path": "res://scenes/main_menu.tscn" },
|
||||
{ "name": "Visual Novel", "path": "res://scenes/vn_scene.tscn" },
|
||||
{ "name": "Dialogue", "path": "res://scenes/dialogue_scene.tscn" },
|
||||
]
|
||||
|
||||
var commands: Array[ConsoleCommand] = []
|
||||
|
||||
@onready var panel: PanelContainer = %Panel
|
||||
@onready var scene_list: VBoxContainer = %SceneList
|
||||
@onready var command_input: LineEdit = %CommandInput
|
||||
@onready var result_label: Label = %ResultLabel
|
||||
|
||||
func _ready() -> void:
|
||||
commands = [
|
||||
HelpCommand.new(),
|
||||
ListScenesCommand.new(),
|
||||
SwapCommand.new(),
|
||||
]
|
||||
_build_scene_buttons()
|
||||
command_input.text_submitted.connect(_on_command_submitted)
|
||||
result_label.text = ""
|
||||
|
||||
func _build_scene_buttons() -> void:
|
||||
for child in scene_list.get_children():
|
||||
child.queue_free()
|
||||
for entry: Dictionary in scene_registry:
|
||||
var button := Button.new()
|
||||
button.text = entry["name"]
|
||||
button.pressed.connect(swap_scene.bind(entry))
|
||||
scene_list.add_child(button)
|
||||
|
||||
func swap_scene(entry: Dictionary) -> void:
|
||||
for child in active_scene_container.get_children():
|
||||
child.queue_free()
|
||||
var scene: PackedScene = load(entry["path"])
|
||||
var instance := scene.instantiate()
|
||||
active_scene_container.add_child(instance)
|
||||
if entry.has("setup"):
|
||||
_apply_setup(entry["setup"], instance)
|
||||
close_requested.emit()
|
||||
|
||||
func _apply_setup(setup_key: String, scene_instance: Node) -> void:
|
||||
match setup_key:
|
||||
_:
|
||||
push_warning("Unknown setup key: %s" % setup_key)
|
||||
|
||||
func _on_command_submitted(text: String) -> void:
|
||||
command_input.text = ""
|
||||
if text.strip_edges().is_empty():
|
||||
return
|
||||
var result := _execute_command(text.strip_edges())
|
||||
_show_result(result)
|
||||
|
||||
func _execute_command(input: String) -> String:
|
||||
var parts := input.split(" ", false)
|
||||
var command_name := parts[0]
|
||||
var args: Array = []
|
||||
if parts.size() > 1:
|
||||
args = Array(parts.slice(1))
|
||||
|
||||
var context := {
|
||||
"commands": commands,
|
||||
"scene_registry": scene_registry,
|
||||
"debug_menu": self,
|
||||
"active_scene_container": active_scene_container,
|
||||
}
|
||||
|
||||
for command: ConsoleCommand in commands:
|
||||
if command.get_command_name() == command_name:
|
||||
return command.run(args, context)
|
||||
|
||||
return _eval_expression(input)
|
||||
|
||||
func _eval_expression(input: String) -> String:
|
||||
var expression := Expression.new()
|
||||
var error := expression.parse(input)
|
||||
if error != OK:
|
||||
return "Parse error: %s" % expression.get_error_text()
|
||||
|
||||
var active_scene: Node = null
|
||||
if active_scene_container.get_child_count() > 0:
|
||||
active_scene = active_scene_container.get_child(0)
|
||||
|
||||
var result = expression.execute([], active_scene)
|
||||
if expression.has_execute_failed():
|
||||
return "Error: %s" % expression.get_error_text()
|
||||
|
||||
return str(result)
|
||||
|
||||
func _show_result(text: String) -> void:
|
||||
result_label.text = text
|
||||
var tween := create_tween()
|
||||
tween.tween_interval(2.0)
|
||||
tween.tween_callback(func(): result_label.text = "")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 6: DebugMenu Scene (Prefab)
|
||||
|
||||
**Files:**
|
||||
- Create: `prefabs/debug_menu.tscn`
|
||||
|
||||
- [ ] **Step 1: Build the scene in the Godot editor**
|
||||
|
||||
Create `prefabs/debug_menu.tscn` with this node tree:
|
||||
|
||||
```
|
||||
DebugMenu (CanvasLayer, layer = 100, script = res://nodes/debug_menu.gd)
|
||||
└── Panel (PanelContainer, unique name ✓)
|
||||
├── anchors: full rect (anchor_left=0, anchor_top=0, anchor_right=1, anchor_bottom=1)
|
||||
└── MarginContainer
|
||||
└── VBoxContainer
|
||||
├── Label (text = "Debug Menu", horizontal_alignment = CENTER)
|
||||
├── HSeparator
|
||||
├── Label (text = "Scenes:")
|
||||
├── SceneList (VBoxContainer, unique name ✓)
|
||||
├── HSeparator
|
||||
├── Label (text = "Console:")
|
||||
├── CommandInput (LineEdit, unique name ✓, placeholder_text = "Enter command or expression...")
|
||||
└── ResultLabel (Label, unique name ✓, text = "")
|
||||
```
|
||||
|
||||
Make sure to:
|
||||
- Set `DebugMenu` CanvasLayer `layer` property to `100`
|
||||
- Attach `res://nodes/debug_menu.gd` as the script
|
||||
- Use "Access as Unique Name" (%) on: `Panel`, `SceneList`, `CommandInput`, `ResultLabel`
|
||||
- Set `Panel`'s layout to "Full Rect" so it covers the viewport
|
||||
- Set `CommandInput.placeholder_text` to `"Enter command or expression..."`
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Game Script
|
||||
|
||||
**Files:**
|
||||
- Create: `nodes/game.gd`
|
||||
|
||||
- [ ] **Step 1: Create the script**
|
||||
|
||||
```gdscript
|
||||
class_name Game extends Node
|
||||
|
||||
@onready var debug_menu: DebugMenu = $DebugMenu
|
||||
@onready var active_scene_container: Node = $ActiveSceneContainer
|
||||
|
||||
var _default_scene: PackedScene = preload("res://scenes/strategy_phase.tscn")
|
||||
|
||||
func _ready() -> void:
|
||||
debug_menu.active_scene_container = active_scene_container
|
||||
debug_menu.close_requested.connect(_close_debug_menu)
|
||||
debug_menu.visible = false
|
||||
var instance := _default_scene.instantiate()
|
||||
active_scene_container.add_child(instance)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("debug_toggle"):
|
||||
_toggle_debug_menu()
|
||||
|
||||
func _toggle_debug_menu() -> void:
|
||||
if debug_menu.visible:
|
||||
_close_debug_menu()
|
||||
else:
|
||||
_open_debug_menu()
|
||||
|
||||
func _open_debug_menu() -> void:
|
||||
debug_menu.visible = true
|
||||
active_scene_container.process_mode = Node.PROCESS_MODE_DISABLED
|
||||
|
||||
func _close_debug_menu() -> void:
|
||||
debug_menu.visible = false
|
||||
active_scene_container.process_mode = Node.PROCESS_MODE_INHERIT
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Game Scene and Project Config
|
||||
|
||||
**Files:**
|
||||
- Create: `scenes/game.tscn`
|
||||
- Modify: `project.godot`
|
||||
|
||||
- [ ] **Step 1: Register the F1 input action**
|
||||
|
||||
In Godot editor: Project > Project Settings > Input Map. Add a new action `debug_toggle` mapped to the F1 key.
|
||||
|
||||
Alternatively, add this to `project.godot` under a new `[input]` section:
|
||||
|
||||
```ini
|
||||
[input]
|
||||
|
||||
debug_toggle={
|
||||
"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":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
(Physical keycode `4194332` is F1 in Godot.)
|
||||
|
||||
- [ ] **Step 2: Build the game scene in the Godot editor**
|
||||
|
||||
Create `scenes/game.tscn` with this tree:
|
||||
|
||||
```
|
||||
Game (Node, script = res://nodes/game.gd, process_mode = PROCESS_MODE_ALWAYS)
|
||||
├── DebugMenu (instance of res://prefabs/debug_menu.tscn)
|
||||
└── ActiveSceneContainer (Node)
|
||||
```
|
||||
|
||||
- Set `Game`'s `process_mode` to `PROCESS_MODE_ALWAYS` in the inspector.
|
||||
- Instance `prefabs/debug_menu.tscn` as a child named `DebugMenu`.
|
||||
- Add a plain `Node` child named `ActiveSceneContainer`.
|
||||
|
||||
- [ ] **Step 3: Update project.godot main scene**
|
||||
|
||||
Change `run/main_scene` in `project.godot` to point to `scenes/game.tscn` (use Project > Project Settings > General > Application > Run > Main Scene in the editor, or update the UID reference manually).
|
||||
|
||||
- [ ] **Step 4: Smoke test**
|
||||
|
||||
Run the project. Expected:
|
||||
1. Strategy phase loads as before inside ActiveSceneContainer.
|
||||
2. Press F1 — debug menu overlay appears, game underneath freezes.
|
||||
3. Press F1 again — debug menu hides, game resumes.
|
||||
4. Click a scene button — scene swaps, debug menu closes.
|
||||
5. Type `help` in command input, press Enter — lists commands in result label.
|
||||
6. Type `list_scenes` — shows scene names.
|
||||
7. Type `swap main menu` — swaps to main menu scene.
|
||||
8. Type an expression like `2 + 2` — shows `4`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
320
docs/superpowers/plans/2026-04-05-room-system.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# Room System 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:** Add a room/wall system where rooms are tile groups, walls are derived from room boundaries, openings are explicit doorways, and movement is blocked by walls.
|
||||
|
||||
**Architecture:** Two new Resources (`Room`, `MapLayout`) define the room data. `MapLayout` computes walls from room boundaries and exposes passability queries. `CombatMap` renders walls on tile edges and uses `MapLayout` for movement checks. `PlayerController` is updated to check edge passability instead of tile-level wall checks.
|
||||
|
||||
**Tech Stack:** Godot 4.6 / GDScript
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Create Room Resource
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/resource_definitions/room.gd`
|
||||
|
||||
- [ ] **Step 1: Create the Room resource**
|
||||
|
||||
```gdscript
|
||||
class_name Room extends Resource
|
||||
|
||||
@export var id: int
|
||||
@export var tiles: Array[Vector2i]
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify it loads in the editor**
|
||||
|
||||
Open Godot, create a new Resource, confirm `Room` appears as a type and `id`/`tiles` fields are visible in the inspector.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create MapLayout Resource
|
||||
|
||||
**Files:**
|
||||
- Create: `resources/resource_definitions/map_layout.gd`
|
||||
|
||||
- [ ] **Step 1: Create the MapLayout resource with room data and opening storage**
|
||||
|
||||
```gdscript
|
||||
class_name MapLayout extends Resource
|
||||
|
||||
@export var rooms: Array[Room]
|
||||
@export var openings: Array[Vector2i]
|
||||
## Openings are stored as a flat array of pairs: [from1, to1, from2, to2, ...].
|
||||
## Each consecutive pair of Vector2i values represents a bidirectional doorway
|
||||
## between two adjacent tiles in different rooms.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the tile-to-room lookup cache and initialization**
|
||||
|
||||
```gdscript
|
||||
var _tile_room_map: Dictionary = {}
|
||||
var _opening_set: Dictionary = {}
|
||||
|
||||
|
||||
func initialize() -> void:
|
||||
_tile_room_map.clear()
|
||||
_opening_set.clear()
|
||||
for room in rooms:
|
||||
for tile in room.tiles:
|
||||
_tile_room_map[tile] = room
|
||||
for i in range(0, openings.size(), 2):
|
||||
var a := openings[i]
|
||||
var b := openings[i + 1]
|
||||
_opening_set[_edge_key(a, b)] = true
|
||||
|
||||
|
||||
static func _edge_key(a: Vector2i, b: Vector2i) -> String:
|
||||
if a < b:
|
||||
return "%d,%d-%d,%d" % [a.x, a.y, b.x, b.y]
|
||||
return "%d,%d-%d,%d" % [b.x, b.y, a.x, a.y]
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add the public API methods**
|
||||
|
||||
```gdscript
|
||||
func is_tile_valid(tile: Vector2i) -> bool:
|
||||
return _tile_room_map.has(tile)
|
||||
|
||||
|
||||
func get_room_at(tile: Vector2i) -> Room:
|
||||
return _tile_room_map.get(tile, null)
|
||||
|
||||
|
||||
func is_passable(from: Vector2i, to: Vector2i) -> bool:
|
||||
if not is_tile_valid(from) or not is_tile_valid(to):
|
||||
return false
|
||||
var room_from: Room = _tile_room_map[from]
|
||||
var room_to: Room = _tile_room_map[to]
|
||||
if room_from == room_to:
|
||||
return true
|
||||
return _opening_set.has(_edge_key(from, to))
|
||||
|
||||
|
||||
func get_walls() -> Array:
|
||||
## Returns an array of [Vector2i, Vector2i] pairs representing wall edges.
|
||||
## A wall exists where a room tile borders void or a different room (without an opening).
|
||||
var walls: Array = []
|
||||
var directions := [Vector2i.RIGHT, Vector2i.DOWN, Vector2i.LEFT, Vector2i.UP]
|
||||
var visited_edges: Dictionary = {}
|
||||
|
||||
for room in rooms:
|
||||
for tile in room.tiles:
|
||||
for dir in directions:
|
||||
var neighbor := tile + dir
|
||||
var key := _edge_key(tile, neighbor)
|
||||
if visited_edges.has(key):
|
||||
continue
|
||||
visited_edges[key] = true
|
||||
|
||||
var neighbor_room: Room = _tile_room_map.get(neighbor, null)
|
||||
if neighbor_room == room:
|
||||
continue
|
||||
# Neighbor is void or different room — wall unless opening
|
||||
if not _opening_set.has(key):
|
||||
walls.append([tile, neighbor])
|
||||
return walls
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify it loads in the editor**
|
||||
|
||||
Open Godot, create a new Resource, confirm `MapLayout` appears as a type and `rooms`/`openings` fields are visible.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Integrate MapLayout into CombatMap
|
||||
|
||||
**Files:**
|
||||
- Modify: `nodes/combat_map.gd`
|
||||
|
||||
- [ ] **Step 1: Add MapLayout export and initialization**
|
||||
|
||||
Add after the existing `@export var tile_set: DLTileset` line in `combat_map.gd`:
|
||||
|
||||
```gdscript
|
||||
@export var map_layout: MapLayout
|
||||
```
|
||||
|
||||
Add at the end of the existing `_ready()` function:
|
||||
|
||||
```gdscript
|
||||
if map_layout:
|
||||
map_layout.initialize()
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add passability and validity methods that delegate to MapLayout**
|
||||
|
||||
Add these methods to `combat_map.gd`:
|
||||
|
||||
```gdscript
|
||||
func is_tile_passable(from: Vector2i, to: Vector2i) -> bool:
|
||||
if map_layout:
|
||||
return map_layout.is_passable(from, to)
|
||||
# Fallback: no room system, use legacy wall check
|
||||
return not is_wall(to)
|
||||
|
||||
|
||||
func is_tile_valid(coords: Vector2i) -> bool:
|
||||
if map_layout:
|
||||
return map_layout.is_tile_valid(coords)
|
||||
# Fallback: no room system, any non-wall tile is valid
|
||||
return not is_wall(coords)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add wall rendering from MapLayout**
|
||||
|
||||
Add this method to `combat_map.gd`:
|
||||
|
||||
```gdscript
|
||||
func draw_room_walls() -> void:
|
||||
if not map_layout:
|
||||
return
|
||||
var walls := map_layout.get_walls()
|
||||
for wall in walls:
|
||||
var from_world := coords_to_world(wall[0]) + Vector2(TILE_SIZE / 2, TILE_SIZE / 2)
|
||||
var to_world := coords_to_world(wall[1]) + Vector2(TILE_SIZE / 2, TILE_SIZE / 2)
|
||||
var midpoint := (from_world + to_world) / 2
|
||||
var diff := to_world - from_world
|
||||
var wall_dir := Vector2(-diff.y, diff.x).normalized()
|
||||
var half_len := TILE_SIZE / 2
|
||||
|
||||
var line := Line2D.new()
|
||||
line.add_point(midpoint - wall_dir * half_len)
|
||||
line.add_point(midpoint + wall_dir * half_len)
|
||||
line.width = 4.0
|
||||
line.default_color = Color(0.6, 0.5, 0.4)
|
||||
add_child(line)
|
||||
```
|
||||
|
||||
This draws a line segment on the border between each pair of tiles that has a wall. The line is perpendicular to the direction between the two tiles and spans the full tile edge.
|
||||
|
||||
- [ ] **Step 4: Add floor rendering from MapLayout and call everything from _ready()**
|
||||
|
||||
Add this method to `combat_map.gd`:
|
||||
|
||||
```gdscript
|
||||
func load_from_layout() -> void:
|
||||
if not map_layout:
|
||||
return
|
||||
for room in map_layout.rooms:
|
||||
for tile in room.tiles:
|
||||
draw_floor(tile)
|
||||
```
|
||||
|
||||
Update the `_ready()` addition so it renders floors then walls:
|
||||
|
||||
```gdscript
|
||||
if map_layout:
|
||||
map_layout.initialize()
|
||||
load_from_layout()
|
||||
draw_room_walls()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Update PlayerController to Use Edge-Based Passability
|
||||
|
||||
**Files:**
|
||||
- Modify: `nodes/player_controller.gd`
|
||||
|
||||
- [ ] **Step 1: Update movement check in `_physics_process`**
|
||||
|
||||
In `player_controller.gd`, find the movement blocking check in `_physics_process` (around line 121-123):
|
||||
|
||||
```gdscript
|
||||
if dl_map.is_wall(grid_coords):
|
||||
_goal_pos = _selected_unit.position
|
||||
return
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```gdscript
|
||||
var current_coords := dl_map.world_to_coords(_selected_unit.position)
|
||||
if not dl_map.is_tile_passable(current_coords, grid_coords):
|
||||
_goal_pos = _selected_unit.position
|
||||
return
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update click destination check in `_handle_left_click`**
|
||||
|
||||
In `player_controller.gd`, find the wall check in `_handle_left_click` (around line 142-143):
|
||||
|
||||
```gdscript
|
||||
if dl_map.is_wall(grid_coords):
|
||||
return
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```gdscript
|
||||
if not dl_map.is_tile_valid(grid_coords):
|
||||
return
|
||||
```
|
||||
|
||||
This prevents clicking on void tiles as a destination. The step-by-step movement in `_physics_process` handles wall/opening checks as the unit walks.
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Create a Test Map Layout
|
||||
|
||||
**Files:**
|
||||
- Modify: `nodes/strategy_phase.gd`
|
||||
|
||||
- [ ] **Step 1: Add test layout setup in strategy_phase.gd**
|
||||
|
||||
Add at the top of `_ready()` in `strategy_phase.gd`, before existing code:
|
||||
|
||||
```gdscript
|
||||
# -- Test room layout (remove once map editor exists) --
|
||||
var room_a := Room.new()
|
||||
room_a.id = 0
|
||||
room_a.tiles = [
|
||||
Vector2i(0, 0), Vector2i(1, 0), Vector2i(2, 0),
|
||||
Vector2i(0, 1), Vector2i(1, 1), Vector2i(2, 1),
|
||||
Vector2i(0, 2), Vector2i(1, 2), Vector2i(2, 2),
|
||||
]
|
||||
|
||||
var room_b := Room.new()
|
||||
room_b.id = 1
|
||||
room_b.tiles = [
|
||||
Vector2i(3, 0), Vector2i(4, 0), Vector2i(5, 0),
|
||||
Vector2i(3, 1), Vector2i(4, 1), Vector2i(5, 1),
|
||||
Vector2i(3, 2), Vector2i(4, 2), Vector2i(5, 2),
|
||||
]
|
||||
|
||||
var layout := MapLayout.new()
|
||||
layout.rooms = [room_a, room_b]
|
||||
# Opening between (2,1) in room_a and (3,1) in room_b
|
||||
layout.openings = [Vector2i(2, 1), Vector2i(3, 1)]
|
||||
|
||||
combat_map.map_layout = layout
|
||||
# -- End test room layout --
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the game and verify**
|
||||
|
||||
Run the strategy phase scene. Expected:
|
||||
- Two 3x3 rooms appear side by side
|
||||
- Walls are drawn on all outer edges and on the room-to-room border
|
||||
- The middle row (y=1) has no wall between tiles (2,1) and (3,1) — that's the opening
|
||||
- A unit can walk through the opening but not through the walls
|
||||
- Clicking on void tiles (outside rooms) does nothing
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Review All Changes
|
||||
|
||||
**Files:**
|
||||
- All modified files
|
||||
|
||||
- [ ] **Step 1: Verify completeness**
|
||||
|
||||
Confirm:
|
||||
- `Room` resource has `id` and `tiles`
|
||||
- `MapLayout` resource has `rooms`, `openings`, `initialize()`, `is_passable()`, `is_tile_valid()`, `get_room_at()`, `get_walls()`
|
||||
- `CombatMap` has `map_layout` export, `is_tile_passable()`, `is_tile_valid()`, `draw_room_walls()`, `load_from_layout()`
|
||||
- `PlayerController` uses `is_tile_passable()` for movement and `is_tile_valid()` for click validation
|
||||
- Legacy `is_wall()` still works as fallback when no `map_layout` is set
|
||||
169
docs/superpowers/specs/2026-04-04-combat-tactics-design.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Combat Tactics System Design
|
||||
|
||||
## Overview
|
||||
|
||||
Units gain a list of selectable combat tactics that modify how they participate in combat. Tactics encapsulate their own stat logic — each tactic knows how to produce offensive stats and determine the relevant defensive stat, rather than exposing configuration for the combat system to interpret. Both attacker and defender select a tactic during the combat proposal phase, with AI auto-selecting for non-player units.
|
||||
|
||||
## Data Model
|
||||
|
||||
### CombatTactic Base Class
|
||||
|
||||
A new base `CombatTactic` resource (`resources/resource_definitions/combat_tactic.gd`):
|
||||
|
||||
**Properties:**
|
||||
- `name: String` — display name (e.g., "Attack", "Heavy Strike", "Fireball", "Defend")
|
||||
- `range: CombatTacticRange` — exported resource determining range validity (see below)
|
||||
|
||||
**Methods (virtual, overridden by subclasses):**
|
||||
- `func get_offensive_stats(unit: Unit) -> Dictionary` — returns `{"atk": int, "hit": int}` with the tactic's effective offensive values pulled from the unit's stats and modified as needed. Returns `null` if the tactic does not attack (e.g., Defend).
|
||||
- `func get_relevant_defense(unit: Unit) -> int` — returns the defense value from the given unit that applies against this tactic's attack. E.g., a physical tactic returns `unit.current_stats.phys_def`, a magical one returns `unit.current_stats.magic_def`.
|
||||
- `func deals_damage() -> bool` — returns whether this tactic produces an attack. Base implementation returns `get_offensive_stats() != null`. Subclasses like Defend override to return `false`.
|
||||
|
||||
The combat system never interprets damage types or applies modifiers — it just calls these methods.
|
||||
|
||||
### CombatTactic Subclasses
|
||||
|
||||
**`AttackCombatTactic`** extends `CombatTactic`:
|
||||
- `get_offensive_stats(unit)` returns `{"atk": unit.current_stats.phys_atk, "hit": unit.current_stats.hit}`
|
||||
- `get_relevant_defense(unit)` returns `unit.current_stats.phys_def`
|
||||
|
||||
**`DefendCombatTactic`** extends `CombatTactic`:
|
||||
- `get_offensive_stats(unit)` returns `null`
|
||||
- `deals_damage()` returns `false`
|
||||
- `get_relevant_defense(unit)` returns `unit.current_stats.phys_def` (default for display; unused in resolution)
|
||||
|
||||
Future subclasses (not built now, examples for illustration):
|
||||
- `MagicAttackCombatTactic` — pulls `magic_atk`/`magic_def`, could apply modifiers
|
||||
- `HeavyAttackCombatTactic` — pulls `phys_atk + 5` and `hit - 20`
|
||||
|
||||
### CombatTacticRange Hierarchy
|
||||
|
||||
Range checking is polymorphic via a resource subclass hierarchy:
|
||||
|
||||
**`CombatTacticRange`** (base Resource, `resources/resource_definitions/combat_tactic_range.gd`):
|
||||
- `func is_valid_range(distance: int, unit: Unit) -> bool` — abstract, returns false by default
|
||||
|
||||
**`FixedCombatTacticRange`** extends `CombatTacticRange`:
|
||||
- `tactic_range: int` — the fixed max range in tiles
|
||||
- Returns `distance <= tactic_range`
|
||||
|
||||
**`AnyCombatTacticRange`** extends `CombatTacticRange`:
|
||||
- Always returns `true`
|
||||
|
||||
**`UnitMatchingCombatTacticRange`** extends `CombatTacticRange`:
|
||||
- Returns `distance <= unit.current_stats.atk_range`
|
||||
|
||||
### Built-in Tactics
|
||||
|
||||
Two built-in tactic instances, always available to all units:
|
||||
|
||||
- **Attack** — `AttackCombatTactic` with `range: UnitMatchingCombatTacticRange`
|
||||
- **Defend** — `DefendCombatTactic` with `range: AnyCombatTacticRange`
|
||||
|
||||
These are instantiated in code (not `.tres` files) since they use specific subclasses. Future tactics with more complex behavior would also be subclasses; a generic parameterized subclass can be added later when patterns emerge across many similar tactics.
|
||||
|
||||
## Unit Changes
|
||||
|
||||
### Tactic Assignment
|
||||
|
||||
`Unit` gains:
|
||||
- `@export var tactics: Array[CombatTactic]` — additional tactics beyond the built-ins (e.g., Fireball, Heavy Strike)
|
||||
- At `_ready()`, Attack and Defend are appended to the unit's tactic list automatically. Custom tactics from the export list are kept as-is. This guarantees every unit always has Attack and Defend.
|
||||
|
||||
## CombatProposal Changes
|
||||
|
||||
### CombatantStats Additions
|
||||
|
||||
`CombatantStats` gains:
|
||||
- `available_tactics: Array[CombatTactic]` — tactics filtered to those valid for the combat distance
|
||||
- `selected_tactic: CombatTactic` — the chosen tactic (defaults to Attack)
|
||||
|
||||
The stat snapshot (`atk`, `def`, `hit`, etc.) is produced by calling the selected tactic's methods:
|
||||
- `atk` and `hit` come from `selected_tactic.get_offensive_stats(unit)` (null if the tactic doesn't attack)
|
||||
- `def` comes from the **opponent's** `selected_tactic.get_relevant_defense(this_unit)` — the defense stat used is determined by what attack is incoming, not what this unit is doing
|
||||
- When the opponent's tactic doesn't deal damage, `def` defaults to `phys_def` for display purposes
|
||||
|
||||
### Stat Recalculation
|
||||
|
||||
When a tactic is changed on either side:
|
||||
1. The combatant's offensive stats recalculate via `new_tactic.get_offensive_stats(unit)`
|
||||
2. The opponent's defensive stat recalculates via `new_tactic.get_relevant_defense(opponent)`
|
||||
3. Both sides' snapshots update accordingly
|
||||
|
||||
## CombatSystem Changes
|
||||
|
||||
### Proposal Creation
|
||||
|
||||
`create_proposal(attacker, defender)` expands to:
|
||||
1. Calculate tile distance between attacker and defender
|
||||
2. For each unit, filter their tactics list by `tactic.range.is_valid_range(distance, unit)`
|
||||
3. Default-select Attack for both sides
|
||||
4. Snapshot stats with Attack's modifiers applied (none for the base case)
|
||||
5. For AI combatants, auto-select optimal tactic (see AI Selection below)
|
||||
|
||||
### New Method: update_tactic
|
||||
|
||||
`update_tactic(proposal, is_attacker: bool, tactic: CombatTactic)`:
|
||||
- Sets the combatant's `selected_tactic`
|
||||
- Recalculates that combatant's offensive stats via `tactic.get_offensive_stats(unit)`
|
||||
- Recalculates the opponent's defensive stat via `tactic.get_relevant_defense(opponent)`
|
||||
- Called by the UI when the player picks a different tactic
|
||||
|
||||
### Combat Resolution
|
||||
|
||||
`apply_proposal()` changes:
|
||||
- If a combatant's `selected_tactic.deals_damage()` returns `false`, skip their attack entirely (no hit roll, no damage)
|
||||
- Otherwise, resolution works as before: roll against `hit`, apply `atk - def` damage
|
||||
- The stats in the snapshot are already produced by the tactic's methods, so resolution logic stays simple
|
||||
|
||||
## AI Tactic Selection
|
||||
|
||||
For non-player combatants, auto-select the tactic that maximizes predicted damage:
|
||||
|
||||
1. For each available tactic where `tactic.deals_damage()` is true:
|
||||
- Get offensive stats via `tactic.get_offensive_stats(unit)`
|
||||
- Get opponent's relevant defense via `tactic.get_relevant_defense(opponent)`
|
||||
- Calculate effective damage: `offensive.atk - defense`
|
||||
2. Pick the tactic with the highest damage
|
||||
3. If all tactics result in 0 or negative damage, select Defend
|
||||
4. This logic should be isolated in its own method for easy future modification (e.g., factoring in hit chance later)
|
||||
|
||||
## Combat UI Changes
|
||||
|
||||
### Tactic Selector
|
||||
|
||||
Each combatant's side in the proposal panel gains a tactic dropdown/list:
|
||||
- Shows `available_tactics` by name
|
||||
- Defaults to Attack
|
||||
- On selection change, calls `CombatSystem.update_tactic()` and refreshes the displayed stats
|
||||
- For AI combatants, the selector is shown as read-only so the player can see what the AI chose
|
||||
|
||||
### Existing UI
|
||||
|
||||
- Stat labels (ATK, DEF, HIT, SPD) continue working as-is — they read from the snapshot which now includes tactic modifiers
|
||||
- Fight/cancel buttons and overall flow unchanged
|
||||
- The selected tactic name should be visible for each combatant
|
||||
|
||||
## File Changes Summary
|
||||
|
||||
### New Files
|
||||
- `resources/resource_definitions/combat_tactic.gd` — CombatTactic base class
|
||||
- `resources/resource_definitions/attack_combat_tactic.gd` — AttackCombatTactic subclass
|
||||
- `resources/resource_definitions/defend_combat_tactic.gd` — DefendCombatTactic subclass
|
||||
- `resources/resource_definitions/combat_tactic_range.gd` — base range class
|
||||
- `resources/resource_definitions/fixed_combat_tactic_range.gd`
|
||||
- `resources/resource_definitions/any_combat_tactic_range.gd`
|
||||
- `resources/resource_definitions/unit_matching_combat_tactic_range.gd`
|
||||
|
||||
### Modified Files
|
||||
- `nodes/unit.gd` — add tactics export, append built-in Attack/Defend at ready
|
||||
- `resources/resource_definitions/combat_proposal.gd` — add available_tactics, selected_tactic to CombatantStats
|
||||
- `nodes/combat_system.gd` — distance calculation, tactic filtering, update_tactic method, AI selection, tactic-driven stat resolution
|
||||
- `scripts/combat_ui.gd` — tactic selector UI elements, refresh on tactic change
|
||||
- `prefabs/combat_ui.tscn` — add tactic selector nodes to proposal panel
|
||||
|
||||
## Out of Scope
|
||||
- SP/resource costs for tactics
|
||||
- Healing tactics (targeting allies) — future work, would be a new CombatTactic subclass
|
||||
- Generic parameterized tactic subclass (for when many tactics share a pattern)
|
||||
- Complex AI (hit chance weighting, situational awareness)
|
||||
145
docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# F1 Debug Menu Design
|
||||
|
||||
## Overview
|
||||
|
||||
An F1-toggled debug menu overlay that works regardless of the current scene. Introduces a new root scene (`game.tscn`) that hosts both the debug menu and the active game scene as children. The game pauses underneath while the debug menu is open.
|
||||
|
||||
## Scene Tree Structure
|
||||
|
||||
```
|
||||
Game (Node, process_mode = PROCESS_MODE_ALWAYS)
|
||||
├── DebugMenu (CanvasLayer, layer 100)
|
||||
│ ├── Panel (PanelContainer)
|
||||
│ │ ├── SceneList (VBoxContainer of Buttons)
|
||||
│ │ └── CommandInput (LineEdit)
|
||||
│ └── ResultLabel (Label, brief flash for command output)
|
||||
└── ActiveSceneContainer (Node)
|
||||
└── <currently loaded scene>
|
||||
```
|
||||
|
||||
- `Game` is the new main scene, replacing `strategy_phase.tscn` as the entry point in `project.godot`.
|
||||
- `DebugMenu` uses a high `CanvasLayer` (layer 100) so it always renders above the active scene.
|
||||
- `ActiveSceneContainer` is a plain `Node` whose child gets swapped when changing scenes.
|
||||
- `Game` has `process_mode = PROCESS_MODE_ALWAYS` so it can receive F1 input even while the tree is paused.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
### Game (`game.gd`)
|
||||
|
||||
Thin shell only:
|
||||
|
||||
- F1 input toggles `DebugMenu` visibility.
|
||||
- When debug menu opens: sets `ActiveSceneContainer.process_mode = PROCESS_MODE_DISABLED`.
|
||||
- When debug menu closes: sets `ActiveSceneContainer.process_mode = PROCESS_MODE_INHERIT`.
|
||||
- Loads the default scene (strategy_phase) into `ActiveSceneContainer` at startup.
|
||||
- Passes a reference to `ActiveSceneContainer` to `DebugMenu` at `_ready()`.
|
||||
|
||||
### DebugMenu (`debug_menu.gd`)
|
||||
|
||||
Owns all debug functionality:
|
||||
|
||||
- **Scene registry**: hardcoded array of scene entries.
|
||||
- **Scene swapping**: frees old child of `ActiveSceneContainer`, instantiates new scene, applies setup if present.
|
||||
- **Command console**: parses input, dispatches to registered commands or falls back to expression eval.
|
||||
- **Command registration**: holds an explicit `Array[ConsoleCommand]` populated at `_ready()`.
|
||||
|
||||
## Scene Swapping
|
||||
|
||||
### Registry Format
|
||||
|
||||
A hardcoded array in `DebugMenu`:
|
||||
|
||||
```gdscript
|
||||
var scene_registry: Array = [
|
||||
{ "name": "Strategy Phase", "path": "res://scenes/strategy_phase.tscn" },
|
||||
{ "name": "Strategy Phase (Test Data)", "path": "res://scenes/strategy_phase.tscn", "setup": "test_data" },
|
||||
{ "name": "Main Menu", "path": "res://scenes/main_menu.tscn" },
|
||||
{ "name": "Visual Novel", "path": "res://scenes/vn_scene.tscn" },
|
||||
{ "name": "Dialogue", "path": "res://scenes/dialogue_scene.tscn" },
|
||||
]
|
||||
```
|
||||
|
||||
### Swap Flow
|
||||
|
||||
1. Free the current child of `ActiveSceneContainer`.
|
||||
2. Instantiate the new scene from `path` and add as child.
|
||||
3. If the entry has a `"setup"` key, call a setup method on `DebugMenu` that applies that configuration to the new scene instance.
|
||||
4. Close the debug menu and signal `Game` to unpause.
|
||||
|
||||
### Setup Hooks
|
||||
|
||||
Setup functions live in `DebugMenu`, dispatched by matching the `"setup"` string key. This keeps test configurations self-contained within the debug system.
|
||||
|
||||
## Command Console
|
||||
|
||||
### Input
|
||||
|
||||
A `LineEdit` at the bottom of the debug panel. On Enter:
|
||||
|
||||
1. Split input text — first token is the command name, rest are arguments.
|
||||
2. Check against registered commands by matching `get_command_name()`.
|
||||
3. If found, call `run()` with args and context.
|
||||
4. If no match, fall back to Godot's `Expression` class (evaluated with the active scene as base instance).
|
||||
5. Show the result (or error) in `ResultLabel`, then hide it after 2 seconds using a `Timer` or `create_tween()`.
|
||||
|
||||
### Command Pattern
|
||||
|
||||
Base class:
|
||||
|
||||
```gdscript
|
||||
class_name ConsoleCommand
|
||||
|
||||
func get_command_name() -> String:
|
||||
return ""
|
||||
|
||||
func get_help_text() -> String:
|
||||
return ""
|
||||
|
||||
func run(args: Array, context: Dictionary) -> String:
|
||||
return ""
|
||||
```
|
||||
|
||||
Each command extends `ConsoleCommand` and overrides these three methods. The `context` dictionary provides access to the active scene container, debug menu reference, and other shared state without coupling commands to specific nodes.
|
||||
|
||||
Commands are explicitly registered in an `Array[ConsoleCommand]` in `DebugMenu._ready()`.
|
||||
|
||||
### Starter Commands
|
||||
|
||||
- **`help`** — lists all registered commands with their help text.
|
||||
- **`swap <name>`** — scene swap by name (matches against registry entry names).
|
||||
- **`list_scenes`** — prints available scene registry entries.
|
||||
|
||||
### Expression Eval Fallback
|
||||
|
||||
Uses Godot's `Expression` class. The expression executes with the current active scene as the base instance, allowing direct access to nodes and properties in the loaded scene. Errors are displayed in `ResultLabel`.
|
||||
|
||||
## File Organization
|
||||
|
||||
```
|
||||
nodes/
|
||||
├── game.gd # Thin shell
|
||||
├── debug_menu.gd # Scene registry, command console, swap logic
|
||||
|
||||
resources/resource_definitions/
|
||||
├── console_command.gd # Base class
|
||||
|
||||
resources/console_commands/
|
||||
├── swap_command.gd
|
||||
├── help_command.gd
|
||||
├── list_scenes_command.gd
|
||||
|
||||
scenes/
|
||||
├── game.tscn # New root scene (new main_scene in project.godot)
|
||||
|
||||
prefabs/
|
||||
├── debug_menu.tscn # CanvasLayer with panel, buttons, input, result label
|
||||
```
|
||||
|
||||
Existing scenes are unchanged — they are loaded as children of `ActiveSceneContainer` instead of being the root.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- **No autoload**: The debug menu is a UI node, not a headless service. The `XXXServer` autoload pattern is reserved for game services like a future `SceneManagerServer`.
|
||||
- **Explicit command registration**: Commands are listed in an array rather than auto-discovered from a folder. Simpler and more readable for a small number of commands. Can be swapped to auto-discovery later if needed.
|
||||
- **Single-line output**: `ResultLabel` shows one result at a time with a brief display. No scrollable history for now.
|
||||
- **Hardcoded scene list**: Entries can carry setup data/context, which auto-discovery from `.tscn` files wouldn't support.
|
||||
56
docs/superpowers/specs/2026-04-05-room-system-design.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Room System Design
|
||||
|
||||
## Overview
|
||||
|
||||
A room system for battle maps where rooms are defined as groups of tiles, walls exist on tile borders (not as tiles), and openings are explicitly marked doorways between rooms. Tiles outside rooms are impassable void.
|
||||
|
||||
## Data Model
|
||||
|
||||
### Room (Resource)
|
||||
|
||||
- `id: int` — unique room identifier
|
||||
- `tiles: Array[Vector2i]` — tile coordinates belonging to this room
|
||||
|
||||
### MapLayout (Resource)
|
||||
|
||||
- `rooms: Array[Room]` — all rooms on the map
|
||||
- `openings: Array` — list of tile-coordinate pairs (`[Vector2i, Vector2i]`) representing bidirectional doorways between adjacent tiles in different rooms
|
||||
|
||||
### Derived Wall Computation
|
||||
|
||||
A wall exists on a tile edge when:
|
||||
|
||||
1. One side is a room tile and the other is void (not in any room), OR
|
||||
2. The two sides belong to different rooms AND the edge is not in the openings list
|
||||
|
||||
### MapLayout API
|
||||
|
||||
- `get_walls() -> Array` — computes all wall edge segments from room and opening data
|
||||
- `is_passable(from: Vector2i, to: Vector2i) -> bool` — returns whether movement is allowed between two adjacent tiles (false if wall, false if either tile is void)
|
||||
- `is_tile_valid(tile: Vector2i) -> bool` — returns whether a tile belongs to any room
|
||||
- `get_room_at(tile: Vector2i) -> Room` — returns the room a tile belongs to, or null
|
||||
|
||||
## Integration with Movement
|
||||
|
||||
- `CombatMap` holds a reference to the `MapLayout`
|
||||
- Before allowing a unit to move from tile A to tile B, the movement system calls `MapLayout.is_passable(a, b)`
|
||||
- If there is a wall on that edge (and no opening), movement is blocked
|
||||
- Tiles not belonging to any room are impassable — `is_tile_valid()` returns false for void tiles
|
||||
- Future pathfinding uses the same `is_passable` check as its neighbor filter
|
||||
|
||||
## Wall Rendering
|
||||
|
||||
- `CombatMap` iterates over computed wall edges from `MapLayout.get_walls()`
|
||||
- Each wall edge is defined by two adjacent tile coordinates — the wall is drawn on the border between them
|
||||
- Openings have no wall drawn
|
||||
- Initial implementation uses simple `Line2D` segments or sprite strips on tile edges
|
||||
- Can be replaced with proper art assets later
|
||||
|
||||
## Design Decisions
|
||||
|
||||
- **Rooms are the primary data, walls are derived** — single source of truth, no sync issues
|
||||
- **Openings are explicit** — all room boundaries are walls by default; doorways are punched explicitly as tile-coordinate pairs
|
||||
- **Openings are always bidirectional**
|
||||
- **No room metadata beyond ID** — tile ownership stays tile-level per existing system
|
||||
- **Void tiles are impassable** — no hallway/corridor concept; all walkable tiles belong to a room
|
||||
- **Room data is independent of rendering** — clean for future map editor serialization
|
||||
15
docs/todo.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
* Singletons named 'XXXServer'
|
||||
* Dialogue scene command system (ShowText, ShowSprite, MoveSprite, PlaySound, ChangeBackground, etc)
|
||||
* Need to figure out more complicated systems like choices, script eval, conditionals, jumping, etc. Probably end up wanting a DialogueEditor or just making them gdscripts and being done with it
|
||||
* Finish Dialogue Scene (fast forward, auto, history functionality, etc)
|
||||
* Finish main menu
|
||||
* Game Start screen
|
||||
* Load Data Screen
|
||||
* Eushully room (CG viewer, scene viewer (after dialogue system), unit data screen (needs a unit registry))
|
||||
* Options
|
||||
* Battle View
|
||||
* Fog of war
|
||||
* Basic map editor (test map data will be harder to craft the more we add)
|
||||
* Start plugging in the Himegari UI
|
||||
* Unit panel needs fixed width number boxes, difference between digits causes UI jumps now
|
||||
* Dialog boxes
|
||||
71
export_presets.cfg
Normal file
@@ -0,0 +1,71 @@
|
||||
[preset.0]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path=""
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
patch_delta_min_reduction=0.1
|
||||
patch_delta_include_filters="*"
|
||||
patch_delta_exclude_filters=""
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
shader_baker/enabled=false
|
||||
binary_format/architecture="x86_64"
|
||||
codesign/enable=false
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
application/modify_resources=true
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
application/export_d3d12=0
|
||||
application/d3d12_agility_sdk_multiarch=true
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||
Start-ScheduledTask -TaskName godot_remote_debug
|
||||
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||
@@ -1,87 +0,0 @@
|
||||
class_name CombatMap
|
||||
extends Node2D
|
||||
|
||||
@export var tile_set: DLTileset
|
||||
@onready var tile_map: TileMapLayer = %TerrainLayer
|
||||
@onready var highlight_map: GridOverlay = %OverlayLayer
|
||||
@onready var tile_highlight: ColorRect = $TileHighlight
|
||||
|
||||
const TILE_SIZE := 48.0
|
||||
const SOURCE_ID: int = 0
|
||||
|
||||
var _pending_layout: String
|
||||
var _pending_units: Array[Dictionary] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if _pending_layout:
|
||||
_apply_layout(_pending_layout)
|
||||
for entry in _pending_units:
|
||||
_apply_deploy(entry.unit, entry.coords)
|
||||
_pending_units.clear()
|
||||
|
||||
|
||||
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 load_map(layout: String) -> void:
|
||||
if is_node_ready():
|
||||
_apply_layout(layout)
|
||||
else:
|
||||
_pending_layout = layout
|
||||
|
||||
|
||||
func deploy_unit(unit: Unit, coords: Vector2i) -> void:
|
||||
if is_node_ready():
|
||||
_apply_deploy(unit, coords)
|
||||
else:
|
||||
_pending_units.append({unit = unit, coords = coords})
|
||||
|
||||
|
||||
func _apply_layout(layout: String) -> void:
|
||||
var rows := layout.split("\n")
|
||||
for y in rows.size():
|
||||
for x in rows[y].length():
|
||||
var coords := Vector2i(x, y)
|
||||
match rows[y][x]:
|
||||
"#":
|
||||
draw_wall(coords)
|
||||
".":
|
||||
draw_floor(coords)
|
||||
|
||||
|
||||
func _apply_deploy(unit: Unit, coords: Vector2i) -> void:
|
||||
unit.position = coords_to_world(coords)
|
||||
add_child(unit)
|
||||
|
||||
|
||||
func is_wall(coords: Vector2i) -> bool:
|
||||
return tile_map.get_cell_atlas_coords(coords) == tile_set.wall_tile_coords
|
||||
|
||||
func remove_unit(unit: Unit) -> void:
|
||||
if unit.get_parent() == self:
|
||||
remove_child(unit)
|
||||
|
||||
|
||||
func target_tile(coords: Vector2i) -> void:
|
||||
highlight_map.target_tile(coords)
|
||||
|
||||
|
||||
func set_highlight_enabled(enabled: bool) -> void:
|
||||
tile_highlight.visible = enabled
|
||||
tile_highlight.set_process(enabled)
|
||||
@@ -1,55 +0,0 @@
|
||||
class_name CombatSystem extends Node
|
||||
|
||||
func create_proposal(attacker: Unit, defender: Unit) -> CombatProposal:
|
||||
var proposal := CombatProposal.new()
|
||||
|
||||
proposal.attacker = _snapshot(attacker, defender)
|
||||
proposal.defender = _snapshot(defender, attacker)
|
||||
|
||||
return proposal
|
||||
|
||||
func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats:
|
||||
var stats := CombatProposal.CombatantStats.new()
|
||||
stats.unit = unit
|
||||
stats.max_hp = unit.current_stats.max_hp
|
||||
stats.hp = unit.current_stats.current_hp
|
||||
stats.sp = unit.current_stats.current_sp
|
||||
stats.hit = unit.current_stats.hit - opponent.current_stats.eva
|
||||
stats.atk = unit.current_stats.phys_atk
|
||||
stats.def = unit.current_stats.phys_def
|
||||
stats.spd = unit.current_stats.spd
|
||||
return stats
|
||||
|
||||
func process_combat(attacker: Unit, defender: Unit) -> void:
|
||||
if not attacker.is_alive() or not defender.is_alive():
|
||||
return
|
||||
var proposal := create_proposal(attacker, defender)
|
||||
var atk_name := attacker.current_info.name
|
||||
var def_name := defender.current_info.name
|
||||
print("=== Combat: %s vs %s ===" % [atk_name, def_name])
|
||||
print(" %s — HP:%d ATK:%d DEF:%d HIT:%d" % [atk_name, proposal.attacker.hp, proposal.attacker.atk, proposal.attacker.def, proposal.attacker.hit])
|
||||
print(" %s — HP:%d ATK:%d DEF:%d HIT:%d" % [def_name, proposal.defender.hp, proposal.defender.atk, proposal.defender.def, proposal.defender.hit])
|
||||
apply_proposal(proposal)
|
||||
var atk_hp := attacker.current_stats.current_hp if is_instance_valid(attacker) else 0
|
||||
var def_hp := defender.current_stats.current_hp if is_instance_valid(defender) else 0
|
||||
print(" Result: %s HP=%d, %s HP=%d" % [atk_name, atk_hp, def_name, def_hp])
|
||||
|
||||
|
||||
func apply_proposal(proposal: CombatProposal) -> void:
|
||||
var atk_stats := proposal.attacker
|
||||
var def_stats := proposal.defender
|
||||
var atk_unit := atk_stats.unit
|
||||
var def_unit := def_stats.unit
|
||||
|
||||
# Attacker strikes
|
||||
var atk_roll := randi_range(1, 100)
|
||||
if atk_roll <= atk_stats.hit:
|
||||
var damage := maxi(atk_stats.atk - def_stats.def, 0)
|
||||
def_unit.take_damage(damage)
|
||||
|
||||
# Counterattack if defender survives
|
||||
if def_unit.is_alive():
|
||||
var def_roll := randi_range(1, 100)
|
||||
if def_roll <= def_stats.hit:
|
||||
var damage := maxi(def_stats.atk - atk_stats.def, 0)
|
||||
atk_unit.take_damage(damage)
|
||||
@@ -1,39 +0,0 @@
|
||||
class_name StrategyPhase extends Node2D
|
||||
|
||||
@onready var player_controller: PlayerController = $PlayerController
|
||||
@onready var combat_system: CombatSystem = $CombatSystem
|
||||
@onready var combat_ui: CombatUI = $CombatUI
|
||||
@onready var combat_map: CombatMap = $CombatMap
|
||||
@onready var camera: CameraController = $Camera2D
|
||||
|
||||
func _ready() -> void:
|
||||
player_controller.combat_requested.connect(_on_combat_requested)
|
||||
player_controller.mouse_grid_changed.connect(_on_mouse_grid_changed)
|
||||
player_controller.camera_drag.connect(camera.apply_drag)
|
||||
combat_ui.fight_confirmed.connect(_on_fight_confirmed)
|
||||
combat_ui.fight_cancelled.connect(_on_fight_cancelled)
|
||||
|
||||
|
||||
func _on_mouse_grid_changed(coords: Vector2i) -> void:
|
||||
combat_map.target_tile(coords)
|
||||
combat_map.tile_highlight.set_grid_coords(coords)
|
||||
|
||||
|
||||
func _on_combat_requested(attacker: Unit, defender: Unit) -> void:
|
||||
var proposal := combat_system.create_proposal(attacker, defender)
|
||||
_set_input_disabled(true)
|
||||
combat_ui.show_proposal(proposal)
|
||||
|
||||
|
||||
func _on_fight_confirmed(proposal: CombatProposal) -> void:
|
||||
combat_system.apply_proposal(proposal)
|
||||
_set_input_disabled(false)
|
||||
|
||||
|
||||
func _on_fight_cancelled() -> void:
|
||||
_set_input_disabled(false)
|
||||
|
||||
|
||||
func _set_input_disabled(disabled: bool) -> void:
|
||||
player_controller.input_disabled = disabled
|
||||
combat_map.set_highlight_enabled(not disabled)
|
||||
@@ -1,43 +0,0 @@
|
||||
class_name Unit extends Node2D
|
||||
|
||||
enum UnitState { ALIVE, DEAD }
|
||||
|
||||
#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
|
||||
var state: UnitState = UnitState.ALIVE
|
||||
|
||||
signal unit_selected_changed(unit: Unit, selected: bool)
|
||||
signal unit_allegiance_changed(unit: Unit, allegiance: UnitAllegiance)
|
||||
signal unit_died(unit: Unit)
|
||||
|
||||
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)
|
||||
|
||||
func is_alive() -> bool:
|
||||
return state == UnitState.ALIVE
|
||||
|
||||
func take_damage(amount: int) -> void:
|
||||
if state != UnitState.ALIVE:
|
||||
return
|
||||
current_stats.current_hp -= amount
|
||||
if current_stats.current_hp <= 0:
|
||||
current_stats.current_hp = 0
|
||||
_die()
|
||||
|
||||
func _die() -> void:
|
||||
state = UnitState.DEAD
|
||||
unit_died.emit(self)
|
||||
queue_free()
|
||||
21
prefabs/chip_bar.tscn
Normal file
@@ -0,0 +1,21 @@
|
||||
[gd_scene format=3 uid="uid://8edgswcwdiwu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cvmmsm13nyr62" path="res://scripts/ui/chip_bar.gd" id="1_3whrn"]
|
||||
|
||||
[node name="ChipBar" type="Control" unique_id=379110810]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_3whrn")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=185867767]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 0
|
||||
@@ -1,16 +1,18 @@
|
||||
[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="Script" uid="uid://bks7uplgjjdg0" path="res://scripts/battle/map/combat_map.gd" id="1_jyv1f"]
|
||||
[ext_resource type="Script" uid="uid://c6701vy8h5rfx" path="res://scripts/battle/map/dl_tileset.gd" id="2_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"]
|
||||
[ext_resource type="Script" uid="uid://cxl38x2m6sj3w" path="res://scripts/battle/map/grid_overlay.gd" id="4_jelju"]
|
||||
[ext_resource type="Texture2D" uid="uid://b1ks72fiesfrm" path="res://assets/sprites/combat_map_ui.BMP" id="5_mycp7"]
|
||||
[ext_resource type="Texture2D" uid="uid://65rmoynep5hy" path="res://assets/sprites/MP000A.BMP" id="6_muxvo"]
|
||||
[ext_resource type="Script" uid="uid://c4f1vflwd81b8" path="res://scripts/battle/map/wall_renderer.gd" id="7_wallr"]
|
||||
[ext_resource type="Texture2D" uid="uid://b20mhn7ca5xyo" path="res://assets/sprites/aux_terrain.BMP" id="8_auxtr"]
|
||||
[ext_resource type="Script" uid="uid://d1d1nbetdvynk" path="res://scripts/battle/map/fog_renderer.gd" id="9_fogrn"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_vcj5e"]
|
||||
script = ExtResource("2_8rn0j")
|
||||
wall_tile_coords = Vector2i(2, 15)
|
||||
wall_tile_coords = Vector2i(0, 1)
|
||||
metadata/_custom_type_script = "uid://c6701vy8h5rfx"
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_jelju"]
|
||||
@@ -26,9 +28,46 @@ texture_region_size = Vector2i(48, 48)
|
||||
0:0/0 = 0
|
||||
|
||||
[sub_resource type="TileSet" id="TileSet_muxvo"]
|
||||
tile_size = Vector2i(48, 48)
|
||||
tile_size = Vector2i(100, 100)
|
||||
sources/0 = SubResource("TileSetAtlasSource_jelju")
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_e2u25"]
|
||||
atlas = ExtResource("5_mycp7")
|
||||
region = Rect2(0, 428, 100, 100)
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_mycp7"]
|
||||
texture = ExtResource("6_muxvo")
|
||||
texture_region_size = Vector2i(100, 100)
|
||||
0:0/0 = 0
|
||||
1:0/0 = 0
|
||||
2:0/0 = 0
|
||||
3:0/0 = 0
|
||||
4:0/0 = 0
|
||||
0:1/0 = 0
|
||||
1:1/0 = 0
|
||||
2:1/0 = 0
|
||||
3:1/0 = 0
|
||||
4:1/0 = 0
|
||||
0:2/0 = 0
|
||||
1:2/0 = 0
|
||||
2:2/0 = 0
|
||||
3:2/0 = 0
|
||||
4:2/0 = 0
|
||||
0:3/0 = 0
|
||||
1:3/0 = 0
|
||||
2:3/0 = 0
|
||||
3:3/0 = 0
|
||||
4:3/0 = 0
|
||||
0:4/0 = 0
|
||||
1:4/0 = 0
|
||||
2:4/0 = 0
|
||||
3:4/0 = 0
|
||||
4:4/0 = 0
|
||||
|
||||
[sub_resource type="TileSet" id="TileSet_e2u25"]
|
||||
tile_size = Vector2i(100, 100)
|
||||
sources/0 = SubResource("TileSetAtlasSource_mycp7")
|
||||
|
||||
[node name="CombatMap" type="Node2D" unique_id=546780706]
|
||||
script = ExtResource("1_jyv1f")
|
||||
tile_set = SubResource("Resource_vcj5e")
|
||||
@@ -41,12 +80,20 @@ 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)
|
||||
texture = SubResource("AtlasTexture_e2u25")
|
||||
offset = Vector2(50, 50)
|
||||
|
||||
[node name="TerrainLayer" type="TileMapLayer" parent="." unique_id=1201875024]
|
||||
unique_name_in_owner = true
|
||||
tile_set = ExtResource("3_8rn0j")
|
||||
tile_set = SubResource("TileSet_e2u25")
|
||||
|
||||
[node name="TileHighlight" type="ColorRect" parent="." unique_id=211433569]
|
||||
script = ExtResource("7_tileh")
|
||||
[node name="FogRenderer" type="Node2D" parent="." unique_id=641669860]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("9_fogrn")
|
||||
atlas_texture = ExtResource("8_auxtr")
|
||||
|
||||
[node name="WallRenderer" type="Node2D" parent="." unique_id=1008825128]
|
||||
unique_name_in_owner = true
|
||||
z_index = 1
|
||||
script = ExtResource("7_wallr")
|
||||
atlas_texture = ExtResource("8_auxtr")
|
||||
|
||||
@@ -1,164 +1,435 @@
|
||||
[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"]
|
||||
[ext_resource type="Script" uid="uid://w2wh6gtv3u2l" path="res://scripts/battle/combat_ui.gd" id="2_ui_script"]
|
||||
[ext_resource type="Texture2D" uid="uid://cavpqnd0qqoou" path="res://assets/ui/SO008B.BMP" id="3_hadma"]
|
||||
[ext_resource type="Texture2D" uid="uid://cau61m1755dkn" path="res://assets/ui/SO008A.BMP" id="3_tfn3h"]
|
||||
[ext_resource type="PackedScene" uid="uid://bc5a7tb0my6n5" path="res://prefabs/stylized_number_display.tscn" id="5_55shj"]
|
||||
[ext_resource type="PackedScene" uid="uid://8edgswcwdiwu" path="res://prefabs/chip_bar.tscn" id="6_gqe5k"]
|
||||
|
||||
[node name="CombatUI" type="CanvasLayer" unique_id=1093388037]
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hadma"]
|
||||
atlas = ExtResource("3_tfn3h")
|
||||
region = Rect2(0, 0, 800, 600)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_55shj"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(393, 280, 168, 77)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_gqe5k"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(141, 0, 513, 169)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hr2yf"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(393, 169, 263, 111)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_3wejr"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(614, 711, 137, 26)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_uh1k2"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(614, 737, 55, 20)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_yayqj"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(547, 839, 270, 36)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_eskga"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(249, 272, 3, 14)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_14b7f"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(246, 272, 3, 14)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_manhy"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(1055, 151, 28, 19)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ox7qj"]
|
||||
atlas = ExtResource("3_hadma")
|
||||
region = Rect2(547, 824, 130, 15)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_gqe5k"]
|
||||
load_path = "res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_yhw6j"]
|
||||
atlas = SubResource("CompressedTexture2D_gqe5k")
|
||||
region = Rect2(261, 272, 3, 9)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_icxrh"]
|
||||
load_path = "res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kdblo"]
|
||||
atlas = SubResource("CompressedTexture2D_icxrh")
|
||||
region = Rect2(255, 272, 3, 9)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_ptl6p"]
|
||||
load_path = "res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vj7wc"]
|
||||
atlas = SubResource("CompressedTexture2D_ptl6p")
|
||||
region = Rect2(1056, 172, 27, 15)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5b4vf"]
|
||||
load_path = "res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kd55s"]
|
||||
atlas = SubResource("CompressedTexture2D_5b4vf")
|
||||
region = Rect2(261, 272, 3, 9)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_4lqfr"]
|
||||
load_path = "res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_alhr0"]
|
||||
atlas = SubResource("CompressedTexture2D_4lqfr")
|
||||
region = Rect2(258, 272, 3, 9)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_eajv6"]
|
||||
load_path = "res://.godot/imported/SO008B.BMP-57a2d92123e6b32a5ec6367855ebadf6.ctex"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_r8i3r"]
|
||||
atlas = SubResource("CompressedTexture2D_eajv6")
|
||||
region = Rect2(1056, 190, 26, 16)
|
||||
|
||||
[node name="BattleViewUI" 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
|
||||
|
||||
[node name="BackgroundTint" type="ColorRect" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
[node name="UIBase" type="Control" parent="." unique_id=7839209]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
color = Color(0, 0, 0, 0.4)
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="CombatProposalPanel" type="PanelContainer" parent="."]
|
||||
[node name="Overlay" type="Control" parent="UIBase" unique_id=114439631]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="Background" type="TextureRect" parent="UIBase/Overlay" unique_id=1726665864]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_hadma")
|
||||
|
||||
[node name="HoverInfo" type="TextureRect" parent="UIBase/Overlay" unique_id=1180275113]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
texture = SubResource("AtlasTexture_55shj")
|
||||
|
||||
[node name="Bars" type="TextureRect" parent="UIBase/Overlay" unique_id=780801800]
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -513.0
|
||||
offset_bottom = 169.0
|
||||
grow_horizontal = 0
|
||||
texture = SubResource("AtlasTexture_gqe5k")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="UIBase/Overlay" unique_id=1469571203]
|
||||
layout_mode = 0
|
||||
offset_left = 536.0
|
||||
offset_top = 432.0
|
||||
offset_right = 799.0
|
||||
offset_bottom = 543.0
|
||||
texture = SubResource("AtlasTexture_hr2yf")
|
||||
|
||||
[node name="BackgroundTint" type="ColorRect" parent="UIBase" unique_id=253893594]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0, 0, 0, 0.4)
|
||||
|
||||
[node name="CombatProposalPanel" type="PanelContainer" parent="UIBase" unique_id=2088533653]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -88.0
|
||||
offset_top = -103.5
|
||||
offset_right = 88.0
|
||||
offset_bottom = 103.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_2ro41")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="CombatProposalPanel"]
|
||||
[node name="MarginContainer" type="MarginContainer" parent="UIBase/CombatProposalPanel" unique_id=924522188]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer" unique_id=666671196]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="TitleLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=1520669125]
|
||||
layout_mode = 2
|
||||
text = "COMBAT FORECAST"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="StatsContainer" type="HBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="StatsContainer" type="HBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=1101898616]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 24
|
||||
|
||||
[node name="AttackerStats" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer"]
|
||||
[node name="AttackerStats" type="VBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer" unique_id=1193603706]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AttackerNameLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
[node name="AttackerNameLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1574861154]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Attacker"
|
||||
|
||||
[node name="AttackerHPBar" type="ProgressBar" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
[node name="AttackerTacticSelect" type="OptionButton" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1466363800]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AttackerHPBar" type="ProgressBar" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=241886156]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
show_percentage = false
|
||||
|
||||
[node name="AttackerATKLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
[node name="AttackerATKLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1656041171]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "ATK: 0"
|
||||
|
||||
[node name="AttackerDEFLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
[node name="AttackerDEFLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=2145851939]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "DEF: 0"
|
||||
|
||||
[node name="AttackerHITLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
[node name="AttackerHITLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1234331828]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "HIT: 0%"
|
||||
|
||||
[node name="AttackerSPDLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||
[node name="AttackerSPDLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1461624142]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "SPD: 0"
|
||||
|
||||
[node name="DefenderStats" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer"]
|
||||
[node name="DefenderStats" type="VBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer" unique_id=1747482540]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DefenderNameLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
[node name="DefenderNameLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=365769500]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Defender"
|
||||
|
||||
[node name="DefenderHPBar" type="ProgressBar" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
[node name="DefenderTacticSelect" type="OptionButton" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1226652499]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DefenderHPBar" type="ProgressBar" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1878516243]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
show_percentage = false
|
||||
|
||||
[node name="DefenderATKLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
[node name="DefenderATKLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1571527922]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "ATK: 0"
|
||||
|
||||
[node name="DefenderDEFLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
[node name="DefenderDEFLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1755454849]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "DEF: 0"
|
||||
|
||||
[node name="DefenderHITLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
[node name="DefenderHITLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=717216786]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "HIT: 0%"
|
||||
|
||||
[node name="DefenderSPDLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||
[node name="DefenderSPDLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=132072292]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "SPD: 0"
|
||||
|
||||
[node name="ButtonContainer" type="HBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="ButtonContainer" type="HBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=2145647565]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="FightButton" type="Button" parent="CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer"]
|
||||
[node name="FightButton" type="Button" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer" unique_id=2109262268]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Fight"
|
||||
|
||||
[node name="CancelButton" type="Button" parent="CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer"]
|
||||
[node name="CancelButton" type="Button" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer" unique_id=238295206]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
|
||||
[node name="UnitPanel" type="Control" parent="UIBase" unique_id=1823763147]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -265.0
|
||||
offset_top = -194.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="UIBase/UnitPanel" unique_id=691752297]
|
||||
layout_mode = 0
|
||||
offset_left = 128.0
|
||||
offset_right = 265.0
|
||||
offset_bottom = 26.0
|
||||
size_flags_horizontal = 8
|
||||
texture = SubResource("AtlasTexture_3wejr")
|
||||
|
||||
[node name="UnitLevel" type="HBoxContainer" parent="UIBase/UnitPanel/TextureRect" unique_id=828992814]
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -63.0
|
||||
offset_top = -20.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="Spacer" type="Control" parent="UIBase/UnitPanel/TextureRect/UnitLevel" unique_id=1068603879]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="TextureRect" parent="UIBase/UnitPanel/TextureRect/UnitLevel" unique_id=1299333906]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
texture = SubResource("AtlasTexture_uh1k2")
|
||||
|
||||
[node name="LevelNumber" parent="UIBase/UnitPanel/TextureRect/UnitLevel" unique_id=702997768 instance=ExtResource("5_55shj")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
sprite_sheet = SubResource("AtlasTexture_yayqj")
|
||||
number_sprite_width = 27
|
||||
number_sprite_height = 39
|
||||
value = 12
|
||||
|
||||
[node name="UnitHealth" type="HBoxContainer" parent="UIBase/UnitPanel" unique_id=53239936]
|
||||
layout_mode = 0
|
||||
offset_left = -127.0
|
||||
offset_top = 103.0
|
||||
offset_right = 253.0
|
||||
offset_bottom = 131.0
|
||||
alignment = 2
|
||||
|
||||
[node name="HealthChipBar" parent="UIBase/UnitPanel/UnitHealth" unique_id=379110810 instance=ExtResource("6_gqe5k")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
max_chips_per_row = 100
|
||||
empty_chip_texture = SubResource("AtlasTexture_eskga")
|
||||
filled_chip_texture = SubResource("AtlasTexture_14b7f")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="UIBase/UnitPanel/UnitHealth" unique_id=668812970]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
texture = SubResource("AtlasTexture_manhy")
|
||||
|
||||
[node name="HealthNumber" parent="UIBase/UnitPanel/UnitHealth" unique_id=1442983008 instance=ExtResource("5_55shj")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
sprite_sheet = SubResource("AtlasTexture_ox7qj")
|
||||
number_sprite_width = 13
|
||||
number_sprite_height = 15
|
||||
|
||||
[node name="UnitSP" type="HBoxContainer" parent="UIBase/UnitPanel" unique_id=514403074]
|
||||
layout_mode = 0
|
||||
offset_left = 1.0
|
||||
offset_top = 133.0
|
||||
offset_right = 249.0
|
||||
offset_bottom = 151.0
|
||||
|
||||
[node name="SPChipBar" parent="UIBase/UnitPanel/UnitSP" unique_id=374103132 instance=ExtResource("6_gqe5k")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
max_chips_per_row = 100
|
||||
empty_chip_texture = SubResource("AtlasTexture_yhw6j")
|
||||
filled_chip_texture = SubResource("AtlasTexture_kdblo")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="UIBase/UnitPanel/UnitSP" unique_id=805168370]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
texture = SubResource("AtlasTexture_vj7wc")
|
||||
|
||||
[node name="SPNumber" parent="UIBase/UnitPanel/UnitSP" unique_id=442319509 instance=ExtResource("5_55shj")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
sprite_sheet = SubResource("AtlasTexture_ox7qj")
|
||||
number_sprite_width = 13
|
||||
number_sprite_height = 15
|
||||
|
||||
[node name="UnitFS" type="HBoxContainer" parent="UIBase/UnitPanel" unique_id=1209088756]
|
||||
layout_mode = 0
|
||||
offset_left = 1.0
|
||||
offset_top = 160.0
|
||||
offset_right = 249.0
|
||||
offset_bottom = 178.0
|
||||
|
||||
[node name="FSChipBar" parent="UIBase/UnitPanel/UnitFS" unique_id=1380843979 instance=ExtResource("6_gqe5k")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
max_chips_per_row = 100
|
||||
empty_chip_texture = SubResource("AtlasTexture_kd55s")
|
||||
filled_chip_texture = SubResource("AtlasTexture_alhr0")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="UIBase/UnitPanel/UnitFS" unique_id=1340673242]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
texture = SubResource("AtlasTexture_r8i3r")
|
||||
|
||||
[node name="FSNumber" parent="UIBase/UnitPanel/UnitFS" unique_id=1558008542 instance=ExtResource("5_55shj")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
sprite_sheet = SubResource("AtlasTexture_ox7qj")
|
||||
number_sprite_width = 13
|
||||
number_sprite_height = 15
|
||||
|
||||
[node name="UnitName" type="RichTextLabel" parent="UIBase/UnitPanel" unique_id=1945794101]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
offset_left = 69.0
|
||||
offset_top = 51.0
|
||||
offset_right = 258.0
|
||||
offset_bottom = 66.0
|
||||
theme = ExtResource("1_2ro41")
|
||||
theme_override_font_sizes/normal_font_size = 16
|
||||
theme_override_font_sizes/bold_font_size = 16
|
||||
bbcode_enabled = true
|
||||
scroll_active = false
|
||||
text_direction = 2
|
||||
|
||||
52
prefabs/contiguous_bar.tscn
Normal file
@@ -0,0 +1,52 @@
|
||||
[gd_scene format=3 uid="uid://fw4ug70qd8nm"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://cavpqnd0qqoou" path="res://assets/ui/SO008B.BMP" id="1_nj0tn"]
|
||||
[ext_resource type="Script" uid="uid://cb32ywwuyi706" path="res://scripts/ui/contiguous_bar.gd" id="2_cbar"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vkyrt"]
|
||||
atlas = ExtResource("1_nj0tn")
|
||||
region = Rect2(680, 557, 52, 7)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_3kyon"]
|
||||
atlas = ExtResource("1_nj0tn")
|
||||
region = Rect2(680, 564, 50, 5)
|
||||
|
||||
[node name="ContiguousBar" type="Control" unique_id=1119297666]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("2_cbar")
|
||||
|
||||
[node name="EmptyBar" type="NinePatchRect" parent="." unique_id=303722124]
|
||||
layout_mode = 1
|
||||
anchors_preset = 14
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_bottom = 7.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_vkyrt")
|
||||
region_rect = Rect2(0, 0, 52, 7)
|
||||
patch_margin_left = 1
|
||||
patch_margin_top = 1
|
||||
patch_margin_right = 1
|
||||
patch_margin_bottom = 1
|
||||
|
||||
[node name="Fill" type="NinePatchRect" parent="EmptyBar" unique_id=1864372620]
|
||||
layout_mode = 1
|
||||
anchors_preset = 11
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -1.0
|
||||
offset_top = 1.0
|
||||
offset_right = -1.0
|
||||
offset_bottom = -1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_3kyon")
|
||||
region_rect = Rect2(0, 0, 50, 5)
|
||||
58
prefabs/debug_menu.tscn
Normal file
@@ -0,0 +1,58 @@
|
||||
[gd_scene format=3 uid="uid://dpcaa8x6xxup0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c64yr8xvkb5cw" path="res://scripts/debug/debug_menu.gd" id="1_script"]
|
||||
|
||||
[node name="DebugMenu" type="CanvasLayer" unique_id=240858900]
|
||||
layer = 100
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Panel" type="PanelContainer" parent="." unique_id=349886438]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Panel" unique_id=322235564]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer" unique_id=1731008558]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=211532752]
|
||||
layout_mode = 2
|
||||
text = "Debug Menu"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer" unique_id=1527486356]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ScenesLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=1071710223]
|
||||
layout_mode = 2
|
||||
text = "Scenes:"
|
||||
|
||||
[node name="SceneList" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer" unique_id=1684653951]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer" unique_id=487142357]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ConsoleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=1165373860]
|
||||
layout_mode = 2
|
||||
text = "Console:"
|
||||
|
||||
[node name="CommandInput" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer" unique_id=1402278573]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Enter command or expression..."
|
||||
|
||||
[node name="ResultLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=881735021]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
autowrap_mode = 3
|
||||
51
prefabs/deployed_unit.tscn
Normal file
@@ -0,0 +1,51 @@
|
||||
[gd_scene format=3 uid="uid://b6a7nlnf58mc4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cmh4lphvboggy" path="res://scripts/battle/deployed_units/deployed_unit.gd" id="1_cq4v0"]
|
||||
[ext_resource type="Texture2D" uid="uid://cw5su6lignryo" path="res://assets/sprites/flag.png" id="2_fhs1y"]
|
||||
[ext_resource type="Shader" uid="uid://bd8ki8xwym5nc" path="res://shaders/chroma_key.gdshader" id="3_fhs1y"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_on614"]
|
||||
resource_name = "UnitSelectorHandler"
|
||||
script/source = "extends ColorRect
|
||||
|
||||
func _unit_selected_changed(_deployed: DeployedUnit, selected: bool) -> void:
|
||||
visible = selected
|
||||
"
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_fhs1y"]
|
||||
resource_name = "AllegianceIndicatorManager"
|
||||
script/source = "extends Sprite2D
|
||||
|
||||
|
||||
func _on_unit_unit_allegiance_changed(_deployed: DeployedUnit, allegiance: UnitAllegiance) -> void:
|
||||
self_modulate = allegiance.color
|
||||
"
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_fhs1y"]
|
||||
shader = ExtResource("3_fhs1y")
|
||||
shader_parameter/key_color = Color(0, 1, 0, 1)
|
||||
shader_parameter/threshold = 0.01
|
||||
|
||||
[node name="DeployedUnit" type="Node2D" unique_id=1893234933 groups=["deployed_units"]]
|
||||
script = ExtResource("1_cq4v0")
|
||||
metadata/_custom_type_script = "uid://cmh4lphvboggy"
|
||||
|
||||
[node name="SelectionIndicator" type="ColorRect" parent="." unique_id=1313394023]
|
||||
visible = false
|
||||
offset_right = 100.0
|
||||
offset_bottom = 100.0
|
||||
color = Color(1, 1, 0.3019608, 0.36078432)
|
||||
script = SubResource("GDScript_on614")
|
||||
|
||||
[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="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1796991032]
|
||||
material = SubResource("ShaderMaterial_fhs1y")
|
||||
position = Vector2(50, 50)
|
||||
|
||||
[connection signal="unit_allegiance_changed" from="." to="AllegianceIndicator" method="_on_unit_unit_allegiance_changed"]
|
||||
[connection signal="unit_selected_changed" from="." to="SelectionIndicator" method="_unit_selected_changed"]
|
||||
186
prefabs/deployment_slot.tscn
Normal file
@@ -0,0 +1,186 @@
|
||||
[gd_scene format=3 uid="uid://csu4xocsj71td"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://cavpqnd0qqoou" path="res://assets/ui/SO008B.BMP" id="1_vc35g"]
|
||||
[ext_resource type="Shader" uid="uid://bd8ki8xwym5nc" path="res://shaders/chroma_key.gdshader" id="2_mwfff"]
|
||||
[ext_resource type="Texture2D" uid="uid://c7coajdu61crq" path="res://assets/ui/unit_faces.BMP" id="3_q3r6y"]
|
||||
[ext_resource type="Script" uid="uid://cb32ywwuyi706" path="res://scripts/ui/contiguous_bar.gd" id="4_byj02"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_07lbo"]
|
||||
atlas = ExtResource("1_vc35g")
|
||||
region = Rect2(680, 522, 60, 35)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_scfqt"]
|
||||
atlas = ExtResource("1_vc35g")
|
||||
region = Rect2(680, 605, 50, 17)
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_a8mo2"]
|
||||
shader = ExtResource("2_mwfff")
|
||||
shader_parameter/key_color = Color(0, 1, 0, 1)
|
||||
shader_parameter/threshold = 0.01
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ajsjv"]
|
||||
atlas = ExtResource("3_q3r6y")
|
||||
region = Rect2(40, 0, 40, 40)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kqrkf"]
|
||||
atlas = ExtResource("1_vc35g")
|
||||
region = Rect2(708, 587, 28, 17)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1c3gn"]
|
||||
atlas = ExtResource("1_vc35g")
|
||||
region = Rect2(264, 272, 52, 7)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_d4kxr"]
|
||||
atlas = ExtResource("1_vc35g")
|
||||
region = Rect2(264, 279, 50, 5)
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_vxtih"]
|
||||
blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_og56w"]
|
||||
atlas = ExtResource("1_vc35g")
|
||||
region = Rect2(740, 522, 45, 35)
|
||||
|
||||
[node name="DeploymentSlot" type="Control" unique_id=258677476]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Base" type="TextureRect" parent="." unique_id=1397025328]
|
||||
self_modulate = Color(0.85881317, 0.8389489, 0.60661924, 1)
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -30.0
|
||||
offset_top = -17.5
|
||||
offset_right = 30.0
|
||||
offset_bottom = 17.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_07lbo")
|
||||
|
||||
[node name="Contents" type="Control" parent="Base" unique_id=1351926154]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Empty" type="Control" parent="Base/Contents" unique_id=1459198631]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Label" type="TextureRect" parent="Base/Contents/Empty" unique_id=460221998]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -25.0
|
||||
offset_top = -8.5
|
||||
offset_right = 25.0
|
||||
offset_bottom = 8.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_scfqt")
|
||||
|
||||
[node name="Deployed" type="Control" parent="Base/Contents" unique_id=1502290289]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="UnitSprite" type="TextureRect" parent="Base/Contents/Deployed" unique_id=1617524406]
|
||||
material = SubResource("ShaderMaterial_a8mo2")
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -45.0
|
||||
offset_right = 40.0
|
||||
offset_bottom = -5.0
|
||||
grow_vertical = 0
|
||||
texture = SubResource("AtlasTexture_ajsjv")
|
||||
|
||||
[node name="ReadyLabel" type="TextureRect" parent="Base/Contents/Deployed" unique_id=1608591044]
|
||||
layout_mode = 1
|
||||
anchors_preset = 6
|
||||
anchor_left = 1.0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -30.0
|
||||
offset_top = -14.0
|
||||
offset_right = -2.0
|
||||
offset_bottom = 3.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_kqrkf")
|
||||
|
||||
[node name="EmptyBar" type="NinePatchRect" parent="Base/Contents/Deployed" unique_id=855067973]
|
||||
layout_mode = 1
|
||||
anchors_preset = 14
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 2.0
|
||||
offset_top = 7.0
|
||||
offset_right = -2.0
|
||||
offset_bottom = 14.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_1c3gn")
|
||||
region_rect = Rect2(0, 0, 52, 7)
|
||||
patch_margin_left = 1
|
||||
patch_margin_top = 1
|
||||
patch_margin_right = 1
|
||||
patch_margin_bottom = 1
|
||||
script = ExtResource("4_byj02")
|
||||
value = 50
|
||||
max_value = 50
|
||||
|
||||
[node name="Fill" type="NinePatchRect" parent="Base/Contents/Deployed/EmptyBar" unique_id=318061429]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 1.0
|
||||
offset_top = 1.0
|
||||
offset_right = -1.0
|
||||
offset_bottom = -1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_d4kxr")
|
||||
region_rect = Rect2(0, 0, 50, 5)
|
||||
|
||||
[node name="Hover" type="TextureRect" parent="." unique_id=1517469291]
|
||||
visible = false
|
||||
material = SubResource("CanvasItemMaterial_vxtih")
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -22.5
|
||||
offset_top = -17.5
|
||||
offset_right = 22.5
|
||||
offset_bottom = 17.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_og56w")
|
||||
30
prefabs/stylized_number_display.tscn
Normal file
@@ -0,0 +1,30 @@
|
||||
[gd_scene format=3 uid="uid://bc5a7tb0my6n5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://biud4ob4h0rrs" path="res://scripts/ui/stylized_number_display.gd" id="1_yn0fd"]
|
||||
[ext_resource type="Texture2D" uid="uid://cavpqnd0qqoou" path="res://assets/ui/SO008B.BMP" id="2_2wvj5"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_b1oqg"]
|
||||
atlas = ExtResource("2_2wvj5")
|
||||
region = Rect2(546, 839, 272, 39)
|
||||
|
||||
[node name="StylizedNumberDisplay" type="Control" unique_id=702997768]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_yn0fd")
|
||||
sprite_sheet = SubResource("AtlasTexture_b1oqg")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=2089310026]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 2
|
||||
alignment = 1
|
||||
@@ -1,26 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://b6a7nlnf58mc4"]
|
||||
[gd_scene format=3 uid="uid://dy0s7rfs4i64y"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c016mxgatcpse" path="res://nodes/unit.gd" id="1_cq4v0"]
|
||||
[ext_resource type="Script" uid="uid://c016mxgatcpse" path="res://scripts/units/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)
|
||||
[ext_resource type="Texture2D" uid="uid://dyutp4m5d53gd" path="res://assets/sprites/CP002AA.BMP" id="3_on614"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_on614"]
|
||||
resource_name = "UnitSelectorHandler"
|
||||
@@ -30,28 +13,76 @@ func _unit_selected_changed(_unit: Unit, selected: bool) -> void:
|
||||
visible = selected
|
||||
"
|
||||
|
||||
[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_fhs1y"]
|
||||
shader = ExtResource("3_fhs1y")
|
||||
shader_parameter/key_color = Color(0, 1, 0, 1)
|
||||
shader_parameter/threshold = 0.010000000475
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_fhs1y"]
|
||||
atlas = ExtResource("3_on614")
|
||||
region = Rect2(0, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_4j20j"]
|
||||
atlas = ExtResource("3_on614")
|
||||
region = Rect2(40, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_v0xod"]
|
||||
atlas = ExtResource("3_on614")
|
||||
region = Rect2(0, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_50p1h"]
|
||||
atlas = ExtResource("3_on614")
|
||||
region = Rect2(40, 50, 40, 50)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_7jqdg"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_fhs1y")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_4j20j")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_v0xod")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_50p1h")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"idle",
|
||||
"speed": 5.0
|
||||
}]
|
||||
|
||||
[node name="Unit" type="Node2D" unique_id=1893234933 groups=["units"]]
|
||||
script = ExtResource("1_cq4v0")
|
||||
metadata/_custom_type_script = "uid://c016mxgatcpse"
|
||||
|
||||
[node name="SelectionIndicator" type="ColorRect" parent="." unique_id=1313394023]
|
||||
visible = false
|
||||
offset_right = 100.0
|
||||
offset_bottom = 100.0
|
||||
color = Color(1, 1, 0.3019608, 0.36078432)
|
||||
script = SubResource("GDScript_on614")
|
||||
|
||||
[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"]
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1796991032]
|
||||
material = SubResource("ShaderMaterial_fhs1y")
|
||||
position = Vector2(50, 50)
|
||||
sprite_frames = SubResource("SpriteFrames_7jqdg")
|
||||
animation = &"idle"
|
||||
autoplay = "idle"
|
||||
|
||||
@@ -10,22 +10,35 @@ config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Dungeon Lords"
|
||||
run/main_scene="uid://dlbuo46n6q238"
|
||||
config/name="OpenMaidEngine"
|
||||
run/main_scene="res://scenes/game.tscn"
|
||||
config/features=PackedStringArray("4.6", "Mobile")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
BattleMapHelper="*res://scripts/autoloads/battle_map_helper.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=800
|
||||
window/size/viewport_height=600
|
||||
|
||||
[input]
|
||||
|
||||
debug_toggle={
|
||||
"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":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
3d/physics_engine="Jolt Physics"
|
||||
3d/physics_engine="Dummy"
|
||||
|
||||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
rendering_device/driver.windows="d3d12"
|
||||
renderer/rendering_method="mobile"
|
||||
environment/defaults/default_clear_color=Color(0, 0, 0, 1)
|
||||
|
||||
BIN
reference_images/Screenshot 2026-04-07 075123.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
40
reference_images/Screenshot 2026-04-07 075123.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d4fwfh0cyj3bd"
|
||||
path="res://.godot/imported/Screenshot 2026-04-07 075123.png-98f6527da5d69103c0f83722dd6ba790.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://reference_images/Screenshot 2026-04-07 075123.png"
|
||||
dest_files=["res://.godot/imported/Screenshot 2026-04-07 075123.png-98f6527da5d69103c0f83722dd6ba790.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
reference_images/chip.png
Normal file
|
After Width: | Height: | Size: 463 KiB |
40
reference_images/chip.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bhtnqu000slm2"
|
||||
path="res://.godot/imported/chip.png-5735088daa8f24a5994c25e597283774.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://reference_images/chip.png"
|
||||
dest_files=["res://.godot/imported/chip.png-5735088daa8f24a5994c25e597283774.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
|
||||
@@ -1,6 +1,6 @@
|
||||
[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"]
|
||||
[ext_resource type="Script" uid="uid://bhglsexm8dtpj" path="res://scripts/units/unit_allegiance.gd" id="1_40cg2"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_40cg2")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[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"]
|
||||
[ext_resource type="Script" uid="uid://bhglsexm8dtpj" path="res://scripts/units/unit_allegiance.gd" id="1_4mkdx"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_4mkdx")
|
||||
|
||||
@@ -1,836 +1,36 @@
|
||||
[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"]
|
||||
[ext_resource type="Texture2D" uid="uid://65rmoynep5hy" path="res://assets/sprites/MP000A.BMP" id="1_v1d02"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_5lppa"]
|
||||
texture = ExtResource("1_v1d02")
|
||||
texture_region_size = Vector2i(48, 48)
|
||||
texture_region_size = Vector2i(100, 100)
|
||||
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")
|
||||
tile_size = Vector2i(100, 100)
|
||||
sources/1 = SubResource("TileSetAtlasSource_5lppa")
|
||||
|
||||
@@ -1,6 +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"]
|
||||
[ext_resource type="FontFile" uid="uid://1a55lafcbss" path="res://assets/fonts/MS Gothic.ttf" id="1_nmd6r"]
|
||||
|
||||
[resource]
|
||||
default_font = ExtResource("1_nmd6r")
|
||||
|
||||
178
resources/units/appearance_sets/lily_child_deployed.tres
Normal file
@@ -0,0 +1,178 @@
|
||||
[gd_resource type="Resource" script_class="UnitAppearanceSet" format=3 uid="uid://c18djmm6orf5y"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://divxkbo321ql" path="res://scripts/units/unit_appearance_set.gd" id="1_am7go"]
|
||||
[ext_resource type="Texture2D" uid="uid://b6smsdyydtiv4" path="res://assets/sprites/CP002AB.BMP" id="1_cdqv0"]
|
||||
[ext_resource type="Texture2D" uid="uid://dyutp4m5d53gd" path="res://assets/sprites/CP002AA.BMP" id="2_3pgcd"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_4hgk0"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(0, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_scnul"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(40, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ec51f"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(80, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ix7mx"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(120, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_su6vo"]
|
||||
atlas = ExtResource("2_3pgcd")
|
||||
region = Rect2(0, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6idlb"]
|
||||
atlas = ExtResource("2_3pgcd")
|
||||
region = Rect2(40, 0, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ui8f1"]
|
||||
atlas = ExtResource("2_3pgcd")
|
||||
region = Rect2(0, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_wk1hp"]
|
||||
atlas = ExtResource("2_3pgcd")
|
||||
region = Rect2(40, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_p4e4i"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(0, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_knbtp"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(40, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ni45v"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(80, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_a51r0"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(120, 50, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_pvxdl"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(0, 150, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qle0x"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(40, 150, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_iork5"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(80, 150, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hbqxy"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(120, 150, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_e4j68"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(0, 100, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_n1qde"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(40, 100, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_de2lv"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(80, 100, 40, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1nls0"]
|
||||
atlas = ExtResource("1_cdqv0")
|
||||
region = Rect2(120, 100, 40, 50)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_psufo"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_4hgk0")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_scnul")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ec51f")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ix7mx")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"down",
|
||||
"speed": 5.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_su6vo")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_6idlb")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ui8f1")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_wk1hp")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"idle",
|
||||
"speed": 5.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_p4e4i")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_knbtp")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ni45v")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_a51r0")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"left",
|
||||
"speed": 5.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_pvxdl")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qle0x")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_iork5")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_hbqxy")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"right",
|
||||
"speed": 5.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_e4j68")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_n1qde")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_de2lv")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1nls0")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"up",
|
||||
"speed": 5.0
|
||||
}]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_am7go")
|
||||
deployed_sprite_sheet = SubResource("SpriteFrames_psufo")
|
||||
metadata/_custom_type_script = "uid://divxkbo321ql"
|
||||
33
resources/units/lily_child.tres
Normal file
@@ -0,0 +1,33 @@
|
||||
[gd_resource type="Resource" script_class="Unit" format=3 uid="uid://sqrlba15ncyr"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c016mxgatcpse" path="res://scripts/units/unit.gd" id="1_bqd3m"]
|
||||
[ext_resource type="Script" uid="uid://divxkbo321ql" path="res://scripts/units/unit_appearance_set.gd" id="1_lko56"]
|
||||
[ext_resource type="Script" uid="uid://b67rtbb5gixus" path="res://scripts/battle/combat_tactics/combat_tactic.gd" id="2_0tmvt"]
|
||||
[ext_resource type="Resource" uid="uid://c18djmm6orf5y" path="res://resources/units/appearance_sets/lily_child_deployed.tres" id="2_f8ij3"]
|
||||
[ext_resource type="Script" uid="uid://b402hsmbaj536" path="res://scripts/units/unit_appearance.gd" id="2_nxnrh"]
|
||||
[ext_resource type="Script" uid="uid://d37ulss2k0bq5" path="res://scripts/units/unit_info.gd" id="3_f8ij3"]
|
||||
[ext_resource type="Script" uid="uid://cydoey8a8nmb8" path="res://scripts/units/unit_stats.gd" id="5_rqhbp"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ki4ax"]
|
||||
script = ExtResource("2_nxnrh")
|
||||
appearance_sets = Dictionary[String, ExtResource("1_lko56")]({
|
||||
"default": ExtResource("2_f8ij3")
|
||||
})
|
||||
metadata/_custom_type_script = "uid://b402hsmbaj536"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_q2jxx"]
|
||||
script = ExtResource("3_f8ij3")
|
||||
name = "Lily"
|
||||
metadata/_custom_type_script = "uid://d37ulss2k0bq5"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_nc6h6"]
|
||||
script = ExtResource("5_rqhbp")
|
||||
max_hp = 156
|
||||
metadata/_custom_type_script = "uid://cydoey8a8nmb8"
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_bqd3m")
|
||||
stats = SubResource("Resource_nc6h6")
|
||||
info = SubResource("Resource_q2jxx")
|
||||
appearance = SubResource("Resource_ki4ax")
|
||||
metadata/_custom_type_script = "uid://c016mxgatcpse"
|
||||
BIN
room_wall_example.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
40
room_wall_example.webp.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://2d7pwqqr03ra"
|
||||
path="res://.godot/imported/room_wall_example.webp-46656a4dae88a645a1bd5646f3349f4d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://room_wall_example.webp"
|
||||
dest_files=["res://.godot/imported/room_wall_example.webp-46656a4dae88a645a1bd5646f3349f4d.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
|
||||
280
scenes/dialogue_scene.tscn
Normal file
@@ -0,0 +1,280 @@
|
||||
[gd_scene format=3 uid="uid://c35md0oc82je2"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://cyl18yt5hxyb5" path="res://assets/sprites/dialogue_ui.BMP" id="1_cno1m"]
|
||||
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_qpcyj"]
|
||||
[ext_resource type="Texture2D" uid="uid://dj621xih5cam7" path="res://assets/sprites/dialogue_continue.BMP" id="2_qpcyj"]
|
||||
[ext_resource type="AudioStream" uid="uid://5ndo4w06umsa" path="res://assets/sounds/SE020.WAV" id="4_hover"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_hover"]
|
||||
resource_name = "DialogueButtonHover"
|
||||
script/source = "extends HBoxContainer
|
||||
|
||||
@onready var hover_sfx: AudioStreamPlayer = $HoverSFX
|
||||
@onready var hover_node: TextureRect = $Hover
|
||||
|
||||
func _ready() -> void:
|
||||
hover_node.visible = false
|
||||
for child in get_children():
|
||||
if child is TextureButton:
|
||||
child.mouse_entered.connect(_on_button_hovered.bind(child))
|
||||
child.mouse_exited.connect(_on_button_unhovered)
|
||||
|
||||
func _on_button_hovered(button: TextureButton) -> void:
|
||||
hover_sfx.play()
|
||||
hover_node.reparent(button, false)
|
||||
hover_node.visible = true
|
||||
|
||||
func _on_button_unhovered() -> void:
|
||||
hover_node.visible = false
|
||||
"
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cno1m"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_bhyd5"]
|
||||
blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_2e7es"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(0, 0, 800, 228)
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_bhyd5"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xcky8"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(0, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_q4mab"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(30, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_o6y45"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(60, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_18hkw"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(90, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_w4lkc"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(120, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kolsc"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(150, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6myds"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(180, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6ge27"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(210, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_i6h2p"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(240, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6kf4t"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(270, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dox35"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(300, 0, 30, 27)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lapyf"]
|
||||
atlas = ExtResource("2_qpcyj")
|
||||
region = Rect2(330, 0, 30, 27)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_61f6o"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xcky8")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_q4mab")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_o6y45")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_18hkw")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_w4lkc")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_kolsc")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_6myds")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_6ge27")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_i6h2p")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_6kf4t")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_dox35")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_lapyf")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"default",
|
||||
"speed": 10.0
|
||||
}]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_cno1m"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(1, 257, 22, 25)
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_qpcyj"]
|
||||
blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_gkt67"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(114, 255, 18, 17)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qpcyj"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(23, 257, 22, 25)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bhyd5"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(45, 257, 22, 25)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_61f6o"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(67, 257, 22, 25)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6ui40"]
|
||||
atlas = ExtResource("1_cno1m")
|
||||
region = Rect2(89, 257, 22, 25)
|
||||
|
||||
[node name="DialogueScene" type="Control" unique_id=1681812093]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_qpcyj")
|
||||
|
||||
[node name="BackgroundColor" type="Panel" parent="." unique_id=452136654]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_cno1m")
|
||||
|
||||
[node name="VerticalScreen" type="VBoxContainer" parent="." unique_id=529668640]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="DialogueBox" type="TextureRect" parent="VerticalScreen" unique_id=1915405731]
|
||||
self_modulate = Color(1, 1, 1, 0.078431375)
|
||||
material = SubResource("CanvasItemMaterial_bhyd5")
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
texture = SubResource("AtlasTexture_2e7es")
|
||||
|
||||
[node name="Text" type="VBoxContainer" parent="VerticalScreen/DialogueBox" unique_id=939057416]
|
||||
layout_mode = 0
|
||||
offset_left = 50.0
|
||||
offset_top = 100.0
|
||||
offset_right = 750.0
|
||||
offset_bottom = 175.0
|
||||
|
||||
[node name="Speaker" type="RichTextLabel" parent="VerticalScreen/DialogueBox/Text" unique_id=956457649]
|
||||
custom_minimum_size = Vector2(0, 25)
|
||||
layout_mode = 2
|
||||
bbcode_enabled = true
|
||||
text = "Test Speaker"
|
||||
scroll_active = false
|
||||
|
||||
[node name="Body" type="Label" parent="VerticalScreen/DialogueBox/Text" unique_id=364265039]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Lorem ipsum dolor"
|
||||
label_settings = SubResource("LabelSettings_bhyd5")
|
||||
|
||||
[node name="VerticalDBox" type="VBoxContainer" parent="VerticalScreen/DialogueBox" unique_id=1761650639]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="BottomRow" type="HBoxContainer" parent="VerticalScreen/DialogueBox/VerticalDBox" unique_id=1729668164]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="Control" type="Control" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow" unique_id=924058102]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ContinuePlacer" type="Control" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow" unique_id=1584205137]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ContinueSprite" type="AnimatedSprite2D" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/ContinuePlacer" unique_id=1540136989]
|
||||
position = Vector2(0, 5)
|
||||
sprite_frames = SubResource("SpriteFrames_61f6o")
|
||||
autoplay = "default"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow" unique_id=1533161680]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="DialogueButtons" type="HBoxContainer" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow" unique_id=1572455129]
|
||||
layout_mode = 2
|
||||
script = SubResource("GDScript_hover")
|
||||
|
||||
[node name="HoverSFX" type="AudioStreamPlayer" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons"]
|
||||
stream = ExtResource("4_hover")
|
||||
|
||||
[node name="Hover" type="TextureRect" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons" unique_id=1721109871]
|
||||
self_modulate = Color(1, 1, 1, 0.28235295)
|
||||
material = SubResource("CanvasItemMaterial_qpcyj")
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_top = -7.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 18.0
|
||||
texture = SubResource("AtlasTexture_gkt67")
|
||||
|
||||
[node name="History" type="TextureButton" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons" unique_id=535301616]
|
||||
layout_mode = 2
|
||||
texture_normal = SubResource("AtlasTexture_cno1m")
|
||||
|
||||
[node name="Auto" type="TextureButton" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons" unique_id=1254069471]
|
||||
layout_mode = 2
|
||||
texture_normal = SubResource("AtlasTexture_qpcyj")
|
||||
|
||||
[node name="FastForward" type="TextureButton" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons" unique_id=1088780299]
|
||||
layout_mode = 2
|
||||
texture_normal = SubResource("AtlasTexture_bhyd5")
|
||||
|
||||
[node name="Skip" type="TextureButton" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons" unique_id=1854859021]
|
||||
layout_mode = 2
|
||||
texture_normal = SubResource("AtlasTexture_61f6o")
|
||||
|
||||
[node name="Hide" type="TextureButton" parent="VerticalScreen/DialogueBox/VerticalDBox/BottomRow/DialogueButtons" unique_id=1549897664]
|
||||
layout_mode = 2
|
||||
texture_normal = SubResource("AtlasTexture_6ui40")
|
||||
12
scenes/game.tscn
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_scene format=3 uid="uid://gfrxev22t0bc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ifv6cww6fk6c" path="res://scripts/game.gd" id="1_script"]
|
||||
[ext_resource type="PackedScene" path="res://prefabs/debug_menu.tscn" id="2_debug_menu"]
|
||||
|
||||
[node name="Game" type="Node" unique_id=906681388]
|
||||
process_mode = 1
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="DebugMenu" parent="." unique_id=486799252 instance=ExtResource("2_debug_menu")]
|
||||
|
||||
[node name="ActiveSceneContainer" type="Node" parent="." unique_id=2133680613]
|
||||
@@ -1,8 +1,47 @@
|
||||
[gd_scene format=3 uid="uid://b7hhdysqqmx4y"]
|
||||
[gd_scene format=4 uid="uid://b7hhdysqqmx4y"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://65rmoynep5hy" path="res://assets/sprites/MP000A.BMP" id="1_7ddre"]
|
||||
[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"]
|
||||
[ext_resource type="Texture2D" uid="uid://b20mhn7ca5xyo" path="res://assets/sprites/aux_terrain.BMP" id="5_qjeyg"]
|
||||
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/battle/camera_controller.gd" id="6_wtsjf"]
|
||||
[ext_resource type="PackedScene" uid="uid://bc5a7tb0my6n5" path="res://prefabs/stylized_number_display.tscn" id="7_rnaij"]
|
||||
[ext_resource type="PackedScene" uid="uid://8edgswcwdiwu" path="res://prefabs/chip_bar.tscn" id="8_h3xc6"]
|
||||
[ext_resource type="Texture2D" uid="uid://cavpqnd0qqoou" path="res://assets/ui/SO008B.BMP" id="9_s36qc"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_qjeyg"]
|
||||
texture = ExtResource("1_7ddre")
|
||||
texture_region_size = Vector2i(100, 100)
|
||||
0:0/0 = 0
|
||||
1:0/0 = 0
|
||||
2:0/0 = 0
|
||||
3:0/0 = 0
|
||||
4:0/0 = 0
|
||||
0:1/0 = 0
|
||||
1:1/0 = 0
|
||||
2:1/0 = 0
|
||||
3:1/0 = 0
|
||||
4:1/0 = 0
|
||||
0:2/0 = 0
|
||||
1:2/0 = 0
|
||||
2:2/0 = 0
|
||||
3:2/0 = 0
|
||||
4:2/0 = 0
|
||||
0:3/0 = 0
|
||||
1:3/0 = 0
|
||||
2:3/0 = 0
|
||||
3:3/0 = 0
|
||||
4:3/0 = 0
|
||||
0:4/0 = 0
|
||||
1:4/0 = 0
|
||||
2:4/0 = 0
|
||||
3:4/0 = 0
|
||||
4:4/0 = 0
|
||||
|
||||
[sub_resource type="TileSet" id="TileSet_3qnke"]
|
||||
tile_size = Vector2i(100, 100)
|
||||
sources/0 = SubResource("TileSetAtlasSource_qjeyg")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_qjeyg"]
|
||||
shader = ExtResource("1_nd71p")
|
||||
@@ -14,8 +53,65 @@ shader_parameter/chroma_threshold = 0.10000000475
|
||||
atlas = ExtResource("1_g7g4h")
|
||||
region = Rect2(0, 100, 100, 100)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_3qnke"]
|
||||
atlas = ExtResource("5_qjeyg")
|
||||
region = Rect2(210, 0, 41, 32)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6qxox"]
|
||||
atlas = ExtResource("9_s36qc")
|
||||
region = Rect2(249, 272, 3, 14)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_gthg3"]
|
||||
atlas = ExtResource("9_s36qc")
|
||||
region = Rect2(246, 272, 3, 14)
|
||||
|
||||
[node name="TestScene" type="Node2D" unique_id=1687841395]
|
||||
|
||||
[node name="TileMapLayer" type="TileMapLayer" parent="." unique_id=265586128]
|
||||
tile_map_data = PackedByteArray("AAD+//7/AAAAAAAAAAD+////AAAAAAEAAAD+/wAAAAAAAAIAAAD+/wEAAAAAAAMAAAD+/wIAAAAAAAQAAAD///7/AAABAAAAAAD/////AAABAAEAAAD//wAAAAABAAIAAAD//wEAAAABAAMAAAD//wIAAAABAAQAAAAAAP7/AAACAAAAAAAAAP//AAACAAEAAAAAAAAAAAACAAIAAAAAAAEAAAACAAMAAAAAAAIAAAACAAQAAAABAP7/AAADAAAAAAABAP//AAADAAEAAAABAAAAAAADAAIAAAABAAEAAAADAAMAAAABAAIAAAADAAQAAAACAP7/AAAEAAAAAAACAP//AAAEAAEAAAACAAAAAAAEAAIAAAACAAEAAAAEAAMAAAACAAIAAAAEAAQAAAADAP7/AAAAAAAAAAADAP//AAAAAAEAAAADAAAAAAAAAAIAAAADAAEAAAAAAAMAAAADAAIAAAAAAAQAAAAEAP7/AAABAAAAAAAEAP//AAABAAEAAAAEAAAAAAABAAIAAAAEAAEAAAABAAMAAAAEAAIAAAABAAQAAAAFAP7/AAACAAAAAAAFAP//AAACAAEAAAAFAAAAAAACAAIAAAAFAAEAAAACAAMAAAAFAAIAAAACAAQAAAAGAP7/AAADAAAAAAAGAP//AAADAAEAAAAGAAAAAAADAAIAAAAGAAEAAAADAAMAAAAGAAIAAAADAAQAAAAHAP7/AAAEAAAAAAAHAP//AAAEAAEAAAAHAAAAAAAEAAIAAAAHAAEAAAAEAAMAAAAHAAIAAAAEAAQAAAA=")
|
||||
tile_set = SubResource("TileSet_3qnke")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1997336331]
|
||||
material = SubResource("ShaderMaterial_qjeyg")
|
||||
position = Vector2(-150, -148)
|
||||
texture = SubResource("AtlasTexture_j8ivh")
|
||||
|
||||
[node name="Sprite2D2" type="Sprite2D" parent="." unique_id=1517711877]
|
||||
texture = SubResource("AtlasTexture_3qnke")
|
||||
|
||||
[node name="CameraController" type="Camera2D" parent="." unique_id=1277373781]
|
||||
script = ExtResource("6_wtsjf")
|
||||
metadata/_custom_type_script = "uid://csdcbi2gtwrly"
|
||||
|
||||
[node name="Control" type="Control" parent="." unique_id=794632273]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="StylizedNumberDisplay" parent="Control" unique_id=702997768 instance=ExtResource("7_rnaij")]
|
||||
layout_mode = 0
|
||||
anchors_preset = 0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 50.0
|
||||
grow_horizontal = 1
|
||||
grow_vertical = 1
|
||||
number_sprite_width = 27
|
||||
number_sprite_height = 36
|
||||
value = 1234567890
|
||||
|
||||
[node name="ChipBar" parent="Control" unique_id=379110810 instance=ExtResource("8_h3xc6")]
|
||||
layout_mode = 1
|
||||
offset_left = -87.0
|
||||
offset_top = -101.0
|
||||
offset_right = -37.0
|
||||
offset_bottom = -1.0
|
||||
value = 5
|
||||
max_value = 5
|
||||
max_chips_per_row = 10
|
||||
empty_chip_texture = SubResource("AtlasTexture_6qxox")
|
||||
filled_chip_texture = SubResource("AtlasTexture_gthg3")
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
[gd_scene format=3 uid="uid://wy7ur5r23ek3"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cy7r0udfcsqbn" path="res://prefabs/combat_ui.tscn" id="1_6gip4"]
|
||||
[ext_resource type="PackedScene" uid="uid://dkhyh5ce4iuk3" path="res://prefabs/combat_map.tscn" id="2_iuoca"]
|
||||
[ext_resource type="Script" uid="uid://dfojm3n0em4ef" path="res://nodes/player_controller.gd" id="3_esrqm"]
|
||||
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/camera_controller.gd" id="4_ww3c6"]
|
||||
[ext_resource type="AudioStream" uid="uid://dsikulned64qt" path="res://assets/music/combat_bgm_01.OGG" id="5_ficdm"]
|
||||
[ext_resource type="Script" uid="uid://cf4ivrcbky0s3" path="res://nodes/combat_system.gd" id="6_combat"]
|
||||
[ext_resource type="Script" uid="uid://dnsqtsx4u2hx4" path="res://nodes/strategy_phase.gd" id="7_strat"]
|
||||
[ext_resource type="Script" uid="uid://dnsqtsx4u2hx4" path="res://scripts/battle/strategy_phase.gd" id="1_qs1ys"]
|
||||
[ext_resource type="PackedScene" uid="uid://cy7r0udfcsqbn" path="res://prefabs/combat_ui.tscn" id="2_4s0rq"]
|
||||
[ext_resource type="PackedScene" uid="uid://dkhyh5ce4iuk3" path="res://prefabs/combat_map.tscn" id="3_n1a8d"]
|
||||
[ext_resource type="Script" uid="uid://dfojm3n0em4ef" path="res://scripts/battle/player_controller.gd" id="4_208pr"]
|
||||
[ext_resource type="Script" uid="uid://cf4ivrcbky0s3" path="res://scripts/battle/combat_engine/combat_system.gd" id="5_n11my"]
|
||||
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/battle/camera_controller.gd" id="6_m48os"]
|
||||
[ext_resource type="AudioStream" uid="uid://dsikulned64qt" path="res://assets/music/combat_bgm_01.OGG" id="7_oih6t"]
|
||||
|
||||
[node name="CombatTest" type="Node2D" unique_id=855645983]
|
||||
script = ExtResource("7_strat")
|
||||
[node name="BattleView" type="Node2D" unique_id=855645983]
|
||||
script = ExtResource("1_qs1ys")
|
||||
|
||||
[node name="CombatUI" parent="." unique_id=329168107 instance=ExtResource("1_6gip4")]
|
||||
[node name="CombatUI" parent="." unique_id=329168107 instance=ExtResource("2_4s0rq")]
|
||||
|
||||
[node name="CombatMap" parent="." unique_id=546780706 instance=ExtResource("2_iuoca")]
|
||||
[node name="CombatMap" parent="." unique_id=546780706 instance=ExtResource("3_n1a8d")]
|
||||
|
||||
[node name="PlayerController" type="Node" parent="." unique_id=774568109 node_paths=PackedStringArray("dl_map")]
|
||||
script = ExtResource("3_esrqm")
|
||||
script = ExtResource("4_208pr")
|
||||
dl_map = NodePath("../CombatMap")
|
||||
|
||||
[node name="CombatSystem" type="Node" parent="." unique_id=1234567890]
|
||||
script = ExtResource("6_combat")
|
||||
script = ExtResource("5_n11my")
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="." unique_id=1739569732]
|
||||
position = Vector2(384, 216)
|
||||
zoom = Vector2(1.5, 1.5)
|
||||
script = ExtResource("4_ww3c6")
|
||||
script = ExtResource("6_m48os")
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1057500234]
|
||||
stream = ExtResource("5_ficdm")
|
||||
stream = ExtResource("7_oih6t")
|
||||
autoplay = true
|
||||
456
scenes/views/main_menu_view.tscn
Normal file
@@ -0,0 +1,456 @@
|
||||
[gd_scene format=3 uid="uid://dlbuo46n6q238"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_wmt4g"]
|
||||
[ext_resource type="AudioStream" uid="uid://b7dgmblbcm0cj" path="res://assets/music/menu_theme.OGG" id="2_0dhhe"]
|
||||
[ext_resource type="Texture2D" uid="uid://b47b6tt142b25" path="res://assets/sprites/main_menu.BMP" id="3_xgjk6"]
|
||||
[ext_resource type="AudioStream" uid="uid://5ndo4w06umsa" path="res://assets/sounds/SE020.WAV" id="4_somrw"]
|
||||
[ext_resource type="AudioStream" uid="uid://d1hacs4t5qni1" path="res://assets/sounds/SE015.WAV" id="5_ybnw1"]
|
||||
[ext_resource type="Texture2D" uid="uid://8kr4vmvhu03p" path="res://assets/sprites/menu_selector_flame.BMP" id="6_5jfhr"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_wu84c"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(0, 0, 800, 400)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_8ln24"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(0, 600, 800, 348)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_a8gd2"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(800, 0, 745, 745)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bqqt6"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(-1, 995, 800, 43)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_rtw2f"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(0, 950, 800, 45)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_oa1go"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(800, 744, 515, 210)
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_hover"]
|
||||
resource_name = "ButtonHoverSFX"
|
||||
script/source = "extends VBoxContainer
|
||||
|
||||
@onready var hover_sfx: AudioStreamPlayer = $HoverSFX
|
||||
@onready var left_indicator: Control = %LeftIndicator
|
||||
@onready var right_indicator: Control = %RightIndicator
|
||||
@onready var click_sfx: AudioStreamPlayer = $ClickSFX
|
||||
|
||||
func _ready() -> void:
|
||||
for child in get_children():
|
||||
if child is TextureButton:
|
||||
child.mouse_entered.connect(_on_button_hovered.bind(child))
|
||||
child.mouse_exited.connect(_on_button_unhovered)
|
||||
child.pressed.connect(_on_button_clicked)
|
||||
|
||||
func _on_button_hovered(button: TextureButton) -> void:
|
||||
hover_sfx.play()
|
||||
var button_center := button.global_position + button.size / 2.0
|
||||
left_indicator.global_position = Vector2(button.global_position.x, button_center.y)
|
||||
right_indicator.global_position = Vector2(button.global_position.x + button.size.x, button_center.y)
|
||||
left_indicator.visible = true
|
||||
right_indicator.visible = true
|
||||
|
||||
func _on_button_unhovered() -> void:
|
||||
left_indicator.visible = false
|
||||
right_indicator.visible = false
|
||||
|
||||
func _on_button_clicked() -> void:
|
||||
click_sfx.play()
|
||||
"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tbmy8"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 0, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_jk1qb"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 300, 330, 50)
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_bqqt6"]
|
||||
resource_name = "StartButton"
|
||||
script/source = "extends TextureButton
|
||||
|
||||
const COMBAT_SCENE = preload(\"res://scenes/views/battle_view.tscn\")
|
||||
const PLAYER_ALLEGIANCE = preload(\"res://resources/allegiance_types/player_allegiance.tres\")
|
||||
const ENEMY_ALLEGIANCE = preload(\"res://resources/allegiance_types/enemy_allegiance.tres\")
|
||||
const LILY_CHILD = preload(\"res://resources/units/lily_child.tres\")
|
||||
|
||||
func _pressed() -> void:
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
var combat_instance := COMBAT_SCENE.instantiate()
|
||||
var combat_map: CombatMap = combat_instance.find_child(\"CombatMap\")
|
||||
|
||||
var player_unit: Unit = LILY_CHILD.duplicate(true)
|
||||
player_unit.allegiance = PLAYER_ALLEGIANCE
|
||||
player_unit.stats.level = 68
|
||||
combat_map.deploy_unit(player_unit, Vector2i(3, 3))
|
||||
|
||||
var enemy_unit: Unit = LILY_CHILD.duplicate(true)
|
||||
enemy_unit.allegiance = ENEMY_ALLEGIANCE
|
||||
combat_map.deploy_unit(enemy_unit, Vector2i(6, 3))
|
||||
|
||||
var tree := get_tree()
|
||||
var root := tree.root
|
||||
var current_scene := tree.current_scene
|
||||
root.remove_child(current_scene)
|
||||
current_scene.queue_free()
|
||||
root.add_child(combat_instance)
|
||||
tree.current_scene = combat_instance
|
||||
"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5dd4i"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 60, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lgwnu"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 360, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_flqon"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 120, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_rcqid"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 420, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1ajci"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 180, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_7b55j"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 480, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5pajh"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 240, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_j7ex8"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 540, 330, 50)
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_wu84c"]
|
||||
resource_name = "ExitButton"
|
||||
script/source = "extends TextureButton
|
||||
|
||||
func _pressed():
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
get_tree().quit(0)
|
||||
"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tcusk"]
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1320, 746, 25, 22)
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_8ln24"]
|
||||
blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_8egab"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_gw5y6"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_svtp6"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1dfpl"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qywvv"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_3wgol"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1acrt"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vr8o3"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1a85y"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hl5e0"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_engjn"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6h3lr"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dj67d"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 420, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6vcge"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 420, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ip0br"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 420, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xyero"]
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 420, 140, 140)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_tcusk"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_8egab")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_gw5y6")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_svtp6")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1dfpl")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qywvv")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_3wgol")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1acrt")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_vr8o3")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1a85y")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_hl5e0")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_engjn")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_6h3lr")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_dj67d")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_6vcge")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ip0br")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xyero")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"default",
|
||||
"speed": 10.0
|
||||
}]
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_rtw2f"]
|
||||
blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hstxw"]
|
||||
|
||||
[node name="MainMenu" 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_wmt4g")
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1976575731]
|
||||
stream = ExtResource("2_0dhhe")
|
||||
autoplay = true
|
||||
parameters/looping = true
|
||||
|
||||
[node name="Background" type="Control" parent="." unique_id=801579001]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Top" type="TextureRect" parent="Background" unique_id=2030397311]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 400.0
|
||||
grow_horizontal = 2
|
||||
texture = SubResource("AtlasTexture_wu84c")
|
||||
|
||||
[node name="Bottom" type="TextureRect" parent="Background" unique_id=736979824]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -348.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
texture = SubResource("AtlasTexture_8ln24")
|
||||
|
||||
[node name="MagicCircle" type="TextureRect" parent="Background" unique_id=1610277203]
|
||||
self_modulate = Color(1, 1, 1, 0.6156863)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -72.5
|
||||
offset_bottom = 72.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("AtlasTexture_a8gd2")
|
||||
|
||||
[node name="BottomBorder" type="TextureRect" parent="Background" unique_id=2048064934]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -45.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
texture = SubResource("AtlasTexture_bqqt6")
|
||||
|
||||
[node name="TopBorder" type="TextureRect" parent="Background" unique_id=812827884]
|
||||
layout_mode = 0
|
||||
offset_right = 800.0
|
||||
offset_bottom = 45.0
|
||||
texture = SubResource("AtlasTexture_rtw2f")
|
||||
|
||||
[node name="Logo" type="TextureRect" parent="Background" unique_id=815631332]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -257.5
|
||||
offset_top = 50.0
|
||||
offset_right = 257.5
|
||||
offset_bottom = 210.0
|
||||
grow_horizontal = 2
|
||||
texture = SubResource("AtlasTexture_oa1go")
|
||||
|
||||
[node name="Buttons" type="VBoxContainer" parent="." unique_id=1869378860]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 0.29375
|
||||
anchor_top = 0.5566667
|
||||
anchor_right = 0.70625
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 1.5258789e-05
|
||||
offset_bottom = -55.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
alignment = 1
|
||||
script = SubResource("GDScript_hover")
|
||||
metadata/_edit_use_anchors_ = true
|
||||
|
||||
[node name="HoverSFX" type="AudioStreamPlayer" parent="Buttons" unique_id=256435189]
|
||||
stream = ExtResource("4_somrw")
|
||||
|
||||
[node name="ClickSFX" type="AudioStreamPlayer" parent="Buttons" unique_id=2129807302]
|
||||
stream = ExtResource("5_ybnw1")
|
||||
|
||||
[node name="StartButton" type="TextureButton" parent="Buttons" unique_id=973041905]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
texture_normal = SubResource("AtlasTexture_tbmy8")
|
||||
texture_hover = SubResource("AtlasTexture_jk1qb")
|
||||
script = SubResource("GDScript_bqqt6")
|
||||
|
||||
[node name="LoadButton" type="TextureButton" parent="Buttons" unique_id=2075751086]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
texture_normal = SubResource("AtlasTexture_5dd4i")
|
||||
texture_hover = SubResource("AtlasTexture_lgwnu")
|
||||
|
||||
[node name="EushullyButton" type="TextureButton" parent="Buttons" unique_id=412756984]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
texture_normal = SubResource("AtlasTexture_flqon")
|
||||
texture_hover = SubResource("AtlasTexture_rcqid")
|
||||
|
||||
[node name="OptionsButton" type="TextureButton" parent="Buttons" unique_id=1002907774]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
texture_normal = SubResource("AtlasTexture_1ajci")
|
||||
texture_hover = SubResource("AtlasTexture_7b55j")
|
||||
|
||||
[node name="ExitButton" type="TextureButton" parent="Buttons" unique_id=286651369]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
texture_normal = SubResource("AtlasTexture_5pajh")
|
||||
texture_hover = SubResource("AtlasTexture_j7ex8")
|
||||
script = SubResource("GDScript_wu84c")
|
||||
|
||||
[node name="LeftIndicator" type="Control" parent="." unique_id=100000001]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
anchors_preset = 0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 50.0
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="LeftIndicator" unique_id=1510731086]
|
||||
position = Vector2(0, 15)
|
||||
texture = SubResource("AtlasTexture_tcusk")
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="LeftIndicator" unique_id=1906133697]
|
||||
material = SubResource("CanvasItemMaterial_8ln24")
|
||||
scale = Vector2(0.75, 0.75)
|
||||
sprite_frames = SubResource("SpriteFrames_tcusk")
|
||||
autoplay = "default"
|
||||
frame = 12
|
||||
frame_progress = 0.23836201
|
||||
|
||||
[node name="RightIndicator" type="Control" parent="." unique_id=100000002]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
anchors_preset = 0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 50.0
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="RightIndicator" unique_id=979863490]
|
||||
position = Vector2(0, 15)
|
||||
texture = SubResource("AtlasTexture_tcusk")
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="RightIndicator" unique_id=1047794140]
|
||||
material = SubResource("CanvasItemMaterial_rtw2f")
|
||||
scale = Vector2(0.75, 0.75)
|
||||
sprite_frames = SubResource("SpriteFrames_tcusk")
|
||||
autoplay = "default"
|
||||
frame = 12
|
||||
frame_progress = 0.23836201
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="." unique_id=968381019]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
texture = SubResource("AtlasTexture_hstxw")
|
||||
@@ -1,34 +1,34 @@
|
||||
[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"]
|
||||
[ext_resource type="Texture2D" uid="uid://b47b6tt142b25" path="res://assets/sprites/main_menu.BMP" id="3_bqqt6"]
|
||||
[ext_resource type="AudioStream" uid="uid://5ndo4w06umsa" path="res://assets/sounds/SE020.WAV" id="4_wu84c"]
|
||||
[ext_resource type="Texture2D" uid="uid://8kr4vmvhu03p" path="res://assets/sprites/menu_selector_flame.BMP" id="5_flame"]
|
||||
[ext_resource type="AudioStream" uid="uid://d1hacs4t5qni1" path="res://assets/sounds/SE015.WAV" id="5_rtw2f"]
|
||||
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_wmt4g"]
|
||||
[ext_resource type="AudioStream" uid="uid://b7dgmblbcm0cj" path="res://assets/music/menu_theme.OGG" id="2_0dhhe"]
|
||||
[ext_resource type="Texture2D" uid="uid://b47b6tt142b25" path="res://assets/sprites/main_menu.BMP" id="3_xgjk6"]
|
||||
[ext_resource type="AudioStream" uid="uid://5ndo4w06umsa" path="res://assets/sounds/SE020.WAV" id="4_somrw"]
|
||||
[ext_resource type="AudioStream" uid="uid://d1hacs4t5qni1" path="res://assets/sounds/SE015.WAV" id="5_ybnw1"]
|
||||
[ext_resource type="Texture2D" uid="uid://8kr4vmvhu03p" path="res://assets/sprites/menu_selector_flame.BMP" id="6_5jfhr"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_wu84c"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(0, 0, 800, 400)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_8ln24"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(0, 600, 800, 348)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_a8gd2"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(800, 0, 745, 745)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bqqt6"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(-1, 995, 800, 43)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_rtw2f"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(0, 950, 800, 45)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_oa1go"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(800, 744, 515, 210)
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_hover"]
|
||||
@@ -64,49 +64,40 @@ func _on_button_clicked() -> void:
|
||||
"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tbmy8"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 0, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_jk1qb"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 300, 330, 50)
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_bqqt6"]
|
||||
resource_name = "StartButton"
|
||||
script/source = "extends TextureButton
|
||||
|
||||
const COMBAT_SCENE = preload(\"res://scenes/strategy_phase.tscn\")
|
||||
const COMBAT_SCENE = preload(\"res://scenes/views/battle_view.tscn\")
|
||||
const UNIT_SCENE = preload(\"res://prefabs/unit.tscn\")
|
||||
const PLAYER_ALLEGIANCE = preload(\"res://resources/allegiance_types/player_allegiance.tres\")
|
||||
const ENEMY_ALLEGIANCE = preload(\"res://resources/allegiance_types/enemy_allegiance.tres\")
|
||||
|
||||
const MAP_LAYOUT := \"\"\"\\
|
||||
#####
|
||||
#...#
|
||||
#...#
|
||||
#...#
|
||||
#####\"\"\"
|
||||
|
||||
func _pressed() -> void:
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
var combat_instance := COMBAT_SCENE.instantiate()
|
||||
var combat_map: CombatMap = combat_instance.find_child(\"CombatMap\")
|
||||
|
||||
combat_map.load_map(MAP_LAYOUT)
|
||||
|
||||
var player_unit: Unit = UNIT_SCENE.instantiate()
|
||||
player_unit.stat_template = UnitStats.new(50)
|
||||
player_unit.stat_template = UnitStats.new()
|
||||
player_unit.info_template = UnitInfo.new()
|
||||
player_unit.info_template.name = \"Putit\"
|
||||
player_unit.allegiance_template = PLAYER_ALLEGIANCE
|
||||
combat_map.deploy_unit(player_unit, Vector2i(2, 2))
|
||||
combat_map.deploy_unit(player_unit, Vector2i(3, 3))
|
||||
|
||||
var enemy_unit: Unit = UNIT_SCENE.instantiate()
|
||||
enemy_unit.stat_template = UnitStats.new(50)
|
||||
enemy_unit.stat_template = UnitStats.new()
|
||||
enemy_unit.info_template = UnitInfo.new()
|
||||
enemy_unit.info_template.name = \"Putit\"
|
||||
enemy_unit.allegiance_template = ENEMY_ALLEGIANCE
|
||||
combat_map.deploy_unit(enemy_unit, Vector2i(2, 1))
|
||||
combat_map.deploy_unit(enemy_unit, Vector2i(6, 3))
|
||||
|
||||
var tree := get_tree()
|
||||
var root := tree.root
|
||||
@@ -118,35 +109,35 @@ func _pressed() -> void:
|
||||
"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5dd4i"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 60, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lgwnu"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 360, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_flqon"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 120, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_rcqid"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 420, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1ajci"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 180, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_7b55j"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 480, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5pajh"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 240, 330, 50)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_j7ex8"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1550, 540, 330, 50)
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_wu84c"]
|
||||
@@ -159,74 +150,74 @@ func _pressed():
|
||||
"
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tcusk"]
|
||||
atlas = ExtResource("3_bqqt6")
|
||||
atlas = ExtResource("3_xgjk6")
|
||||
region = Rect2(1320, 746, 25, 22)
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_8ln24"]
|
||||
blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_8egab"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_gw5y6"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_svtp6"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1dfpl"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 0, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qywvv"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_3wgol"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1acrt"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vr8o3"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 140, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1a85y"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hl5e0"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_engjn"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6h3lr"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 280, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dj67d"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(0, 420, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_6vcge"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(140, 420, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ip0br"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(280, 420, 140, 140)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xyero"]
|
||||
atlas = ExtResource("5_flame")
|
||||
atlas = ExtResource("6_5jfhr")
|
||||
region = Rect2(420, 420, 140, 140)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_tcusk"]
|
||||
@@ -290,17 +281,17 @@ blend_mode = 1
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hstxw"]
|
||||
|
||||
[node name="Menu" type="Control" unique_id=528000941]
|
||||
[node name="MainMenu" 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")
|
||||
theme = ExtResource("1_wmt4g")
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1976575731]
|
||||
stream = ExtResource("1_yqeox")
|
||||
stream = ExtResource("2_0dhhe")
|
||||
autoplay = true
|
||||
parameters/looping = true
|
||||
|
||||
@@ -388,10 +379,10 @@ script = SubResource("GDScript_hover")
|
||||
metadata/_edit_use_anchors_ = true
|
||||
|
||||
[node name="HoverSFX" type="AudioStreamPlayer" parent="Buttons" unique_id=256435189]
|
||||
stream = ExtResource("4_wu84c")
|
||||
stream = ExtResource("4_somrw")
|
||||
|
||||
[node name="ClickSFX" type="AudioStreamPlayer" parent="Buttons" unique_id=2129807302]
|
||||
stream = ExtResource("5_rtw2f")
|
||||
stream = ExtResource("5_ybnw1")
|
||||
|
||||
[node name="StartButton" type="TextureButton" parent="Buttons" unique_id=973041905]
|
||||
layout_mode = 2
|
||||
17
scripts/autoloads/battle_map_helper.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
extends Node
|
||||
|
||||
var TILE_SIZE: float:
|
||||
get:
|
||||
return BattleMapConstants.TILE_SIZE
|
||||
|
||||
|
||||
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
|
||||
1
scripts/autoloads/battle_map_helper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bt28d2xnvfjmf
|
||||
18
scripts/battle/camera_controller.gd
Normal file
@@ -0,0 +1,18 @@
|
||||
class_name CameraController extends Camera2D
|
||||
|
||||
|
||||
func apply_drag(delta: Vector2) -> void:
|
||||
var half_view: Vector2 = get_viewport_rect().size * 0.5 / zoom
|
||||
var min_pos := Vector2(limit_left, limit_top) + half_view
|
||||
var max_pos := Vector2(limit_right, limit_bottom) - half_view
|
||||
# Guard against maps smaller than the viewport (min > max)
|
||||
max_pos.x = max(min_pos.x, max_pos.x)
|
||||
max_pos.y = max(min_pos.y, max_pos.y)
|
||||
position = (position + delta / zoom).clamp(min_pos, max_pos)
|
||||
|
||||
|
||||
func set_map_bounds(rect: Rect2) -> void:
|
||||
limit_left = int(rect.position.x)
|
||||
limit_top = int(rect.position.y)
|
||||
limit_right = int(rect.position.x + rect.size.x)
|
||||
limit_bottom = int(rect.position.y + rect.size.y)
|
||||
@@ -1,7 +1,7 @@
|
||||
class_name CombatProposal extends Resource
|
||||
|
||||
class CombatantStats:
|
||||
var unit: Unit
|
||||
var deployed: DeployedUnit
|
||||
var max_hp: int
|
||||
var hp: int
|
||||
var sp: int
|
||||
@@ -9,6 +9,8 @@ class CombatantStats:
|
||||
var atk: int
|
||||
var def: int
|
||||
var spd: int
|
||||
var available_tactics: Array[CombatTactic] = []
|
||||
var selected_tactic: CombatTactic
|
||||
|
||||
var attacker: CombatantStats
|
||||
var defender: CombatantStats
|
||||
143
scripts/battle/combat_engine/combat_system.gd
Normal file
@@ -0,0 +1,143 @@
|
||||
class_name CombatSystem extends Node
|
||||
|
||||
func create_proposal(attacker: DeployedUnit, defender: DeployedUnit, distance: int) -> CombatProposal:
|
||||
var proposal := CombatProposal.new()
|
||||
|
||||
var atk_tactics := _filter_tactics(attacker, distance)
|
||||
var def_tactics := _filter_tactics(defender, distance)
|
||||
|
||||
var atk_tactic := _find_default_attack(atk_tactics)
|
||||
var def_tactic := _find_default_attack(def_tactics)
|
||||
|
||||
# AI auto-selects for non-player units
|
||||
if not _is_player_controlled(defender):
|
||||
def_tactic = select_ai_tactic(defender, attacker, def_tactics)
|
||||
|
||||
proposal.attacker = _snapshot(attacker, defender, atk_tactics, atk_tactic, def_tactic)
|
||||
proposal.defender = _snapshot(defender, attacker, def_tactics, def_tactic, atk_tactic)
|
||||
|
||||
return proposal
|
||||
|
||||
|
||||
func _filter_tactics(deployed: DeployedUnit, distance: int) -> Array[CombatTactic]:
|
||||
var valid: Array[CombatTactic] = []
|
||||
for tactic in deployed.tactics:
|
||||
if tactic.tactic_range and tactic.tactic_range.is_valid_range(distance, deployed.current_stats):
|
||||
valid.append(tactic)
|
||||
return valid
|
||||
|
||||
|
||||
func _find_default_attack(tactics: Array[CombatTactic]) -> CombatTactic:
|
||||
for tactic in tactics:
|
||||
if tactic is AttackCombatTactic:
|
||||
return tactic
|
||||
return tactics[0] if tactics.size() > 0 else null
|
||||
|
||||
|
||||
func _snapshot(deployed: DeployedUnit, opponent: DeployedUnit, available: Array[CombatTactic], selected: CombatTactic, opponent_selected: CombatTactic) -> CombatProposal.CombatantStats:
|
||||
var current := deployed.current_stats
|
||||
var opp_current := opponent.current_stats
|
||||
var stats := CombatProposal.CombatantStats.new()
|
||||
stats.deployed = deployed
|
||||
stats.max_hp = current.max_hp
|
||||
stats.hp = current.current_hp
|
||||
stats.sp = current.current_sp
|
||||
stats.spd = current.spd
|
||||
stats.available_tactics = available
|
||||
stats.selected_tactic = selected
|
||||
|
||||
if selected and selected.deals_damage():
|
||||
var offensive: Dictionary = selected.get_offensive_stats(current)
|
||||
stats.atk = offensive["atk"]
|
||||
stats.hit = offensive["hit"] - opp_current.eva
|
||||
else:
|
||||
stats.atk = 0
|
||||
stats.hit = 0
|
||||
|
||||
if opponent_selected and opponent_selected.deals_damage():
|
||||
stats.def = opponent_selected.get_relevant_defense(current)
|
||||
else:
|
||||
stats.def = current.phys_def
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
func update_tactic(proposal: CombatProposal, is_attacker: bool, tactic: CombatTactic) -> void:
|
||||
var self_stats: CombatProposal.CombatantStats
|
||||
var opp_stats: CombatProposal.CombatantStats
|
||||
if is_attacker:
|
||||
self_stats = proposal.attacker
|
||||
opp_stats = proposal.defender
|
||||
else:
|
||||
self_stats = proposal.defender
|
||||
opp_stats = proposal.attacker
|
||||
|
||||
self_stats.selected_tactic = tactic
|
||||
|
||||
# Recalculate this side's offensive stats
|
||||
if tactic and tactic.deals_damage():
|
||||
var offensive: Dictionary = tactic.get_offensive_stats(self_stats.deployed.current_stats)
|
||||
self_stats.atk = offensive["atk"]
|
||||
self_stats.hit = offensive["hit"] - opp_stats.deployed.current_stats.eva
|
||||
else:
|
||||
self_stats.atk = 0
|
||||
self_stats.hit = 0
|
||||
|
||||
# Recalculate opponent's def based on this side's new tactic
|
||||
if tactic and tactic.deals_damage():
|
||||
opp_stats.def = tactic.get_relevant_defense(opp_stats.deployed.current_stats)
|
||||
else:
|
||||
opp_stats.def = opp_stats.deployed.current_stats.phys_def
|
||||
|
||||
|
||||
func select_ai_tactic(deployed: DeployedUnit, opponent: DeployedUnit, available_tactics: Array[CombatTactic]) -> CombatTactic:
|
||||
var best_tactic: CombatTactic = null
|
||||
var best_damage := -1
|
||||
|
||||
for tactic in available_tactics:
|
||||
if not tactic.deals_damage():
|
||||
continue
|
||||
var offensive: Dictionary = tactic.get_offensive_stats(deployed.current_stats)
|
||||
var defense: int = tactic.get_relevant_defense(opponent.current_stats)
|
||||
var damage := maxi(offensive["atk"] - defense, 0)
|
||||
if damage > best_damage:
|
||||
best_damage = damage
|
||||
best_tactic = tactic
|
||||
|
||||
if best_tactic == null or best_damage <= 0:
|
||||
for tactic in available_tactics:
|
||||
if tactic is DefendCombatTactic:
|
||||
return tactic
|
||||
return available_tactics[0] if available_tactics.size() > 0 else null
|
||||
|
||||
return best_tactic
|
||||
|
||||
|
||||
func apply_proposal(proposal: CombatProposal) -> void:
|
||||
var atk_stats := proposal.attacker
|
||||
var def_stats := proposal.defender
|
||||
var atk_deployed := atk_stats.deployed
|
||||
var def_deployed := def_stats.deployed
|
||||
|
||||
if not is_instance_valid(atk_deployed) or not is_instance_valid(def_deployed):
|
||||
return
|
||||
|
||||
# Attacker strikes (if their tactic deals damage)
|
||||
if atk_stats.selected_tactic and atk_stats.selected_tactic.deals_damage():
|
||||
var atk_roll := randi_range(1, 100)
|
||||
if atk_roll <= atk_stats.hit:
|
||||
var damage := maxi(atk_stats.atk - def_stats.def, 0)
|
||||
def_deployed.take_damage(damage)
|
||||
|
||||
# Counterattack if defender survives and their tactic deals damage
|
||||
if is_instance_valid(def_deployed) and def_deployed.is_alive() \
|
||||
and is_instance_valid(atk_deployed) \
|
||||
and def_stats.selected_tactic and def_stats.selected_tactic.deals_damage():
|
||||
var def_roll := randi_range(1, 100)
|
||||
if def_roll <= def_stats.hit:
|
||||
var damage := maxi(def_stats.atk - atk_stats.def, 0)
|
||||
atk_deployed.take_damage(damage)
|
||||
|
||||
|
||||
func _is_player_controlled(deployed: DeployedUnit) -> bool:
|
||||
return deployed.unit.allegiance.type == UnitAllegiance.AllegianceType.PLAYER
|
||||
13
scripts/battle/combat_tactics/combat_tactic.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
class_name CombatTactic extends Resource
|
||||
|
||||
@export var tactic_name: String = ""
|
||||
@export var tactic_range: CombatTacticRange
|
||||
|
||||
func get_offensive_stats(_stats: DeployedUnitStats) -> Variant:
|
||||
return null
|
||||
|
||||
func get_relevant_defense(stats: DeployedUnitStats) -> int:
|
||||
return stats.phys_def
|
||||
|
||||
func deals_damage() -> bool:
|
||||
return false
|
||||
1
scripts/battle/combat_tactics/combat_tactic.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b67rtbb5gixus
|
||||
@@ -0,0 +1,5 @@
|
||||
# resources/resource_definitions/any_combat_tactic_range.gd
|
||||
class_name AnyCombatTacticRange extends CombatTacticRange
|
||||
|
||||
func is_valid_range(_distance: int, _stats: DeployedUnitStats) -> bool:
|
||||
return true
|
||||
@@ -0,0 +1 @@
|
||||
uid://danory6304bl6
|
||||
@@ -0,0 +1,5 @@
|
||||
# resources/resource_definitions/combat_tactic_range.gd
|
||||
class_name CombatTacticRange extends Resource
|
||||
|
||||
func is_valid_range(_distance: int, _stats: DeployedUnitStats) -> bool:
|
||||
return false
|
||||
@@ -0,0 +1 @@
|
||||
uid://5cr4kl14gvd7
|
||||
@@ -0,0 +1,7 @@
|
||||
# resources/resource_definitions/fixed_combat_tactic_range.gd
|
||||
class_name FixedCombatTacticRange extends CombatTacticRange
|
||||
|
||||
@export var tactic_range: int = 1
|
||||
|
||||
func is_valid_range(distance: int, _stats: DeployedUnitStats) -> bool:
|
||||
return distance <= tactic_range
|
||||
@@ -0,0 +1 @@
|
||||
uid://6jxhvwrkiq6f
|
||||
@@ -0,0 +1,5 @@
|
||||
# resources/resource_definitions/unit_matching_combat_tactic_range.gd
|
||||
class_name UnitMatchingCombatTacticRange extends CombatTacticRange
|
||||
|
||||
func is_valid_range(distance: int, stats: DeployedUnitStats) -> bool:
|
||||
return distance <= stats.atk_range
|
||||
@@ -0,0 +1 @@
|
||||
uid://7locjqufdkgj
|
||||
@@ -0,0 +1,10 @@
|
||||
class_name AttackCombatTactic extends CombatTactic
|
||||
|
||||
func get_offensive_stats(stats: DeployedUnitStats) -> Variant:
|
||||
return {"atk": stats.phys_atk, "hit": stats.hit}
|
||||
|
||||
func get_relevant_defense(stats: DeployedUnitStats) -> int:
|
||||
return stats.phys_def
|
||||
|
||||
func deals_damage() -> bool:
|
||||
return true
|
||||
@@ -0,0 +1 @@
|
||||
uid://k8xmyrygnrcl
|
||||
@@ -0,0 +1,10 @@
|
||||
class_name DefendCombatTactic extends CombatTactic
|
||||
|
||||
func get_offensive_stats(_stats: DeployedUnitStats) -> Variant:
|
||||
return null
|
||||
|
||||
func get_relevant_defense(stats: DeployedUnitStats) -> int:
|
||||
return stats.phys_def
|
||||
|
||||
func deals_damage() -> bool:
|
||||
return false
|
||||
@@ -0,0 +1 @@
|
||||
uid://dq74qh01wi7sy
|
||||
168
scripts/battle/combat_ui.gd
Normal file
@@ -0,0 +1,168 @@
|
||||
class_name CombatUI extends CanvasLayer
|
||||
|
||||
signal fight_confirmed(proposal: CombatProposal)
|
||||
signal fight_cancelled
|
||||
|
||||
@onready var unit_panel: Control = %UnitPanel
|
||||
@onready var unit_name_label: RichTextLabel = %UnitName
|
||||
@onready var level_number: StylizedNumberDisplay = %LevelNumber
|
||||
@onready var health_chip_bar: ChipBar = %HealthChipBar
|
||||
@onready var health_number: StylizedNumberDisplay = %HealthNumber
|
||||
@onready var sp_chip_bar: ChipBar = %SPChipBar
|
||||
@onready var sp_number: StylizedNumberDisplay = %SPNumber
|
||||
@onready var fs_chip_bar: ChipBar = %FSChipBar
|
||||
@onready var fs_number: StylizedNumberDisplay = %FSNumber
|
||||
|
||||
@onready var background_tint: ColorRect = %BackgroundTint
|
||||
@onready var proposal_panel: PanelContainer = %CombatProposalPanel
|
||||
@onready var atk_name_label: Label = %AttackerNameLabel
|
||||
@onready var atk_hp_bar: ProgressBar = %AttackerHPBar
|
||||
@onready var atk_atk_label: Label = %AttackerATKLabel
|
||||
@onready var atk_def_label: Label = %AttackerDEFLabel
|
||||
@onready var atk_hit_label: Label = %AttackerHITLabel
|
||||
@onready var atk_spd_label: Label = %AttackerSPDLabel
|
||||
@onready var def_name_label: Label = %DefenderNameLabel
|
||||
@onready var def_hp_bar: ProgressBar = %DefenderHPBar
|
||||
@onready var def_atk_label: Label = %DefenderATKLabel
|
||||
@onready var def_def_label: Label = %DefenderDEFLabel
|
||||
@onready var def_hit_label: Label = %DefenderHITLabel
|
||||
@onready var def_spd_label: Label = %DefenderSPDLabel
|
||||
@onready var fight_button: Button = %FightButton
|
||||
@onready var cancel_button: Button = %CancelButton
|
||||
@onready var atk_tactic_select: OptionButton = %AttackerTacticSelect
|
||||
@onready var def_tactic_select: OptionButton = %DefenderTacticSelect
|
||||
|
||||
var _selected_unit: DeployedUnit
|
||||
var _current_proposal: CombatProposal
|
||||
var combat_system: CombatSystem
|
||||
|
||||
func _ready() -> void:
|
||||
unit_panel.visible = false
|
||||
proposal_panel.visible = false
|
||||
fight_button.pressed.connect(_on_fight_pressed)
|
||||
cancel_button.pressed.connect(_on_cancel_pressed)
|
||||
atk_tactic_select.item_selected.connect(_on_atk_tactic_selected)
|
||||
def_tactic_select.item_selected.connect(_on_def_tactic_selected)
|
||||
for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"):
|
||||
deployed.unit_selected_changed.connect(_on_unit_selected_changed)
|
||||
deployed.unit_died.connect(_on_unit_died)
|
||||
get_tree().node_added.connect(_on_node_added)
|
||||
|
||||
func _on_node_added(node: Node) -> void:
|
||||
if node is DeployedUnit and node.is_in_group("deployed_units"):
|
||||
if not node.unit_selected_changed.is_connected(_on_unit_selected_changed):
|
||||
node.unit_selected_changed.connect(_on_unit_selected_changed)
|
||||
if not node.unit_died.is_connected(_on_unit_died):
|
||||
node.unit_died.connect(_on_unit_died)
|
||||
|
||||
func _on_unit_died(deployed: DeployedUnit) -> void:
|
||||
if _selected_unit == deployed:
|
||||
_selected_unit = null
|
||||
unit_panel.visible = false
|
||||
if _current_proposal:
|
||||
if _current_proposal.attacker.deployed == deployed or _current_proposal.defender.deployed == deployed:
|
||||
_hide_proposal()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _selected_unit and is_instance_valid(_selected_unit):
|
||||
_refresh_unit_panel()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if proposal_panel.visible and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
_hide_proposal()
|
||||
fight_cancelled.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_unit_selected_changed(deployed: DeployedUnit, selected: bool) -> void:
|
||||
if selected:
|
||||
_selected_unit = deployed
|
||||
_refresh_unit_panel()
|
||||
unit_panel.visible = true
|
||||
else:
|
||||
_selected_unit = null
|
||||
unit_panel.visible = false
|
||||
|
||||
func _refresh_unit_panel() -> void:
|
||||
var stats := _selected_unit.current_stats
|
||||
unit_name_label.text = "[b]%s[/b]" % _selected_unit.unit.info.name
|
||||
level_number.value = stats.level
|
||||
health_chip_bar.max_value = stats.max_hp
|
||||
health_chip_bar.value = stats.current_hp
|
||||
health_number.value = stats.current_hp
|
||||
sp_chip_bar.max_value = stats.max_sp
|
||||
sp_chip_bar.value = stats.current_sp
|
||||
sp_number.value = stats.current_sp
|
||||
fs_chip_bar.max_value = stats.max_fs
|
||||
fs_chip_bar.value = stats.current_fs
|
||||
fs_number.value = stats.current_fs
|
||||
|
||||
func show_proposal(proposal: CombatProposal) -> void:
|
||||
_current_proposal = proposal
|
||||
_populate_tactic_select(atk_tactic_select, proposal.attacker)
|
||||
_populate_tactic_select(def_tactic_select, proposal.defender)
|
||||
_refresh_stats()
|
||||
background_tint.visible = true
|
||||
proposal_panel.visible = true
|
||||
|
||||
func _hide_proposal() -> void:
|
||||
background_tint.visible = false
|
||||
proposal_panel.visible = false
|
||||
_current_proposal = null
|
||||
|
||||
func _on_fight_pressed() -> void:
|
||||
if _current_proposal:
|
||||
var proposal := _current_proposal
|
||||
_hide_proposal()
|
||||
fight_confirmed.emit(proposal)
|
||||
|
||||
func _on_cancel_pressed() -> void:
|
||||
_hide_proposal()
|
||||
fight_cancelled.emit()
|
||||
|
||||
|
||||
func _populate_tactic_select(button: OptionButton, combatant: CombatProposal.CombatantStats) -> void:
|
||||
button.clear()
|
||||
var selected_idx := 0
|
||||
for i in combatant.available_tactics.size():
|
||||
var tactic := combatant.available_tactics[i]
|
||||
button.add_item(tactic.tactic_name)
|
||||
if tactic == combatant.selected_tactic:
|
||||
selected_idx = i
|
||||
button.selected = selected_idx
|
||||
var is_player := combatant.deployed.unit.allegiance.type == UnitAllegiance.AllegianceType.PLAYER
|
||||
button.disabled = not is_player
|
||||
|
||||
|
||||
func _refresh_stats() -> void:
|
||||
var atk := _current_proposal.attacker
|
||||
var def := _current_proposal.defender
|
||||
atk_name_label.text = atk.deployed.unit.info.name
|
||||
atk_hp_bar.max_value = atk.max_hp
|
||||
atk_hp_bar.value = atk.hp
|
||||
atk_atk_label.text = "ATK: %d" % atk.atk
|
||||
atk_def_label.text = "DEF: %d" % atk.def
|
||||
atk_hit_label.text = "HIT: %d%%" % atk.hit
|
||||
atk_spd_label.text = "SPD: %d" % atk.spd
|
||||
def_name_label.text = def.deployed.unit.info.name
|
||||
def_hp_bar.max_value = def.max_hp
|
||||
def_hp_bar.value = def.hp
|
||||
def_atk_label.text = "ATK: %d" % def.atk
|
||||
def_def_label.text = "DEF: %d" % def.def
|
||||
def_hit_label.text = "HIT: %d%%" % def.hit
|
||||
def_spd_label.text = "SPD: %d" % def.spd
|
||||
|
||||
|
||||
func _on_atk_tactic_selected(index: int) -> void:
|
||||
if not _current_proposal or not combat_system:
|
||||
return
|
||||
var tactic := _current_proposal.attacker.available_tactics[index]
|
||||
combat_system.update_tactic(_current_proposal, true, tactic)
|
||||
_refresh_stats()
|
||||
|
||||
|
||||
func _on_def_tactic_selected(index: int) -> void:
|
||||
if not _current_proposal or not combat_system:
|
||||
return
|
||||
var tactic := _current_proposal.defender.available_tactics[index]
|
||||
combat_system.update_tactic(_current_proposal, false, tactic)
|
||||
_refresh_stats()
|
||||
81
scripts/battle/deployed_units/deployed_unit.gd
Normal file
@@ -0,0 +1,81 @@
|
||||
class_name DeployedUnit extends Node2D
|
||||
|
||||
enum UnitState { ALIVE, DEAD }
|
||||
|
||||
@export var unit: Unit
|
||||
|
||||
var current_stats: DeployedUnitStats
|
||||
var tactics: Array[CombatTactic] = []
|
||||
var state: UnitState = UnitState.ALIVE
|
||||
|
||||
var _sprite: AnimatedSprite2D
|
||||
var _previous_position: Vector2
|
||||
|
||||
signal unit_selected_changed(deployed: DeployedUnit, selected: bool)
|
||||
signal unit_allegiance_changed(deployed: DeployedUnit, allegiance: UnitAllegiance)
|
||||
signal unit_died(deployed: DeployedUnit)
|
||||
|
||||
func _ready() -> void:
|
||||
current_stats = DeployedUnitStats.from_unit_stats(unit.stats)
|
||||
tactics = unit.tactics.duplicate()
|
||||
_append_builtin_tactics()
|
||||
unit_allegiance_changed.emit(self, unit.allegiance)
|
||||
_previous_position = position
|
||||
_setup_appearance()
|
||||
|
||||
func _setup_appearance() -> void:
|
||||
_sprite = get_node_or_null("AnimatedSprite2D") as AnimatedSprite2D
|
||||
if not _sprite:
|
||||
return
|
||||
var sprite_frames: SpriteFrames = unit.appearance.deployed_sprite_sheet if unit.appearance else null
|
||||
if not sprite_frames:
|
||||
return
|
||||
_sprite.sprite_frames = sprite_frames
|
||||
_sprite.play("idle")
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if not _sprite or not _sprite.sprite_frames:
|
||||
return
|
||||
var delta_pos := position - _previous_position
|
||||
_previous_position = position
|
||||
if delta_pos.length_squared() < 0.01:
|
||||
if _sprite.animation != &"idle":
|
||||
_sprite.play("idle")
|
||||
return
|
||||
var anim: StringName
|
||||
if absf(delta_pos.x) >= absf(delta_pos.y):
|
||||
anim = &"right" if delta_pos.x > 0 else &"left"
|
||||
else:
|
||||
anim = &"down" if delta_pos.y > 0 else &"up"
|
||||
if _sprite.animation != anim:
|
||||
_sprite.play(anim)
|
||||
|
||||
func _append_builtin_tactics() -> void:
|
||||
var attack := AttackCombatTactic.new()
|
||||
attack.tactic_name = "Attack"
|
||||
attack.tactic_range = UnitMatchingCombatTacticRange.new()
|
||||
tactics.append(attack)
|
||||
|
||||
var defend := DefendCombatTactic.new()
|
||||
defend.tactic_name = "Defend"
|
||||
defend.tactic_range = AnyCombatTacticRange.new()
|
||||
tactics.append(defend)
|
||||
|
||||
func set_selected(selected: bool) -> void:
|
||||
unit_selected_changed.emit(self, selected)
|
||||
|
||||
func is_alive() -> bool:
|
||||
return state == UnitState.ALIVE
|
||||
|
||||
func take_damage(amount: int) -> void:
|
||||
if state != UnitState.ALIVE:
|
||||
return
|
||||
current_stats.current_hp -= amount
|
||||
if current_stats.current_hp <= 0:
|
||||
current_stats.current_hp = 0
|
||||
_die()
|
||||
|
||||
func _die() -> void:
|
||||
state = UnitState.DEAD
|
||||
unit_died.emit(self)
|
||||
queue_free()
|
||||
1
scripts/battle/deployed_units/deployed_unit.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cmh4lphvboggy
|
||||
45
scripts/battle/deployed_units/deployed_unit_stats.gd
Normal file
@@ -0,0 +1,45 @@
|
||||
class_name DeployedUnitStats extends Resource
|
||||
|
||||
@export var unit_stats: UnitStats
|
||||
@export var current_hp: int
|
||||
@export var current_sp: int
|
||||
@export var current_fs: int
|
||||
|
||||
# Passthrough accessors. Future buff/debuff layers can override these
|
||||
# without mutating the underlying UnitStats template.
|
||||
var max_hp: int:
|
||||
get: return unit_stats.max_hp
|
||||
var max_sp: int:
|
||||
get: return unit_stats.max_sp
|
||||
var max_fs: int:
|
||||
get: return unit_stats.max_fs
|
||||
var phys_atk: int:
|
||||
get: return unit_stats.phys_atk
|
||||
var phys_def: int:
|
||||
get: return unit_stats.phys_def
|
||||
var magic_atk: int:
|
||||
get: return unit_stats.magic_atk
|
||||
var magic_def: int:
|
||||
get: return unit_stats.magic_def
|
||||
var hit: int:
|
||||
get: return unit_stats.hit
|
||||
var atk_range: int:
|
||||
get: return unit_stats.atk_range
|
||||
var spd: int:
|
||||
get: return unit_stats.spd
|
||||
var eva: int:
|
||||
get: return unit_stats.eva
|
||||
var lck: int:
|
||||
get: return unit_stats.lck
|
||||
var mov: int:
|
||||
get: return unit_stats.mov
|
||||
var level: int:
|
||||
get: return unit_stats.level
|
||||
|
||||
static func from_unit_stats(source: UnitStats) -> DeployedUnitStats:
|
||||
var stats := DeployedUnitStats.new()
|
||||
stats.unit_stats = source
|
||||
stats.current_hp = source.max_hp
|
||||
stats.current_sp = source.max_sp
|
||||
stats.current_fs = source.max_fs
|
||||
return stats
|
||||
1
scripts/battle/deployed_units/deployed_unit_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b3jekvxwi8sxi
|
||||
120
scripts/battle/map/combat_map.gd
Normal file
@@ -0,0 +1,120 @@
|
||||
class_name CombatMap
|
||||
extends Node2D
|
||||
|
||||
@export var tile_set: DLTileset
|
||||
@export var map_layout: MapLayout
|
||||
@onready var tile_map: TileMapLayer = %TerrainLayer
|
||||
@onready var highlight_map: GridOverlay = %OverlayLayer
|
||||
@onready var wall_renderer: WallRenderer = %WallRenderer
|
||||
@onready var fog_renderer: FogRenderer = %FogRenderer
|
||||
|
||||
const DEPLOYED_UNIT_SCENE = preload("res://prefabs/deployed_unit.tscn")
|
||||
const SOURCE_ID: int = 0
|
||||
|
||||
var _pending_layout: String
|
||||
var _pending_units: Array[Dictionary] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if _pending_layout:
|
||||
_apply_layout(_pending_layout)
|
||||
for entry in _pending_units:
|
||||
_apply_deploy(entry.deployed, entry.coords)
|
||||
_pending_units.clear()
|
||||
if map_layout:
|
||||
apply_layout(map_layout)
|
||||
|
||||
|
||||
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 load_map(layout: String) -> void:
|
||||
if is_node_ready():
|
||||
_apply_layout(layout)
|
||||
else:
|
||||
_pending_layout = layout
|
||||
|
||||
|
||||
func deploy_unit(unit: Unit, coords: Vector2i) -> void:
|
||||
var deployed: DeployedUnit = DEPLOYED_UNIT_SCENE.instantiate()
|
||||
deployed.unit = unit
|
||||
if is_node_ready():
|
||||
_apply_deploy(deployed, coords)
|
||||
else:
|
||||
_pending_units.append({deployed = deployed, coords = coords})
|
||||
|
||||
|
||||
func _apply_layout(layout: String) -> void:
|
||||
var rows := layout.split("\n")
|
||||
for y in rows.size():
|
||||
for x in rows[y].length():
|
||||
var coords := Vector2i(x, y)
|
||||
match rows[y][x]:
|
||||
"#":
|
||||
draw_wall(coords)
|
||||
".":
|
||||
draw_floor(coords)
|
||||
|
||||
|
||||
func _apply_deploy(deployed: DeployedUnit, coords: Vector2i) -> void:
|
||||
deployed.position = BattleMapHelper.coords_to_world(coords)
|
||||
add_child(deployed)
|
||||
|
||||
|
||||
func remove_unit(deployed: DeployedUnit) -> void:
|
||||
if deployed.get_parent() == self:
|
||||
remove_child(deployed)
|
||||
|
||||
|
||||
func target_tile(coords: Vector2i) -> void:
|
||||
highlight_map.target_tile(coords)
|
||||
|
||||
|
||||
func apply_layout(layout: MapLayout) -> void:
|
||||
map_layout = layout
|
||||
map_layout.initialize()
|
||||
load_from_layout()
|
||||
draw_room_walls()
|
||||
draw_fog()
|
||||
|
||||
|
||||
func is_tile_passable(from: Vector2i, to: Vector2i) -> bool:
|
||||
assert(map_layout != null, "CombatMap.is_tile_passable called before map_layout was set")
|
||||
return map_layout.is_passable(from, to)
|
||||
|
||||
|
||||
func is_tile_valid(coords: Vector2i) -> bool:
|
||||
assert(map_layout != null, "CombatMap.is_tile_valid called before map_layout was set")
|
||||
return map_layout.is_tile_valid(coords)
|
||||
|
||||
|
||||
func draw_room_walls() -> void:
|
||||
if not map_layout:
|
||||
return
|
||||
wall_renderer.draw_walls_for_layout(map_layout)
|
||||
|
||||
|
||||
func draw_fog() -> void:
|
||||
if not map_layout:
|
||||
return
|
||||
fog_renderer.draw_fog_for_layout(map_layout)
|
||||
|
||||
|
||||
func get_map_rect() -> Rect2:
|
||||
if not map_layout:
|
||||
return Rect2()
|
||||
return Rect2(Vector2.ZERO, Vector2(map_layout.size) * BattleMapHelper.TILE_SIZE)
|
||||
|
||||
|
||||
func load_from_layout() -> void:
|
||||
if not map_layout:
|
||||
return
|
||||
for room in map_layout.rooms:
|
||||
for tile in room.tiles:
|
||||
draw_floor(tile)
|
||||
38
scripts/battle/map/fog_renderer.gd
Normal file
@@ -0,0 +1,38 @@
|
||||
class_name FogRenderer
|
||||
extends Node2D
|
||||
|
||||
## Renders a fog/cave texture over every tile inside the map's bounding rect
|
||||
## that is not part of any room. Future: drive visibility from map state.
|
||||
|
||||
## Fog tile region in aux_terrain.BMP
|
||||
const FOG_RECT := Rect2(53, 53, 100, 100)
|
||||
|
||||
@export var atlas_texture: Texture2D
|
||||
|
||||
var _fog_tiles: PackedVector2Array = []
|
||||
|
||||
|
||||
func draw_fog_for_layout(map_layout: MapLayout) -> void:
|
||||
_fog_tiles.clear()
|
||||
if not map_layout or not atlas_texture:
|
||||
queue_redraw()
|
||||
return
|
||||
for y in map_layout.size.y:
|
||||
for x in map_layout.size.x:
|
||||
var tile := Vector2i(x, y)
|
||||
if map_layout.is_tile_valid(tile):
|
||||
continue
|
||||
_fog_tiles.append(Vector2(tile) * BattleMapHelper.TILE_SIZE)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not atlas_texture:
|
||||
return
|
||||
var dest_size := Vector2(BattleMapHelper.TILE_SIZE, BattleMapHelper.TILE_SIZE)
|
||||
for pos in _fog_tiles:
|
||||
draw_texture_rect_region(
|
||||
atlas_texture,
|
||||
Rect2(pos, dest_size),
|
||||
FOG_RECT,
|
||||
)
|
||||
1
scripts/battle/map/fog_renderer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d1d1nbetdvynk
|
||||