Reorganized files, started splitting up unit

This commit is contained in:
gamer147
2026-04-08 18:28:52 -04:00
parent 24134cfa33
commit c192d48bc4
70 changed files with 528 additions and 56 deletions

View File

@@ -0,0 +1,127 @@
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 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()
if map_layout:
apply_layout(map_layout)
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 remove_unit(unit: Unit) -> void:
if unit.get_parent() == self:
remove_child(unit)
func target_tile(coords: Vector2i) -> void:
highlight_map.target_tile(coords)
func 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) * 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 @@
uid://bks7uplgjjdg0

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
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.
const TILE_SIZE := 100.0
## 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) * TILE_SIZE)
queue_redraw()
func _draw() -> void:
if not atlas_texture:
return
var dest_size := Vector2(TILE_SIZE, 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,16 @@
class_name GridOverlay extends TileMapLayer
const SOURCE_ID = 0
const HIGHLIGHT_SPRITE_ID = Vector2i(0,0)
@onready var targeting_selector: Sprite2D = $TargetingIndicator
func highlight_tile(coords: Vector2i) -> void:
set_cell(coords, SOURCE_ID, HIGHLIGHT_SPRITE_ID)
func clear_tile(coords: Vector2i) -> void:
set_cell(coords)
func target_tile(coords: Vector2i) -> void:
targeting_selector.position = coords * tile_set.tile_size
targeting_selector.visible = true

View File

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

View File

@@ -0,0 +1,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.
const TILE_SIZE := 100.0
## 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
const HALF_EDGE := 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) * 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(TILE_SIZE - WALL_THICKNESS, 0)
seg_b_offset = Vector2(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, TILE_SIZE - WALL_THICKNESS)
seg_b_offset = Vector2(HALF_EDGE, 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) * TILE_SIZE
var origin_b := Vector2(tile_b) * 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 := (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, 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 := (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(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(TILE_SIZE - CORNER_SIZE, 0),
corner_size,
INNER_CORNER_UPPER_RIGHT_RECT
)
if has_bottom and has_left:
_queue_segment(
tile_origin + Vector2(0, TILE_SIZE - CORNER_SIZE),
corner_size,
INNER_CORNER_LOWER_LEFT_RECT
)
if has_bottom and has_right:
_queue_segment(
tile_origin + Vector2(TILE_SIZE - CORNER_SIZE, TILE_SIZE - CORNER_SIZE),
corner_size,
INNER_CORNER_LOWER_RIGHT_RECT
)

View File

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