Line 11: |
Line 11: |
| * '''Physical Server''' - Often known as the dedicated server, the ''physical server'' is the entire program that runs whenever you launch any dedicated server executable or JAR (<code>minecraft_server.jar</code>) that does not bring up a playable GUI. | | * '''Physical Server''' - Often known as the dedicated server, the ''physical server'' is the entire program that runs whenever you launch any dedicated server executable or JAR (<code>minecraft_server.jar</code>) that does not bring up a playable GUI. |
| * '''Logical Server''' - The ''logical server'' is what runs game logic: mob spawning, weather, updating inventories, health, AI, and all other game mechanics. The logical server is present within the physical server, but is also can run inside a physical client together with a logical client, as a single player world. The logical server always runs in a thread named the <code>Server Thread</code>. | | * '''Logical Server''' - The ''logical server'' is what runs game logic: mob spawning, weather, updating inventories, health, AI, and all other game mechanics. The logical server is present within the physical server, but is also can run inside a physical client together with a logical client, as a single player world. The logical server always runs in a thread named the <code>Server Thread</code>. |
− | * '''Logical Client''' - The ''logical client'' is what accepts input from the player and relays it to the logical server. In addition, it also receives information from the logical server and makes it available graphically to the player. The logical client runs in the <code>Client Thread</code>, though often several other threads are spawned to handle things like audio and chunk render batching. | + | * '''Logical Client''' - The ''logical client'' is what accepts input from the player and relays it to the logical server. In addition, it also receives information from the logical server and makes it available graphically to the player. The logical client runs in the <code>Render Thread</code>, though often several other threads are spawned to handle things like audio and chunk render batching. |
| | | |
| In the Minecraft codebase, the physical sides are represented by an enum called <code>Dist</code>, while the logical sides are represented by an enum called <code>LogicalSide</code>. | | In the Minecraft codebase, the physical sides are represented by an enum called <code>Dist</code>, while the logical sides are represented by an enum called <code>LogicalSide</code>. |
Line 19: |
Line 19: |
| == Performing Side-Specific Operations == | | == Performing Side-Specific Operations == |
| | | |
− | === <tt>World#isRemote</tt> === | + | === <tt>Level#isClientSide</tt> === |
| | | |
− | This <code>boolean</code> check is the most common way (and the most recommended way) to check the currently running '''logical side'''. Querying this field on a <code>World</code> object establishes the logical side that the world belongs to. That is, if this field is <code>true</code>, the world extends <code>ClientWorld</code> and is currently running on the logical client, while if the field is <code>false</code>, the world extends <code>ServerWorld</code> and is running on the logical server. | + | This <code>boolean</code> check is the most common way (and the most recommended way) to check the currently running '''logical side'''. Querying this field on a <code>Level</code> object establishes the logical side that the level belongs to. That is, if this field is <code>true</code>, the level extends <code>ClientLevel</code> and is currently running on the logical client, while if the field is <code>false</code>, the level extends <code>ServerLevel</code> and is running on the logical server. |
| | | |
| It follows that the physical/dedicated server will always contain <code>false</code> in this field, but we cannot assume that <code>false</code> implies a physical server, since this field can also be <code>false</code> for the logical server inside a physical client (in other words, a single player world). | | It follows that the physical/dedicated server will always contain <code>false</code> in this field, but we cannot assume that <code>false</code> implies a physical server, since this field can also be <code>false</code> for the logical server inside a physical client (in other words, a single player world). |
| | | |
− | Use this check whenever you need to determine if game logic and other mechanics should be run. For example, if you want to damage the player every time they click your block, or have your machine process dirt into diamonds, you should only do so after ensuring <code>world.isRemote</code> is <code>false</code>. Applying game logic to the logical client can cause desynchronization (ghost entities, desynchronized stats, etc.) in the best case, and crashes in the worst case. | + | Use this check whenever you need to determine if game logic and other mechanics should be run. For example, if you want to damage the player every time they click your block, or have your machine process dirt into diamonds, you should only do so after ensuring <code>level#isClientSide</code> is <code>false</code>. Applying game logic to the logical client can cause desynchronization (ghost entities, desynchronized stats, etc.) in the best case, and crashes in the worst case. |
| | | |
| This check should be used as your go-to default. Aside from the sided events and <code>DistExecutor</code>, rarely will you need the other ways of determining sides and adjusting behavior. | | This check should be used as your go-to default. Aside from the sided events and <code>DistExecutor</code>, rarely will you need the other ways of determining sides and adjusting behavior. |
Line 39: |
Line 39: |
| Considering the use of a single "universal" jar for client and server mods, and the separation of the physical sides into two jars, an important question comes to mind: How do we use code that is only present on one physical side? All code in <code>net.minecraft.client</code> (such as anything rendering-related) is only present on the physical client, and all code in <code>net.minecraft.server.dedicated</code> is only present on the physical server. | | Considering the use of a single "universal" jar for client and server mods, and the separation of the physical sides into two jars, an important question comes to mind: How do we use code that is only present on one physical side? All code in <code>net.minecraft.client</code> (such as anything rendering-related) is only present on the physical client, and all code in <code>net.minecraft.server.dedicated</code> is only present on the physical server. |
| | | |
− | If any class you write references those names in any way, they will crash the game when that respective class is loaded in an environment where those names do not exist. For example, a very common mistake in beginners is to call <code>Minecraft.getMinecraft().<doStuff>()</code> in block or tile entity classes, which will crash any physical/dedicated server as soon as the class is loaded. | + | If any class you write references those names in any way, they will crash the game when that respective class is loaded in an environment where those names do not exist. For example, a very common mistake in beginners is to call <code>Minecraft.getMinecraft().<doStuff>()</code> in block or block entity classes, which will crash any physical/dedicated server as soon as the class is loaded. |
| | | |
− | How do we resolve this? Forge provides the <code>DistExecutor</code> utility class, which provides various methods to run and call different code depending on the physical side. | + | How do we resolve this? Forge provides the <code>DistExecutor</code> utility class, which provides various methods to run and call different code depending on the physical side. There are two versions of each method: <code>safe*</code> and <code>unsafe</code>. <code>safe*</code> methods accept a supplied method reference from another class; otherwise, an error will be thrown. <code>unsafe*</code> methods accept a doubly supplied instance instead. <code>unsafe*</code> methods could cause <code>ClassNotFoundException</code>s depending on how they are used, though in standard cases referencing an external class within the double supplier should be safe. In any case, make sure you understand how the [https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-5.html#jvms-5.4.1 class verifier] works to load classes before using this. |
| | | |
| {{Tip/Important| | | {{Tip/Important| |
Line 48: |
Line 48: |
| === Thread Groups === | | === Thread Groups === |
| | | |
− | If <code>Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER</code>, it is likely the current thread is on the logical server. Otherwise, it is likely on the logical client. This is useful to retrieve the '''logical''' side when you do not have access to a <code>World</code> object to check <code>isRemote</code>. It ''guesses'' which logical side you are on by looking at the thread group of the currently running thread. Because it is a guess, this method should only be used when other options have been exhausted. In all other cases, you should prefer checking <code>world.isRemote</code> to this check. | + | If <code>Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER</code>, it is likely the current thread is on the logical server. Otherwise, it is likely on the logical client. This is useful to retrieve the '''logical''' side when you do not have access to a <code>Level</code> object to check <code>isClientSide</code>. It ''guesses'' which logical side you are on by looking at the thread group of the currently running thread. Because it is a guess, this method should only be used when other options have been exhausted. In all other cases, you should prefer checking <code>level#isClientSide</code> to this check. |
| | | |
| === <tt>FMLEnvironment.dist</tt> === | | === <tt>FMLEnvironment.dist</tt> === |
Line 58: |
Line 58: |
| Annotating a method or field with the <code>@OnlyIn(Dist)</code> annotation indicates to the loader that the respective member should be completely stripped out of the definition not on the specified '''physical''' side. Usually, these are only seen when browsing through the decompiled Minecraft code, indicating methods that the Mojang obfuscator stripped out. | | Annotating a method or field with the <code>@OnlyIn(Dist)</code> annotation indicates to the loader that the respective member should be completely stripped out of the definition not on the specified '''physical''' side. Usually, these are only seen when browsing through the decompiled Minecraft code, indicating methods that the Mojang obfuscator stripped out. |
| | | |
− | {{Tip/Important|There is little to no reason for using this annotation directly. The only valid reason to use this annotation is if you are extending a vanilla class that is already annotated with <code>@OnlyIn</code>. In all other cases where you need to dispatch behavior based on physical sides, use <code>DistExecutor</code> or a check on <code>FMLEnvironment.dist</code> instead.}} | + | {{Tip/Important|There is '''NO''' reason for using this annotation directly. Use <code>DistExecutor</code> or a check on <code>FMLEnvironment.dist</code> instead.}} |
| | | |
| == Common Mistakes == | | == Common Mistakes == |
Line 75: |
Line 75: |
| So for one-sided mods, you would typically register your event handlers using [[#DistExecutor|<code>DistExecutor</code>]] or <code>@EventBusSubscriber(value = Dist.*)</code>, instead of directly calling the relevant registration methods in the constructor. The idea is that, if your mod is loaded on the wrong side, it should simply do nothing: listen to no events, do no special behaviors, and so on. A one-sided mod by nature should not register blocks, items, … since they would need to be available on the other side, too. | | So for one-sided mods, you would typically register your event handlers using [[#DistExecutor|<code>DistExecutor</code>]] or <code>@EventBusSubscriber(value = Dist.*)</code>, instead of directly calling the relevant registration methods in the constructor. The idea is that, if your mod is loaded on the wrong side, it should simply do nothing: listen to no events, do no special behaviors, and so on. A one-sided mod by nature should not register blocks, items, … since they would need to be available on the other side, too. |
| | | |
− | Additionally, if your mod is one-sided, it typically does not forbid the user from joining a server that is lacking that mod, but the server menu will display the server as being incompatible (with a red <code>X</code> at the side). Therefore, you should override the <code>DISPLAYTEST</code> extension point to make sure that Forge does not think your mod is required on the server: (this is usually done in the mod constructor) | + | Additionally, if your mod is one-sided, it typically does not forbid the user from joining a server that is lacking that mod. Therefore, you should set the <code>displayTest</code> property in your [[Mods.toml|mods.toml]] to whatever value is necessary. |
| + | |
| + | <syntaxhighlight lang="toml> |
| + | [[mods]] |
| + | # ... |
| + | # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. |
| + | # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. |
| + | # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. |
| + | # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. |
| + | # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. |
| + | displayTest="IGNORE_ALL_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) |
| + | </syntaxhighlight> |
| + | |
| + | If a custom display test is to be used, then the <code>displayTest</code> option should be set to <code>NONE</code>, and an <code>IExtensionPoint$DisplayTest</code> extension should be registered: |
| | | |
| {{Template:Tabs/Code_Snippets | | {{Template:Tabs/Code_Snippets |
| |java=// Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible | | |java=// Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible |
− | ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.DISPLAYTEST, () -> Pair.of(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true)); | + | ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); |
| |kotlin=// Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible | | |kotlin=// Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible |
− | ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.DISPLAYTEST) { Pair.of(Supplier { FMLNetworkConstants.IGNORESERVERONLY }, BiPredicate { _: String, _: Boolean -> true }) } | + | ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class) { IExtensionPoint.DisplayTest(Supplier { NetworkConstants.IGNORESERVERONLY }, BiPredicate { _: String, _: Boolean -> true }) } |
| |scala=import scala.compat.java8.FunctionConverters._ | | |scala=import scala.compat.java8.FunctionConverters._ |
| // Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible | | // Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible |
− | ModLoadingContext.get.registerExtensionPoint(ExtensionPoint.DISPLAYTEST, (() => Pair.of((() => FMLNetworkConstants.IGNORESERVERONLY).asJava, asJavaBiPredicate((_: String, _: java.lang.Boolean) => true))).asJava) | + | ModLoadingContext.get.registerExtensionPoint(IExtensionPoint.DisplayTest.class, (() => new IExtensionPoint.DisplayTest((() => NetworkConstants.IGNORESERVERONLY).asJava, asJavaBiPredicate((_: String, _: java.lang.Boolean) => true))).asJava) |
| |}} | | |}} |
| | | |