Compare commits

..

31 Commits

Author SHA1 Message Date
gamer147
2891fb6248 Deployment slot UI mostly setup 2026-04-11 23:03:00 -04:00
gamer147
31a787a7f0 Update todo 2026-04-10 14:11:14 -04:00
gamer147
f332771153 Needs fine-tuning polish but looks good. 2026-04-10 14:10:25 -04:00
gamer147
2d74e15006 Fixing 2026-04-10 13:13:01 -04:00
gamer147
6d9d08d78c Testing chip bar 2026-04-10 12:23:45 -04:00
gamer147
834d6a3a83 Better alignment, still needs work 2026-04-10 09:56:43 -04:00
gamer147
676c82c4e5 Weird alignments 2026-04-10 09:46:39 -04:00
gamer147
e2d23bec48 Actual rewire coming soon 2026-04-10 09:34:52 -04:00
gamer147
b01d8c6648 Unit panel rewire 2026-04-10 09:29:36 -04:00
gamer147
f6ac31b52e Level updates 2026-04-09 18:19:07 -04:00
gamer147
9b1d6e8c8f More UI stuff, fancy number display (needs work) 2026-04-09 12:03:31 -04:00
gamer147
664c9694de Working on look and feel 2026-04-09 08:24:54 -04:00
gamer147
e356078a9f Restructure tile size usage 2026-04-09 08:07:25 -04:00
gamer147
6b46d1c274 Appearance sets 2026-04-09 07:58:58 -04:00
gamer147
b807e9897d Refactored unit 2026-04-08 18:44:58 -04:00
gamer147
c192d48bc4 Reorganized files, started splitting up unit 2026-04-08 18:28:52 -04:00
gamer147
24134cfa33 Fixed doorway shadows 2026-04-08 12:46:17 -04:00
gamer147
767df71975 Openings between rooms 2026-04-08 08:06:15 -04:00
gamer147
92a0bb1d58 Good place 2026-04-08 07:09:24 -04:00
gamer147
e42a98fece Optimize wall/fog rendering 2026-04-07 10:22:30 -04:00
gamer147
0882908e4c Code review 2026-04-07 10:13:34 -04:00
gamer147
880d4ecc77 Map out more todos 2026-04-07 08:58:18 -04:00
gamer147
39d2222546 Camera fixes 2026-04-07 08:27:50 -04:00
gamer147
97909235ff Fog of war 2026-04-07 07:56:48 -04:00
gamer147
344efee7b4 Map size and camera bounds 2026-04-07 07:41:43 -04:00
gamer147
b086c7d181 Corners 2026-04-07 07:21:45 -04:00
gamer147
7f6fd7a0d4 Fix wall orientations 2026-04-06 21:57:43 -04:00
gamer147
1f87df8149 Wall textures, need to do corners 2026-04-06 21:53:54 -04:00
gamer147
85f593cf56 Organization updates 2026-04-06 21:32:01 -04:00
gamer147
612e88579d Started working on real wall textures, but not suitable for a tilesheet right now unfortunately 2026-04-05 23:05:08 -04:00
gamer147
3a8e3edc03 Room system added 2026-04-05 22:58:30 -04:00
123 changed files with 3596 additions and 624 deletions

BIN
assets/sprites/CP002AB.BMP Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View 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/ui/SO008A.BMP Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cau61m1755dkn"
path="res://.godot/imported/SO008A.BMP-dedc5f46caf0f66b50eed190cef6a3a9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/ui/SO008A.BMP"
dest_files=["res://.godot/imported/SO008A.BMP-dedc5f46caf0f66b50eed190cef6a3a9.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/SO008B.BMP Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View 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

View 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** |

View 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

View 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

View File

@@ -1,7 +1,15 @@
* Debug menu on F1, right now just include quick scene swapping
* Finish buttons on dialogue scene
* Reogranize files
* Singletons named 'XXXServer' * Singletons named 'XXXServer'
* Dialogue scene command system (ShowText, ShowSprite, MoveSprite, PlaySound, ChangeBackground, etc) * Dialogue scene command system (ShowText, ShowSprite, MoveSprite, PlaySound, ChangeBackground, etc)
* Setup room system (everything is unpassable, carve out rooms, walls automatic, specify connections between rooms on tiles) * 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
* Basic map editor (test map data will be harder to craft the more we add) * 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
View 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}'"

View File

@@ -1,81 +0,0 @@
class_name CombatMap
extends Node2D
@export var tile_set: DLTileset
@onready var tile_map: TileMapLayer = %TerrainLayer
@onready var highlight_map: GridOverlay = %OverlayLayer
const TILE_SIZE := 100.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)

View File

@@ -1,56 +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
@export var tactics: Array[CombatTactic] = []
#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)
_append_builtin_tactics()
unit_allegiance_changed.emit(self, current_allegiance)
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()

21
prefabs/chip_bar.tscn Normal file
View 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

View File

@@ -1,11 +1,14 @@
[gd_scene format=3 uid="uid://dkhyh5ce4iuk3"] [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://bks7uplgjjdg0" path="res://scripts/battle/map/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="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="Texture2D" uid="uid://sjsl8q7tkx8" path="res://assets/sprites/grid_highlight.png" id="3_vcj5e"]
[ext_resource type="Script" uid="uid://cxl38x2m6sj3w" path="res://scripts/battle/grid_overlay.gd" id="4_jelju"] [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://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="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"] [sub_resource type="Resource" id="Resource_vcj5e"]
script = ExtResource("2_8rn0j") script = ExtResource("2_8rn0j")
@@ -83,3 +86,14 @@ offset = Vector2(50, 50)
[node name="TerrainLayer" type="TileMapLayer" parent="." unique_id=1201875024] [node name="TerrainLayer" type="TileMapLayer" parent="." unique_id=1201875024]
unique_name_in_owner = true unique_name_in_owner = true
tile_set = SubResource("TileSet_e2u25") tile_set = SubResource("TileSet_e2u25")
[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")

View File

@@ -2,47 +2,155 @@
[ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_2ro41"] [ext_resource type="Theme" uid="uid://dx26d6py3n8xi" path="res://resources/main_ui_theme.tres" id="1_2ro41"]
[ext_resource type="Script" uid="uid://w2wh6gtv3u2l" path="res://scripts/battle/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") script = ExtResource("2_ui_script")
[node name="UnitPanel" type="PanelContainer" parent="." unique_id=2000000001] [node name="UIBase" type="Control" parent="." unique_id=7839209]
unique_name_in_owner = true layout_mode = 3
visible = false anchors_preset = 15
anchors_preset = 2 anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
offset_left = 8.0 grow_horizontal = 2
offset_top = -78.0 grow_vertical = 2
offset_right = 208.0 mouse_filter = 1
offset_bottom = -8.0
grow_vertical = 0
theme = ExtResource("1_2ro41")
[node name="MarginContainer" type="MarginContainer" parent="UnitPanel" unique_id=899996785] [node name="Overlay" type="Control" parent="UIBase" unique_id=114439631]
layout_mode = 2 layout_mode = 1
theme_override_constants/margin_left = 8 anchors_preset = 15
theme_override_constants/margin_top = 8 anchor_right = 1.0
theme_override_constants/margin_right = 8 anchor_bottom = 1.0
theme_override_constants/margin_bottom = 8 grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="VBoxContainer" type="VBoxContainer" parent="UnitPanel/MarginContainer" unique_id=333573880] [node name="Background" type="TextureRect" parent="UIBase/Overlay" unique_id=1726665864]
layout_mode = 2 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="NameLabel" type="Label" parent="UnitPanel/MarginContainer/VBoxContainer" unique_id=2000000002] [node name="HoverInfo" type="TextureRect" parent="UIBase/Overlay" unique_id=1180275113]
unique_name_in_owner = true layout_mode = 0
layout_mode = 2 offset_right = 40.0
text = "Unit" offset_bottom = 40.0
texture = SubResource("AtlasTexture_55shj")
[node name="HPBar" type="ProgressBar" parent="UnitPanel/MarginContainer/VBoxContainer" unique_id=2000000003] [node name="Bars" type="TextureRect" parent="UIBase/Overlay" unique_id=780801800]
unique_name_in_owner = true layout_mode = 1
layout_mode = 2 anchors_preset = 1
value = 100.0 anchor_left = 1.0
show_percentage = false anchor_right = 1.0
offset_left = -513.0
offset_bottom = 169.0
grow_horizontal = 0
texture = SubResource("AtlasTexture_gqe5k")
[node name="BackgroundTint" type="ColorRect" parent="." unique_id=253893594] [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 unique_name_in_owner = true
visible = false visible = false
layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
@@ -50,121 +158,278 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
color = Color(0, 0, 0, 0.4) color = Color(0, 0, 0, 0.4)
[node name="CombatProposalPanel" type="PanelContainer" parent="." unique_id=2088533653] [node name="CombatProposalPanel" type="PanelContainer" parent="UIBase" unique_id=2088533653]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 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_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_2ro41") theme = ExtResource("1_2ro41")
[node name="MarginContainer" type="MarginContainer" parent="CombatProposalPanel" unique_id=924522188] [node name="MarginContainer" type="MarginContainer" parent="UIBase/CombatProposalPanel" unique_id=924522188]
layout_mode = 2 layout_mode = 2
theme_override_constants/margin_left = 12 theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12 theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12 theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12 theme_override_constants/margin_bottom = 12
[node name="VBoxContainer" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer" unique_id=666671196] [node name="VBoxContainer" type="VBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer" unique_id=666671196]
layout_mode = 2 layout_mode = 2
[node name="TitleLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=1520669125] [node name="TitleLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=1520669125]
layout_mode = 2 layout_mode = 2
text = "COMBAT FORECAST" text = "COMBAT FORECAST"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="StatsContainer" type="HBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=1101898616] [node name="StatsContainer" type="HBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=1101898616]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 24 theme_override_constants/separation = 24
[node name="AttackerStats" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer" unique_id=1193603706] [node name="AttackerStats" type="VBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer" unique_id=1193603706]
layout_mode = 2 layout_mode = 2
[node name="AttackerNameLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1574861154] [node name="AttackerNameLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1574861154]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Attacker" text = "Attacker"
[node name="AttackerTacticSelect" type="OptionButton" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1466363800] [node name="AttackerTacticSelect" type="OptionButton" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1466363800]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
[node name="AttackerHPBar" type="ProgressBar" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=241886156] [node name="AttackerHPBar" type="ProgressBar" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=241886156]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
show_percentage = false show_percentage = false
[node name="AttackerATKLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1656041171] [node name="AttackerATKLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1656041171]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "ATK: 0" text = "ATK: 0"
[node name="AttackerDEFLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=2145851939] [node name="AttackerDEFLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=2145851939]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "DEF: 0" text = "DEF: 0"
[node name="AttackerHITLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1234331828] [node name="AttackerHITLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1234331828]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "HIT: 0%" text = "HIT: 0%"
[node name="AttackerSPDLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1461624142] [node name="AttackerSPDLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats" unique_id=1461624142]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "SPD: 0" text = "SPD: 0"
[node name="DefenderStats" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer" unique_id=1747482540] [node name="DefenderStats" type="VBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer" unique_id=1747482540]
layout_mode = 2 layout_mode = 2
[node name="DefenderNameLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=365769500] [node name="DefenderNameLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=365769500]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Defender" text = "Defender"
[node name="DefenderTacticSelect" type="OptionButton" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1226652499] [node name="DefenderTacticSelect" type="OptionButton" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1226652499]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
[node name="DefenderHPBar" type="ProgressBar" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1878516243] [node name="DefenderHPBar" type="ProgressBar" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1878516243]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
show_percentage = false show_percentage = false
[node name="DefenderATKLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1571527922] [node name="DefenderATKLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1571527922]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "ATK: 0" text = "ATK: 0"
[node name="DefenderDEFLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1755454849] [node name="DefenderDEFLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=1755454849]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "DEF: 0" text = "DEF: 0"
[node name="DefenderHITLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=717216786] [node name="DefenderHITLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=717216786]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "HIT: 0%" text = "HIT: 0%"
[node name="DefenderSPDLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=132072292] [node name="DefenderSPDLabel" type="Label" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats" unique_id=132072292]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "SPD: 0" text = "SPD: 0"
[node name="ButtonContainer" type="HBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=2145647565] [node name="ButtonContainer" type="HBoxContainer" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer" unique_id=2145647565]
layout_mode = 2 layout_mode = 2
alignment = 1 alignment = 1
[node name="FightButton" type="Button" parent="CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer" unique_id=2109262268] [node name="FightButton" type="Button" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer" unique_id=2109262268]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Fight" text = "Fight"
[node name="CancelButton" type="Button" parent="CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer" unique_id=238295206] [node name="CancelButton" type="Button" parent="UIBase/CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer" unique_id=238295206]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Cancel" 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

View 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)

View File

@@ -1,12 +1,12 @@
[gd_scene format=3] [gd_scene format=3 uid="uid://dpcaa8x6xxup0"]
[ext_resource type="Script" path="res://nodes/debug_menu.gd" id="1_script"] [ext_resource type="Script" uid="uid://c64yr8xvkb5cw" path="res://scripts/debug/debug_menu.gd" id="1_script"]
[node name="DebugMenu" type="CanvasLayer"] [node name="DebugMenu" type="CanvasLayer" unique_id=240858900]
layer = 100 layer = 100
script = ExtResource("1_script") script = ExtResource("1_script")
[node name="Panel" type="PanelContainer" parent="."] [node name="Panel" type="PanelContainer" parent="." unique_id=349886438]
unique_name_in_owner = true unique_name_in_owner = true
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -14,46 +14,45 @@ anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="Panel"] [node name="MarginContainer" type="MarginContainer" parent="Panel" unique_id=322235564]
layout_mode = 2 layout_mode = 2
theme_override_constants/margin_left = 12 theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12 theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12 theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12 theme_override_constants/margin_bottom = 12
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"] [node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer" unique_id=1731008558]
layout_mode = 2 layout_mode = 2
[node name="TitleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] [node name="TitleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=211532752]
layout_mode = 2 layout_mode = 2
text = "Debug Menu" text = "Debug Menu"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer"] [node name="HSeparator" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer" unique_id=1527486356]
layout_mode = 2 layout_mode = 2
[node name="ScenesLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] [node name="ScenesLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=1071710223]
layout_mode = 2 layout_mode = 2
text = "Scenes:" text = "Scenes:"
[node name="SceneList" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer"] [node name="SceneList" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer" unique_id=1684653951]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
[node name="HSeparator2" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer"] [node name="HSeparator2" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer" unique_id=487142357]
layout_mode = 2 layout_mode = 2
[node name="ConsoleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] [node name="ConsoleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=1165373860]
layout_mode = 2 layout_mode = 2
text = "Console:" text = "Console:"
[node name="CommandInput" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer"] [node name="CommandInput" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer" unique_id=1402278573]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
placeholder_text = "Enter command or expression..." placeholder_text = "Enter command or expression..."
[node name="ResultLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] [node name="ResultLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=881735021]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = ""
autowrap_mode = 3 autowrap_mode = 3

View 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"]

View 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")

View File

@@ -1,9 +0,0 @@
[gd_scene format=3 uid="uid://c6fnawy2wtqii"]
[node name="Control" type="Control" unique_id=1778107518]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

View 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

View File

@@ -1,23 +1,31 @@
[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://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"] [ext_resource type="Shader" uid="uid://bd8ki8xwym5nc" path="res://shaders/chroma_key.gdshader" id="3_fhs1y"]
[ext_resource type="Texture2D" uid="uid://dyutp4m5d53gd" path="res://assets/sprites/CP002AA.BMP" id="3_on614"] [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"
script/source = "extends ColorRect
func _unit_selected_changed(_unit: Unit, selected: bool) -> void:
visible = selected
"
[sub_resource type="GDScript" id="GDScript_fhs1y"] [sub_resource type="GDScript" id="GDScript_fhs1y"]
resource_name = "AllegianceIndicatorManager" resource_name = "AllegianceIndicatorManager"
script/source = "extends Sprite2D script/source = "extends Sprite2D
func _on_unit_unit_allegiance_changed(unit: Unit, allegiance: UnitAllegiance) -> void: func _on_unit_unit_allegiance_changed(_unit: Unit, allegiance: UnitAllegiance) -> void:
self_modulate = allegiance.color self_modulate = allegiance.color
" "
[sub_resource type="ShaderMaterial" id="ShaderMaterial_fhs1y"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_fhs1y"]
shader = ExtResource("3_fhs1y") shader = ExtResource("3_fhs1y")
shader_parameter/key_color = Color(0, 1, 0, 1) shader_parameter/key_color = Color(0, 1, 0, 1)
shader_parameter/threshold = 0.01 shader_parameter/threshold = 0.010000000475
[sub_resource type="AtlasTexture" id="AtlasTexture_fhs1y"] [sub_resource type="AtlasTexture" id="AtlasTexture_fhs1y"]
atlas = ExtResource("3_on614") atlas = ExtResource("3_on614")
@@ -55,18 +63,17 @@ animations = [{
"speed": 5.0 "speed": 5.0
}] }]
[sub_resource type="GDScript" id="GDScript_on614"]
resource_name = "UnitSelectorHandler"
script/source = "extends ColorRect
func _unit_selected_changed(_unit: Unit, selected: bool) -> void:
visible = selected
"
[node name="Unit" type="Node2D" unique_id=1893234933 groups=["units"]] [node name="Unit" type="Node2D" unique_id=1893234933 groups=["units"]]
script = ExtResource("1_cq4v0") script = ExtResource("1_cq4v0")
metadata/_custom_type_script = "uid://c016mxgatcpse" 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] [node name="AllegianceIndicator" type="Sprite2D" parent="." unique_id=1567439632]
z_index = 2 z_index = 2
texture = ExtResource("2_fhs1y") texture = ExtResource("2_fhs1y")
@@ -79,13 +86,3 @@ position = Vector2(50, 50)
sprite_frames = SubResource("SpriteFrames_7jqdg") sprite_frames = SubResource("SpriteFrames_7jqdg")
animation = &"idle" animation = &"idle"
autoplay = "idle" autoplay = "idle"
[node name="ColorRect" 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")
[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"]

View File

@@ -10,11 +10,15 @@ config_version=5
[application] [application]
config/name="Dungeon Lords" config/name="OpenMaidEngine"
run/main_scene="res://scenes/game.tscn" run/main_scene="res://scenes/game.tscn"
config/features=PackedStringArray("4.6", "Mobile") config/features=PackedStringArray("4.6", "Mobile")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload]
BattleMapHelper="*res://scripts/autoloads/battle_map_helper.gd"
[display] [display]
window/size/viewport_width=800 window/size/viewport_width=800
@@ -30,10 +34,11 @@ debug_toggle={
[physics] [physics]
3d/physics_engine="Jolt Physics" 3d/physics_engine="Dummy"
[rendering] [rendering]
textures/canvas_textures/default_texture_filter=0 textures/canvas_textures/default_texture_filter=0
rendering_device/driver.windows="d3d12" rendering_device/driver.windows="d3d12"
renderer/rendering_method="mobile" renderer/rendering_method="mobile"
environment/defaults/default_clear_color=Color(0, 0, 0, 1)

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

View 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

View File

@@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="UnitAllegiance" format=3 uid="uid://cuc7kkknpsr1g"] [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] [resource]
script = ExtResource("1_40cg2") script = ExtResource("1_40cg2")

View File

@@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="UnitAllegiance" format=3 uid="uid://dufi2h00j5vrq"] [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] [resource]
script = ExtResource("1_4mkdx") script = ExtResource("1_4mkdx")

View File

@@ -1,10 +0,0 @@
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

View File

@@ -1,10 +0,0 @@
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 ""

View File

@@ -1,10 +0,0 @@
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

View 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"

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View 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

View File

@@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://gfrxev22t0bc"] [gd_scene format=3 uid="uid://gfrxev22t0bc"]
[ext_resource type="Script" uid="uid://ifv6cww6fk6c" path="res://nodes/game.gd" id="1_script"] [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"] [ext_resource type="PackedScene" path="res://prefabs/debug_menu.tscn" id="2_debug_menu"]
[node name="Game" type="Node" unique_id=906681388] [node name="Game" type="Node" unique_id=906681388]

View File

@@ -4,6 +4,11 @@
[ext_resource type="Texture2D" uid="uid://c7e4jw4xcti0q" path="res://assets/sprites/castle_spritesheet.png" id="1_g7g4h"] [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="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://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"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_qjeyg"]
texture = ExtResource("1_7ddre") texture = ExtResource("1_7ddre")
@@ -48,6 +53,18 @@ shader_parameter/chroma_threshold = 0.10000000475
atlas = ExtResource("1_g7g4h") atlas = ExtResource("1_g7g4h")
region = Rect2(0, 100, 100, 100) 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="TestScene" type="Node2D" unique_id=1687841395]
[node name="TileMapLayer" type="TileMapLayer" parent="." unique_id=265586128] [node name="TileMapLayer" type="TileMapLayer" parent="." unique_id=265586128]
@@ -58,3 +75,43 @@ tile_set = SubResource("TileSet_3qnke")
material = SubResource("ShaderMaterial_qjeyg") material = SubResource("ShaderMaterial_qjeyg")
position = Vector2(-150, -148) position = Vector2(-150, -148)
texture = SubResource("AtlasTexture_j8ivh") 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")

View File

@@ -1,30 +1,30 @@
[gd_scene format=3 uid="uid://wy7ur5r23ek3"] [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="Script" uid="uid://dnsqtsx4u2hx4" path="res://scripts/battle/strategy_phase.gd" id="1_qs1ys"]
[ext_resource type="PackedScene" uid="uid://dkhyh5ce4iuk3" path="res://prefabs/combat_map.tscn" id="2_iuoca"] [ext_resource type="PackedScene" uid="uid://cy7r0udfcsqbn" path="res://prefabs/combat_ui.tscn" id="2_4s0rq"]
[ext_resource type="Script" uid="uid://dfojm3n0em4ef" path="res://nodes/player_controller.gd" id="3_esrqm"] [ext_resource type="PackedScene" uid="uid://dkhyh5ce4iuk3" path="res://prefabs/combat_map.tscn" id="3_n1a8d"]
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/battle/camera_controller.gd" id="4_ww3c6"] [ext_resource type="Script" uid="uid://dfojm3n0em4ef" path="res://scripts/battle/player_controller.gd" id="4_208pr"]
[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://scripts/battle/combat_engine/combat_system.gd" id="5_n11my"]
[ext_resource type="Script" uid="uid://cf4ivrcbky0s3" path="res://nodes/combat_system.gd" id="6_combat"] [ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/battle/camera_controller.gd" id="6_m48os"]
[ext_resource type="Script" uid="uid://dnsqtsx4u2hx4" path="res://nodes/strategy_phase.gd" id="7_strat"] [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] [node name="BattleView" type="Node2D" unique_id=855645983]
script = ExtResource("7_strat") 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")] [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") dl_map = NodePath("../CombatMap")
[node name="CombatSystem" type="Node" parent="." unique_id=1234567890] [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] [node name="Camera2D" type="Camera2D" parent="." unique_id=1739569732]
script = ExtResource("4_ww3c6") script = ExtResource("6_m48os")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1057500234] [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1057500234]
stream = ExtResource("5_ficdm") stream = ExtResource("7_oih6t")
autoplay = true autoplay = true

View 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")

View File

@@ -1,34 +1,34 @@
[gd_scene format=3 uid="uid://dlbuo46n6q238"] [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="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="1_yqeox"] [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_bqqt6"] [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_wu84c"] [ext_resource type="AudioStream" uid="uid://5ndo4w06umsa" path="res://assets/sounds/SE020.WAV" id="4_somrw"]
[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_ybnw1"]
[ext_resource type="AudioStream" uid="uid://d1hacs4t5qni1" path="res://assets/sounds/SE015.WAV" id="5_rtw2f"] [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"] [sub_resource type="AtlasTexture" id="AtlasTexture_wu84c"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(0, 0, 800, 400) region = Rect2(0, 0, 800, 400)
[sub_resource type="AtlasTexture" id="AtlasTexture_8ln24"] [sub_resource type="AtlasTexture" id="AtlasTexture_8ln24"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(0, 600, 800, 348) region = Rect2(0, 600, 800, 348)
[sub_resource type="AtlasTexture" id="AtlasTexture_a8gd2"] [sub_resource type="AtlasTexture" id="AtlasTexture_a8gd2"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(800, 0, 745, 745) region = Rect2(800, 0, 745, 745)
[sub_resource type="AtlasTexture" id="AtlasTexture_bqqt6"] [sub_resource type="AtlasTexture" id="AtlasTexture_bqqt6"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(-1, 995, 800, 43) region = Rect2(-1, 995, 800, 43)
[sub_resource type="AtlasTexture" id="AtlasTexture_rtw2f"] [sub_resource type="AtlasTexture" id="AtlasTexture_rtw2f"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(0, 950, 800, 45) region = Rect2(0, 950, 800, 45)
[sub_resource type="AtlasTexture" id="AtlasTexture_oa1go"] [sub_resource type="AtlasTexture" id="AtlasTexture_oa1go"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(800, 744, 515, 210) region = Rect2(800, 744, 515, 210)
[sub_resource type="GDScript" id="GDScript_hover"] [sub_resource type="GDScript" id="GDScript_hover"]
@@ -64,49 +64,40 @@ func _on_button_clicked() -> void:
" "
[sub_resource type="AtlasTexture" id="AtlasTexture_tbmy8"] [sub_resource type="AtlasTexture" id="AtlasTexture_tbmy8"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 0, 330, 50) region = Rect2(1550, 0, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_jk1qb"] [sub_resource type="AtlasTexture" id="AtlasTexture_jk1qb"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 300, 330, 50) region = Rect2(1550, 300, 330, 50)
[sub_resource type="GDScript" id="GDScript_bqqt6"] [sub_resource type="GDScript" id="GDScript_bqqt6"]
resource_name = "StartButton" resource_name = "StartButton"
script/source = "extends TextureButton 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 UNIT_SCENE = preload(\"res://prefabs/unit.tscn\")
const PLAYER_ALLEGIANCE = preload(\"res://resources/allegiance_types/player_allegiance.tres\") const PLAYER_ALLEGIANCE = preload(\"res://resources/allegiance_types/player_allegiance.tres\")
const ENEMY_ALLEGIANCE = preload(\"res://resources/allegiance_types/enemy_allegiance.tres\") const ENEMY_ALLEGIANCE = preload(\"res://resources/allegiance_types/enemy_allegiance.tres\")
const MAP_LAYOUT := \"\"\"\\
#####
#...#
#...#
#...#
#####\"\"\"
func _pressed() -> void: func _pressed() -> void:
await get_tree().create_timer(0.2).timeout await get_tree().create_timer(0.2).timeout
var combat_instance := COMBAT_SCENE.instantiate() var combat_instance := COMBAT_SCENE.instantiate()
var combat_map: CombatMap = combat_instance.find_child(\"CombatMap\") var combat_map: CombatMap = combat_instance.find_child(\"CombatMap\")
combat_map.load_map(MAP_LAYOUT)
var player_unit: Unit = UNIT_SCENE.instantiate() 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 = UnitInfo.new()
player_unit.info_template.name = \"Putit\" player_unit.info_template.name = \"Putit\"
player_unit.allegiance_template = PLAYER_ALLEGIANCE 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() 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 = UnitInfo.new()
enemy_unit.info_template.name = \"Putit\" enemy_unit.info_template.name = \"Putit\"
enemy_unit.allegiance_template = ENEMY_ALLEGIANCE 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 tree := get_tree()
var root := tree.root var root := tree.root
@@ -118,35 +109,35 @@ func _pressed() -> void:
" "
[sub_resource type="AtlasTexture" id="AtlasTexture_5dd4i"] [sub_resource type="AtlasTexture" id="AtlasTexture_5dd4i"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 60, 330, 50) region = Rect2(1550, 60, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_lgwnu"] [sub_resource type="AtlasTexture" id="AtlasTexture_lgwnu"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 360, 330, 50) region = Rect2(1550, 360, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_flqon"] [sub_resource type="AtlasTexture" id="AtlasTexture_flqon"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 120, 330, 50) region = Rect2(1550, 120, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_rcqid"] [sub_resource type="AtlasTexture" id="AtlasTexture_rcqid"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 420, 330, 50) region = Rect2(1550, 420, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_1ajci"] [sub_resource type="AtlasTexture" id="AtlasTexture_1ajci"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 180, 330, 50) region = Rect2(1550, 180, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_7b55j"] [sub_resource type="AtlasTexture" id="AtlasTexture_7b55j"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 480, 330, 50) region = Rect2(1550, 480, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_5pajh"] [sub_resource type="AtlasTexture" id="AtlasTexture_5pajh"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 240, 330, 50) region = Rect2(1550, 240, 330, 50)
[sub_resource type="AtlasTexture" id="AtlasTexture_j7ex8"] [sub_resource type="AtlasTexture" id="AtlasTexture_j7ex8"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1550, 540, 330, 50) region = Rect2(1550, 540, 330, 50)
[sub_resource type="GDScript" id="GDScript_wu84c"] [sub_resource type="GDScript" id="GDScript_wu84c"]
@@ -159,74 +150,74 @@ func _pressed():
" "
[sub_resource type="AtlasTexture" id="AtlasTexture_tcusk"] [sub_resource type="AtlasTexture" id="AtlasTexture_tcusk"]
atlas = ExtResource("3_bqqt6") atlas = ExtResource("3_xgjk6")
region = Rect2(1320, 746, 25, 22) region = Rect2(1320, 746, 25, 22)
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_8ln24"] [sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_8ln24"]
blend_mode = 1 blend_mode = 1
[sub_resource type="AtlasTexture" id="AtlasTexture_8egab"] [sub_resource type="AtlasTexture" id="AtlasTexture_8egab"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(0, 0, 140, 140) region = Rect2(0, 0, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_gw5y6"] [sub_resource type="AtlasTexture" id="AtlasTexture_gw5y6"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(140, 0, 140, 140) region = Rect2(140, 0, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_svtp6"] [sub_resource type="AtlasTexture" id="AtlasTexture_svtp6"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(280, 0, 140, 140) region = Rect2(280, 0, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_1dfpl"] [sub_resource type="AtlasTexture" id="AtlasTexture_1dfpl"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(420, 0, 140, 140) region = Rect2(420, 0, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_qywvv"] [sub_resource type="AtlasTexture" id="AtlasTexture_qywvv"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(0, 140, 140, 140) region = Rect2(0, 140, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_3wgol"] [sub_resource type="AtlasTexture" id="AtlasTexture_3wgol"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(140, 140, 140, 140) region = Rect2(140, 140, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_1acrt"] [sub_resource type="AtlasTexture" id="AtlasTexture_1acrt"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(280, 140, 140, 140) region = Rect2(280, 140, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_vr8o3"] [sub_resource type="AtlasTexture" id="AtlasTexture_vr8o3"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(420, 140, 140, 140) region = Rect2(420, 140, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_1a85y"] [sub_resource type="AtlasTexture" id="AtlasTexture_1a85y"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(0, 280, 140, 140) region = Rect2(0, 280, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_hl5e0"] [sub_resource type="AtlasTexture" id="AtlasTexture_hl5e0"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(140, 280, 140, 140) region = Rect2(140, 280, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_engjn"] [sub_resource type="AtlasTexture" id="AtlasTexture_engjn"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(280, 280, 140, 140) region = Rect2(280, 280, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_6h3lr"] [sub_resource type="AtlasTexture" id="AtlasTexture_6h3lr"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(420, 280, 140, 140) region = Rect2(420, 280, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_dj67d"] [sub_resource type="AtlasTexture" id="AtlasTexture_dj67d"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(0, 420, 140, 140) region = Rect2(0, 420, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_6vcge"] [sub_resource type="AtlasTexture" id="AtlasTexture_6vcge"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(140, 420, 140, 140) region = Rect2(140, 420, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_ip0br"] [sub_resource type="AtlasTexture" id="AtlasTexture_ip0br"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(280, 420, 140, 140) region = Rect2(280, 420, 140, 140)
[sub_resource type="AtlasTexture" id="AtlasTexture_xyero"] [sub_resource type="AtlasTexture" id="AtlasTexture_xyero"]
atlas = ExtResource("5_flame") atlas = ExtResource("6_5jfhr")
region = Rect2(420, 420, 140, 140) region = Rect2(420, 420, 140, 140)
[sub_resource type="SpriteFrames" id="SpriteFrames_tcusk"] [sub_resource type="SpriteFrames" id="SpriteFrames_tcusk"]
@@ -290,17 +281,17 @@ blend_mode = 1
[sub_resource type="AtlasTexture" id="AtlasTexture_hstxw"] [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 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_ekxnf") theme = ExtResource("1_wmt4g")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1976575731] [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1976575731]
stream = ExtResource("1_yqeox") stream = ExtResource("2_0dhhe")
autoplay = true autoplay = true
parameters/looping = true parameters/looping = true
@@ -388,10 +379,10 @@ script = SubResource("GDScript_hover")
metadata/_edit_use_anchors_ = true metadata/_edit_use_anchors_ = true
[node name="HoverSFX" type="AudioStreamPlayer" parent="Buttons" unique_id=256435189] [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] [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] [node name="StartButton" type="TextureButton" parent="Buttons" unique_id=973041905]
layout_mode = 2 layout_mode = 2

View File

@@ -1,158 +0,0 @@
[gd_scene format=3 uid="uid://dsg3pxopc0fab"]
[ext_resource type="Texture2D" uid="uid://cyl18yt5hxyb5" path="res://assets/sprites/dialogue_ui.BMP" id="1_xtnmq"]
[ext_resource type="Texture2D" uid="uid://dj621xih5cam7" path="res://assets/sprites/dialogue_continue.BMP" id="2_4b6ix"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xtnmq"]
bg_color = Color(0, 0, 0, 1)
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_xyfjv"]
blend_mode = 1
[sub_resource type="AtlasTexture" id="AtlasTexture_qmngx"]
atlas = ExtResource("1_xtnmq")
region = Rect2(0, 0, 800, 227)
[sub_resource type="AtlasTexture" id="AtlasTexture_3sx2y"]
atlas = ExtResource("2_4b6ix")
region = Rect2(0, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_6inra"]
atlas = ExtResource("2_4b6ix")
region = Rect2(30, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_ptlyn"]
atlas = ExtResource("2_4b6ix")
region = Rect2(60, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_jvfhn"]
atlas = ExtResource("2_4b6ix")
region = Rect2(90, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_fvlad"]
atlas = ExtResource("2_4b6ix")
region = Rect2(120, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_kklm6"]
atlas = ExtResource("2_4b6ix")
region = Rect2(150, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_1rigp"]
atlas = ExtResource("2_4b6ix")
region = Rect2(180, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_qw2nj"]
atlas = ExtResource("2_4b6ix")
region = Rect2(210, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_bubp4"]
atlas = ExtResource("2_4b6ix")
region = Rect2(240, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_iivyk"]
atlas = ExtResource("2_4b6ix")
region = Rect2(270, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_5hpqg"]
atlas = ExtResource("2_4b6ix")
region = Rect2(300, 0, 30, 27)
[sub_resource type="AtlasTexture" id="AtlasTexture_256wb"]
atlas = ExtResource("2_4b6ix")
region = Rect2(330, 0, 30, 27)
[sub_resource type="SpriteFrames" id="SpriteFrames_r6vt6"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_3sx2y")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_6inra")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_ptlyn")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_jvfhn")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_fvlad")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_kklm6")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_1rigp")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_qw2nj")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_bubp4")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_iivyk")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_5hpqg")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_256wb")
}],
"loop": true,
"name": &"default",
"speed": 5.0
}]
[node name="VnScene" type="Control" unique_id=74039790]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Panel" type="Panel" parent="." unique_id=108231475]
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_xtnmq")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=546478917]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
alignment = 2
[node name="TextureRect" type="TextureRect" parent="VBoxContainer" unique_id=653606980]
material = SubResource("CanvasItemMaterial_xyfjv")
layout_mode = 2
texture = SubResource("AtlasTexture_qmngx")
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TextureRect" unique_id=1212665758]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
alignment = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TextureRect/VBoxContainer" unique_id=238151332]
layout_mode = 2
alignment = 1
[node name="Control" type="Control" parent="VBoxContainer/TextureRect/VBoxContainer/HBoxContainer" unique_id=1997339270]
layout_mode = 2
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="VBoxContainer/TextureRect/VBoxContainer/HBoxContainer/Control" unique_id=419477945]
position = Vector2(0, -27)
sprite_frames = SubResource("SpriteFrames_r6vt6")
autoplay = "default"

View 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

View File

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

View File

@@ -2,4 +2,17 @@ class_name CameraController extends Camera2D
func apply_drag(delta: Vector2) -> void: func apply_drag(delta: Vector2) -> void:
position += delta / zoom 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)

View File

@@ -1,7 +1,7 @@
class_name CombatProposal extends Resource class_name CombatProposal extends Resource
class CombatantStats: class CombatantStats:
var unit: Unit var deployed: DeployedUnit
var max_hp: int var max_hp: int
var hp: int var hp: int
var sp: int var sp: int

View File

@@ -1,6 +1,6 @@
class_name CombatSystem extends Node class_name CombatSystem extends Node
func create_proposal(attacker: Unit, defender: Unit, distance: int) -> CombatProposal: func create_proposal(attacker: DeployedUnit, defender: DeployedUnit, distance: int) -> CombatProposal:
var proposal := CombatProposal.new() var proposal := CombatProposal.new()
var atk_tactics := _filter_tactics(attacker, distance) var atk_tactics := _filter_tactics(attacker, distance)
@@ -19,10 +19,10 @@ func create_proposal(attacker: Unit, defender: Unit, distance: int) -> CombatPro
return proposal return proposal
func _filter_tactics(unit: Unit, distance: int) -> Array[CombatTactic]: func _filter_tactics(deployed: DeployedUnit, distance: int) -> Array[CombatTactic]:
var valid: Array[CombatTactic] = [] var valid: Array[CombatTactic] = []
for tactic in unit.tactics: for tactic in deployed.tactics:
if tactic.tactic_range and tactic.tactic_range.is_valid_range(distance, unit): if tactic.tactic_range and tactic.tactic_range.is_valid_range(distance, deployed.current_stats):
valid.append(tactic) valid.append(tactic)
return valid return valid
@@ -34,28 +34,30 @@ func _find_default_attack(tactics: Array[CombatTactic]) -> CombatTactic:
return tactics[0] if tactics.size() > 0 else null 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: 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() var stats := CombatProposal.CombatantStats.new()
stats.unit = unit stats.deployed = deployed
stats.max_hp = unit.current_stats.max_hp stats.max_hp = current.max_hp
stats.hp = unit.current_stats.current_hp stats.hp = current.current_hp
stats.sp = unit.current_stats.current_sp stats.sp = current.current_sp
stats.spd = unit.current_stats.spd stats.spd = current.spd
stats.available_tactics = available stats.available_tactics = available
stats.selected_tactic = selected stats.selected_tactic = selected
if selected and selected.deals_damage(): if selected and selected.deals_damage():
var offensive: Dictionary = selected.get_offensive_stats(unit) var offensive: Dictionary = selected.get_offensive_stats(current)
stats.atk = offensive["atk"] stats.atk = offensive["atk"]
stats.hit = offensive["hit"] - opponent.current_stats.eva stats.hit = offensive["hit"] - opp_current.eva
else: else:
stats.atk = 0 stats.atk = 0
stats.hit = 0 stats.hit = 0
if opponent_selected and opponent_selected.deals_damage(): if opponent_selected and opponent_selected.deals_damage():
stats.def = opponent_selected.get_relevant_defense(unit) stats.def = opponent_selected.get_relevant_defense(current)
else: else:
stats.def = unit.current_stats.phys_def stats.def = current.phys_def
return stats return stats
@@ -74,29 +76,29 @@ func update_tactic(proposal: CombatProposal, is_attacker: bool, tactic: CombatTa
# Recalculate this side's offensive stats # Recalculate this side's offensive stats
if tactic and tactic.deals_damage(): if tactic and tactic.deals_damage():
var offensive: Dictionary = tactic.get_offensive_stats(self_stats.unit) var offensive: Dictionary = tactic.get_offensive_stats(self_stats.deployed.current_stats)
self_stats.atk = offensive["atk"] self_stats.atk = offensive["atk"]
self_stats.hit = offensive["hit"] - opp_stats.unit.current_stats.eva self_stats.hit = offensive["hit"] - opp_stats.deployed.current_stats.eva
else: else:
self_stats.atk = 0 self_stats.atk = 0
self_stats.hit = 0 self_stats.hit = 0
# Recalculate opponent's def based on this side's new tactic # Recalculate opponent's def based on this side's new tactic
if tactic and tactic.deals_damage(): if tactic and tactic.deals_damage():
opp_stats.def = tactic.get_relevant_defense(opp_stats.unit) opp_stats.def = tactic.get_relevant_defense(opp_stats.deployed.current_stats)
else: else:
opp_stats.def = opp_stats.unit.current_stats.phys_def opp_stats.def = opp_stats.deployed.current_stats.phys_def
func select_ai_tactic(unit: Unit, opponent: Unit, available_tactics: Array[CombatTactic]) -> CombatTactic: func select_ai_tactic(deployed: DeployedUnit, opponent: DeployedUnit, available_tactics: Array[CombatTactic]) -> CombatTactic:
var best_tactic: CombatTactic = null var best_tactic: CombatTactic = null
var best_damage := -1 var best_damage := -1
for tactic in available_tactics: for tactic in available_tactics:
if not tactic.deals_damage(): if not tactic.deals_damage():
continue continue
var offensive: Dictionary = tactic.get_offensive_stats(unit) var offensive: Dictionary = tactic.get_offensive_stats(deployed.current_stats)
var defense: int = tactic.get_relevant_defense(opponent) var defense: int = tactic.get_relevant_defense(opponent.current_stats)
var damage := maxi(offensive["atk"] - defense, 0) var damage := maxi(offensive["atk"] - defense, 0)
if damage > best_damage: if damage > best_damage:
best_damage = damage best_damage = damage
@@ -111,41 +113,31 @@ func select_ai_tactic(unit: Unit, opponent: Unit, available_tactics: Array[Comba
return best_tactic 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: func apply_proposal(proposal: CombatProposal) -> void:
var atk_stats := proposal.attacker var atk_stats := proposal.attacker
var def_stats := proposal.defender var def_stats := proposal.defender
var atk_unit := atk_stats.unit var atk_deployed := atk_stats.deployed
var def_unit := def_stats.unit 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) # Attacker strikes (if their tactic deals damage)
if atk_stats.selected_tactic and atk_stats.selected_tactic.deals_damage(): if atk_stats.selected_tactic and atk_stats.selected_tactic.deals_damage():
var atk_roll := randi_range(1, 100) var atk_roll := randi_range(1, 100)
if atk_roll <= atk_stats.hit: if atk_roll <= atk_stats.hit:
var damage := maxi(atk_stats.atk - def_stats.def, 0) var damage := maxi(atk_stats.atk - def_stats.def, 0)
def_unit.take_damage(damage) def_deployed.take_damage(damage)
# Counterattack if defender survives and their tactic deals 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(): 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) var def_roll := randi_range(1, 100)
if def_roll <= def_stats.hit: if def_roll <= def_stats.hit:
var damage := maxi(def_stats.atk - atk_stats.def, 0) var damage := maxi(def_stats.atk - atk_stats.def, 0)
atk_unit.take_damage(damage) atk_deployed.take_damage(damage)
func _is_player_controlled(unit: Unit) -> bool: func _is_player_controlled(deployed: DeployedUnit) -> bool:
return unit.current_allegiance.type == UnitAllegiance.AllegianceType.PLAYER return deployed.unit.allegiance.type == UnitAllegiance.AllegianceType.PLAYER

View File

@@ -3,11 +3,11 @@ class_name CombatTactic extends Resource
@export var tactic_name: String = "" @export var tactic_name: String = ""
@export var tactic_range: CombatTacticRange @export var tactic_range: CombatTacticRange
func get_offensive_stats(unit: Unit) -> Variant: func get_offensive_stats(_stats: DeployedUnitStats) -> Variant:
return null return null
func get_relevant_defense(unit: Unit) -> int: func get_relevant_defense(stats: DeployedUnitStats) -> int:
return unit.current_stats.phys_def return stats.phys_def
func deals_damage() -> bool: func deals_damage() -> bool:
return false return false

View File

@@ -1,5 +1,5 @@
# resources/resource_definitions/any_combat_tactic_range.gd # resources/resource_definitions/any_combat_tactic_range.gd
class_name AnyCombatTacticRange extends CombatTacticRange class_name AnyCombatTacticRange extends CombatTacticRange
func is_valid_range(distance: int, unit: Unit) -> bool: func is_valid_range(_distance: int, _stats: DeployedUnitStats) -> bool:
return true return true

View File

@@ -1,5 +1,5 @@
# resources/resource_definitions/combat_tactic_range.gd # resources/resource_definitions/combat_tactic_range.gd
class_name CombatTacticRange extends Resource class_name CombatTacticRange extends Resource
func is_valid_range(distance: int, unit: Unit) -> bool: func is_valid_range(_distance: int, _stats: DeployedUnitStats) -> bool:
return false return false

View File

@@ -3,5 +3,5 @@ class_name FixedCombatTacticRange extends CombatTacticRange
@export var tactic_range: int = 1 @export var tactic_range: int = 1
func is_valid_range(distance: int, unit: Unit) -> bool: func is_valid_range(distance: int, _stats: DeployedUnitStats) -> bool:
return distance <= tactic_range return distance <= tactic_range

View File

@@ -1,5 +1,5 @@
# resources/resource_definitions/unit_matching_combat_tactic_range.gd # resources/resource_definitions/unit_matching_combat_tactic_range.gd
class_name UnitMatchingCombatTacticRange extends CombatTacticRange class_name UnitMatchingCombatTacticRange extends CombatTacticRange
func is_valid_range(distance: int, unit: Unit) -> bool: func is_valid_range(distance: int, stats: DeployedUnitStats) -> bool:
return distance <= unit.current_stats.atk_range return distance <= stats.atk_range

View File

@@ -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

View File

@@ -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

View File

@@ -3,9 +3,15 @@ class_name CombatUI extends CanvasLayer
signal fight_confirmed(proposal: CombatProposal) signal fight_confirmed(proposal: CombatProposal)
signal fight_cancelled signal fight_cancelled
@onready var unit_panel: PanelContainer = %UnitPanel @onready var unit_panel: Control = %UnitPanel
@onready var name_label: Label = %NameLabel @onready var unit_name_label: RichTextLabel = %UnitName
@onready var hp_bar: ProgressBar = %HPBar @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 background_tint: ColorRect = %BackgroundTint
@onready var proposal_panel: PanelContainer = %CombatProposalPanel @onready var proposal_panel: PanelContainer = %CombatProposalPanel
@@ -26,7 +32,7 @@ signal fight_cancelled
@onready var atk_tactic_select: OptionButton = %AttackerTacticSelect @onready var atk_tactic_select: OptionButton = %AttackerTacticSelect
@onready var def_tactic_select: OptionButton = %DefenderTacticSelect @onready var def_tactic_select: OptionButton = %DefenderTacticSelect
var _selected_unit: Unit var _selected_unit: DeployedUnit
var _current_proposal: CombatProposal var _current_proposal: CombatProposal
var combat_system: CombatSystem var combat_system: CombatSystem
@@ -37,30 +43,29 @@ func _ready() -> void:
cancel_button.pressed.connect(_on_cancel_pressed) cancel_button.pressed.connect(_on_cancel_pressed)
atk_tactic_select.item_selected.connect(_on_atk_tactic_selected) atk_tactic_select.item_selected.connect(_on_atk_tactic_selected)
def_tactic_select.item_selected.connect(_on_def_tactic_selected) def_tactic_select.item_selected.connect(_on_def_tactic_selected)
for unit: Unit in get_tree().get_nodes_in_group("units"): for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"):
unit.unit_selected_changed.connect(_on_unit_selected_changed) deployed.unit_selected_changed.connect(_on_unit_selected_changed)
unit.unit_died.connect(_on_unit_died) deployed.unit_died.connect(_on_unit_died)
get_tree().node_added.connect(_on_node_added) get_tree().node_added.connect(_on_node_added)
func _on_node_added(node: Node) -> void: func _on_node_added(node: Node) -> void:
if node is Unit and node.is_in_group("units"): if node is DeployedUnit and node.is_in_group("deployed_units"):
if not node.unit_selected_changed.is_connected(_on_unit_selected_changed): if not node.unit_selected_changed.is_connected(_on_unit_selected_changed):
node.unit_selected_changed.connect(_on_unit_selected_changed) node.unit_selected_changed.connect(_on_unit_selected_changed)
if not node.unit_died.is_connected(_on_unit_died): if not node.unit_died.is_connected(_on_unit_died):
node.unit_died.connect(_on_unit_died) node.unit_died.connect(_on_unit_died)
func _on_unit_died(unit: Unit) -> void: func _on_unit_died(deployed: DeployedUnit) -> void:
if _selected_unit == unit: if _selected_unit == deployed:
_selected_unit = null _selected_unit = null
unit_panel.visible = false unit_panel.visible = false
if _current_proposal: if _current_proposal:
if _current_proposal.attacker.unit == unit or _current_proposal.defender.unit == unit: if _current_proposal.attacker.deployed == deployed or _current_proposal.defender.deployed == deployed:
_hide_proposal() _hide_proposal()
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
if _selected_unit and is_instance_valid(_selected_unit): if _selected_unit and is_instance_valid(_selected_unit):
hp_bar.max_value = _selected_unit.current_stats.max_hp _refresh_unit_panel()
hp_bar.value = _selected_unit.current_stats.current_hp
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if proposal_panel.visible and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT: if proposal_panel.visible and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
@@ -68,17 +73,29 @@ func _unhandled_input(event: InputEvent) -> void:
fight_cancelled.emit() fight_cancelled.emit()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
func _on_unit_selected_changed(unit: Unit, selected: bool) -> void: func _on_unit_selected_changed(deployed: DeployedUnit, selected: bool) -> void:
if selected: if selected:
_selected_unit = unit _selected_unit = deployed
name_label.text = unit.current_info.name _refresh_unit_panel()
hp_bar.max_value = unit.current_stats.max_hp
hp_bar.value = unit.current_stats.current_hp
unit_panel.visible = true unit_panel.visible = true
else: else:
_selected_unit = null _selected_unit = null
unit_panel.visible = false 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: func show_proposal(proposal: CombatProposal) -> void:
_current_proposal = proposal _current_proposal = proposal
_populate_tactic_select(atk_tactic_select, proposal.attacker) _populate_tactic_select(atk_tactic_select, proposal.attacker)
@@ -112,21 +129,21 @@ func _populate_tactic_select(button: OptionButton, combatant: CombatProposal.Com
if tactic == combatant.selected_tactic: if tactic == combatant.selected_tactic:
selected_idx = i selected_idx = i
button.selected = selected_idx button.selected = selected_idx
var is_player := combatant.unit.current_allegiance.type == UnitAllegiance.AllegianceType.PLAYER var is_player := combatant.deployed.unit.allegiance.type == UnitAllegiance.AllegianceType.PLAYER
button.disabled = not is_player button.disabled = not is_player
func _refresh_stats() -> void: func _refresh_stats() -> void:
var atk := _current_proposal.attacker var atk := _current_proposal.attacker
var def := _current_proposal.defender var def := _current_proposal.defender
atk_name_label.text = atk.unit.current_info.name atk_name_label.text = atk.deployed.unit.info.name
atk_hp_bar.max_value = atk.max_hp atk_hp_bar.max_value = atk.max_hp
atk_hp_bar.value = atk.hp atk_hp_bar.value = atk.hp
atk_atk_label.text = "ATK: %d" % atk.atk atk_atk_label.text = "ATK: %d" % atk.atk
atk_def_label.text = "DEF: %d" % atk.def atk_def_label.text = "DEF: %d" % atk.def
atk_hit_label.text = "HIT: %d%%" % atk.hit atk_hit_label.text = "HIT: %d%%" % atk.hit
atk_spd_label.text = "SPD: %d" % atk.spd atk_spd_label.text = "SPD: %d" % atk.spd
def_name_label.text = def.unit.current_info.name def_name_label.text = def.deployed.unit.info.name
def_hp_bar.max_value = def.max_hp def_hp_bar.max_value = def.max_hp
def_hp_bar.value = def.hp def_hp_bar.value = def.hp
def_atk_label.text = "ATK: %d" % def.atk def_atk_label.text = "ATK: %d" % def.atk

View 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()

View File

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

View 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

View File

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

View 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)

View 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,
)

View File

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

View File

@@ -0,0 +1,81 @@
class_name MapLayout extends Resource
@export var size: Vector2i = Vector2i.ZERO
@export var rooms: Array[Room]
## 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.
@export var openings: Array[Vector2i]
var _tile_room_map: Dictionary = {}
var _opening_set: Dictionary = {}
func initialize() -> void:
assert(openings.size() % 2 == 0, "Openings must be provided as pairs of Vector2i")
_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]
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_openings() -> Array:
## Returns an array of [Vector2i, Vector2i] pairs representing opening edges.
var result: Array = []
for i in range(0, openings.size(), 2):
result.append([openings[i], openings[i + 1]])
return result
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: Vector2i = 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

View File

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

View File

@@ -0,0 +1,4 @@
class_name Room extends Resource
@export var id: int
@export var tiles: Array[Vector2i]

View File

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

View File

@@ -0,0 +1,266 @@
class_name WallRenderer
extends Node2D
## Renders wall textures by sampling segments from the aux_terrain texture atlas
## and compositing them onto tile edges. Each edge is made of two half-segments.
## Source atlas rects (x, y, w, h) from aux_terrain.BMP
## Each edge has two half-segments that together span the full tile edge.
# -- Left edge --
const LEFT_UPPER_RECT := Rect2(0, 103, 20, 50)
const LEFT_LOWER_RECT := Rect2(0, 53, 20, 50)
# -- Right edge --
const RIGHT_UPPER_RECT := Rect2(186, 103, 20, 50)
const RIGHT_LOWER_RECT := Rect2(186, 53, 20, 50)
# -- Top edge --
const TOP_LEFT_RECT := Rect2(103, 0, 50, 20)
const TOP_RIGHT_RECT := Rect2(53, 0, 50, 20)
# -- Bottom edge --
const BOTTOM_LEFT_RECT := Rect2(103, 186, 50, 20)
const BOTTOM_RIGHT_RECT := Rect2(53, 186, 50, 20)
# -- Inner corners (drawn where two perpendicular wall edges meet) --
const INNER_CORNER_UPPER_LEFT_RECT := Rect2(0, 0, 50, 50)
const INNER_CORNER_UPPER_RIGHT_RECT := Rect2(156, 0, 50, 50)
const INNER_CORNER_LOWER_LEFT_RECT := Rect2(0, 156, 50, 50)
const INNER_CORNER_LOWER_RIGHT_RECT := Rect2(156, 156, 50, 50)
# -- Openings (drawn on top of wall segments at doorway edges) --
## Vertical opening: tiles separated on y-axis (north-south doorway through a horizontal wall)
const VERTICAL_OPENING_RECT := Rect2(206, 36, 36, 42)
## Horizontal opening: tiles separated on x-axis (east-west doorway through a vertical wall)
const HORIZONTAL_OPENING_RECT := Rect2(210, 0, 41, 32)
## Wall thickness in game pixels (how far the border extends into the tile)
const WALL_THICKNESS := 20.0
## Inner corner piece size in game pixels (quarter of a tile)
const CORNER_SIZE := 50.0
## Half the tile edge length
var HALF_EDGE: float:
get:
return BattleMapHelper.TILE_SIZE / 2.0
@export var atlas_texture: Texture2D
# Each entry is [dest_rect: Rect2, source_rect: Rect2]
var _segments: Array = []
func draw_walls_for_layout(map_layout: MapLayout) -> void:
_segments.clear()
if not map_layout or not atlas_texture:
queue_redraw()
return
var tile_edges := _collect_tile_edges(map_layout)
for tile in tile_edges:
var edges: Array = tile_edges[tile]
_build_tile_walls(tile, edges)
_build_opening_sprites(map_layout)
queue_redraw()
func _draw() -> void:
if not atlas_texture:
return
for seg in _segments:
draw_texture_rect_region(atlas_texture, seg[0], seg[1])
func _collect_tile_edges(map_layout: MapLayout) -> Dictionary:
## Returns {Vector2i: Array[StringName]} mapping each tile to its wall edge directions.
## Includes both true walls and opening edges, so wall sprites are drawn underneath openings.
var tile_edges: Dictionary = {}
for edge_pair in map_layout.get_walls():
_add_edge_pair(tile_edges, edge_pair, map_layout)
for edge_pair in map_layout.get_openings():
_add_edge_pair(tile_edges, edge_pair, map_layout)
return tile_edges
func _add_edge_pair(tile_edges: Dictionary, edge_pair: Array, map_layout: MapLayout) -> void:
var tile_a: Vector2i = edge_pair[0]
var tile_b: Vector2i = edge_pair[1]
var diff: Vector2i = tile_b - tile_a
var edge_a := _direction_to_edge(diff)
if edge_a != &"":
if not tile_edges.has(tile_a):
tile_edges[tile_a] = []
tile_edges[tile_a].append(edge_a)
var edge_b := _direction_to_edge(-diff)
if edge_b != &"" and map_layout.is_tile_valid(tile_b):
if not tile_edges.has(tile_b):
tile_edges[tile_b] = []
tile_edges[tile_b].append(edge_b)
func _direction_to_edge(dir: Vector2i) -> StringName:
match dir:
Vector2i.RIGHT:
return &"right"
Vector2i.LEFT:
return &"left"
Vector2i.UP:
return &"top"
Vector2i.DOWN:
return &"bottom"
return &""
func _build_tile_walls(tile: Vector2i, edges: Array) -> void:
var tile_origin := Vector2(tile) * BattleMapHelper.TILE_SIZE
for edge in edges:
_build_edge_segments(tile_origin, edge)
# TODO: Outer corner segments
_build_outer_corners(tile, tile_origin, edges)
# Inner corner segments (for non-rectangular room support)
_build_inner_corners(tile, tile_origin, edges)
func _build_edge_segments(tile_origin: Vector2, edge: StringName) -> void:
var seg_a_rect: Rect2
var seg_b_rect: Rect2
var seg_a_offset: Vector2
var seg_b_offset: Vector2
var seg_a_size: Vector2
var seg_b_size: Vector2
match edge:
&"left":
seg_a_rect = LEFT_UPPER_RECT
seg_b_rect = LEFT_LOWER_RECT
seg_a_size = Vector2(WALL_THICKNESS, HALF_EDGE)
seg_b_size = Vector2(WALL_THICKNESS, HALF_EDGE)
seg_a_offset = Vector2(0, 0)
seg_b_offset = Vector2(0, HALF_EDGE)
&"right":
seg_a_rect = RIGHT_UPPER_RECT
seg_b_rect = RIGHT_LOWER_RECT
seg_a_size = Vector2(WALL_THICKNESS, HALF_EDGE)
seg_b_size = Vector2(WALL_THICKNESS, HALF_EDGE)
seg_a_offset = Vector2(BattleMapHelper.TILE_SIZE - WALL_THICKNESS, 0)
seg_b_offset = Vector2(BattleMapHelper.TILE_SIZE - WALL_THICKNESS, HALF_EDGE)
&"top":
seg_a_rect = TOP_LEFT_RECT
seg_b_rect = TOP_RIGHT_RECT
seg_a_size = Vector2(HALF_EDGE, WALL_THICKNESS)
seg_b_size = Vector2(HALF_EDGE, WALL_THICKNESS)
seg_a_offset = Vector2(0, 0)
seg_b_offset = Vector2(HALF_EDGE, 0)
&"bottom":
seg_a_rect = BOTTOM_LEFT_RECT
seg_b_rect = BOTTOM_RIGHT_RECT
seg_a_size = Vector2(HALF_EDGE, WALL_THICKNESS)
seg_b_size = Vector2(HALF_EDGE, WALL_THICKNESS)
seg_a_offset = Vector2(0, BattleMapHelper.TILE_SIZE - WALL_THICKNESS)
seg_b_offset = Vector2(HALF_EDGE, BattleMapHelper.TILE_SIZE - WALL_THICKNESS)
_queue_segment(tile_origin + seg_a_offset, seg_a_size, seg_a_rect)
_queue_segment(tile_origin + seg_b_offset, seg_b_size, seg_b_rect)
func _queue_segment(pos: Vector2, target_size: Vector2, source_rect: Rect2) -> void:
_segments.append([Rect2(pos, target_size), source_rect])
func _build_opening_sprites(map_layout: MapLayout) -> void:
## Composites opening (doorway) sprites on top of the wall segments at opening edges.
## Each opening is split in half across the shared edge, half drawn on each tile.
for opening in map_layout.get_openings():
var tile_a: Vector2i = opening[0]
var tile_b: Vector2i = opening[1]
var diff: Vector2i = tile_b - tile_a
# Normalize so the pair is ordered along the positive axis (tile_a < tile_b).
if diff == Vector2i.LEFT or diff == Vector2i.UP:
var swap := tile_a
tile_a = tile_b
tile_b = swap
diff = -diff
var origin_a := Vector2(tile_a) * BattleMapHelper.TILE_SIZE
var origin_b := Vector2(tile_b) * BattleMapHelper.TILE_SIZE
if diff == Vector2i.DOWN:
_queue_vertical_opening(origin_a, origin_b)
elif diff == Vector2i.RIGHT:
_queue_horizontal_opening(origin_a, origin_b)
func _queue_vertical_opening(origin_upper: Vector2, origin_lower: Vector2) -> void:
# Vertical opening: tiles vertically adjacent; horizontal wall edge between them.
var src := VERTICAL_OPENING_RECT
var w: float = src.size.x
var h_total: float = src.size.y
var h_upper: float = floorf(h_total / 2.0) # 14
var h_lower: float = h_total - h_upper # 15
var x_offset := (BattleMapHelper.TILE_SIZE - w) / 2.0
var src_upper := Rect2(src.position, Vector2(w, h_upper))
var src_lower := Rect2(src.position + Vector2(0, h_upper), Vector2(w, h_lower))
_queue_segment(origin_upper + Vector2(x_offset, BattleMapHelper.TILE_SIZE - h_upper), Vector2(w, h_upper), src_upper)
_queue_segment(origin_lower + Vector2(x_offset, 0), Vector2(w, h_lower), src_lower)
func _queue_horizontal_opening(origin_left: Vector2, origin_right: Vector2) -> void:
# Horizontal opening: tiles horizontally adjacent; vertical wall edge between them.
var src := HORIZONTAL_OPENING_RECT
var w_total: float = src.size.x
var h: float = src.size.y
var w_left: float = floorf(w_total / 2.0) # 14
var w_right: float = w_total - w_left # 14
var y_offset := (BattleMapHelper.TILE_SIZE - h) / 2.0
var src_left := Rect2(src.position, Vector2(w_left, h))
var src_right := Rect2(src.position + Vector2(w_left, 0), Vector2(w_right, h))
_queue_segment(origin_left + Vector2(BattleMapHelper.TILE_SIZE - w_left, y_offset), Vector2(w_left, h), src_left)
_queue_segment(origin_right + Vector2(0, y_offset), Vector2(w_right, h), src_right)
func _build_outer_corners(_tile: Vector2i, _tile_origin: Vector2, _edges: Array) -> void:
# TODO: Implement outer corner segments
# Check pairs of adjacent edges (e.g., "top" + "left" → top-left outer corner)
# and draw the corner piece from the atlas.
pass
func _build_inner_corners(_tile: Vector2i, tile_origin: Vector2, edges: Array) -> void:
## Draws decorative corner pieces wherever two perpendicular wall edges meet
## on the same tile (e.g., a top wall + left wall produces an upper-left corner).
var has_top := edges.has(&"top")
var has_bottom := edges.has(&"bottom")
var has_left := edges.has(&"left")
var has_right := edges.has(&"right")
var corner_size := Vector2(CORNER_SIZE, CORNER_SIZE)
if has_top and has_left:
_queue_segment(
tile_origin + Vector2(0, 0),
corner_size,
INNER_CORNER_UPPER_LEFT_RECT
)
if has_top and has_right:
_queue_segment(
tile_origin + Vector2(BattleMapHelper.TILE_SIZE - CORNER_SIZE, 0),
corner_size,
INNER_CORNER_UPPER_RIGHT_RECT
)
if has_bottom and has_left:
_queue_segment(
tile_origin + Vector2(0, BattleMapHelper.TILE_SIZE - CORNER_SIZE),
corner_size,
INNER_CORNER_LOWER_LEFT_RECT
)
if has_bottom and has_right:
_queue_segment(
tile_origin + Vector2(BattleMapHelper.TILE_SIZE - CORNER_SIZE, BattleMapHelper.TILE_SIZE - CORNER_SIZE),
corner_size,
INNER_CORNER_LOWER_RIGHT_RECT
)

View File

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

View File

@@ -4,13 +4,13 @@ const SPEED = 192.0
@export var dl_map: CombatMap @export var dl_map: CombatMap
signal combat_requested(attacker: Unit, defender: Unit) signal combat_requested(attacker: DeployedUnit, defender: DeployedUnit)
signal mouse_grid_changed(coords: Vector2i) signal mouse_grid_changed(coords: Vector2i)
signal camera_drag(delta: Vector2) signal camera_drag(delta: Vector2)
var input_disabled := false var input_disabled := false
var _selected_unit: Unit = null var _selected_unit: DeployedUnit = null
var _target_pos: Vector2 var _target_pos: Vector2
var _goal_pos: Vector2 var _goal_pos: Vector2
var _moving := false var _moving := false
@@ -24,19 +24,19 @@ const DRAG_THRESHOLD := 8.0
func _ready() -> void: func _ready() -> void:
for unit: Unit in get_tree().get_nodes_in_group("units"): for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"):
unit.unit_died.connect(_on_unit_died) deployed.unit_died.connect(_on_unit_died)
get_tree().node_added.connect(_on_node_added) get_tree().node_added.connect(_on_node_added)
func _on_node_added(node: Node) -> void: func _on_node_added(node: Node) -> void:
if node is Unit and node.is_in_group("units"): if node is DeployedUnit and node.is_in_group("deployed_units"):
if not node.unit_died.is_connected(_on_unit_died): if not node.unit_died.is_connected(_on_unit_died):
node.unit_died.connect(_on_unit_died) node.unit_died.connect(_on_unit_died)
func _on_unit_died(unit: Unit) -> void: func _on_unit_died(deployed: DeployedUnit) -> void:
if _selected_unit == unit: if _selected_unit == deployed:
_selected_unit = null _selected_unit = null
_moving = false _moving = false
@@ -45,7 +45,7 @@ func _process(_delta: float) -> void:
if input_disabled: if input_disabled:
return return
var mouse_pos := get_viewport().get_canvas_transform().affine_inverse() * get_viewport().get_mouse_position() var mouse_pos := get_viewport().get_canvas_transform().affine_inverse() * get_viewport().get_mouse_position()
var coords := dl_map.world_to_coords(mouse_pos) var coords := BattleMapHelper.world_to_coords(mouse_pos)
if coords != _current_grid_coords: if coords != _current_grid_coords:
_current_grid_coords = coords _current_grid_coords = coords
mouse_grid_changed.emit(coords) mouse_grid_changed.emit(coords)
@@ -116,9 +116,10 @@ func _physics_process(delta: float) -> void:
else: else:
dir = Vector2(0, signf(diff.y)) dir = Vector2(0, signf(diff.y))
var next_pos := _selected_unit.position + dir * dl_map.TILE_SIZE var next_pos := _selected_unit.position + dir * BattleMapHelper.TILE_SIZE
var grid_coords := dl_map.world_to_coords(next_pos) var grid_coords := BattleMapHelper.world_to_coords(next_pos)
if dl_map.is_wall(grid_coords): var current_coords := BattleMapHelper.world_to_coords(_selected_unit.position)
if not dl_map.is_tile_passable(current_coords, grid_coords):
_goal_pos = _selected_unit.position _goal_pos = _selected_unit.position
return return
@@ -137,30 +138,30 @@ func _handle_left_click(screen_pos: Vector2) -> void:
_select_unit(clicked_unit) _select_unit(clicked_unit)
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
elif _selected_unit: elif _selected_unit:
var snapped_pos := dl_map.snap_to_grid(world_pos) var snapped_pos := BattleMapHelper.snap_to_grid(world_pos)
var grid_coords := dl_map.world_to_coords(world_pos) var grid_coords := BattleMapHelper.world_to_coords(world_pos)
if dl_map.is_wall(grid_coords): if not dl_map.is_tile_valid(grid_coords):
return return
_goal_pos = snapped_pos _goal_pos = snapped_pos
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
func _select_unit(unit: Unit) -> void: func _select_unit(deployed: DeployedUnit) -> void:
if _selected_unit: if _selected_unit:
_selected_unit.set_selected(false) _selected_unit.set_selected(false)
_selected_unit = unit _selected_unit = deployed
_selected_unit.set_selected(true) _selected_unit.set_selected(true)
_goal_pos = _selected_unit.position _goal_pos = _selected_unit.position
_target_pos = _selected_unit.position _target_pos = _selected_unit.position
_moving = false _moving = false
func _get_unit_at(world_pos: Vector2) -> Unit: func _get_unit_at(world_pos: Vector2) -> DeployedUnit:
var snapped := dl_map.snap_to_grid(world_pos) var snapped_coords := BattleMapHelper.snap_to_grid(world_pos)
for unit: Unit in get_tree().get_nodes_in_group("units"): for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"):
if not unit.is_alive(): if not deployed.is_alive():
continue continue
var unit_snapped := dl_map.snap_to_grid(unit.global_position) var unit_snapped := BattleMapHelper.snap_to_grid(deployed.global_position)
if unit_snapped == snapped: if unit_snapped == snapped_coords:
return unit return deployed
return null return null

View File

@@ -7,6 +7,33 @@ class_name StrategyPhase extends Node2D
@onready var camera: CameraController = $Camera2D @onready var camera: CameraController = $Camera2D
func _ready() -> void: func _ready() -> void:
# -- Test room layout (remove once map editor exists) --
var room_a := Room.new()
room_a.id = 0
room_a.tiles = [
Vector2i(2, 2), Vector2i(3, 2), Vector2i(4, 2),
Vector2i(2, 3), Vector2i(3, 3), Vector2i(4, 3),
Vector2i(2, 4), Vector2i(3, 4), Vector2i(4, 4),
]
var room_b := Room.new()
room_b.id = 1
room_b.tiles = [
Vector2i(5, 2), Vector2i(6, 2), Vector2i(7, 2),
Vector2i(5, 3), Vector2i(6, 3), Vector2i(7, 3),
Vector2i(5, 4), Vector2i(6, 4), Vector2i(7, 4),
]
var layout := MapLayout.new()
layout.rooms = [room_a, room_b]
# Opening between (4,3) in room_a and (5,3) in room_b
layout.openings = [Vector2i(4, 3), Vector2i(5, 3)]
layout.size = Vector2i(10, 7)
combat_map.apply_layout(layout)
camera.set_map_bounds(combat_map.get_map_rect())
# -- End test room layout --
player_controller.combat_requested.connect(_on_combat_requested) player_controller.combat_requested.connect(_on_combat_requested)
player_controller.mouse_grid_changed.connect(_on_mouse_grid_changed) player_controller.mouse_grid_changed.connect(_on_mouse_grid_changed)
player_controller.camera_drag.connect(camera.apply_drag) player_controller.camera_drag.connect(camera.apply_drag)
@@ -18,9 +45,9 @@ func _ready() -> void:
func _on_mouse_grid_changed(coords: Vector2i) -> void: func _on_mouse_grid_changed(coords: Vector2i) -> void:
combat_map.target_tile(coords) combat_map.target_tile(coords)
func _on_combat_requested(attacker: Unit, defender: Unit) -> void: func _on_combat_requested(attacker: DeployedUnit, defender: DeployedUnit) -> void:
var atk_coords := combat_map.world_to_coords(attacker.position) var atk_coords := BattleMapHelper.world_to_coords(attacker.position)
var def_coords := combat_map.world_to_coords(defender.position) var def_coords := BattleMapHelper.world_to_coords(defender.position)
var distance := absi(atk_coords.x - def_coords.x) + absi(atk_coords.y - def_coords.y) 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) var proposal := combat_system.create_proposal(attacker, defender, distance)
_set_input_disabled(true) _set_input_disabled(true)
@@ -38,4 +65,3 @@ func _on_fight_cancelled() -> void:
func _set_input_disabled(disabled: bool) -> void: func _set_input_disabled(disabled: bool) -> void:
player_controller.input_disabled = disabled player_controller.input_disabled = disabled
combat_map.set_highlight_enabled(not disabled)

View File

@@ -0,0 +1,3 @@
class_name BattleMapConstants extends Object
const TILE_SIZE: float = 100.0

View File

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

View File

@@ -0,0 +1,7 @@
@abstract class_name ConsoleCommand extends RefCounted
@abstract func get_command_name() -> String
@abstract func get_help_text() -> String
@abstract func run(args: Array, context: Dictionary) -> String

View File

@@ -6,7 +6,7 @@ func get_command_name() -> String:
func get_help_text() -> String: func get_help_text() -> String:
return "Lists all available commands" return "Lists all available commands"
func run(args: Array, context: Dictionary) -> String: func run(_args: Array, context: Dictionary) -> String:
var commands: Array = context["commands"] var commands: Array = context["commands"]
var lines: PackedStringArray = [] var lines: PackedStringArray = []
for command: ConsoleCommand in commands: for command: ConsoleCommand in commands:

View File

@@ -6,7 +6,7 @@ func get_command_name() -> String:
func get_help_text() -> String: func get_help_text() -> String:
return "Lists available scenes for swapping" return "Lists available scenes for swapping"
func run(args: Array, context: Dictionary) -> String: func run(_args: Array, context: Dictionary) -> String:
var registry: Array = context["scene_registry"] var registry: Array = context["scene_registry"]
var lines: PackedStringArray = [] var lines: PackedStringArray = []
for entry: Dictionary in registry: for entry: Dictionary in registry:

Some files were not shown because too many files have changed in this diff Show More