/* * Minecraft Forge * Copyright (c) 2016-2019. * * 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 io.netty.buffer.Unpooled; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.IHasContainer; import net.minecraft.client.gui.ScreenManager; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.inventory.container.Container; import net.minecraft.inventory.container.ContainerType; import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraft.world.biome.FuzzedBiomeMagnifier; import net.minecraft.world.dimension.DimensionType; import net.minecraftforge.common.ModDimension; import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraft.util.registry.Registry; import net.minecraft.util.text.ITextComponent; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.registries.ForgeRegistries; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; public class FMLPlayMessages { /** * Used to spawn a custom entity without the same restrictions as * {@link net.minecraft.network.play.server.SSpawnObjectPacket} or {@link net.minecraft.network.play.server.SSpawnMobPacket} * * To customize how your entity is created clientside (instead of using the default factory provided to the {@link EntityType}) * see {@link EntityType.Builder#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 PacketBuffer buf; SpawnEntity(Entity e) { this.entity = e; this.typeId = Registry.ENTITY_TYPE.getId(e.getType()); this.entityId = e.getEntityId(); this.uuid = e.getUniqueID(); this.posX = e.getPosX(); this.posY = e.getPosY(); this.posZ = e.getPosZ(); this.pitch = (byte) MathHelper.floor(e.rotationPitch * 256.0F / 360.0F); this.yaw = (byte) MathHelper.floor(e.rotationYaw * 256.0F / 360.0F); this.headYaw = (byte) (e.getRotationYawHead() * 256.0F / 360.0F); Vec3d vec3d = e.getMotion(); double d1 = MathHelper.clamp(vec3d.x, -3.9D, 3.9D); double d2 = MathHelper.clamp(vec3d.y, -3.9D, 3.9D); double d3 = MathHelper.clamp(vec3d.z, -3.9D, 3.9D); this.velX = (int)(d1 * 8000.0D); this.velY = (int)(d2 * 8000.0D); this.velZ = (int)(d3 * 8000.0D); this.buf = null; } private SpawnEntity(int typeId, int entityId, UUID uuid, double posX, double posY, double posZ, byte pitch, byte yaw, byte headYaw, int velX, int velY, int velZ, PacketBuffer buf) { this.entity = null; this.typeId = typeId; this.entityId = entityId; this.uuid = uuid; this.posX = posX; this.posY = posY; this.posZ = posZ; this.pitch = pitch; this.yaw = yaw; this.headYaw = headYaw; this.velX = velX; this.velY = velY; this.velZ = velZ; this.buf = buf; } public static void encode(SpawnEntity msg, PacketBuffer buf) { buf.writeVarInt(msg.typeId); buf.writeInt(msg.entityId); buf.writeLong(msg.uuid.getMostSignificantBits()); buf.writeLong(msg.uuid.getLeastSignificantBits()); 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(PacketBuffer buf) { return new SpawnEntity( buf.readVarInt(), buf.readInt(), new UUID(buf.readLong(), buf.readLong()), buf.readDouble(), buf.readDouble(), buf.readDouble(), buf.readByte(), buf.readByte(), buf.readByte(), buf.readShort(), buf.readShort(), buf.readShort(), buf ); } public static void handle(SpawnEntity msg, Supplier ctx) { ctx.get().enqueueWork(() -> { EntityType type = Registry.ENTITY_TYPE.getByValue(msg.typeId); if (type == null) { 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)); } Optional world = LogicalSidedProvider.CLIENTWORLD.get(ctx.get().getDirection().getReceptionSide()); Entity e = world.map(w->type.customClientSpawn(msg, w)).orElse(null); if (e == null) { return; } e.setPacketCoordinates(msg.posX, msg.posY, msg.posZ); e.setPositionAndRotation(msg.posX, msg.posY, msg.posZ, (msg.yaw * 360) / 256.0F, (msg.pitch * 360) / 256.0F); e.setRotationYawHead((msg.headYaw * 360) / 256.0F); e.setRenderYawOffset((msg.headYaw * 360) / 256.0F); e.setEntityId(msg.entityId); e.setUniqueId(msg.uuid); world.filter(ClientWorld.class::isInstance).ifPresent(w->((ClientWorld)w).addEntity(msg.entityId, e)); e.setVelocity(msg.velX / 8000.0, msg.velY / 8000.0, msg.velZ / 8000.0); if (e instanceof IEntityAdditionalSpawnData) { ((IEntityAdditionalSpawnData) e).readSpawnData(msg.buf); } }); ctx.get().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 PacketBuffer getAdditionalData() { return buf; } } public static class OpenContainer { private final int id; private final int windowId; private final ITextComponent name; private final PacketBuffer additionalData; OpenContainer(ContainerType id, int windowId, ITextComponent name, PacketBuffer additionalData) { this(Registry.MENU.getId(id), windowId, name, additionalData); } private OpenContainer(int id, int windowId, ITextComponent name, PacketBuffer additionalData) { this.id = id; this.windowId = windowId; this.name = name; this.additionalData = additionalData; } public static void encode(OpenContainer msg, PacketBuffer buf) { buf.writeVarInt(msg.id); buf.writeVarInt(msg.windowId); buf.writeTextComponent(msg.name); buf.writeByteArray(msg.additionalData.readByteArray()); } public static OpenContainer decode(PacketBuffer buf) { return new OpenContainer(buf.readVarInt(), buf.readVarInt(), buf.readTextComponent(), new PacketBuffer(Unpooled.wrappedBuffer(buf.readByteArray(32600)))); } public static void handle(OpenContainer msg, Supplier ctx) { ctx.get().enqueueWork(() -> { ScreenManager.getScreenFactory(msg.getType(), Minecraft.getInstance(), msg.getWindowId(), msg.getName()) .ifPresent(f -> { Container c = msg.getType().create(msg.getWindowId(), Minecraft.getInstance().player.inventory, msg.getAdditionalData()); @SuppressWarnings("unchecked") Screen s = ((ScreenManager.IScreenFactory)f).create(c, Minecraft.getInstance().player.inventory, msg.getName()); Minecraft.getInstance().player.openContainer = ((IHasContainer)s).getContainer(); Minecraft.getInstance().displayGuiScreen(s); }); }); ctx.get().setPacketHandled(true); } public final ContainerType getType() { return Registry.MENU.getByValue(this.id); } public int getWindowId() { return windowId; } public ITextComponent getName() { return name; } public PacketBuffer getAdditionalData() { return additionalData; } } public static class DimensionInfoMessage { private ResourceLocation dimName; private boolean skylight; private int id; private ResourceLocation modDimensionName; private PacketBuffer extraData; DimensionInfoMessage(DimensionType type) { id = type.getId() + 1; dimName = type.getRegistryName(); modDimensionName = type.getModType().getRegistryName(); skylight = type.hasSkyLight(); extraData = new PacketBuffer(Unpooled.buffer()); type.getModType().write(extraData, true); } DimensionInfoMessage(final int dimId, final ResourceLocation dimname, final ResourceLocation modDimensionName, final boolean skylight, final PacketBuffer extraData) { id = dimId; this.dimName = dimname; this.modDimensionName = modDimensionName; this.skylight = skylight; this.extraData = extraData; } public static DimensionInfoMessage decode(PacketBuffer buffer) { int dimId = buffer.readInt(); ResourceLocation dimname = buffer.readResourceLocation(); ResourceLocation moddimname = buffer.readResourceLocation(); boolean skylight = buffer.readBoolean(); PacketBuffer pb = new PacketBuffer(Unpooled.wrappedBuffer(buffer.readByteArray())); return new DimensionInfoMessage(dimId, dimname, moddimname, skylight, pb); } public static void encode(DimensionInfoMessage message, PacketBuffer buffer) { buffer.writeInt(message.id); buffer.writeResourceLocation(message.dimName); buffer.writeResourceLocation(message.modDimensionName); buffer.writeBoolean(message.skylight); buffer.writeByteArray(message.extraData.array()); } private DimensionType makeDummyDimensionType() { final ModDimension modDim = ForgeRegistries.MOD_DIMENSIONS.getValue(modDimensionName); // default to overworld if no moddim found if (modDim == null) return DimensionType.OVERWORLD; modDim.read(extraData, true); return new DimensionType(id, "dummy", "dummy", modDim.getFactory(), skylight, FuzzedBiomeMagnifier.INSTANCE, modDim, extraData); } public static boolean handle(final DimensionInfoMessage message, final Supplier contextSupplier) { contextSupplier.get().enqueueWork(()-> NetworkHooks.addCachedDimensionType(message.makeDummyDimensionType(), message.dimName)); return true; } } }