Add real server pool, race roster overhaul, and ship energy system
Server browsing & matchmaking: - HL2-style main menu (QUICK PLAY/SERVER SELECT/OPTIONS/PROFILE/QUIT) replaces the old CASUAL/RANKED tiles; RANKED removed end-to-end (menu, matchmaking-api's Mode.ranked queue path, MMR matching) until ranked is real. - New menu/server_select.gd lists real servers from the matchmaking API's GET /servers and connects directly, retiring the old unreachable menu/server_browser.gd. - Real game-server pool: NetworkManager.host_server() self-registers on boot and heartbeats every 8s with live player counts; API computes available/full status from player_count, and a background sweep marks any server offline once its heartbeat goes stale (catches a crashed server that never deregistered). - Server-select rows get a live UDP ping probe (query port = game port + 10000) instead of trusting stale DB numbers; post-connect HUD shows live RTT off ENet's own peer stats. Race roster overhaul: - Swapped Terran/Mechanos/Vorg for the pivoted roster — Apex Dynamics, Inner Sphere Navy, Outer Rim Collective — each with a 3-ship Fighter/Gunner/Tank lineup, art cropped from concept sheets with background removal + orientation fixes per sheet. - Live headcount + roster + "TEAM FULL" lock on the race-select screen, shared between the initial pre-spawn pick and the pause menu's live SELECT TEAM swap. - Bot personalities (bots/bot_personality.gd): aggression/caution/ accuracy/reaction/awareness traits rolled per bot instead of one fixed AI profile. HUD additions: - Player list (roster, teammates white/enemies yellow, bots flagged), kill feed, minimap, and explosion VFX on death. - Health and energy now render as bars (hud/stat_bar.gd) instead of text in the top-left HUD. Ship energy system: - Per-role max energy (Fighter 100 / Gunner 150 / Tank 250), 75 energy per shot, flat regen (100 per 2.5s), fully server-authoritative and piggybacked on the existing per-tick state broadcast alongside health. - New blue "mirrored" bar top-middle of the screen (hud/energy_bar.gd) whose fill drains from both edges toward the center instead of left-to-right. Ship handling tuning: - Turn rate reduced (4.0 -> 1.0 rad/s) so a quick tap no longer over-rotates; holding past 0.15s ramps to double speed (2.0 rad/s) for fast full turns, gated the same way damage already is so replay during reconciliation can't double-count the hold timer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+187
-82
@@ -1,107 +1,75 @@
|
||||
extends CanvasLayer
|
||||
class_name TeamSelect
|
||||
|
||||
# Movement speed factor by role, shared across all 3 races so e.g. every
|
||||
# race's Interceptor moves at the same speed as every other race's.
|
||||
# Movement speed factor by role, shared across all 3 factions so e.g. every
|
||||
# faction's Fighter moves at the same speed as every other faction's.
|
||||
const SPEED_BY_ROLE := {
|
||||
"Interceptor": 1.3,
|
||||
"Gunship": 1.15,
|
||||
"Bomber": 1.0,
|
||||
"Support": 0.9,
|
||||
"Heavy": 0.7,
|
||||
"Fighter": 1.3,
|
||||
"Gunner": 1.15,
|
||||
"Tank": 0.7,
|
||||
}
|
||||
|
||||
const RACES := [
|
||||
{
|
||||
"id": 1, "name": "TERRAN REPUBLIC", "sub": "Angular gray-blue military ships, standard balanced fighters",
|
||||
"color": Color(0.40, 0.60, 0.85),
|
||||
"id": 1, "name": "APEX DYNAMICS", "sub": "Sleek pearlescent-white corporate fleet — precision energy weapons, wins by not being seen",
|
||||
"color": Color(0.55, 0.85, 1.0),
|
||||
"ships": [
|
||||
{
|
||||
"name": "WARFRAME", "role": "Interceptor",
|
||||
"desc": "Very fast, rapid weak laser bursts. Built to close distance instantly and chip away at anything in range. Low individual hit strength but a relentless rate of fire punishes anyone who stands still.",
|
||||
"path": "res://assets/images/ships/terran/warframe.png", "scale": 0.229, "speed_factor": SPEED_BY_ROLE.Interceptor,
|
||||
"name": "LANCET", "role": "Fighter",
|
||||
"desc": "A single, high-precision bolt of blue coherent light. Built to poke from range and vanish before anything can return fire — the sharpest shot in the fleet, if you can land it.",
|
||||
"path": "res://assets/images/ships/apex/lancet.png", "scale": 0.296, "speed_factor": SPEED_BY_ROLE.Fighter,
|
||||
},
|
||||
{
|
||||
"name": "STRIKER", "role": "Gunship",
|
||||
"desc": "Super fast fire rate, very low damage lasers. Trades hitting power for volume — the more shots in the air, the more that connect. Best flown aggressively, applying constant pressure rather than picking single shots.",
|
||||
"path": "res://assets/images/ships/terran/striker.png", "scale": 0.244, "speed_factor": SPEED_BY_ROLE.Gunship,
|
||||
"name": "PULSAR", "role": "Gunner",
|
||||
"desc": "A wide, rapid-fire fan of low-damage green plasma pulses. Trades precision for volume — spray enough bolts and something connects.",
|
||||
"path": "res://assets/images/ships/apex/pulsar.png", "scale": 0.373, "speed_factor": SPEED_BY_ROLE.Gunner,
|
||||
},
|
||||
{
|
||||
"name": "DEPLOYER", "role": "Bomber",
|
||||
"desc": "Fast lasers plus an EMP mine dropped in your wake. Punishes anyone chasing too closely and disables systems on contact. Strong for area denial and covering a retreat.",
|
||||
"path": "res://assets/images/ships/terran/deployer.png", "scale": 0.265, "speed_factor": SPEED_BY_ROLE.Bomber,
|
||||
},
|
||||
{
|
||||
"name": "LINK NODE", "role": "Support",
|
||||
"desc": "Standard lasers plus a small repair pack dropped for allies to pick up. Keeps a team topped off in prolonged fights. Not built to duel alone — stay near the fight, not in the middle of it.",
|
||||
"path": "res://assets/images/ships/terran/link_node.png", "scale": 0.329, "speed_factor": SPEED_BY_ROLE.Support,
|
||||
},
|
||||
{
|
||||
"name": "BASTION", "role": "Heavy",
|
||||
"desc": "Tankier and slower, but its laser hits harder than anything else in the fleet. Built to anchor a push and absorb damage while dealing it back. Discourages anyone from engaging head-on.",
|
||||
"path": "res://assets/images/ships/terran/bastion.png", "scale": 0.283, "speed_factor": SPEED_BY_ROLE.Heavy,
|
||||
"name": "SOVEREIGN", "role": "Tank",
|
||||
"desc": "Homing micro-missiles alongside dual heavy plasma batteries. Slow and telegraphed, but its lock-on missiles punish anyone who tries to just outrun it.",
|
||||
"path": "res://assets/images/ships/apex/sovereign.png", "scale": 0.348, "speed_factor": SPEED_BY_ROLE.Tank,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 2, "name": "MECHANOS SOVEREIGNTY", "sub": "Industrial war machines built for brute-force firepower",
|
||||
"color": Color(0.85, 0.28, 0.28),
|
||||
"id": 2, "name": "INNER SPHERE NAVY", "sub": "Gunmetal-gray navy of Earth, Mars, and Mercury — disciplined, frontal, built to hold a line",
|
||||
"color": Color(0.80, 0.32, 0.32),
|
||||
"ships": [
|
||||
{
|
||||
"name": "SENTINEL", "role": "Interceptor",
|
||||
"desc": "A light scout mech with twin rapid cannons. Fast and maneuverable for a Mechanos hull, built to screen the front line and harass before the heavier machines arrive.",
|
||||
"path": "res://assets/images/ships/mech/sentinel.png", "scale": 0.205, "speed_factor": SPEED_BY_ROLE.Interceptor,
|
||||
"name": "PATRIOT", "role": "Fighter",
|
||||
"desc": "A single, hard-hitting high-caliber explosive shell. Standard-issue and dependable — a clean, punishing hit if you land it.",
|
||||
"path": "res://assets/images/ships/isn/patriot.png", "scale": 0.504, "speed_factor": SPEED_BY_ROLE.Fighter,
|
||||
},
|
||||
{
|
||||
"name": "VANGUARD", "role": "Gunship",
|
||||
"desc": "Dual autocannons built for sustained fire. Sits between the light scouts and the heavy machines — durable enough to hold a line, fast enough to reposition when it breaks.",
|
||||
"path": "res://assets/images/ships/mech/vanguard.png", "scale": 0.266, "speed_factor": SPEED_BY_ROLE.Gunship,
|
||||
"name": "BARRAGE", "role": "Gunner",
|
||||
"desc": "A relentless quad-barrel spray of fast, low-damage light bullets. Built for sustained suppressing fire rather than any one killing blow.",
|
||||
"path": "res://assets/images/ships/isn/barrage.png", "scale": 0.516, "speed_factor": SPEED_BY_ROLE.Gunner,
|
||||
},
|
||||
{
|
||||
"name": "RAVAGER", "role": "Bomber",
|
||||
"desc": "Drops heavy ordnance and mines from its cargo pods. Slow to turn but devastating to anything caught in the blast radius. Best used to seal off a chokepoint.",
|
||||
"path": "res://assets/images/ships/mech/ravager.png", "scale": 0.25, "speed_factor": SPEED_BY_ROLE.Bomber,
|
||||
},
|
||||
{
|
||||
"name": "AEGIS", "role": "Support",
|
||||
"desc": "Projects a short-range shield and deploys a repair drone for nearby allies. Doesn't hit hard on its own — its job is keeping everyone else alive long enough to win the fight.",
|
||||
"path": "res://assets/images/ships/mech/aegis.png", "scale": 0.248, "speed_factor": SPEED_BY_ROLE.Support,
|
||||
},
|
||||
{
|
||||
"name": "COLOSSUS", "role": "Heavy",
|
||||
"desc": "A walking fortress bristling with cannons. Slow and telegraphed, but absorbs enormous damage and returns it with interest. Usually the last thing standing in an engagement.",
|
||||
"path": "res://assets/images/ships/mech/colossus.png", "scale": 0.252, "speed_factor": SPEED_BY_ROLE.Heavy,
|
||||
"name": "BEHEMOTH", "role": "Tank",
|
||||
"desc": "Heavy tracking torpedoes plus massive physical deck-guns. The navy's flagship-killer — slow to turn, but nothing stands in its way for long.",
|
||||
"path": "res://assets/images/ships/isn/behemoth.png", "scale": 0.331, "speed_factor": SPEED_BY_ROLE.Tank,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 3, "name": "VORG SWARM", "sub": "Organic swarm built for chaotic damage-over-time",
|
||||
"color": Color(0.45, 0.80, 0.35),
|
||||
"id": 3, "name": "OUTER RIM COLLECTIVE", "sub": "Gritty, jury-rigged mining rebels — kinetic weapons scavenged and welded onto anything that flies",
|
||||
"color": Color(0.82, 0.52, 0.24),
|
||||
"ships": [
|
||||
{
|
||||
"name": "STINGER", "role": "Interceptor",
|
||||
"desc": "Fast, acid shots with light damage-over-time. Built to swarm a target from range, applying just enough acid to wear an enemy down before backing off.",
|
||||
"path": "res://assets/images/ships/vorg/stinger.png", "scale": 0.254, "speed_factor": SPEED_BY_ROLE.Interceptor,
|
||||
"name": "RAIL-JACK", "role": "Fighter",
|
||||
"desc": "A single, heavy magnetic railgun slug. Repurposed mining hardware — one slug, but it hits like a dropped asteroid.",
|
||||
"path": "res://assets/images/ships/orc/rail_jack.png", "scale": 0.492, "speed_factor": SPEED_BY_ROLE.Fighter,
|
||||
},
|
||||
{
|
||||
"name": "SPITTER", "role": "Gunship",
|
||||
"desc": "Faster-firing acid with a stronger damage-over-time effect. Trades some speed for sustained corrosive pressure — anything it tags keeps taking damage long after contact.",
|
||||
"path": "res://assets/images/ships/vorg/spitter.png", "scale": 0.247, "speed_factor": SPEED_BY_ROLE.Gunship,
|
||||
"name": "SCRAP-SPITTER", "role": "Gunner",
|
||||
"desc": "A rotary scrap-cannon spraying rapid, low-damage metal shrapnel. Loud, dirty, and built to keep the pressure on.",
|
||||
"path": "res://assets/images/ships/orc/scrap_spitter.png", "scale": 0.462, "speed_factor": SPEED_BY_ROLE.Gunner,
|
||||
},
|
||||
{
|
||||
"name": "BROODLING", "role": "Bomber",
|
||||
"desc": "Normal acid shots that also spawn a small slowing swarm cloud. The cloud lingers, fouling movement for anyone caught inside it — ideal for locking down a retreat or a chokepoint.",
|
||||
"path": "res://assets/images/ships/vorg/broodling.png", "scale": 0.404, "speed_factor": SPEED_BY_ROLE.Bomber,
|
||||
},
|
||||
{
|
||||
"name": "NURSE", "role": "Support",
|
||||
"desc": "Acid shots plus a small health/regeneration pack dropped for allies. Keeps the swarm alive in extended fights, at the cost of raw offensive power.",
|
||||
"path": "res://assets/images/ships/vorg/nurse.png", "scale": 0.271, "speed_factor": SPEED_BY_ROLE.Support,
|
||||
},
|
||||
{
|
||||
"name": "BEHEMOTH", "role": "Heavy",
|
||||
"desc": "Tankier and slower, with heavy acid shots that carry a large damage-over-time effect. The swarm's apex predator — absorbs punishment while its acid keeps eating away at anyone nearby.",
|
||||
"path": "res://assets/images/ships/vorg/behemoth.png", "scale": 0.171, "speed_factor": SPEED_BY_ROLE.Heavy,
|
||||
"name": "IRON-CLAD", "role": "Tank",
|
||||
"desc": "Unguided explosive rockets plus continuous vulcan machine-gun fire. Bolted-together and brutal — built to grind a fight down, not finesse it.",
|
||||
"path": "res://assets/images/ships/orc/iron_clad.png", "scale": 0.495, "speed_factor": SPEED_BY_ROLE.Tank,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -123,10 +91,29 @@ var _loading_lbl: Label
|
||||
var _offered: Array = []
|
||||
var _chosen_race: Dictionary = {}
|
||||
|
||||
# False until the player's first-ever pick (pre-spawn, mandatory — no cancel
|
||||
# possible). True afterward, once reopen() below can show this screen again
|
||||
# for a live swap, at which point ESC should be able to back out of it
|
||||
# without forcing a re-pick.
|
||||
var _reopenable: bool = false
|
||||
|
||||
# race_id -> {btn, count_lbl, hint_lbl, roster_lbl, color}, populated by
|
||||
# _show_race_selection() and cleared the moment that screen isn't showing —
|
||||
# _refresh_race_boxes() no-ops while empty, so a loadout_updated/player_removed
|
||||
# signal arriving on the ship-selection screen (or while closed) is harmless.
|
||||
var _race_boxes: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 20
|
||||
_build_root()
|
||||
GameConfig.team_select_focused = true
|
||||
|
||||
# Keeps the race-selection screen's live counts/rosters/TEAM FULL state
|
||||
# in sync as other players join, leave, or swap — both here (pre-spawn)
|
||||
# and when reopened mid-match from the pause menu.
|
||||
PlayerRegistry.loadout_updated.connect(_on_registry_changed)
|
||||
PlayerRegistry.player_removed.connect(_on_registry_player_removed)
|
||||
|
||||
# The 2 offered races must be the same for every player in the match, not
|
||||
# independently randomized per client — the server decides once (see
|
||||
@@ -140,6 +127,95 @@ func _ready() -> void:
|
||||
world.request_offered_races.rpc_id(1)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if _reopenable and _root.visible and event.is_action_pressed("toggle_pause"):
|
||||
_close()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
# Called by PauseMenu's SELECT TEAM button so an already-playing player can
|
||||
# swap race/ship mid-match. Reuses the match's already-decided _offered pair
|
||||
# instead of re-requesting it — the server decides that once for everyone
|
||||
# (World.decide_offered_races()) and it never changes mid-match.
|
||||
func reopen() -> void:
|
||||
_root.visible = true
|
||||
GameConfig.team_select_focused = true
|
||||
_show_race_selection()
|
||||
|
||||
|
||||
func _close() -> void:
|
||||
_root.visible = false
|
||||
GameConfig.team_select_focused = false
|
||||
_race_boxes.clear()
|
||||
|
||||
|
||||
func _on_registry_changed(_peer_id: int) -> void:
|
||||
_refresh_race_boxes()
|
||||
|
||||
|
||||
func _on_registry_player_removed(_peer_id: int, _info: Dictionary) -> void:
|
||||
_refresh_race_boxes()
|
||||
|
||||
|
||||
# Recomputes each offered race's human headcount/roster and whether it's
|
||||
# blocked as "TEAM FULL". Bots don't count — they're padding, not players
|
||||
# choosing a side, so they'd distort the balance check for no reason. Only
|
||||
# ever blocks the strictly larger side, and never a race the local player is
|
||||
# already on (re-picking your own current race shouldn't lock you out of it).
|
||||
func _refresh_race_boxes() -> void:
|
||||
if _race_boxes.is_empty():
|
||||
return
|
||||
|
||||
var counts: Dictionary = {}
|
||||
var rosters: Dictionary = {}
|
||||
for race in _offered:
|
||||
counts[race.id] = 0
|
||||
rosters[race.id] = []
|
||||
|
||||
for peer_id in PlayerRegistry.players:
|
||||
if peer_id < 0:
|
||||
continue
|
||||
var info: Dictionary = PlayerRegistry.players[peer_id]
|
||||
var race_id: int = info.get("race", 0)
|
||||
if not counts.has(race_id):
|
||||
continue
|
||||
counts[race_id] += 1
|
||||
rosters[race_id].append(String(info.get("name", "?")))
|
||||
|
||||
var my_race: int = PlayerRegistry.get_info(PlayerRegistry.get_local_id()).get("race", 0)
|
||||
var diff: int = counts[_offered[0].id] - counts[_offered[1].id]
|
||||
const MAX_ROSTER_SHOWN := 14
|
||||
|
||||
for race in _offered:
|
||||
var box: Dictionary = _race_boxes[race.id]
|
||||
var count: int = counts[race.id]
|
||||
box.count_lbl.text = "%d PLAYER%s" % [count, "" if count == 1 else "S"]
|
||||
|
||||
var names: Array = rosters[race.id]
|
||||
if names.is_empty():
|
||||
box.roster_lbl.text = "—"
|
||||
elif names.size() > MAX_ROSTER_SHOWN:
|
||||
box.roster_lbl.text = "\n".join(names.slice(0, MAX_ROSTER_SHOWN)) + \
|
||||
"\n+%d more" % (names.size() - MAX_ROSTER_SHOWN)
|
||||
else:
|
||||
box.roster_lbl.text = "\n".join(names)
|
||||
|
||||
# Only one side can ever be "larger" at a time with exactly 2 offered
|
||||
# races, so there's always at least one joinable side.
|
||||
# Explicit `bool` types below (not `:=`): `race` comes from the
|
||||
# untyped `_offered` Array, so `race.id == ...` resolves to Variant,
|
||||
# and this project treats inferring a var's type from Variant as an
|
||||
# error (same class of issue as the clamp() gotcha elsewhere).
|
||||
var is_larger: bool = (race.id == _offered[0].id and diff > 2) or \
|
||||
(race.id == _offered[1].id and diff < -2)
|
||||
var is_full: bool = is_larger and race.id != my_race
|
||||
box.btn.disabled = is_full
|
||||
box.hint_lbl.text = "TEAM FULL" if is_full else "CLICK TO SELECT"
|
||||
var col: Color = box.color
|
||||
box.hint_lbl.add_theme_color_override("font_color",
|
||||
Color(0.95, 0.35, 0.35) if is_full else Color(col.r * 0.5, col.g * 0.5, col.b * 0.5))
|
||||
|
||||
|
||||
# Called once this client knows which 2 races (by id) the match is offering.
|
||||
func offer_races(race_ids: Array) -> void:
|
||||
if _loading_lbl:
|
||||
@@ -198,19 +274,35 @@ func _show_race_selection() -> void:
|
||||
const BTN_W := 440.0
|
||||
const HALF_GAP := 50.0
|
||||
|
||||
var left_btn := _make_race_btn(_offered[0])
|
||||
_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)
|
||||
_race_boxes.clear()
|
||||
_build_race_box(c, _offered[0], -(BTN_W + HALF_GAP), -HALF_GAP)
|
||||
_build_race_box(c, _offered[1], HALF_GAP, BTN_W + HALF_GAP)
|
||||
_refresh_race_boxes()
|
||||
|
||||
var right_btn := _make_race_btn(_offered[1])
|
||||
_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)
|
||||
|
||||
# Builds one race's button + the player roster below it, and registers both
|
||||
# in _race_boxes under the race's id so _refresh_race_boxes() can update them
|
||||
# live as PlayerRegistry changes.
|
||||
func _build_race_box(c: Control, race: Dictionary, x1: float, x2: float) -> void:
|
||||
var built := _make_race_btn(race)
|
||||
_place(built.btn, x1, 225, x2, 540, 0.5, 0.5)
|
||||
built.btn.pressed.connect(_on_race_chosen.bind(race))
|
||||
c.add_child(built.btn)
|
||||
|
||||
var roster_lbl := _label(c, "", 12, Color(0.55, 0.65, 0.78),
|
||||
x1, 550, x2, 760, HORIZONTAL_ALIGNMENT_CENTER, 0.5, 0.5)
|
||||
roster_lbl.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
||||
roster_lbl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
|
||||
_race_boxes[race.id] = {
|
||||
"btn": built.btn, "count_lbl": built.count_lbl, "hint_lbl": built.hint_lbl,
|
||||
"roster_lbl": roster_lbl, "color": race.color,
|
||||
}
|
||||
|
||||
|
||||
func _on_race_chosen(race: Dictionary) -> void:
|
||||
_chosen_race = race
|
||||
_race_boxes.clear()
|
||||
_show_ship_selection()
|
||||
|
||||
|
||||
@@ -239,14 +331,15 @@ func _show_ship_selection() -> void:
|
||||
|
||||
func _on_ship_chosen(ship: Dictionary) -> void:
|
||||
PlayerRegistry.submit_local_loadout(
|
||||
GameConfig.player_name, _chosen_race.id, ship.path, ship.scale, ship.speed_factor
|
||||
GameConfig.player_name, _chosen_race.id, ship.path, ship.scale, ship.speed_factor, ship.role
|
||||
)
|
||||
queue_free()
|
||||
_reopenable = true
|
||||
_close()
|
||||
|
||||
|
||||
# ── Button builders ──────────────────────────────────────────────────────────
|
||||
|
||||
func _make_race_btn(race: Dictionary) -> Button:
|
||||
func _make_race_btn(race: Dictionary) -> Dictionary:
|
||||
var btn := Button.new()
|
||||
btn.text = ""
|
||||
var col: Color = race.color
|
||||
@@ -257,9 +350,13 @@ func _make_race_btn(race: Dictionary) -> Button:
|
||||
var sh := _flat(Color(col.r * 0.24, col.g * 0.24, col.b * 0.24, 0.96), 12)
|
||||
sh.set_border_width_all(2)
|
||||
sh.border_color = col
|
||||
var sd := _flat(Color(0.08, 0.08, 0.09, 0.9), 12)
|
||||
sd.set_border_width_all(2)
|
||||
sd.border_color = Color(0.4, 0.15, 0.15)
|
||||
btn.add_theme_stylebox_override("normal", sn)
|
||||
btn.add_theme_stylebox_override("hover", sh)
|
||||
btn.add_theme_stylebox_override("pressed", sh)
|
||||
btn.add_theme_stylebox_override("disabled", sd)
|
||||
|
||||
var vb := VBoxContainer.new()
|
||||
vb.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -290,6 +387,14 @@ func _make_race_btn(race: Dictionary) -> Button:
|
||||
sub_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(sub_lbl)
|
||||
|
||||
var count_lbl := Label.new()
|
||||
count_lbl.text = "0 PLAYERS"
|
||||
count_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
count_lbl.add_theme_font_size_override("font_size", 16)
|
||||
count_lbl.add_theme_color_override("font_color", col)
|
||||
count_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(count_lbl)
|
||||
|
||||
var hint := Label.new()
|
||||
hint.text = "CLICK TO SELECT"
|
||||
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
@@ -298,7 +403,7 @@ func _make_race_btn(race: Dictionary) -> Button:
|
||||
hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vb.add_child(hint)
|
||||
|
||||
return btn
|
||||
return {"btn": btn, "count_lbl": count_lbl, "hint_lbl": hint}
|
||||
|
||||
|
||||
func _make_ship_row(ship: Dictionary, accent: Color) -> Button:
|
||||
|
||||
Reference in New Issue
Block a user