diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index 2f9fefe31..dd43238b5 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -19,10 +19,7 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; -import com.lambda.event.events.MovementEvent; -import com.lambda.event.events.PlayerEvent; -import com.lambda.event.events.PlayerPacketEvent; -import com.lambda.event.events.TickEvent; +import com.lambda.event.events.*; import com.lambda.interaction.managers.rotating.Rotation; import com.lambda.interaction.managers.rotating.RotationManager; import com.lambda.module.modules.movement.ElytraFly; @@ -38,6 +35,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.authlib.GameProfile; +import net.minecraft.block.entity.SignBlockEntity; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.input.Input; import net.minecraft.client.network.AbstractClientPlayerEntity; @@ -71,6 +69,13 @@ public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) { super(world, profile); } + @Inject(method = "openEditSignScreen", at = @At("HEAD"), cancellable = true) + private void onOpenEditSignScreen(SignBlockEntity sign, boolean front, CallbackInfo ci) { + if (EventFlow.post(new GuiEvent.SignEditorOpen(sign, front)).isCanceled()) { + ci.cancel(); + } + } + @WrapOperation(method = "move", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V")) private void wrapMove(ClientPlayerEntity instance, MovementType movementType, Vec3d vec3d, Operation original) { EventFlow.post(new MovementEvent.Player.Pre(movementType, vec3d)); diff --git a/src/main/kotlin/com/lambda/event/events/GuiEvent.kt b/src/main/kotlin/com/lambda/event/events/GuiEvent.kt index 669b3df5f..828d5dbb5 100644 --- a/src/main/kotlin/com/lambda/event/events/GuiEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/GuiEvent.kt @@ -18,6 +18,9 @@ package com.lambda.event.events import com.lambda.event.Event +import com.lambda.event.callback.Cancellable +import com.lambda.event.callback.ICancellable +import net.minecraft.block.entity.SignBlockEntity sealed class GuiEvent { /** @@ -33,4 +36,12 @@ sealed class GuiEvent { * By default, the game's framebuffer is bound. */ data object EndFrame : Event + + /** + * Triggered when the sign editor GUI is opened. Can be canceled. + */ + data class SignEditorOpen( + var sign: SignBlockEntity, + var front: Boolean + ) : ICancellable by Cancellable() } diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoSign.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoSign.kt new file mode 100644 index 000000000..9a336963e --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoSign.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.player + +import com.ibm.icu.util.Calendar +import com.lambda.event.events.GuiEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import net.minecraft.block.entity.HangingSignBlockEntity +import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen +import net.minecraft.client.gui.screen.ingame.HangingSignEditScreen +import net.minecraft.client.gui.screen.ingame.SignEditScreen +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket +import java.util.* + + +class AutoSign : Module( + name = "AutoSign", + description = """Auto fills signs with customizable text. Leave lines empty to skip them. Supports data formatting with: + | - Day of month (1-31) + |
- Day of month (01-31) + | - Month (1-12) + | - Month (01-12) + | - Month (short name, e.g., Jan) + | - Month (full name, e.g., January) + | - Year (last two digits, e.g., 26) + | - Year (e.g., 2026) + | - Hour (00-23) + | - Minute (00-59) + | - Second (00-59) + """.trimMargin(), + tag = ModuleTag.PLAYER +) { + var autoWrite by setting("Auto Write", true) + var line1 by setting("Line 1", "Welcome to Lambda!") { autoWrite } + var line2 by setting("Line 2", "Enjoy your stay.") { autoWrite } + var line3 by setting("Line 3", "Have fun!") { autoWrite } + var line4 by setting("Line 4", "Lambda
//") { autoWrite } + var writeOnFront by setting("Write Front", true, description = "Write on front side of the sign") { autoWrite } + + var autoClose by setting("Auto Close", true) + + init { + listen { event -> + val lines = Array(4) { i -> event.component1().frontText.getMessages(false)[i].string } + if (autoWrite) { + var formatLines = arrayOf(line1, line2, line3, line4) + val calendar = Calendar.getInstance() + val month = calendar.get(Calendar.MONTH) + 1 // Months are 0-based in Calendar + + for (i in 0 until 4) { + val formattedLine = formatLines[i] + .replace("
", String.format($$"%1$td", Date())) + .replace("", String.format($$"%1$te", Date())) + .replace("", month.toString()) + .replace("", String.format("%02d", month)) + .replace("", String.format($$"%1$tb", Date())) + .replace("", String.format($$"%1$tB", Date())) + .replace("", String.format($$"%1$ty", Date())) + .replace("", String.format($$"%1$tY", Date())) + .replace("", String.format($$"%1$tH", Date())) + .replace("", String.format($$"%1$tM", Date())) + .replace("", String.format($$"%1$tS", Date())) + + if (formattedLine.isNotEmpty()) lines[i] = String.format(formattedLine, Date()) + } + } + + var editor: AbstractSignEditScreen = if (event.sign is HangingSignBlockEntity) HangingSignEditScreen(event.sign, true, mc.shouldFilterText()) + else SignEditScreen(event.sign, true, mc.shouldFilterText()) + for (i in 0 until 4) editor.messages[i] = lines[i] + if (autoClose) { + if (writeOnFront) mc.networkHandler?.sendPacket(UpdateSignC2SPacket(event.sign.pos, true, editor.messages[0], editor.messages[1], editor.messages[2], editor.messages[3])) + else mc.networkHandler?.sendPacket(UpdateSignC2SPacket(event.sign.pos, false, editor.messages[0], editor.messages[1], editor.messages[2], editor.messages[3])) + } else { + mc.setScreen(editor) + } + event.cancel() + } + } +} \ No newline at end of file diff --git a/src/main/resources/lambda.accesswidener b/src/main/resources/lambda.accesswidener index 484d80718..444487ccb 100644 --- a/src/main/resources/lambda.accesswidener +++ b/src/main/resources/lambda.accesswidener @@ -123,3 +123,6 @@ transitive-accessible method net/minecraft/item/BlockItem getPlacementState (Lne transitive-accessible method net/minecraft/block/AbstractBlock getPickStack (Lnet/minecraft/world/WorldView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)Lnet/minecraft/item/ItemStack; transitive-accessible field net/minecraft/client/gui/screen/ingame/HandledScreen focusedSlot Lnet/minecraft/screen/slot/Slot; transitive-accessible field net/minecraft/registry/SimpleRegistry frozen Z + +# AutoSign / GuiEvent.SignEditorOpen event +accessible field net/minecraft/client/gui/screen/ingame/AbstractSignEditScreen messages [Ljava/lang/String;