first commit

This commit is contained in:
paisc 2024-12-24 14:12:40 +01:00
commit 2f05711bb4
45 changed files with 1004 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -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

15
.idea/checkstyle-idea.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>10.20.1</checkstyleVersion>
<scanScope>JavaOnly</scanScope>
<option name="thirdPartyClasspath" />
<option name="activeLocationIds" />
<option name="locations">
<list>
<ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">(bundled)</ConfigurationLocation>
<ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">(bundled)</ConfigurationLocation>
</list>
</option>
</component>
</project>

13
.idea/compiler.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="npcconversations" />
</profile>
</annotationProcessing>
</component>
</project>

7
.idea/encodings.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

30
.idea/jarRepositories.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="sonatype" />
<option name="name" value="sonatype" />
<option name="url" value="https://oss.sonatype.org/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="papermc-repo" />
<option name="name" value="papermc-repo" />
<option name="url" value="https://repo.papermc.io/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

14
.idea/misc.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/npcconversations.iml" filepath="$PROJECT_DIR$/npcconversations.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.voltexstudios</groupId>
<artifactId>npcconversations</artifactId>
<name>npcconversations</name>
<version>1.0</version>
<build>
<defaultGoal>clean package</defaultGoal>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

13
npcconversations.iml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>ADVENTURE</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
</facet>
</component>
</module>

85
pom.xml Normal file
View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.voltexstudios</groupId>
<artifactId>npcconversations</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>npcconversations</name>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.launchableinc.openai-java/service -->
<dependency>
<groupId>com.launchableinc.openai-java</groupId>
<artifactId>service</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<Player, ConverstionSession> CACHE;
public static Cache<Player, Type> 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<Player, ConverstionSession>) 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;
}
}

View File

@ -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 <create|delete> <name>"))
);
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 <create|delete> <name>"))
);
return true;
}
@Override
public @Nullable List<String> 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();
}
}

View File

@ -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<Player> recipients = switch (NPCConversations.USER_TYPE.asMap().getOrDefault(player, hasFull ? Type.FULL : Type.SINGLE)) {
case SINGLE -> Collections.singletonList(player);
case FULL, BROADCAST -> event.getRecipients();
};
List<String> 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<ChatMessage> 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<Player> 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);
}
}

View File

@ -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)
)
);
}
}
}
}

View File

@ -0,0 +1,9 @@
package net.voltexstudios.npcConversations.npc;
import org.bukkit.entity.Player;
public interface Clickable {
void onClick(Player player);
}

View File

@ -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);
}
}

View File

@ -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<NPC> npcs;
public NPCData(Set<NPC> 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<NPC> npcs() {
return npcs;
}
}

View File

@ -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<NPC> 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;
}
}

View File

@ -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<ChatMessage> 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()));
}
}

View File

@ -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("&", "§");
}
}

View File

@ -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<Void> init(String key) {
return CompletableFuture.runAsync(() -> service = new OpenAiService(key, Duration.ofSeconds(5)));
}
public static CompletableFuture<String> getResponse(ConfigurationSection section, List<ChatMessage> 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);
});
}
}

View File

@ -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;
};
}
}

View File

@ -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

View File

@ -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]

26
target/classes/config.yml Normal file
View File

@ -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

12
target/classes/plugin.yml Normal file
View File

@ -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]

View File

@ -0,0 +1,3 @@
artifactId=npcconversations
groupId=net.voltexstudios
version=1.0

View File

@ -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

View File

@ -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

Binary file not shown.

Binary file not shown.