diff --git a/src/main/java/net/fabricmc/fabric/api/client/gui/GuiProviderRegistry.java b/src/main/java/net/fabricmc/fabric/api/client/gui/GuiProviderRegistry.java new file mode 100644 index 000000000..a8eb4a222 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/client/gui/GuiProviderRegistry.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.gui; + +import net.fabricmc.fabric.api.container.ContainerFactory; +import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.fabricmc.fabric.api.container.GuiFactory; +import net.fabricmc.fabric.impl.client.gui.GuiProviderImpl; +import net.minecraft.client.gui.ContainerGui; +import net.minecraft.container.Container; +import net.minecraft.util.Identifier; + +public interface GuiProviderRegistry { + + GuiProviderRegistry INSTANCE = GuiProviderImpl.INSTANCE; + + /** + * + * Register a gui factory, this should only be done on the client side and not on the dedicated server. + * + * @param identifier a shared identifier, this identifier should also be used to register a container using {@link ContainerProviderRegistry} + * @param factory the gui factory, this should return a new {@link ContainerGui} + */ + void registerFactory(Identifier identifier, ContainerFactory factory); + + /** + * + * Register a GuiFactory that will be used to create a new gui when provided with a container + * + * @param identifier a shared identifier, this identifier should also be used to register a container using {@link ContainerProviderRegistry} + * @param guiFactory the supplier that should be used to create the new gui + */ + void registerFactory(Identifier identifier, GuiFactory guiFactory); + +} diff --git a/src/main/java/net/fabricmc/fabric/api/container/ContainerFactory.java b/src/main/java/net/fabricmc/fabric/api/container/ContainerFactory.java new file mode 100644 index 000000000..213732a7f --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/container/ContainerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.container; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; + +@FunctionalInterface +public interface ContainerFactory { + + /** + * + * Creates the new gui or container + * + * @param identifier the Identifier is the name that was used when registering the factory + * @param player the player that is opening the gui/container + * @param buf the buffer contains the same data that was provided with {@Link ContainerProviderRegistry.openContainer} + * @return the new gui or container + */ + T create(Identifier identifier, PlayerEntity player, PacketByteBuf buf); + +} diff --git a/src/main/java/net/fabricmc/fabric/api/container/ContainerProviderRegistry.java b/src/main/java/net/fabricmc/fabric/api/container/ContainerProviderRegistry.java new file mode 100644 index 000000000..c70f2cd38 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/container/ContainerProviderRegistry.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.container; + +import net.fabricmc.fabric.api.client.gui.GuiProviderRegistry; +import net.fabricmc.fabric.impl.container.ContainerProviderImpl; +import net.minecraft.container.Container; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; + +import java.util.function.Consumer; + +public interface ContainerProviderRegistry { + + ContainerProviderRegistry INSTANCE = ContainerProviderImpl.INSTANCE; + + /** + * + * Register a container factory + * + * @param identifier a shared identifier, this identifier should also be used to register a container using {@link GuiProviderRegistry} + * @param factory the ContainerFactory that should return a new {@link Container} + */ + void registerFactory(Identifier identifier, ContainerFactory factory); + + /** + * + * This is used to open a container on the client, call this from the server + * + * @param identifier the identifier that was used when registering the container + * @param player the player that should open the container + * @param writer a PacketByteBuf where data can be written to, this data is then accessible by the container factory when creating the container or the gui + */ + void openContainer(Identifier identifier, ServerPlayerEntity player, Consumer writer); + +} diff --git a/src/main/java/net/fabricmc/fabric/api/container/GuiFactory.java b/src/main/java/net/fabricmc/fabric/api/container/GuiFactory.java new file mode 100644 index 000000000..4aa214c18 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/container/GuiFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.container; + +import net.minecraft.client.gui.ContainerGui; +import net.minecraft.container.Container; + +@FunctionalInterface +public interface GuiFactory { + + ContainerGui create(C container); + +} diff --git a/src/main/java/net/fabricmc/fabric/impl/FabricAPIClientInitializer.java b/src/main/java/net/fabricmc/fabric/impl/FabricAPIClientInitializer.java index 4b47dfa2b..3af775235 100644 --- a/src/main/java/net/fabricmc/fabric/impl/FabricAPIClientInitializer.java +++ b/src/main/java/net/fabricmc/fabric/impl/FabricAPIClientInitializer.java @@ -17,6 +17,7 @@ package net.fabricmc.fabric.impl; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.impl.client.gui.GuiProviderImpl; import net.fabricmc.fabric.networking.CustomPayloadPacketRegistry; import net.fabricmc.fabric.registry.RegistrySyncManager; import net.minecraft.client.MinecraftClient; @@ -29,5 +30,7 @@ public class FabricAPIClientInitializer implements ClientModInitializer { // if not hosting server, apply packet RegistrySyncManager.receivePacket(ctx, buf, !MinecraftClient.getInstance().method_1496()); }); + + ((GuiProviderImpl)GuiProviderImpl.INSTANCE).init(); } } diff --git a/src/main/java/net/fabricmc/fabric/impl/client/gui/GuiProviderImpl.java b/src/main/java/net/fabricmc/fabric/impl/client/gui/GuiProviderImpl.java new file mode 100644 index 000000000..bf305a4a7 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/impl/client/gui/GuiProviderImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.gui; + +import net.fabricmc.fabric.api.client.gui.GuiProviderRegistry; +import net.fabricmc.fabric.api.container.ContainerFactory; +import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.fabricmc.fabric.api.container.GuiFactory; +import net.fabricmc.fabric.impl.container.ContainerProviderImpl; +import net.fabricmc.fabric.networking.CustomPayloadPacketRegistry; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.ContainerGui; +import net.minecraft.container.Container; +import net.minecraft.util.Identifier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +public class GuiProviderImpl implements GuiProviderRegistry { + + /** + * Use the instance provided by GuiProviderRegistry + */ + public static final GuiProviderRegistry INSTANCE = new GuiProviderImpl(); + + private static final Logger LOGGER = LogManager.getLogger(); + + private static final Identifier OPEN_CONTAINER = new Identifier("fabric", "open_container"); + private static final Map> FACTORIES = new HashMap<>(); + + public void registerFactory(Identifier identifier, ContainerFactory factory) { + if (FACTORIES.containsKey(identifier)) { + throw new RuntimeException("A factory has already been registered as " + identifier.toString()); + } + FACTORIES.put(identifier, factory); + } + + @Override + public void registerFactory(Identifier identifier, GuiFactory guiFactory) { + registerFactory(identifier, (identifier1, player, buf) -> { + C container = ((ContainerProviderImpl)ContainerProviderRegistry.INSTANCE).createContainer(identifier1, player, buf); + if(container == null){ + LOGGER.error("A null container was created for " + identifier1.toString()); + return null; + } + return guiFactory.create(container); + }); + } + + public void init() { + CustomPayloadPacketRegistry.CLIENT.register(OPEN_CONTAINER, (packetContext, packetByteBuf) -> { + Identifier identifier = packetByteBuf.readIdentifier(); + int syncId = packetByteBuf.readUnsignedByte(); + MinecraftClient.getInstance().execute(() -> { + ContainerFactory factory = FACTORIES.get(identifier); + if (factory == null) { + LOGGER.error("No factory found for " + identifier.toString()); + return; + } + ContainerGui gui = factory.create(identifier, packetContext.getPlayer(), packetByteBuf); + gui.container.syncId = syncId; + MinecraftClient.getInstance().openGui(gui); + }); + }); + } +} diff --git a/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java b/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java new file mode 100644 index 000000000..2a8025701 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.container; + +import io.netty.buffer.Unpooled; +import net.fabricmc.fabric.api.container.ContainerFactory; +import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.minecraft.client.network.packet.CustomPayloadClientPacket; +import net.minecraft.container.Container; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class ContainerProviderImpl implements ContainerProviderRegistry { + + /** + * Use the instance provided by ContainerProviderRegistry + */ + public static final ContainerProviderRegistry INSTANCE = new ContainerProviderImpl(); + + private static final Logger LOGGER = LogManager.getLogger(); + + private static final Identifier OPEN_CONTAINER = new Identifier("fabric", "open_container"); + private static final Map> FACTORIES = new HashMap<>(); + + @Override + public void registerFactory(Identifier identifier, ContainerFactory factory) { + if (FACTORIES.containsKey(identifier)) { + throw new RuntimeException("A factory has already been registered as " + identifier.toString()); + } + FACTORIES.put(identifier, factory); + } + + @Override + public void openContainer(Identifier identifier, ServerPlayerEntity player, Consumer writer) { + SyncIdProvider syncIDProvider = (SyncIdProvider) player; + int syncId = syncIDProvider.fabric_incrementSyncId(); + PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); + buf.writeIdentifier(identifier); + buf.writeByte(syncId); + + writer.accept(buf); + player.networkHandler.sendPacket(new CustomPayloadClientPacket(OPEN_CONTAINER, buf)); + + PacketByteBuf clonedBuf = new PacketByteBuf(buf.duplicate()); + clonedBuf.readIdentifier(); + clonedBuf.readUnsignedByte(); + + Container container = createContainer(identifier, player, clonedBuf); + if(container == null){ + return; + } + player.container = container; + player.container.syncId = syncId; + player.container.addListener(player); + } + + public C createContainer(Identifier identifier, PlayerEntity player, PacketByteBuf buf){ + ContainerFactory factory = FACTORIES.get(identifier); + if (factory == null) { + LOGGER.error("No container factory found for %s ", identifier.toString()); + return null; + } + return (C) factory.create(identifier, player, buf); + } +} diff --git a/src/main/java/net/fabricmc/fabric/impl/container/SyncIdProvider.java b/src/main/java/net/fabricmc/fabric/impl/container/SyncIdProvider.java new file mode 100644 index 000000000..1d66fbcad --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/impl/container/SyncIdProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.container; + +/** + * This is a interface that is present on a ServerPlayerEntity, it allows access to the sync id. + */ +public interface SyncIdProvider { + + /** + * gets and sets the new player sync id, and returns the new value + * + * @return the new sync id of the player + */ + int fabric_incrementSyncId(); + +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/container/MixinServerPlayerEntity.java b/src/main/java/net/fabricmc/fabric/mixin/container/MixinServerPlayerEntity.java new file mode 100644 index 000000000..c6219a2fa --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/container/MixinServerPlayerEntity.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.container; + +import net.fabricmc.fabric.impl.container.SyncIdProvider; +import net.minecraft.server.network.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ServerPlayerEntity.class) +public abstract class MixinServerPlayerEntity implements SyncIdProvider { + @Shadow private int containerSyncId; + + @Shadow protected abstract void incrementContainerSyncId(); + + @Override + public int fabric_incrementSyncId() { + incrementContainerSyncId(); + return containerSyncId; + } +} diff --git a/src/main/resources/net.fabricmc.fabric.mixins.common.json b/src/main/resources/net.fabricmc.fabric.mixins.common.json index c17429609..774bededf 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.common.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.common.json @@ -6,6 +6,7 @@ "block.MixinBlockBuilder", "block.entity.MixinBlockEntity", "commands.MixinServerCommandManager", + "container.MixinServerPlayerEntity", "entity.MixinEntityTracker", "entity.MixinEntityTrackerEntry", "events.objectbuilder.MixinBlock", diff --git a/src/test/java/net/fabricmc/fabric/containers/ContainerMod.java b/src/test/java/net/fabricmc/fabric/containers/ContainerMod.java new file mode 100644 index 000000000..878daf5ae --- /dev/null +++ b/src/test/java/net/fabricmc/fabric/containers/ContainerMod.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.containers; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.fabricmc.fabric.commands.CommandRegistry; +import net.minecraft.container.Container; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.command.ServerCommandManager; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +public class ContainerMod implements ModInitializer { + + public static final Identifier EXAMPLE_CONTAINER = new Identifier("fabric_container", "example_container"); + + @Override + public void onInitialize() { + //Registers a basic server side command that shows that the openContainer works from the server side. + CommandRegistry.INSTANCE.register(false, serverCommandSourceCommandDispatcher -> + serverCommandSourceCommandDispatcher.register(ServerCommandManager + .literal("container") + .executes(context -> { + BlockPos pos = context.getSource().getEntity().getPos(); + + //Opens a container, sending the block pos + ContainerProviderRegistry.INSTANCE.openContainer(EXAMPLE_CONTAINER, context.getSource().getPlayer(), buf -> buf.writeBlockPos(pos)); + + return 1; + }))); + + //Registers a container factory that opens our example Container, this reads the block pos from the buffer + ContainerProviderRegistry.INSTANCE.registerFactory(EXAMPLE_CONTAINER, (identifier, player, buf) -> { + BlockPos pos = buf.readBlockPos(); + return new ExampleContainer(pos, player); + }); + + } + + //A basic container that prints to console when opened, this should print on the client + server + public static class ExampleContainer extends Container { + + BlockPos pos; + + public ExampleContainer(BlockPos pos, PlayerEntity playerEntity) { + this.pos = pos; + System.out.println("Opened container, " + pos); + } + + @Override + public boolean canUse(PlayerEntity playerEntity) { + return true; + } + } + +} diff --git a/src/test/java/net/fabricmc/fabric/containers/ContainerModClient.java b/src/test/java/net/fabricmc/fabric/containers/ContainerModClient.java new file mode 100644 index 000000000..89895ea8b --- /dev/null +++ b/src/test/java/net/fabricmc/fabric/containers/ContainerModClient.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.containers; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.gui.GuiProviderRegistry; +import net.minecraft.client.gui.ContainerGui; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; + +public class ContainerModClient implements ClientModInitializer { + + @Override + public void onInitializeClient() { + //Registers a gui factory that opens our example gui, this reads the block pos from the buffer + GuiProviderRegistry.INSTANCE.registerFactory(ContainerMod.EXAMPLE_CONTAINER, (identifier, player, buf) -> { + BlockPos pos = buf.readBlockPos(); + return new ExampleContainerGui(pos, player); + }); + + //Registers a gui factory that opens our example gui, this uses the container created by ContainerProviderRegistry + GuiProviderRegistry.INSTANCE.registerFactory(ContainerMod.EXAMPLE_CONTAINER, ExampleContainerGui2::new); + } + + //A container gui that shows the block pos that was sent + public static class ExampleContainerGui extends ContainerGui { + + BlockPos pos; + + public ExampleContainerGui(BlockPos pos, PlayerEntity playerEntity) { + super(new ContainerMod.ExampleContainer(pos, playerEntity)); + this.pos = pos; + } + + @Override + protected void drawBackground(float v, int i, int i1) { + fontRenderer.draw(pos.toString(), width / 2, height / 2, 0); + } + } + + + //A container gui that shows how you can take in a container provided by a GuiFactory + public static class ExampleContainerGui2 extends ContainerGui { + + BlockPos pos; + + public ExampleContainerGui2(ContainerMod.ExampleContainer container) { + super(container); + this.pos = container.pos; + } + + @Override + protected void drawBackground(float v, int i, int i1) { + fontRenderer.draw(pos.toString(), width / 2, height / 2, 0); + } + } + +} diff --git a/src/test/resources/mod.json b/src/test/resources/mod.json index 16e1e41dd..e24ed7115 100644 --- a/src/test/resources/mod.json +++ b/src/test/resources/mod.json @@ -7,6 +7,8 @@ "license": "Apache-2.0", "initializers": [ "net.fabricmc.fabric.colormapper.ColorProviderMod", - "net.fabricmc.fabric.events.ServerEventMod" + "net.fabricmc.fabric.events.ServerEventMod", + "net.fabricmc.fabric.containers.ContainerMod", + "net.fabricmc.fabric.containers.ContainerModClient" ] }