Commit 802d4d67 by Jonathan Thomas

Large refactor of EntityChatData, EntityChatDataLight, and ChatDataManager.…

Large refactor of EntityChatData, EntityChatDataLight, and ChatDataManager. Separating these into smaller, individual files, to make it easier to maintain.
parent c3112c6b
Pipeline #12733 passed with stages
in 2 minutes 4 seconds
...@@ -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.chat.EntityChatData;
import com.owlmaddie.ui.BubbleRenderer; 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;
...@@ -111,7 +112,7 @@ public class ClientPackets { ...@@ -111,7 +112,7 @@ public class ClientPackets {
MobEntity entity = ClientEntityFinder.getEntityByUUID(client.world, entityId); MobEntity entity = ClientEntityFinder.getEntityByUUID(client.world, entityId);
if (entity != null) { if (entity != null) {
ChatDataManager chatDataManager = ChatDataManager.getClientInstance(); ChatDataManager chatDataManager = ChatDataManager.getClientInstance();
ChatDataManager.EntityChatData chatData = chatDataManager.getOrCreateChatData(entity.getUuidAsString()); EntityChatData chatData = chatDataManager.getOrCreateChatData(entity.getUuidAsString());
chatData.playerId = playerIdStr; chatData.playerId = playerIdStr;
if (!message.isEmpty()) { if (!message.isEmpty()) {
chatData.currentMessage = message; chatData.currentMessage = message;
...@@ -161,7 +162,7 @@ public class ClientPackets { ...@@ -161,7 +162,7 @@ public class ClientPackets {
// Parse JSON and update client chat data // Parse JSON and update client chat data
Gson GSON = new Gson(); Gson GSON = new Gson();
Type type = new TypeToken<ConcurrentHashMap<String, ChatDataManager.EntityChatData>>(){}.getType(); Type type = new TypeToken<ConcurrentHashMap<String, EntityChatData>>(){}.getType();
ChatDataManager.getClientInstance().entityChatDataMap = GSON.fromJson(chatDataJSON, type); ChatDataManager.getClientInstance().entityChatDataMap = GSON.fromJson(chatDataJSON, type);
// Clear receivedChunks for future use // Clear receivedChunks for future use
......
...@@ -2,6 +2,7 @@ package com.owlmaddie.ui; ...@@ -2,6 +2,7 @@ package com.owlmaddie.ui;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
import com.owlmaddie.utils.EntityHeights; import com.owlmaddie.utils.EntityHeights;
import com.owlmaddie.utils.EntityRendererAccessor; import com.owlmaddie.utils.EntityRendererAccessor;
import com.owlmaddie.utils.TextureLoader; import com.owlmaddie.utils.TextureLoader;
...@@ -470,7 +471,7 @@ public class BubbleRenderer { ...@@ -470,7 +471,7 @@ public class BubbleRenderer {
Matrix4f matrix = matrices.peek().getPositionMatrix(); Matrix4f matrix = matrices.peek().getPositionMatrix();
// Look-up greeting (if any) // Look-up greeting (if any)
ChatDataManager.EntityChatData chatData = null; EntityChatData chatData = null;
if (entity instanceof MobEntity) { if (entity instanceof MobEntity) {
chatData = ChatDataManager.getClientInstance().getOrCreateChatData(entity.getUuidAsString()); chatData = ChatDataManager.getClientInstance().getOrCreateChatData(entity.getUuidAsString());
} else if (entity instanceof PlayerEntity) { } else if (entity instanceof PlayerEntity) {
......
package com.owlmaddie.ui; package com.owlmaddie.ui;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
import com.owlmaddie.network.ClientPackets; import com.owlmaddie.network.ClientPackets;
import com.owlmaddie.utils.ClientEntityFinder; import com.owlmaddie.utils.ClientEntityFinder;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
...@@ -119,7 +120,7 @@ public class ClickHandler { ...@@ -119,7 +120,7 @@ public class ClickHandler {
MobEntity closestEntity = ClientEntityFinder.getEntityByUUID(client.world, closestEntityUUID); MobEntity closestEntity = ClientEntityFinder.getEntityByUUID(client.world, closestEntityUUID);
if (closestEntity != null) { if (closestEntity != null) {
// Look-up conversation // Look-up conversation
ChatDataManager.EntityChatData chatData = ChatDataManager.getClientInstance().getOrCreateChatData(closestEntityUUID.toString()); EntityChatData chatData = ChatDataManager.getClientInstance().getOrCreateChatData(closestEntityUUID.toString());
// Determine area clicked inside chat bubble (top, left, right) // Determine area clicked inside chat bubble (top, left, right)
String hitRegion = determineHitRegion(closestHitResult.get(), closestBubbleData.position, camera, closestBubbleData.height); String hitRegion = determineHitRegion(closestHitResult.get(), closestBubbleData.position, camera, closestBubbleData.height);
......
package com.owlmaddie.ui; package com.owlmaddie.ui;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
/** /**
...@@ -8,7 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -8,7 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* many ticks to remain visible, and the message to display. Similar to an EntityChatData, but * many ticks to remain visible, and the message to display. Similar to an EntityChatData, but
* much simpler. * much simpler.
*/ */
public class PlayerMessage extends ChatDataManager.EntityChatData { public class PlayerMessage extends EntityChatData {
public AtomicInteger tickCountdown; public AtomicInteger tickCountdown;
public PlayerMessage(String playerId, String messageText, int ticks) { public PlayerMessage(String playerId, String messageText, int ticks) {
......
...@@ -3,39 +3,23 @@ package com.owlmaddie.chat; ...@@ -3,39 +3,23 @@ package com.owlmaddie.chat;
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.commands.ConfigurationHandler; import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.controls.SpeedControls;
import com.owlmaddie.goals.*;
import com.owlmaddie.items.RarityItemCollector; import com.owlmaddie.items.RarityItemCollector;
import com.owlmaddie.json.QuestJson; import com.owlmaddie.json.QuestJson;
import com.owlmaddie.message.Behavior;
import com.owlmaddie.message.MessageParser;
import com.owlmaddie.message.ParsedMessage;
import com.owlmaddie.network.ServerPackets; import com.owlmaddie.network.ServerPackets;
import com.owlmaddie.utils.Randomizer;
import com.owlmaddie.utils.ServerEntityFinder;
import com.owlmaddie.utils.VillagerEntityAccessor;
import net.minecraft.entity.boss.dragon.EnderDragonEntity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.passive.VillagerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Rarity; import net.minecraft.util.Rarity;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import net.minecraft.util.math.MathHelper;
import net.minecraft.village.VillageGossipType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.*; import java.io.*;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** /**
* The {@code ChatDataManager} class manages chat data for all entities. This class also helps * The {@code ChatDataManager} class manages chat data for all entities. This class also helps
...@@ -69,414 +53,6 @@ public class ChatDataManager { ...@@ -69,414 +53,6 @@ public class ChatDataManager {
// HashMap to associate unique entity IDs with their chat data // HashMap to associate unique entity IDs with their chat data
public ConcurrentHashMap<String, EntityChatData> entityChatDataMap; public ConcurrentHashMap<String, EntityChatData> entityChatDataMap;
public static class ChatMessage {
public String message;
public ChatSender sender;
public ChatMessage(String message, ChatSender sender) {
this.message = message;
this.sender = sender;
}
}
// Inner class to hold entity-specific data
public static class EntityChatData {
public String entityId;
public String playerId;
public String currentMessage;
public int currentLineNumber;
public ChatStatus status;
public List<ChatMessage> previousMessages;
public String characterSheet;
public ChatSender sender;
public int friendship; // -3 to 3 (0 = neutral)
public int auto_generated;
public EntityChatData(String entityId, String playerId) {
this.entityId = entityId;
this.playerId = playerId;
this.currentMessage = "";
this.currentLineNumber = 0;
this.previousMessages = new ArrayList<>();
this.characterSheet = "";
this.status = ChatStatus.NONE;
this.sender = ChatSender.USER;
this.friendship = 0;
this.auto_generated = 0;
}
// Light version with no 'previousMessages' attribute
public class EntityChatDataLight {
public String entityId;
public String currentMessage;
public int currentLineNumber;
public ChatStatus status;
public ChatSender sender;
public int friendship;
}
// Generate light version of chat data (no previous messages)
public EntityChatDataLight toLightVersion() {
EntityChatDataLight light = new EntityChatDataLight();
light.entityId = this.entityId;
light.currentMessage = this.currentMessage;
light.currentLineNumber = this.currentLineNumber;
light.status = this.status;
light.sender = this.sender;
light.friendship = this.friendship;
return light;
}
public String getCharacterProp(String propertyName) {
// Create a case-insensitive regex pattern to match the property name and capture its value
Pattern pattern = Pattern.compile("-?\\s*" + Pattern.quote(propertyName) + ":\\s*(.+)", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(characterSheet);
if (matcher.find()) {
// Return the captured value, trimmed of any excess whitespace
return matcher.group(1).trim().replace("\"", "");
}
return "N/A";
}
// Generate context object
public Map<String, String> getPlayerContext(ServerPlayerEntity player, String userLanguage) {
// Add PLAYER context information
Map<String, String> contextData = new HashMap<>();
contextData.put("player_name", player.getDisplayName().getString());
contextData.put("player_health", player.getHealth() + "/" + player.getMaxHealth());
contextData.put("player_hunger", String.valueOf(player.getHungerManager().getFoodLevel()));
contextData.put("player_held_item", String.valueOf(player.getMainHandStack().getItem().toString()));
contextData.put("player_biome", player.getWorld().getBiome(player.getBlockPos()).getKey().get().getValue().getPath());
contextData.put("player_is_creative", player.isCreative() ? "yes" : "no");
contextData.put("player_is_swimming", player.isSwimming() ? "yes" : "no");
contextData.put("player_is_on_ground", player.isOnGround() ? "yes" : "no");
contextData.put("player_language", userLanguage);
ItemStack feetArmor = player.getInventory().armor.get(0);
ItemStack legsArmor = player.getInventory().armor.get(1);
ItemStack chestArmor = player.getInventory().armor.get(2);
ItemStack headArmor = player.getInventory().armor.get(3);
contextData.put("player_armor_head", headArmor.getItem().toString());
contextData.put("player_armor_chest", chestArmor.getItem().toString());
contextData.put("player_armor_legs", legsArmor.getItem().toString());
contextData.put("player_armor_feet", feetArmor.getItem().toString());
// Get active player effects
String effectsString = player.getActiveStatusEffects().entrySet().stream()
.map(entry -> entry.getKey().getTranslationKey() + " x" + (entry.getValue().getAmplifier() + 1))
.collect(Collectors.joining(", "));
contextData.put("player_active_effects", effectsString);
// Get World time (as 24 hour value)
int hours = (int) ((player.getWorld().getTimeOfDay() / 1000 + 6) % 24); // Minecraft day starts at 6 AM
int minutes = (int) (((player.getWorld().getTimeOfDay() % 1000) / 1000.0) * 60);
contextData.put("world_time", String.format("%02d:%02d", hours, minutes));
contextData.put("world_is_raining", player.getWorld().isRaining() ? "yes" : "no");
contextData.put("world_is_thundering", player.getWorld().isThundering() ? "yes" : "no");
contextData.put("world_difficulty", player.getWorld().getDifficulty().getName());
contextData.put("world_is_hardcore", player.getWorld().getLevelProperties().isHardcore() ? "yes" : "no");
// Get moon phase
String moonPhaseDescription = switch (player.getWorld().getMoonPhase()) {
case 0 -> "Full Moon";
case 1 -> "Waning Gibbous";
case 2 -> "Last Quarter";
case 3 -> "Waning Crescent";
case 4 -> "New Moon";
case 5 -> "Waxing Crescent";
case 6 -> "First Quarter";
case 7 -> "Waxing Gibbous";
default -> "Unknown";
};
contextData.put("world_moon_phase", moonPhaseDescription);
// Get Entity details
MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), UUID.fromString(entityId));
if (entity.getCustomName() == null) {
contextData.put("entity_name", "");
} else {
contextData.put("entity_name", entity.getCustomName().getString());
}
contextData.put("entity_type", entity.getType().getName().getString());
contextData.put("entity_health", entity.getHealth() + "/" + entity.getMaxHealth());
contextData.put("entity_personality", getCharacterProp("Personality"));
contextData.put("entity_speaking_style", getCharacterProp("Speaking Style / Tone"));
contextData.put("entity_likes", getCharacterProp("Likes"));
contextData.put("entity_dislikes", getCharacterProp("Dislikes"));
contextData.put("entity_age", getCharacterProp("Age"));
contextData.put("entity_alignment", getCharacterProp("Alignment"));
contextData.put("entity_class", getCharacterProp("Class"));
contextData.put("entity_skills", getCharacterProp("Skills"));
contextData.put("entity_background", getCharacterProp("Background"));
contextData.put("entity_friendship", String.valueOf(friendship));
return contextData;
}
// Generate greeting
public void generateMessage(String userLanguage, ServerPlayerEntity player, String systemPrompt, String userMessage, boolean is_auto_message) {
this.status = ChatStatus.PENDING;
if (is_auto_message) {
// Increment an auto-generated message
this.auto_generated++;
} else {
// Reset auto-generated counter
this.auto_generated = 0;
}
// Add USER Message
if (systemPrompt == "system-character") {
// Add message without playerId (so it does not display)
this.addMessage(userMessage, ChatSender.USER, "");
} else if (systemPrompt == "system-chat") {
this.addMessage(userMessage, ChatSender.USER, player.getUuidAsString());
}
// Add PLAYER context information
Map<String, String> contextData = getPlayerContext(player, userLanguage);
// Get config (api key, url, settings)
ConfigurationHandler.Config config = new ConfigurationHandler(ServerPackets.serverInstance).loadConfig();
String promptText = ChatPrompt.loadPromptFromResource(ServerPackets.serverInstance.getResourceManager(), systemPrompt);
// fetch HTTP response from ChatGPT
ChatGPTRequest.fetchMessageFromChatGPT(config, promptText, contextData, previousMessages, false).thenAccept(output_message -> {
if (output_message != null && systemPrompt == "system-character") {
// Character Sheet: Remove system-character message from previous messages
previousMessages.clear();
// Add NEW CHARACTER sheet & greeting
this.characterSheet = output_message;
String shortGreeting = getCharacterProp("short greeting");
if (shortGreeting.isEmpty()) {
shortGreeting = Randomizer.getRandomMessage(Randomizer.RandomType.NO_RESPONSE);
}
this.addMessage(shortGreeting.replace("\n", " "), ChatSender.ASSISTANT, player.getUuidAsString());
} else if (output_message != null && systemPrompt == "system-chat") {
// Chat Message: Parse message for behaviors
ParsedMessage result = MessageParser.parseMessage(output_message.replace("\n", " "));
MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), UUID.fromString(entityId));
// Determine entity's default speed
// Some Entities (i.e. Axolotl) set this incorrectly... so adjusting in the SpeedControls class
float entitySpeed = SpeedControls.getMaxSpeed(entity);
float entitySpeedMedium = MathHelper.clamp(entitySpeed * 1.15F, 0.5f, 1.15f);
float entitySpeedFast = MathHelper.clamp(entitySpeed * 1.3F, 0.5f, 1.3f);
// Apply behaviors (if any)
for (Behavior behavior : result.getBehaviors()) {
LOGGER.info("Behavior: " + behavior.getName() + (behavior.getArgument() != null ?
", Argument: " + behavior.getArgument() : ""));
// Apply behaviors to entity
if (behavior.getName().equals("FOLLOW")) {
FollowPlayerGoal followGoal = new FollowPlayerGoal(player, entity, entitySpeedMedium);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, followGoal, GoalPriority.FOLLOW_PLAYER);
} else if (behavior.getName().equals("UNFOLLOW")) {
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
} else if (behavior.getName().equals("FLEE")) {
float fleeDistance = 40F;
FleePlayerGoal fleeGoal = new FleePlayerGoal(player, entity, entitySpeedFast, fleeDistance);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, fleeGoal, GoalPriority.FLEE_PLAYER);
} else if (behavior.getName().equals("UNFLEE")) {
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
} else if (behavior.getName().equals("ATTACK")) {
AttackPlayerGoal attackGoal = new AttackPlayerGoal(player, entity, entitySpeedFast);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, attackGoal, GoalPriority.ATTACK_PLAYER);
} else if (behavior.getName().equals("PROTECT")) {
ProtectPlayerGoal protectGoal = new ProtectPlayerGoal(player, entity, 1.0);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, protectGoal, GoalPriority.PROTECT_PLAYER);
} else if (behavior.getName().equals("UNPROTECT")) {
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
} else if (behavior.getName().equals("LEAD")) {
LeadPlayerGoal leadGoal = new LeadPlayerGoal(player, entity, entitySpeedMedium);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, leadGoal, GoalPriority.LEAD_PLAYER);
} else if (behavior.getName().equals("UNLEAD")) {
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
} else if (behavior.getName().equals("FRIENDSHIP")) {
int new_friendship = Math.max(-3, Math.min(3, behavior.getArgument()));
// Does friendship improve?
if (new_friendship > this.friendship) {
// Stop any attack/flee if friendship improves
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
if (entity instanceof EnderDragonEntity && new_friendship == 3) {
// Trigger end of game (friendship always wins!)
EnderDragonEntity dragon = (EnderDragonEntity) entity;
dragon.getFight().dragonKilled(dragon);
}
}
// Merchant deals (if friendship changes with a Villager
if (entity instanceof VillagerEntity && this.friendship != new_friendship) {
VillagerEntityAccessor villager = (VillagerEntityAccessor) entity;
switch (new_friendship) {
case 3:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MAJOR_POSITIVE, 20);
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_POSITIVE, 25);
break;
case 2:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_POSITIVE, 25);
break;
case 1:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_POSITIVE, 10);
break;
case -1:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_NEGATIVE, 10);
break;
case -2:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_NEGATIVE, 25);
break;
case -3:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MAJOR_NEGATIVE, 20);
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_NEGATIVE, 25);
break;
}
}
// Tame best friends and un-tame worst enemies
if (entity instanceof TameableEntity && this.friendship != new_friendship) {
TameableEntity tamableEntity = (TameableEntity) entity;
if (new_friendship == 3 && !tamableEntity.isTamed()) {
tamableEntity.setOwner(player);
} else if (new_friendship == -3 && tamableEntity.isTamed()) {
tamableEntity.setTamed(false);
tamableEntity.setOwnerUuid(null);
}
}
this.friendship = new_friendship;
}
}
// Add ASSISTANT message to history
this.addMessage(result.getOriginalMessage(), ChatSender.ASSISTANT, player.getUuidAsString());
// Get cleaned message (i.e. no <BEHAVIOR> strings)
String cleanedMessage = result.getCleanedMessage();
if (cleanedMessage.isEmpty()) {
cleanedMessage = Randomizer.getRandomMessage(Randomizer.RandomType.NO_RESPONSE);
}
// Update the current message to a 'cleaned version'
this.currentMessage = cleanedMessage;
} else {
// Error / No Chat Message (Failure)
String randomErrorMessage = Randomizer.getRandomMessage(Randomizer.RandomType.ERROR);
this.addMessage(randomErrorMessage, ChatSender.ASSISTANT, player.getUuidAsString());
// Determine error message to display
String errorMessage = "Help is available at discord.creaturechat.com";
if (!ChatGPTRequest.lastErrorMessage.isEmpty()) {
errorMessage = "Error: " + truncateString(ChatGPTRequest.lastErrorMessage, 55) + "\n" + errorMessage;
}
// Send clickable error message
ServerPackets.SendClickableError(player,
errorMessage, "http://discord.creaturechat.com");
// Clear history (if no character sheet was generated)
if (characterSheet.isEmpty()) {
previousMessages.clear();
}
}
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
});
}
public static String truncateString(String input, int maxLength) {
return input.length() > maxLength ? input.substring(0, maxLength - 3) + "..." : input;
}
// Add a message to the history and update the current message
public void addMessage(String message, ChatSender messageSender, String playerId) {
// Truncate message (prevent crazy long messages... just in case)
String truncatedMessage = message.substring(0, Math.min(message.length(), MAX_CHAR_IN_USER_MESSAGE));
// Add message to history
previousMessages.add(new ChatMessage(truncatedMessage, messageSender));
// Set new message and reset line number of displayed text
currentMessage = truncatedMessage;
currentLineNumber = 0;
if (messageSender == ChatSender.ASSISTANT) {
// Show new generated message
status = ChatStatus.DISPLAY;
} else if (messageSender == ChatSender.USER) {
// Show pending icon
status = ChatStatus.PENDING;
}
sender = messageSender;
this.playerId = playerId;
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
}
// Get wrapped lines
public List<String> getWrappedLines() {
return LineWrapper.wrapLines(this.currentMessage, MAX_CHAR_PER_LINE);
}
public boolean isEndOfMessage() {
int totalLines = this.getWrappedLines().size();
// Check if the current line number plus DISPLAY_NUM_LINES covers or exceeds the total number of lines
return currentLineNumber + DISPLAY_NUM_LINES >= totalLines;
}
public void setLineNumber(Integer lineNumber) {
int totalLines = this.getWrappedLines().size();
// Ensure the lineNumber is within the valid range
currentLineNumber = Math.min(Math.max(lineNumber, 0), totalLines);
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
}
public void setStatus(ChatStatus new_status) {
status = new_status;
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
}
}
public void clearData() { public void clearData() {
// Clear the chat data for the previous session // Clear the chat data for the previous session
entityChatDataMap.clear(); entityChatDataMap.clear();
...@@ -564,7 +140,7 @@ public class ChatDataManager { ...@@ -564,7 +140,7 @@ public class ChatDataManager {
public String GetLightChatData() { public String GetLightChatData() {
try { try {
// Create "light" version of entire chat data HashMap // Create "light" version of entire chat data HashMap
HashMap<String, EntityChatData.EntityChatDataLight> lightVersionMap = new HashMap<>(); HashMap<String, EntityChatDataLight> lightVersionMap = new HashMap<>();
this.entityChatDataMap.forEach((id, entityChatData) -> lightVersionMap.put(id, entityChatData.toLightVersion())); this.entityChatDataMap.forEach((id, entityChatData) -> lightVersionMap.put(id, entityChatData.toLightVersion()));
// Convert light chat data to JSON string // Convert light chat data to JSON string
......
...@@ -118,7 +118,7 @@ public class ChatGPTRequest { ...@@ -118,7 +118,7 @@ public class ChatGPTRequest {
return (int) Math.round(text.length() / 3.5); return (int) Math.round(text.length() / 3.5);
} }
public static CompletableFuture<String> fetchMessageFromChatGPT(ConfigurationHandler.Config config, String systemPrompt, Map<String, String> contextData, List<ChatDataManager.ChatMessage> messageHistory, Boolean jsonMode) { public static CompletableFuture<String> fetchMessageFromChatGPT(ConfigurationHandler.Config config, String systemPrompt, Map<String, String> contextData, List<ChatMessage> messageHistory, Boolean jsonMode) {
// Init API & LLM details // Init API & LLM details
String apiUrl = config.getUrl(); String apiUrl = config.getUrl();
String apiKey = config.getApiKey(); String apiKey = config.getApiKey();
...@@ -151,7 +151,7 @@ public class ChatGPTRequest { ...@@ -151,7 +151,7 @@ public class ChatGPTRequest {
// Iterate backwards through the message history // Iterate backwards through the message history
for (int i = messageHistory.size() - 1; i >= 0; i--) { for (int i = messageHistory.size() - 1; i >= 0; i--) {
ChatDataManager.ChatMessage chatMessage = messageHistory.get(i); ChatMessage chatMessage = messageHistory.get(i);
String senderName = chatMessage.sender.toString().toLowerCase(Locale.ENGLISH); String senderName = chatMessage.sender.toString().toLowerCase(Locale.ENGLISH);
String messageText = replacePlaceholders(chatMessage.message, contextData); String messageText = replacePlaceholders(chatMessage.message, contextData);
int messageTokens = estimateTokenSize(senderName + ": " + messageText); int messageTokens = estimateTokenSize(senderName + ": " + messageText);
......
package com.owlmaddie.chat;
/**
* The {@code ChatMessage} class represents a single message.
*/
public class ChatMessage {
public String message;
public ChatDataManager.ChatSender sender;
public ChatMessage(String message, ChatDataManager.ChatSender sender) {
this.message = message;
this.sender = sender;
}
}
\ No newline at end of file
package com.owlmaddie.chat;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.controls.SpeedControls;
import com.owlmaddie.goals.*;
import com.owlmaddie.message.Behavior;
import com.owlmaddie.message.MessageParser;
import com.owlmaddie.message.ParsedMessage;
import com.owlmaddie.network.ServerPackets;
import com.owlmaddie.utils.Randomizer;
import com.owlmaddie.utils.ServerEntityFinder;
import com.owlmaddie.utils.VillagerEntityAccessor;
import net.minecraft.entity.boss.dragon.EnderDragonEntity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.passive.VillagerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.village.VillageGossipType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* The {@code EntityChatData} class represents a conversation between an
* entity and one or more players, including friendship, character sheets,
* and the status of the current displayed message.
*/
public class EntityChatData {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
public String entityId;
public String playerId;
public String currentMessage;
public int currentLineNumber;
public ChatDataManager.ChatStatus status;
public List<ChatMessage> previousMessages;
public String characterSheet;
public ChatDataManager.ChatSender sender;
public int friendship; // -3 to 3 (0 = neutral)
public int auto_generated;
public EntityChatData(String entityId, String playerId) {
this.entityId = entityId;
this.playerId = playerId;
this.currentMessage = "";
this.currentLineNumber = 0;
this.previousMessages = new ArrayList<>();
this.characterSheet = "";
this.status = ChatDataManager.ChatStatus.NONE;
this.sender = ChatDataManager.ChatSender.USER;
this.friendship = 0;
this.auto_generated = 0;
}
// Generate light version of chat data (no previous messages)
public EntityChatDataLight toLightVersion() {
EntityChatDataLight light = new EntityChatDataLight();
light.entityId = this.entityId;
light.currentMessage = this.currentMessage;
light.currentLineNumber = this.currentLineNumber;
light.status = this.status;
light.sender = this.sender;
light.friendship = this.friendship;
return light;
}
public String getCharacterProp(String propertyName) {
// Create a case-insensitive regex pattern to match the property name and capture its value
Pattern pattern = Pattern.compile("-?\\s*" + Pattern.quote(propertyName) + ":\\s*(.+)", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(characterSheet);
if (matcher.find()) {
// Return the captured value, trimmed of any excess whitespace
return matcher.group(1).trim().replace("\"", "");
}
return "N/A";
}
// Generate context object
public Map<String, String> getPlayerContext(ServerPlayerEntity player, String userLanguage) {
// Add PLAYER context information
Map<String, String> contextData = new HashMap<>();
contextData.put("player_name", player.getDisplayName().getString());
contextData.put("player_health", player.getHealth() + "/" + player.getMaxHealth());
contextData.put("player_hunger", String.valueOf(player.getHungerManager().getFoodLevel()));
contextData.put("player_held_item", String.valueOf(player.getMainHandStack().getItem().toString()));
contextData.put("player_biome", player.getWorld().getBiome(player.getBlockPos()).getKey().get().getValue().getPath());
contextData.put("player_is_creative", player.isCreative() ? "yes" : "no");
contextData.put("player_is_swimming", player.isSwimming() ? "yes" : "no");
contextData.put("player_is_on_ground", player.isOnGround() ? "yes" : "no");
contextData.put("player_language", userLanguage);
ItemStack feetArmor = player.getInventory().armor.get(0);
ItemStack legsArmor = player.getInventory().armor.get(1);
ItemStack chestArmor = player.getInventory().armor.get(2);
ItemStack headArmor = player.getInventory().armor.get(3);
contextData.put("player_armor_head", headArmor.getItem().toString());
contextData.put("player_armor_chest", chestArmor.getItem().toString());
contextData.put("player_armor_legs", legsArmor.getItem().toString());
contextData.put("player_armor_feet", feetArmor.getItem().toString());
// Get active player effects
String effectsString = player.getActiveStatusEffects().entrySet().stream()
.map(entry -> entry.getKey().getTranslationKey() + " x" + (entry.getValue().getAmplifier() + 1))
.collect(Collectors.joining(", "));
contextData.put("player_active_effects", effectsString);
// Get World time (as 24 hour value)
int hours = (int) ((player.getWorld().getTimeOfDay() / 1000 + 6) % 24); // Minecraft day starts at 6 AM
int minutes = (int) (((player.getWorld().getTimeOfDay() % 1000) / 1000.0) * 60);
contextData.put("world_time", String.format("%02d:%02d", hours, minutes));
contextData.put("world_is_raining", player.getWorld().isRaining() ? "yes" : "no");
contextData.put("world_is_thundering", player.getWorld().isThundering() ? "yes" : "no");
contextData.put("world_difficulty", player.getWorld().getDifficulty().getName());
contextData.put("world_is_hardcore", player.getWorld().getLevelProperties().isHardcore() ? "yes" : "no");
// Get moon phase
String moonPhaseDescription = switch (player.getWorld().getMoonPhase()) {
case 0 -> "Full Moon";
case 1 -> "Waning Gibbous";
case 2 -> "Last Quarter";
case 3 -> "Waning Crescent";
case 4 -> "New Moon";
case 5 -> "Waxing Crescent";
case 6 -> "First Quarter";
case 7 -> "Waxing Gibbous";
default -> "Unknown";
};
contextData.put("world_moon_phase", moonPhaseDescription);
// Get Entity details
MobEntity entity = (MobEntity) ServerEntityFinder.getEntityByUUID(player.getServerWorld(), UUID.fromString(entityId));
if (entity.getCustomName() == null) {
contextData.put("entity_name", "");
} else {
contextData.put("entity_name", entity.getCustomName().getString());
}
contextData.put("entity_type", entity.getType().getName().getString());
contextData.put("entity_health", entity.getHealth() + "/" + entity.getMaxHealth());
contextData.put("entity_personality", getCharacterProp("Personality"));
contextData.put("entity_speaking_style", getCharacterProp("Speaking Style / Tone"));
contextData.put("entity_likes", getCharacterProp("Likes"));
contextData.put("entity_dislikes", getCharacterProp("Dislikes"));
contextData.put("entity_age", getCharacterProp("Age"));
contextData.put("entity_alignment", getCharacterProp("Alignment"));
contextData.put("entity_class", getCharacterProp("Class"));
contextData.put("entity_skills", getCharacterProp("Skills"));
contextData.put("entity_background", getCharacterProp("Background"));
contextData.put("entity_friendship", String.valueOf(friendship));
return contextData;
}
// Generate greeting
public void generateMessage(String userLanguage, ServerPlayerEntity player, String systemPrompt, String userMessage, boolean is_auto_message) {
this.status = ChatDataManager.ChatStatus.PENDING;
if (is_auto_message) {
// Increment an auto-generated message
this.auto_generated++;
} else {
// Reset auto-generated counter
this.auto_generated = 0;
}
// Add USER Message
if (systemPrompt == "system-character") {
// Add message without playerId (so it does not display)
this.addMessage(userMessage, ChatDataManager.ChatSender.USER, "");
} else if (systemPrompt == "system-chat") {
this.addMessage(userMessage, ChatDataManager.ChatSender.USER, player.getUuidAsString());
}
// Add PLAYER context information
Map<String, String> contextData = getPlayerContext(player, userLanguage);
// Get config (api key, url, settings)
ConfigurationHandler.Config config = new ConfigurationHandler(ServerPackets.serverInstance).loadConfig();
String promptText = ChatPrompt.loadPromptFromResource(ServerPackets.serverInstance.getResourceManager(), systemPrompt);
// fetch HTTP response from ChatGPT
ChatGPTRequest.fetchMessageFromChatGPT(config, promptText, contextData, previousMessages, false).thenAccept(output_message -> {
if (output_message != null && systemPrompt == "system-character") {
// Character Sheet: Remove system-character message from previous messages
previousMessages.clear();
// Add NEW CHARACTER sheet & greeting
this.characterSheet = output_message;
String shortGreeting = getCharacterProp("short greeting");
if (shortGreeting.isEmpty()) {
shortGreeting = Randomizer.getRandomMessage(Randomizer.RandomType.NO_RESPONSE);
}
this.addMessage(shortGreeting.replace("\n", " "), ChatDataManager.ChatSender.ASSISTANT, player.getUuidAsString());
} else if (output_message != null && systemPrompt == "system-chat") {
// Chat Message: Parse message for behaviors
ParsedMessage result = MessageParser.parseMessage(output_message.replace("\n", " "));
MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), UUID.fromString(entityId));
// Determine entity's default speed
// Some Entities (i.e. Axolotl) set this incorrectly... so adjusting in the SpeedControls class
float entitySpeed = SpeedControls.getMaxSpeed(entity);
float entitySpeedMedium = MathHelper.clamp(entitySpeed * 1.15F, 0.5f, 1.15f);
float entitySpeedFast = MathHelper.clamp(entitySpeed * 1.3F, 0.5f, 1.3f);
// Apply behaviors (if any)
for (Behavior behavior : result.getBehaviors()) {
LOGGER.info("Behavior: " + behavior.getName() + (behavior.getArgument() != null ?
", Argument: " + behavior.getArgument() : ""));
// Apply behaviors to entity
if (behavior.getName().equals("FOLLOW")) {
FollowPlayerGoal followGoal = new FollowPlayerGoal(player, entity, entitySpeedMedium);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, followGoal, GoalPriority.FOLLOW_PLAYER);
} else if (behavior.getName().equals("UNFOLLOW")) {
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
} else if (behavior.getName().equals("FLEE")) {
float fleeDistance = 40F;
FleePlayerGoal fleeGoal = new FleePlayerGoal(player, entity, entitySpeedFast, fleeDistance);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, fleeGoal, GoalPriority.FLEE_PLAYER);
} else if (behavior.getName().equals("UNFLEE")) {
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
} else if (behavior.getName().equals("ATTACK")) {
AttackPlayerGoal attackGoal = new AttackPlayerGoal(player, entity, entitySpeedFast);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, attackGoal, GoalPriority.ATTACK_PLAYER);
} else if (behavior.getName().equals("PROTECT")) {
ProtectPlayerGoal protectGoal = new ProtectPlayerGoal(player, entity, 1.0);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, protectGoal, GoalPriority.PROTECT_PLAYER);
} else if (behavior.getName().equals("UNPROTECT")) {
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
} else if (behavior.getName().equals("LEAD")) {
LeadPlayerGoal leadGoal = new LeadPlayerGoal(player, entity, entitySpeedMedium);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, leadGoal, GoalPriority.LEAD_PLAYER);
} else if (behavior.getName().equals("UNLEAD")) {
EntityBehaviorManager.removeGoal(entity, LeadPlayerGoal.class);
} else if (behavior.getName().equals("FRIENDSHIP")) {
int new_friendship = Math.max(-3, Math.min(3, behavior.getArgument()));
// Does friendship improve?
if (new_friendship > this.friendship) {
// Stop any attack/flee if friendship improves
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
if (entity instanceof EnderDragonEntity && new_friendship == 3) {
// Trigger end of game (friendship always wins!)
EnderDragonEntity dragon = (EnderDragonEntity) entity;
dragon.getFight().dragonKilled(dragon);
}
}
// Merchant deals (if friendship changes with a Villager
if (entity instanceof VillagerEntity && this.friendship != new_friendship) {
VillagerEntityAccessor villager = (VillagerEntityAccessor) entity;
switch (new_friendship) {
case 3:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MAJOR_POSITIVE, 20);
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_POSITIVE, 25);
break;
case 2:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_POSITIVE, 25);
break;
case 1:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_POSITIVE, 10);
break;
case -1:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_NEGATIVE, 10);
break;
case -2:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_NEGATIVE, 25);
break;
case -3:
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MAJOR_NEGATIVE, 20);
villager.getGossip().startGossip(player.getUuid(), VillageGossipType.MINOR_NEGATIVE, 25);
break;
}
}
// Tame best friends and un-tame worst enemies
if (entity instanceof TameableEntity && this.friendship != new_friendship) {
TameableEntity tamableEntity = (TameableEntity) entity;
if (new_friendship == 3 && !tamableEntity.isTamed()) {
tamableEntity.setOwner(player);
} else if (new_friendship == -3 && tamableEntity.isTamed()) {
tamableEntity.setTamed(false);
tamableEntity.setOwnerUuid(null);
}
}
this.friendship = new_friendship;
}
}
// Add ASSISTANT message to history
this.addMessage(result.getOriginalMessage(), ChatDataManager.ChatSender.ASSISTANT, player.getUuidAsString());
// Get cleaned message (i.e. no <BEHAVIOR> strings)
String cleanedMessage = result.getCleanedMessage();
if (cleanedMessage.isEmpty()) {
cleanedMessage = Randomizer.getRandomMessage(Randomizer.RandomType.NO_RESPONSE);
}
// Update the current message to a 'cleaned version'
this.currentMessage = cleanedMessage;
} else {
// Error / No Chat Message (Failure)
String randomErrorMessage = Randomizer.getRandomMessage(Randomizer.RandomType.ERROR);
this.addMessage(randomErrorMessage, ChatDataManager.ChatSender.ASSISTANT, player.getUuidAsString());
// Determine error message to display
String errorMessage = "Help is available at discord.creaturechat.com";
if (!ChatGPTRequest.lastErrorMessage.isEmpty()) {
errorMessage = "Error: " + truncateString(ChatGPTRequest.lastErrorMessage, 55) + "\n" + errorMessage;
}
// Send clickable error message
ServerPackets.SendClickableError(player,
errorMessage, "http://discord.creaturechat.com");
// Clear history (if no character sheet was generated)
if (characterSheet.isEmpty()) {
previousMessages.clear();
}
}
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
});
}
public static String truncateString(String input, int maxLength) {
return input.length() > maxLength ? input.substring(0, maxLength - 3) + "..." : input;
}
// Add a message to the history and update the current message
public void addMessage(String message, ChatDataManager.ChatSender messageSender, String playerId) {
// Truncate message (prevent crazy long messages... just in case)
String truncatedMessage = message.substring(0, Math.min(message.length(), ChatDataManager.MAX_CHAR_IN_USER_MESSAGE));
// Add message to history
previousMessages.add(new ChatMessage(truncatedMessage, messageSender));
// Set new message and reset line number of displayed text
currentMessage = truncatedMessage;
currentLineNumber = 0;
if (messageSender == ChatDataManager.ChatSender.ASSISTANT) {
// Show new generated message
status = ChatDataManager.ChatStatus.DISPLAY;
} else if (messageSender == ChatDataManager.ChatSender.USER) {
// Show pending icon
status = ChatDataManager.ChatStatus.PENDING;
}
sender = messageSender;
this.playerId = playerId;
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
}
// Get wrapped lines
public List<String> getWrappedLines() {
return LineWrapper.wrapLines(this.currentMessage, ChatDataManager.MAX_CHAR_PER_LINE);
}
public boolean isEndOfMessage() {
int totalLines = this.getWrappedLines().size();
// Check if the current line number plus DISPLAY_NUM_LINES covers or exceeds the total number of lines
return currentLineNumber + ChatDataManager.DISPLAY_NUM_LINES >= totalLines;
}
public void setLineNumber(Integer lineNumber) {
int totalLines = this.getWrappedLines().size();
// Ensure the lineNumber is within the valid range
currentLineNumber = Math.min(Math.max(lineNumber, 0), totalLines);
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
}
public void setStatus(ChatDataManager.ChatStatus new_status) {
status = new_status;
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this);
}
}
\ No newline at end of file
package com.owlmaddie.chat;
/**
* The {@code EntityChatDataLight} class represents the current displayed message, and no
* previous messages or player message history. This is primarily used to broadcast the
* currently displayed messages to players as they connect to the server.
*/
public class EntityChatDataLight {
public String entityId;
public String currentMessage;
public int currentLineNumber;
public ChatDataManager.ChatStatus status;
public ChatDataManager.ChatSender sender;
public int friendship;
}
\ No newline at end of file
package com.owlmaddie.goals; package com.owlmaddie.goals;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
import com.owlmaddie.controls.LookControls; import com.owlmaddie.controls.LookControls;
import com.owlmaddie.network.ServerPackets; import com.owlmaddie.network.ServerPackets;
import com.owlmaddie.utils.RandomTargetFinder; import com.owlmaddie.utils.RandomTargetFinder;
...@@ -71,7 +72,7 @@ public class LeadPlayerGoal extends PlayerBaseGoal { ...@@ -71,7 +72,7 @@ public class LeadPlayerGoal extends PlayerBaseGoal {
String arrivedMessage = "<You have arrived at your destination>"; String arrivedMessage = "<You have arrived at your destination>";
ChatDataManager chatDataManager = ChatDataManager.getServerInstance(); ChatDataManager chatDataManager = ChatDataManager.getServerInstance();
ChatDataManager.EntityChatData chatData = chatDataManager.getOrCreateChatData(this.entity.getUuidAsString()); EntityChatData chatData = chatDataManager.getOrCreateChatData(this.entity.getUuidAsString());
if (!chatData.characterSheet.isEmpty() && chatData.auto_generated < chatDataManager.MAX_AUTOGENERATE_RESPONSES) { if (!chatData.characterSheet.isEmpty() && chatData.auto_generated < chatDataManager.MAX_AUTOGENERATE_RESPONSES) {
ServerPackets.generate_chat("N/A", chatData, (ServerPlayerEntity) this.targetEntity, this.entity, arrivedMessage, true); ServerPackets.generate_chat("N/A", chatData, (ServerPlayerEntity) this.targetEntity, this.entity, arrivedMessage, true);
} }
......
package com.owlmaddie.mixin; package com.owlmaddie.mixin;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
import com.owlmaddie.network.ServerPackets; import com.owlmaddie.network.ServerPackets;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
...@@ -21,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; ...@@ -21,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LivingEntity.class) @Mixin(LivingEntity.class)
public class MixinLivingEntity { public class MixinLivingEntity {
private ChatDataManager.EntityChatData getChatData(LivingEntity entity) { private EntityChatData getChatData(LivingEntity entity) {
ChatDataManager chatDataManager = ChatDataManager.getServerInstance(); ChatDataManager chatDataManager = ChatDataManager.getServerInstance();
return chatDataManager.getOrCreateChatData(entity.getUuidAsString()); return chatDataManager.getOrCreateChatData(entity.getUuidAsString());
} }
...@@ -30,7 +31,7 @@ public class MixinLivingEntity { ...@@ -30,7 +31,7 @@ public class MixinLivingEntity {
private void modifyCanTarget(LivingEntity target, CallbackInfoReturnable<Boolean> cir) { private void modifyCanTarget(LivingEntity target, CallbackInfoReturnable<Boolean> cir) {
if (target instanceof PlayerEntity) { if (target instanceof PlayerEntity) {
LivingEntity thisEntity = (LivingEntity) (Object) this; LivingEntity thisEntity = (LivingEntity) (Object) this;
ChatDataManager.EntityChatData chatData = getChatData(thisEntity); EntityChatData chatData = getChatData(thisEntity);
if (chatData.friendship > 0) { if (chatData.friendship > 0) {
// Friendly creatures can't target a player // Friendly creatures can't target a player
cir.setReturnValue(false); cir.setReturnValue(false);
...@@ -53,7 +54,7 @@ public class MixinLivingEntity { ...@@ -53,7 +54,7 @@ public class MixinLivingEntity {
if (attacker instanceof PlayerEntity && thisEntity instanceof MobEntity && !thisEntity.isDead()) { if (attacker instanceof PlayerEntity && thisEntity instanceof MobEntity && !thisEntity.isDead()) {
// Generate attacked message (only if the previous user message was not an attacked message) // Generate attacked message (only if the previous user message was not an attacked message)
// We don't want to constantly generate messages during a prolonged, multi-damage event // We don't want to constantly generate messages during a prolonged, multi-damage event
ChatDataManager.EntityChatData chatData = getChatData(thisEntity); EntityChatData chatData = getChatData(thisEntity);
if (!chatData.characterSheet.isEmpty() && chatData.auto_generated < ChatDataManager.MAX_AUTOGENERATE_RESPONSES) { if (!chatData.characterSheet.isEmpty() && chatData.auto_generated < ChatDataManager.MAX_AUTOGENERATE_RESPONSES) {
// Only auto-generate a response to being attacked if chat data already exists // Only auto-generate a response to being attacked if chat data already exists
// and this is the first attack event. // and this is the first attack event.
......
package com.owlmaddie.mixin; package com.owlmaddie.mixin;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
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.passive.TameableEntity; import net.minecraft.entity.passive.TameableEntity;
...@@ -52,7 +53,7 @@ public class MixinMobEntity { ...@@ -52,7 +53,7 @@ public class MixinMobEntity {
// Get chat data for entity // Get chat data for entity
ChatDataManager chatDataManager = ChatDataManager.getServerInstance(); ChatDataManager chatDataManager = ChatDataManager.getServerInstance();
ChatDataManager.EntityChatData chatData = chatDataManager.getOrCreateChatData(thisEntity.getUuidAsString()); EntityChatData chatData = chatDataManager.getOrCreateChatData(thisEntity.getUuidAsString());
// Check if the player successfully interacts with an item // Check if the player successfully interacts with an item
if (player instanceof ServerPlayerEntity) { if (player instanceof ServerPlayerEntity) {
......
package com.owlmaddie.network; package com.owlmaddie.network;
import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.EntityChatData;
import com.owlmaddie.chat.ChatDataSaverScheduler; import com.owlmaddie.chat.ChatDataSaverScheduler;
import com.owlmaddie.commands.ConfigurationHandler; import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.goals.EntityBehaviorManager; import com.owlmaddie.goals.EntityBehaviorManager;
...@@ -63,7 +64,7 @@ public class ServerPackets { ...@@ -63,7 +64,7 @@ public class ServerPackets {
server.execute(() -> { server.execute(() -> {
MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId); MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) { if (entity != null) {
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString()); EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
if (chatData.characterSheet.isEmpty()) { if (chatData.characterSheet.isEmpty()) {
generate_character(userLanguage, chatData, player, entity); generate_character(userLanguage, chatData, player, entity);
} }
...@@ -84,7 +85,7 @@ public class ServerPackets { ...@@ -84,7 +85,7 @@ public class ServerPackets {
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F); TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString()); EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
LOGGER.debug("Update read lines to " + lineNumber + " for: " + entity.getType().toString()); LOGGER.debug("Update read lines to " + lineNumber + " for: " + entity.getType().toString());
chatData.setLineNumber(lineNumber); chatData.setLineNumber(lineNumber);
} }
...@@ -104,7 +105,7 @@ public class ServerPackets { ...@@ -104,7 +105,7 @@ public class ServerPackets {
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F); TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString()); EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
LOGGER.debug("Hiding chat bubble for: " + entity.getType().toString()); LOGGER.debug("Hiding chat bubble for: " + entity.getType().toString());
chatData.setStatus(ChatDataManager.ChatStatus.valueOf(status_name)); chatData.setStatus(ChatDataManager.ChatStatus.valueOf(status_name));
} }
...@@ -148,7 +149,7 @@ public class ServerPackets { ...@@ -148,7 +149,7 @@ public class ServerPackets {
server.execute(() -> { server.execute(() -> {
MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId); MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) { if (entity != null) {
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString()); EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
if (chatData.characterSheet.isEmpty()) { if (chatData.characterSheet.isEmpty()) {
generate_character(userLanguage, chatData, player, entity); generate_character(userLanguage, chatData, player, entity);
} else { } else {
...@@ -257,7 +258,7 @@ public class ServerPackets { ...@@ -257,7 +258,7 @@ public class ServerPackets {
} }
} }
public static void generate_character(String userLanguage, ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity) { public static void generate_character(String userLanguage, 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);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
...@@ -288,7 +289,7 @@ public class ServerPackets { ...@@ -288,7 +289,7 @@ public class ServerPackets {
chatData.generateMessage(userLanguage, player, "system-character", userMessageBuilder.toString(), false); chatData.generateMessage(userLanguage, player, "system-character", userMessageBuilder.toString(), false);
} }
public static void generate_chat(String userLanguage, ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity, String message, boolean is_auto_message) { public static void generate_chat(String userLanguage, EntityChatData chatData, ServerPlayerEntity player, MobEntity entity, String message, boolean is_auto_message) {
// 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);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
...@@ -299,7 +300,7 @@ public class ServerPackets { ...@@ -299,7 +300,7 @@ public class ServerPackets {
} }
// Send new message to all connected players // Send new message to all connected players
public static void BroadcastPacketMessage(ChatDataManager.EntityChatData chatData) { public static void BroadcastPacketMessage(EntityChatData chatData) {
for (ServerWorld world : serverInstance.getWorlds()) { for (ServerWorld world : serverInstance.getWorlds()) {
UUID entityId = UUID.fromString(chatData.entityId); UUID entityId = UUID.fromString(chatData.entityId);
MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(world, entityId); MobEntity entity = (MobEntity)ServerEntityFinder.getEntityByUUID(world, entityId);
......
...@@ -3,6 +3,7 @@ package com.owlmaddie.utils; ...@@ -3,6 +3,7 @@ package com.owlmaddie.utils;
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.chat.ChatMessage;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
...@@ -26,7 +27,7 @@ public class EntityTestData { ...@@ -26,7 +27,7 @@ public class EntityTestData {
public String currentMessage; public String currentMessage;
public int currentLineNumber; public int currentLineNumber;
public ChatDataManager.ChatStatus status; public ChatDataManager.ChatStatus status;
public List<ChatDataManager.ChatMessage> previousMessages; public List<ChatMessage> previousMessages;
public String characterSheet; public String characterSheet;
public ChatDataManager.ChatSender sender; public ChatDataManager.ChatSender sender;
public int friendship; // -3 to 3 (0 = neutral) public int friendship; // -3 to 3 (0 = neutral)
...@@ -64,7 +65,7 @@ public class EntityTestData { ...@@ -64,7 +65,7 @@ public class EntityTestData {
String truncatedMessage = message.substring(0, Math.min(message.length(), ChatDataManager.MAX_CHAR_IN_USER_MESSAGE)); String truncatedMessage = message.substring(0, Math.min(message.length(), ChatDataManager.MAX_CHAR_IN_USER_MESSAGE));
// Add message to history // Add message to history
previousMessages.add(new ChatDataManager.ChatMessage(truncatedMessage, messageSender)); previousMessages.add(new ChatMessage(truncatedMessage, messageSender));
// Set new message and reset line number of displayed text // Set new message and reset line number of displayed text
currentMessage = truncatedMessage; currentMessage = truncatedMessage;
......
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