feat: added multiplayer support

This commit is contained in:
moonleay 2024-04-24 01:18:23 +02:00
parent 136bd78f00
commit 60ceb1b3ae
Signed by: moonleay
GPG key ID: 82667543CCD715FB
22 changed files with 183 additions and 42 deletions

View file

@ -67,6 +67,8 @@ dependencies {
modImplementation("net.fabricmc:fabric-language-kotlin:$fabricKotlinVersion") modImplementation("net.fabricmc:fabric-language-kotlin:$fabricKotlinVersion")
modImplementation("de.huebcraft.mod-libs:configlib:$configlibVersion") modImplementation("de.huebcraft.mod-libs:configlib:$configlibVersion")
modImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
} }
val targetJavaVersion = 17 val targetJavaVersion = 17

View file

@ -2,12 +2,18 @@ package net.moonleay.gimble
import net.moonleay.gimble.build.BuildConstants import net.moonleay.gimble.build.BuildConstants
import net.fabricmc.api.ModInitializer import net.fabricmc.api.ModInitializer
import net.moonleay.gimble.networking.GimbleServer
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
internal object Main : ModInitializer { internal object Main : ModInitializer {
private val LOGGER = LogManager.getLogger(BuildConstants.modName) private val LOGGER = LogManager.getLogger(BuildConstants.modName)
override fun onInitialize() { override fun onInitialize() {
LOGGER.info("Main has been initialized") LOGGER.info("Initializing Gimble on the common side...")
LOGGER.info("Registering packets...")
GimbleServer.registerPacketHandler()
LOGGER.info("Packets have been registered.")
LOGGER.info("Gimble has been initialized on the common side.")
LOGGER.info("${BuildConstants.modName} (${BuildConstants.modId}) v.${BuildConstants.modVersion} by moonleay")
} }
} }

View file

@ -2,8 +2,11 @@ package net.moonleay.gimble.client.editor
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.moonleay.gimble.client.util.ChatUtil import net.moonleay.gimble.client.util.ChatUtil
import net.moonleay.gimble.client.editor.modes.Mode import net.moonleay.gimble.editor.ServerEditorManager
import net.moonleay.gimble.client.editor.modes.ModeModifier import net.moonleay.gimble.editor.state.EditorState
import net.moonleay.gimble.editor.state.mode.Mode
import net.moonleay.gimble.editor.state.mode.ModeModifier
import net.moonleay.gimble.networking.GimbleClient
object ClientEditor { object ClientEditor {
var CURRENT_MODE = Mode.NORMAL var CURRENT_MODE = Mode.NORMAL
@ -11,6 +14,15 @@ object ClientEditor {
private val TEMP_DISABLED_MODIFIERS = mutableListOf<ModeModifier>() private val TEMP_DISABLED_MODIFIERS = mutableListOf<ModeModifier>()
/*
* Send an updated player state to the server
* */
fun updateServerState() {
val state = EditorState(CURRENT_MODE, CURRENT_MODE_MODIFIER)
GimbleClient.sendEditorState(state)
ServerEditorManager.updateEditorState(MinecraftClient.getInstance().player!!.uuid, state)
}
fun toggleModifier(mod: ModeModifier) { fun toggleModifier(mod: ModeModifier) {
if (CURRENT_MODE.incompatibleModifiers.contains(mod)){ if (CURRENT_MODE.incompatibleModifiers.contains(mod)){
if (TEMP_DISABLED_MODIFIERS.contains(mod)) if (TEMP_DISABLED_MODIFIERS.contains(mod))
@ -33,6 +45,9 @@ object ClientEditor {
checkForIncompatibleModeModifiers() checkForIncompatibleModeModifiers()
} }
/**
* This runs on Mode updated
*/
fun checkForIncompatibleModeModifiers() { fun checkForIncompatibleModeModifiers() {
if (TEMP_DISABLED_MODIFIERS.size > 0) { if (TEMP_DISABLED_MODIFIERS.size > 0) {
CURRENT_MODE_MODIFIER.addAll( CURRENT_MODE_MODIFIER.addAll(
@ -53,6 +68,9 @@ object ClientEditor {
} }
} }
// Update State
this.updateServerState()
if(TEMP_DISABLED_MODIFIERS.isNotEmpty()) { if(TEMP_DISABLED_MODIFIERS.isNotEmpty()) {
ChatUtil.addToChatHistory("The following modifiers are not supported by this editor mode and are therefore currently disabled: " + ChatUtil.addToChatHistory("The following modifiers are not supported by this editor mode and are therefore currently disabled: " +
getListAsString(TEMP_DISABLED_MODIFIERS), MinecraftClient.getInstance()) getListAsString(TEMP_DISABLED_MODIFIERS), MinecraftClient.getInstance())

View file

@ -4,7 +4,7 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.Mode import net.moonleay.gimble.editor.state.mode.Mode
class EnableInsertModeShortcut(key: KeyBinding): GimbleShortcut(key) { class EnableInsertModeShortcut(key: KeyBinding): GimbleShortcut(key) {

View file

@ -4,7 +4,7 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.Mode import net.moonleay.gimble.editor.state.mode.Mode
class EnableReplaceModeShortcut(key: KeyBinding): GimbleShortcut(key) { class EnableReplaceModeShortcut(key: KeyBinding): GimbleShortcut(key) {

View file

@ -4,7 +4,7 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.Mode import net.moonleay.gimble.editor.state.mode.Mode
class EnableVisualModeShortcut(key: KeyBinding): GimbleShortcut(key) { class EnableVisualModeShortcut(key: KeyBinding): GimbleShortcut(key) {

View file

@ -4,12 +4,10 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.ModeModifier import net.moonleay.gimble.editor.state.mode.ModeModifier
class ToggleBulldozerModifierShortcut(key: KeyBinding): GimbleShortcut(key) { class ToggleBulldozerModifierShortcut(key: KeyBinding): GimbleShortcut(key) {
override fun onPressed(client: MinecraftClient) { override fun onPressed(client: MinecraftClient) {
ClientEditor.toggleModifier(ModeModifier.BULLDOZER) ClientEditor.toggleModifier(ModeModifier.BULLDOZER)
ClientEditor.onModifiersUpdated()
} }
} }

View file

@ -4,12 +4,10 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.ModeModifier import net.moonleay.gimble.editor.state.mode.ModeModifier
class ToggleForcePlaceModifierShortcut(key: KeyBinding): GimbleShortcut(key) { class ToggleForcePlaceModifierShortcut(key: KeyBinding): GimbleShortcut(key) {
override fun onPressed(client: MinecraftClient) { override fun onPressed(client: MinecraftClient) {
ClientEditor.toggleModifier(ModeModifier.FORCE_PLACE) ClientEditor.toggleModifier(ModeModifier.FORCE_PLACE)
ClientEditor.onModifiersUpdated()
} }
} }

View file

@ -4,12 +4,10 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.ModeModifier import net.moonleay.gimble.editor.state.mode.ModeModifier
class ToggleNoClipModifierShortcut(key: KeyBinding): GimbleShortcut(key) { class ToggleNoClipModifierShortcut(key: KeyBinding): GimbleShortcut(key) {
override fun onPressed(client: MinecraftClient) { override fun onPressed(client: MinecraftClient) {
ClientEditor.toggleModifier(ModeModifier.NO_CLIP) ClientEditor.toggleModifier(ModeModifier.NO_CLIP)
ClientEditor.onModifiersUpdated()
} }
} }

View file

@ -4,12 +4,10 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.moonleay.gimble.client.editor.ClientEditor import net.moonleay.gimble.client.editor.ClientEditor
import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut import net.moonleay.gimble.client.keybindings.impl.GimbleShortcut
import net.moonleay.gimble.client.editor.modes.ModeModifier import net.moonleay.gimble.editor.state.mode.ModeModifier
class ToggleNoUpdatesModifierShortcut(key: KeyBinding): GimbleShortcut(key) { class ToggleNoUpdatesModifierShortcut(key: KeyBinding): GimbleShortcut(key) {
override fun onPressed(client: MinecraftClient) { override fun onPressed(client: MinecraftClient) {
ClientEditor.toggleModifier(ModeModifier.NO_UPDATES) ClientEditor.toggleModifier(ModeModifier.NO_UPDATES)
ClientEditor.onModifiersUpdated()
} }
} }

View file

@ -0,0 +1,28 @@
package net.moonleay.gimble.editor
import net.moonleay.gimble.build.BuildConstants
import net.moonleay.gimble.editor.state.EditorState
import net.moonleay.gimble.editor.state.mode.ModeModifier
import org.apache.logging.log4j.LogManager
import java.util.UUID
object ServerEditorManager {
private val LOGGER = LogManager.getLogger(BuildConstants.modName)
val STATEMAP = mutableMapOf<UUID, EditorState>()
fun updateEditorState(playerUUID: UUID, editorState: EditorState) {
STATEMAP[playerUUID] = editorState
LOGGER.info("$playerUUID: ${editorState.editorMode} with ${editorState.editorModifier}")
}
fun getEditorState(playerUUID: UUID): EditorState? {
return STATEMAP[playerUUID]
}
fun playerHasModifier(playerUUID: UUID, modifier: ModeModifier): Boolean {
if (!STATEMAP.containsKey(playerUUID))
return false
return STATEMAP[playerUUID]!!.editorModifier.contains(modifier) ?: false
}
}

View file

@ -0,0 +1,11 @@
package net.moonleay.gimble.editor.state
import kotlinx.serialization.Serializable
import net.moonleay.gimble.editor.state.mode.Mode
import net.moonleay.gimble.editor.state.mode.ModeModifier
@Serializable
data class EditorState(
var editorMode: Mode,
var editorModifier: MutableList<ModeModifier>,
)

View file

@ -1,6 +1,7 @@
package net.moonleay.gimble.client.editor.modes package net.moonleay.gimble.editor.state.mode
enum class Mode(val displayName: String, val color: Int, val incompatibleModifiers: List<ModeModifier>){ enum class Mode(val displayName: String, val color: Int, val incompatibleModifiers: List<ModeModifier>){
UNKNOWN("UNKNOWN", 0x000000, listOf()), // Unknown mode. This mode cannot be entered
NORMAL("NORMAL", 0x90a959, listOf(ModeModifier.NO_UPDATES, ModeModifier.BULLDOZER, ModeModifier.FORCE_PLACE)), // Do nothing NORMAL("NORMAL", 0x90a959, listOf(ModeModifier.NO_UPDATES, ModeModifier.BULLDOZER, ModeModifier.FORCE_PLACE)), // Do nothing
INSERT("INSERT", 0xf4bf75, listOf()), // Place and break blocks INSERT("INSERT", 0xf4bf75, listOf()), // Place and break blocks
REPLACE("REPLACE", 0xac4242, listOf()), // Replace blocks REPLACE("REPLACE", 0xac4242, listOf()), // Replace blocks

View file

@ -1,4 +1,4 @@
package net.moonleay.gimble.client.editor.modes package net.moonleay.gimble.editor.state.mode
enum class ModeModifier(val displayName: String) { enum class ModeModifier(val displayName: String) {
// NONE("None"), // No Modifiers - default behavior // NONE("None"), // No Modifiers - default behavior

View file

@ -6,8 +6,8 @@ import net.minecraft.client.gui.hud.InGameHud;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.moonleay.gimble.client.editor.ClientEditor; import net.moonleay.gimble.client.editor.ClientEditor;
import net.moonleay.gimble.client.editor.modes.Mode; import net.moonleay.gimble.editor.state.mode.Mode;
import net.moonleay.gimble.client.editor.modes.ModeModifier; import net.moonleay.gimble.editor.state.mode.ModeModifier;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;

View file

@ -2,7 +2,7 @@ package net.moonleay.gimble.mixin;
import net.minecraft.client.render.Camera; import net.minecraft.client.render.Camera;
import net.moonleay.gimble.client.editor.ClientEditor; import net.moonleay.gimble.client.editor.ClientEditor;
import net.moonleay.gimble.client.editor.modes.ModeModifier; import net.moonleay.gimble.editor.state.mode.ModeModifier;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;

View file

@ -1,32 +1,59 @@
package net.moonleay.gimble.mixin; package net.moonleay.gimble.mixin;
import net.minecraft.entity.Entity; import com.mojang.authlib.GameProfile;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerAbilities;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.moonleay.gimble.client.editor.ClientEditor; import net.minecraft.world.World;
import net.moonleay.gimble.client.editor.modes.ModeModifier; import net.moonleay.gimble.editor.ServerEditorManager;
import net.moonleay.gimble.editor.state.mode.ModeModifier;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class) import java.util.UUID;
public class NoClipMixin {
@Shadow public boolean noClip; @Mixin(PlayerEntity.class)
public abstract class NoClipMixin extends LivingEntity {
@Shadow public abstract GameProfile getGameProfile();
@Inject(method = "tick", at = @At(value = "HEAD")) @Shadow public abstract PlayerAbilities getAbilities();
private void enoClip(CallbackInfo ci) {
// TODO: Add player check, add multiplayer compat protected NoClipMixin(EntityType<? extends LivingEntity> entityType, World world) {
if(!((Entity)(Object)this instanceof PlayerEntity)) // Only check on player super(entityType, world);
} // Server side
@Inject(method = "tick", at = @At(value = "FIELD",
target = "Lnet/minecraft/entity/player/PlayerEntity;noClip:Z", shift = At.Shift.AFTER)
) private void enoClip(CallbackInfo ci) {
UUID uuid = this.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.playerHasModifier(uuid, ModeModifier.NO_CLIP)) {
return; // NoClip is not enabled
}
if (!this.getAbilities().flying) {
return; return;
}
if (!ClientEditor.INSTANCE.containsModifier(ModeModifier.NO_CLIP)) // Enable NoClip
return;
PlayerEntity thePlayer = (PlayerEntity)(Object)this;
if (!thePlayer.getAbilities().flying)
return;
this.noClip = true; this.noClip = true;
} }
@Inject(method = "updatePose", at = @At("HEAD"))
private void onUpdatePose(CallbackInfo ci) {
UUID uuid = this.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.playerHasModifier(uuid, ModeModifier.NO_CLIP))
return; // NoClip is not enabled
if (!this.getAbilities().flying)
return;
// Force standing pose in NoClip mode
this.setPose(EntityPose.STANDING);
}
} }

View file

@ -2,7 +2,7 @@ package net.moonleay.gimble.mixin;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.GameOptions; import net.minecraft.client.option.GameOptions;
import net.moonleay.gimble.client.editor.modes.Mode; import net.moonleay.gimble.editor.state.mode.Mode;
import net.moonleay.gimble.client.editor.ClientEditor; import net.moonleay.gimble.client.editor.ClientEditor;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;

View file

@ -12,7 +12,7 @@ import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.moonleay.gimble.client.editor.ClientEditor; import net.moonleay.gimble.client.editor.ClientEditor;
import net.moonleay.gimble.client.editor.modes.Mode; import net.moonleay.gimble.editor.state.mode.Mode;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;

View file

@ -0,0 +1,21 @@
package net.moonleay.gimble.networking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.encodeToByteArray
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.moonleay.gimble.editor.state.EditorState
object GimbleClient {
/**
* Sends the given [EditorState] to the server.
*/
@OptIn(ExperimentalSerializationApi::class)
fun sendEditorState(state: EditorState) {
val buf = PacketByteBufs.create()
buf.writeByteArray(Cbor.encodeToByteArray(state))
ClientPlayNetworking.send(PacketIDs.UPDATE_EDITOR_STATE_ID, buf)
}
}

View file

@ -0,0 +1,27 @@
package net.moonleay.gimble.networking
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromByteArray
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.moonleay.gimble.editor.ServerEditorManager
import net.moonleay.gimble.editor.state.EditorState
object GimbleServer {
fun registerPacketHandler() {
ServerPlayNetworking
.registerGlobalReceiver(PacketIDs.UPDATE_EDITOR_STATE_ID)
{ server, player, handler, buf, responseSender ->
handleStateUpdate(player, buf)
}
}
private fun handleStateUpdate(player: ServerPlayerEntity, buf: PacketByteBuf){
val state = Cbor.decodeFromByteArray<EditorState>(buf.readByteArray())
ServerEditorManager.updateEditorState(player.uuid, state)
player.sendMessage(Text.of("Updated State (Server) ${state.editorMode} with ${state.editorModifier}"))
}
}

View file

@ -0,0 +1,8 @@
package net.moonleay.gimble.networking
import net.minecraft.util.Identifier
import net.moonleay.gimble.build.BuildConstants
object PacketIDs {
val UPDATE_EDITOR_STATE_ID = Identifier(BuildConstants.modId, "update_editor_state")
}