diff --git a/pom.xml b/pom.xml
index ef007f5..45ea5c1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,8 +17,8 @@
- 25
- 25
+ 26
+ 26
UTF-8
@@ -59,6 +59,15 @@
org.apache.maven.plugins
maven-compiler-plugin
3.15.0
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.46
+
+
+
diff --git a/src/main/java/com/softwarerat/API/data/GameData.java b/src/main/java/com/softwarerat/API/GameData.java
similarity index 91%
rename from src/main/java/com/softwarerat/API/data/GameData.java
rename to src/main/java/com/softwarerat/API/GameData.java
index 809fe31..923c3e0 100644
--- a/src/main/java/com/softwarerat/API/data/GameData.java
+++ b/src/main/java/com/softwarerat/API/GameData.java
@@ -1,7 +1,8 @@
-package com.softwarerat.API.data;
+package com.softwarerat.API;
import java.util.List;
+import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
@@ -34,6 +35,7 @@ public class GameData {
public GameData(GameAPI gameapi) {
this.gameapi = gameapi;
this.gameStateManager = new GameStateManager();
+ this.scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
}
public String getGameHost(JavaPlugin javaPlugin){
diff --git a/src/main/java/com/softwarerat/API/data/entity/EntityFlag.java b/src/main/java/com/softwarerat/API/data/entity/EntityFlag.java
new file mode 100644
index 0000000..c40f09a
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/entity/EntityFlag.java
@@ -0,0 +1,6 @@
+package com.softwarerat.API.data.entity;
+
+import com.softwarerat.API.data.store.Flag;
+
+public interface EntityFlag extends Flag {
+}
diff --git a/src/main/java/com/softwarerat/API/data/entity/EntityFlagAPI.java b/src/main/java/com/softwarerat/API/data/entity/EntityFlagAPI.java
new file mode 100644
index 0000000..fc22139
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/entity/EntityFlagAPI.java
@@ -0,0 +1,32 @@
+package com.softwarerat.API.data.entity;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.bukkit.entity.Entity;
+
+// Registry of EntityFlagHolders keyed by entity UUID, usable for NPCs and any other entity
+public class EntityFlagAPI {
+ private final Map holders = new ConcurrentHashMap<>();
+
+ public EntityFlagHolder get(Entity entity) {
+ return this.holders.computeIfAbsent(entity.getUniqueId(), id -> new EntityFlagHolder(entity));
+ }
+
+ public void setFlag(Entity entity, EntityFlag flag) {
+ get(entity).setFlag(flag);
+ }
+
+ public void removeFlag(Entity entity, EntityFlag flag) {
+ get(entity).removeFlag(flag);
+ }
+
+ public boolean hasFlag(Entity entity, EntityFlag... flags) {
+ return get(entity).hasFlag(flags);
+ }
+
+ public void clear(Entity entity) {
+ this.holders.remove(entity.getUniqueId());
+ }
+}
diff --git a/src/main/java/com/softwarerat/API/data/entity/EntityFlagHolder.java b/src/main/java/com/softwarerat/API/data/entity/EntityFlagHolder.java
new file mode 100644
index 0000000..7958421
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/entity/EntityFlagHolder.java
@@ -0,0 +1,52 @@
+package com.softwarerat.API.data.entity;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.bukkit.entity.Entity;
+
+import com.softwarerat.API.data.store.CacheManager;
+import com.softwarerat.API.data.store.FlagManager;
+
+// Per-entity flag storage. Unlike GamePlayer's single current PlayerFlag, entities
+// may carry several flags at once (e.g. NPC + PROTECTED + INTERACTABLE).
+public class EntityFlagHolder extends CacheManager implements FlagManager {
+ private static final String FLAGS_KEY = "__flags__";
+
+ private final Entity entity;
+
+ public EntityFlagHolder(Entity entity) {
+ this.entity = entity;
+ addToCache(FLAGS_KEY, new LinkedHashSet());
+ }
+
+ public Entity getEntity() {
+ return this.entity;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set flags() {
+ return (Set) getValueFromCache(FLAGS_KEY);
+ }
+
+ @Override
+ public void setFlag(EntityFlag flag) {
+ flags().add(flag);
+ }
+
+ @Override
+ public void removeFlag(EntityFlag flag) {
+ flags().remove(flag);
+ }
+
+ @Override
+ public boolean hasFlag(EntityFlag... flag) {
+ return Arrays.stream(flag).anyMatch(flags()::contains);
+ }
+
+ public Set getFlags() {
+ return Collections.unmodifiableSet(flags());
+ }
+}
diff --git a/src/main/java/com/softwarerat/API/data/entity/EntityFlags.java b/src/main/java/com/softwarerat/API/data/entity/EntityFlags.java
new file mode 100644
index 0000000..e6ec833
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/entity/EntityFlags.java
@@ -0,0 +1,26 @@
+package com.softwarerat.API.data.entity;
+
+public enum EntityFlags implements EntityFlag {
+ NPC("marks the entity as a non-player character", 1),
+ PROTECTED("entity cannot take damage or be targeted", 2),
+ INTERACTABLE("entity runs an action when right-clicked", 3);
+
+ private final String description;
+
+ private final Integer number;
+
+ EntityFlags(String description, Integer number) {
+ this.description = description;
+ this.number = number;
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public Integer getInt() {
+ return this.number;
+ }
+}
diff --git a/src/main/java/com/softwarerat/API/data/location/LocationAPI.java b/src/main/java/com/softwarerat/API/data/location/LocationAPI.java
index 6c8ff04..dbf65fd 100644
--- a/src/main/java/com/softwarerat/API/data/location/LocationAPI.java
+++ b/src/main/java/com/softwarerat/API/data/location/LocationAPI.java
@@ -1,12 +1,14 @@
package com.softwarerat.API.data.location;
-import com.softwarerat.API.data.GameData;
-import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
+import com.softwarerat.API.GameData;
+
+import lombok.AllArgsConstructor;
+
// Game related Location building
@AllArgsConstructor
public class LocationAPI {
diff --git a/src/main/java/com/softwarerat/API/data/npc/NPC.java b/src/main/java/com/softwarerat/API/data/npc/NPC.java
new file mode 100644
index 0000000..77348ac
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/npc/NPC.java
@@ -0,0 +1,59 @@
+package com.softwarerat.API.data.npc;
+
+import java.util.Set;
+import java.util.UUID;
+
+import org.bukkit.entity.Entity;
+
+import com.softwarerat.API.data.entity.EntityFlag;
+import com.softwarerat.API.data.entity.EntityFlagHolder;
+
+public class NPC {
+ private final String name;
+ private final Entity entity;
+ private final NPCAction action;
+ private final EntityFlagHolder flags;
+
+ public NPC(String name, Entity entity, NPCAction action, EntityFlagHolder flags) {
+ this.name = name;
+ this.entity = entity;
+ this.action = action;
+ this.flags = flags;
+ }
+
+ public UUID getEntityId() {
+ return this.entity.getUniqueId();
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public Entity getEntity() {
+ return this.entity;
+ }
+
+ public NPCAction getAction() {
+ return this.action;
+ }
+
+ public void setFlag(EntityFlag flag) {
+ this.flags.setFlag(flag);
+ }
+
+ public void removeFlag(EntityFlag flag) {
+ this.flags.removeFlag(flag);
+ }
+
+ public boolean hasFlag(EntityFlag... flag) {
+ return this.flags.hasFlag(flag);
+ }
+
+ public Set getFlags() {
+ return this.flags.getFlags();
+ }
+
+ public void remove() {
+ this.entity.remove();
+ }
+}
diff --git a/src/main/java/com/softwarerat/API/data/npc/NPCAPI.java b/src/main/java/com/softwarerat/API/data/npc/NPCAPI.java
new file mode 100644
index 0000000..1001af1
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/npc/NPCAPI.java
@@ -0,0 +1,73 @@
+package com.softwarerat.API.data.npc;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Mob;
+
+import com.softwarerat.API.data.entity.EntityFlagAPI;
+import com.softwarerat.API.data.entity.EntityFlagHolder;
+import com.softwarerat.API.data.entity.EntityFlags;
+
+import net.kyori.adventure.text.Component;
+
+// Spawns non-interactive NPC entities and dispatches an NPCAction when a player clicks them
+public class NPCAPI {
+ private final Map npcsByEntityId = new ConcurrentHashMap<>();
+ private final EntityFlagAPI entityFlagAPI;
+
+ public NPCAPI(EntityFlagAPI entityFlagAPI) {
+ this.entityFlagAPI = entityFlagAPI;
+ }
+
+ public NPC spawnNPC(Location location, EntityType type, String name, NPCAction action) {
+ Entity entity = location.getWorld().spawnEntity(location, type);
+ entity.customName(Component.text(name));
+ entity.setCustomNameVisible(true);
+ entity.setPersistent(false);
+ entity.setInvulnerable(true);
+
+ if (entity instanceof LivingEntity livingEntity) {
+ livingEntity.setCollidable(false);
+ livingEntity.setRemoveWhenFarAway(false);
+ livingEntity.setCanPickupItems(false);
+ livingEntity.setAI(false);
+ }
+ if (entity instanceof Mob mob) {
+ mob.setAware(false);
+ }
+
+ EntityFlagHolder flags = this.entityFlagAPI.get(entity);
+ flags.setFlag(EntityFlags.NPC);
+ flags.setFlag(EntityFlags.INTERACTABLE);
+
+ NPC npc = new NPC(name, entity, action, flags);
+ this.npcsByEntityId.put(npc.getEntityId(), npc);
+ return npc;
+ }
+
+ public void removeNPC(NPC npc) {
+ this.npcsByEntityId.remove(npc.getEntityId());
+ this.entityFlagAPI.clear(npc.getEntity());
+ npc.remove();
+ }
+
+ public void removeAll() {
+ this.npcsByEntityId.values().forEach(NPC::remove);
+ this.npcsByEntityId.clear();
+ }
+
+ public NPC getNPC(UUID entityId) {
+ return this.npcsByEntityId.get(entityId);
+ }
+
+ public Collection getNPCs() {
+ return this.npcsByEntityId.values();
+ }
+}
diff --git a/src/main/java/com/softwarerat/API/data/npc/NPCAction.java b/src/main/java/com/softwarerat/API/data/npc/NPCAction.java
new file mode 100644
index 0000000..7ddf88d
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/data/npc/NPCAction.java
@@ -0,0 +1,9 @@
+package com.softwarerat.API.data.npc;
+
+import org.bukkit.entity.Player;
+
+// Callback executed when a player clicks an NPC, e.g. open an interactive inventory
+@FunctionalInterface
+public interface NPCAction {
+ void onInteract(Player player, NPC npc);
+}
diff --git a/src/main/java/com/softwarerat/API/data/player/PlayerAPI.java b/src/main/java/com/softwarerat/API/data/player/PlayerAPI.java
index 870c187..7c4e639 100644
--- a/src/main/java/com/softwarerat/API/data/player/PlayerAPI.java
+++ b/src/main/java/com/softwarerat/API/data/player/PlayerAPI.java
@@ -3,8 +3,8 @@ package com.softwarerat.API.data.player;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
-import com.softwarerat.API.data.GameData;
import com.softwarerat.GameAPI;
+import com.softwarerat.API.GameData;
// Game related PlayerAPI for checking if a player is the GameHost
@@ -18,4 +18,5 @@ JavaPlugin javaPlugin = gameAPI.getJavaPlugin();
return p.getName().equals(s);
}
+
}
diff --git a/src/main/java/com/softwarerat/API/data/player/kit/KitAPI.java b/src/main/java/com/softwarerat/API/data/player/kit/KitAPI.java
index ea2fd43..a5d733c 100644
--- a/src/main/java/com/softwarerat/API/data/player/kit/KitAPI.java
+++ b/src/main/java/com/softwarerat/API/data/player/kit/KitAPI.java
@@ -17,36 +17,61 @@ import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
+import com.softwarerat.API.GameData;
+
public class KitAPI {
- private final HashMap kits;
-
+ private final GameData gameData;
+
+ private final HashMap kits;
+
private final ConcurrentHashMap playerKit;
-
+
private final ArrayList playersWithKits;
-
-
- public KitAPI() {
- this.kits = (HashMap)new HashMap<>();
+
+
+ public KitAPI(GameData gameData) {
+ this.gameData = gameData;
+ this.kits = new HashMap<>();
this.playerKit = new ConcurrentHashMap<>();
this.playersWithKits = new ArrayList<>();
}
-
+
public ItemStack[] loadKit(String base64) {
System.out.println("Loading Kit with data: " + base64);
try {
return itemStackArrayFromBase64(base64);
} catch (IOException iOException) {
return null;
- }
+ }
}
-
+
public ConcurrentHashMap getPlayerKit() {
return this.playerKit;
}
-
- public void giveKit(Player player, String base64) {
+
+ public boolean hasKit(String kitName) {
+ return this.kits.containsKey(kitName);
+ }
+
+ public void giveKit(Player player, String kitName) {
+ String base64 = this.kits.get(kitName);
+ if (base64 == null) {
+ throw new IllegalArgumentException("No kit saved with name: " + kitName);
+ }
+ giveKitFromData(player, base64);
+ this.playerKit.put(player.getUniqueId(), kitName);
+ }
+
+ public void givePrimaryKit(Player player) {
+ giveKit(player, this.gameData.getPrimaryKit());
+ }
+
+ public void giveSecondaryKit(Player player) {
+ giveKit(player, this.gameData.getSecondaryKit());
+ }
+
+ public void giveKitFromData(Player player, String base64) {
player.getInventory().clear();
- System.out.println(this.playerKit);
ItemStack[] kit = loadKit(base64);
for (int i = 0; i < kit.length; i++) {
ItemStack itemStack = kit[i];
@@ -122,13 +147,11 @@ public class KitAPI {
public void saveKit(Player player, String name) {
String kitBase64 = itemStackArrayToBase64(player.getInventory().getContents());
-
-
- this.kits.put(name, player.getInventory().getContents());
+ this.kits.put(name, kitBase64);
}
- public HashMap getKits() {
+ public HashMap getKits() {
return this.kits;
}
diff --git a/src/main/java/com/softwarerat/API/data/scoreboard/ScoreAPI.java b/src/main/java/com/softwarerat/API/data/scoreboard/ScoreAPI.java
index 46354fe..1d28b1f 100644
--- a/src/main/java/com/softwarerat/API/data/scoreboard/ScoreAPI.java
+++ b/src/main/java/com/softwarerat/API/data/scoreboard/ScoreAPI.java
@@ -1,21 +1,60 @@
package com.softwarerat.API.data.scoreboard;
-import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
+import org.bukkit.scoreboard.Criteria;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
-import org.bukkit.scoreboard.ScoreboardManager;
+import com.softwarerat.API.GameData;
+
+import net.kyori.adventure.text.Component;
+
+// Sidebar scoreboard for the game, built on GameData's shared Scoreboard so it
+// coexists with PrefixAPI's teams instead of replacing the player's board.
public class ScoreAPI {
+ private final GameData gameData;
+ private Objective objective;
- public ScoreAPI(String minigameName, String mapName,Player player,String localName){
- ScoreboardManager manager = Bukkit.getScoreboardManager();
- Scoreboard board = manager.getNewScoreboard();
- Objective objective = board.registerNewObjective(localName, "dummy");
- objective.setDisplaySlot(DisplaySlot.SIDEBAR);
- objective.setDisplayName(minigameName);
- player.setScoreboard(board);
+ public ScoreAPI(GameData gameData) {
+ this.gameData = gameData;
+ }
+ public void createSidebar(String objectiveName, String title) {
+ Scoreboard board = this.gameData.getScoreboard();
+ Objective existing = board.getObjective(objectiveName);
+ if (existing != null) {
+ existing.unregister();
+ }
+ this.objective = board.registerNewObjective(objectiveName, Criteria.DUMMY, Component.text(title));
+ this.objective.setDisplaySlot(DisplaySlot.SIDEBAR);
+ }
+
+ public void showTo(Player player) {
+ player.setScoreboard(this.gameData.getScoreboard());
+ }
+
+ public void setTitle(String title) {
+ if (this.objective == null) return;
+ this.objective.displayName(Component.text(title));
+
+ }
+
+
+
+
+ public void setScore(String line, int value) {
+ if (this.objective == null) return;
+ this.objective.getScore(line).setScore(value);
+ }
+
+ public void removeScore(String line) {
+ this.gameData.getScoreboard().resetScores(line);
+ }
+
+ public void clear() {
+ if (this.objective == null) return;
+ this.objective.unregister();
+ this.objective = null;
}
}
diff --git a/src/main/java/com/softwarerat/API/event/listener/NPCInteractListener.java b/src/main/java/com/softwarerat/API/event/listener/NPCInteractListener.java
new file mode 100644
index 0000000..2ab19a8
--- /dev/null
+++ b/src/main/java/com/softwarerat/API/event/listener/NPCInteractListener.java
@@ -0,0 +1,30 @@
+package com.softwarerat.API.event.listener;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+
+import com.softwarerat.API.data.entity.EntityFlags;
+import com.softwarerat.API.data.npc.NPC;
+import com.softwarerat.API.data.npc.NPCAPI;
+
+// Dispatches clicks on spawned NPCs to their registered NPCAction
+public class NPCInteractListener implements Listener {
+ private final NPCAPI npcAPI;
+
+ public NPCInteractListener(NPCAPI npcAPI) {
+ this.npcAPI = npcAPI;
+ }
+
+ @EventHandler
+ public void onPlayerInteractNPC(PlayerInteractEntityEvent event) {
+ NPC npc = this.npcAPI.getNPC(event.getRightClicked().getUniqueId());
+ if (npc == null) return;
+ if (!npc.hasFlag(EntityFlags.INTERACTABLE)) return;
+
+ event.setCancelled(true);
+ Player player = event.getPlayer();
+ npc.getAction().onInteract(player, npc);
+ }
+}
diff --git a/src/main/java/com/softwarerat/GameAPI.java b/src/main/java/com/softwarerat/GameAPI.java
index 6f10536..da9c580 100644
--- a/src/main/java/com/softwarerat/GameAPI.java
+++ b/src/main/java/com/softwarerat/GameAPI.java
@@ -5,10 +5,15 @@ import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
-import com.softwarerat.API.data.GameData;
+import com.softwarerat.API.GameData;
+import com.softwarerat.API.data.entity.EntityFlagAPI;
import com.softwarerat.API.data.location.LocationAPI;
+import com.softwarerat.API.data.npc.NPCAPI;
+import com.softwarerat.API.data.player.kit.KitAPI;
import com.softwarerat.API.data.prefix.PrefixAPI;
+import com.softwarerat.API.data.scoreboard.ScoreAPI;
import com.softwarerat.API.data.world.WorldAPI;
+import com.softwarerat.API.event.listener.NPCInteractListener;
import lombok.Getter;
import lombok.Setter;
@@ -23,13 +28,24 @@ GameData gameData;
WorldAPI worldAPI;
LocationAPI locationAPI;
PrefixAPI prefixAPI;
+KitAPI kitAPI;
+EntityFlagAPI entityFlagAPI;
+NPCAPI npcAPI;
+ScoreAPI scoreAPI;
public void enableAPI(JavaPlugin jPlugin){
javaPlugin = jPlugin;
+gameData = new GameData(this);
worldAPI = new WorldAPI();
System.out.println("Enabled GameAPI");
PluginManager pluginManager = Bukkit.getPluginManager();
locationAPI = new LocationAPI(gameData);
+kitAPI = new KitAPI(gameData);
+prefixAPI = new PrefixAPI(gameData.getScoreboard());
+scoreAPI = new ScoreAPI(gameData);
+entityFlagAPI = new EntityFlagAPI();
+npcAPI = new NPCAPI(entityFlagAPI);
+pluginManager.registerEvents(new NPCInteractListener(npcAPI), jPlugin);
//javaPlugin.getCommand("getNavigator").setExecutor(new Navigator(this));
//if (gameData.getGameType().equals(GameType.GAME_TYPE_TYPELESS) || gameData.getCurrentGameState().equals(GameState.GAME_STATE_LOBBY)){