aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-01-26 10:57:06 +0100
committerLinnea Gräf <nea@nea.moe>2024-01-26 13:46:55 +0100
commite7a9511c1a49e3a2db57dbf3d6b3a63516d9cde9 (patch)
tree9d758140030be67f6b0c01717e8b53ae8521239b /docs
parent9fba044f498c73f02e582399279301bc5e6a8427 (diff)
downloadSkyBlockModWiki-e7a9511c1a49e3a2db57dbf3d6b3a63516d9cde9.tar.gz
SkyBlockModWiki-e7a9511c1a49e3a2db57dbf3d6b3a63516d9cde9.tar.bz2
SkyBlockModWiki-e7a9511c1a49e3a2db57dbf3d6b3a63516d9cde9.zip
Add event and command tutorials
Diffstat (limited to 'docs')
-rw-r--r--docs/development/commands.md137
-rw-r--r--docs/development/events.md111
2 files changed, 248 insertions, 0 deletions
diff --git a/docs/development/commands.md b/docs/development/commands.md
new file mode 100644
index 0000000..56f52e6
--- /dev/null
+++ b/docs/development/commands.md
@@ -0,0 +1,137 @@
+# Creating your first command
+
+This tutorial focuses on client commands, meaning they will get run on the client. If you want to develop a server command there are more considerations to be done (like permissions, and synchronizing server state to the client).
+
+## Basic command class
+
+First, let's create a new class for our command. We will call it `CrashCommand` because it will crash your game. Of course, your command can do whatever you want. We need to make sure our command `extends CommandBase`.
+
+```java
+public class CrashCommand extends CommandBase {
+
+ @Override
+ public String getCommandName() {
+ return "crashme";
+ }
+
+ @Override
+ public String getCommandUsage(ICommandSender sender) {
+ return "";
+ }
+
+ @Override
+ public void processCommand(ICommandSender sender, String[] args) throws CommandException {
+ throw new RuntimeException("Not yet implemented!");
+ }
+
+ @Override
+ public boolean canCommandSenderUseCommand(ICommandSender sender) {
+ return true;
+ }
+
+}
+```
+
+I already implemented three methods into the command.
+
+!!! warning
+ When writing a client command you will need to override `canCommandSenderUseCommand`. By default this method does not generate, but without it you will get a `You do not have permission to use this command` error (since you by default do not have any permissions on a server). Just always return `true`, since the command is client side only anyway.
+
+
+First the method `getCommandName`, which returns the name of your command. The name is what you use in chat to run the command. The command name should be just numbers and letters (and maybe dashes, if you want). In chat you will need to run `/crashme` to run this command (the `/` gets added to the name automatically).
+
+The second method is `getCommandUsage`. You need to override it because every commands needs to have a usage, but for most SkyBlock mods that usage doesn't matter — it only gets displayed in the `/help` menu, and Hypixel does not show client commands in it's help menu.
+
+## Running your command
+
+The `processCommand` method is run when your command is executed:
+
+
+```java
+@Override
+public void processCommand(ICommandSender sender, String[] args) throws CommandException {
+ LogManager.getLogger("CrashCommand").info("Intentionally crashing the Game!");
+ FMLCommonHandler.instance().exitJava(1, false);
+}
+```
+
+!!! info
+ When using a Logger, make sure to use the `LogManager` from `org.apache.logging.log4j.LogManager`. Using the other log managers won't work.
+
+!!! info
+ If you want to close the game, you need to use `FMLCommonHandler.instance().exitJava(exitCode, false)` instead of `System.exit`. Forge disables the normal `System.exit` calls.
+
+But, this way of crashing the game might be a bit too easy to accidentally run. So let's add a confirmation system. When your `processCommand` is called, you are given two arguments: the `sender` is always the current player (since this is a client command), and the `args` array gives you all the arguments you are being called with. If a player runs the command `/crashme foo bar`, args will be `new String[] {"foo", "bar"}`.
+
+```java
+@Override
+public void processCommand(ICommandSender sender, String[] args) throws CommandException {
+ // Make sure to check the array length before checking an argument
+ if (args.length == 1 && args[0].equals("confirm")) {
+ LogManager.getLogger("CrashCommand").info("Intentionally crashing the Game!");
+ FMLCommonHandler.instance().exitJava(1, false);
+ } else {
+ sender.addChatMessage(new ChatComponentText("§aAre you sure you want to crash the game? Click to confirm!")
+ .setChatStyle(new ChatStyle()
+ .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/crashme confirm"))));
+ }
+}
+```
+
+!!! info
+ Because `sender` is always the current player, you can also use
+ ```java
+ Minecraft.getMinecraft().thePlayer.addChatMessage(/* ... */);
+ ```
+
+Minecraft uses `IChatComponent`s in chat (and a few other places). You can make those by calling `new ChatComponentText("")`. In there you can use format codes like `§a`. If you want, you can also use `EnumChatFormatting.GREEN.toString()` instead of `§a`. You can change the chat style of a `ChatComponentText` in order to give it hover or click effects.
+
+
+!!! warning
+ You might be tempted to open a gui from your command like this:
+ ```java
+ @Override
+ public void processCommand(ICommandSender sender, String[] args) throws CommandException {
+ Minecraft.getMinecraft().displayGuiScreen(new MyGuiScreen());
+ }
+ ```
+ This will not work, since your command gets executed from the chat gui and sending a chat line schedules the chat gui to be closed in the same tick (accidentally closing your gui instead).
+
+ In order to make this work, you need to instead wait a tick and then open your gui. You can do this by having a tick event handler in your main mod class like this:
+ ```java
+ // In your main mod class
+ public static GuiScreen screenToOpenNextTick = null;
+
+ @SubscribeEvent
+ public void onTick(TickEvent.ClientTickEvent event) {
+ if (event.phase == TickEvent.Phase.END) return;
+ if (screenToOpenNextTick != null) {
+ Minecraft.getMinecraft().displayGuiScreen(screenToOpenNextTick);
+ screenToOpenNextTick = null;
+ }
+ }
+
+ // In your command class:
+ @Override
+ public void processCommand(ICommandSender sender, String[] args) throws CommandException {
+ ExampleMod.screenToOpenNextTick = new MyGuiScreen();
+ }
+ ```
+
+ See [Events](/development/events) for more info on how to set up event handlers.
+
+## Registering your command
+
+After all this work your command still just will not run. This is because the final step of client commands is still missing. You need to register your command. You typically do this in the `FMLInitializationEvent`:
+
+
+```java
+@Mod.EventHandler
+public void init(FMLInitializationEvent event) {
+ ClientCommandHandler.instance.registerCommand(new CrashCommand());
+}
+```
+
+
+
+
diff --git a/docs/development/events.md b/docs/development/events.md
new file mode 100644
index 0000000..152ae62
--- /dev/null
+++ b/docs/development/events.md
@@ -0,0 +1,111 @@
+# Events in Forge
+
+Forge uses events to allow mods to communicate with Minecraft and each other. Most of the events you will need to use come from Forge, but you can also create your own events if you need more.
+
+## Subscribing to events
+
+If you are interested in an event you need to create an event handler. For this first create a method that has the `@SubscribeEvent` annotation, is `public`, return `void` and takes an event as an argument. The type of the event argument is what decides which events your method receives. You can also only have one argument on an event handler.
+
+```java
+public class MyEventHandlerClass {
+ int chatCount = 0;
+ @SubscribeEvent
+ public void onChat(ClientChatReceivedEvent event) {
+ chatCount++;
+ System.out.println("Chats received total: " + chatCount);
+ }
+}
+```
+
+This on it's own will not do anything yet. You must also register the event handler. To do that you register it on the corresponding event bus. For almost everything you will do, you need the `MinecraftForge.EVENT_BUS` (yes, even your own custom events should use this event bus).
+
+
+```java
+@Mod(modid = "examplemod", useMetadata = true)
+public class ExampleMod {
+ @Mod.EventHandler
+ public void init(FMLInitializationEvent event) {
+ MinecraftForge.EVENT_BUS.register(new MyEventHandlerClass());
+ }
+}
+```
+
+## Cancelling Events
+
+Forge Events can be cancelled. What exactly that means depends on the event, but it usually stops the action the event indicates from happening.
+
+```java
+@SubscribeEvent
+public void onChat(ClientChatReceivedEvent event) {
+ // No more talking about cheese
+ if (event.message.getFormattedText().contains("cheese"))
+ event.setCanceled(true);
+}
+```
+
+Not all events can be cancelled. Check the event class in the decompilation for the `@Cancellable` annotation.
+
+
+## Custom Events
+
+!!! note
+ This is an advanced topic that most mod developers don't need to worry about.
+
+Forge also allows you to create custom events. Each event needs to have it's own class extending `Event` (transitively or not). (Make sure you extend `net.minecraftforge.fml.common.eventhandler.Event`).
+
+```java
+// If you don't want your event to be cancellable, remove this annotation
+@Cancelable
+public class CheeseEvent extends Event {
+ public final int totalCheeseCount;
+
+ public CheeseEvent(int totalCheeseCount) {
+ this.totalCheeseCount = totalCheeseCount;
+ }
+}
+```
+
+That's it, you are done. You have a custom event!
+
+I'm kidding of course. The next step is actually using your event. For now, let's put our own custom event inside the forge chat event:
+
+```java
+int cheeseCount = 0;
+
+@SubscribeEvent
+public void onChat(ClientChatReceivedEvent event) {
+ if (event.message.getFormattedText().contains("cheese")) {
+ CheeseEvent cheeseEvent = new CheeseEvent(++cheeseCount);
+ MinecraftForge.EVENT_BUS.post(cheeseEvent);
+ }
+}
+```
+
+And now we are done. Unless you want your event to be cancellable. We also need to add code to handle cancelled events (if you made your event `@Cancelable`). What that cancelling does is up to you, but in our example let's just cancel the original chat message event (hiding that chat message):
+
+```java
+@SubscribeEvent
+public void onChat(ClientChatReceivedEvent event) {
+ if (event.message.getFormattedText().contains("cheese")) {
+ CheeseEvent cheeseEvent = new CheeseEvent(++cheeseCount);
+ MinecraftForge.EVENT_BUS.post(cheeseEvent);
+ if (cheeseEvent.isCanceled()) {
+ event.setCanceled(true);
+ }
+ }
+}
+```
+
+You can now subscribe to your custom event like you would to any other event:
+
+```java
+@SubscribeEvent
+public void onCheese(CheeseEvent event) {
+ if (event.totalCheeseCount > 10) {
+ // Only 10 cheese messages are allowed per restart
+ event.setCanceled(true);
+ }
+}
+```
+
+