Commit 8c3cbca1 by Jonathan Thomas

Merge branch 'whitelist' into 'develop'

Whitelist / Blacklist

See merge request !12
parents 1b5a6302 8e54c9d0
Pipeline #12675 passed with stages
in 2 minutes 23 seconds
...@@ -7,15 +7,22 @@ All notable changes to **CreatureChat** are documented in this file. The format ...@@ -7,15 +7,22 @@ All notable changes to **CreatureChat** are documented in this file. The format
## [Unreleased] ## [Unreleased]
### Added ### Added
- Added UNFLEE behavior (to stop fleeing from a player) - New **whitelist / blacklist** Minecraft **commands**, to show and hide chat bubbles based on entity type
- Added support for non path aware entities to FLEE (i.e. Ghast) - New **S2C packets** to send whitelist / blacklist changes on login and after commands are executed
- Added new LLM tests for UNFLEE - Added **UNFLEE behavior** (to stop fleeing from a player)
- Added support for **non path aware** entities to **FLEE** (i.e. Ghast)
- Added **new LLM tests** for UNFLEE
### Changed ### Changed
- Chat Bubble **rendering** & interacting is now dependent on **whitelist / blacklist** config
- Improved client **render performance** (only query nearby entities every 3rd call)
- Fixed a **crash with FLEE** when non-path aware entities (i.e. Ghast) attempted to flee. - Fixed a **crash with FLEE** when non-path aware entities (i.e. Ghast) attempted to flee.
- Fixed certain behaviors from colliding with others (i.e. mutual exclusive ones) - Updated ATTACK **CHARGE_TIME** to be a little **faster** (when non-native attacks are used)
- Extended **click sounds** to 12 blocks away (from 8)
- Fixed certain **behaviors** from colliding with others (i.e. **mutual exclusive** ones)
- Updated README.md with new video thumbnail, and simplified text, added spoiler to install instructions - Updated README.md with new video thumbnail, and simplified text, added spoiler to install instructions
- Fixed CurseForge deploy script to be much faster, and correctly lookup valid Type and Version IDs - Large **refactor** of Minecraft **commands** (and how --config args are parsed)
- Fixed **CurseForge deploy script** to be much faster, and correctly lookup valid Type and Version IDs
## [1.0.7] - 2024-07-03 ## [1.0.7] - 2024-07-03
......
...@@ -3,6 +3,7 @@ package com.owlmaddie.network; ...@@ -3,6 +3,7 @@ package com.owlmaddie.network;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.ui.BubbleRenderer;
import com.owlmaddie.ui.PlayerMessageManager; import com.owlmaddie.ui.PlayerMessageManager;
import com.owlmaddie.utils.ClientEntityFinder; import com.owlmaddie.utils.ClientEntityFinder;
import com.owlmaddie.utils.Decompression; import com.owlmaddie.utils.Decompression;
...@@ -19,7 +20,9 @@ import org.slf4j.LoggerFactory; ...@@ -19,7 +20,9 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
...@@ -166,6 +169,28 @@ public class ClientPackets { ...@@ -166,6 +169,28 @@ public class ClientPackets {
}); });
}); });
// Client-side packet handler, receive entire whitelist / blacklist, and update BubbleRenderer
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_WHITELIST, (client, handler, buffer, responseSender) -> {
// Read the whitelist data from the buffer
int whitelistSize = buffer.readInt();
List<String> whitelist = new ArrayList<>(whitelistSize);
for (int i = 0; i < whitelistSize; i++) {
whitelist.add(buffer.readString(32767));
}
// Read the blacklist data from the buffer
int blacklistSize = buffer.readInt();
List<String> blacklist = new ArrayList<>(blacklistSize);
for (int i = 0; i < blacklistSize; i++) {
blacklist.add(buffer.readString(32767));
}
client.execute(() -> {
BubbleRenderer.whitelist = whitelist;
BubbleRenderer.blacklist = blacklist;
});
});
// Client-side packet handler, player status sync // Client-side packet handler, player status sync
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_PLAYER_STATUS, (client, handler, buffer, responseSender) -> { ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_PLAYER_STATUS, (client, handler, buffer, responseSender) -> {
// Read the data from the server packet // Read the data from the server packet
...@@ -189,7 +214,7 @@ public class ClientPackets { ...@@ -189,7 +214,7 @@ public class ClientPackets {
private static void playNearbyUISound(MinecraftClient client, Entity player, float maxVolume) { private static void playNearbyUISound(MinecraftClient client, Entity player, float maxVolume) {
// Play sound with volume based on distance // Play sound with volume based on distance
int distance_squared = 64; int distance_squared = 144;
if (client.player != null) { if (client.player != null) {
double distance = client.player.squaredDistanceTo(player.getX(), player.getY(), player.getZ()); double distance = client.player.squaredDistanceTo(player.getX(), player.getY(), player.getZ());
if (distance <= distance_squared) { if (distance <= distance_squared) {
......
...@@ -17,6 +17,7 @@ import net.minecraft.entity.boss.dragon.EnderDragonEntity; ...@@ -17,6 +17,7 @@ import net.minecraft.entity.boss.dragon.EnderDragonEntity;
import net.minecraft.entity.boss.dragon.EnderDragonPart; import net.minecraft.entity.boss.dragon.EnderDragonPart;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
...@@ -27,6 +28,7 @@ import org.joml.Quaternionf; ...@@ -27,6 +28,7 @@ import org.joml.Quaternionf;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -43,6 +45,10 @@ public class BubbleRenderer { ...@@ -43,6 +45,10 @@ public class BubbleRenderer {
public static long lastTick = 0; public static long lastTick = 0;
public static int light = 15728880; public static int light = 15728880;
public static int overlay = OverlayTexture.DEFAULT_UV; public static int overlay = OverlayTexture.DEFAULT_UV;
public static List<String> whitelist = new ArrayList<>();
public static List<String> blacklist = new ArrayList<>();
private static int queryEntityDataCount = 0;
private static List<Entity> relevantEntities;
public static void drawTextBubbleBackground(String base_name, MatrixStack matrices, float x, float y, float width, float height, int friendship) { public static void drawTextBubbleBackground(String base_name, MatrixStack matrices, float x, float y, float width, float height, int friendship) {
// Set shader & texture // Set shader & texture
...@@ -347,17 +353,40 @@ public class BubbleRenderer { ...@@ -347,17 +353,40 @@ public class BubbleRenderer {
// Get camera position // Get camera position
Vec3d interpolatedCameraPos = new Vec3d(camera.getPos().x, camera.getPos().y, camera.getPos().z); Vec3d interpolatedCameraPos = new Vec3d(camera.getPos().x, camera.getPos().y, camera.getPos().z);
// Increment query counter
queryEntityDataCount++;
// This query count helps us cache the list of relevant entities. We can refresh
// the list every 3rd call to this render function
if (queryEntityDataCount % 3 == 0 || relevantEntities == null) {
// Get all entities // Get all entities
List<Entity> nearbyEntities = world.getOtherEntities(null, area); List<Entity> nearbyEntities = world.getOtherEntities(null, area);
// Filter to include only MobEntity & PlayerEntity but exclude any camera 1st person entity and any entities with passengers // Filter to include only MobEntity & PlayerEntity but exclude any camera 1st person entity and any entities with passengers
List<Entity> relevantEntities = nearbyEntities.stream() relevantEntities = nearbyEntities.stream()
.filter(entity -> (entity instanceof MobEntity || entity instanceof PlayerEntity)) .filter(entity -> (entity instanceof MobEntity || entity instanceof PlayerEntity))
.filter(entity -> !entity.hasPassengers()) .filter(entity -> !entity.hasPassengers())
.filter(entity -> !(entity.equals(cameraEntity) && !camera.isThirdPerson())) .filter(entity -> !(entity.equals(cameraEntity) && !camera.isThirdPerson()))
.filter(entity -> !(entity.equals(cameraEntity) && entity.isSpectator())) .filter(entity -> !(entity.equals(cameraEntity) && entity.isSpectator()))
.filter(entity -> {
// Always include PlayerEntity
if (entity instanceof PlayerEntity) {
return true;
}
Identifier entityId = Registries.ENTITY_TYPE.getId(entity.getType());
String entityIdString = entityId.toString();
// Check blacklist first
if (blacklist.contains(entityIdString)) {
return false;
}
// If whitelist is not empty, only include entities in the whitelist
return whitelist.isEmpty() || whitelist.contains(entityIdString);
})
.collect(Collectors.toList()); .collect(Collectors.toList());
queryEntityDataCount = 0;
}
for (Entity entity : relevantEntities) { for (Entity entity : relevantEntities) {
// Push a new matrix onto the stack. // Push a new matrix onto the stack.
......
...@@ -14,6 +14,8 @@ import java.io.Writer; ...@@ -14,6 +14,8 @@ import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/** /**
* The {@code ConfigurationHandler} class loads and saves configuration settings for this mod. It first * The {@code ConfigurationHandler} class loads and saves configuration settings for this mod. It first
...@@ -69,6 +71,8 @@ public class ConfigurationHandler { ...@@ -69,6 +71,8 @@ public class ConfigurationHandler {
private int maxOutputTokens = 200; private int maxOutputTokens = 200;
private double percentOfContext = 0.75; private double percentOfContext = 0.75;
private int timeout = 10; private int timeout = 10;
private List<String> whitelist = new ArrayList<>();
private List<String> blacklist = new ArrayList<>();
// Getters and setters for existing fields // Getters and setters for existing fields
public String getApiKey() { return apiKey; } public String getApiKey() { return apiKey; }
...@@ -77,7 +81,7 @@ public class ConfigurationHandler { ...@@ -77,7 +81,7 @@ public class ConfigurationHandler {
// Update URL if a CreatureChat API key is detected // Update URL if a CreatureChat API key is detected
setUrl("https://api.creaturechat.com/v1/chat/completions"); setUrl("https://api.creaturechat.com/v1/chat/completions");
} else if (apiKey.startsWith("sk-")) { } else if (apiKey.startsWith("sk-")) {
// Update URL if a OpenAI API key is detected // Update URL if an OpenAI API key is detected
setUrl("https://api.openai.com/v1/chat/completions"); setUrl("https://api.openai.com/v1/chat/completions");
} }
this.apiKey = apiKey; this.apiKey = apiKey;
...@@ -92,7 +96,6 @@ public class ConfigurationHandler { ...@@ -92,7 +96,6 @@ public class ConfigurationHandler {
public int getTimeout() { return timeout; } public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; } public void setTimeout(int timeout) { this.timeout = timeout; }
// Getters and setters for new fields
public int getMaxContextTokens() { return maxContextTokens; } public int getMaxContextTokens() { return maxContextTokens; }
public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; } public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; }
...@@ -102,5 +105,10 @@ public class ConfigurationHandler { ...@@ -102,5 +105,10 @@ public class ConfigurationHandler {
public double getPercentOfContext() { return percentOfContext; } public double getPercentOfContext() { return percentOfContext; }
public void setPercentOfContext(double percentOfContext) { this.percentOfContext = percentOfContext; } public void setPercentOfContext(double percentOfContext) { this.percentOfContext = percentOfContext; }
public List<String> getWhitelist() { return whitelist; }
public void setWhitelist(List<String> whitelist) { this.whitelist = whitelist; }
public List<String> getBlacklist() { return blacklist; }
public void setBlacklist(List<String> blacklist) { this.blacklist = blacklist; }
} }
} }
...@@ -5,14 +5,27 @@ import com.mojang.brigadier.arguments.ArgumentType; ...@@ -5,14 +5,27 @@ import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.owlmaddie.network.ServerPackets;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnGroup;
import net.minecraft.registry.Registries;
import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/** /**
* The {@code CreatureChatCommands} class registers custom commands to set new API key, model, and url. * The {@code CreatureChatCommands} class registers custom commands to set new API key, model, and url.
* Permission level set to 4 (server owner), since this deals with API keys and potential costs. * Permission level set to 4 (server owner), since this deals with API keys and potential costs.
...@@ -33,6 +46,8 @@ public class CreatureChatCommands { ...@@ -33,6 +46,8 @@ public class CreatureChatCommands {
.then(registerSetCommand("url", "URL", StringArgumentType.string())) .then(registerSetCommand("url", "URL", StringArgumentType.string()))
.then(registerSetCommand("model", "Model", StringArgumentType.string())) .then(registerSetCommand("model", "Model", StringArgumentType.string()))
.then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer())) .then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer()))
.then(registerWhitelistCommand())
.then(registerBlacklistCommand())
.then(registerHelpCommand())); .then(registerHelpCommand()));
} }
...@@ -41,26 +56,13 @@ public class CreatureChatCommands { ...@@ -41,26 +56,13 @@ public class CreatureChatCommands {
.requires(source -> source.hasPermissionLevel(4)) .requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set") .then(CommandManager.literal("set")
.then(CommandManager.argument("value", valueType) .then(CommandManager.argument("value", valueType)
.then(CommandManager.literal("--config") .then(addConfigArgs((context, useServerConfig) -> {
.then(CommandManager.literal("default")
.executes(context -> {
if (valueType instanceof StringArgumentType) if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription); return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), useServerConfig, settingDescription);
else if (valueType instanceof IntegerArgumentType) else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), false, settingDescription); return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), useServerConfig, settingDescription);
return 1; return 1;
}) }))
)
.then(CommandManager.literal("server")
.executes(context -> {
if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), true, settingDescription);
else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), true, settingDescription);
return 1;
})
)
)
.executes(context -> { .executes(context -> {
if (valueType instanceof StringArgumentType) if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription); return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription);
...@@ -71,6 +73,77 @@ public class CreatureChatCommands { ...@@ -71,6 +73,77 @@ public class CreatureChatCommands {
)); ));
} }
private static List<Identifier> getLivingEntityIds() {
List<Identifier> livingEntityIds = Registries.ENTITY_TYPE.getIds().stream()
.filter(id -> {
EntityType<?> entityType = Registries.ENTITY_TYPE.get(id);
return entityType != null && (entityType.getSpawnGroup() != SpawnGroup.MISC || isIncludedEntity(entityType));
})
.collect(Collectors.toList());
return livingEntityIds;
}
private static boolean isIncludedEntity(EntityType<?> entityType) {
return entityType == EntityType.VILLAGER
|| entityType == EntityType.IRON_GOLEM
|| entityType == EntityType.SNOW_GOLEM;
}
private static List<String> getLivingEntityTypeNames() {
return getLivingEntityIds().stream()
.map(Identifier::toString)
.collect(Collectors.toList());
}
private static LiteralArgumentBuilder<ServerCommandSource> registerWhitelistCommand() {
return CommandManager.literal("whitelist")
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.argument("entityType", IdentifierArgumentType.identifier())
.suggests((context, builder) -> CommandSource.suggestIdentifiers(getLivingEntityIds(), builder))
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "whitelist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), useServerConfig)))
.executes(context -> modifyList(context, "whitelist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), false)))
.then(CommandManager.literal("all")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "whitelist", "all", useServerConfig)))
.executes(context -> modifyList(context, "whitelist", "all", false)))
.then(CommandManager.literal("clear")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "whitelist", "clear", useServerConfig)))
.executes(context -> modifyList(context, "whitelist", "clear", false)));
}
private static LiteralArgumentBuilder<ServerCommandSource> registerBlacklistCommand() {
return CommandManager.literal("blacklist")
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.argument("entityType", IdentifierArgumentType.identifier())
.suggests((context, builder) -> CommandSource.suggestIdentifiers(getLivingEntityIds(), builder))
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "blacklist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), useServerConfig)))
.executes(context -> modifyList(context, "blacklist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), false)))
.then(CommandManager.literal("all")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "blacklist", "all", useServerConfig)))
.executes(context -> modifyList(context, "blacklist", "all", false)))
.then(CommandManager.literal("clear")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "blacklist", "clear", useServerConfig)))
.executes(context -> modifyList(context, "blacklist", "clear", false)));
}
private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() {
return CommandManager.literal("help")
.executes(context -> {
String helpMessage = "Usage of CreatureChat Commands:\n"
+ "/creaturechat key set <key> - Sets the API key\n"
+ "/creaturechat url set \"<url>\" - Sets the URL\n"
+ "/creaturechat model set <model> - Sets the model\n"
+ "/creaturechat timeout set <seconds> - Sets the API timeout\n"
+ "/creaturechat whitelist <entityType | all | clear> - Show chat bubbles\n"
+ "/creaturechat blacklist <entityType | all | clear> - Hide chat bubbles\n"
+ "\n"
+ "Optional: Append [--config default | server] to any command to specify configuration scope.\n"
+ "\n"
+ "Security: Level 4 permission required.";
context.getSource().sendFeedback(() -> Text.literal(helpMessage), false);
return 1;
});
}
private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) { private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) {
ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer()); ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
ConfigurationHandler.Config config = configHandler.loadConfig(); ConfigurationHandler.Config config = configHandler.loadConfig();
...@@ -109,13 +182,11 @@ public class CreatureChatCommands { ...@@ -109,13 +182,11 @@ public class CreatureChatCommands {
Text feedbackMessage; Text feedbackMessage;
if (configHandler.saveConfig(config, useServerConfig)) { if (configHandler.saveConfig(config, useServerConfig)) {
// succeeded
feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN); feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false); source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString()); LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1; return 1;
} else { } else {
// failed
feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED); feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false); source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString()); LOGGER.info("Command executed: " + feedbackMessage.getString());
...@@ -123,20 +194,83 @@ public class CreatureChatCommands { ...@@ -123,20 +194,83 @@ public class CreatureChatCommands {
} }
} }
private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() { private static int modifyList(CommandContext<ServerCommandSource> context, String listName, String action, boolean useServerConfig) {
return CommandManager.literal("help") ServerCommandSource source = context.getSource();
.executes(context -> { ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
String helpMessage = "Usage of CreatureChat Commands:\n" ConfigurationHandler.Config config = configHandler.loadConfig();
+ "/creaturechat key set <key> - Sets the API key\n" List<String> entityTypes = getLivingEntityTypeNames();
+ "/creaturechat url set \"<url>\" - Sets the URL\n"
+ "/creaturechat model set <model> - Sets the model\n" try {
+ "/creaturechat timeout set <seconds> - Sets the API timeout\n" if ("all".equals(action)) {
+ "\n" if ("whitelist".equals(listName)) {
+ "Optional: Append [--config default | server] to any command to specify configuration scope.\n" config.setWhitelist(entityTypes);
+ "\n" config.setBlacklist(new ArrayList<>()); // Clear blacklist
+ "Security: Level 4 permission required."; } else if ("blacklist".equals(listName)) {
context.getSource().sendFeedback(() -> Text.literal(helpMessage), false); config.setBlacklist(entityTypes);
config.setWhitelist(new ArrayList<>()); // Clear whitelist
}
} else if ("clear".equals(action)) {
if ("whitelist".equals(listName)) {
config.setWhitelist(new ArrayList<>());
} else if ("blacklist".equals(listName)) {
config.setBlacklist(new ArrayList<>());
}
} else {
if (!entityTypes.contains(action)) {
throw new IllegalArgumentException("Invalid entity type: " + action);
}
if ("whitelist".equals(listName)) {
List<String> whitelist = new ArrayList<>(config.getWhitelist());
if (!whitelist.contains(action)) {
whitelist.add(action);
config.setWhitelist(whitelist);
}
// Remove from blacklist if present
List<String> blacklist = new ArrayList<>(config.getBlacklist());
blacklist.remove(action);
config.setBlacklist(blacklist);
} else if ("blacklist".equals(listName)) {
List<String> blacklist = new ArrayList<>(config.getBlacklist());
if (!blacklist.contains(action)) {
blacklist.add(action);
config.setBlacklist(blacklist);
}
// Remove from whitelist if present
List<String> whitelist = new ArrayList<>(config.getWhitelist());
whitelist.remove(action);
config.setWhitelist(whitelist);
}
}
} catch (IllegalArgumentException e) {
Text errorMessage = Text.literal(e.getMessage()).formatted(Formatting.RED);
source.sendFeedback(() -> errorMessage, false);
LOGGER.error("Error modifying list: " + e.getMessage(), e);
return 0;
}
if (configHandler.saveConfig(config, useServerConfig)) {
Text feedbackMessage = Text.literal("Successfully updated " + listName + " with " + action).formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false);
// Send whitelist / blacklist to all players
ServerPackets.send_whitelist_blacklist(null);
return 1; return 1;
}); } else {
Text feedbackMessage = Text.literal("Failed to update " + listName).formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false);
return 0;
}
}
private static LiteralArgumentBuilder<ServerCommandSource> addConfigArgs(CommandExecutor executor) {
return CommandManager.literal("--config")
.then(CommandManager.literal("default").executes(context -> executor.run(context, false)))
.then(CommandManager.literal("server").executes(context -> executor.run(context, true)))
.executes(context -> executor.run(context, false));
}
@FunctionalInterface
private interface CommandExecutor {
int run(CommandContext<ServerCommandSource> context, boolean useServerConfig) throws CommandSyntaxException;
} }
} }
...@@ -23,7 +23,7 @@ public class AttackPlayerGoal extends PlayerBaseGoal { ...@@ -23,7 +23,7 @@ public class AttackPlayerGoal extends PlayerBaseGoal {
protected enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING } protected enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING }
protected EntityState currentState = EntityState.IDLE; protected EntityState currentState = EntityState.IDLE;
protected int cooldownTimer = 0; protected int cooldownTimer = 0;
protected final int CHARGE_TIME = 15; // Time before leaping / attacking protected final int CHARGE_TIME = 12; // Time before leaping / attacking
protected final double MOVE_DISTANCE = 200D; // 20 blocks away protected final double MOVE_DISTANCE = 200D; // 20 blocks away
protected final double CHARGE_DISTANCE = 25D; // 5 blocks away protected final double CHARGE_DISTANCE = 25D; // 5 blocks away
protected final double ATTACK_DISTANCE = 4D; // 2 blocks away protected final double ATTACK_DISTANCE = 4D; // 2 blocks away
......
package com.owlmaddie.mixin; package com.owlmaddie.mixin;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.network.ServerPackets; import com.owlmaddie.network.ServerPackets;
import com.owlmaddie.utils.LivingEntityInterface; import com.owlmaddie.utils.LivingEntityInterface;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
...@@ -10,8 +11,10 @@ import net.minecraft.entity.mob.MobEntity; ...@@ -10,8 +11,10 @@ import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.TameableEntity; import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
...@@ -19,6 +22,8 @@ import org.spongepowered.asm.mixin.injection.Inject; ...@@ -19,6 +22,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(LivingEntity.class) @Mixin(LivingEntity.class)
public class MixinLivingEntity implements LivingEntityInterface { public class MixinLivingEntity implements LivingEntityInterface {
......
package com.owlmaddie.mixin; package com.owlmaddie.mixin;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.network.ServerPackets; import com.owlmaddie.network.ServerPackets;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.registry.Registries;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
/** /**
* The {@code MixinMobEntity} mixin class exposes the goalSelector field from the MobEntity class. * The {@code MixinMobEntity} mixin class exposes the goalSelector field from the MobEntity class.
*/ */
......
...@@ -2,6 +2,7 @@ package com.owlmaddie.network; ...@@ -2,6 +2,7 @@ package com.owlmaddie.network;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.ChatDataSaverScheduler; import com.owlmaddie.chat.ChatDataSaverScheduler;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.goals.EntityBehaviorManager; import com.owlmaddie.goals.EntityBehaviorManager;
import com.owlmaddie.goals.GoalPriority; import com.owlmaddie.goals.GoalPriority;
import com.owlmaddie.goals.TalkPlayerGoal; import com.owlmaddie.goals.TalkPlayerGoal;
...@@ -30,6 +31,7 @@ import org.slf4j.Logger; ...@@ -30,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -49,6 +51,7 @@ public class ServerPackets { ...@@ -49,6 +51,7 @@ public class ServerPackets {
public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("creaturechat", "packet_c2s_send_chat"); public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("creaturechat", "packet_c2s_send_chat");
public static final Identifier PACKET_S2C_MESSAGE = new Identifier("creaturechat", "packet_s2c_message"); public static final Identifier PACKET_S2C_MESSAGE = new Identifier("creaturechat", "packet_s2c_message");
public static final Identifier PACKET_S2C_LOGIN = new Identifier("creaturechat", "packet_s2c_login"); public static final Identifier PACKET_S2C_LOGIN = new Identifier("creaturechat", "packet_s2c_login");
public static final Identifier PACKET_S2C_WHITELIST = new Identifier("creaturechat", "packet_s2c_whitelist");
public static final Identifier PACKET_S2C_PLAYER_STATUS = new Identifier("creaturechat", "packet_s2c_player_status"); public static final Identifier PACKET_S2C_PLAYER_STATUS = new Identifier("creaturechat", "packet_s2c_player_status");
public static void register() { public static void register() {
...@@ -160,8 +163,11 @@ public class ServerPackets { ...@@ -160,8 +163,11 @@ public class ServerPackets {
// Data is sent in chunks, to prevent exceeding the 32767 limit per String. // Data is sent in chunks, to prevent exceeding the 32767 limit per String.
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = handler.player; ServerPlayerEntity player = handler.player;
LOGGER.info("Server send compressed, chunked login message packets to player: " + player.getName().getString());
// Send entire whitelist / blacklist to logged in player
send_whitelist_blacklist(player);
LOGGER.info("Server send compressed, chunked login message packets to player: " + player.getName().getString());
// Get lite JSON data & compress to byte array // Get lite JSON data & compress to byte array
String chatDataJSON = ChatDataManager.getServerInstance().GetLightChatData(); String chatDataJSON = ChatDataManager.getServerInstance().GetLightChatData();
byte[] compressedData = Compression.compressString(chatDataJSON); byte[] compressedData = Compression.compressString(chatDataJSON);
...@@ -232,6 +238,37 @@ public class ServerPackets { ...@@ -232,6 +238,37 @@ public class ServerPackets {
} }
public static void send_whitelist_blacklist(ServerPlayerEntity player) {
ConfigurationHandler.Config config = new ConfigurationHandler(ServerPackets.serverInstance).loadConfig();
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
// Write the whitelist data to the buffer
List<String> whitelist = config.getWhitelist();
buffer.writeInt(whitelist.size());
for (String entry : whitelist) {
buffer.writeString(entry);
}
// Write the blacklist data to the buffer
List<String> blacklist = config.getBlacklist();
buffer.writeInt(blacklist.size());
for (String entry : blacklist) {
buffer.writeString(entry);
}
if (player != null) {
// Send packet to specific player
LOGGER.info("Sending whitelist / blacklist packet to player: " + player.getName().getString());
ServerPlayNetworking.send(player, PACKET_S2C_WHITELIST, buffer);
} else {
// Iterate over all players and send the packet
for (ServerPlayerEntity serverPlayer : serverInstance.getPlayerManager().getPlayerList()) {
LOGGER.info("Broadcast whitelist / blacklist packet to player: " + serverPlayer.getName().getString());
ServerPlayNetworking.send(serverPlayer, PACKET_S2C_WHITELIST, buffer);
}
}
}
public static void generate_character(String userLanguage, ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity) { public static void generate_character(String userLanguage, ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity) {
// Set talk to player goal (prevent entity from walking off) // Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F); TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment