Containers
Containers are used wherever you need a GUI to do something on the server. This can be accessing an inventory, configuring a block or item, or anything in between.
Container classes
Facade containers must have a single constructor. That constructor’s parameters must
begin with int windowId, PlayerEntity player
, followed by any number of
Prism-serializable parameters. This constructor should not be called
directly, only indirectly via the FacadeContainerType
‘s open
method.
Opening containers
To open a container you call the FacadeContainerType.open(player, title, args...)
method, passing the player, title, and the extra arguments in the container’s
constructor. Those arguments are used to call the container’s constructor on the server,
then serialized and sent to the client so it can create an identical container.
Note that containers can only be opened on the server.
When possible, you should pass specific values to the constructor instead of relying on it to get those values itself. For example, pass tile entity locations directly (you can’t pass the tile entity itself), instead of having the client deduce what block the player is looking at.
Container communication
Inventories in containers will automatically sync, but for manual/direct communication
between the client and the server, use messages. To create a message, create a method
with any number of Prism-serializable parameters and annotate it with @Message
.
To send a message from one side to the other (either the server container to the client
or the client container to the server), call sendMessage
, passing the method name and
the arguments to pass to it. The arguments will be serialized into a packet, sent over
the network, and then the @Message
method will be called.
NOTE! These messages are still packets, so it’s vital you take the same precautions as you would with any client-to-server communication.
public class CoolContainer extends FacadeContainer {
@Message
private void doThingClicked(String thing, int value) {
if(!this.isClientContainer()) {
// do something on the server, then send a message to the client container
this.sendClientMessage("thingDone");
}
}
@Message
private void thingDone() {
}
}
public class CoolContainerScreen extends FacadeContainerScreen<CoolContainer> {
private void doThingButtonClicked() {
// send a message to the container (both on the client and the server)
this.sendMessage("doThingClicked", "wow", 42);
}
}
Ideally the client and server containers should move in lockstep, with the same thing happening exactly the same way on both sides. Messages can help facilitate that by calling the same code on both sides.
Messages from the GUI to the container should generally describe what the user did,
not what the container should do. The container is the
only part you can trust, so all the business decisions,
including what action to take based on user input, should happen there. This is not to
say you need to handle every button press in the container, just that your GUI should
send a clickedItem(String id)
message instead of an openPage(String id)
message.
These may very well be behave identically in this case, but just changing the way you
name your messages can help you get in the right mindset.
Container registration
If you’re using Foundation, the registration manager can register containers for you. If you aren’t using Foundation, registering containers is relatively simple.
The first thing you’ll need to do is create the FacadeContainerType
and put it
somewhere global. For simplicity in this example I’ll put it in the mod class and
constructor, but you can put it wherever you feel works best.
~import com.teamwizardry.librarianlib.facade.container.FacadeContainerType;
~import net.minecraft.util.ResourceLocation;
~
public class ExampleMod {
public static FacadeContainerType<CoolContainer> coolContainerType;
public ExampleMod() {
coolContainerType = new FacadeContainerType(CoolContainer.class);
coolContainerType.setRegistryName(new ResourceLocation(
"examplemod:cool_container"
));
}
}
Next you need to register the type in the appropriate registry event and specify a
FacadeContainerScreen
type in the client setup event. In order to use a constructor
reference, the screen’s constructor should have three parameters: your container,
the player’s inventory, and the title
(CoolContainer container, PlayerInventory inventory, ITextComponent title
).
~import com.teamwizardry.librarianlib.facade.container.FacadeContainerType;
~import net.minecraft.client.gui.ScreenManager;
~import net.minecraft.inventory.container.ContainerType;
~import net.minecraft.util.ResourceLocation;
~import net.minecraftforge.api.distmarker.Dist;
~import net.minecraftforge.api.distmarker.OnlyIn;
~import net.minecraftforge.event.RegistryEvent;
~import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
~
public class ExampleMod {
public static FacadeContainerType<CoolContainer> coolContainerType;
public ExampleMod() {
coolContainerType = new FacadeContainerType(CoolContainer.class);
coolContainerType.setRegistryName(new ResourceLocation(
"examplemod:cool_container"
));
}
@SubscribeEvent
public void registerContainers(RegistryEvent.Register<ContainerType<?>> e) {
e.getRegistry().register(coolContainerType);
}
@OnlyIn(Dist.CLIENT)
public void clientSetup(FMLClientSetupEvent e) {
ScreenManager.registerFactory(coolContainerType, CoolContainerScreen::new);
}
}