package net.minecraft.client.multiplayer.p2p; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.onvoid.webrtc.RTCIceCandidate; import java.util.Optional; import java.util.UUID; import net.minecraft.client.multiplayer.p2p.SignalingMessage.FriendJoin.Accepted; import net.minecraft.client.multiplayer.p2p.SignalingMessage.FriendJoin.InviteDeclined; import net.minecraft.client.multiplayer.p2p.SignalingMessage.FriendJoin.Rejected; import net.minecraft.client.multiplayer.p2p.SignalingMessage.FriendJoin.Request; import net.minecraft.client.multiplayer.p2p.SignalingMessage.WebRtc.Answer; import net.minecraft.client.multiplayer.p2p.SignalingMessage.WebRtc.Candidate; import net.minecraft.client.multiplayer.p2p.SignalingMessage.WebRtc.IceCandidate; import net.minecraft.client.multiplayer.p2p.SignalingMessage.WebRtc.Offer; import net.minecraft.util.StringRepresentable; import org.jspecify.annotations.Nullable; public record SignalingMessage(SignalingMessage.Type type, String sessionId, @Nullable String sdp, @Nullable Candidate iceCandidate) { public static final Codec CODEC = RecordCodecBuilder.create( i -> i.group( SignalingMessage.Type.CODEC.fieldOf("type").forGetter(SignalingMessage::type), Codec.STRING.fieldOf("sessionId").forGetter(SignalingMessage::sessionId), Codec.STRING.optionalFieldOf("sdp").forGetter(m -> Optional.ofNullable(m.sdp())), Candidate.CODEC.optionalFieldOf("iceCandidate").forGetter(m -> Optional.ofNullable(m.iceCandidate())) ) .apply(i, (type, sessionId, sdp, iceCandidate) -> new SignalingMessage(type, sessionId, (String)sdp.orElse(null), (Candidate)iceCandidate.orElse(null))) ); public static SignalingMessage joinRequest(final String sessionId) { return from(new Request(sessionId)); } public static SignalingMessage joinAccepted(final String sessionId) { return from(new Accepted(sessionId)); } public static SignalingMessage joinRejected(final String sessionId) { return from(new Rejected(sessionId)); } public static SignalingMessage inviteDeclined() { return from(InviteDeclined.INSTANCE); } public static SignalingMessage offer(final String sessionId, final String sdp) { return from(new Offer(sessionId, sdp)); } public static SignalingMessage answer(final String sessionId, final String sdp) { return from(new Answer(sessionId, sdp)); } public static SignalingMessage iceCandidate(final String sessionId, final RTCIceCandidate candidate) { return from(new IceCandidate(sessionId, Candidate.from(candidate))); } private static SignalingMessage from(final SignalingMessage.Payload payload) { return switch (payload) { case Request m -> new SignalingMessage(SignalingMessage.Type.JOIN_REQUEST, m.sessionId(), null, null); case Accepted mx -> new SignalingMessage(SignalingMessage.Type.JOIN_ACCEPTED, mx.sessionId(), null, null); case Rejected mxx -> new SignalingMessage(SignalingMessage.Type.JOIN_REJECTED, mxx.sessionId(), null, null); case InviteDeclined ignored -> new SignalingMessage(SignalingMessage.Type.INVITE_DECLINED, UUID.randomUUID().toString(), null, null); case Offer mxxx -> new SignalingMessage(SignalingMessage.Type.OFFER, mxxx.sessionId(), mxxx.sdp(), null); case Answer mxxxx -> new SignalingMessage(SignalingMessage.Type.ANSWER, mxxxx.sessionId(), mxxxx.sdp(), null); case IceCandidate mxxxxx -> new SignalingMessage(SignalingMessage.Type.ICE_CANDIDATE, mxxxxx.sessionId(), null, mxxxxx.candidate()); default -> throw new MatchException(null, null); }; } @Nullable public SignalingMessage.Payload decode() { return (SignalingMessage.Payload)(switch (this.type) { case JOIN_REQUEST -> new Request(this.sessionId); case JOIN_ACCEPTED -> new Accepted(this.sessionId); case JOIN_REJECTED -> new Rejected(this.sessionId); case INVITE_DECLINED -> InviteDeclined.INSTANCE; case OFFER -> this.sdp != null ? new Offer(this.sessionId, this.sdp) : null; case ANSWER -> this.sdp != null ? new Answer(this.sessionId, this.sdp) : null; case ICE_CANDIDATE -> this.iceCandidate != null ? new IceCandidate(this.sessionId, this.iceCandidate) : (this.sdp != null ? new IceCandidate(this.sessionId, new Candidate(this.sdp, "0", 0)) : null); }); } public sealed interface FriendJoin extends SignalingMessage.Payload permits Request, Accepted, Rejected, InviteDeclined { } public sealed interface Payload permits SignalingMessage.FriendJoin, SignalingMessage.WebRtc { } private static enum Type implements StringRepresentable { JOIN_REQUEST, JOIN_ACCEPTED, JOIN_REJECTED, INVITE_DECLINED, OFFER, ANSWER, ICE_CANDIDATE; private static final Codec CODEC = StringRepresentable.fromEnum(SignalingMessage.Type::values); public String getSerializedName() { return this.name(); } } public sealed interface WebRtc extends SignalingMessage.Payload permits Offer, Answer, IceCandidate { String sessionId(); } }