Commit 339ead1e by Jonathan Thomas

Merge branch 'new-deploy-ci' into 'develop'

Improved render settings (fixed shaders and Fabulous) and death messages

See merge request !6
parents b906c360 e118d990
Pipeline #12307 passed with stages
in 1 minute 50 seconds
......@@ -4,6 +4,18 @@ 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
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- New automated deployments for Modrinth and CurseForge (GitLab CI Pipeline)
- Death messages added for all named creatures except players and tamed ones (RIP)
- Added Minecraft Forge installation instructions
### Fixed
- Parse OpenAI JSON error messages, to display a more readable error message
- Remove quotes from CreatureChat API error messages
- If OpenAI key is set, switch URL automatically back to OpenAI endpoint
## [1.0.4] - 2024-05-15
### Added
......
......@@ -18,12 +18,23 @@
Ready to deepen your Minecraft journey with meaningful conversations and enduring friendships?
**Step into the world of CreatureChat 🗨 and spark your first conversation today!**
## Installation
## Recommended Installation (with Fabric)
1. **Install Fabric Loader & API**: Follow the instructions [here](https://fabricmc.net/use/).
1. **Install CreatureChat Mod**: Download and copy `creaturechat-*.jar` and `fabric-api-*.jar` into your `.minecraft/mods`
folder.
1. **Install CreatureChat Mod**: Download and copy `creaturechat-*.jar` and `fabric-api-*.jar` into your `.minecraft/mods` folder.
1. **Create an OpenAI API key**: Visit https://platform.openai.com/api-keys, and use the **+ Create new secret key** button.
Copy/Paste your key into the `/creaturechat key set <YOUR-SECRET-KEY-HERE>` command.
1. **Launch Minecraft** with the Fabric profile
## OR
## Forge Installation (with Sinytra Connector)
1. **Install Forge:** Download [Forge Installer](https://files.minecraftforge.net/), run it, select "Install client".
1. **Install Forgified Fabric API:** Download [Forgified Fabric API](https://curseforge.com/minecraft/mc-mods/forgified-fabric-api) and copy the `*.jar` into your `.minecraft/mods` folder.
1. **Install Sinytra Connector:** Download [Sinytra Connector](https://www.curseforge.com/minecraft/mc-mods/sinytra-connector) and copy the `*.jar` into your `.minecraft/mods` folder.
1. **Install CreatureChat Mod**: Download and copy `creaturechat-*.jar` into your `.minecraft/mods` folder.
1. **Create an OpenAI API key**: Visit https://platform.openai.com/api-keys, and use the **+ Create new secret key** button.
Copy/Paste your key into the `/creaturechat key set <YOUR-SECRET-KEY-HERE>` command.
1. **Launch Minecraft** with the Forge profile
## Commands
The CreatureChat mod allows users to configure settings via in-game commands. Here's how to use them:
......@@ -59,6 +70,13 @@ the OpenAI developer API does not extend any free models or free usage. You will
consumed and generated. We use the `gpt-3.5-turbo` model by default, due to its extremely low cost
and fast performance... however it is not free.
## Free Local LLM
CreatureChat fully supports **free & open-source** LLMs. An HTTP endpoint which supports the OpenAI Chat Completion
JSON syntax is required. We highly recommend using [Ollama](https://ollama.com/) or [LiteLLM](https://litellm.vercel.app/) as your HTTP proxy.
LiteLLM supports **100+ LLMs** (including Anthropic, VertexAI, HuggingFace, Google Gemini, and Ollama), and proxies them through a
local HTTP endpoint in a compatible format with CreatureChat. *NOTE: You must have a very expensive GPU to run a local
LLM on your computer at a speed which is fast enough to be playable in Minecraft.*
## Screenshots
![Interact with Minecraft Creatures](src/main/resources/assets/creaturechat/screenshots/salmon-follow.png)
![Panda Following the Player](src/main/resources/assets/creaturechat/screenshots/panda-follow.png)
......
package com.owlmaddie.chat;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.owlmaddie.commands.ConfigurationHandler;
import com.owlmaddie.json.ChatGPTResponse;
import com.owlmaddie.network.ServerPackets;
......@@ -63,6 +64,47 @@ public class ChatGPTRequest {
}
}
public static String removeQuotes(String str) {
if (str != null && str.length() > 1 && str.startsWith("\"") && str.endsWith("\"")) {
return str.substring(1, str.length() - 1);
}
return str;
}
// Class to represent the error response structure
public static class ErrorResponse {
Error error;
static class Error {
String message;
String type;
String code;
}
}
public static String parseAndLogErrorResponse(String errorResponse) {
try {
Gson gson = new Gson();
ErrorResponse response = gson.fromJson(errorResponse, ErrorResponse.class);
if (response.error != null) {
LOGGER.error("Error Message: " + response.error.message);
LOGGER.error("Error Type: " + response.error.type);
LOGGER.error("Error Code: " + response.error.code);
return response.error.message;
} else {
LOGGER.error("Unknown error response: " + errorResponse);
return "Unknown";
}
} catch (JsonSyntaxException e) {
LOGGER.warn("Failed to parse error response as JSON, falling back to plain text");
LOGGER.error("Error response: " + errorResponse);
} catch (Exception e) {
LOGGER.error("Failed to parse error response", e);
}
return removeQuotes(errorResponse);
}
// This method should be called in an appropriate context where ResourceManager is available
public static String loadPromptFromResource(ResourceManager resourceManager, String filePath) {
Identifier fileIdentifier = new Identifier("creaturechat", filePath);
......@@ -173,8 +215,12 @@ public class ChatGPTRequest {
while ((errorLine = errorReader.readLine()) != null) {
errorResponse.append(errorLine.trim());
}
LOGGER.error("Error response from API: " + errorResponse);
lastErrorMessage = errorResponse.toString();
// Parse and log the error response using Gson
String cleanError = parseAndLogErrorResponse(errorResponse.toString());
lastErrorMessage = cleanError;
} catch (Exception e) {
LOGGER.error("Failed to read error response", e);
}
return null;
} else {
......
......@@ -73,9 +73,12 @@ public class ConfigurationHandler {
// Getters and setters for existing fields
public String getApiKey() { return apiKey; }
public void setApiKey(String apiKey) {
// Update URL if a CreatureChat API key is detected
if (apiKey.startsWith("cc_") && apiKey.length() == 15) {
// Update URL if a CreatureChat API key is detected
setUrl("https://api.creaturechat.com/v1/chat/completions");
} else if (apiKey.startsWith("sk-")) {
// Update URL if a OpenAI API key is detected
setUrl("https://api.openai.com/v1/chat/completions");
}
this.apiKey = apiKey;
}
......
......@@ -7,12 +7,16 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.world.World;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
......@@ -61,6 +65,28 @@ public class MixinLivingEntity implements LivingEntityInterface {
}
}
@Inject(method = "onDeath", at = @At("HEAD"))
private void onDeath(DamageSource source, CallbackInfo info) {
LivingEntity entity = (LivingEntity) (Object) this;
World world = entity.getWorld();
if (!world.isClient() && entity.hasCustomName()) {
// Skip tamed entities and players
if (entity instanceof TameableEntity && ((TameableEntity) entity).isTamed()) {
return;
}
if (entity instanceof PlayerEntity) {
return;
}
// Get the original death message
Text deathMessage = entity.getDamageTracker().getDeathMessage();
// Broadcast the death message to all players in the world
ServerPackets.BroadcastMessage(deathMessage);
}
}
@Override
public void setCanTargetPlayers(boolean canTarget) {
this.canTargetPlayers = canTarget;
......
......@@ -316,6 +316,13 @@ public class ServerPackets {
}
}
// Send a chat message to all players (i.e. death message)
public static void BroadcastMessage(Text message) {
for (ServerPlayerEntity serverPlayer : serverInstance.getPlayerManager().getPlayerList()) {
serverPlayer.sendMessage(message, false);
};
}
// Send a chat message to a player which is clickable (for error messages with a link for help)
public static void SendClickableError(PlayerEntity player, String message, String url) {
MutableText text = Text.literal(message)
......
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