Add in-game chat, bot fill for casual, and borderless fullscreen

- In-game chat: T=all, Y=team (race doubles as team), server-relayed and
  team-filtered via new ChatManager autoload; last 10 messages shown
  bottom-left (chat/chat_box.gd).
- Bot fill for casual (bots/): keeps each of the match's 2 offered races
  at a minimum of 7 total humans+bots, spawning/despawning reactively as
  players join/leave. Bots always fly their race's fighter and use
  negative peer_ids so they ride the existing networked-ship stack
  (spawning, loadout sync, health/position sync, bullet attribution) with
  no special-casing. Casual matchmaking now forms with just 1 real player
  queued instead of waiting for a second (matchmaking-api/).
- Borderless fullscreen with stretch scaling disabled instead of scaling
  the Steam-Deck-matched 1280x800 canvas up to fill PC monitors (which
  read as zoomed in) — PC monitors now reveal more world/HUD at native
  size instead. Reworked main_menu/team_select/chat_box to position via
  anchors relative to the real window instead of hardcoded coordinates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 17:02:12 -04:00
parent edcda92812
commit c0b5f421c5
28 changed files with 705 additions and 79 deletions
+51 -18
View File
@@ -107,9 +107,11 @@ const RACES := [
},
]
# Row layout constants (screen is 1280×800)
const ROW_X1 := 120.0
const ROW_X2 := 1160.0
# Row layout constants — ROW_X1/ROW_X2_MARGIN are left/right margins (not
# absolute x-coordinates), so rows stretch to fill the actual window width;
# stretch mode is disabled (project.godot), so nothing scales automatically.
const ROW_X1 := 120.0
const ROW_X2_MARGIN := 120.0
const ROW_H := 115.0
const ROW_GAP := 6.0
const ROW_START := 175.0
@@ -166,7 +168,7 @@ func _build_root() -> void:
_root.add_child(overlay)
_loading_lbl = _label(_root, "LOADING MATCH...", 20, Color(0.5, 0.6, 0.75),
0, 380, 1280, 420, HORIZONTAL_ALIGNMENT_CENTER)
0, 380, 0, 420, HORIZONTAL_ALIGNMENT_CENTER, 0.0, 1.0)
func _swap_content() -> Control:
@@ -185,18 +187,24 @@ func _show_race_selection() -> void:
var c := _swap_content()
_label(c, "CHOOSE YOUR FACTION", 32, Color(0.42, 0.87, 1.0),
0, 105, 1280, 160, HORIZONTAL_ALIGNMENT_CENTER)
0, 105, 0, 160, HORIZONTAL_ALIGNMENT_CENTER, 0.0, 1.0)
_label(c, "This match: %s vs %s" % [_offered[0].name, _offered[1].name],
13, Color(0.45, 0.58, 0.72),
0, 170, 1280, 200, HORIZONTAL_ALIGNMENT_CENTER)
0, 170, 0, 200, HORIZONTAL_ALIGNMENT_CENTER, 0.0, 1.0)
# Both race buttons are a fixed 440px wide with a 100px gap, centered on
# the actual screen width via anchor 0.5 — not a hardcoded 1280px design
# width, since stretch mode is disabled (see project.godot).
const BTN_W := 440.0
const HALF_GAP := 50.0
var left_btn := _make_race_btn(_offered[0])
_place(left_btn, 150, 225, 590, 540)
_place(left_btn, -(BTN_W + HALF_GAP), 225, -HALF_GAP, 540, 0.5, 0.5)
left_btn.pressed.connect(_on_race_chosen.bind(_offered[0]))
c.add_child(left_btn)
var right_btn := _make_race_btn(_offered[1])
_place(right_btn, 690, 225, 1130, 540)
_place(right_btn, HALF_GAP, 225, BTN_W + HALF_GAP, 540, 0.5, 0.5)
right_btn.pressed.connect(_on_race_chosen.bind(_offered[1]))
c.add_child(right_btn)
@@ -212,15 +220,19 @@ func _show_ship_selection() -> void:
var c := _swap_content()
_label(c, "SELECT YOUR SHIP", 30, Color(0.42, 0.87, 1.0),
0, 85, 1280, 130, HORIZONTAL_ALIGNMENT_CENTER)
0, 85, 0, 130, HORIZONTAL_ALIGNMENT_CENTER, 0.0, 1.0)
_label(c, "Playing as — " + _chosen_race.name, 14, _chosen_race.color,
0, 135, 1280, 165, HORIZONTAL_ALIGNMENT_CENTER)
0, 135, 0, 165, HORIZONTAL_ALIGNMENT_CENTER, 0.0, 1.0)
var ships: Array = _chosen_race.ships
for i in ships.size():
var y := ROW_START + i * (ROW_H + ROW_GAP)
var btn := _make_ship_row(ships[i], _chosen_race.color)
_place(btn, ROW_X1, y, ROW_X2, y + ROW_H)
# ROW_X1 margin from the left edge, ROW_X2_MARGIN margin from the
# actual right edge (anchor_right=1.0) — the row stretches to fill
# whatever width is available instead of stopping at a hardcoded
# 1280px design width.
_place(btn, ROW_X1, y, -ROW_X2_MARGIN, y + ROW_H, 0.0, 1.0)
btn.pressed.connect(_on_ship_chosen.bind(ships[i]))
c.add_child(btn)
@@ -368,23 +380,44 @@ func _make_ship_row(ship: Dictionary, accent: Color) -> Button:
func _label(parent: Control, text: String, font_size: int, color: Color,
x1: float, y1: float, x2: float, y2: float,
align: HorizontalAlignment = HORIZONTAL_ALIGNMENT_LEFT) -> Label:
align: HorizontalAlignment = HORIZONTAL_ALIGNMENT_LEFT,
anchor_left: float = 0.0, anchor_right: float = 0.0) -> Label:
var lbl := Label.new()
lbl.text = text
lbl.horizontal_alignment = align
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lbl.add_theme_font_size_override("font_size", font_size)
lbl.add_theme_color_override("font_color", color)
_place(lbl, x1, y1, x2, y2)
_place(lbl, x1, y1, x2, y2, anchor_left, anchor_right)
parent.add_child(lbl)
return lbl
func _place(node: Control, x1: float, y1: float, x2: float, y2: float) -> void:
node.set_anchor_and_offset(SIDE_LEFT, 0.0, x1)
node.set_anchor_and_offset(SIDE_TOP, 0.0, y1)
node.set_anchor_and_offset(SIDE_RIGHT, 0.0, x2)
node.set_anchor_and_offset(SIDE_BOTTOM, 0.0, y2)
# anchor_left/anchor_right default to 0.0 (pixels measured from the left
# edge), matching the design-resolution layout this screen was originally
# built at. Pass 1.0 for anchor_right to make x2 a margin from the actual
# right edge instead (stretches with window width), or 0.5 for both to
# center a fixed-size element regardless of window width — see call sites
# below for which each element needs (stretch mode is disabled, so nothing
# scales automatically the way it used to under canvas_items stretch).
#
# Anchors and offsets are set as plain property assignments, NOT via
# sequential set_anchor_and_offset() calls — that method's default
# push_opposite_anchor=true drags the opposite side's anchor/offset along
# whenever the two calls momentarily disagree (e.g. left set to 0.5 while
# right is still its 0.0 default), corrupting layout for any non-0.0 anchor.
# Found via main_menu.gd's play buttons rendering flush against the left
# edge instead of centered.
func _place(node: Control, x1: float, y1: float, x2: float, y2: float,
anchor_left: float = 0.0, anchor_right: float = 0.0) -> void:
node.anchor_left = anchor_left
node.anchor_right = anchor_right
node.anchor_top = 0.0
node.anchor_bottom = 0.0
node.offset_left = x1
node.offset_top = y1
node.offset_right = x2
node.offset_bottom = y2
func _flat(col: Color, radius: int = 0) -> StyleBoxFlat: