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)){