Merge pull request #21 from ritonioz/safety-branch

Safety branch
This commit is contained in:
Cametendo
2026-03-18 11:14:52 +01:00
committed by GitHub
5 changed files with 154 additions and 2 deletions

View File

@@ -31,6 +31,13 @@ public class AiChatSession {
public void sendMessage(String userMessage, Consumer<String> 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();

View File

@@ -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<Consumer<String>> 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<String> 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<String> callback = pendingArchResponses.pollFirst();
if (callback != null) {
callback.accept(response);
}
}
private static void openChatScreen(MinecraftClient client) {
if (currentSession == null) {
currentSession = new AiChatSession(

View File

@@ -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<Boolean> TUX_MODE =
DataTracker.registerData(AIEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private int tuxTicksRemaining = 0;
public AIEntity(EntityType<? extends TameableEntity> 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,6 +100,15 @@ 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();

View File

@@ -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<UUID, Long> 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<AIEntity> 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";
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB