Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,19 @@ jobs:

- name: Bump patch version in pom.xml
id: version
env:
GH_TOKEN: ${{ github.token }}
run: |
CURRENT="$(mvn -B -ntp help:evaluate -Dexpression=project.version -q -DforceStdout)"
IFS='.' read -r major minor patch <<< "${CURRENT//-SNAPSHOT/}"
NEW="${major}.${minor}.$((patch + 1))"

# pom.xml can lag behind tags when release bump PRs merge on main only
while gh release view "v${NEW}" >/dev/null 2>&1; do
IFS='.' read -r major minor patch <<< "${NEW}"
NEW="${major}.${minor}.$((patch + 1))"
done

echo "current=${CURRENT}" >> "$GITHUB_OUTPUT"
echo "new=${NEW}" >> "$GITHUB_OUTPUT"
mvn -B -ntp org.codehaus.mojo:versions-maven-plugin:2.17.1:set \
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@
log/
target/
/bin/
dependency-reduced-pom.xml
dependency-reduced-pom.xml

# Local dev (portable JDK, downloaded release JARs)
.jdk/
dev/
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>edu.whimc</groupId>
<artifactId>WHIMC-QRF-Agent</artifactId>
<version>3.3.16</version>
<version>3.3.20</version>
<name>WHIMC QRF Agent</name>
<description>Defines overworld agent traits</description>

Expand Down
20 changes: 20 additions & 0 deletions scripts/build-local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
JDK_HOME="${ROOT}/.jdk/jdk-21.0.11+10/Contents/Home"

if [[ ! -x "${JDK_HOME}/bin/java" ]]; then
echo "Portable JDK 21 not found at ${JDK_HOME}"
echo "Download Temurin 21 for macOS aarch64 into ${ROOT}/.jdk/ or install Java 21 and set JAVA_HOME."
exit 1
fi

export JAVA_HOME="${JDK_HOME}"
cd "${ROOT}"
chmod +x mvnw
./mvnw -B -ntp -DskipTests package

echo
echo "Built: ${ROOT}/target/WHIMC-QRF-Agent-$(./mvnw -q -DforceStdout help:evaluate -Dexpression=project.version).jar"
echo "Copy that JAR into your Paper server's plugins/ folder and restart."
2 changes: 1 addition & 1 deletion src/main/java/edu/whimc/overworld_agent/Listeners.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void onPlayerJoin(PlayerJoinEvent event){
plugin.relinkOwnedAgent(player);
Map<String, NPC> agents = plugin.getAgents();
NPC npc = agents.get(player.getName());
if(npc != null) {
if (npc != null) {
npc.getOrAddTrait(AgentPermanentFlyingTrait.class);
npc.getOrAddTrait(AgentFollowCatchUpTrait.class);
npc.spawn(player.getLocation());
Expand Down
107 changes: 87 additions & 20 deletions src/main/java/edu/whimc/overworld_agent/OverworldAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import edu.whimc.overworld_agent.dialoguetemplate.models.llm.LlmRagContextBuilder;
import edu.whimc.overworld_agent.dialoguetemplate.models.llm.WorldLlmPromptRegistry;
import edu.whimc.overworld_agent.dialoguetemplate.models.BuildTemplate;
import edu.whimc.overworld_agent.utils.AgentEntityTypes;
import edu.whimc.overworld_agent.utils.sql.Queryer;

import org.bukkit.Bukkit;
Expand Down Expand Up @@ -78,6 +79,7 @@ public class OverworldAgent extends JavaPlugin {
@Override
public void onEnable() {
saveDefaultConfig();
AgentEntityTypes.load(this);
//receiver = (SpeechReceiver) Bukkit.getServer().getPluginManager().getPlugin("SpeechReceiver");
sessions = new HashMap<>();
buildTemplates = new HashMap<>();
Expand Down Expand Up @@ -119,6 +121,8 @@ public void onEnable() {
getCommand("agent").setExecutor(agentCommand);
getCommand("agent").setTabCompleter(agentCommand);

edu.whimc.overworld_agent.utils.AgentPermissions.register();

HabitatAssessCommand assessCommand = new HabitatAssessCommand(this);
getCommand("assess-habitat").setExecutor(assessCommand);
getCommand("assess-habitat").setTabCompleter(assessCommand);
Expand Down Expand Up @@ -212,13 +216,92 @@ public void onDisable(){

public Map<String, NPC> getAgents(){return agents;}

/** When false, hides builder dialogue options and blocks {@code /agent rebuilderspawn}. */
public boolean isBuilderEnabled() {
return getConfig().getBoolean("builder.enabled", false);
}

public void removeAgents(){
agents = new HashMap<>();
}

public void removeAgent(String playerName){
agents.remove(playerName);
}

/**
* Permanently removes every Citizens NPC assigned to {@code playerName} and drops them from {@link #agents}.
* Destroy must clear Citizens persistence, not just the in-memory map, or {@link #relinkOwnedAgent} will
* resurrect the agent on the next login.
*/
public void destroyOwnedAgent(String playerName) {
if (playerName == null || playerName.isBlank()) {
return;
}
agents.remove(playerName);
List<NPC> snapshot = new ArrayList<>();
for (NPC npc : CitizensAPI.getNPCRegistry()) {
snapshot.add(npc);
}
for (NPC npc : snapshot) {
if (!playerName.equalsIgnoreCase(resolveNpcOwnerName(npc))) {
continue;
}
destroyCitizensNpc(npc);
}
}

/** Removes all player-owned agent NPCs from Citizens and clears {@link #agents}. */
public void destroyAllOwnedAgents() {
for (String owner : new ArrayList<>(agents.keySet())) {
destroyOwnedAgent(owner);
}
List<NPC> snapshot = new ArrayList<>();
for (NPC npc : CitizensAPI.getNPCRegistry()) {
snapshot.add(npc);
}
for (NPC npc : snapshot) {
if (resolveNpcOwnerName(npc) != null) {
destroyCitizensNpc(npc);
}
}
agents.clear();
}

private static void destroyCitizensNpc(NPC npc) {
if (npc == null) {
return;
}
try {
if (npc.isSpawned()) {
npc.despawn();
}
npc.destroy();
} catch (Exception ex) {
JavaPlugin.getPlugin(OverworldAgent.class).getLogger().warning(
"Failed to destroy agent NPC " + npc.getId() + ": " + ex.getMessage());
}
}

/** @return owning player name for guide/builder agent NPCs, or null */
public static String resolveNpcOwnerName(NPC npc) {
if (npc == null) {
return null;
}
if (npc.hasTrait(SpawnExpertTrait.class)) {
return npc.getOrAddTrait(SpawnExpertTrait.class).getAssignedPlayerName();
}
if (npc.hasTrait(SpawnNoviceTrait.class)) {
SpawnNoviceTrait trait = npc.getTrait(SpawnNoviceTrait.class);
return trait != null ? trait.getAssignedPlayerName() : null;
}
if (npc.hasTrait(RebuilderTrait.class)) {
RebuilderTrait trait = npc.getTrait(RebuilderTrait.class);
return trait != null ? trait.getTargetPlayerName() : null;
}
return null;
}

public SignMenuFactory getSignMenuFactory(){return signMenuFactory; }

public ChatTextInputFactory getChatTextInputFactory() {
Expand Down Expand Up @@ -302,26 +385,10 @@ public void relinkOwnedAgent(Player player) {
return;
}
for (NPC npc : CitizensAPI.getNPCRegistry()) {
if (npc.hasTrait(SpawnExpertTrait.class)) {
SpawnExpertTrait t = npc.getOrAddTrait(SpawnExpertTrait.class);
if (name.equals(t.getAssignedPlayerName())) {
agents.put(name, npc);
return;
}
}
if (npc.hasTrait(SpawnNoviceTrait.class)) {
SpawnNoviceTrait t = npc.getTrait(SpawnNoviceTrait.class);
if (t != null && name.equals(t.getAssignedPlayerName())) {
agents.put(name, npc);
return;
}
}
if (npc.hasTrait(RebuilderTrait.class)) {
RebuilderTrait t = npc.getTrait(RebuilderTrait.class);
if (t != null && name.equals(t.getTargetPlayerName())) {
agents.put(name, npc);
return;
}
String owner = resolveNpcOwnerName(npc);
if (owner != null && name.equalsIgnoreCase(owner)) {
agents.put(name, npc);
return;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edu.whimc.overworld_agent.commands;

import edu.whimc.overworld_agent.OverworldAgent;
import edu.whimc.overworld_agent.utils.AgentPermissions;
import edu.whimc.overworld_agent.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
Expand Down Expand Up @@ -89,7 +90,8 @@ protected List<String> onTabComplete(CommandSender sender, String[] args) {
}

public List<String> executeOnTabComplete(CommandSender sender, String[] args) {
if (!sender.hasPermission(getPermission()) || args.length > this.maxArgs) {
if ((!AgentPermissions.bypassesRestrictions(sender) && !sender.hasPermission(getPermission()))
|| args.length > this.maxArgs) {
return Arrays.asList();
}
return onTabComplete(sender, args);
Expand Down Expand Up @@ -133,7 +135,7 @@ public Permission getPermission() {
protected abstract boolean onCommand(CommandSender sender, String[] args);

public boolean executeSubCommand(CommandSender sender, String[] args) {
if (!sender.hasPermission(getPermission())) {
if (!AgentPermissions.bypassesRestrictions(sender) && !sender.hasPermission(getPermission())) {
Utils.msg(sender,
"&cYou do not have the required permission!",
" &f&o" + getPermission().getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
*/
public class AgentCommand implements CommandExecutor, TabCompleter {

private final OverworldAgent plugin;
private final Map<String, AbstractSubCommand> subCommands = new HashMap<>();

public AgentCommand(OverworldAgent plugin) {
this.plugin = plugin;
String base = "agent";
subCommands.put("chat", new ChatCommand(plugin, base, "chat"));
subCommands.put("spawn", plugin.getExpertSpawnCommand());
Expand Down Expand Up @@ -57,13 +59,17 @@ public boolean onCommand(CommandSender sender, Command cmd, String commandLabel,
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 0) {
return subCommands.keySet().stream().sorted().collect(Collectors.toList());
return subCommands.keySet().stream()
.filter(v -> !v.equals("rebuilderspawn") || plugin.isBuilderEnabled())
.sorted()
.collect(Collectors.toList());
}

if (args.length == 1) {
return subCommands.keySet()
.stream()
.filter(v -> v.startsWith(args[0].toLowerCase()))
.filter(v -> !v.equals("rebuilderspawn") || plugin.isBuilderEnabled())
.sorted()
.collect(Collectors.toList());
}
Expand Down
Loading
Loading