Commit f67f7ad0 by Jonathan Thomas

Large refactor of clientPackets and serverPackets, to gather all network code…

Large refactor of clientPackets and serverPackets, to gather all network code into a more sane place.
parent 339c5bb7
Pipeline #12007 passed with stage
in 22 seconds
package com.owlmaddie;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.network.ClientPackets;
import com.owlmaddie.ui.BubbleRenderer;
import com.owlmaddie.ui.ClickHandler;
import com.owlmaddie.ui.PlayerMessageManager;
......@@ -23,7 +24,9 @@ public class ClientInit implements ClientModInitializer {
PlayerMessageManager.tickUpdate();
});
// Register events
ClickHandler.register();
ClientPackets.register();
// Register an event callback to render text bubbles
WorldRenderEvents.LAST.register((context) -> {
......
package com.owlmaddie.network;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.ui.PlayerMessageManager;
import com.owlmaddie.utils.ClientEntityFinder;
import com.owlmaddie.utils.Decompression;
import io.netty.buffer.Unpooled;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.entity.Entity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.network.PacketByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.UUID;
/**
* The {@code ClientPackets} class provides methods to send packets to/from the server for generating greetings,
* updating message details, and sending user messages.
*/
public class ClientPackets {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
static HashMap<Integer, byte[]> receivedChunks = new HashMap<>();
public static void sendGenerateGreeting(Entity entity) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
// Send C2S packet
ClientPlayNetworking.send(ServerPackets.PACKET_C2S_GREETING, buf);
}
public static void sendUpdateLineNumber(Entity entity, Integer lineNumber) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
buf.writeInt(lineNumber);
// Send C2S packet
ClientPlayNetworking.send(ServerPackets.PACKET_C2S_READ_NEXT, buf);
}
public static void sendStartChat(Entity entity) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
// Send C2S packet
ClientPlayNetworking.send(ServerPackets.PACKET_C2S_START_CHAT, buf);
}
public static void setChatStatus(Entity entity, ChatDataManager.ChatStatus new_status) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
buf.writeString(new_status.toString());
// Send C2S packet
ClientPlayNetworking.send(ServerPackets.PACKET_C2S_SET_STATUS, buf);
}
public static void sendChat(Entity entity, String message) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
buf.writeString(message);
// Send C2S packet
ClientPlayNetworking.send(ServerPackets.PACKET_C2S_SEND_CHAT, buf);
}
public static void register() {
// Client-side packet handler, message sync
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_MESSAGE, (client, handler, buffer, responseSender) -> {
// Read the data from the server packet
UUID entityId = UUID.fromString(buffer.readString());
String playerId = buffer.readString();
String message = buffer.readString(32767);
int line = buffer.readInt();
String status_name = buffer.readString(32767);
String sender_name = buffer.readString(32767);
int friendship = buffer.readInt();
// Update the chat data manager on the client-side
client.execute(() -> { // Make sure to run on the client thread
MobEntity entity = ClientEntityFinder.getEntityByUUID(client.world, entityId);
if (entity != null) {
ChatDataManager chatDataManager = ChatDataManager.getClientInstance();
ChatDataManager.EntityChatData chatData = chatDataManager.getOrCreateChatData(entity.getUuidAsString());
chatData.playerId = playerId;
if (!message.isEmpty()) {
chatData.currentMessage = message;
}
chatData.currentLineNumber = line;
chatData.status = ChatDataManager.ChatStatus.valueOf(status_name);
chatData.sender = ChatDataManager.ChatSender.valueOf(sender_name);
chatData.friendship = friendship;
if (chatData.sender == ChatDataManager.ChatSender.USER && !playerId.isEmpty()) {
// Add player message to queue for rendering
PlayerMessageManager.addMessage(UUID.fromString(chatData.playerId), chatData.currentMessage, ChatDataManager.TICKS_TO_DISPLAY_USER_MESSAGE);
}
}
});
});
// Client-side player login: get all chat data
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_LOGIN, (client, handler, buffer, responseSender) -> {
int sequenceNumber = buffer.readInt(); // Sequence number of the current packet
int totalPackets = buffer.readInt(); // Total number of packets for this data
byte[] chunk = buffer.readByteArray(); // Read the byte array chunk from the current packet
client.execute(() -> { // Make sure to run on the client thread
// Store the received chunk
receivedChunks.put(sequenceNumber, chunk);
// Check if all chunks have been received
if (receivedChunks.size() == totalPackets) {
LOGGER.info("Reassemble chunks on client and decompress lite JSON data string");
// Combine all byte array chunks
ByteArrayOutputStream combined = new ByteArrayOutputStream();
for (int i = 0; i < totalPackets; i++) {
combined.write(receivedChunks.get(i), 0, receivedChunks.get(i).length);
}
// Decompress the combined byte array to get the original JSON string
String chatDataJSON = Decompression.decompressString(combined.toByteArray());
if (chatDataJSON == null) {
LOGGER.info("Error decompressing lite JSON string from bytes");
return;
}
// Parse JSON and update client chat data
Gson GSON = new Gson();
Type type = new TypeToken<HashMap<String, ChatDataManager.EntityChatData>>(){}.getType();
ChatDataManager.getClientInstance().entityChatDataMap = GSON.fromJson(chatDataJSON, type);
// Clear receivedChunks for future use
receivedChunks.clear();
}
});
});
}
}
package com.owlmaddie.network;
import com.owlmaddie.ModInit;
import com.owlmaddie.chat.ChatDataManager;
import io.netty.buffer.Unpooled;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.entity.Entity;
import net.minecraft.network.PacketByteBuf;
/**
* The {@code ModPackets} class provides methods to send packets to the server for generating greetings,
* updating message details, and sending user messages.
*/
public class ModPackets {
public static void sendGenerateGreeting(Entity entity) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_GREETING, buf);
}
public static void sendUpdateLineNumber(Entity entity, Integer lineNumber) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
buf.writeInt(lineNumber);
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_READ_NEXT, buf);
}
public static void sendStartChat(Entity entity) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_START_CHAT, buf);
}
public static void setChatStatus(Entity entity, ChatDataManager.ChatStatus new_status) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
buf.writeString(new_status.toString());
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_SET_STATUS, buf);
}
public static void sendChat(Entity entity, String message) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeString(entity.getUuidAsString());
buf.writeString(message);
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_SEND_CHAT, buf);
}
}
package com.owlmaddie.ui;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.network.ModPackets;
import com.owlmaddie.network.ClientPackets;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
......@@ -72,7 +72,7 @@ public class ChatScreen extends Screen {
private void sendChatMessage() {
// Send message to server
String message = textField.getText();
ModPackets.sendChat(screenEntity, message);
ClientPackets.sendChat(screenEntity, message);
close();
}
......
package com.owlmaddie.ui;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.owlmaddie.ModInit;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.network.ModPackets;
import com.owlmaddie.network.ClientPackets;
import com.owlmaddie.utils.ClientEntityFinder;
import com.owlmaddie.utils.Decompression;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.render.Camera;
......@@ -21,9 +16,6 @@ import net.minecraft.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
......@@ -37,7 +29,6 @@ import java.util.stream.Stream;
public class ClickHandler {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
private static boolean wasClicked = false;
static HashMap<Integer, byte[]> receivedChunks = new HashMap<>();
public static void register() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
......@@ -52,78 +43,6 @@ public class ClickHandler {
wasClicked = false;
}
});
// Client-side packet handler, message sync
ClientPlayNetworking.registerGlobalReceiver(ModInit.PACKET_S2C_MESSAGE, (client, handler, buffer, responseSender) -> {
// Read the data from the server packet
UUID entityId = UUID.fromString(buffer.readString());
String playerId = buffer.readString();
String message = buffer.readString(32767);
int line = buffer.readInt();
String status_name = buffer.readString(32767);
String sender_name = buffer.readString(32767);
int friendship = buffer.readInt();
// Update the chat data manager on the client-side
client.execute(() -> { // Make sure to run on the client thread
MobEntity entity = ClientEntityFinder.getEntityByUUID(client.world, entityId);
if (entity != null) {
ChatDataManager chatDataManager = ChatDataManager.getClientInstance();
ChatDataManager.EntityChatData chatData = chatDataManager.getOrCreateChatData(entity.getUuidAsString());
chatData.playerId = playerId;
if (!message.isEmpty()) {
chatData.currentMessage = message;
}
chatData.currentLineNumber = line;
chatData.status = ChatDataManager.ChatStatus.valueOf(status_name);
chatData.sender = ChatDataManager.ChatSender.valueOf(sender_name);
chatData.friendship = friendship;
if (chatData.sender == ChatDataManager.ChatSender.USER && !playerId.isEmpty()) {
// Add player message to queue for rendering
PlayerMessageManager.addMessage(UUID.fromString(chatData.playerId), chatData.currentMessage, ChatDataManager.TICKS_TO_DISPLAY_USER_MESSAGE);
}
}
});
});
// Client-side player login: get all chat data
ClientPlayNetworking.registerGlobalReceiver(ModInit.PACKET_S2C_LOGIN, (client, handler, buffer, responseSender) -> {
int sequenceNumber = buffer.readInt(); // Sequence number of the current packet
int totalPackets = buffer.readInt(); // Total number of packets for this data
byte[] chunk = buffer.readByteArray(); // Read the byte array chunk from the current packet
client.execute(() -> { // Make sure to run on the client thread
// Store the received chunk
receivedChunks.put(sequenceNumber, chunk);
// Check if all chunks have been received
if (receivedChunks.size() == totalPackets) {
LOGGER.info("Reassemble chunks on client and decompress lite JSON data string");
// Combine all byte array chunks
ByteArrayOutputStream combined = new ByteArrayOutputStream();
for (int i = 0; i < totalPackets; i++) {
combined.write(receivedChunks.get(i), 0, receivedChunks.get(i).length);
}
// Decompress the combined byte array to get the original JSON string
String chatDataJSON = Decompression.decompressString(combined.toByteArray());
if (chatDataJSON == null) {
LOGGER.info("Error decompressing lite JSON string from bytes");
return;
}
// Parse JSON and update client chat data
Gson GSON = new Gson();
Type type = new TypeToken<HashMap<String, ChatDataManager.EntityChatData>>(){}.getType();
ChatDataManager.getClientInstance().entityChatDataMap = GSON.fromJson(chatDataJSON, type);
// Clear receivedChunks for future use
receivedChunks.clear();
}
});
});
}
public static void handleUseKeyClick(MinecraftClient client) {
......@@ -187,26 +106,26 @@ public class ClickHandler {
if (chatData.status == ChatDataManager.ChatStatus.NONE) {
// Start conversation
ModPackets.sendGenerateGreeting(closestEntity);
ClientPackets.sendGenerateGreeting(closestEntity);
} else if (chatData.status == ChatDataManager.ChatStatus.DISPLAY) {
if (hitRegion.equals("RIGHT") && !chatData.isEndOfMessage()) {
// Update lines read > next lines
ModPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber + ChatDataManager.DISPLAY_NUM_LINES);
ClientPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber + ChatDataManager.DISPLAY_NUM_LINES);
} else if (hitRegion.equals("LEFT") && chatData.currentLineNumber > 0) {
// Update lines read < previous lines
ModPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber - ChatDataManager.DISPLAY_NUM_LINES);
ClientPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber - ChatDataManager.DISPLAY_NUM_LINES);
} else if (hitRegion.equals("RIGHT") && chatData.isEndOfMessage()) {
// End of chat (open player chat screen)
ModPackets.sendStartChat(closestEntity);
ClientPackets.sendStartChat(closestEntity);
client.setScreen(new ChatScreen(closestEntity));
} else if (hitRegion.equals("TOP")) {
// Hide chat
ModPackets.setChatStatus(closestEntity, ChatDataManager.ChatStatus.HIDDEN);
ClientPackets.setChatStatus(closestEntity, ChatDataManager.ChatStatus.HIDDEN);
}
} else if (chatData.status == ChatDataManager.ChatStatus.HIDDEN) {
// Show chat
ModPackets.setChatStatus(closestEntity, ChatDataManager.ChatStatus.DISPLAY);
ClientPackets.setChatStatus(closestEntity, ChatDataManager.ChatStatus.DISPLAY);
}
}
......
package com.owlmaddie;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.ChatDataSaverScheduler;
import com.owlmaddie.commands.CreatureChatCommands;
import com.owlmaddie.goals.EntityBehaviorManager;
import com.owlmaddie.goals.GoalPriority;
import com.owlmaddie.goals.TalkPlayerGoal;
import com.owlmaddie.utils.Compression;
import com.owlmaddie.utils.LivingEntityInterface;
import com.owlmaddie.utils.RandomUtils;
import com.owlmaddie.utils.ServerEntityFinder;
import io.netty.buffer.Unpooled;
import com.owlmaddie.network.ServerPackets;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.Entity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* The {@code ModInit} class initializes this mod on the server and defines all the server message
* identifiers. It also listens for messages from the client, and has code to send
* messages to the client.
*/
public class ModInit implements ModInitializer {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
public static MinecraftServer serverInstance;
private static ChatDataSaverScheduler scheduler = null;
public static final Identifier PACKET_C2S_GREETING = new Identifier("creaturechat", "packet_c2s_greeting");
public static final Identifier PACKET_C2S_READ_NEXT = new Identifier("creaturechat", "packet_c2s_read_next");
public static final Identifier PACKET_C2S_SET_STATUS = new Identifier("creaturechat", "packet_c2s_set_status");
public static final Identifier PACKET_C2S_START_CHAT = new Identifier("creaturechat", "packet_c2s_start_chat");
public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("creaturechat", "packet_c2s_send_chat");
public static final Identifier PACKET_S2C_MESSAGE = new Identifier("creaturechat", "packet_s2c_message");
public static final Identifier PACKET_S2C_LOGIN = new Identifier("creaturechat", "packet_s2c_login");
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
@Override
public void onInitialize() {
......@@ -58,238 +23,9 @@ public class ModInit implements ModInitializer {
// Register server commands
CreatureChatCommands.register();
// Handle packet for Greeting
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_GREETING, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
if (chatData.characterSheet.isEmpty()) {
generate_character(chatData, player, entity);
}
}
});
});
// Handle packet for reading lines of message
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_READ_NEXT, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
int lineNumber = buf.readInt();
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
LOGGER.info("Update read lines to " + lineNumber + " for: " + entity.getType().toString());
chatData.setLineNumber(lineNumber);
}
});
});
// Handle packet for setting status of chat bubbles
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_SET_STATUS, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
String status_name = buf.readString(32767);
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
LOGGER.info("Hiding chat bubble for: " + entity.getType().toString());
chatData.setStatus(ChatDataManager.ChatStatus.valueOf(status_name));
}
});
});
// Handle packet for Start Chat
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_START_CHAT, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 7F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
}
});
});
// Handle packet for new chat message
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_SEND_CHAT, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
String message = buf.readString(32767);
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
if (chatData.characterSheet.isEmpty()) {
generate_character(chatData, player, entity);
} else {
generate_chat(chatData, player, entity, message);
}
}
});
});
// Send lite chat data JSON to new player (to populate client data)
// 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());
// Get lite JSON data & compress to byte array
String chatDataJSON = ChatDataManager.getServerInstance().GetLightChatData();
byte[] compressedData = Compression.compressString(chatDataJSON);
if (compressedData == null) {
LOGGER.error("Failed to compress chat data.");
return;
}
final int chunkSize = 32000; // Define chunk size
int totalPackets = (int) Math.ceil((double) compressedData.length / chunkSize);
// Loop through each chunk of bytes, and send bytes to player
for (int i = 0; i < totalPackets; i++) {
int start = i * chunkSize;
int end = Math.min(compressedData.length, start + chunkSize);
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeInt(i); // Packet sequence number
buffer.writeInt(totalPackets); // Total number of packets
// Write chunk as byte array
byte[] chunk = Arrays.copyOfRange(compressedData, start, end);
buffer.writeByteArray(chunk);
ServerPlayNetworking.send(player, PACKET_S2C_LOGIN, buffer);
}
});
ServerWorldEvents.LOAD.register((server, world) -> {
String world_name = world.getRegistryKey().getValue().getPath();
if (world_name.equals("overworld")) {
serverInstance = server;
ChatDataManager.getServerInstance().loadChatData(server);
// Start the auto-save task to save every X minutes
scheduler = new ChatDataSaverScheduler();
scheduler.startAutoSaveTask(server, 15, TimeUnit.MINUTES);
}
});
ServerWorldEvents.UNLOAD.register((server, world) -> {
String world_name = world.getRegistryKey().getValue().getPath();
if (world_name == "overworld") {
ChatDataManager.getServerInstance().saveChatData(server);
serverInstance = null;
// Shutdown auto scheduler
scheduler.stopAutoSaveTask();
}
});
ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> {
String entityUUID = entity.getUuidAsString();
if (ChatDataManager.getServerInstance().entityChatDataMap.containsKey(entityUUID)) {
int friendship = ChatDataManager.getServerInstance().entityChatDataMap.get(entityUUID).friendship;
if (friendship > 0) {
LOGGER.info("Entity loaded (" + entityUUID + "), setting friendship to " + friendship);
((LivingEntityInterface)entity).setCanTargetPlayers(false);
}
}
});
ServerEntityEvents.ENTITY_UNLOAD.register((entity, world) -> {
String entityUUID = entity.getUuidAsString();
if (entity.getRemovalReason() == Entity.RemovalReason.KILLED && ChatDataManager.getServerInstance().entityChatDataMap.containsKey(entityUUID)) {
LOGGER.info("Entity killed (" + entityUUID + "), removing chat data.");
ChatDataManager.getServerInstance().entityChatDataMap.remove(entityUUID);
}
});
// Register events
ServerPackets.register();
LOGGER.info("CreatureChat MOD Initialized!");
}
public static void generate_character(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);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
// Only generate a new greeting if not already doing so
String player_biome = player.getWorld().getBiome(player.getBlockPos()).getKey().get().getValue().getPath();
StringBuilder userMessageBuilder = new StringBuilder();
userMessageBuilder.append("Please generate a new character ");
if (entity.getCustomName() != null && !entity.getCustomName().getLiteralString().equals("N/A")) {
userMessageBuilder.append("named '").append(entity.getCustomName().getLiteralString()).append("' ");
} else {
userMessageBuilder.append("whose name starts with the letter '").append(RandomUtils.RandomLetter()).append("' ");
userMessageBuilder.append("and which uses ").append(RandomUtils.RandomNumber(4) + 1).append(" syllables ");
}
userMessageBuilder.append("of type '").append(entity.getType().getUntranslatedName().toLowerCase(Locale.ROOT)).append("' ");
userMessageBuilder.append("who lives near the ").append(player_biome).append(".");
LOGGER.info(userMessageBuilder.toString());
chatData.generateMessage(player, "system-character", userMessageBuilder.toString());
}
public static void generate_chat(ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity, String message) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
// Add new message
LOGGER.info("Player message received: " + message + " | Entity: " + entity.getType().toString());
chatData.generateMessage(player, "system-chat", message);
}
// Send new message to all connected players
public static void BroadcastPacketMessage(ChatDataManager.EntityChatData chatData) {
for (ServerWorld world : serverInstance.getWorlds()) {
UUID entityId = UUID.fromString(chatData.entityId);
MobEntity entity = ServerEntityFinder.getEntityByUUID(world, entityId);
if (entity != null) {
// Set custom name (if null)
String characterName = chatData.getCharacterProp("name");
if (!characterName.isEmpty() && !characterName.equals("N/A") && entity.getCustomName() == null) {
LOGGER.info("Setting entity name to " + characterName + " for " + chatData.entityId);
entity.setCustomName(Text.literal(characterName));
entity.setCustomNameVisible(true);
}
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
// Write the entity's chat updated data
buffer.writeString(chatData.entityId);
buffer.writeString(chatData.playerId);
buffer.writeString(chatData.currentMessage);
buffer.writeInt(chatData.currentLineNumber);
buffer.writeString(chatData.status.toString());
buffer.writeString(chatData.sender.toString());
buffer.writeInt(chatData.friendship);
// Iterate over all players and send the packet
for (ServerPlayerEntity player : serverInstance.getPlayerManager().getPlayerList()) {
LOGGER.info("Server broadcast message to client: " + player.getName().getString() + " | Message: " + chatData.currentMessage);
ServerPlayNetworking.send(player, PACKET_S2C_MESSAGE, buffer);
}
break;
}
}
}
}
\ No newline at end of file
......@@ -2,7 +2,6 @@ package com.owlmaddie.chat;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.owlmaddie.ModInit;
import com.owlmaddie.controls.SpeedControls;
import com.owlmaddie.goals.*;
import com.owlmaddie.items.RarityItemCollector;
......@@ -10,6 +9,7 @@ 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.utils.LivingEntityInterface;
import com.owlmaddie.utils.ServerEntityFinder;
import net.minecraft.entity.mob.MobEntity;
......@@ -303,7 +303,7 @@ public class ChatDataManager {
}
// Broadcast to all players
ModInit.BroadcastPacketMessage(this);
ServerPackets.BroadcastPacketMessage(this);
});
}
......@@ -329,7 +329,7 @@ public class ChatDataManager {
this.playerId = playerId;
// Broadcast to all players
ModInit.BroadcastPacketMessage(this);
ServerPackets.BroadcastPacketMessage(this);
}
// Get wrapped lines
......@@ -349,14 +349,14 @@ public class ChatDataManager {
currentLineNumber = Math.min(Math.max(lineNumber, 0), totalLines);
// Broadcast to all players
ModInit.BroadcastPacketMessage(this);
ServerPackets.BroadcastPacketMessage(this);
}
public void setStatus(ChatStatus new_status) {
status = new_status;
// Broadcast to all players
ModInit.BroadcastPacketMessage(this);
ServerPackets.BroadcastPacketMessage(this);
}
}
......
package com.owlmaddie.chat;
import com.google.gson.Gson;
import com.owlmaddie.ModInit;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.json.ChatGPTResponse;
import com.owlmaddie.network.ServerPackets;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import org.slf4j.Logger;
......@@ -99,7 +99,7 @@ public class ChatGPTRequest {
public static CompletableFuture<String> fetchMessageFromChatGPT(String systemPrompt, Map<String, String> context, List<ChatDataManager.ChatMessage> messageHistory, Boolean jsonMode) {
// Get config (api key, url, settings)
ConfigurationHandler.Config config = new ConfigurationHandler(ModInit.serverInstance).loadConfig();
ConfigurationHandler.Config config = new ConfigurationHandler(ServerPackets.serverInstance).loadConfig();
// Init API & LLM details
String apiUrl = config.getUrl();
......@@ -113,7 +113,7 @@ public class ChatGPTRequest {
try {
String systemMessage = "";
if (systemPrompt != null && !systemPrompt.isEmpty()) {
systemMessage = loadPromptFromResource(ModInit.serverInstance.getResourceManager(), "prompts/" + systemPrompt);
systemMessage = loadPromptFromResource(ServerPackets.serverInstance.getResourceManager(), "prompts/" + systemPrompt);
systemMessage = replacePlaceholders(systemMessage, context);
}
......
package com.owlmaddie.network;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.chat.ChatDataSaverScheduler;
import com.owlmaddie.goals.EntityBehaviorManager;
import com.owlmaddie.goals.GoalPriority;
import com.owlmaddie.goals.TalkPlayerGoal;
import com.owlmaddie.utils.Compression;
import com.owlmaddie.utils.LivingEntityInterface;
import com.owlmaddie.utils.RandomUtils;
import com.owlmaddie.utils.ServerEntityFinder;
import io.netty.buffer.Unpooled;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.Entity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* The {@code ServerPackets} class provides methods to send packets to/from the client for generating greetings,
* updating message details, and sending user messages.
*/
public class ServerPackets {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
public static MinecraftServer serverInstance;
private static ChatDataSaverScheduler scheduler = null;
public static final Identifier PACKET_C2S_GREETING = new Identifier("creaturechat", "packet_c2s_greeting");
public static final Identifier PACKET_C2S_READ_NEXT = new Identifier("creaturechat", "packet_c2s_read_next");
public static final Identifier PACKET_C2S_SET_STATUS = new Identifier("creaturechat", "packet_c2s_set_status");
public static final Identifier PACKET_C2S_START_CHAT = new Identifier("creaturechat", "packet_c2s_start_chat");
public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("creaturechat", "packet_c2s_send_chat");
public static final Identifier PACKET_S2C_MESSAGE = new Identifier("creaturechat", "packet_s2c_message");
public static final Identifier PACKET_S2C_LOGIN = new Identifier("creaturechat", "packet_s2c_login");
public static void register() {
// Handle packet for Greeting
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_GREETING, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
if (chatData.characterSheet.isEmpty()) {
generate_character(chatData, player, entity);
}
}
});
});
// Handle packet for reading lines of message
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_READ_NEXT, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
int lineNumber = buf.readInt();
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
LOGGER.info("Update read lines to " + lineNumber + " for: " + entity.getType().toString());
chatData.setLineNumber(lineNumber);
}
});
});
// Handle packet for setting status of chat bubbles
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_SET_STATUS, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
String status_name = buf.readString(32767);
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
LOGGER.info("Hiding chat bubble for: " + entity.getType().toString());
chatData.setStatus(ChatDataManager.ChatStatus.valueOf(status_name));
}
});
});
// Handle packet for Start Chat
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_START_CHAT, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 7F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
}
});
});
// Handle packet for new chat message
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_SEND_CHAT, (server, player, handler, buf, responseSender) -> {
UUID entityId = UUID.fromString(buf.readString());
String message = buf.readString(32767);
// Ensure that the task is synced with the server thread
server.execute(() -> {
MobEntity entity = ServerEntityFinder.getEntityByUUID(player.getServerWorld(), entityId);
if (entity != null) {
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entity.getUuidAsString());
if (chatData.characterSheet.isEmpty()) {
generate_character(chatData, player, entity);
} else {
generate_chat(chatData, player, entity, message);
}
}
});
});
// Send lite chat data JSON to new player (to populate client data)
// 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());
// Get lite JSON data & compress to byte array
String chatDataJSON = ChatDataManager.getServerInstance().GetLightChatData();
byte[] compressedData = Compression.compressString(chatDataJSON);
if (compressedData == null) {
LOGGER.error("Failed to compress chat data.");
return;
}
final int chunkSize = 32000; // Define chunk size
int totalPackets = (int) Math.ceil((double) compressedData.length / chunkSize);
// Loop through each chunk of bytes, and send bytes to player
for (int i = 0; i < totalPackets; i++) {
int start = i * chunkSize;
int end = Math.min(compressedData.length, start + chunkSize);
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeInt(i); // Packet sequence number
buffer.writeInt(totalPackets); // Total number of packets
// Write chunk as byte array
byte[] chunk = Arrays.copyOfRange(compressedData, start, end);
buffer.writeByteArray(chunk);
ServerPlayNetworking.send(player, PACKET_S2C_LOGIN, buffer);
}
});
ServerWorldEvents.LOAD.register((server, world) -> {
String world_name = world.getRegistryKey().getValue().getPath();
if (world_name.equals("overworld")) {
serverInstance = server;
ChatDataManager.getServerInstance().loadChatData(server);
// Start the auto-save task to save every X minutes
scheduler = new ChatDataSaverScheduler();
scheduler.startAutoSaveTask(server, 15, TimeUnit.MINUTES);
}
});
ServerWorldEvents.UNLOAD.register((server, world) -> {
String world_name = world.getRegistryKey().getValue().getPath();
if (world_name == "overworld") {
ChatDataManager.getServerInstance().saveChatData(server);
serverInstance = null;
// Shutdown auto scheduler
scheduler.stopAutoSaveTask();
}
});
ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> {
String entityUUID = entity.getUuidAsString();
if (ChatDataManager.getServerInstance().entityChatDataMap.containsKey(entityUUID)) {
int friendship = ChatDataManager.getServerInstance().entityChatDataMap.get(entityUUID).friendship;
if (friendship > 0) {
LOGGER.info("Entity loaded (" + entityUUID + "), setting friendship to " + friendship);
((LivingEntityInterface)entity).setCanTargetPlayers(false);
}
}
});
ServerEntityEvents.ENTITY_UNLOAD.register((entity, world) -> {
String entityUUID = entity.getUuidAsString();
if (entity.getRemovalReason() == Entity.RemovalReason.KILLED && ChatDataManager.getServerInstance().entityChatDataMap.containsKey(entityUUID)) {
LOGGER.info("Entity killed (" + entityUUID + "), removing chat data.");
ChatDataManager.getServerInstance().entityChatDataMap.remove(entityUUID);
}
});
}
public static void generate_character(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);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
// Only generate a new greeting if not already doing so
String player_biome = player.getWorld().getBiome(player.getBlockPos()).getKey().get().getValue().getPath();
StringBuilder userMessageBuilder = new StringBuilder();
userMessageBuilder.append("Please generate a new character ");
if (entity.getCustomName() != null && !entity.getCustomName().getLiteralString().equals("N/A")) {
userMessageBuilder.append("named '").append(entity.getCustomName().getLiteralString()).append("' ");
} else {
userMessageBuilder.append("whose name starts with the letter '").append(RandomUtils.RandomLetter()).append("' ");
userMessageBuilder.append("and which uses ").append(RandomUtils.RandomNumber(4) + 1).append(" syllables ");
}
userMessageBuilder.append("of type '").append(entity.getType().getUntranslatedName().toLowerCase(Locale.ROOT)).append("' ");
userMessageBuilder.append("who lives near the ").append(player_biome).append(".");
LOGGER.info(userMessageBuilder.toString());
chatData.generateMessage(player, "system-character", userMessageBuilder.toString());
}
public static void generate_chat(ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity, String message) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
// Add new message
LOGGER.info("Player message received: " + message + " | Entity: " + entity.getType().toString());
chatData.generateMessage(player, "system-chat", message);
}
// Send new message to all connected players
public static void BroadcastPacketMessage(ChatDataManager.EntityChatData chatData) {
for (ServerWorld world : serverInstance.getWorlds()) {
UUID entityId = UUID.fromString(chatData.entityId);
MobEntity entity = ServerEntityFinder.getEntityByUUID(world, entityId);
if (entity != null) {
// Set custom name (if null)
String characterName = chatData.getCharacterProp("name");
if (!characterName.isEmpty() && !characterName.equals("N/A") && entity.getCustomName() == null) {
LOGGER.info("Setting entity name to " + characterName + " for " + chatData.entityId);
entity.setCustomName(Text.literal(characterName));
entity.setCustomNameVisible(true);
}
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
// Write the entity's chat updated data
buffer.writeString(chatData.entityId);
buffer.writeString(chatData.playerId);
buffer.writeString(chatData.currentMessage);
buffer.writeInt(chatData.currentLineNumber);
buffer.writeString(chatData.status.toString());
buffer.writeString(chatData.sender.toString());
buffer.writeInt(chatData.friendship);
// Iterate over all players and send the packet
for (ServerPlayerEntity player : serverInstance.getPlayerManager().getPlayerList()) {
LOGGER.info("Server broadcast message to client: " + player.getName().getString() + " | Message: " + chatData.currentMessage);
ServerPlayNetworking.send(player, PACKET_S2C_MESSAGE, buffer);
}
break;
}
}
}
}
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