Jump to content

[19.3] How do I do logical server side setup?


Jontom Xire

Recommended Posts

There are several setup events I can hande:

  • FMLCommonSetupEvent - called for both client and server.
  • FMLClientSetupEvent - client only.
  • FMLDedicatedServerSetupEvent - dedicated server only, NOT single player/LAN game server.

 

Where is the FMLServerSetupEvent that runs for both single player/LAN game servers and dedicated servers?

 

I have read https://docs.minecraftforge.net/en/1.19.x/concepts/sides/ extensively. A DistExecutor seems overkill. In the common setup event handler I just want to check the side and do client/server specific stuff.

 

How do I do setup for a logical server that may be running as a single player game or may be running as a dedicated server?

Link to comment
Share on other sites

import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.util.thread.EffectiveSide;


...
    private void commonSetup(final FMLCommonSetupEvent event)
    {
        if (LogicalSide.SERVER == EffectiveSide.get())
        {
...
        }
    }

 

The above code compiles (when you fill in the missing bits). It does not work for a single player game. I am not sure if it works for a dedicated server.

Link to comment
Share on other sites

Maybe if FMLCommonSetupEvent was called FMLLogicalServerSetupEvent you would understand what it is for? 🙂 

If you are checking isClient() in the common setup you are doing it wrong.

The DistExecutor is for runtime when you might not know which side you are on. Or you do know but want to safely switch to the other logical side.

The DistExecutor is just a different form of control statement. e.g. the java if control statement could be used in a similar way

static void If(boolean predicate, Supplier<Runnable> Then, Supplier<Runnable> Else) 
    if (predicate) {
       Then.get().run();
    } else {
       Else.get().run();
    }
}

Boilerplate:

If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one.

If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install

Large files should be posted to a file sharing site like https://gist.github.com  You should also read the support forum sticky post.

Link to comment
Share on other sites

My understanding is that FMLCommonSetupEvent() triggers for both client and local server and dedicated server, which is why it has "Common" in the name, so calling it FMLLogicalServerSetup() would be incorrect.

 

I have found that my snippet above works for other events, such as PlayerEvent.PlayerLoggedInEvent.

 

Link to comment
Share on other sites

The LogicalServer is present everywhere, it is "common".

So yes it will trigger on the PHYSCIAL client.

If Mojang ever release a "thin client" that doesn't include single player mode things will be different.

 

PlayerLoggedInEvent is a runtime event not a mod event.

The correct way to do it there is as mentioned in the document you link 

if (player.level.isClientSide) ...

You can find literally hundreds of examples in the vanilla code.

Boilerplate:

If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one.

If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install

Large files should be posted to a file sharing site like https://gist.github.com  You should also read the support forum sticky post.

Link to comment
Share on other sites

Quote

The LogicalServer is present everywhere, it is "common".

This contradicts both the documentation and my own experience. In the single player version there is a separate server thread. According to the documentation, and I agree based on over two decades as a professional software engineer, the code needs to distinguish between the server thread and the client (render) thread when doing anything server based so that when the mod is used on a dedicated server, everything works as expected. There are explicit warnings in the documentation to the effect that failing to do this correctly leads to crashes when a mod that works perfectly as a single player/LAN game is used with a dedicated server with multiplayer clients connecting to it.

 

The documentation talks about Level#isClientSide(), yes, but the information I was missing was that I didn't know where to get an instance of Level from. I didn't realise that it was a member of the Entity class until I read your post and searched for "level" in the documentation of the "Player" class.

Quote

if (player.level.isClientSide) ...

Thank you very much for helping me figure this out.

 

Link to comment
Share on other sites

Quote

This contradicts both the documentation and my own experience. In the single player version there is a separate server thread. According to the documentation, and I agree based on over two decades as a professional software engineer, the code needs to distinguish between the server thread and the client (render) thread 

We are talking about very different things.

There are the mod event bus events that run at game startup to do one time initialisation.

 

Then there are the runtime events for when the actual server/runtime is starting (ServerLifecycleEvents + others).

Which do things that need to change things based on which save is loaded.

Boilerplate:

If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one.

If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install

Large files should be posted to a file sharing site like https://gist.github.com  You should also read the support forum sticky post.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • I made a block entity in forge 1.20.1, I want to prevent hopper from taking input slot item, i tried to override the extractItem method, it prevented hopper from taking input slot item, but the player also unable to take/change the item in input slot unless the slot is empty. public class FluidSeparatorBlockEntity extends BlockEntity implements MenuProvider { private static final int INPUT_SLOT = 0; private final CustomItemHandler itemHandler = new CustomItemHandler(3){ @Override protected void onContentsChanged(int slot) { setChanged(); } @Override public boolean isItemValid(int slot, @NotNull ItemStack stack) { return slot == INPUT_SLOT; } @Override public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { if (slot == INPUT_SLOT) { return ItemStack.EMPTY; } return super.extractItem(slot, amount, simulate); } }; private LazyOptional<IItemHandler> lazyItemHandler = LazyOptional.empty(); protected final ContainerData data; private int progress = 0; private int maxProgress = 78; public FluidSeparatorBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.FLUID_SEPARATOR_BE.get(), pPos, pBlockState); this.data = new ContainerData() { @Override public int get(int pIndex) { return switch (pIndex) { case 0 -> FluidSeparatorBlockEntity.this.progress; case 1, 2 -> FluidSeparatorBlockEntity.this.maxProgress; default -> 0; }; } @Override public void set(int pIndex, int pValue) { switch (pIndex) { case 0 -> FluidSeparatorBlockEntity.this.progress = pValue; case 1, 2 -> FluidSeparatorBlockEntity.this.maxProgress = pValue; } } @Override public int getCount() { return 3; } }; } @Override public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) { if(cap == ForgeCapabilities.ITEM_HANDLER) { return lazyItemHandler.cast(); } return super.getCapability(cap, side); } @Override public void onLoad() { super.onLoad(); lazyItemHandler = LazyOptional.of(() -> itemHandler); } @Override public void invalidateCaps() { super.invalidateCaps(); lazyItemHandler.invalidate(); } public void drops() { SimpleContainer inventory = new SimpleContainer(itemHandler.getSlots()); for(int i = 0; i < itemHandler.getSlots(); i++) { inventory.setItem(i, itemHandler.getStackInSlot(i)); } Containers.dropContents(this.level, this.worldPosition, inventory); } @Override public Component getDisplayName() { return Component.translatable("block.chemmaster.fluid_separator"); } @Nullable @Override public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) { return new FluidSeparatorMenu(pContainerId, pPlayerInventory, this, this.data); } @Override protected void saveAdditional(CompoundTag pTag) { pTag.put("inventory", itemHandler.serializeNBT()); pTag.putInt("fluid_separator.progress", progress); super.saveAdditional(pTag); } @Override public void load(CompoundTag pTag) { super.load(pTag); itemHandler.deserializeNBT(pTag.getCompound("inventory")); progress = pTag.getInt("fluid_separator.progress"); } public void tick(Level pLevel, BlockPos pPos, BlockState pState) { ItemStack inputStack = this.itemHandler.getStackInSlot(INPUT_SLOT); if (inputStack.getCount() < 2) { resetProgress(); return; } if(hasRecipe()) { increaseCraftingProgress(); setChanged(pLevel, pPos, pState); if(hasProgressFinished()) { craftItem(); resetProgress(); } } else { resetProgress(); } } private void resetProgress() { progress = 0; } private void craftItem() { Optional<FluidSeparatingRecipe> recipe = getCurrentRecipe(); if (recipe.isPresent()) { List<ItemStack> results = recipe.get().getOutputs(); ItemStack inputStack = this.itemHandler.getStackInSlot(INPUT_SLOT); if (inputStack.getCount() < 2) { // If there are not enough items, do not proceed with crafting return; } // Extract the input item from the input slot this.itemHandler.internalExtractItem(INPUT_SLOT, 2, false); // Loop through each result item and find suitable output slots for (ItemStack result : results) { int outputSlot = findSuitableOutputSlot(result); if (outputSlot != -1) { this.itemHandler.setStackInSlot(outputSlot, new ItemStack(result.getItem(), this.itemHandler.getStackInSlot(outputSlot).getCount() + result.getCount())); } else { // Handle the case where no suitable output slot is found // This can be logging an error, throwing an exception, or any other handling logic System.err.println("No suitable output slot found for item: " + result); } } } } private int findSuitableOutputSlot(ItemStack result) { // Implement logic to find a suitable output slot for the given result // Return the slot index or -1 if no suitable slot is found for (int i = 0; i < this.itemHandler.getSlots(); i++) { // Ensure we do not place the output item in the input slot if (i == INPUT_SLOT) { continue; } ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || (stackInSlot.getItem() == result.getItem() && stackInSlot.getCount() + result.getCount() <= stackInSlot.getMaxStackSize())) { return i; } } return -1; } private boolean hasRecipe() { Optional<FluidSeparatingRecipe> recipe = getCurrentRecipe(); if (recipe.isEmpty()) { return false; } List<ItemStack> results = recipe.get().getOutputs(); for (ItemStack result : results) { if (!canInsertAmountIntoOutputSlot(result) || !canInsertItemIntoOutputSlot(result.getItem())) { return false; } } return true; } private Optional<FluidSeparatingRecipe> getCurrentRecipe(){ SimpleContainer inventory = new SimpleContainer(this.itemHandler.getSlots()); for (int i = 0; i < itemHandler.getSlots(); i++) { inventory.setItem(i, this.itemHandler.getStackInSlot(i)); } return this.level.getRecipeManager().getRecipeFor(FluidSeparatingRecipe.Type.INSTANCE, inventory, level); } private boolean canInsertAmountIntoOutputSlot(ItemStack result) { for (int i = 1; i < this.itemHandler.getSlots(); i++) { ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || (stackInSlot.getItem() == result.getItem() && stackInSlot.getCount() + result.getCount() <= stackInSlot.getMaxStackSize())) { return true; } } return false; } private boolean canInsertItemIntoOutputSlot(Item item) { for (int i = 1; i < this.itemHandler.getSlots(); i++) { ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || stackInSlot.getItem() == item) { return true; } } return false; } private boolean hasProgressFinished() { return progress >= maxProgress; } private void increaseCraftingProgress() { progress++; } }  
    • No dice. Unfortunately this fix didn't work, thank you though.
    • Maybe you need a rayon mod. https://www.curseforge.com/minecraft/mc-mods/rayon
    • Not sure what's going on the logs are making even less sense than usual to me. Any help would be much appreciated.   https://paste.ee/p/KBHyP#s=0
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.