46ff83325f
- team_data.gd: Team enum (SPECTATOR/CT/Terrorist), constants for names, colors, starting money, and spawn groups; TeamData resource class - team_manager.gd: Player-to-team assignment, auto-balancing, score tracking; round-independent (persists across rounds) - spawn_manager.gd: Scans scene for Marker3D nodes in spawn_points_ct and spawn_points_t groups; selects valid spawns (farthest from enemies with proximity check, fallback to first available) - buy_zone.gd: Area3D-based trigger zone with team filtering, player enter/exit tracking and signals - test_range.tscn: Added 2 CT spawn markers and 2 T spawn markers with appropriate groups
182 lines
6.0 KiB
GDScript
182 lines
6.0 KiB
GDScript
## BuyZone — Area3D that defines where players can buy weapons/equipment.
|
|
##
|
|
## A trigger zone that tracks which players are inside it and emits
|
|
## enter/exit signals. Buy zones can be filtered by team, so only
|
|
## the appropriate team can purchase within a given zone.
|
|
##
|
|
## In the map scene, place BuyZone nodes (Area3D) near each team's
|
|
## spawn area. Set zone_team to restrict which team can use it.
|
|
##
|
|
## Usage (map tscn):
|
|
## [node name="BuyZone_CT" type="Area3D" parent="."]
|
|
## script = ExtResource("...buy_zone.gd")
|
|
## zone_team = 1 # Team.COUNTER_TERRORIST
|
|
## zone_radius = 10.0
|
|
##
|
|
## (Add a CollisionShape3D child with a SphereShape3D for the trigger volume.)
|
|
##
|
|
## Signals:
|
|
## player_entered_buyzone(player_id) — emitted when a player enters the zone
|
|
## player_exited_buyzone(player_id) — emitted when a player leaves the zone
|
|
##
|
|
## Usage (server runtime):
|
|
## var bz: BuyZone = $BuyZone_CT
|
|
## if bz.is_in_buyzone(player_id):
|
|
## show_buy_menu(player_id)
|
|
|
|
extends Area3D
|
|
class_name BuyZone
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Signals
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Emitted when a player enters the buy zone.
|
|
signal player_entered_buyzone(player_id: int)
|
|
|
|
## Emitted when a player exits the buy zone.
|
|
signal player_exited_buyzone(player_id: int)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Exports
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Which team(s) can use this buy zone.
|
|
## Team.COUNTER_TERRORIST = 1, Team.TERRORIST = 2, Team.SPECTATOR = 0.
|
|
## Use the Team enum: TeamData.Team.COUNTER_TERRORIST, etc.
|
|
@export var zone_team: TeamData.Team = TeamData.Team.COUNTER_TERRORIST
|
|
|
|
## Radius of the buy zone in Godot units (visual hint only; actual collision
|
|
## shape determines the trigger volume).
|
|
@export var zone_radius: float = 10.0
|
|
|
|
## If true, any team can use this zone (overrides zone_team).
|
|
@export var allow_all_teams: bool = false
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# State
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Set of player IDs currently inside this buy zone.
|
|
var _players_in_zone: Dictionary = {} # player_id (int) → true
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
func _ready() -> void:
|
|
# Connect area signals
|
|
body_entered.connect(_on_body_entered)
|
|
body_exited.connect(_on_body_exited)
|
|
area_entered.connect(_on_area_entered)
|
|
area_exited.connect(_on_area_exited)
|
|
|
|
# Set collision layer/mask for buy zones (layer 4 by convention)
|
|
collision_layer = 0 # Buy zones don't collide with physics
|
|
collision_mask = 0 # They detect bodies via signals only
|
|
|
|
# Ensure we have at least a basic collision shape hint
|
|
_setup_collision_shape()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Public API
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Check if a specific player is inside this buy zone.
|
|
func is_in_buyzone(player_id: int) -> bool:
|
|
return _players_in_zone.has(player_id)
|
|
|
|
|
|
## Get an array of player IDs currently inside this buy zone.
|
|
func get_players_in_buyzone() -> Array[int]:
|
|
return _players_in_zone.keys()
|
|
|
|
|
|
## Check if the given team is allowed to use this buy zone.
|
|
func is_team_allowed(team: TeamData.Team) -> bool:
|
|
if allow_all_teams:
|
|
return true
|
|
return team == zone_team
|
|
|
|
|
|
## Get the number of players currently in the zone.
|
|
func get_player_count() -> int:
|
|
return _players_in_zone.size()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Internal: Signal handlers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
func _on_body_entered(body: Node) -> void:
|
|
var player_id: int = _extract_player_id(body)
|
|
if player_id >= 0:
|
|
_players_in_zone[player_id] = true
|
|
player_entered_buyzone.emit(player_id)
|
|
|
|
|
|
func _on_body_exited(body: Node) -> void:
|
|
var player_id: int = _extract_player_id(body)
|
|
if player_id >= 0 and _players_in_zone.erase(player_id):
|
|
player_exited_buyzone.emit(player_id)
|
|
|
|
|
|
func _on_area_entered(_area: Area3D) -> void:
|
|
# Areas can also trigger if the player is an Area3D (e.g. a trigger zone
|
|
# on the player). For now, body detection is the primary path.
|
|
pass
|
|
|
|
|
|
func _on_area_exited(_area: Area3D) -> void:
|
|
pass
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Internal: helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Try to extract a multiplayer peer ID from a body node.
|
|
## The body is expected to be a Player node or have a "get_peer_id" method,
|
|
## or the body's owner is a Player node with multiplayer authority info.
|
|
func _extract_player_id(body: Node) -> int:
|
|
# If the body has a peer_id property or method
|
|
if body.has_method(&"get_peer_id"):
|
|
return body.get_peer_id()
|
|
|
|
# If the body is a Player node with multiplayer authority
|
|
if body.has_method(&"get_multiplayer_authority"):
|
|
return body.get_multiplayer_authority()
|
|
|
|
# Check for a direct peer_id variable
|
|
if "peer_id" in body and body.get("peer_id") is int:
|
|
return body.get("peer_id")
|
|
|
|
# Try the body's owner
|
|
var owner_node: Node = body.owner if body.owner else body.get_parent()
|
|
if owner_node and owner_node != body:
|
|
return _extract_player_id(owner_node)
|
|
|
|
return -1
|
|
|
|
|
|
## Ensure there's a collision shape for the trigger volume.
|
|
## If no CollisionShape3D child exists, create a SphereShape3D one.
|
|
func _setup_collision_shape() -> void:
|
|
# Check if we already have a CollisionShape3D
|
|
for child in get_children():
|
|
if child is CollisionShape3D:
|
|
return # Already have one
|
|
|
|
# Create a default sphere shape
|
|
var shape := SphereShape3D.new()
|
|
shape.radius = zone_radius
|
|
|
|
var col_shape := CollisionShape3D.new()
|
|
col_shape.shape = shape
|
|
add_child(col_shape)
|
|
col_shape.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner
|