t_p1_round: Add round/match lifecycle system (RoundManager)

- New server/scripts/round/round_manager.gd with:
  - RoundPhase enum (WARMUP, PREP, LIVE, POST)
  - Round life cycle: warmup -> prep (15s) -> live (105s) -> post (8s)
  - Team elimination detection using entity->peer->team mappings
  - Time-expired round ending (default: CT wins)
  - Economy hooks: win money (250), loss money (900 escalating to 900), kill bonus (00)
  - All requested signals: round_phase_changed, round_started, round_ended, match_point, warmup_ended, round_economy_data

- Updated GameServer:
  - Creates and wires RoundManager in _ready()
  - Connects DamageProcessor.player_killed -> round tracking + elimination detection
  - Added peer_to_entity mapping for backwards entity lookup
  - Added RCON command handler (_on_rcon_command) for start_match/end_round

- Updated RCON command handler:
  - Added start_match and end_round to dispatch list and help text
This commit is contained in:
2026-07-01 20:30:01 -04:00
parent 705b068ed2
commit f20d532add
14 changed files with 908 additions and 2 deletions
+160
View File
@@ -0,0 +1,160 @@
@tool
extends Node
# =============================================================================
# art_kit.gd — Modular Art Kit Factory
# =============================================================================
# Provides factory methods for spawning modular environment pieces and a
# preview catalogue for map builders.
#
# Each piece is a standalone .tscn under pieces/ with:
# - CSG shapes (Box3D, Cylinder3D, Polygon3D, Combiner3D)
# - Solid-color StandardMaterial3D placeholders (roughness 0.8, metallic 0.0)
# - Collision enabled (use_collision = true or StaticBody3D + CollisionShape3D)
# - < 200 tris each
#
# Usage:
# var piece = ArtKit.spawn_piece("crate", parent_node)
# var cat = ArtKit.preview_catalogue()
# var stack = ArtKit.random_crate_layout(3, 2) # returns parent Node3D
# =============================================================================
const PIECES_DIR := "res://client/assets/scenes/modular/pieces/"
# Catalogue definition: name, description, filename (without .tscn)
const CATALOGUE: Array[Dictionary] = [
{
"name": "floor_tile",
"description": "2×2×0.2m concrete slab — grey",
"file": "floor_tile"
},
{
"name": "wall_segment",
"description": "4×0.2×3m wall panel — blue-grey",
"file": "wall_segment"
},
{
"name": "corner_wall",
"description": "L-shaped corner piece (2m each leg) — blue-grey",
"file": "corner_wall"
},
{
"name": "pillar",
"description": "0.3×3×0.3m column — dark grey",
"file": "pillar"
},
{
"name": "crate",
"description": "1×1×1m storage box — brown/tan",
"file": "crate"
},
{
"name": "barrel",
"description": "0.6m radius × 1m tall cylinder — red",
"file": "barrel"
},
{
"name": "cover_wall",
"description": "2×0.2×1.3m low wall (crouch-height cover) — grey",
"file": "cover_wall"
},
{
"name": "ramp",
"description": "2×2m, 30° incline ramp — yellow",
"file": "ramp"
},
{
"name": "stairs",
"description": "3-step staircase (2m wide, 0.25m rise per step) — grey",
"file": "stairs"
},
{
"name": "window_frame",
"description": "4×3m wall with 2×2m opening — blue-grey",
"file": "window_frame"
}
]
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
## Spawns a piece by name, adds it as a child of `parent`, and returns it.
## Returns null if the piece name is unknown or the scene fails to load.
static func spawn_piece(piece_name: String, parent: Node) -> Node3D:
var entry := _find_entry(piece_name)
if entry.is_empty():
push_error("ArtKit: Unknown piece '%s'. Use preview_catalogue() to see available pieces." % piece_name)
return null
var path := PIECES_DIR + entry.file + ".tscn"
var scene := load(path)
if scene == null:
push_error("ArtKit: Failed to load scene at '%s'." % path)
return null
var instance: Node3D = scene.instantiate()
parent.add_child(instance)
instance.owner = parent if not Engine.is_editor_hint() else _get_editor_owner(parent)
return instance
## Returns an array of { name, description, path } for all available pieces.
static func preview_catalogue() -> Array[Dictionary]:
var result: Array[Dictionary] = []
for entry in CATALOGUE:
result.append({
"name": entry.name,
"description": entry.description,
"path": PIECES_DIR + entry.file + ".tscn"
})
return result
## Generates a random stack of crates for quick testing layouts.
## `width` — number of crates along X (default 3)
## `height` — number of crates stacked vertically (default 2)
## Returns a Node3D containing the crate instances.
static func random_crate_layout(width: int = 3, height: int = 2) -> Node3D:
var root := Node3D.new()
root.name = "CrateLayout_%dx%d" % [width, height]
var crate_scene := load(PIECES_DIR + "crate.tscn")
if crate_scene == null:
push_error("ArtKit: Could not load crate scene.")
return root
for y in range(height):
for x in range(width):
# Add slight random offset for natural look (±0.05m)
var offset_x := randf_range(-0.05, 0.05)
var offset_z := randf_range(-0.05, 0.05)
# Full rotation in 90° increments
var rot_y := randi() % 4 * 90.0
var crate: Node3D = crate_scene.instantiate()
crate.position = Vector3(x * 1.0 + offset_x, y * 1.0 + 0.5, offset_z)
crate.rotation_degrees = Vector3(0, rot_y, 0)
root.add_child(crate)
return root
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
static func _find_entry(piece_name: String) -> Dictionary:
for entry in CATALOGUE:
if entry.name == piece_name:
return entry
return {}
## Returns the editor's top-level owner so new nodes are treated as part of the scene.
static func _get_editor_owner(node: Node) -> Node:
var owner = node
while owner and owner.owner:
owner = owner.owner
return owner
@@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.7, 0.15, 0.1, 1.0)
roughness = 0.6
metallic = 0.2
resource_name = "red_barrel"
[node name="Barrel" type="CSGCylinder3D"]
radius = 0.6
height = 1.0
material = SubResource("1")
use_collision = true
slices = 16
visibility_range_end = 30.0
visibility_range_end_margin = 3.0
@@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.35, 0.4, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "bluegrey_corner"
[node name="CornerWall" type="CSGCombiner3D"]
use_collision = false
visibility_range_end = 50.0
visibility_range_end_margin = 5.0
[node name="WallA" type="CSGBox3D" parent="CornerWall"]
operation = 0
size = Vector3(2, 3, 0.2)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.1)
[node name="WallB" type="CSGBox3D" parent="CornerWall"]
operation = 0
size = Vector3(0.2, 3, 2)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.1, 0, 0)
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.4, 0.4, 0.4, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "grey_cover"
[node name="CoverWall" type="CSGBox3D"]
size = Vector3(2, 1.3, 0.2)
material = SubResource("1")
use_collision = true
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.55, 0.35, 0.15, 1.0)
roughness = 0.85
metallic = 0.0
resource_name = "brown_crate"
[node name="Crate" type="CSGBox3D"]
size = Vector3(1, 1, 1)
material = SubResource("1")
use_collision = true
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.5, 0.5, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "concrete_grey"
[node name="FloorTile" type="CSGBox3D"]
size = Vector3(2, 0.2, 2)
material = SubResource("1")
use_collision = true
visibility_range_end = 60.0
visibility_range_end_margin = 10.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.2, 0.2, 0.22, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "darkgrey_pillar"
[node name="Pillar" type="CSGBox3D"]
size = Vector3(0.3, 3, 0.3)
material = SubResource("1")
use_collision = true
visibility_range_end = 30.0
visibility_range_end_margin = 3.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.7, 0.7, 0.3, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "yellow_ramp"
[node name="Ramp" type="CSGPolygon3D"]
polygon = PackedVector2Array(0, 0, 2, 0, 2, 1.155)
depth = 2.0
material = SubResource("1")
use_collision = true
smooth_faces = true
@@ -0,0 +1,33 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.45, 0.45, 0.45, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "grey_stairs"
[node name="Stairs" type="CSGCombiner3D"]
use_collision = false
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
[node name="Step1" type="CSGBox3D" parent="Stairs"]
operation = 0
size = Vector3(2, 0.25, 0.5)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.125, 0.75)
[node name="Step2" type="CSGBox3D" parent="Stairs"]
operation = 0
size = Vector3(2, 0.25, 0.5)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.375, 0.25)
[node name="Step3" type="CSGBox3D" parent="Stairs"]
operation = 0
size = Vector3(2, 0.25, 0.5)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.625, -0.25)
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.35, 0.4, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "bluegrey_wall"
[node name="WallSegment" type="CSGBox3D"]
size = Vector3(4, 3, 0.2)
material = SubResource("1")
use_collision = true
visibility_range_end = 50.0
visibility_range_end_margin = 5.0
@@ -0,0 +1,24 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.35, 0.4, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "bluegrey_window"
[node name="WindowFrame" type="CSGCombiner3D"]
use_collision = false
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
[node name="MainWall" type="CSGBox3D" parent="WindowFrame"]
operation = 0
size = Vector3(4, 3, 0.2)
material = SubResource("1")
use_collision = true
[node name="WindowCutout" type="CSGBox3D" parent="WindowFrame"]
operation = 1
size = Vector3(2, 2, 0.25)
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)