/* * Minecraft Forge, Patchwork Project * Copyright (c) 2016-2020, 2019-2020 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.fml.network; import java.util.UUID; import java.util.function.Supplier; import io.netty.buffer.Unpooled; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraft.client.MinecraftClient; import net.minecraft.client.world.ClientWorld; import net.minecraft.container.ContainerType; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.text.Text; import net.minecraft.util.PacketByteBuf; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; import net.fabricmc.fabric.api.network.PacketContext; import net.patchworkmc.impl.networking.ClientEntitySpawner; import net.patchworkmc.impl.networking.PatchworkNetworking; public class FMLPlayMessages { /** * Used to spawn a custom entity without the same restrictions as * {@link net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket} or {@link net.minecraft.network.packet.s2c.play.MobSpawnS2CPacket} * * <p>To customize how your entity is created clientside (instead of using the default factory provided to the {@link EntityType}) * see {@link net.patchworkmc.mixin.networking.MixinEntityTypeBuilder#setCustomClientFactory}. */ public static class SpawnEntity { private final Entity entity; private final int typeId; private final int entityId; private final UUID uuid; private final double posX, posY, posZ; private final byte pitch, yaw, headYaw; private final int velX, velY, velZ; private final PacketByteBuf buf; // Note: package-private on Forge public SpawnEntity(Entity entity) { this.entity = entity; this.typeId = Registry.ENTITY_TYPE.getRawId(entity.getType()); this.entityId = entity.getEntityId(); this.uuid = entity.getUuid(); this.posX = entity.x; this.posY = entity.y; this.posZ = entity.z; this.pitch = (byte) MathHelper.floor(entity.pitch * 256.0F / 360.0F); this.yaw = (byte) MathHelper.floor(entity.yaw * 256.0F / 360.0F); this.headYaw = (byte) (entity.getHeadYaw() * 256.0F / 360.0F); Vec3d velocity = entity.getVelocity(); double clampedVelX = MathHelper.clamp(velocity.x, -3.9D, 3.9D); double clampedVelY = MathHelper.clamp(velocity.y, -3.9D, 3.9D); double clampedVelZ = MathHelper.clamp(velocity.z, -3.9D, 3.9D); this.velX = (int) (clampedVelX * 8000.0D); this.velY = (int) (clampedVelY * 8000.0D); this.velZ = (int) (clampedVelZ * 8000.0D); this.buf = null; } private SpawnEntity(PacketByteBuf buf) { this.entity = null; this.typeId = buf.readVarInt(); this.entityId = buf.readInt(); this.uuid = buf.readUuid(); this.posX = buf.readDouble(); this.posY = buf.readDouble(); this.posZ = buf.readDouble(); this.pitch = buf.readByte(); this.yaw = buf.readByte(); this.headYaw = buf.readByte(); this.velX = buf.readShort(); this.velY = buf.readShort(); this.velZ = buf.readShort(); this.buf = buf; } public static void encode(SpawnEntity msg, PacketByteBuf buf) { buf.writeVarInt(msg.typeId); buf.writeInt(msg.entityId); buf.writeUuid(msg.uuid); buf.writeDouble(msg.posX); buf.writeDouble(msg.posY); buf.writeDouble(msg.posZ); buf.writeByte(msg.pitch); buf.writeByte(msg.yaw); buf.writeByte(msg.headYaw); buf.writeShort(msg.velX); buf.writeShort(msg.velY); buf.writeShort(msg.velZ); if (msg.entity instanceof IEntityAdditionalSpawnData) { ((IEntityAdditionalSpawnData) msg.entity).writeSpawnData(buf); } } public static SpawnEntity decode(PacketByteBuf buf) { return new SpawnEntity(buf); } public static void handle(SpawnEntity msg, PacketContext context) { PatchworkNetworking.enqueueWork(context.getTaskQueue(), () -> { EntityType<?> type = Registry.ENTITY_TYPE.get(msg.typeId); if (type.equals(Registry.ENTITY_TYPE.get(Registry.ENTITY_TYPE.getDefaultId()))) { throw new RuntimeException(String.format("Could not spawn entity (id %d) with unknown type at (%f, %f, %f)", msg.entityId, msg.posX, msg.posY, msg.posZ)); } ClientWorld world = MinecraftClient.getInstance().world; Entity entity = ((ClientEntitySpawner<?>) type).customClientSpawn(msg, world); if (entity == null) { return; } entity.updateTrackedPosition(msg.posX, msg.posY, msg.posZ); entity.updatePositionAndAngles(msg.posX, msg.posY, msg.posZ, (msg.yaw * 360) / 256.0F, (msg.pitch * 360) / 256.0F); entity.setHeadYaw((msg.headYaw * 360) / 256.0F); entity.setYaw((msg.headYaw * 360) / 256.0F); entity.setEntityId(msg.entityId); entity.setUuid(msg.uuid); world.addEntity(msg.entityId, entity); entity.setVelocity(msg.velX / 8000.0, msg.velY / 8000.0, msg.velZ / 8000.0); if (entity instanceof IEntityAdditionalSpawnData) { ((IEntityAdditionalSpawnData) entity).readSpawnData(msg.buf); } }); } public static void handle(SpawnEntity msg, Supplier<NetworkEvent.Context> contextSupplier) { NetworkEvent.Context context = contextSupplier.get(); if (context.getDirection().getReceptionSide() != LogicalSide.CLIENT) { return; } handle(msg, context); context.setPacketHandled(true); } public Entity getEntity() { return entity; } public int getTypeId() { return typeId; } public int getEntityId() { return entityId; } public UUID getUuid() { return uuid; } public double getPosX() { return posX; } public double getPosY() { return posY; } public double getPosZ() { return posZ; } public byte getPitch() { return pitch; } public byte getYaw() { return yaw; } public byte getHeadYaw() { return headYaw; } public int getVelX() { return velX; } public int getVelY() { return velY; } public int getVelZ() { return velZ; } public PacketByteBuf getAdditionalData() { return buf; } } public static class OpenContainer { private final int id; private final int windowId; private final Text name; private final PacketByteBuf additionalData; // Note: package-private on Forge public OpenContainer(ContainerType<?> id, int windowId, Text name, PacketByteBuf additionalData) { this(Registry.CONTAINER.getRawId(id), windowId, name, additionalData); } private OpenContainer(int id, int windowId, Text name, PacketByteBuf additionalData) { this.id = id; this.windowId = windowId; this.name = name; this.additionalData = additionalData; } public static void encode(OpenContainer msg, PacketByteBuf buf) { buf.writeVarInt(msg.id); buf.writeVarInt(msg.windowId); buf.writeText(msg.name); buf.writeByteArray(msg.additionalData.readByteArray()); } public static OpenContainer decode(PacketByteBuf buf) { return new OpenContainer(buf.readVarInt(), buf.readVarInt(), buf.readText(), new PacketByteBuf(Unpooled.wrappedBuffer(buf.readByteArray(32600)))); } public static void handle(OpenContainer msg, PacketContext context) { // TODO: IForgeContainerType throw new UnsupportedOperationException("Cannot yet handle custom OpenContainer packets"); /*PatchworkNetworking.enqueueWork(context.getTaskQueue(), () -> { Screens.getScreenFactory(msg.getType(), MinecraftClient.getInstance(), msg.getWindowId(), msg.getName()) .ifPresent(f -> { Container c = msg.getType().create(msg.getWindowId(), MinecraftClient.getInstance().player.inventory, msg.getAdditionalData()); @SuppressWarnings("unchecked") Screen s = ((Screens.Provider<Container, ?>) f).create(c, MinecraftClient.getInstance().player.inventory, msg.getName()); MinecraftClient.getInstance().player.container = ((ContainerProvider<?>) s).getContainer(); MinecraftClient.getInstance().openScreen(s); }); });*/ } public static void handle(OpenContainer msg, Supplier<NetworkEvent.Context> contextSupplier) { NetworkEvent.Context context = contextSupplier.get(); if (context.getDirection().getReceptionSide() != LogicalSide.CLIENT) { return; } handle(msg, context); context.setPacketHandled(true); } public final ContainerType<?> getType() { return Registry.CONTAINER.get(this.id); } public int getWindowId() { return windowId; } public Text getName() { return name; } public PacketByteBuf getAdditionalData() { return additionalData; } } // TODO: DimensionInfoMessage }