Add ClientSide Builds
All checks were successful
Build / build (push) Successful in 1m50s

This commit is contained in:
unurled 2024-03-14 17:00:01 +01:00
parent e147e995bb
commit cde1c48b7a
Signed by: unurled
GPG key ID: FDBC9CBE1F82423F
8 changed files with 645 additions and 1 deletions

View file

@ -5,6 +5,7 @@ import static me.unurled.sacredrealms.sr.utils.Logger.error;
import java.lang.reflect.InvocationTargetException;
import me.unurled.sacredrealms.sr.SR;
import me.unurled.sacredrealms.sr.commands.admin.AttributeCommand;
import me.unurled.sacredrealms.sr.commands.admin.ClientBuildCommand;
import me.unurled.sacredrealms.sr.commands.player.ResetAdventureCommand;
import me.unurled.sacredrealms.sr.managers.Manager;
import org.bukkit.command.PluginCommand;

View file

@ -0,0 +1,241 @@
package me.unurled.sacredrealms.sr.commands.admin;
import static me.unurled.sacredrealms.sr.utils.Logger.log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import me.unurled.sacredrealms.sr.components.clientbuild.ClientBuild;
import me.unurled.sacredrealms.sr.components.clientbuild.ClientBuildManager;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
// TODO: test this command
public class ClientBuildCommand implements TabExecutor, Listener {
private List<String> nameCompletions = new ArrayList<>();
private ClientBuildManager manager = null;
/**
* Executes the given command, returning its success. <br>
* If false is returned, then the "usage" plugin.yml entry for this command (if defined) will be
* sent to the player.
*
* @param sender Source of the command
* @param command Command which was executed
* @param label Alias of the command which was used
* @param args Passed command arguments
* @return true if a valid command, otherwise false
*/
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args) {
if (!sender.hasPermission("sr.clientbuild")) {
sender.sendMessage("You do not have permission to use this command.");
return true;
}
if (!(sender instanceof Player p)) {
sender.sendMessage("You must be a player to use this command.");
return true;
}
if (args.length == 0) {
sender.sendMessage("Usage: /clientbuild <create|delete|list|modify|save|display>");
return true;
}
ClientBuildManager manager = ClientBuildManager.getInstance(ClientBuildManager.class);
if (args.length == 1) { // list
if (args[0].equalsIgnoreCase("list")) {
List<ClientBuild> builds = manager.getBuilds();
if (builds.isEmpty()) {
sender.sendMessage("There are no client builds.");
return true;
}
sender.sendMessage("Client Builds:");
for (ClientBuild build : builds) {
if (build != null) sender.sendMessage(build.getName());
}
return true;
} else {
sender.sendMessage("Usage: /clientbuild <create|delete|modify|save|display> <name>");
return true;
}
} else if (args.length == 2) {
// create, delete, modify, save, display
if (args[0].equalsIgnoreCase("create")) {
String name = args[1].toLowerCase();
if (manager.getBuildNames().contains(name)) {
sender.sendMessage("A client build with that name already exists.");
return true;
}
sender.sendMessage(
"Client build created, place blocks that you want to add to the Client"
+ " Build and save after you are finished.");
manager.setTemporaryBlocks(p, new HashMap<>());
manager.setTemporaryPreviousBlocks(p, new HashMap<>());
return true;
} else if (args[0].equalsIgnoreCase("delete")) {
String name = args[1].toLowerCase();
if (!manager.getBuildNames().contains(name)) {
sender.sendMessage("A client build with that name does not exist.");
return true;
}
manager.removeBuild(name);
sender.sendMessage("Client build deleted.");
return true;
} else if (args[0].equalsIgnoreCase("modify")) {
String name = args[1].toLowerCase();
if (!manager.getBuildNames().contains(name)) {
sender.sendMessage("A client build with that name does not exist.");
return true;
}
sender.sendMessage("Modify the client build and save after you are finished.");
manager.setTemporaryBlocks(p, new HashMap<>());
manager.setTemporaryPreviousBlocks(p, new HashMap<>());
return true;
} else if (args[0].equalsIgnoreCase("save")) {
// save the client build to the list of builds so stopping the listener.
ClientBuild build = new ClientBuild(args[1].toLowerCase());
if (!manager.hasTemporaryBlocks(p)) {
sender.sendMessage("You do not have any temporary blocks to save.");
return true;
}
HashMap<Location, BlockData> blocks = manager.getTemporaryBlocks(p);
log("blocks: " + blocks.size());
for (Entry<Location, BlockData> block : blocks.entrySet()) {
build.addBlock(block.getKey(), block.getValue());
}
manager.addBuild(build);
// remove blocks from the world
for (Entry<Location, BlockData> block : manager.getTemporaryPreviousBlocks(p).entrySet()) {
block.getKey().getWorld().setBlockData(block.getKey(), block.getValue());
// p.sendBlockChange(block.getLocation(), block.getBlockData());
}
manager.removeTemporaryBlocks(p);
manager.removeTemporaryPreviousBlocks(p);
sender.sendMessage("Client builds saved.");
return true;
} else if (args[0].equalsIgnoreCase("display")) {
String name = args[1].toLowerCase();
if (!manager.getBuildNames().contains(name)) {
sender.sendMessage("A client build with that name does not exist.");
return true;
}
ClientBuild build = manager.getBuild(name);
if (build == null) {
sender.sendMessage("An error occurred while displaying the client build.");
return true;
}
if (manager.getPlayerBuilds(p).contains(name)) {
manager.removeDisplayFromPlayer(p, build);
sender.sendMessage("Removed the client build display.");
} else {
manager.displayToPlayer(p, build);
sender.sendMessage("Displayed the client build.");
}
return true;
} else {
sender.sendMessage("Usage: /clientbuild <create|delete|modify|save|display> <name>");
return true;
}
} else {
sender.sendMessage("Usage: /clientbuild <create|delete|modify|save|display> <name>");
return true;
}
}
@EventHandler
public void onBlockPlace(@NotNull BlockPlaceEvent e) {
Player p = e.getPlayer();
if (manager == null) {
manager = ClientBuildManager.getInstance(ClientBuildManager.class);
}
if (manager.hasTemporaryBlocks(p)) {
log("block placed " + e.getBlock().getType());
manager.getTemporaryBlocks(p).put(e.getBlock().getLocation(), e.getBlock().getBlockData());
}
if (manager.hasTemporaryPreviousBlocks(p)) {
Block block = e.getBlock();
// set blockstate to block
manager
.getTemporaryPreviousBlocks(p)
.put(block.getLocation(), e.getBlockReplacedState().getBlockData());
}
}
@EventHandler
public void onBlockBreak(@NotNull BlockBreakEvent e) {
Player p = e.getPlayer();
if (manager == null) {
manager = ClientBuildManager.getInstance(ClientBuildManager.class);
}
if (manager.hasTemporaryBlocks(p)) {
manager.getTemporaryBlocks(p).remove(e.getBlock().getLocation());
}
if (manager.hasTemporaryPreviousBlocks(p)) {
Block block = e.getBlock();
// set blockstate to block
manager.getTemporaryPreviousBlocks(p).put(block.getLocation(), block.getBlockData());
}
}
/**
* Requests a list of possible completions for a command argument.
*
* @param sender Source of the command. For players tab-completing a command inside of a command
* block, this will be the player, not the command block.
* @param command Command which was executed
* @param label Alias of the command which was used
* @param args The arguments passed to the command, including final partial argument to be
* completed
* @return A List of possible completions for the final argument, or null to default to the
* command executor
*/
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args) {
if (args.length == 1) {
return List.of("create", "delete", "list", "modify", "save", "display");
} else if (args.length == 2) {
if (args[0].equalsIgnoreCase("delete")
|| args[0].equalsIgnoreCase("modify")
|| args[0].equalsIgnoreCase("display")) {
ClientBuildManager manager = ClientBuildManager.getInstance(ClientBuildManager.class);
List<String> buildNames = manager.getBuildNames();
if (nameCompletions.isEmpty() || !new HashSet<>(nameCompletions).containsAll(buildNames)) {
nameCompletions = buildNames;
}
return nameCompletions;
}
}
return null;
}
}

View file

@ -0,0 +1,39 @@
package me.unurled.sacredrealms.sr.components.clientbuild;
import java.util.HashMap;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.jetbrains.annotations.NotNull;
public class ClientBuild {
private final String name;
private final HashMap<Location, BlockData> blocks = new HashMap<>();
public ClientBuild(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public HashMap<Location, BlockData> getBlocks() {
return this.blocks;
}
public void addBlock(Location loc, BlockData state) {
this.blocks.put(loc, state);
}
public void removeBlock(@NotNull Location loc) {
this.blocks.remove(loc);
}
public void clearBlocks() {
this.blocks.clear();
}
public boolean containsBlock(@NotNull Location loc, @NotNull BlockData data) {
return this.blocks.containsKey(loc) && this.blocks.get(loc).matches(data);
}
}

View file

@ -0,0 +1,241 @@
package me.unurled.sacredrealms.sr.components.clientbuild;
import static me.unurled.sacredrealms.sr.utils.Logger.error;
import static me.unurled.sacredrealms.sr.utils.Logger.log;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import me.unurled.sacredrealms.sr.SR;
import me.unurled.sacredrealms.sr.commands.admin.ClientBuildCommand;
import me.unurled.sacredrealms.sr.data.DataHandler;
import me.unurled.sacredrealms.sr.data.DataManager;
import me.unurled.sacredrealms.sr.data.gson.ClientBuildDeserializer;
import me.unurled.sacredrealms.sr.data.gson.ClientBuildSerializer;
import me.unurled.sacredrealms.sr.managers.Manager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ClientBuildManager extends Manager {
private final List<ClientBuild> builds = new ArrayList<>();
/** a map of players that have ClientBuild displayed */
private final HashMap<Player, List<String>> playerBlocks = new HashMap<>();
private final HashMap<UUID, HashMap<Location, BlockData>> temporaryBlocks = new HashMap<>();
private final HashMap<UUID, HashMap<Location, BlockData>> temporaryPreviousBlocks =
new HashMap<>();
/** Load the manager */
@Override
public void load() {
super.load();
Bukkit.getPluginManager().registerEvents(new ClientBuildCommand(), SR.getInstance());
}
public List<ClientBuild> getBuilds() {
return this.builds;
}
/** Save the data */
@Override
public void saveData() {
// save client side builds to redis
DataManager dataManager = DataManager.getInstance(DataManager.class);
DataHandler dh = dataManager.getDataHandler();
Gson gson =
new GsonBuilder()
.registerTypeAdapter(ClientBuild.class, new ClientBuildSerializer())
.create();
for (ClientBuild build : builds) {
String json = gson.toJson(build);
dh.set("sr.clientbuild." + build.getName(), json);
}
for (Entry<Player, List<String>> entry : playerBlocks.entrySet()) {
for (String name : entry.getValue()) {
// save for player
dh.set("sr.players.clientbuild." + entry.getKey().getUniqueId() + "." + name, "true");
}
}
}
/** Load the data */
@Override
public void loadData() {
// load client side builds from redis
DataManager dataManager = DataManager.getInstance(DataManager.class);
DataHandler dh = dataManager.getDataHandler();
if (dh == null) {
error("Failed to get DataHandler instance, can't load client builds.");
error("Retrying in 10 seconds.");
Bukkit.getScheduler().runTaskLaterAsynchronously(SR.getInstance(), this::loadData, 200L);
return;
}
List<String> keys = dh.getKeysAll("sr.clientbuild");
log("Loading " + keys.size() + " client builds.");
Gson gson =
new GsonBuilder()
.registerTypeAdapter(ClientBuild.class, new ClientBuildDeserializer())
.create();
for (String key : keys) {
String json = dh.get(key);
ClientBuild build = gson.fromJson(json, ClientBuild.class);
if (build == null) {
error("Failed to load client build " + key + " " + json);
}
builds.add(build);
}
}
@EventHandler
public void onPlayerJoin(@NotNull PlayerJoinEvent e) {
DataHandler dh = DataManager.getInstance(DataManager.class).getDataHandler();
Player p = e.getPlayer();
List<String> names =
new ArrayList<>(dh.getKeysAll("sr.players.clientbuild." + p.getUniqueId()));
playerBlocks.put(p, names);
for (String name : names) {
ClientBuild build = getBuild(name);
if (build != null) {
displayToPlayer(p, build);
}
}
}
@EventHandler
public void onPlayerQuit(@NotNull PlayerQuitEvent e) {
Player p = e.getPlayer();
List<String> names = playerBlocks.get(p);
if (names == null) {
return;
}
Bukkit.getScheduler()
.runTaskAsynchronously(
SR.getInstance(),
() -> {
for (String name : names) {
ClientBuild build = getBuild(name);
if (build != null) {
removeDisplayFromPlayer(p, build);
}
}
});
// save the player's displayed builds
DataManager dataManager = DataManager.getInstance(DataManager.class);
DataHandler dh = dataManager.getDataHandler();
dh.remove("sr.players.clientbuild." + p.getUniqueId());
for (String name : names) {
dh.set("sr.players.clientbuild." + p.getUniqueId() + "." + name, "true");
}
playerBlocks.get(p).clear();
playerBlocks.remove(p);
}
public List<String> getBuildNames() {
List<String> names = new ArrayList<>();
for (ClientBuild build : builds) {
names.add(build.getName());
}
return names;
}
public void addBuild(ClientBuild build) {
builds.add(build);
}
public void removeBuild(String name) {
for (ClientBuild build : builds) {
if (build.getName().equals(name)) {
builds.remove(build);
return;
}
}
}
@Nullable
public ClientBuild getBuild(@NotNull String name) {
for (ClientBuild build : builds) {
if (build.getName().equals(name)) {
return build;
}
}
return null;
}
public List<String> getPlayerBuilds(Player p) {
return playerBlocks.get(p);
}
public void displayToPlayer(@NotNull Player p, @NotNull ClientBuild build) {
playerBlocks.get(p).add(build.getName());
for (Entry<Location, BlockData> entry : build.getBlocks().entrySet()) {
p.sendBlockChange(entry.getKey(), entry.getValue());
}
}
public void removeDisplayFromPlayer(@NotNull Player p, @NotNull ClientBuild build) {
playerBlocks.get(p).remove(build.getName());
for (Entry<Location, BlockData> entry : build.getBlocks().entrySet()) {
p.sendBlockChange(entry.getKey(), entry.getKey().getBlock().getBlockData());
}
}
public HashMap<Location, BlockData> getTemporaryBlocks(@NotNull Player p) {
return temporaryBlocks.get(p.getUniqueId());
}
public void setTemporaryBlocks(@NotNull Player p, HashMap<Location, BlockData> blocks) {
temporaryBlocks.put(p.getUniqueId(), blocks);
}
public boolean hasTemporaryBlocks(@NotNull Player p) {
return temporaryBlocks.containsKey(p.getUniqueId());
}
public void removeTemporaryBlocks(@NotNull Player p) {
temporaryBlocks.remove(p.getUniqueId());
}
public HashMap<Location, BlockData> getTemporaryPreviousBlocks(@NotNull Player p) {
return temporaryPreviousBlocks.get(p.getUniqueId());
}
public void setTemporaryPreviousBlocks(@NotNull Player p, HashMap<Location, BlockData> blocks) {
temporaryPreviousBlocks.put(p.getUniqueId(), blocks);
}
public boolean hasTemporaryPreviousBlocks(@NotNull Player p) {
return temporaryPreviousBlocks.containsKey(p.getUniqueId());
}
public void removeTemporaryPreviousBlocks(@NotNull Player p) {
temporaryPreviousBlocks.remove(p.getUniqueId());
}
}

View file

@ -0,0 +1,65 @@
package me.unurled.sacredrealms.sr.data.gson;
import static me.unurled.sacredrealms.sr.utils.Logger.error;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import me.unurled.sacredrealms.sr.components.clientbuild.ClientBuild;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
public class ClientBuildDeserializer implements JsonDeserializer<ClientBuild> {
/**
* Gson invokes this call-back method during deserialization when it encounters a field of the
* specified type.
*
* <p>In the implementation of this call-back method, you should consider invoking {@link
* JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects for any
* non-trivial field of the returned object. However, you should never invoke it on the same type
* passing {@code json} since that will cause an infinite loop (Gson will call your call-back
* method again).
*
* @param json The Json data being deserialized
* @param typeOfT The type of the Object to deserialize to
* @param context
* @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
* @throws JsonParseException if json is not in the expected format of {@code typeofT}
*/
@Override
public ClientBuild deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
String[] data = json.getAsString().split(";");
ClientBuild build = new ClientBuild(data[0]);
for (String block : data[1].split(",")) {
String[] spl = block.replace("{", "").replace("}", "").split("\\|");
World world = Bukkit.getWorld(spl[0]);
if (world == null) {
error("World " + spl[0] + " does not exist");
continue;
}
Location loc;
BlockData bData;
try {
loc =
new Location(
world,
Integer.parseInt(spl[1]),
Integer.parseInt(spl[2]),
Integer.parseInt(spl[3]));
bData = Bukkit.createBlockData(spl[4]);
} catch (NumberFormatException e) {
error("Invalid block data: " + block);
continue;
}
build.addBlock(loc, bData);
}
return build;
}
}

View file

@ -0,0 +1,50 @@
package me.unurled.sacredrealms.sr.data.gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.Map.Entry;
import me.unurled.sacredrealms.sr.components.clientbuild.ClientBuild;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
public class ClientBuildSerializer implements JsonSerializer<ClientBuild> {
/**
* Gson invokes this call-back method during serialization when it encounters a field of the
* specified type.
*
* <p>In the implementation of this call-back method, you should consider invoking {@link
* JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
* non-trivial field of the {@code src} object. However, you should never invoke it on the {@code
* src} object itself since that will cause an infinite loop (Gson will call your call-back method
* again).
*
* @param src the object that needs to be converted to Json.
* @param typeOfSrc the actual type (fully genericized version) of the source object.
* @param context
* @return a JsonElement corresponding to the specified object.
*/
@Override
public JsonElement serialize(ClientBuild src, Type typeOfSrc, JsonSerializationContext context) {
StringBuilder s = new StringBuilder();
s.append(src.getName());
s.append(";");
for (Entry<Location, BlockData> entry : src.getBlocks().entrySet()) {
s.append("{");
s.append(entry.getKey().getWorld().getName());
s.append("|");
s.append(entry.getKey().getBlockX());
s.append("|");
s.append(entry.getKey().getBlockY());
s.append("|");
s.append(entry.getKey().getBlockZ());
s.append("|");
s.append(entry.getValue().getAsString());
s.append("},");
}
return new JsonPrimitive(s.toString());
}
}

View file

@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.List;
import me.unurled.sacredrealms.sr.SR;
import me.unurled.sacredrealms.sr.commands.CommandManager;
import me.unurled.sacredrealms.sr.components.clientbuild.ClientBuildManager;
import me.unurled.sacredrealms.sr.components.combat.CombatManager;
import me.unurled.sacredrealms.sr.components.entity.EntityManager;
import me.unurled.sacredrealms.sr.components.item.ItemManager;

View file

@ -17,10 +17,16 @@ permissions:
sr.attributes:
default: op
description: When the player has permission for the command /attributes set
sr.clientbuild:
description: When the player has permission for the command /clientbuild
sr.resetadventure:
default: op
description: When the player has permission for the command /resetadventure
commands:
attributes:
description: Set the attributes of the player.
description: Set the attributes of the player.
clientbuild:
description: Set the client build of the player.
resetadventure:
description: Reset the adventure of the player