commit 27a424aa2172ab91b6252984dd314a2396e5dcec Author: Micle Date: Mon Oct 31 20:07:14 2022 +0000 Initial port of 1.18.2-3.0.1 to 1.19.2. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dda5cbb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +*.blend filter=lfs diff=lfs merge=lfs -text +*.obj filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.kra filter=lfs diff=lfs merge=lfs -text +*.mtl filter=lfs diff=lfs merge=lfs -text +*.bbmodel filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f83c7f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.gradle/ +/.idea/ +/build/ +/run/ +/src/test/ +/src/generated/resources/.cache/cache diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5739720 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +Copyright (c) 2022 Micle +All rights reserved. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d6ade27 --- /dev/null +++ b/build.gradle @@ -0,0 +1,125 @@ +buildscript { + repositories { + // These repositories are only for Gradle plugins, put any other repositories in the repository block further below + maven { url = 'https://maven.minecraftforge.net' } + maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } + mavenCentral() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true + classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT' + } +} + +apply plugin: 'net.minecraftforge.gradle' +apply plugin: 'org.spongepowered.mixin' + +def archiveVersion = "${project.mcVersion}-${project.buildVersion}" as Object + +java.toolchain.languageVersion = JavaLanguageVersion.of(17) + +minecraft { + mappings channel: 'official', version: mcVersion + + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + + runs { + client { + workingDirectory project.file('run') + + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + + arg "-mixin.config=${modID}.mixins.json" + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + + mods { + loginprotection { + source sourceSets.main + } + } + } + + server { + workingDirectory project.file('run') + + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + + arg "-mixin.config=${modID}.mixins.json" + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + + mods { + loginprotection { + source sourceSets.main + } + } + } + + data { + workingDirectory project.file('run') + + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + + arg "-mixin.config=${modID}.mixins.json" + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + + args '--mod', 'loginprotection', '--all', + '--existing', file('src/main/resources').toString(), + '--existing', file('src/generated/resources').toString(), + '--output', file('src/generated/resources/') + + mods { + loginprotection { + source sourceSets.main + } + } + } + } +} + +mixin { + add sourceSets.main, "${modID}.refmap.json" + config "${modID}.mixins.json" +} + +sourceSets.main.resources { + srcDir 'src/generated/resources' +} + +repositories { + maven { + name = "Progwml6 maven" + url = "https://dvs1.progwml6.com/files/maven/" + } + maven { + name = "ModMaven" + url = "https://modmaven.dev" + } +} + +dependencies { + minecraft "net.minecraftforge:forge:${project.mcVersion}-${project.forgeVersion}" + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' +} + +jar { + archiveFileName = "${project.archivesBaseName}-${archiveVersion}.jar" + manifest { + attributes([ + "Specification-Title" : project.name, + "Specification-Vendor" : project.author, + "Specification-Version" : "1", + "Implementation-Title" : project.name, + "Implementation-Vendor" : project.author, + "Implementation-Version" : archiveVersion, + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + ]) + } +} + +jar.finalizedBy('reobfJar') \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..31be383 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false + +group = dev.micle +archivesBaseName = micles-login-protection-forge +modID = loginprotection +name = Micle's Login Protection +author = Micle + +buildVersion = 3.0.0 +mcVersion = 1.19.2 +forgeVersion = 43.1.47 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..73bb918 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..46fbea0 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'LoginProtection-1.19' diff --git a/src/main/java/dev/micle/loginprotection/LoginProtection.java b/src/main/java/dev/micle/loginprotection/LoginProtection.java new file mode 100644 index 0000000..3cbc3f3 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/LoginProtection.java @@ -0,0 +1,43 @@ +package dev.micle.loginprotection; + +import dev.micle.loginprotection.proxy.IProxy; +import dev.micle.loginprotection.proxy.Proxy; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; + +import java.util.Optional; + +@Mod(LoginProtection.MOD_ID) +public class LoginProtection { + public static final String MOD_ID = "loginprotection"; + private static IProxy proxy; + + public LoginProtection() { + proxy = DistExecutor.safeRunForDist( + () -> Proxy.Client::new, + () -> Proxy.Server::new + ); + } + + public static ResourceLocation createResourceLocation(String name) throws IllegalArgumentException { + if (name.contains(":")) { + throw new IllegalArgumentException("Name contains namespace!"); + } + return new ResourceLocation(MOD_ID, name); + } + + public static String getVersion() { + Optional modContainer = ModList.get().getModContainerById(MOD_ID); + if (modContainer.isPresent()) { + return modContainer.get().getModInfo().getVersion().toString(); + } + return "0.0.0"; + } + + public static IProxy getProxy() { + return proxy; + } +} diff --git a/src/main/java/dev/micle/loginprotection/data/ProtectedPlayer.java b/src/main/java/dev/micle/loginprotection/data/ProtectedPlayer.java new file mode 100644 index 0000000..d4e22fb --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/data/ProtectedPlayer.java @@ -0,0 +1,76 @@ +package dev.micle.loginprotection.data; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +public class ProtectedPlayer { + // Initialize variables + private final UUID playerUUID; + private final Timer timer = new Timer(); + private TimerTask afkTimerTask, gracePeriodTimerTask; + private State state; + + /** + * Constructor for a ProtectedPlayer. + * @param playerUUID UUID of player to use. + */ + public ProtectedPlayer(UUID playerUUID, State state) { + this.playerUUID = playerUUID; + this.state = state; + } + + /** + * @return UUID of player, + */ + public UUID getPlayerUUID() { + return playerUUID; + } + + public Timer getTimer() { + return timer; + } + + public void setGracePeriodTimerTask(TimerTask gracePeriodTimerTask, long delay) { + if (this.gracePeriodTimerTask != null) { + this.gracePeriodTimerTask.cancel(); + } + if (gracePeriodTimerTask != null) { + this.gracePeriodTimerTask = gracePeriodTimerTask; + timer.schedule(this.gracePeriodTimerTask, delay); + } + } + + public void setAfkTimerTask(TimerTask afkTimerTask, long delay) { + if (this.afkTimerTask != null) { + this.afkTimerTask.cancel(); + } + if (afkTimerTask != null) { + this.afkTimerTask = afkTimerTask; + timer.schedule(this.afkTimerTask, delay); + } + } + + /** + * @return Current state of the player. + */ + public State getState() { + return state; + } + + /** + * Set the state of the player. + * @param state Player's new state. + */ + public void setState(State state) { + this.state = state; + } + + public enum State { + JOINING, + AFK, + ACTIVE, + LOGIN_GRACE, + AFK_GRACE + } +} diff --git a/src/main/java/dev/micle/loginprotection/data/ProtectedPlayerManager.java b/src/main/java/dev/micle/loginprotection/data/ProtectedPlayerManager.java new file mode 100644 index 0000000..85f28da --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/data/ProtectedPlayerManager.java @@ -0,0 +1,234 @@ +package dev.micle.loginprotection.data; + +import dev.micle.loginprotection.LoginProtection; +import dev.micle.loginprotection.network.NetworkManager; +import dev.micle.loginprotection.network.server.PlayerStatePacket; +import dev.micle.loginprotection.network.server.RequestLastInputTickPacket; +import dev.micle.loginprotection.setup.Config; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.network.NetworkDirection; + +import java.util.ArrayList; +import java.util.List; +import java.util.TimerTask; +import java.util.UUID; + +public class ProtectedPlayerManager { + // Initialize variables + private static final List protectedPlayers = new ArrayList<>(); + + /** + * Method for initializing the ProtectedPlayerManager. + */ + public static void init() { + for (ProtectedPlayer protectedPlayer : protectedPlayers) { + removePlayer(protectedPlayer.getPlayerUUID()); + } + } + + /** + * Adds a player to be protected. + * @param playerUUID UUID of player to protect. + */ + public static void addPlayer(UUID playerUUID) { + if (getPlayer(playerUUID) != null) { + return; + } + try { + protectedPlayers.add(new ProtectedPlayer(playerUUID, ProtectedPlayer.State.JOINING)); + NetworkManager.getChannel().sendTo(new PlayerStatePacket(ProtectedPlayer.State.JOINING), + LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID).connection.getConnection(), + NetworkDirection.PLAY_TO_CLIENT); + } catch (NullPointerException ignored) {} + } + + /** + * Gets a protected player from the list of protected players. + * @param playerUUID UUID of player to get. + * @return ProtectedPlayer instance if player is present, otherwise null. + */ + public static ProtectedPlayer getPlayer(UUID playerUUID) { + for (ProtectedPlayer protectedPlayer : protectedPlayers) { + if (protectedPlayer.getPlayerUUID().equals(playerUUID)) { + return protectedPlayer; + } + } + return null; + } + + /** + * Removes a player from the list of protected players. + * @param playerUUID UUID of player to remove. + */ + public static void removePlayer(UUID playerUUID) { + ProtectedPlayer player = getPlayer(playerUUID); + if (player == null) { + return; + } + + player.getTimer().cancel(); + protectedPlayers.remove(player); + } + + /** + * Updates a player's state appropriately. + * @param playerUUID UUID of player to update the state of. + */ + public static void updateState(UUID playerUUID) { + ProtectedPlayer protectedPlayer = getPlayer(playerUUID); + ServerPlayer player = LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID); + if (player == null) { + removePlayer(playerUUID); + return; + } + + if (protectedPlayer == null) { + addPlayer(playerUUID); + } else { + ProtectedPlayer.State currentState = protectedPlayer.getState(); + if (currentState.equals(ProtectedPlayer.State.JOINING)) { + if (Config.Server.LOGIN_GRACE_ENABLED.get()) { + // JOINING -> LOGIN_GRACE + protectedPlayer.setState(ProtectedPlayer.State.LOGIN_GRACE); + startGraceTimer(playerUUID, Config.Server.LOGIN_GRACE_DURATION.get() * 1000); + } else { + // JOINING -> ACTIVE + protectedPlayer.setState(ProtectedPlayer.State.ACTIVE); + if (Config.Server.LOGIN_APPLY_POST_EFFECTS.get()) { + applyPostEffects(playerUUID); + } + if (Config.Server.AFK_PROTECTION_ENABLED.get()) { + startAfkTimer(playerUUID, Config.Server.AFK_TIME_THRESHOLD.get() * 1000); + } else { + removePlayer(playerUUID); + } + } + } else if (currentState.equals(ProtectedPlayer.State.LOGIN_GRACE) || + currentState.equals(ProtectedPlayer.State.AFK_GRACE)) { + // LOGIN_GRACE, AFK_GRACE -> ACTIVE + protectedPlayer.setState(ProtectedPlayer.State.ACTIVE); + if ((currentState.equals(ProtectedPlayer.State.LOGIN_GRACE) && Config.Server.LOGIN_APPLY_POST_EFFECTS.get()) || + (currentState.equals(ProtectedPlayer.State.AFK_GRACE) && Config.Server.AFK_APPLY_POST_EFFECTS.get())) { + applyPostEffects(playerUUID); + } + if (Config.Server.AFK_PROTECTION_ENABLED.get()) { + startAfkTimer(playerUUID, Config.Server.AFK_TIME_THRESHOLD.get() * 1000); + } else { + removePlayer(playerUUID); + } + } else if (currentState.equals(ProtectedPlayer.State.ACTIVE)) { + // ACTIVE -> AFK + protectedPlayer.setState(ProtectedPlayer.State.AFK); + } else if (currentState.equals(ProtectedPlayer.State.AFK)) { + if (Config.Server.AFK_GRACE_ENABLED.get()) { + // AFK -> AFK_GRACE + protectedPlayer.setState(ProtectedPlayer.State.AFK_GRACE); + startGraceTimer(playerUUID, Config.Server.AFK_GRACE_DURATION.get() * 1000); + } else { + // AFK -> ACTIVE + protectedPlayer.setState(ProtectedPlayer.State.ACTIVE); + if (Config.Server.AFK_APPLY_POST_EFFECTS.get()) { + applyPostEffects(playerUUID); + } + if (Config.Server.AFK_PROTECTION_ENABLED.get()) { + startAfkTimer(playerUUID, Config.Server.AFK_TIME_THRESHOLD.get() * 1000); + } else { + removePlayer(playerUUID); + } + } + } + + // Send state packet to player + NetworkManager.getChannel().sendTo(new PlayerStatePacket(protectedPlayer.getState()), + player.connection.getConnection(), + NetworkDirection.PLAY_TO_CLIENT); + } + } + + /** + * Starts the afk timer for a given player if they are a protected player. + * @param playerUUID The UUID of the player. + * @param delay After how much time should the task run? (in milliseconds) + */ + public static void startAfkTimer(UUID playerUUID, long delay) { + ProtectedPlayer player = getPlayer(playerUUID); + if (player == null) { + return; + } + + // Create scheduled task + player.setAfkTimerTask(new TimerTask() { + @Override + public void run() { + try { + // Send request for list input tick packet to player + NetworkManager.getChannel().sendTo(new RequestLastInputTickPacket(), + LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID).connection.getConnection(), + NetworkDirection.PLAY_TO_CLIENT); + } catch (NullPointerException e) { + removePlayer(playerUUID); + } + } + }, delay); + } + + /** + * Starts the grace period timer for a given player if they are a protected player. + * @param playerUUID UUID of the player. + * @param delay How long should the grace period last? (in milliseconds) + */ + private static void startGraceTimer(UUID playerUUID, long delay) { + ProtectedPlayer player = getPlayer(playerUUID); + if (player == null) { + return; + } + + // Create scheduled task + player.setGracePeriodTimerTask(new TimerTask() { + @Override + public void run() { + // Update player state + updateState(playerUUID); + } + }, delay); + } + + /** + * Applies effects to the player. + * @param playerUUID UUID of player to apply effects to. + */ + private static void applyPostEffects(UUID playerUUID) { + // Get player entity + Player player = LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID); + if (player == null) { + removePlayer(playerUUID); + return; + } + + // Apply effects + if (player.isInWater()) { + if (Config.Server.POST_REFILL_AIR_ENABLED.get()) { + player.setAirSupply(player.getMaxAirSupply()); + } + if (Config.Server.POST_WATER_ENABLED.get()) { + player.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, + Config.Server.POST_WATER_DURATION.get() * 20, 0)); + } + } + if (player.isInLava()) { + if (Config.Server.POST_LAVA_ENABLED.get()) { + player.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, + Config.Server.POST_LAVA_DURATION.get()*20, 0)); + } + } + if (player.isOnFire()) { + if (Config.Server.POST_FIRE_ENABLED.get()) { + player.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, + Config.Server.POST_FIRE_DURATION.get()*20, 0)); + } + } + } +} diff --git a/src/main/java/dev/micle/loginprotection/events/client/OnClientInputEventHandler.java b/src/main/java/dev/micle/loginprotection/events/client/OnClientInputEventHandler.java new file mode 100644 index 0000000..bd55fb8 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/events/client/OnClientInputEventHandler.java @@ -0,0 +1,345 @@ +package dev.micle.loginprotection.events.client; + +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.network.NetworkManager; +import dev.micle.loginprotection.network.client.InputPacket; +import dev.micle.loginprotection.proxy.Proxy; +import dev.micle.loginprotection.setup.Config; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.gui.screens.PauseScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.InventoryScreen; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.InputEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.lwjgl.glfw.GLFW; + +import java.util.Arrays; +import java.util.List; + +@OnlyIn(Dist.CLIENT) +public class OnClientInputEventHandler { + private static boolean isPausePressed = false; + private static boolean isDebugPressed = false; + private static boolean isFullscreenPressed = false; + private static boolean isTogglePerspectivePressed = false; + private static boolean isSmoothCameraPressed = false; + private static boolean isScreenshotPressed = false; + private static boolean isSpectatorOutlinesPressed = false; + private static boolean isAdvancementsPressed = false; + private static boolean isPlayerListPressed = false; + private static boolean isChatPressed = false; + private static boolean isChatCommandPressed = false; + private static boolean isChatEnterPressed = false; + private static boolean isSocialInteractionsPressed = false; + private static boolean isLoadHotbarActivatorPressed = false; + private static boolean isSaveHotbarActivatorPressed = false; + private static boolean isSwapOffhandPressed = false; + private static boolean isInventoryPressed = false; + private static boolean isDropItemPressed = false; + private static boolean isUseItemPressed = false; + private static boolean isPickBlockPressed = false; + private static boolean isAttackPressed = false; + private static boolean isMoveUpPressed = false; + private static boolean isMoveRightPressed = false; + private static boolean isMoveDownPressed = false; + private static boolean isMoveLeftPressed = false; + private static boolean isMoveSprintPressed = false; + private static boolean isSneakPressed = false; + private static boolean isJumpPressed = false; + private static final Boolean[] isHotBarPressed = new Boolean[9]; + + private static Screen previousScreen = null; + + public OnClientInputEventHandler() { + // Initialize isHotBarPressed to false + Arrays.fill(isHotBarPressed, false); + } + + @SubscribeEvent + public void KeyInputEvent(InputEvent.KeyInputEvent event) { + handle(event.getAction(), event.getKey()); + } + + @SubscribeEvent + public void MouseInputEvent(InputEvent.MouseInputEvent event) { + handle(event.getAction(), event.getButton()); + } + + private static void handle(int action, int key) { + // Initialize variables + Minecraft minecraft = Minecraft.getInstance(); + + // Cancel event if not in a game + if ((minecraft.getCurrentServer() == null && minecraft.getSingleplayerServer() == null) || + Proxy.Client.getPlayerState() == null) { + return; + } + + // Check if input is a press or release + if (action == GLFW.GLFW_PRESS || action == GLFW.GLFW_RELEASE) { + // Initialize variables + Options keyBinds = minecraft.options; + boolean isPressed = action == GLFW.GLFW_PRESS; + List allowedKeys = (Proxy.Client.getPlayerState().equals(ProtectedPlayer.State.JOINING)) ? + (List) Config.Server.LOGIN_KEY_ALLOW_LIST.get() : + (List) Config.Server.AFK_KEY_ALLOW_LIST.get(); + + // Check if the key is monitored, save it's state, update last input tick and notify server if not allowed + if (key == GLFW.GLFW_KEY_ESCAPE) { + isPausePressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.PAUSE.toString())) { + updateAndNotify(); + } + } else if (key == GLFW.GLFW_KEY_F3) { + isDebugPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.DEBUG.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyFullscreen.getKey().getValue()) { + isFullscreenPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.FULLSCREEN.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyTogglePerspective.getKey().getValue()) { + isTogglePerspectivePressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.PERSPECTIVE.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keySmoothCamera.getKey().getValue()) { + isSmoothCameraPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SMOOTH_CAMERA.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyScreenshot.getKey().getValue()) { + isScreenshotPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SCREENSHOT.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keySpectatorOutlines.getKey().getValue()) { + isSpectatorOutlinesPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SPECTATOR_OUTLINES.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyAdvancements.getKey().getValue()) { + isAdvancementsPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.ADVANCEMENTS.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyPlayerList.getKey().getValue()) { + isPlayerListPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.PLAYER_LIST.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyChat.getKey().getValue()) { + isChatPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.CHAT.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyCommand.getKey().getValue()) { + isChatCommandPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.CHAT.toString())) { + updateAndNotify(); + } + } else if (key == GLFW.GLFW_KEY_ENTER) { + isChatEnterPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.CHAT.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keySocialInteractions.getKey().getValue()) { + isSocialInteractionsPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SOCIAL_INTERACTIONS.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyLoadHotbarActivator.getKey().getValue()) { + isLoadHotbarActivatorPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.LOAD_HOTBAR_ACTIVATOR.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keySaveHotbarActivator.getKey().getValue()) { + isSaveHotbarActivatorPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SAVE_HOTBAR_ACTIVATOR.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keySwapOffhand.getKey().getValue()) { + isSwapOffhandPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SWAP_ITEM.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyInventory.getKey().getValue()) { + isInventoryPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.INVENTORY.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyDrop.getKey().getValue()) { + isDropItemPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.DROP_ITEM.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyUse.getKey().getValue()) { + isUseItemPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.USE_ITEM.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyPickItem.getKey().getValue()) { + isPickBlockPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.PICK_BLOCK.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyAttack.getKey().getValue()) { + isAttackPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.ATTACK.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyUp.getKey().getValue()) { + isMoveUpPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.MOVE.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyRight.getKey().getValue()) { + isMoveRightPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.MOVE.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyDown.getKey().getValue()) { + isMoveDownPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.MOVE.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyLeft.getKey().getValue()) { + isMoveLeftPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.MOVE.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keySprint.getKey().getValue()) { + isMoveSprintPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.MOVE.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyShift.getKey().getValue()) { + isSneakPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.SNEAK.toString())) { + updateAndNotify(); + } + } else if (key == keyBinds.keyJump.getKey().getValue()) { + isJumpPressed = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.JUMP.toString())) { + updateAndNotify(); + } + } else { + for (int i = 0; i < isHotBarPressed.length; i++) { + if (key == keyBinds.keyHotbarSlots[i].getKey().getValue()) { + isHotBarPressed[i] = isPressed; + if (!checkIfScreenIsAllowed(allowedKeys, minecraft.screen, !isPressed) && + !allowedKeys.contains(Config.Server.KEYS.HOTBAR.toString())) { + updateAndNotify(); + } + } + } + } + } + } + + private static boolean checkIfScreenIsAllowed(List allowedKeys, Screen screen, boolean isActionReleased) { + // Initialize variables + boolean isAllowed; + + // Check if the previous screen or the current screen are significant and their respective keys are allowed + if (previousScreen instanceof PauseScreen || screen instanceof PauseScreen) { + isAllowed = allowedKeys.contains(Config.Server.KEYS.PAUSE.toString()); + } else if (previousScreen instanceof InventoryScreen || screen instanceof InventoryScreen) { + isAllowed = allowedKeys.contains(Config.Server.KEYS.INVENTORY.toString()); + } else if (previousScreen instanceof ChatScreen || screen instanceof ChatScreen) { + isAllowed = allowedKeys.contains(Config.Server.KEYS.CHAT.toString()); + } else { + // Allow any other screen that is not the game itself + isAllowed = screen != null; + } + + // Only update the previous screen when key is released + if (isActionReleased) { + previousScreen = screen; + } + + // Return value + return isAllowed; + } + + private static void updateAndNotify() { + // Update last input tick + Proxy.Client.updateLastInputTick(); + + // Send an input packet if player is joining or is afk + if (Proxy.Client.getPlayerState().equals(ProtectedPlayer.State.JOINING) || + Proxy.Client.getPlayerState().equals(ProtectedPlayer.State.AFK)) { + NetworkManager.getChannel().sendToServer(new InputPacket()); + } + } + + public static void checkIfInputIsAllowed() { + // Get list of allowed keys + List allowedKeys = (Proxy.Client.getPlayerState().equals(ProtectedPlayer.State.JOINING)) ? + (List) Config.Server.LOGIN_KEY_ALLOW_LIST.get() : + (List) Config.Server.AFK_KEY_ALLOW_LIST.get(); + + // Update last input tick and notify server if any of the monitored keys are pressed and not allowed + if ((isPausePressed && !allowedKeys.contains(Config.Server.KEYS.PAUSE.toString())) || + (isDebugPressed && !allowedKeys.contains(Config.Server.KEYS.DEBUG.toString())) || + (isFullscreenPressed && !allowedKeys.contains(Config.Server.KEYS.FULLSCREEN.toString())) || + (isTogglePerspectivePressed && !allowedKeys.contains(Config.Server.KEYS.PERSPECTIVE.toString())) || + (isSmoothCameraPressed && !allowedKeys.contains(Config.Server.KEYS.SMOOTH_CAMERA.toString())) || + (isScreenshotPressed && !allowedKeys.contains(Config.Server.KEYS.SCREENSHOT.toString())) || + (isSpectatorOutlinesPressed && !allowedKeys.contains(Config.Server.KEYS.SPECTATOR_OUTLINES.toString())) || + (isAdvancementsPressed && !allowedKeys.contains(Config.Server.KEYS.ADVANCEMENTS.toString())) || + (isPlayerListPressed && !allowedKeys.contains(Config.Server.KEYS.PLAYER_LIST.toString())) || + ((isChatPressed || isChatCommandPressed || isChatEnterPressed) && + !allowedKeys.contains(Config.Server.KEYS.CHAT.toString())) || + (isSocialInteractionsPressed && !allowedKeys.contains(Config.Server.KEYS.SOCIAL_INTERACTIONS.toString())) || + (isLoadHotbarActivatorPressed && !allowedKeys.contains(Config.Server.KEYS.LOAD_HOTBAR_ACTIVATOR.toString())) || + (isSaveHotbarActivatorPressed && !allowedKeys.contains(Config.Server.KEYS.SAVE_HOTBAR_ACTIVATOR.toString())) || + (isSwapOffhandPressed && !allowedKeys.contains(Config.Server.KEYS.SWAP_ITEM.toString())) || + (isInventoryPressed && !allowedKeys.contains(Config.Server.KEYS.INVENTORY.toString())) || + (isDropItemPressed && !allowedKeys.contains(Config.Server.KEYS.DROP_ITEM.toString())) || + (isUseItemPressed && !allowedKeys.contains(Config.Server.KEYS.USE_ITEM.toString())) || + (isPickBlockPressed && !allowedKeys.contains(Config.Server.KEYS.PICK_BLOCK.toString())) || + (isAttackPressed && !allowedKeys.contains(Config.Server.KEYS.ATTACK.toString())) || + ((isMoveUpPressed || isMoveRightPressed || isMoveDownPressed || isMoveLeftPressed || isMoveSprintPressed) && + !allowedKeys.contains(Config.Server.KEYS.MOVE.toString())) || + (isSneakPressed && !allowedKeys.contains(Config.Server.KEYS.SNEAK.toString())) || + (isJumpPressed && !allowedKeys.contains(Config.Server.KEYS.JUMP.toString())) || + (Arrays.stream(isHotBarPressed).anyMatch(b -> b) && !allowedKeys.contains(Config.Server.KEYS.HOTBAR.toString()))) { + updateAndNotify(); + } + } +} diff --git a/src/main/java/dev/micle/loginprotection/events/common/OnLivingSetAttackTargetEventHandler.java b/src/main/java/dev/micle/loginprotection/events/common/OnLivingSetAttackTargetEventHandler.java new file mode 100644 index 0000000..8665116 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/events/common/OnLivingSetAttackTargetEventHandler.java @@ -0,0 +1,32 @@ +package dev.micle.loginprotection.events.common; + +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import dev.micle.loginprotection.setup.Config; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class OnLivingSetAttackTargetEventHandler { + @SubscribeEvent + public void LivingSetAttackTargetEvent(LivingSetAttackTargetEvent event) { + if (!(event.getTarget() instanceof Player target)) { + return; + } + + ProtectedPlayer player = ProtectedPlayerManager.getPlayer(target.getUUID()); + if (player == null) { + return; + } + + // Check if mob should ignore player + if ((player.getState().equals(ProtectedPlayer.State.JOINING) && Config.Server.LOGIN_MOBS_IGNORE_PLAYER.get()) || + (player.getState().equals(ProtectedPlayer.State.AFK) && Config.Server.AFK_MOBS_IGNORE_PLAYER.get()) || + (player.getState().equals(ProtectedPlayer.State.LOGIN_GRACE) && Config.Server.LOGIN_GRACE_MOBS_IGNORE_PLAYER.get()) || + (player.getState().equals(ProtectedPlayer.State.AFK_GRACE) && Config.Server.AFK_GRACE_MOBS_IGNORE_PLAYER.get())) { + ((Mob) event.getEntityLiving()).setTarget(null); + } + } + +} diff --git a/src/main/java/dev/micle/loginprotection/events/common/OnPlayerDamageEventHandler.java b/src/main/java/dev/micle/loginprotection/events/common/OnPlayerDamageEventHandler.java new file mode 100644 index 0000000..909a06f --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/events/common/OnPlayerDamageEventHandler.java @@ -0,0 +1,23 @@ +package dev.micle.loginprotection.events.common; + +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.entity.living.LivingDamageEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class OnPlayerDamageEventHandler { + @SubscribeEvent + public void LivingDamageEvent(LivingDamageEvent event) { + if (!(event.getEntity() instanceof Player player)) { + return; + } + + ProtectedPlayer protectedPlayer = ProtectedPlayerManager.getPlayer(player.getUUID()); + if (protectedPlayer == null || protectedPlayer.getState().equals(ProtectedPlayer.State.ACTIVE)) { + return; + } + + event.setCanceled(true); + } +} diff --git a/src/main/java/dev/micle/loginprotection/events/common/OnPlayerJoinEventHandler.java b/src/main/java/dev/micle/loginprotection/events/common/OnPlayerJoinEventHandler.java new file mode 100644 index 0000000..80a27fa --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/events/common/OnPlayerJoinEventHandler.java @@ -0,0 +1,17 @@ +package dev.micle.loginprotection.events.common; + +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class OnPlayerJoinEventHandler { + @SubscribeEvent + public void EntityJoinWorldEvent(PlayerEvent.PlayerLoggedInEvent event) { + if (!(event.getEntity() instanceof Player player)) { + return; + } + + ProtectedPlayerManager.addPlayer(player.getUUID()); + } +} diff --git a/src/main/java/dev/micle/loginprotection/events/common/OnPlayerLeaveEventHandler.java b/src/main/java/dev/micle/loginprotection/events/common/OnPlayerLeaveEventHandler.java new file mode 100644 index 0000000..7626d41 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/events/common/OnPlayerLeaveEventHandler.java @@ -0,0 +1,17 @@ +package dev.micle.loginprotection.events.common; + +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class OnPlayerLeaveEventHandler { + @SubscribeEvent + public void PlayerLeaveEvent(PlayerEvent.PlayerLoggedOutEvent event) { + if (!(event.getEntity() instanceof Player player)) { + return; + } + + ProtectedPlayerManager.removePlayer(player.getUUID()); + } +} diff --git a/src/main/java/dev/micle/loginprotection/mixin/GuiRenderTickMixin.java b/src/main/java/dev/micle/loginprotection/mixin/GuiRenderTickMixin.java new file mode 100644 index 0000000..04901f0 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/mixin/GuiRenderTickMixin.java @@ -0,0 +1,50 @@ +package dev.micle.loginprotection.mixin; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.proxy.Proxy; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.gui.ForgeIngameGui; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ForgeIngameGui.class) +@OnlyIn(Dist.CLIENT) +public class GuiRenderTickMixin { + @Inject(method = "render", at = @At(value = "HEAD")) + private void onClientTick(PoseStack poseStack, float partialTicks, CallbackInfo callbackInfo) { + // Setup + poseStack.pushPose(); + poseStack.translate(Minecraft.getInstance().getWindow().getGuiScaledWidth() / 2.0, + Minecraft.getInstance().getWindow().getGuiScaledHeight() / 2.0, 0); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + + // Render player state text + poseStack.pushPose(); + poseStack.scale(5, 5, 5); + + // Check if we should draw the state + if (Proxy.Client.getPlayerState() != null && !Proxy.Client.getPlayerState().equals(ProtectedPlayer.State.ACTIVE)) { + // Initialize variables + Font font = Minecraft.getInstance().font; + float offsetX = -(font.width(Proxy.Client.getPlayerState().toString()) / 2f); + float offsetY = -((font.lineHeight / 2f) + ((Minecraft.getInstance().getWindow().getGuiScaledHeight() / 4f) / 5)); + int argb = 0xFFFFFFFF; + + // Draw the player's protection state + font.drawShadow(poseStack, Proxy.Client.getPlayerState().toString().replace("_", " "), offsetX, offsetY, argb); + } + poseStack.popPose(); + + // Finish + RenderSystem.disableBlend(); + poseStack.popPose(); + } +} diff --git a/src/main/java/dev/micle/loginprotection/network/NetworkManager.java b/src/main/java/dev/micle/loginprotection/network/NetworkManager.java new file mode 100644 index 0000000..cca8353 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/network/NetworkManager.java @@ -0,0 +1,49 @@ +package dev.micle.loginprotection.network; + +import dev.micle.loginprotection.LoginProtection; +import dev.micle.loginprotection.network.client.InputPacket; +import dev.micle.loginprotection.network.client.LastInputTickPacket; +import dev.micle.loginprotection.network.server.PlayerStatePacket; +import dev.micle.loginprotection.network.server.RequestLastInputTickPacket; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +public class NetworkManager { + private static SimpleChannel channel; + + public static void init() { + // Create channel + channel = NetworkRegistry.ChannelBuilder.named(LoginProtection.createResourceLocation("network")) + .clientAcceptedVersions(v -> v.equals(LoginProtection.getVersion())) + .serverAcceptedVersions(v -> v.equals(LoginProtection.getVersion())) + .networkProtocolVersion(LoginProtection::getVersion) + .simpleChannel(); + + // Register packets + int id = 0; + channel.messageBuilder(InputPacket.class, id++) + .encoder(InputPacket::encode) + .decoder(InputPacket::decode) + .consumer(InputPacket::handle) + .add(); + channel.messageBuilder(PlayerStatePacket.class, id++) + .encoder(PlayerStatePacket::encode) + .decoder(PlayerStatePacket::decode) + .consumer(PlayerStatePacket::handle) + .add(); + channel.messageBuilder(RequestLastInputTickPacket.class, id++) + .encoder(RequestLastInputTickPacket::encode) + .decoder(RequestLastInputTickPacket::decode) + .consumer(RequestLastInputTickPacket::handle) + .add(); + channel.messageBuilder(LastInputTickPacket.class, id++) + .encoder(LastInputTickPacket::encode) + .decoder(LastInputTickPacket::decode) + .consumer(LastInputTickPacket::handle) + .add(); + } + + public static SimpleChannel getChannel() { + return channel; + } +} diff --git a/src/main/java/dev/micle/loginprotection/network/client/InputPacket.java b/src/main/java/dev/micle/loginprotection/network/client/InputPacket.java new file mode 100644 index 0000000..b2f621c --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/network/client/InputPacket.java @@ -0,0 +1,41 @@ +package dev.micle.loginprotection.network.client; + +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class InputPacket { + public static void encode(final InputPacket packet, final FriendlyByteBuf buffer) {} + + public static InputPacket decode(final FriendlyByteBuf buffer) { + return new InputPacket(); + } + + public static void handle(final InputPacket packet, final Supplier contextSupplier) { + final NetworkEvent.Context context = contextSupplier.get(); + context.enqueueWork(() -> { + // Get sender + ServerPlayer sender = context.getSender(); + if (sender == null) { + return; + } + + // Get protected player + ProtectedPlayer protectedPlayer = ProtectedPlayerManager.getPlayer(sender.getUUID()); + if (protectedPlayer == null) { + return; + } + + // Update player state if they are joining or afk + if (protectedPlayer.getState().equals(ProtectedPlayer.State.JOINING) || + protectedPlayer.getState().equals(ProtectedPlayer.State.AFK)) { + ProtectedPlayerManager.updateState(protectedPlayer.getPlayerUUID()); + } + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/dev/micle/loginprotection/network/client/LastInputTickPacket.java b/src/main/java/dev/micle/loginprotection/network/client/LastInputTickPacket.java new file mode 100644 index 0000000..3126762 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/network/client/LastInputTickPacket.java @@ -0,0 +1,56 @@ +package dev.micle.loginprotection.network.client; + +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import dev.micle.loginprotection.proxy.Proxy; +import dev.micle.loginprotection.setup.Config; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class LastInputTickPacket { + private final int lastInputTick; + + public LastInputTickPacket() { + this(Proxy.Client.getLastInputTick()); + } + public LastInputTickPacket(int lastInputTick) { + this.lastInputTick = lastInputTick; + } + + public static void encode(final LastInputTickPacket packet, final FriendlyByteBuf buffer) { + buffer.writeInt(packet.lastInputTick); + } + + public static LastInputTickPacket decode(final FriendlyByteBuf buffer) { + return new LastInputTickPacket(buffer.readInt()); + } + + public static void handle(final LastInputTickPacket packet, final Supplier contextSupplier) { + final NetworkEvent.Context context = contextSupplier.get(); + context.enqueueWork(() -> { + // Get sender + ServerPlayer sender = context.getSender(); + if (sender == null) { + return; + } + + // Get protected player + ProtectedPlayer protectedPlayer = ProtectedPlayerManager.getPlayer(sender.getUUID()); + if (protectedPlayer == null) { + return; + } + + // Check if player is afk + if (sender.tickCount - packet.lastInputTick >= Config.Server.AFK_TIME_THRESHOLD.get() * 20) { + ProtectedPlayerManager.updateState(sender.getUUID()); // Update state + } else { + ProtectedPlayerManager.startAfkTimer(sender.getUUID(), (long) ((Config.Server.AFK_TIME_THRESHOLD.get() - + ((sender.tickCount - packet.lastInputTick) / 20.0)) * 1000)); // Start new afk timer + } + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/dev/micle/loginprotection/network/server/PlayerStatePacket.java b/src/main/java/dev/micle/loginprotection/network/server/PlayerStatePacket.java new file mode 100644 index 0000000..334c00d --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/network/server/PlayerStatePacket.java @@ -0,0 +1,32 @@ +package dev.micle.loginprotection.network.server; + +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.proxy.Proxy; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class PlayerStatePacket { + private final ProtectedPlayer.State state; + + public PlayerStatePacket(ProtectedPlayer.State state) { + this.state = state; + } + + public static void encode(final PlayerStatePacket packet, final FriendlyByteBuf buffer) { + buffer.writeUtf(packet.state.toString()); + } + + public static PlayerStatePacket decode(final FriendlyByteBuf buffer) { + return new PlayerStatePacket(ProtectedPlayer.State.valueOf(buffer.readUtf())); + } + + public static void handle(final PlayerStatePacket packet, final Supplier contextSupplier) { + final NetworkEvent.Context context = contextSupplier.get(); + context.enqueueWork(() -> { + Proxy.Client.setPlayerState(packet.state); + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/dev/micle/loginprotection/network/server/RequestLastInputTickPacket.java b/src/main/java/dev/micle/loginprotection/network/server/RequestLastInputTickPacket.java new file mode 100644 index 0000000..f7974d2 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/network/server/RequestLastInputTickPacket.java @@ -0,0 +1,30 @@ +package dev.micle.loginprotection.network.server; + +import dev.micle.loginprotection.events.client.OnClientInputEventHandler; +import dev.micle.loginprotection.network.NetworkManager; +import dev.micle.loginprotection.network.client.LastInputTickPacket; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class RequestLastInputTickPacket { + public static void encode(final RequestLastInputTickPacket packet, final FriendlyByteBuf buffer) { + } + + public static RequestLastInputTickPacket decode(final FriendlyByteBuf buffer) { + return new RequestLastInputTickPacket(); + } + + public static void handle(final RequestLastInputTickPacket packet, final Supplier contextSupplier) { + final NetworkEvent.Context context = contextSupplier.get(); + context.enqueueWork(() -> { + // Update last input tick (This works around GLFW forgetting about a repeat key after another one is pressed) + OnClientInputEventHandler.checkIfInputIsAllowed(); + + // Send last input tick packet back to server + NetworkManager.getChannel().sendToServer(new LastInputTickPacket()); + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/dev/micle/loginprotection/proxy/IProxy.java b/src/main/java/dev/micle/loginprotection/proxy/IProxy.java new file mode 100644 index 0000000..3842dd0 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/proxy/IProxy.java @@ -0,0 +1,11 @@ +package dev.micle.loginprotection.proxy; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +public interface IProxy { + MinecraftServer getServer(); + Player getClientPlayer(); + Level getClientWorld(); +} diff --git a/src/main/java/dev/micle/loginprotection/proxy/Proxy.java b/src/main/java/dev/micle/loginprotection/proxy/Proxy.java new file mode 100644 index 0000000..d1d0440 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/proxy/Proxy.java @@ -0,0 +1,145 @@ +package dev.micle.loginprotection.proxy; + +import dev.micle.loginprotection.LoginProtection; +import dev.micle.loginprotection.data.ProtectedPlayer; +import dev.micle.loginprotection.data.ProtectedPlayerManager; +import dev.micle.loginprotection.events.client.OnClientInputEventHandler; +import dev.micle.loginprotection.events.common.OnLivingSetAttackTargetEventHandler; +import dev.micle.loginprotection.events.common.OnPlayerDamageEventHandler; +import dev.micle.loginprotection.events.common.OnPlayerJoinEventHandler; +import dev.micle.loginprotection.events.common.OnPlayerLeaveEventHandler; +import dev.micle.loginprotection.network.NetworkManager; +import dev.micle.loginprotection.setup.Config; +import net.minecraft.client.Minecraft; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.AddReloadListenerEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.event.lifecycle.*; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; + +public class Proxy implements IProxy { + // Initialize variables + private static MinecraftServer server = null; + + // Common setup + public Proxy() { + // Initialize setup + Config.init(); + NetworkManager.init(); + + // Register mod event bus listeners + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + modEventBus.addListener(Proxy::setup); + modEventBus.addListener(Proxy::imcEnqueue); + modEventBus.addListener(Proxy::imcProcess); + + // Register event but listeners + MinecraftForge.EVENT_BUS.addListener(Proxy::onAddReloadListeners); + MinecraftForge.EVENT_BUS.addListener(Proxy::serverStarted); + MinecraftForge.EVENT_BUS.addListener(Proxy::serverStopping); + + // Register event handlers + MinecraftForge.EVENT_BUS.register(new OnLivingSetAttackTargetEventHandler()); + MinecraftForge.EVENT_BUS.register(new OnPlayerDamageEventHandler()); + MinecraftForge.EVENT_BUS.register(new OnPlayerJoinEventHandler()); + MinecraftForge.EVENT_BUS.register(new OnPlayerLeaveEventHandler()); + } + + private static void setup(FMLCommonSetupEvent event) {} + + private static void imcEnqueue(InterModEnqueueEvent event) {} + + private static void imcProcess(InterModProcessEvent event) {} + + private static void onAddReloadListeners(AddReloadListenerEvent event) {} + + private static void serverStarted(ServerStartedEvent event) { + ProtectedPlayerManager.init(); + server = event.getServer(); + } + + private static void serverStopping(ServerStoppingEvent event) { + server = null; + } + + @Override + public MinecraftServer getServer() { + return server; + } + + @Override + public Player getClientPlayer() { + return null; + } + + @Override + public Level getClientWorld() { + return null; + } + + // Client setup + public static class Client extends Proxy { + private static ProtectedPlayer.State playerState; + private static int lastInputTick; + + public Client() { + // Register mod event bus listeners + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + modEventBus.addListener(Client::setup); + modEventBus.addListener(Client::postSetup); + + // Register event handlers + MinecraftForge.EVENT_BUS.register(new OnClientInputEventHandler()); + } + + private static void setup(FMLClientSetupEvent event) {} + + private static void postSetup(FMLLoadCompleteEvent event) {} + + public static ProtectedPlayer.State getPlayerState() { + return playerState; + } + + public static void setPlayerState(ProtectedPlayer.State newPlayerState) { + playerState = newPlayerState; + } + + public static int getLastInputTick() { + return lastInputTick; + } + + public static void updateLastInputTick() { + lastInputTick = LoginProtection.getProxy().getClientPlayer().tickCount; + } + + @Override + @OnlyIn(Dist.CLIENT) + public Player getClientPlayer() { + return Minecraft.getInstance().player; + } + + @Override + @OnlyIn(Dist.CLIENT) + public Level getClientWorld() { + return Minecraft.getInstance().level; + } + } + + // Server setup + public static class Server extends Proxy { + public Server() { + // Register mod event bus listeners + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + modEventBus.addListener(Server::setup); + } + + private static void setup(FMLDedicatedServerSetupEvent event) {} + } +} diff --git a/src/main/java/dev/micle/loginprotection/setup/Config.java b/src/main/java/dev/micle/loginprotection/setup/Config.java new file mode 100644 index 0000000..933bb57 --- /dev/null +++ b/src/main/java/dev/micle/loginprotection/setup/Config.java @@ -0,0 +1,174 @@ +package dev.micle.loginprotection.setup; + +import com.google.common.collect.Lists; +import dev.micle.loginprotection.LoginProtection; +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; + +@Mod.EventBusSubscriber(modid = LoginProtection.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public final class Config { + public static final Server SERVER; + public static final ForgeConfigSpec SERVER_SPEC; + static { + Pair spec_pair = new ForgeConfigSpec.Builder().configure(Server::new); + SERVER = spec_pair.getLeft(); + SERVER_SPEC = spec_pair.getRight(); + } + + public static void init() { + ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, SERVER_SPEC); + } + + public static class Server { + public static ForgeConfigSpec.BooleanValue POST_REFILL_AIR_ENABLED; + public static ForgeConfigSpec.BooleanValue POST_WATER_ENABLED; + public static ForgeConfigSpec.IntValue POST_WATER_DURATION; + public static ForgeConfigSpec.BooleanValue POST_LAVA_ENABLED; + public static ForgeConfigSpec.IntValue POST_LAVA_DURATION; + public static ForgeConfigSpec.BooleanValue POST_FIRE_ENABLED; + public static ForgeConfigSpec.IntValue POST_FIRE_DURATION; + + public static ForgeConfigSpec.BooleanValue LOGIN_MOBS_IGNORE_PLAYER; + public static ForgeConfigSpec.BooleanValue LOGIN_APPLY_POST_EFFECTS; + public static ForgeConfigSpec.ConfigValue> LOGIN_KEY_ALLOW_LIST; + public static ForgeConfigSpec.BooleanValue LOGIN_GRACE_ENABLED; + public static ForgeConfigSpec.BooleanValue LOGIN_GRACE_MOBS_IGNORE_PLAYER; + public static ForgeConfigSpec.IntValue LOGIN_GRACE_DURATION; + + public static ForgeConfigSpec.BooleanValue AFK_PROTECTION_ENABLED; + public static ForgeConfigSpec.IntValue AFK_TIME_THRESHOLD; + public static ForgeConfigSpec.BooleanValue AFK_APPLY_POST_EFFECTS; + public static ForgeConfigSpec.BooleanValue AFK_MOBS_IGNORE_PLAYER; + public static ForgeConfigSpec.ConfigValue> AFK_KEY_ALLOW_LIST; + public static ForgeConfigSpec.BooleanValue AFK_GRACE_ENABLED; + public static ForgeConfigSpec.BooleanValue AFK_GRACE_MOBS_IGNORE_PLAYER; + public static ForgeConfigSpec.IntValue AFK_GRACE_DURATION; + + Server(ForgeConfigSpec.Builder builder) { + builder.comment("Settings for protecting players while they are joining.").push("Login"); + LOGIN_APPLY_POST_EFFECTS = builder + .comment("Whether to apply any post protection effects to joining players.") + .define("applyPostProtectionEffects", true); + LOGIN_MOBS_IGNORE_PLAYER = builder + .comment("Whether mobs will ignore a protected player. (They will not attack/aggro)") + .define("mobsIgnorePlayer", true); + builder.push("AllowedKeys"); + LOGIN_KEY_ALLOW_LIST = builder + .comment("Allowed keys players can press without becoming active.\n" + + "Available values: PAUSE, DEBUG, FULLSCREEN, PERSPECTIVE, SMOOTH_CAMERA, SCREENSHOT, SPECTATOR_OUTLINES,\n" + + "ADVANCEMENTS, PLAYER_LIST, CHAT, SOCIAL_INTERACTIONS, LOAD_HOTBAR_ACTIVATOR, SAVE_HOTBAR_ACTIVATOR,\n" + + "SWAP_ITEM, INVENTORY, HOTBAR, DROP_ITEM, USE_ITEM, PICK_BLOCK, ATTACK, MOVE, SNEAK, JUMP") + .defineList("allowedKeys", Lists.newArrayList(KEYS.PAUSE.toString(), KEYS.DEBUG.toString(), KEYS.FULLSCREEN.toString(), KEYS.PERSPECTIVE.toString(), KEYS.SMOOTH_CAMERA.toString(), + KEYS.SCREENSHOT.toString(), KEYS.SPECTATOR_OUTLINES.toString(), KEYS.ADVANCEMENTS.toString(), KEYS.PLAYER_LIST.toString(), KEYS.CHAT.toString(), KEYS.SOCIAL_INTERACTIONS.toString(), + KEYS.LOAD_HOTBAR_ACTIVATOR.toString(), KEYS.SAVE_HOTBAR_ACTIVATOR.toString(), KEYS.SWAP_ITEM.toString(), KEYS.HOTBAR.toString(), KEYS.PICK_BLOCK.toString()), o -> o instanceof String); + builder.pop(); + builder.push("Grace"); + LOGIN_GRACE_ENABLED = builder + .comment("Whether a player receives a grace period after becoming active or not.") + .define("graceEnabled", true); + LOGIN_GRACE_MOBS_IGNORE_PLAYER = builder + .comment("Whether mobs ignore the player during their grace period.") + .define("graceMobsIgnorePlayer", true); + LOGIN_GRACE_DURATION = builder + .comment("How long the grace period lasts in seconds.") + .defineInRange("graceDuration", 10, 1, Integer.MAX_VALUE); + builder.pop(); + builder.pop(); + + builder.comment("Settings for protecting players that are afk.").push("AFK"); + AFK_PROTECTION_ENABLED = builder + .comment("Enable protection of afk players?") + .define("enabled", true); + AFK_TIME_THRESHOLD = builder + .comment("How long a player needs to be afk to become protected. (seconds)") + .defineInRange("timeThreshold", 600, 1, Integer.MAX_VALUE/20); + AFK_APPLY_POST_EFFECTS = builder + .comment("Whether to apply any post protection effects to afk players.") + .define("applyPostProtectionEffects", false); + AFK_MOBS_IGNORE_PLAYER = builder + .comment("Whether mobs will ignore a protected player. (They will not attack/aggro") + .define("mobsIgnorePlayerEnabled", true); + builder.push("AllowedKeys"); + AFK_KEY_ALLOW_LIST = builder + .comment("Allowed keys players can press without becoming active.\n" + + "Available values: PAUSE, DEBUG, FULLSCREEN, PERSPECTIVE, SMOOTH_CAMERA, SCREENSHOT, SPECTATOR_OUTLINES,\n" + + "ADVANCEMENTS, PLAYER_LIST, CHAT, SOCIAL_INTERACTIONS, LOAD_HOTBAR_ACTIVATOR, SAVE_HOTBAR_ACTIVATOR,\n" + + "SWAP_ITEM, INVENTORY, HOTBAR, DROP_ITEM, USE_ITEM, PICK_BLOCK, ATTACK, MOVE, SNEAK, JUMP") + .defineList("allowedKeys", Lists.newArrayList(KEYS.PAUSE.toString(), KEYS.FULLSCREEN.toString(), KEYS.SCREENSHOT.toString(), KEYS.ADVANCEMENTS.toString()), o -> o instanceof String); + builder.pop(); + builder.push("Grace"); + AFK_GRACE_ENABLED = builder + .comment("Whether a player receives a grace period after becoming active or not.") + .define("graceEnabled", false); + AFK_GRACE_MOBS_IGNORE_PLAYER = builder + .comment("Whether mobs ignore the player during their grace period.") + .define("graceMobsIgnorePlayer", true); + AFK_GRACE_DURATION = builder + .comment("How long the grace period lasts in seconds.") + .defineInRange("graceDuration", 5, 1, Integer.MAX_VALUE); + builder.pop(); + builder.pop(); + + builder.comment("Additional protection settings that apply as soon as a player becomes active if enabled.").push("Post"); + builder.push("WaterProtection"); + POST_REFILL_AIR_ENABLED = builder + .comment("Whether a player's air supply gets refilled.") + .define("refillAir", true); + POST_WATER_ENABLED = builder + .comment("Whether a player receives water breathing when in water.") + .define("waterEnabled", false); + POST_WATER_DURATION = builder + .comment("Water breathing duration in seconds.") + .defineInRange("waterDuration", 10, 1, Integer.MAX_VALUE/20); + builder.pop(); + builder.push("LavaProtection"); + POST_LAVA_ENABLED = builder + .comment("Whether a player receives fire resistance when in lava.") + .define("enabled", true); + POST_LAVA_DURATION = builder + .comment("Fire resistance duration in seconds.") + .defineInRange("duration", 10, 1, Integer.MAX_VALUE/20); + builder.pop(); + builder.push("FireProtection"); + POST_FIRE_ENABLED = builder + .comment("Whether a player receives fire resistance when on fire.") + .define("enabled", false); + POST_FIRE_DURATION = builder + .comment("Fire resistance duration in seconds.") + .defineInRange("duration", 10, 1, Integer.MAX_VALUE/20); + builder.pop(); + builder.pop(); + } + + public enum KEYS { + PAUSE, + DEBUG, + FULLSCREEN, + PERSPECTIVE, + SMOOTH_CAMERA, + SCREENSHOT, + SPECTATOR_OUTLINES, + ADVANCEMENTS, + PLAYER_LIST, + CHAT, + SOCIAL_INTERACTIONS, + LOAD_HOTBAR_ACTIVATOR, + SAVE_HOTBAR_ACTIVATOR, + SWAP_ITEM, + INVENTORY, + HOTBAR, + DROP_ITEM, + USE_ITEM, + PICK_BLOCK, + ATTACK, + MOVE, + SNEAK, + JUMP + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..72cfd47 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,58 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader = "javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the forge version +loaderVersion = "[40,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license = "All Rights Reserved" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="http://my.issue.tracker/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId = "loginprotection" #mandatory +# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it +# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata +# see the associated build.gradle script for how to populate this completely automatically during a build +version = "${file.jarVersion}" #mandatory +# A display name for the mod +displayName = "Micle's Login Protection" #mandatory +# A URL to query for updates for this mod. See the JSON update specification +#updateJSONURL="http://myurl.me/" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +#displayURL="http://example.com/" #optional +# A file name (in the root of the mod JAR) containing a logo for display +logoFile="logo.jpg" #optional +# A text field displayed in the mod UI +#credits="Thanks for this example mod goes to Java" #optional +# A text field displayed in the mod UI +authors = "Micle" #optional +# The description text for the mod (multi line!) (#mandatory) +description = ''' +Protects players from damage while they are are joining a server or are AFK. +''' +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.loginprotection]] #optional + # the modid of the dependency + modId = "forge" #mandatory + # Does this dependency have to exist - if not, ordering below must be specified + mandatory = true #mandatory + # The version range of the dependency + versionRange = "[40,)" #mandatory + # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory + ordering = "NONE" + # Side this dependency is applied on - BOTH, CLIENT or SERVER + side = "BOTH" +# Here's another dependency +[[dependencies.loginprotection]] + modId = "minecraft" + mandatory = true + # This version range declares a minimum of the current minecraft version up to but not including the next major version + versionRange = "[1.18.2,1.19)" + ordering = "NONE" + side = "BOTH" diff --git a/src/main/resources/loginprotection.mixins.json b/src/main/resources/loginprotection.mixins.json new file mode 100644 index 0000000..df87769 --- /dev/null +++ b/src/main/resources/loginprotection.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.micle.loginprotection.mixin", + "compatibilityLevel": "JAVA_17", + "refmap": "loginprotection.refmap.json", + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + }, + "client": [ + "GuiRenderTickMixin" + ] +} \ No newline at end of file diff --git a/src/main/resources/logo.jpg b/src/main/resources/logo.jpg new file mode 100644 index 0000000..d0094c6 --- /dev/null +++ b/src/main/resources/logo.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e91a8b6ffd2dd7ee429573bee6e0e3c870ab5061eab9103398fd35da99f703eb +size 31912 diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..af37bcf --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "loginprotection resources", + "pack_format": 8 + } +}