fix(multiplayer): lock signed team assignments

This commit is contained in:
Josh Creek
2026-09-02 19:05:45 +01:00
parent 0bca441ca1
commit d59e0017f7
3 changed files with 36 additions and 5 deletions
+17 -5
View File
@@ -492,17 +492,29 @@ func _set_team(team: int) -> void:
if not multiplayer.is_server():
return
var peer_id := multiplayer.get_remote_sender_id()
if not roster.has(peer_id) or team < 0 or team >= TEAM_COUNT:
if not _apply_team_change(peer_id, team):
return
var info: PlayerInfo = roster[peer_id]
if info.team == team:
return
info.team = team
info.ready = false # switching teams un-readies — the roster you were ready against just changed
player_state_changed.emit(peer_id, info.team, info.ready)
_state_changed.rpc(peer_id, info.team, info.ready)
func _apply_team_change(peer_id: int, team: int) -> bool:
# In allocated matches team and global slot are signed together. Changing
# only team would produce a roster that disagrees with the assignment and
# leave spawn_index anchored to the old team.
if require_join_authorisation:
return false
if not roster.has(peer_id) or team < 0 or team >= TEAM_COUNT:
return false
var info: PlayerInfo = roster[peer_id]
if info.team == team:
return false
info.team = team
info.ready = false # switching teams un-readies — the roster you were ready against just changed
return true
@rpc("any_peer", "call_remote", "reliable")
func _set_ready(ready: bool) -> void:
if not multiplayer.is_server():
+17
View File
@@ -70,6 +70,23 @@ func test_draining_disconnect_still_releases_roster_and_join_token() -> void:
assert_true(float(match_net._join_history[token].get("lost_at", 0.0)) > 0.0, "disconnect records the reclaim boundary during drain")
func test_signed_assignment_locks_team_and_spawn_slot_together() -> void:
var match_net := MatchNet.new()
var info := MatchNet.PlayerInfo.new(42, "Alice", 0, true, "player-1")
info.spawn_index = 2
match_net.roster[42] = info
match_net.require_join_authorisation = true
assert_true(not match_net._apply_team_change(42, 1), "allocated clients cannot override their signed team")
assert_eq(info.team, 0, "signed team is unchanged")
assert_eq(info.spawn_index, 2, "signed spawn index remains paired with its team")
assert_true(info.ready, "rejected mutation does not alter readiness")
match_net.require_join_authorisation = false
assert_true(match_net._apply_team_change(42, 1), "direct lobbies retain team switching")
assert_eq(info.team, 1, "direct team switch applies")
assert_true(not info.ready, "direct team switch still clears readiness")
func test_reservation_reclaim_requires_stable_identity() -> void:
assert_true(MatchNet.reservation_identity_matches("player-a", "player-a", "Alice", "Impostor"), "the verified identity can reclaim despite a changed display name")
assert_true(not MatchNet.reservation_identity_matches("player-a", "player-b", "Alice", "Alice"), "a same-name peer cannot reclaim another identity's slot")