Commit 3d22ffe0 by Jonathan Thomas

Integrate experimental quest system message when ChatDataManager is…

Integrate experimental quest system message when ChatDataManager is initialized... disabled for now. Integrate item and entity rarity collectors into prompt. Added "JSON mode" for both 3.5 and 4.0 ChatGPT.
parent 55765fdb
Pipeline #11665 passed with stage
in 30 seconds
package com.owlmaddie; package com.owlmaddie;
import com.owlmaddie.json.QuestJson;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
...@@ -10,13 +11,16 @@ import java.util.List; ...@@ -10,13 +11,16 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.google.gson.Gson;
import net.minecraft.util.Rarity;
public class ChatDataManager { public class ChatDataManager {
// Use a static instance to manage our data globally // Use a static instance to manage our data globally
private static final ChatDataManager SERVER_INSTANCE = new ChatDataManager(); private static final ChatDataManager SERVER_INSTANCE = new ChatDataManager(true);
private static final ChatDataManager CLIENT_INSTANCE = new ChatDataManager(); private static final ChatDataManager CLIENT_INSTANCE = new ChatDataManager(false);
public static int MAX_CHAR_PER_LINE = 22; public static int MAX_CHAR_PER_LINE = 22;
public QuestJson quest = null;
public enum ChatStatus { public enum ChatStatus {
NONE, // No chat status yet NONE, // No chat status yet
...@@ -76,12 +80,8 @@ public class ChatDataManager { ...@@ -76,12 +80,8 @@ public class ChatDataManager {
return "Umm... hello... ugh..."; // Return a default string if no match is found return "Umm... hello... ugh..."; // Return a default string if no match is found
} }
// Generate greeting // Generate context object
public void generateMessage(ServerPlayerEntity player, String systemPrompt, String user_message) { public Map<String, String> getPlayerContext(ServerPlayerEntity player) {
this.status = ChatStatus.PENDING;
// Add USER Message
this.addMessage(user_message, ChatSender.USER);
// Add PLAYER context information // Add PLAYER context information
Map<String, String> contextData = new HashMap<>(); Map<String, String> contextData = new HashMap<>();
contextData.put("player_name", player.getDisplayName().getString()); contextData.put("player_name", player.getDisplayName().getString());
...@@ -114,6 +114,18 @@ public class ChatDataManager { ...@@ -114,6 +114,18 @@ public class ChatDataManager {
contextData.put("entity_type", entity.getType().getName().getString().toString()); contextData.put("entity_type", entity.getType().getName().getString().toString());
contextData.put("entity_character_sheet", characterSheet); contextData.put("entity_character_sheet", characterSheet);
return contextData;
}
// Generate greeting
public void generateMessage(ServerPlayerEntity player, String systemPrompt, String user_message) {
this.status = ChatStatus.PENDING;
// Add USER Message
this.addMessage(user_message, ChatSender.USER);
// Add PLAYER context information
Map<String, String> contextData = getPlayerContext(player);
// fetch HTTP response from ChatGPT // fetch HTTP response from ChatGPT
ChatGPTRequest.fetchMessageFromChatGPT(systemPrompt, contextData, previousMessages).thenAccept(output_message -> { ChatGPTRequest.fetchMessageFromChatGPT(systemPrompt, contextData, previousMessages).thenAccept(output_message -> {
if (output_message != null && systemPrompt == "system-character") { if (output_message != null && systemPrompt == "system-character") {
...@@ -176,8 +188,15 @@ public class ChatDataManager { ...@@ -176,8 +188,15 @@ public class ChatDataManager {
entityChatDataMap.clear(); entityChatDataMap.clear();
} }
private ChatDataManager() { private ChatDataManager(Boolean server_only) {
// Constructor
entityChatDataMap = new HashMap<>(); entityChatDataMap = new HashMap<>();
if (server_only) {
// Generate initial quest
// TODO: Complete the quest flow
//generateQuest();
}
} }
// Method to get the global instance of the server data manager // Method to get the global instance of the server data manager
...@@ -194,4 +213,37 @@ public class ChatDataManager { ...@@ -194,4 +213,37 @@ public class ChatDataManager {
public EntityChatData getOrCreateChatData(int entityId) { public EntityChatData getOrCreateChatData(int entityId) {
return entityChatDataMap.computeIfAbsent(entityId, k -> new EntityChatData(entityId)); return entityChatDataMap.computeIfAbsent(entityId, k -> new EntityChatData(entityId));
} }
// Generate quest data for this server session
public void generateQuest() {
// Get items needed for Quest prompt
List<String> commonItems = RarityItemCollector.getItemsByRarity(Rarity.COMMON, 5);
List<String> uncommonItems = RarityItemCollector.getItemsByRarity(Rarity.UNCOMMON, 5);
List<String> rareItems = RarityItemCollector.getItemsByRarity(Rarity.RARE, 5);
// Get entities needed for Quest prompt
List<String> commonEntities = RarityItemCollector.getEntitiesByRarity(Rarity.COMMON, 5);
List<String> uncommonEntities = RarityItemCollector.getEntitiesByRarity(Rarity.UNCOMMON, 5);
List<String> rareEntities = RarityItemCollector.getEntitiesByRarity(Rarity.RARE, 5);
// Add context information for prompt
Map<String, String> contextData = new HashMap<>();
contextData.put("items_common", String.join("\n", commonItems));
contextData.put("items_uncommon", String.join("\n", uncommonItems));
contextData.put("items_rare", String.join("\n", rareItems));
contextData.put("entities_common", String.join("\n", commonEntities));
contextData.put("entities_uncommon", String.join("\n", uncommonEntities));
contextData.put("entities_rare", String.join("\n", rareEntities));
// Add message
List<ChatMessage> messages = new ArrayList<>();
messages.add(new ChatMessage("Generate me a new fantasy story with ONLY the 1st character in the story", ChatSender.USER));
// Generate Quest: fetch HTTP response from ChatGPT
ChatGPTRequest.fetchMessageFromChatGPT("system-quest", contextData, messages).thenAccept(output_message -> {
// New Quest
Gson gson = new Gson();
quest = gson.fromJson(output_message, QuestJson.class);
});
}
} }
\ No newline at end of file
...@@ -36,10 +36,20 @@ public class ChatGPTRequest { ...@@ -36,10 +36,20 @@ public class ChatGPTRequest {
static class ChatGPTRequestPayload { static class ChatGPTRequestPayload {
String model; String model;
List<ChatGPTRequestMessage> messages; List<ChatGPTRequestMessage> messages;
ResponseFormat response_format;
public ChatGPTRequestPayload(String model, List<ChatGPTRequestMessage> messages) { public ChatGPTRequestPayload(String model, List<ChatGPTRequestMessage> messages) {
this.model = model; this.model = model;
this.messages = messages; this.messages = messages;
this.response_format = new ResponseFormat("json_object");
}
}
static class ResponseFormat {
String type;
public ResponseFormat(String type) {
this.type = type;
} }
} }
......
...@@ -121,21 +121,6 @@ public class ModInit implements ModInitializer { ...@@ -121,21 +121,6 @@ public class ModInit implements ModInitializer {
// Load chat data... // Load chat data...
LOGGER.info("LOAD chat data from NBT: " + world.getRegistryKey().getValue()); LOGGER.info("LOAD chat data from NBT: " + world.getRegistryKey().getValue());
serverInstance = server; serverInstance = server;
// Example usage: Get 5 random items of RARE rarity
List<Item> rareItems = RarityItemCollector.getItemsByRarity(Rarity.RARE, 5);
System.out.println("Random Rare Items:");
for (Item item : rareItems) {
System.out.println(" - " + item);
}
// Example usage: Get 5 random entities of RARE rarity
List<EntityType> rareEntities = RarityItemCollector.getEntitiesByRarity(Rarity.RARE, 5);
System.out.println("Random Rare Entities:");
for (EntityType item : rareEntities) {
System.out.println(" - " + item);
}
}); });
ServerWorldEvents.UNLOAD.register((server, world) -> { ServerWorldEvents.UNLOAD.register((server, world) -> {
// Save chat data... // Save chat data...
......
...@@ -11,8 +11,8 @@ import java.util.*; ...@@ -11,8 +11,8 @@ import java.util.*;
public class RarityItemCollector { public class RarityItemCollector {
public static List<Item> getItemsByRarity(Rarity rarity, int quantity) { public static List<String> getItemsByRarity(Rarity rarity, int quantity) {
List<Item> itemsOfSpecificRarity = new ArrayList<>(); List<String> itemsOfSpecificRarity = new ArrayList<>();
for (Item item : Registries.ITEM) { for (Item item : Registries.ITEM) {
ItemStack stack = new ItemStack(item); ItemStack stack = new ItemStack(item);
...@@ -20,7 +20,7 @@ public class RarityItemCollector { ...@@ -20,7 +20,7 @@ public class RarityItemCollector {
!item.getName().toString().contains("spawn_egg") && !item.getName().toString().contains("spawn_egg") &&
!item.getName().toString().contains("jukebox") && !item.getName().toString().contains("jukebox") &&
!item.getName().toString().contains("slab")) { !item.getName().toString().contains("slab")) {
itemsOfSpecificRarity.add(item); itemsOfSpecificRarity.add(item.getTranslationKey());
} }
} }
...@@ -40,11 +40,11 @@ public class RarityItemCollector { ...@@ -40,11 +40,11 @@ public class RarityItemCollector {
Categorize all entities and return a random list filtered by rarity. Rarity is calculated mostly with Categorize all entities and return a random list filtered by rarity. Rarity is calculated mostly with
Spawn Group, with a few manual exclusions. Spawn Group, with a few manual exclusions.
*/ */
public static List<EntityType> getEntitiesByRarity(Rarity rarity, int quantity) { public static List<String> getEntitiesByRarity(Rarity rarity, int quantity) {
List<EntityType> categoryCommonEntities = new ArrayList<>(); List<String> categoryCommonEntities = new ArrayList<>();
List<EntityType> categoryUncommonEntities = new ArrayList<>(); List<String> categoryUncommonEntities = new ArrayList<>();
List<EntityType> categoryRareEntities = new ArrayList<>(); List<String> categoryRareEntities = new ArrayList<>();
List<EntityType> entitiesOfSpecificRarity = new ArrayList<>(); List<String> entitiesOfSpecificRarity = new ArrayList<>();
// Categorize spawn groups & entity types into rarity // Categorize spawn groups & entity types into rarity
Set<String> commonEntities = new HashSet<>(Arrays.asList("creature")); Set<String> commonEntities = new HashSet<>(Arrays.asList("creature"));
...@@ -63,11 +63,11 @@ public class RarityItemCollector { ...@@ -63,11 +63,11 @@ public class RarityItemCollector {
if (!excludedMonsters.contains(entityName)) { if (!excludedMonsters.contains(entityName)) {
if (commonEntities.contains(spawnGroup) || commonEntities.contains(entityName)) { if (commonEntities.contains(spawnGroup) || commonEntities.contains(entityName)) {
categoryCommonEntities.add(entityType); categoryCommonEntities.add(entityName);
} else if (unCommonEntities.contains(spawnGroup) || unCommonEntities.contains(entityName)) { } else if (unCommonEntities.contains(spawnGroup) || unCommonEntities.contains(entityName)) {
categoryUncommonEntities.add(entityType); categoryUncommonEntities.add(entityName);
} else if (rareEntities.contains(spawnGroup) || rareEntities.contains(entityName)) { } else if (rareEntities.contains(spawnGroup) || rareEntities.contains(entityName)) {
categoryRareEntities.add(entityType); categoryRareEntities.add(entityName);
} }
} }
} }
......
package com.owlmaddie.json;
import java.util.List;
public class QuestJson {
Story story;
List<Character> characters;
public static class Story {
String background;
String clue;
}
public static class Character {
String name;
int age;
String personality;
String greeting;
String entity_type_key;
Quest quest;
String choice_question;
List<Choice> choices;
}
public static class Quest {
List<QuestItem> quest_items;
List<DropItem> drop_items;
}
public static class QuestItem {
String key;
int quantity;
}
public static class DropItem {
String key;
int quantity;
}
public static class Choice {
String choice;
String clue;
}
}
You are a RPG dungeon master, who writes fun, creative, and interactive quests, about timeless stories which follows a You are a RPG dungeon master, who writes fun, creative, and interactive quests, about timeless stories which follows a
protagonist on an unforeseen quest, where they face challenges, gain insights, and return home transformed. Include a protagonist on an unforeseen quest, where they face challenges, gain insights, and return home transformed. Include a
"background" story, which is known by all characters in the world, which lightly introduces the story, problem, or "background" story, which is known by all characters in the world, which lightly introduces the story, problem, or
legend and a "clue" for the player on where to locate the first quest character. legend and a "clue" to guide the player to the first character in the story (including the entity type of the character).
For example, if the first character is a cow entity type, you MUST mention to the player they are searching for a cow,
otherwise the player will not know what type of character to search for.
The adventures are completely driven forward with dialog between characters. Characters can be located in any The adventures are completely driven forward with dialog between characters. Characters can be located in any
Minecraft biome. The adventure should progress from character to character. More of the story is revealed along Minecraft biome. The adventure should progress from character to character. More of the story is revealed along
...@@ -11,17 +13,17 @@ Each character must be a valid Living Entity in Minecraft. A list is provided he ...@@ -11,17 +13,17 @@ Each character must be a valid Living Entity in Minecraft. A list is provided he
Please ONLY choose entities from this list: Please ONLY choose entities from this list:
Common Entities (for early-story characters): Common Entities (for early-story characters):
{entities_common} {{entities_common}}
Uncommon Entities (for mid-story characters): Uncommon Entities (for mid-story characters):
{entities_uncommon} {{entities_uncommon}}
Rare Entities (for late-story characters): Rare Entities (for late-story characters):
{entities_rare} {{entities_rare}}
Please name each character including a short initial greeting (as spoken by Please name each character including a short initial greeting (as spoken by
the character using their personality traits) which introduces their unique quest and furthers the story. Use creative the character using their personality traits) which introduces their unique quest and furthers the story. Use creative
and original names, and do NOT base names solely on their entity type. For example, avoid silly names such as and original names, and NEVER base names solely on their entity type. For example, avoid childish names such as
Bessie the Cow, Cluck the Chicken, Shelly the Turtle, and instead lean toward fantasy names. Bessie the Cow, Cluck the Chicken, Shelly the Turtle, and instead lean toward fantasy names.
Each quest requires the player to "fetch" actual, valid, obtainable item(s) in Minecraft and return the item(s) to the Each quest requires the player to "fetch" actual, valid, obtainable item(s) in Minecraft and return the item(s) to the
...@@ -29,7 +31,7 @@ character. Be sure to include the actual quest item names in the greeting, so it ...@@ -29,7 +31,7 @@ character. Be sure to include the actual quest item names in the greeting, so it
obtain. The quest items MUST match the items in the greeting. obtain. The quest items MUST match the items in the greeting.
After the quest is completed, a critical story choice is presented to the player as spoken by the character After the quest is completed, a critical story choice is presented to the player as spoken by the character
(choice-question: contains a congratulations for completing the quest, and a question for the user containing (choice_question: contains a congratulations for completing the quest, and a question for the user containing
both choices), and then a clue is revealed to the player (as spoken by the character), informing them of their both choices), and then a clue is revealed to the player (as spoken by the character), informing them of their
choice's consequence to the story, and to help them locate the next character in the story based on their choice, choice's consequence to the story, and to help them locate the next character in the story based on their choice,
and useful item(s) are dropped for the player to help them on their quest. Drop item(s) can also be requested by and useful item(s) are dropped for the player to help them on their quest. Drop item(s) can also be requested by
...@@ -37,19 +39,19 @@ the next character in the story, especially if it helps further the story. Give ...@@ -37,19 +39,19 @@ the next character in the story, especially if it helps further the story. Give
continue the story without frustration. Quest items and Drop items MUST be randomly selected from the following list: continue the story without frustration. Quest items and Drop items MUST be randomly selected from the following list:
Common Items (for early-story characters): Common Items (for early-story characters):
{items_common} {{items_common}}
Uncommon Items (for mid-story characters): Uncommon Items (for mid-story characters):
{items_uncommon} {{items_uncommon}}
Rare Items (for late-story characters): Rare Items (for late-story characters):
{items_rare} {{items_rare}}
Each character should progress the story building tension, the quest fetch item(s) should constantly increase in Each character should progress the story building tension, the quest fetch item(s) should constantly increase in
difficulty, the quest drop item(s) should constantly increase in value/usefulness/rarity, and the choices should difficulty, the quest drop item(s) should constantly increase in value/usefulness/rarity, and the choices should
increase in stakes to the player/story. increase in stakes to the player/story.
The final character should conclude the story with their final choice-question, and give the final and best reward The final character should conclude the story with their final choice_question, and give the final and best reward
drop. Be sure to give an ending to the story based on the player's choice, wrapping up loose ends and giving a good drop. Be sure to give an ending to the story based on the player's choice, wrapping up loose ends and giving a good
pay off for the player's effort. pay off for the player's effort.
...@@ -70,23 +72,22 @@ Please use the following JSON format, and output ONLY JSON with no intro text. ...@@ -70,23 +72,22 @@ Please use the following JSON format, and output ONLY JSON with no intro text.
"age": 0, "age": 0,
"personality": "", "personality": "",
"greeting": "", "greeting": "",
"entity-type-key": "", "entity_type_key": "",
"entity-biome-key": "",
"quest": { "quest": {
"quest-items": [ "quest_items": [
{ {
"key": "", "key": "",
"quantity": 0 "quantity": 0
} }
], ],
"drop-items": [ "drop_items": [
{ {
"key": "", "key": "",
"quantity": 0 "quantity": 0
} }
] ]
}, },
"choice-question": "", "choice_question": "",
"choices": [ "choices": [
{ {
"choice": "", "choice": "",
......
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