Volver al índice

src/main/java/ar/com/companeros/horror/HorrorNetwork.java

package ar.com.companeros.horror;

import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.*;
import net.minecraftforge.network.simple.SimpleChannel;

/** Preferencias y voz enviada sólo a los observadores autorizados. */
public final class HorrorNetwork {
    public static Consumer<Boolean> CLIENT_RECEIVER = enabled -> { };
    public static Consumer<Speech> CLIENT_SPEECH = speech -> { };
    public static final int MAX_SPEECH_BYTES = 192 * 1024;
    private static final String VERSION = "3";
    private static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
        new ResourceLocation("companeros", "horror"), () -> VERSION, VERSION::equals, VERSION::equals);
    private static boolean registered;
    private HorrorNetwork() { }
    public static synchronized void register() {
        if (registered) return; registered = true;
        CHANNEL.messageBuilder(Sensitivity.class, 0, NetworkDirection.PLAY_TO_CLIENT)
            .encoder(Sensitivity::encode).decoder(Sensitivity::decode).consumerMainThread(Sensitivity::handle).add();
        CHANNEL.messageBuilder(Speech.class, 1, NetworkDirection.PLAY_TO_CLIENT)
            .encoder(Speech::encode).decoder(Speech::decode).consumerMainThread(Speech::handle).add();
    }
    public static void send(ServerPlayer player, boolean enabled) {
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new Sensitivity(enabled));
    }

    // VOZ: no transmite rutas, servicios ni URLs; únicamente un OGG pequeño en memoria.
    public static void sendSpeech(ServerPlayer player, ParasiteEntity body, byte[] audio) {
        var server = player.getServer();
        if (server == null || !HorrorModule.mayTarget(server, player)
            || player.level() != body.level() || !body.isAlive() || player.distanceToSqr(body) > 48 * 48) return;
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player),
            new Speech(body.getUUID(), body.getX(), body.getEyeY(), body.getZ(), audio));
    }

    public static boolean validSpeechBytes(byte[] audio) {
        if (audio == null || audio.length < 36 || audio.length > MAX_SPEECH_BYTES
            || audio[0] != 'O' || audio[1] != 'g' || audio[2] != 'g' || audio[3] != 'S' || audio[4] != 0) return false;
        int firstPacket = 27 + Byte.toUnsignedInt(audio[26]);
        if (firstPacket + 30 > audio.length || audio[firstPacket] != 1) return false;
        byte[] vorbis = { 'v', 'o', 'r', 'b', 'i', 's' };
        for (int index = 0; index < vorbis.length; index++) if (audio[firstPacket + 1 + index] != vorbis[index]) return false;
        int channels = Byte.toUnsignedInt(audio[firstPacket + 11]);
        int rate = Byte.toUnsignedInt(audio[firstPacket + 12]) | Byte.toUnsignedInt(audio[firstPacket + 13]) << 8
            | Byte.toUnsignedInt(audio[firstPacket + 14]) << 16 | Byte.toUnsignedInt(audio[firstPacket + 15]) << 24;
        return (channels == 1 || channels == 2) && rate >= 8000 && rate <= 48000;
    }

    public record Speech(UUID body, double x, double y, double z, byte[] audio) {
        public Speech {
            Objects.requireNonNull(body);
            if (!Double.isFinite(x) || !Double.isFinite(y) || !Double.isFinite(z)
                || Math.abs(x) > 30_000_000 || Math.abs(z) > 30_000_000 || Math.abs(y) > 4096
                || !validSpeechBytes(audio)) throw new IllegalArgumentException("Invalid parasite speech packet");
            audio = Arrays.copyOf(audio, audio.length);
        }
        @Override public byte[] audio() { return Arrays.copyOf(audio, audio.length); }
        private void encode(FriendlyByteBuf buffer) {
            buffer.writeUUID(body);
            buffer.writeDouble(x); buffer.writeDouble(y); buffer.writeDouble(z);
            buffer.writeByteArray(audio);
        }
        private static Speech decode(FriendlyByteBuf buffer) {
            return new Speech(buffer.readUUID(), buffer.readDouble(), buffer.readDouble(), buffer.readDouble(),
                buffer.readByteArray(MAX_SPEECH_BYTES));
        }
        private void handle(Supplier<NetworkEvent.Context> context) {
            CLIENT_SPEECH.accept(this);
            context.get().setPacketHandled(true);
        }
    }
    public record Sensitivity(boolean enabled) {
        private void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(enabled); }
        private static Sensitivity decode(FriendlyByteBuf buffer) { return new Sensitivity(buffer.readBoolean()); }
        private void handle(Supplier<NetworkEvent.Context> context) {
            CLIENT_RECEIVER.accept(enabled); context.get().setPacketHandled(true);
        }
    }
}