Player/PartyVoice.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace Prototype;

// Use the engine's owner-authenticated voice transport; never send microphone
// audio to connections outside this player's party.
public sealed class PartyVoice : Voice
{
	public string VolumeKey => Connection.All.FirstOrDefault(c => c.Id == GameObject.Network.OwnerId)?.SteamId.ToString() ?? GameObject.Network.OwnerId.ToString();
	public float PersonalVolume => GameProfile.Settings.VoiceVolumes.TryGetValue(VolumeKey, out var value) ? Math.Clamp(value, 0f, 2f) : 1f;
	public void AdjustVolume(float delta)
	{
		GameProfile.Settings.VoiceVolumes[VolumeKey] = MathF.Round(Math.Clamp(PersonalVolume + delta, 0f, 2f) * 10f) / 10f;
		Volume = PersonalVolume * GameProfile.Settings.VoiceVolume;
		GameProfile.SaveSettings();
	}
	public bool ActiveSpeaker => IsProxy ? Amplitude > .001f && LastPlayed < .35f : IsListening && Input.Down( "Voice" );

	private bool SameParty( Connection connection )
	{
		var game = PrototypeGame.Instance;
		if ( game is null || connection is null ) return false;
		var owner = GameObject.Network.OwnerId;
		if ( game.Phase != RunPhase.Lobby )
			return game.IsRunMember( owner ) && game.IsRunMember( connection.Id );
		return game.PlayerParties.TryGetValue( owner.ToString(), out var party )
			&& !string.IsNullOrEmpty( party )
			&& game.PlayerParties.TryGetValue( connection.Id.ToString(), out var other ) && party == other;
	}

	protected override IEnumerable<Connection> ExcludeFilter() => Connection.All.Where( c => !SameParty( c ) );
	protected override bool ShouldHearVoice( Connection connection ) => connection is not null
		&& connection.Id == GameObject.Network.OwnerId && SameParty( Connection.Local );
}

public sealed class PartyVoiceInput : Component
{
	protected override void OnUpdate()
	{
		var voice = Components.Get<PartyVoice>();
		if ( !voice.IsValid() ) return;
		voice.Volume = voice.PersonalVolume * GameProfile.Settings.VoiceVolume;
		if ( IsProxy ) return;
		// Manual mode also gates users whose engine preference is open mic.
		voice.IsListening = GameProfile.Settings.MicrophoneEnabled && Input.Down( "Voice" );
	}
}