diff --git a/src/client/java/AiCompanion/aicompanion2_0/client/AiChatSession.java b/src/client/java/AiCompanion/aicompanion2_0/client/AiChatSession.java index 8df558a..b244660 100644 --- a/src/client/java/AiCompanion/aicompanion2_0/client/AiChatSession.java +++ b/src/client/java/AiCompanion/aicompanion2_0/client/AiChatSession.java @@ -31,6 +31,13 @@ public class AiChatSession { public void sendMessage(String userMessage, Consumer onResponse) { history.add(new String[]{"user", userMessage}); + if (AiCompanionClient.tryTriggerArchEasterEgg(userMessage, response -> { + history.add(new String[]{"assistant", response}); + onResponse.accept(response); + })) { + return; + } + new Thread(() -> { try { String response = callOpenWebUI(); diff --git a/src/client/java/AiCompanion/aicompanion2_0/client/AiCompanionClient.java b/src/client/java/AiCompanion/aicompanion2_0/client/AiCompanionClient.java index 398303c..f4e8666 100644 --- a/src/client/java/AiCompanion/aicompanion2_0/client/AiCompanionClient.java +++ b/src/client/java/AiCompanion/aicompanion2_0/client/AiCompanionClient.java @@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; import net.fabricmc.fabric.api.event.player.UseEntityCallback; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.entity.BipedEntityRenderer; import net.minecraft.client.render.entity.model.EntityModelLayers; @@ -15,10 +16,17 @@ import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.Identifier; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.function.Consumer; + public class AiCompanionClient implements ClientModInitializer { + private static final Identifier DEFAULT_TEXTURE = new Identifier("aicompanion2_0", "textures/entity/skin.png"); + private static final Identifier TUX_TEXTURE = new Identifier("aicompanion2_0", "textures/entity/tux-mc.png"); // One session per world load, shared across all right-clicks private static AiChatSession currentSession = null; + private static final Deque> pendingArchResponses = new ArrayDeque<>(); @Override public void onInitializeClient() { @@ -31,7 +39,7 @@ public class AiCompanionClient implements ClientModInitializer { ) { @Override public Identifier getTexture(AIEntity entity) { - return new Identifier("aicompanion2_0", "textures/entity/skin.png"); + return entity.isTuxMode() ? TUX_TEXTURE : DEFAULT_TEXTURE; } } ); @@ -64,6 +72,11 @@ public class AiCompanionClient implements ClientModInitializer { }); }); + ClientPlayNetworking.registerGlobalReceiver(Aicompanion2_0.ARCH_EASTER_EGG_RESPONSE_PACKET_ID, (client, handler, buf, responseSender) -> { + String response = buf.readString(32767); + client.execute(() -> finishArchEasterEgg(response)); + }); + // Open chat GUI on right-click UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { if (world.isClient() && entity instanceof AIEntity) { @@ -89,6 +102,29 @@ public class AiCompanionClient implements ClientModInitializer { action.run(); } + public static boolean tryTriggerArchEasterEgg(String message, Consumer onResponse) { + if (!isArchEasterEggTrigger(message)) { + return false; + } + + if (!ClientPlayNetworking.canSend(Aicompanion2_0.ARCH_EASTER_EGG_PACKET_ID)) { + onResponse.accept("§cError: The server does not support the Arch easter egg."); + return true; + } + + pendingArchResponses.addLast(onResponse); + + try { + ClientPlayNetworking.send(Aicompanion2_0.ARCH_EASTER_EGG_PACKET_ID, PacketByteBufs.create()); + } catch (RuntimeException e) { + pendingArchResponses.pollLast(); + String error = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + onResponse.accept("§cError: " + error); + } + + return true; + } + private static void askQuestion(MinecraftClient client, String question) { sendChatMessage(client, Text.literal("§6[AI] §fThink about: " + question)); @@ -124,6 +160,20 @@ public class AiCompanionClient implements ClientModInitializer { } } + private static boolean isArchEasterEggTrigger(String message) { + String normalized = message.trim().replaceAll("\\s+", " ").toLowerCase(); + return "i use arch btw".equals(normalized) + || "sudo".equals(normalized) + || normalized.startsWith("sudo "); + } + + private static void finishArchEasterEgg(String response) { + Consumer callback = pendingArchResponses.pollFirst(); + if (callback != null) { + callback.accept(response); + } + } + private static void openChatScreen(MinecraftClient client) { if (currentSession == null) { currentSession = new AiChatSession( diff --git a/src/main/java/AiCompanion/aicompanion2_0/AIEntity.java b/src/main/java/AiCompanion/aicompanion2_0/AIEntity.java index 6bb63ca..2a9c10d 100644 --- a/src/main/java/AiCompanion/aicompanion2_0/AIEntity.java +++ b/src/main/java/AiCompanion/aicompanion2_0/AIEntity.java @@ -5,6 +5,9 @@ import net.minecraft.entity.ai.goal.*; import net.minecraft.entity.ai.pathing.MobNavigation; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.passive.PassiveEntity; import net.minecraft.entity.passive.TameableEntity; @@ -17,6 +20,9 @@ import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; public class AIEntity extends TameableEntity { + private static final TrackedData TUX_MODE = + DataTracker.registerData(AIEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + private int tuxTicksRemaining = 0; public AIEntity(EntityType entityType, World world) { super(entityType, world); @@ -34,6 +40,12 @@ public class AIEntity extends TameableEntity { .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 32.0); } + @Override + protected void initDataTracker() { + super.initDataTracker(); + this.dataTracker.startTracking(TUX_MODE, false); + } + @Override protected void initGoals() { this.goalSelector.add(1, new FollowOwnerGoal(this, 1.0, 5.0f, 2.0f, false)); @@ -42,6 +54,19 @@ public class AIEntity extends TameableEntity { this.goalSelector.add(4, new LookAroundGoal(this)); } + @Override + public void tick() { + super.tick(); + + if (!this.getWorld().isClient() && tuxTicksRemaining > 0) { + tuxTicksRemaining--; + + if (tuxTicksRemaining == 0) { + this.dataTracker.set(TUX_MODE, false); + } + } + } + @Override public boolean damage(DamageSource source, float amount) { // Allow kill command through, block everything else @@ -75,8 +100,17 @@ public class AIEntity extends TameableEntity { return null; } + public void activateTuxMode(int durationTicks) { + tuxTicksRemaining = Math.max(tuxTicksRemaining, durationTicks); + this.dataTracker.set(TUX_MODE, true); + } + + public boolean isTuxMode() { + return this.dataTracker.get(TUX_MODE); + } + // Bridge method fix public EntityView method_48926() { return (EntityView) this.getWorld(); } -} \ No newline at end of file +} diff --git a/src/main/java/AiCompanion/aicompanion2_0/Aicompanion2_0.java b/src/main/java/AiCompanion/aicompanion2_0/Aicompanion2_0.java index 45c5aaf..c5fda63 100644 --- a/src/main/java/AiCompanion/aicompanion2_0/Aicompanion2_0.java +++ b/src/main/java/AiCompanion/aicompanion2_0/Aicompanion2_0.java @@ -9,7 +9,10 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Path; import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; +import java.util.UUID; import com.mojang.brigadier.arguments.StringArgumentType; @@ -22,9 +25,13 @@ import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; import net.minecraft.entity.EntityDimensions; import net.minecraft.entity.EntityType; import net.minecraft.entity.SpawnGroup; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.server.command.CommandManager; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -40,8 +47,14 @@ public class Aicompanion2_0 implements ModInitializer { private static String API_PATH = DEFAULT_API_PATH; private static final Path SHARED_CONFIG_PATH = Path.of("config", "aicompanion2_0.properties"); private static final Path CLIENT_CONFIG_PATH = Path.of("config", "aicompanion2_0_client.properties"); + private static final long ARCH_EASTER_EGG_COOLDOWN_MS = 15L * 60L * 1000L; + private static final int ARCH_EASTER_EGG_BUFF_DURATION_TICKS = 20 * 60; + private static final int ARCH_EASTER_EGG_TUX_DURATION_TICKS = 20 * 10; + private static final Map ARCH_EASTER_EGG_LAST_USED = new HashMap<>(); public static final Identifier QUESTION_PACKET_ID = new Identifier(MOD_ID, "question"); public static final Identifier DELETE_KEY_PACKET_ID = new Identifier(MOD_ID, "delete_key"); + public static final Identifier ARCH_EASTER_EGG_PACKET_ID = new Identifier(MOD_ID, "arch_easter_egg"); + public static final Identifier ARCH_EASTER_EGG_RESPONSE_PACKET_ID = new Identifier(MOD_ID, "arch_easter_egg_response"); public static final EntityType AI_COMPANION = Registry.register( Registries.ENTITY_TYPE, @@ -61,6 +74,9 @@ public class Aicompanion2_0 implements ModInitializer { System.out.println("[" + MOD_ID + "] MOD STARTET!"); FabricDefaultAttributeRegistry.register(AI_COMPANION, AIEntity.createMobAttributes()); + ServerPlayNetworking.registerGlobalReceiver(ARCH_EASTER_EGG_PACKET_ID, (server, player, handler, buf, responseSender) -> + server.execute(() -> sendArchEasterEggResponse(player)) + ); CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { System.out.println("[" + MOD_ID + "] Registriere /ai command"); @@ -350,4 +366,49 @@ public class Aicompanion2_0 implements ModInitializer { } return true; } + + private void sendArchEasterEggResponse(ServerPlayerEntity player) { + if (!ServerPlayNetworking.canSend(player, ARCH_EASTER_EGG_RESPONSE_PACKET_ID)) { + return; + } + + String response = triggerArchEasterEgg(player); + var buf = PacketByteBufs.create(); + buf.writeString(response); + ServerPlayNetworking.send(player, ARCH_EASTER_EGG_RESPONSE_PACKET_ID, buf); + } + + private String triggerArchEasterEgg(ServerPlayerEntity player) { + long now = System.currentTimeMillis(); + Long lastUsed = ARCH_EASTER_EGG_LAST_USED.get(player.getUuid()); + + if (lastUsed != null) { + long remainingMs = ARCH_EASTER_EGG_COOLDOWN_MS - (now - lastUsed); + if (remainingMs > 0) { + return "Optimization already complete. Cooldown remaining: " + formatCooldown(remainingMs) + "."; + } + } + + ARCH_EASTER_EGG_LAST_USED.put(player.getUuid(), now); + player.addStatusEffect(new StatusEffectInstance(StatusEffects.SPEED, ARCH_EASTER_EGG_BUFF_DURATION_TICKS, 1)); + player.addStatusEffect(new StatusEffectInstance(StatusEffects.HASTE, ARCH_EASTER_EGG_BUFF_DURATION_TICKS, 1)); + activateTuxModeForOwnedCompanions(player); + return "Optimization complete. Bloatware removed."; + } + + private void activateTuxModeForOwnedCompanions(ServerPlayerEntity player) { + for (ServerWorld world : player.getServer().getWorlds()) { + for (AIEntity companion : world.getEntitiesByType(AI_COMPANION, entity -> + player.getUuid().equals(entity.getOwnerUuid()))) { + companion.activateTuxMode(ARCH_EASTER_EGG_TUX_DURATION_TICKS); + } + } + } + + private String formatCooldown(long remainingMs) { + long totalSeconds = Math.max(1, (remainingMs + 999) / 1000); + long minutes = totalSeconds / 60; + long seconds = totalSeconds % 60; + return minutes + "m " + seconds + "s"; + } } diff --git a/src/main/resources/assets/aicompanion2_0/textures/entity/tux-mc.png b/src/main/resources/assets/aicompanion2_0/textures/entity/tux-mc.png new file mode 100644 index 0000000..3e9e993 Binary files /dev/null and b/src/main/resources/assets/aicompanion2_0/textures/entity/tux-mc.png differ