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,16 +353,39 @@ public class BubbleRenderer {
// Get camera position
Vec3d interpolatedCameraPos = new Vec3d(camera.getPos().x, camera.getPos().y, camera.getPos().z);
// 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()
.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()))
.collect(Collectors.toList());
// 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
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) {
......
......@@ -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; }
}
}
......@@ -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