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
## [Unreleased]
### Added
- 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
- New **whitelist / blacklist** Minecraft **commands**, to show and hide chat bubbles based on entity type
- New **S2C packets** to send whitelist / blacklist changes on login and after commands are executed
- 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
- 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 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
- 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
......
......@@ -3,6 +3,7 @@ package com.owlmaddie.network;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.ui.BubbleRenderer;
import com.owlmaddie.ui.PlayerMessageManager;
import com.owlmaddie.utils.ClientEntityFinder;
import com.owlmaddie.utils.Decompression;
......@@ -19,7 +20,9 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
/**
......@@ -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
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_PLAYER_STATUS, (client, handler, buffer, responseSender) -> {
// Read the data from the server packet
......@@ -189,7 +214,7 @@ public class ClientPackets {
private static void playNearbyUISound(MinecraftClient client, Entity player, float maxVolume) {
// Play sound with volume based on distance
int distance_squared = 64;
int distance_squared = 144;
if (client.player != null) {
double distance = client.player.squaredDistanceTo(player.getX(), player.getY(), player.getZ());
if (distance <= distance_squared) {
......
......@@ -17,6 +17,7 @@ import net.minecraft.entity.boss.dragon.EnderDragonEntity;
import net.minecraft.entity.boss.dragon.EnderDragonPart;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
......@@ -27,6 +28,7 @@ import org.joml.Quaternionf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
......@@ -43,6 +45,10 @@ public class BubbleRenderer {
public static long lastTick = 0;
public static int light = 15728880;
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) {
// Set shader & texture
......@@ -347,17 +353,40 @@ public class BubbleRenderer {
// Get camera position
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
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
List<Entity> relevantEntities = nearbyEntities.stream()
relevantEntities = nearbyEntities.stream()
.filter(entity -> (entity instanceof MobEntity || entity instanceof PlayerEntity))
.filter(entity -> !entity.hasPassengers())
.filter(entity -> !(entity.equals(cameraEntity) && !camera.isThirdPerson()))
.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());
queryEntityDataCount = 0;
}
for (Entity entity : relevantEntities) {
// Push a new matrix onto the stack.
......
......@@ -14,6 +14,8 @@ import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
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
......@@ -69,6 +71,8 @@ public class ConfigurationHandler {
private int maxOutputTokens = 200;
private double percentOfContext = 0.75;
private int timeout = 10;
private List<String> whitelist = new ArrayList<>();
private List<String> blacklist = new ArrayList<>();
// Getters and setters for existing fields
public String getApiKey() { return apiKey; }
......@@ -77,7 +81,7 @@ public class ConfigurationHandler {
// Update URL if a CreatureChat API key is detected
setUrl("https://api.creaturechat.com/v1/chat/completions");
} 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");
}
this.apiKey = apiKey;
......@@ -92,7 +96,6 @@ public class ConfigurationHandler {
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
// Getters and setters for new fields
public int getMaxContextTokens() { return maxContextTokens; }
public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; }
......@@ -102,5 +105,10 @@ public class ConfigurationHandler {
public double getPercentOfContext() { return 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;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
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.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.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import org.slf4j.Logger;
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.
* Permission level set to 4 (server owner), since this deals with API keys and potential costs.
......@@ -33,6 +46,8 @@ public class CreatureChatCommands {
.then(registerSetCommand("url", "URL", StringArgumentType.string()))
.then(registerSetCommand("model", "Model", StringArgumentType.string()))
.then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer()))
.then(registerWhitelistCommand())
.then(registerBlacklistCommand())
.then(registerHelpCommand()));
}
......@@ -41,26 +56,13 @@ public class CreatureChatCommands {
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set")
.then(CommandManager.argument("value", valueType)
.then(CommandManager.literal("--config")
.then(CommandManager.literal("default")
.executes(context -> {
.then(addConfigArgs((context, useServerConfig) -> {
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)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), false, settingDescription);
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), useServerConfig, settingDescription);
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 -> {
if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription);
......@@ -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) {
ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
ConfigurationHandler.Config config = configHandler.loadConfig();
......@@ -109,13 +182,11 @@ public class CreatureChatCommands {
Text feedbackMessage;
if (configHandler.saveConfig(config, useServerConfig)) {
// succeeded
feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1;
} else {
// failed
feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
......@@ -123,20 +194,83 @@ public class CreatureChatCommands {
}
}
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"
+ "\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);
private static int modifyList(CommandContext<ServerCommandSource> context, String listName, String action, boolean useServerConfig) {
ServerCommandSource source = context.getSource();
ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
ConfigurationHandler.Config config = configHandler.loadConfig();
List<String> entityTypes = getLivingEntityTypeNames();
try {
if ("all".equals(action)) {
if ("whitelist".equals(listName)) {
config.setWhitelist(entityTypes);
config.setBlacklist(new ArrayList<>()); // Clear blacklist
} else if ("blacklist".equals(listName)) {
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;
});
} 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 {
protected enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING }
protected EntityState currentState = EntityState.IDLE;
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 CHARGE_DISTANCE = 25D; // 5 blocks away
protected final double ATTACK_DISTANCE = 4D; // 2 blocks away
......
package com.owlmaddie.mixin;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.network.ServerPackets;
import com.owlmaddie.utils.LivingEntityInterface;
import net.minecraft.entity.Entity;
......@@ -10,8 +11,10 @@ import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
......@@ -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.CallbackInfoReturnable;
import java.util.List;
@Mixin(LivingEntity.class)
public class MixinLivingEntity implements LivingEntityInterface {
......
package com.owlmaddie.mixin;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.network.ServerPackets;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.registry.Registries;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
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.CallbackInfoReturnable;
import java.util.List;
/**
* The {@code MixinMobEntity} mixin class exposes the goalSelector field from the MobEntity class.
*/
......
......@@ -2,6 +2,7 @@ package com.owlmaddie.network;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.ChatDataSaverScheduler;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.goals.EntityBehaviorManager;
import com.owlmaddie.goals.GoalPriority;
import com.owlmaddie.goals.TalkPlayerGoal;
......@@ -30,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
......@@ -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_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_WHITELIST = new Identifier("creaturechat", "packet_s2c_whitelist");
public static final Identifier PACKET_S2C_PLAYER_STATUS = new Identifier("creaturechat", "packet_s2c_player_status");
public static void register() {
......@@ -160,8 +163,11 @@ public class ServerPackets {
// Data is sent in chunks, to prevent exceeding the 32767 limit per String.
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
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
String chatDataJSON = ChatDataManager.getServerInstance().GetLightChatData();
byte[] compressedData = Compression.compressString(chatDataJSON);
......@@ -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) {
// Set talk to player goal (prevent entity from walking off)
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