commit 2f05711bb43a5bc18dfc62882f6843e2ee408ebd Author: paisc Date: Tue Dec 24 14:12:40 2024 +0100 first commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml new file mode 100644 index 0000000..f1ca92b --- /dev/null +++ b/.idea/checkstyle-idea.xml @@ -0,0 +1,15 @@ + + + + 10.20.1 + JavaOnly + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..7e323f8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..8c89be5 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fdc35ea --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..51a50f8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..4825cfc --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + net.voltexstudios + npcconversations + npcconversations + 1.0 + + clean package + + + true + src/main/resources + + + + + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + + + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + io.papermc.paper + paper-api + 1.21.3-R0.1-SNAPSHOT + provided + + + org.projectlombok + lombok + 1.18.30 + provided + + + + 21 + UTF-8 + + diff --git a/npcconversations.iml b/npcconversations.iml new file mode 100644 index 0000000..a376b96 --- /dev/null +++ b/npcconversations.iml @@ -0,0 +1,13 @@ + + + + + + + ADVENTURE + + 1 + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5710bab --- /dev/null +++ b/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + net.voltexstudios + npcconversations + 1.0 + jar + + npcconversations + + + 21 + UTF-8 + + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + + io.papermc.paper + paper-api + 1.21.3-R0.1-SNAPSHOT + provided + + + + com.launchableinc.openai-java + service + 0.4.0 + + + + org.projectlombok + lombok + 1.18.30 + provided + + + diff --git a/src/main/java/net/voltexstudios/npcConversations/NPCConversations.java b/src/main/java/net/voltexstudios/npcConversations/NPCConversations.java new file mode 100644 index 0000000..7dd488f --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/NPCConversations.java @@ -0,0 +1,109 @@ +package net.voltexstudios.npcConversations; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalListener; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.voltexstudios.npcConversations.commands.ConversationCommand; +import net.voltexstudios.npcConversations.listener.ChatHandler; +import net.voltexstudios.npcConversations.listener.PlayerListener; +import net.voltexstudios.npcConversations.npc.NPC; +import net.voltexstudios.npcConversations.npc.NPCData; +import net.voltexstudios.npcConversations.npc.NPCService; +import net.voltexstudios.npcConversations.session.ConverstionSession; +import net.voltexstudios.npcConversations.util.OpenAI; +import net.voltexstudios.npcConversations.util.Type; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +public final class NPCConversations extends JavaPlugin { + + public static final TextComponent PREFIX = Component.text("§8[§eConversation§8] §7"); + + public static Cache CACHE; + public static Cache USER_TYPE = CacheBuilder.newBuilder().build(); + + private static NPCConversations instance; + + @Override + public void onEnable() { + instance = this; + + saveDefaultConfig(); + + // Check api key and initialize OpenAI service + OpenAI.init(getConfig().getString("API-KEY")).exceptionallyAsync(throwable -> { + getLogger().severe("Error while initializing OpenAI service! Is your API key valid?"); + + getServer().getPluginManager().disablePlugin(this); + return null; + }); + + // Initialize cache + CACHE = CacheBuilder.newBuilder() + .expireAfterWrite(getConfig().getInt("conversation-expiration-duration"), TimeUnit.MINUTES) + .removalListener((RemovalListener) notification -> { + if (notification.getKey() == null) return; + USER_TYPE.invalidate(notification.getKey()); + if (notification.getCause() == RemovalCause.EXPIRED) { + notification.getKey().sendMessage( + PREFIX.append(Component.text("§cYour conversation has expired due to inactivity.")) + ); + } + }).build(); + + new NPCService(NPCData.loadNPCData()); + + registerlisteners(); + registerCommands(); + + getLogger().log(Level.INFO, "Plugin enabled!"); + } + + @Override + public void onDisable() { + getLogger().log(Level.INFO, "Plugin disabled!"); + } + + private void registerCommands() { + getCommand("conversation").setExecutor(new ConversationCommand()); + } + + private void registerlisteners() { + PluginManager pm = getServer().getPluginManager(); + + pm.registerEvents(new PlayerListener(), this); + pm.registerEvents(new ChatHandler(), this); + } + + public static void startConversation(Player player, NPC npc, Type type) { + if (CACHE.asMap().containsKey(player)) { + CACHE.invalidate(player); + + player.sendMessage( + PREFIX.append(Component.text("§cYou are already in a conversation. Exiting the current conversation to start a new one.")) + ); + + return; + } + + ConverstionSession session = new ConverstionSession(npc, player); + + USER_TYPE.put(player, type); + CACHE.put(player, session); + + player.sendMessage( + PREFIX.append(Component.text("§aYou have started a conversation with " + npc.getName() + ".")) + ); + } + + public static NPCConversations getInstance() { + return instance; + } +} diff --git a/src/main/java/net/voltexstudios/npcConversations/commands/ConversationCommand.java b/src/main/java/net/voltexstudios/npcConversations/commands/ConversationCommand.java new file mode 100644 index 0000000..4bc0ff4 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/commands/ConversationCommand.java @@ -0,0 +1,114 @@ +package net.voltexstudios.npcConversations.commands; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.voltexstudios.npcConversations.NPCConversations; +import net.voltexstudios.npcConversations.npc.NPC; +import net.voltexstudios.npcConversations.npc.NPCService; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ConversationCommand implements CommandExecutor, TabCompleter { + + private static final int INTERACTION_ENTITY_RANGE = 5; + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) { + + if (!sender.hasPermission("npcconversation.command")) { + sender.sendMessage( + NPCConversations.PREFIX.append( + Component.text("§cYou do not have permission to use this command.")) + ); + return false; + } + + if (args.length != 2) { + sender.sendMessage(NPCConversations.PREFIX.append( + Component.text("§cUsage: /conversation ")) + ); + return false; + } + + if (!(sender instanceof Player player)) { + sender.sendMessage( + NPCConversations.PREFIX.append( + Component.text("§cOnly players can use this command.")) + ); + return false; + } + + if (args[0].equalsIgnoreCase("delete")) { + NPC npc = NPCService.getInstance().getNPC(args[1]); + if (npc == null) { + sender.sendMessage( + NPCConversations.PREFIX.append( + Component.text("§cNPC with given name not found.")) + ); + return false; + } + NPCService.getInstance().removeNPC(npc); + NPCService.saveNPCData(); + + sender.sendMessage( + NPCConversations.PREFIX.append( + Component.text("§aNPC deleted successfully.")) + ); + return true; + } + if (args[0].equalsIgnoreCase("create")) { + + Entity entity = player.getTargetEntity(INTERACTION_ENTITY_RANGE); + if (entity == null) { + player.sendMessage( + NPCConversations.PREFIX.append( + Component.text("§cYou must be looking at an entity to use this command.")) + ); + return false; + } + + NPC npc = new NPC(args[1], entity.getUniqueId()); + + NPCService.getInstance().addNPC(npc); + NPCService.saveNPCData(); + + sender.sendMessage( + NPCConversations.PREFIX.append( + Component.text("§aNPC created successfully.") + .appendNewline() + .append(Component.text("§7Name: ").append(Component.text(npc.getName()).color(NamedTextColor.BLUE))) + .appendNewline() + .append(Component.text("To edit the background story, go to the npcData.json file.").color(NamedTextColor.GRAY)) + ) + ); + return true; + } + + sender.sendMessage(NPCConversations.PREFIX.append( + Component.text("§cUsage: /conversation ")) + ); + + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { + if (strings.length == 1) { + return List.of("create", "delete"); + } + if (strings.length == 2) { + if (strings[0].equalsIgnoreCase("delete")) { + return NPCService.getInstance().getNpcs().stream().map(NPC::getName).toList(); + } + } + return List.of(); + } +} diff --git a/src/main/java/net/voltexstudios/npcConversations/listener/ChatHandler.java b/src/main/java/net/voltexstudios/npcConversations/listener/ChatHandler.java new file mode 100644 index 0000000..2cb6c75 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/listener/ChatHandler.java @@ -0,0 +1,84 @@ + +package net.voltexstudios.npcConversations.listener; + +import com.launchableinc.openai.completion.chat.ChatMessage; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.voltexstudios.npcConversations.NPCConversations; +import net.voltexstudios.npcConversations.session.ConverstionSession; +import net.voltexstudios.npcConversations.util.Messages; +import net.voltexstudios.npcConversations.util.OpenAI; +import net.voltexstudios.npcConversations.util.Type; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +public class ChatHandler implements Listener { + + @EventHandler + public void onAsyncPlayerChat(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); + + boolean hasFull = NPCConversations.USER_TYPE.asMap().values().stream().anyMatch(type -> type == Type.FULL); + if (!NPCConversations.CACHE.asMap().containsKey(player) && !hasFull) { + return; + } + + Collection recipients = switch (NPCConversations.USER_TYPE.asMap().getOrDefault(player, hasFull ? Type.FULL : Type.SINGLE)) { + case SINGLE -> Collections.singletonList(player); + case FULL, BROADCAST -> event.getRecipients(); + }; + + List list = NPCConversations.getInstance().getConfig().getStringList("format"); + + if (!NPCConversations.getInstance().getConfig().getBoolean("use-default-chat", false)) { + event.setCancelled(true); + + sendMessage(format(list.getFirst(), event.getMessage(), player.getName(), ""), recipients); + } + + ConverstionSession session = NPCConversations.CACHE.getIfPresent(player); + if (session == null) return; + + List messages = session.getMessages(); + + OpenAI.getResponse(NPCConversations.getInstance().getConfig().getConfigurationSection("chatgpt"), messages, event.getMessage()).whenComplete((response, throwable) -> { + if (throwable != null) { + throwable.printStackTrace(); + player.sendMessage( + NPCConversations.PREFIX.append( + NPCConversations.PREFIX.append( + Component.text("An error occurred while processing your message.").color(NamedTextColor.RED) + ) + ) + ); + return; + } + sendMessage(format(list.get(1), response, player.getName(), session.getNpc().getName()), recipients); + }); + } + + private String format(String str, String message, String player, String npcName) { + return Messages.format(str).replace("%message%", message).replace("%player%", player).replace("%npc%", npcName); + } + + private void sendMessage(String message, Collection players) { + Bukkit.getOnlinePlayers().stream() + .filter(player -> !players.contains(player) && player.hasPermission("minecraftgpt.receive")) + .forEach(player -> player.sendMessage(message)); + + for (Player player : players) + player.sendMessage(message); + + if (NPCConversations.getInstance().getConfig().getBoolean("send-messages-to-console", true)) + NPCConversations.getInstance().getServer().getConsoleSender().sendMessage(message); + } +} diff --git a/src/main/java/net/voltexstudios/npcConversations/listener/PlayerListener.java b/src/main/java/net/voltexstudios/npcConversations/listener/PlayerListener.java new file mode 100644 index 0000000..17065d8 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/listener/PlayerListener.java @@ -0,0 +1,52 @@ +package net.voltexstudios.npcConversations.listener; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.voltexstudios.npcConversations.NPCConversations; +import net.voltexstudios.npcConversations.npc.NPC; +import net.voltexstudios.npcConversations.npc.NPCService; +import net.voltexstudios.npcConversations.session.ConverstionSession; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerMoveEvent; + +public class PlayerListener implements Listener { + + @EventHandler + public void onEntityInteract(PlayerInteractEntityEvent event) { + NPC npc = NPCService.getInstance().getNPC(event.getRightClicked()); + if (npc == null) return; + + event.setCancelled(true); + npc.onClick(event.getPlayer()); + } + + @EventHandler + public void onMove(PlayerMoveEvent event) { + if (NPCConversations.CACHE.asMap().containsKey(event.getPlayer())) { + + if (event.getFrom().getBlock().equals(event.getTo().getBlock())) return; + + ConverstionSession session = NPCConversations.CACHE.getIfPresent(event.getPlayer()); + if (session == null) return; + + NPC npc = session.getNpc(); + Entity entity = Bukkit.getEntity(npc.getClickableEntity()); + if (entity == null) return; + + if (event.getTo().distance(entity.getLocation()) > NPCConversations.getInstance().getConfig().getInt("npc-conversation-range", 5)) { + NPCConversations.CACHE.invalidate(event.getPlayer()); + event.getPlayer().sendMessage( + NPCConversations.PREFIX.append( + Component.text("You have moved too far away from the NPC. The conversation has been cancelled.").color(NamedTextColor.RED) + ) + ); + } + + } + } + +} diff --git a/src/main/java/net/voltexstudios/npcConversations/npc/Clickable.java b/src/main/java/net/voltexstudios/npcConversations/npc/Clickable.java new file mode 100644 index 0000000..27493ce --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/npc/Clickable.java @@ -0,0 +1,9 @@ +package net.voltexstudios.npcConversations.npc; + +import org.bukkit.entity.Player; + +public interface Clickable { + + void onClick(Player player); + +} diff --git a/src/main/java/net/voltexstudios/npcConversations/npc/NPC.java b/src/main/java/net/voltexstudios/npcConversations/npc/NPC.java new file mode 100644 index 0000000..2256052 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/npc/NPC.java @@ -0,0 +1,39 @@ +package net.voltexstudios.npcConversations.npc; + +import lombok.Getter; +import lombok.Setter; +import net.voltexstudios.npcConversations.NPCConversations; +import net.voltexstudios.npcConversations.util.Type; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import java.util.UUID; + +@Getter +public class NPC implements Clickable { + + private final String name; + private final UUID clickableEntity; + @Setter private String backgroundStory; + + public NPC(String name, UUID clickableEntity, String backgroundStory) { + this.name = name; + this.clickableEntity = clickableEntity; + this.backgroundStory = backgroundStory; + } + + public NPC(String name, UUID clickableEntity) { + this.name = name; + this.clickableEntity = clickableEntity; + this.backgroundStory = generateGenericBackgroundStory(); + } + + private String generateGenericBackgroundStory() { + return "Hello! I am " + this.name + ". I am a friendly NPC Assistant on this Minecraft Server. I am here to help you with your requests!"; + } + + @Override + public void onClick(Player player) { + NPCConversations.startConversation(player, this, Type.SINGLE); + } +} diff --git a/src/main/java/net/voltexstudios/npcConversations/npc/NPCData.java b/src/main/java/net/voltexstudios/npcConversations/npc/NPCData.java new file mode 100644 index 0000000..bc5c7b8 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/npc/NPCData.java @@ -0,0 +1,48 @@ +package net.voltexstudios.npcConversations.npc; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.Set; + +public class NPCData { + + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + private static final String npcDataPath = "plugins/NPCConversations/npcData.json"; + + private final Set npcs; + + public NPCData(Set npcs) { + this.npcs = npcs; + } + + public static void saveNPCData() { + File folder = new File("plugins/NPCConversations"); + + if(!folder.exists()) { + folder.mkdirs(); + } + + try(FileWriter fileWriter = new FileWriter(npcDataPath)) { + gson.toJson(NPCService.getInstance().getNPCData(), fileWriter); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static NPCData loadNPCData() { + try(FileReader fileReader = new FileReader(npcDataPath)) { + return gson.fromJson(fileReader, NPCData.class); + } catch (Exception e) { + return null; + } + } + + public Set npcs() { + return npcs; + } + +} diff --git a/src/main/java/net/voltexstudios/npcConversations/npc/NPCService.java b/src/main/java/net/voltexstudios/npcConversations/npc/NPCService.java new file mode 100644 index 0000000..e3a8095 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/npc/NPCService.java @@ -0,0 +1,56 @@ +package net.voltexstudios.npcConversations.npc; + +import lombok.Getter; +import org.bukkit.entity.Entity; + +import java.util.HashSet; +import java.util.Set; + +@Getter +public final class NPCService { + + private static NPCService instance; + private final Set npcs; + + public NPCService(NPCData npcData) { + instance = this; + + if (npcData == null) { + this.npcs = new HashSet<>(); + return; + } + + this.npcs = npcData.npcs(); + } + + public NPC getNPC(String name) { + return npcs.stream().filter(npc -> npc.getName().equals(name)).findFirst().orElse(null); + } + + public NPC getNPC(Entity entity) { + return npcs.stream().filter(npc -> npc.getClickableEntity().equals(entity.getUniqueId())).findFirst().orElse(null); + } + + public void addNPC(NPC npc) { + npcs.add(npc); + } + + public void removeNPC(NPC npc) { + npcs.remove(npc); + } + + public NPCData getNPCData() { + return new NPCData(npcs); + } + + public static void saveNPCData() { + NPCData.saveNPCData(); + } + + public static NPCService getInstance() { + if (instance == null) { + instance = new NPCService(null); + } + return instance; + } +} diff --git a/src/main/java/net/voltexstudios/npcConversations/session/ConverstionSession.java b/src/main/java/net/voltexstudios/npcConversations/session/ConverstionSession.java new file mode 100644 index 0000000..81c956d --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/session/ConverstionSession.java @@ -0,0 +1,31 @@ +package net.voltexstudios.npcConversations.session; + +import com.launchableinc.openai.completion.chat.ChatMessage; +import lombok.Getter; +import net.voltexstudios.npcConversations.npc.NPC; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class ConverstionSession { + + private final List messages; + private final NPC npc; + private final Player player; + + public ConverstionSession(@NotNull NPC npc, @NotNull Player player) { + this.npc = npc; + this.player = player; + messages = new ArrayList<>(); + + messages.add(new ChatMessage("user", "Create a new persona for yourself. Once set, you can't change it anymore. " + + "Stick to that persona at all times and react to me as if you were that persona." + + "Also describe your background story. Who are you? What are you doing here? If you get asked a question that does not fit your persona, say that you can't answer that question." + + "Also keep the conversation on a friendly level and don't be to serious." + )); + messages.add(new ChatMessage("assistant", npc.getBackgroundStory())); + } +} diff --git a/src/main/java/net/voltexstudios/npcConversations/util/Messages.java b/src/main/java/net/voltexstudios/npcConversations/util/Messages.java new file mode 100644 index 0000000..bd4485e --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/util/Messages.java @@ -0,0 +1,13 @@ +package net.voltexstudios.npcConversations.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Messages { + + public static @NotNull String format(@Nullable String str) { + if (str == null) return "Error - Bad Config"; + return str.replace("&", "§"); + } + +} \ No newline at end of file diff --git a/src/main/java/net/voltexstudios/npcConversations/util/OpenAI.java b/src/main/java/net/voltexstudios/npcConversations/util/OpenAI.java new file mode 100644 index 0000000..a96e6f3 --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/util/OpenAI.java @@ -0,0 +1,61 @@ +package net.voltexstudios.npcConversations.util; + +import com.launchableinc.openai.completion.chat.ChatCompletionRequest; +import com.launchableinc.openai.completion.chat.ChatMessage; +import com.launchableinc.openai.service.OpenAiService; +import org.bukkit.configuration.ConfigurationSection; +import retrofit2.HttpException; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class OpenAI { + + private static OpenAiService service; + + public static CompletableFuture init(String key) { + return CompletableFuture.runAsync(() -> service = new OpenAiService(key, Duration.ofSeconds(5))); + } + + public static CompletableFuture getResponse(ConfigurationSection section, List chatMessages, String message) { + chatMessages.add(new ChatMessage("user", message)); + + return CompletableFuture.supplyAsync(() -> { + String model = section.getString("model", "text-davinci-003"); + int maxTokens = section.getInt("max-tokens"); + double frequencyPenalty = section.getDouble("frequency-penalty"); + double presencePenalty = section.getDouble("presence-penalty"); + double topP = section.getDouble("top-p"); + double temperature = section.getDouble("temperature"); + + String reply = service.createChatCompletion(ChatCompletionRequest.builder() + .messages(chatMessages) + .model(model) + .temperature(temperature) + .maxTokens(maxTokens) + .topP(topP) + .frequencyPenalty(frequencyPenalty) + .presencePenalty(presencePenalty) + .stop(Arrays.asList("Human:", "AI:")) + .build()) + .getChoices().getFirst().getMessage().getContent(); + + chatMessages.add(new ChatMessage("assistant", reply)); + return reply; + }).exceptionally(throwable -> { + if (throwable.getCause() instanceof HttpException e) { + String reason = switch (e.response().code()) { + case 401 -> "Invalid API key! Please check your configuration."; + case 429 -> "Too many requests! Please wait a few seconds and try again."; + case 500 -> "OpenAI service is currently unavailable. Please try again later."; + default -> "Unknown error! Please try again later. If this error persists, contact the plugin developer."; + }; + throw new RuntimeException(reason, throwable); + } + throw new RuntimeException(throwable); + }); + } + +} \ No newline at end of file diff --git a/src/main/java/net/voltexstudios/npcConversations/util/Type.java b/src/main/java/net/voltexstudios/npcConversations/util/Type.java new file mode 100644 index 0000000..b6709df --- /dev/null +++ b/src/main/java/net/voltexstudios/npcConversations/util/Type.java @@ -0,0 +1,18 @@ +package net.voltexstudios.npcConversations.util; + +public enum Type { + + SINGLE, + BROADCAST, + FULL; + + public static Type getType(String type) { + return switch (type.toLowerCase()) { + case "single" -> Type.SINGLE; + case "broadcast" -> Type.BROADCAST; + case "full" -> Type.FULL; + default -> null; + }; + } + +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..2f6d27e --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,26 @@ +# https://beta.openai.com/account/api-keys +API-KEY: "key" + +chatgpt: + # https://platform.openai.com/docs/models/ + model: "gpt-3.5-turbo" + temperature: 0.9 + max-tokens: 150 + top-p: 1.0 + frequency-penalty: 0.0 + presence-penalty: 0.6 + +# the format of the conversation messages +format: + - "&b%player%: &7%message%" + - "&b%npc%: &a%message%" + +use-default-chat: false +send-messages-to-console: true + + +# how long an inactive conversation will last before it expires (in minutes) +conversation-expiration-duration: 3 + +# the range in which a player can talk to an NPC (in blocks) +npc-conversation-range: 5 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..53bf3fa --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: NPCConversations +version: '1.0' +main: net.voltexstudios.npcConversations.NPCConversations +api-version: '1.21' +authors: [ VoltexStudios ] +website: https://voltexstudios.net + +commands: + conversation: + description: Manage Conversation NPC's + permission: npcconversation.command + aliases: [conv, talk] diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..2f6d27e --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,26 @@ +# https://beta.openai.com/account/api-keys +API-KEY: "key" + +chatgpt: + # https://platform.openai.com/docs/models/ + model: "gpt-3.5-turbo" + temperature: 0.9 + max-tokens: 150 + top-p: 1.0 + frequency-penalty: 0.0 + presence-penalty: 0.6 + +# the format of the conversation messages +format: + - "&b%player%: &7%message%" + - "&b%npc%: &a%message%" + +use-default-chat: false +send-messages-to-console: true + + +# how long an inactive conversation will last before it expires (in minutes) +conversation-expiration-duration: 3 + +# the range in which a player can talk to an NPC (in blocks) +npc-conversation-range: 5 \ No newline at end of file diff --git a/target/classes/net/voltexstudios/npcConversations/NPCConversations.class b/target/classes/net/voltexstudios/npcConversations/NPCConversations.class new file mode 100644 index 0000000..3c6f55e Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/NPCConversations.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/commands/ConversationCommand.class b/target/classes/net/voltexstudios/npcConversations/commands/ConversationCommand.class new file mode 100644 index 0000000..46d25b3 Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/commands/ConversationCommand.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/listener/ChatHandler$1.class b/target/classes/net/voltexstudios/npcConversations/listener/ChatHandler$1.class new file mode 100644 index 0000000..16a7aea Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/listener/ChatHandler$1.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/listener/ChatHandler.class b/target/classes/net/voltexstudios/npcConversations/listener/ChatHandler.class new file mode 100644 index 0000000..fac5e9a Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/listener/ChatHandler.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/listener/PlayerListener.class b/target/classes/net/voltexstudios/npcConversations/listener/PlayerListener.class new file mode 100644 index 0000000..490956c Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/listener/PlayerListener.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/npc/Clickable.class b/target/classes/net/voltexstudios/npcConversations/npc/Clickable.class new file mode 100644 index 0000000..a7bd076 Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/npc/Clickable.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/npc/NPC.class b/target/classes/net/voltexstudios/npcConversations/npc/NPC.class new file mode 100644 index 0000000..99aebac Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/npc/NPC.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/npc/NPCData.class b/target/classes/net/voltexstudios/npcConversations/npc/NPCData.class new file mode 100644 index 0000000..99d40ec Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/npc/NPCData.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/npc/NPCService.class b/target/classes/net/voltexstudios/npcConversations/npc/NPCService.class new file mode 100644 index 0000000..8c976f4 Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/npc/NPCService.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/session/ConverstionSession.class b/target/classes/net/voltexstudios/npcConversations/session/ConverstionSession.class new file mode 100644 index 0000000..aaac6bc Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/session/ConverstionSession.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/util/Messages.class b/target/classes/net/voltexstudios/npcConversations/util/Messages.class new file mode 100644 index 0000000..3d7debd Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/util/Messages.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/util/OpenAI.class b/target/classes/net/voltexstudios/npcConversations/util/OpenAI.class new file mode 100644 index 0000000..24a9ea7 Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/util/OpenAI.class differ diff --git a/target/classes/net/voltexstudios/npcConversations/util/Type.class b/target/classes/net/voltexstudios/npcConversations/util/Type.class new file mode 100644 index 0000000..19b7886 Binary files /dev/null and b/target/classes/net/voltexstudios/npcConversations/util/Type.class differ diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..53bf3fa --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,12 @@ +name: NPCConversations +version: '1.0' +main: net.voltexstudios.npcConversations.NPCConversations +api-version: '1.21' +authors: [ VoltexStudios ] +website: https://voltexstudios.net + +commands: + conversation: + description: Manage Conversation NPC's + permission: npcconversation.command + aliases: [conv, talk] diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..b2f9860 --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=npcconversations +groupId=net.voltexstudios +version=1.0 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..0666c9d --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,13 @@ +net/voltexstudios/npcConversations/session/ConverstionSession.class +net/voltexstudios/npcConversations/npc/NPCData.class +net/voltexstudios/npcConversations/listener/PlayerListener.class +net/voltexstudios/npcConversations/listener/ChatHandler.class +net/voltexstudios/npcConversations/npc/Clickable.class +net/voltexstudios/npcConversations/util/OpenAI.class +net/voltexstudios/npcConversations/listener/ChatHandler$1.class +net/voltexstudios/npcConversations/npc/NPCService.class +net/voltexstudios/npcConversations/util/Type.class +net/voltexstudios/npcConversations/npc/NPC.class +net/voltexstudios/npcConversations/NPCConversations.class +net/voltexstudios/npcConversations/util/Messages.class +net/voltexstudios/npcConversations/commands/ConversationCommand.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..5b531b5 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,12 @@ +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/NPCConversations.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/commands/ConversationCommand.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/listener/ChatHandler.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/listener/PlayerListener.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/npc/Clickable.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/npc/NPC.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/npc/NPCData.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/npc/NPCService.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/session/ConverstionSession.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/util/Messages.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/util/OpenAI.java +/home/paisc/Dokumente/tmp_workspace/Voltex/NPCConversations/src/main/java/net/voltexstudios/npcConversations/util/Type.java diff --git a/target/npcconversations-1.0.jar b/target/npcconversations-1.0.jar new file mode 100644 index 0000000..0a55953 Binary files /dev/null and b/target/npcconversations-1.0.jar differ diff --git a/target/original-npcconversations-1.0.jar b/target/original-npcconversations-1.0.jar new file mode 100644 index 0000000..440041a Binary files /dev/null and b/target/original-npcconversations-1.0.jar differ