Commit a9c7b69f by Jonathan Thomas

Merge branch 'player-messages' into 'develop'

Chat messages are now displayed in chat bubbles above players heads

See merge request !27
parents 65463816 021e2246
Pipeline #13289 passed with stages
in 7 minutes 55 seconds
...@@ -4,18 +4,26 @@ All notable changes to **CreatureChat** are documented in this file. The format ...@@ -4,18 +4,26 @@ All notable changes to **CreatureChat** are documented in this file. The format
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## [1.3.0] - 2025-01-14
### Added ### Added
- Player Icons (custom art embedded in player skin) - In-game chat messages are now displayed in chat bubbles above players heads!
- New Step-by-Step **Icon** Tutorial: [ICON.md](ICONS.md) - Custom player icons (icons can be embedded in player skin file)
- New mixin to extend PlayerSkinTexture to make a copy of the NativeImage + pixel toggle to enable - Step-by-Step **Icon** Tutorial: [ICON.md](ICONS.md)
- Mixin to extend PlayerSkinTexture to make a copy of the NativeImage + pixel toggle to enable
- New command `/creaturechat chatbubbles set <on | off>` to show or hide player chat messages in bubbles
- Improved LLM Unit tests (to prevent rate limit issues from certain providers when running all tests) - Improved LLM Unit tests (to prevent rate limit issues from certain providers when running all tests)
- Check friendship direction (+ or -) in LLM unit tests (to verify friendship direction is output correctly) - Check friendship direction (+ or -) in LLM unit tests (to verify friendship direction is output correctly)
### Changed
- Seperated Player and Entity message broadcasts (different packets for simplicity)
- Reduced size of player skin face on chat bubble, to match sizes of custom icons (for consistency)
- Updated entity icons for allay, creeper, and pig
### Fixed ### Fixed
- Fixed death messages for mobs with no chat data - Hide death messages for mobs with no chat data
- Fixed transparent background behind chat screen for Minecraft 1.20 and 1.20.1. - Fixed transparent background behind chat screen for Minecraft 1.20 and 1.20.1.
- Removed extra message broadcast (which was unnecessary)
## [1.2.1] - 2025-01-01 ## [1.2.1] - 2025-01-01
......
# Icon Tutorial for CreatureChat # Icon Tutorial for CreatureChat
<img src="src/main/resources/assets/creaturechat/screenshots/custom-player-icon1.png" width="100%" style="image-rendering: pixelated;"> <img src="src/main/resources/assets/creaturechat/screenshots/side-by-side-icons.png" width="100%" style="image-rendering: pixelated;">
### Customize entity and player icons in **CreatureChat** by following this step-by-step guide. ### Customize entity and player icons in **CreatureChat** by following this step-by-step guide.
...@@ -65,9 +65,9 @@ Here are the full list of coordinates for the custom player icon UV. ...@@ -65,9 +65,9 @@ Here are the full list of coordinates for the custom player icon UV.
``` ```
UV_COORDINATES = [ UV_COORDINATES = [
[0.0, 0.0, 8.0, 8.0, 0.0, 0.0], # row 1 left [0.0, 0.0, 8.0, 8.0, 0.0, 0.0], # row 1 left
[24.0, 0.0, 32.0, 8.0, 8.0, 0.0], # row 1 middle [24.0, 0.0, 32.0, 8.0, 8.0, 0.0], # row 1 middle
[32.0, 0.0, 40.0, 8.0, 16.0, 0.0], # row 1 right [32.0, 0.0, 40.0, 8.0, 16.0, 0.0], # row 1 right
[56.0, 0.0, 64.0, 8.0, 0.0, 8.0], # row 2 left [56.0, 0.0, 64.0, 8.0, 0.0, 8.0], # row 2 left
[56.0, 20.0, 64.0, 28.0, 8.0, 8.0], # row 2 middle [56.0, 20.0, 64.0, 28.0, 8.0, 8.0], # row 2 middle
...@@ -76,7 +76,8 @@ UV_COORDINATES = [ ...@@ -76,7 +76,8 @@ UV_COORDINATES = [
[56.0, 28.0, 64.0, 36.0, 0.0, 16.0], # row 3 left [56.0, 28.0, 64.0, 36.0, 0.0, 16.0], # row 3 left
[56.0, 36.0, 64.0, 44.0, 8.0, 16.0], # row 3 middle [56.0, 36.0, 64.0, 44.0, 8.0, 16.0], # row 3 middle
[56.0, 44.0, 64.0, 52.0, 16.0, 16.0], # row 3 right [56.0, 44.0, 64.0, 48.0, 16.0, 16.0], # row 3 top right
[12.0, 48.0, 20.0, 52.0, 16.0, 20.0], # row 3 bottom right
] ]
``` ```
......
...@@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G ...@@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
org.gradle.parallel=true org.gradle.parallel=true
# Mod Properties # Mod Properties
mod_version=1.2.1 mod_version=1.3.0
maven_group=com.owlmaddie maven_group=com.owlmaddie
archives_base_name=creaturechat archives_base_name=creaturechat
......
...@@ -108,17 +108,9 @@ public class ClientPackets { ...@@ -108,17 +108,9 @@ public class ClientPackets {
public static void register() { public static void register() {
// Client-side packet handler, message sync // Client-side packet handler, message sync
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_MESSAGE, (client, handler, buffer, responseSender) -> { ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_ENTITY_MESSAGE, (client, handler, buffer, responseSender) -> {
// Read the data from the server packet // Read the data from the server packet
UUID entityId = UUID.fromString(buffer.readString()); UUID entityId = UUID.fromString(buffer.readString());
String sendingPlayerIdStr = buffer.readString(32767);
String senderPlayerName = buffer.readString(32767);
UUID senderPlayerId;
if (!sendingPlayerIdStr.isEmpty()) {
senderPlayerId = UUID.fromString(sendingPlayerIdStr);
} else {
senderPlayerId = null;
}
String message = buffer.readString(32767); String message = buffer.readString(32767);
int line = buffer.readInt(); int line = buffer.readInt();
String status_name = buffer.readString(32767); String status_name = buffer.readString(32767);
...@@ -146,27 +138,40 @@ public class ClientPackets { ...@@ -146,27 +138,40 @@ public class ClientPackets {
ChatDataManager chatDataManager = ChatDataManager.getClientInstance(); ChatDataManager chatDataManager = ChatDataManager.getClientInstance();
EntityChatData chatData = chatDataManager.getOrCreateChatData(entity.getUuidAsString()); EntityChatData chatData = chatDataManager.getOrCreateChatData(entity.getUuidAsString());
if (senderPlayerId != null && sender == ChatDataManager.ChatSender.USER && status == ChatDataManager.ChatStatus.DISPLAY) { // Add entity message
// Add player message to queue for rendering if (!message.isEmpty()) {
PlayerMessageManager.addMessage(senderPlayerId, message, senderPlayerName, ChatDataManager.TICKS_TO_DISPLAY_USER_MESSAGE); chatData.currentMessage = message;
chatData.status = ChatDataManager.ChatStatus.PENDING;
} else {
// Add entity message
if (!message.isEmpty()) {
chatData.currentMessage = message;
}
chatData.currentLineNumber = line;
chatData.status = status;
chatData.sender = sender;
chatData.players = players;
} }
chatData.currentLineNumber = line;
chatData.status = status;
chatData.sender = sender;
chatData.players = players;
// Play sound with volume based on distance (from player or entity) // Play sound with volume based on distance (from player or entity)
playNearbyUISound(client, entity, 0.2f); playNearbyUISound(client, entity, 0.2f);
}); });
}); });
// Client-side packet handler, message sync
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_PLAYER_MESSAGE, (client, handler, buffer, responseSender) -> {
// Read the data from the server packet
UUID senderPlayerId = UUID.fromString(buffer.readString());
String senderPlayerName = buffer.readString(32767);
String message = buffer.readString(32767);
// Update the chat data manager on the client-side
client.execute(() -> { // Make sure to run on the client thread
// Ensure client.player is initialized
if (client.player == null || client.world == null) {
LOGGER.warn("Client not fully initialized. Dropping message for sender '{}'.", senderPlayerId);
return;
}
// Add player message to queue for rendering
PlayerMessageManager.addMessage(senderPlayerId, message, senderPlayerName, ChatDataManager.TICKS_TO_DISPLAY_USER_MESSAGE);
});
});
// Client-side player login: get all chat data // Client-side player login: get all chat data
ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_LOGIN, (client, handler, buffer, responseSender) -> { ClientPlayNetworking.registerGlobalReceiver(ServerPackets.PACKET_S2C_LOGIN, (client, handler, buffer, responseSender) -> {
int sequenceNumber = buffer.readInt(); // Sequence number of the current packet int sequenceNumber = buffer.readInt(); // Sequence number of the current packet
......
...@@ -260,7 +260,8 @@ public class BubbleRenderer { ...@@ -260,7 +260,8 @@ public class BubbleRenderer {
{56.0F, 16.0F, 64.0F, 20.0F, 16F, 12F},// Row 2 right bottom {56.0F, 16.0F, 64.0F, 20.0F, 16F, 12F},// Row 2 right bottom
{56.0F, 28.0F, 64.0F, 36.0F, 0F, 16F}, // Row 3 left {56.0F, 28.0F, 64.0F, 36.0F, 0F, 16F}, // Row 3 left
{56.0F, 36.0F, 64.0F, 44.0F, 8F, 16F}, // Row 3 middle {56.0F, 36.0F, 64.0F, 44.0F, 8F, 16F}, // Row 3 middle
{56.0F, 44.0F, 64.0F, 52.0F, 16F, 16F},// Row 3 right {56.0F, 44.0F, 64.0F, 48, 16F, 16F}, // Row 3 top right
{12.0F, 48.0F, 20.0F, 52, 16F, 20F}, // Row 3 bottom right
}; };
float scaleFactor = 0.77F; float scaleFactor = 0.77F;
...@@ -287,6 +288,12 @@ public class BubbleRenderer { ...@@ -287,6 +288,12 @@ public class BubbleRenderer {
.color(255, 255, 255, 255).texture(newU1, newV1).light(light).overlay(overlay).next(); .color(255, 255, 255, 255).texture(newU1, newV1).light(light).overlay(overlay).next();
} }
} else { } else {
// make skin appear smaller and centered
x += 2;
y += 2;
width -= 4;
height -= 4;
// Normal face coordinates // Normal face coordinates
float u1 = 8.0F / 64.0F; float u1 = 8.0F / 64.0F;
float v1 = 8.0F / 64.0F; float v1 = 8.0F / 64.0F;
......
...@@ -85,7 +85,7 @@ public class ChatDataManager { ...@@ -85,7 +85,7 @@ public class ChatDataManager {
LOGGER.info("Updated chat data from UUID (" + oldUUID + ") to UUID (" + newUUID + ")"); LOGGER.info("Updated chat data from UUID (" + oldUUID + ") to UUID (" + newUUID + ")");
// Broadcast to all players // Broadcast to all players
ServerPackets.BroadcastPacketMessage(data, null); ServerPackets.BroadcastEntityMessage(data);
} else { } else {
LOGGER.info("Unable to update chat data, UUID not found: " + oldUUID); LOGGER.info("Unable to update chat data, UUID not found: " + oldUUID);
} }
......
...@@ -525,16 +525,18 @@ public class EntityChatData { ...@@ -525,16 +525,18 @@ public class EntityChatData {
} }
} }
// Add ASSISTANT message to history
this.addMessage(result.getOriginalMessage(), ChatDataManager.ChatSender.ASSISTANT, player, systemPrompt);
// Get cleaned message (i.e. no <BEHAVIOR> strings) // Get cleaned message (i.e. no <BEHAVIOR> strings)
String cleanedMessage = result.getCleanedMessage(); String cleanedMessage = result.getCleanedMessage();
if (cleanedMessage.isEmpty()) { if (cleanedMessage.isEmpty()) {
cleanedMessage = Randomizer.getRandomMessage(Randomizer.RandomType.NO_RESPONSE); cleanedMessage = Randomizer.getRandomMessage(Randomizer.RandomType.NO_RESPONSE);
} }
// Update the current message to a 'cleaned version'
this.currentMessage = cleanedMessage; // Add ASSISTANT message to history
this.addMessage(cleanedMessage, ChatDataManager.ChatSender.ASSISTANT, player, systemPrompt);
// Update the last entry in previousMessages to use the original message
this.previousMessages.set(this.previousMessages.size() - 1,
new ChatMessage(result.getOriginalMessage(), ChatDataManager.ChatSender.ASSISTANT, player.getDisplayName().getString()));
} else { } else {
// Error / No Chat Message (Failure) // Error / No Chat Message (Failure)
...@@ -556,9 +558,6 @@ public class EntityChatData { ...@@ -556,9 +558,6 @@ public class EntityChatData {
previousMessages.clear(); previousMessages.clear();
} }
} }
// Broadcast to all players
ServerPackets.BroadcastPacketMessage(this, player);
}); });
} }
...@@ -603,15 +602,17 @@ public class EntityChatData { ...@@ -603,15 +602,17 @@ public class EntityChatData {
// Determine status for message // Determine status for message
if (sender == ChatDataManager.ChatSender.ASSISTANT) { if (sender == ChatDataManager.ChatSender.ASSISTANT) {
status = ChatDataManager.ChatStatus.DISPLAY; status = ChatDataManager.ChatStatus.DISPLAY;
} else if (sender == ChatDataManager.ChatSender.USER && systemPrompt.equals("system-chat")) {
// Only show system-chat messages above players (not system-character ones)
status = ChatDataManager.ChatStatus.DISPLAY;
} else { } else {
status = ChatDataManager.ChatStatus.PENDING; status = ChatDataManager.ChatStatus.PENDING;
} }
// Broadcast to all players if (sender == ChatDataManager.ChatSender.USER && systemPrompt.equals("system-chat") && auto_generated == 0) {
ServerPackets.BroadcastPacketMessage(this, player); // Broadcast new player message (when not auto-generated)
ServerPackets.BroadcastPlayerMessage(this, player);
}
// Broadcast new entity message status (i.e. pending)
ServerPackets.BroadcastEntityMessage(this);
} }
// Get wrapped lines // Get wrapped lines
...@@ -631,13 +632,13 @@ public class EntityChatData { ...@@ -631,13 +632,13 @@ public class EntityChatData {
currentLineNumber = Math.min(Math.max(lineNumber, 0), totalLines); currentLineNumber = Math.min(Math.max(lineNumber, 0), totalLines);
// Broadcast to all players // Broadcast to all players
ServerPackets.BroadcastPacketMessage(this, null); ServerPackets.BroadcastEntityMessage(this);
} }
public void setStatus(ChatDataManager.ChatStatus new_status) { public void setStatus(ChatDataManager.ChatStatus new_status) {
status = new_status; status = new_status;
// Broadcast to all players // Broadcast to all players
ServerPackets.BroadcastPacketMessage(this, null); ServerPackets.BroadcastEntityMessage(this);
} }
} }
\ No newline at end of file
...@@ -71,6 +71,7 @@ public class ConfigurationHandler { ...@@ -71,6 +71,7 @@ public class ConfigurationHandler {
private int maxOutputTokens = 200; private int maxOutputTokens = 200;
private double percentOfContext = 0.75; private double percentOfContext = 0.75;
private int timeout = 10; private int timeout = 10;
private boolean chatBubbles = true;
private List<String> whitelist = new ArrayList<>(); private List<String> whitelist = new ArrayList<>();
private List<String> blacklist = new ArrayList<>(); private List<String> blacklist = new ArrayList<>();
private String story = ""; private String story = "";
...@@ -114,5 +115,9 @@ public class ConfigurationHandler { ...@@ -114,5 +115,9 @@ public class ConfigurationHandler {
public String getStory() { return story; } public String getStory() { return story; }
public void setStory(String story) { this.story = story; } public void setStory(String story) { this.story = story; }
// Add getter and setter
public boolean getChatBubbles() { return chatBubbles; }
public void setChatBubbles(boolean chatBubblesEnabled) { this.chatBubbles = chatBubblesEnabled; }
} }
} }
...@@ -49,6 +49,7 @@ public class CreatureChatCommands { ...@@ -49,6 +49,7 @@ public class CreatureChatCommands {
.then(registerStoryCommand()) .then(registerStoryCommand())
.then(registerWhitelistCommand()) .then(registerWhitelistCommand())
.then(registerBlacklistCommand()) .then(registerBlacklistCommand())
.then(registerChatBubbleCommand())
.then(registerHelpCommand())); .then(registerHelpCommand()));
} }
...@@ -95,6 +96,35 @@ public class CreatureChatCommands { ...@@ -95,6 +96,35 @@ public class CreatureChatCommands {
.map(Identifier::toString) .map(Identifier::toString)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private static LiteralArgumentBuilder<ServerCommandSource> registerChatBubbleCommand() {
return CommandManager.literal("chatbubble")
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set")
.then(CommandManager.literal("on")
.then(addConfigArgs((context, useServerConfig) -> setChatBubbleEnabled(context, true, useServerConfig)))
.executes(context -> setChatBubbleEnabled(context, true, false)))
.then(CommandManager.literal("off")
.then(addConfigArgs((context, useServerConfig) -> setChatBubbleEnabled(context, false, useServerConfig)))
.executes(context -> setChatBubbleEnabled(context, false, false))));
}
private static int setChatBubbleEnabled(CommandContext<ServerCommandSource> context, boolean enabled, boolean useServerConfig) {
ServerCommandSource source = context.getSource();
ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
ConfigurationHandler.Config config = configHandler.loadConfig();
config.setChatBubbles(enabled);
if (configHandler.saveConfig(config, useServerConfig)) {
Text feedbackMessage = Text.literal("Player chat bubbles have been " + (enabled ? "enabled" : "disabled") + ".").formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, true);
return 1;
} else {
Text feedbackMessage = Text.literal("Failed to update player chat bubble setting.").formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false);
return 0;
}
}
private static LiteralArgumentBuilder<ServerCommandSource> registerWhitelistCommand() { private static LiteralArgumentBuilder<ServerCommandSource> registerWhitelistCommand() {
return CommandManager.literal("whitelist") return CommandManager.literal("whitelist")
...@@ -135,6 +165,7 @@ public class CreatureChatCommands { ...@@ -135,6 +165,7 @@ public class CreatureChatCommands {
+ "/creaturechat model set <model> - Sets the model\n" + "/creaturechat model set <model> - Sets the model\n"
+ "/creaturechat timeout set <seconds> - Sets the API timeout\n" + "/creaturechat timeout set <seconds> - Sets the API timeout\n"
+ "/creaturechat story set \"<story>\" - Sets a custom story\n" + "/creaturechat story set \"<story>\" - Sets a custom story\n"
+ "/creaturechat chatbubbles set <on | off> - Show player chat bubbles\n"
+ "/creaturechat whitelist <entityType | all | clear> - Show chat bubbles\n" + "/creaturechat whitelist <entityType | all | clear> - Show chat bubbles\n"
+ "/creaturechat blacklist <entityType | all | clear> - Hide chat bubbles\n" + "/creaturechat blacklist <entityType | all | clear> - Hide chat bubbles\n"
+ "\n" + "\n"
...@@ -151,43 +182,42 @@ public class CreatureChatCommands { ...@@ -151,43 +182,42 @@ public class CreatureChatCommands {
.requires(source -> source.hasPermissionLevel(4)) .requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set") .then(CommandManager.literal("set")
.then(CommandManager.argument("value", StringArgumentType.string()) .then(CommandManager.argument("value", StringArgumentType.string())
.executes(context -> { .then(addConfigArgs((context, useServerConfig) -> {
String story = StringArgumentType.getString(context, "value"); String story = StringArgumentType.getString(context, "value");
ConfigurationHandler.Config config = new ConfigurationHandler(context.getSource().getServer()).loadConfig(); ConfigurationHandler.Config config = new ConfigurationHandler(context.getSource().getServer()).loadConfig();
config.setStory(story); // Assuming Config has a `setStory` method config.setStory(story);
if (new ConfigurationHandler(context.getSource().getServer()).saveConfig(config, true)) { if (new ConfigurationHandler(context.getSource().getServer()).saveConfig(config, useServerConfig)) {
context.getSource().sendFeedback(() -> Text.literal("Story set successfully: " + story).formatted(Formatting.GREEN), true); context.getSource().sendFeedback(() -> Text.literal("Story set successfully: " + story).formatted(Formatting.GREEN), true);
return 1; return 1;
} else { } else {
context.getSource().sendFeedback(() -> Text.literal("Failed to set story!").formatted(Formatting.RED), false); context.getSource().sendFeedback(() -> Text.literal("Failed to set story!").formatted(Formatting.RED), false);
return 0; return 0;
} }
}) }))))
))
.then(CommandManager.literal("clear") .then(CommandManager.literal("clear")
.executes(context -> { .then(addConfigArgs((context, useServerConfig) -> {
ConfigurationHandler.Config config = new ConfigurationHandler(context.getSource().getServer()).loadConfig(); ConfigurationHandler.Config config = new ConfigurationHandler(context.getSource().getServer()).loadConfig();
config.setStory(""); // Clear the story config.setStory("");
if (new ConfigurationHandler(context.getSource().getServer()).saveConfig(config, true)) { if (new ConfigurationHandler(context.getSource().getServer()).saveConfig(config, useServerConfig)) {
context.getSource().sendFeedback(() -> Text.literal("Story cleared successfully!").formatted(Formatting.GREEN), true); context.getSource().sendFeedback(() -> Text.literal("Story cleared successfully!").formatted(Formatting.GREEN), true);
return 1; return 1;
} else { } else {
context.getSource().sendFeedback(() -> Text.literal("Failed to clear story!").formatted(Formatting.RED), false); context.getSource().sendFeedback(() -> Text.literal("Failed to clear story!").formatted(Formatting.RED), false);
return 0; return 0;
} }
})) })))
.then(CommandManager.literal("display") .then(CommandManager.literal("display")
.executes(context -> { .executes(context -> {
ConfigurationHandler.Config config = new ConfigurationHandler(context.getSource().getServer()).loadConfig(); ConfigurationHandler.Config config = new ConfigurationHandler(context.getSource().getServer()).loadConfig();
String story = config.getStory(); // Assuming Config has a `getStory` method String story = config.getStory();
if (story == null || story.isEmpty()) { if (story == null || story.isEmpty()) {
context.getSource().sendFeedback(() -> Text.literal("No story is currently set.").formatted(Formatting.RED), false); context.getSource().sendFeedback(() -> Text.literal("No story is currently set.").formatted(Formatting.RED), false);
return 0; return 0;
} else { } else {
context.getSource().sendFeedback(() -> Text.literal("Current story: " + story).formatted(Formatting.AQUA), false); context.getSource().sendFeedback(() -> Text.literal("Current story: " + story).formatted(Formatting.AQUA), false);
return 1; return 1;
} }
})); }));
} }
private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) { private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) {
......
package com.owlmaddie.mixin;
import com.owlmaddie.chat.EntityChatData;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.network.ServerPackets;
import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static com.owlmaddie.network.ServerPackets.BroadcastPlayerMessage;
/**
* The {@code MixinOnChat} mixin class intercepts chat messages from players, and broadcasts them as chat bubbles
*/
@Mixin(ServerPlayNetworkHandler.class)
public abstract class MixinOnChat {
@Inject(method = "onChatMessage", at = @At("HEAD"), cancellable = true)
private void onChatMessage(ChatMessageC2SPacket packet, CallbackInfo ci) {
ConfigurationHandler.Config config = new ConfigurationHandler(ServerPackets.serverInstance).loadConfig();
if (config.getChatBubbles()) {
// Get the player who sent the message
ServerPlayNetworkHandler handler = (ServerPlayNetworkHandler) (Object) this;
ServerPlayerEntity player = handler.player;
// Get the chat message
String chatMessage = packet.chatMessage();
// Example: Call your broadcast function
EntityChatData chatData = new EntityChatData(player.getUuidAsString());
chatData.currentMessage = chatMessage;
BroadcastPlayerMessage(chatData, player);
// Optionally, cancel the event to prevent the default behavior
//ci.cancel();
}
}
}
...@@ -57,7 +57,8 @@ public class ServerPackets { ...@@ -57,7 +57,8 @@ public class ServerPackets {
public static final Identifier PACKET_C2S_OPEN_CHAT = new Identifier("creaturechat", "packet_c2s_open_chat"); public static final Identifier PACKET_C2S_OPEN_CHAT = new Identifier("creaturechat", "packet_c2s_open_chat");
public static final Identifier PACKET_C2S_CLOSE_CHAT = new Identifier("creaturechat", "packet_c2s_close_chat"); public static final Identifier PACKET_C2S_CLOSE_CHAT = new Identifier("creaturechat", "packet_c2s_close_chat");
public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("creaturechat", "packet_c2s_send_chat"); public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("creaturechat", "packet_c2s_send_chat");
public static final Identifier PACKET_S2C_MESSAGE = new Identifier("creaturechat", "packet_s2c_message"); public static final Identifier PACKET_S2C_ENTITY_MESSAGE = new Identifier("creaturechat", "packet_s2c_entity_message");
public static final Identifier PACKET_S2C_PLAYER_MESSAGE = new Identifier("creaturechat", "packet_s2c_player_message");
public static final Identifier PACKET_S2C_LOGIN = new Identifier("creaturechat", "packet_s2c_login"); public static final Identifier PACKET_S2C_LOGIN = new Identifier("creaturechat", "packet_s2c_login");
public static final Identifier PACKET_S2C_WHITELIST = new Identifier("creaturechat", "packet_s2c_whitelist"); public static final Identifier PACKET_S2C_WHITELIST = new Identifier("creaturechat", "packet_s2c_whitelist");
public static final Identifier PACKET_S2C_PLAYER_STATUS = new Identifier("creaturechat", "packet_s2c_player_status"); public static final Identifier PACKET_S2C_PLAYER_STATUS = new Identifier("creaturechat", "packet_s2c_player_status");
...@@ -342,15 +343,12 @@ public class ServerPackets { ...@@ -342,15 +343,12 @@ public class ServerPackets {
} }
// Send new message to all connected players // Send new message to all connected players
public static void BroadcastPacketMessage(EntityChatData chatData, ServerPlayerEntity sender) { public static void BroadcastEntityMessage(EntityChatData chatData) {
// Log useful information before looping through all players // Log useful information before looping through all players
LOGGER.info("Broadcasting message: sender={}, entityId={}, status={}, currentMessage={}, currentLineNumber={}, senderType={}", LOGGER.info("Broadcasting entity message: entityId={}, status={}, currentMessage={}, currentLineNumber={}, senderType={}",
sender != null ? sender.getDisplayName().getString() : "Unknown", chatData.entityId, chatData.status,
chatData.entityId,
chatData.status,
chatData.currentMessage.length() > 24 ? chatData.currentMessage.substring(0, 24) + "..." : chatData.currentMessage, chatData.currentMessage.length() > 24 ? chatData.currentMessage.substring(0, 24) + "..." : chatData.currentMessage,
chatData.currentLineNumber, chatData.currentLineNumber, chatData.sender);
chatData.sender);
for (ServerWorld world : serverInstance.getWorlds()) { for (ServerWorld world : serverInstance.getWorlds()) {
UUID entityId = UUID.fromString(chatData.entityId); UUID entityId = UUID.fromString(chatData.entityId);
...@@ -373,16 +371,7 @@ public class ServerPackets { ...@@ -373,16 +371,7 @@ public class ServerPackets {
// Iterate over all players and send the packet // Iterate over all players and send the packet
for (ServerPlayerEntity player : serverInstance.getPlayerManager().getPlayerList()) { for (ServerPlayerEntity player : serverInstance.getPlayerManager().getPlayerList()) {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
// Write the entity's chat updated data
buffer.writeString(chatData.entityId); buffer.writeString(chatData.entityId);
if (sender != null && chatData.auto_generated == 0) {
buffer.writeString(sender.getUuidAsString());
buffer.writeString(sender.getDisplayName().getString());
} else {
buffer.writeString("");
buffer.writeString("Unknown");
}
buffer.writeString(chatData.currentMessage); buffer.writeString(chatData.currentMessage);
buffer.writeInt(chatData.currentLineNumber); buffer.writeInt(chatData.currentLineNumber);
buffer.writeString(chatData.status.toString()); buffer.writeString(chatData.status.toString());
...@@ -390,7 +379,7 @@ public class ServerPackets { ...@@ -390,7 +379,7 @@ public class ServerPackets {
writePlayerDataMap(buffer, chatData.players); writePlayerDataMap(buffer, chatData.players);
// Send message to player // Send message to player
ServerPlayNetworking.send(player, PACKET_S2C_MESSAGE, buffer); ServerPlayNetworking.send(player, PACKET_S2C_ENTITY_MESSAGE, buffer);
} }
break; break;
} }
...@@ -398,6 +387,26 @@ public class ServerPackets { ...@@ -398,6 +387,26 @@ public class ServerPackets {
} }
// Send new message to all connected players // Send new message to all connected players
public static void BroadcastPlayerMessage(EntityChatData chatData, ServerPlayerEntity sender) {
// Log the specific data being sent
LOGGER.info("Broadcasting player message: senderUUID={}, message={}", sender.getUuidAsString(),
chatData.currentMessage);
// Create the buffer for the packet
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
// Write the sender's UUID and the chat message to the buffer
buffer.writeString(sender.getUuidAsString());
buffer.writeString(sender.getDisplayName().getString());
buffer.writeString(chatData.currentMessage);
// Iterate over all connected players and send the packet
for (ServerPlayerEntity serverPlayer : serverInstance.getPlayerManager().getPlayerList()) {
ServerPlayNetworking.send(serverPlayer, PACKET_S2C_PLAYER_MESSAGE, buffer);
}
}
// Send new message to all connected players
public static void BroadcastPlayerStatus(PlayerEntity player, boolean isChatOpen) { public static void BroadcastPlayerStatus(PlayerEntity player, boolean isChatOpen) {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
"MixinMobEntityAccessor", "MixinMobEntityAccessor",
"MixinLivingEntity", "MixinLivingEntity",
"MixinBucketable", "MixinBucketable",
"MixinVillagerEntity" "MixinVillagerEntity",
"MixinOnChat"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
......
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