## 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 const _td_bz = preload("res://scripts/teams/team_data.gd") # --------------------------------------------------------------------------- # 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: int.COUNTER_TERRORIST, etc. @export var zone_team: int = _td_bz.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: int) -> 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