Citizen's hook WIP

This commit is contained in:
tastybento 2024-11-30 07:59:31 -08:00
parent 55daa72e55
commit 9ab9d7f11b
7 changed files with 299 additions and 16 deletions

21
pom.xml
View File

@ -84,7 +84,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>3.0.1</build.version>
<build.version>3.1.0</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<server.jars>${project.basedir}/lib</server.jars>
@ -192,6 +192,11 @@
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
<!-- Citizens -->
<repository>
<id>citizens-repo</id>
<url>https://maven.citizensnpcs.co/repo</url>
</repository>
</repositories>
<dependencies>
@ -387,6 +392,20 @@
<version>1.1.13</version>
<scope>compile</scope>
</dependency>
<!-- Citizens -->
<dependency>
<groupId>net.citizensnpcs</groupId>
<artifactId>citizens-main</artifactId>
<version>2.0.35-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>

View File

@ -24,6 +24,7 @@ import world.bentobox.bentobox.api.user.Notifier;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.hooks.CitizensHook;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.MultipaperHook;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
@ -192,6 +193,9 @@ public class BentoBox extends JavaPlugin implements Listener {
hooksManager.registerHook(new VaultHook());
// Citizens
hooksManager.registerHook(new CitizensHook());
// MythicMobs
hooksManager.registerHook(new MythicMobsHook());

View File

@ -2,7 +2,6 @@ package world.bentobox.bentobox.blueprints;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@ -23,8 +22,8 @@ import org.bukkit.block.sign.Side;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Horse;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Villager;
@ -44,6 +43,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.hooks.CitizensHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
/**
@ -70,20 +70,21 @@ public class BlueprintClipboard {
private final Map<Vector, BlueprintBlock> bpBlocks = new LinkedHashMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private Optional<MythicMobsHook> mmh;
private Optional<CitizensHook> ch;
/**
* Create a clipboard for blueprint
* @param blueprint - the blueprint to load into the clipboard
*/
public BlueprintClipboard(@NonNull Blueprint blueprint) {
this();
this.blueprint = blueprint;
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
public BlueprintClipboard() {
// MythicMobs
// Citizens Hook
ch = plugin.getHooks().getHook("Citizens").filter(CitizensHook.class::isInstance).map(CitizensHook.class::cast);
// MythicMobs Hook
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
@ -136,13 +137,20 @@ public class BlueprintClipboard {
private void copyAsync(World world, User user, List<Vector> vectorsToCopy, int speed, boolean copyAir, boolean copyBiome) {
copying = false;
// Citizens
if (ch.isPresent()) {
// Add all the citizens for the area in one go. This is pretty fast.
bpEntities.putAll(ch.get().getCitizensInArea(world, vectorsToCopy, origin));
}
// Repeating copy task
copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (copying) {
return;
}
copying = true;
vectorsToCopy.stream().skip(index).limit(speed).forEach(v -> {
List<LivingEntity> ents = world.getLivingEntities().stream()
List<Entity> ents = world.getEntities().stream()
.filter(Objects::nonNull)
.filter(e -> !(e instanceof Player))
.filter(e -> new Vector(Math.rint(e.getLocation().getX()),
@ -153,6 +161,7 @@ public class BlueprintClipboard {
count++;
}
});
index += speed;
int percent = (int)(index * 100 / (double)vectorsToCopy.size());
if (percent != lastPercentage && percent % 10 == 0) {
@ -189,9 +198,9 @@ public class BlueprintClipboard {
return r;
}
private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, Collection<LivingEntity> entities) {
private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, List<Entity> ents) {
Block block = l.getBlock();
if (!copyAir && block.getType().equals(Material.AIR) && entities.isEmpty()) {
if (!copyAir && block.getType().equals(Material.AIR) && ents.isEmpty()) {
return false;
}
// Create position
@ -202,14 +211,14 @@ public class BlueprintClipboard {
Vector pos = new Vector(x, y, z);
// Set entities
List<BlueprintEntity> bpEnts = setEntities(entities);
List<BlueprintEntity> bpEnts = setEntities(ents);
// Store
if (!bpEnts.isEmpty()) {
bpEntities.put(pos, bpEnts);
}
// Return if this is just air block
if (!copyAir && block.getType().equals(Material.AIR) && !entities.isEmpty()) {
if (!copyAir && block.getType().equals(Material.AIR) && !ents.isEmpty()) {
return true;
}
@ -291,9 +300,15 @@ public class BlueprintClipboard {
return cs;
}
private List<BlueprintEntity> setEntities(Collection<LivingEntity> entities) {
/**
* Deals with any entities that are in this block. Technically, this could be more than one, but is usually one.
* @param ents collection of entities
* @return Serialized list of entities
*/
private List<BlueprintEntity> setEntities(List<Entity> ents) {
List<BlueprintEntity> bpEnts = new ArrayList<>();
for (LivingEntity entity: entities) {
for (Entity entity : ents) {
BentoBox.getInstance().logDebug("Etity = " + entity);
BlueprintEntity bpe = new BlueprintEntity();
bpe.setType(entity.getType());
@ -329,6 +344,7 @@ public class BlueprintClipboard {
bpe.setStyle(horse.getStyle());
}
// Mythic mob check
mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity))
.ifPresent(bpe::setMythicMobsRecord);

View File

@ -124,12 +124,38 @@ public class BlueprintPaster {
location.setY(y);
}
private record Bits(Map<Vector, BlueprintBlock> blocks,
/**
* A record of all the "bits" of the blueprint that need to be pasted
* Consists of blocks, attached blocks, entities, iterators for the blocks and a speed
*/
private record Bits(
/**
* Basic blocks to the pasted (not attached blocks)
*/
Map<Vector, BlueprintBlock> blocks,
/**
* Attached blocks
*/
Map<Vector, BlueprintBlock> attached,
/**
* Entities to be pasted
*/
Map<Vector, List<BlueprintEntity>> entities,
/**
* Basic block pasting iterator
*/
Iterator<Entry<Vector, BlueprintBlock>> it,
/**
* Attached block pasting iterator
*/
Iterator<Entry<Vector, BlueprintBlock>> it2,
/**
* Entity pasting iterator
*/
Iterator<Entry<Vector, List<BlueprintEntity>>> it3,
/**
* Paste speed
*/
int pasteSpeed) {}
/**

View File

@ -24,6 +24,11 @@ import com.google.gson.annotations.Expose;
*/
public class BlueprintEntity {
// Citizens storage
@Expose
private String citizen;
// MythicMobs storage
public record MythicMobRecord(String type, String displayName, double level, float power, String stance) {
}
@ -303,5 +308,38 @@ public class BlueprintEntity {
this.MMStance = mmr.stance();
this.MMpower = mmr.power();
}
/**
* @return the citizen
*/
public String getCitizen() {
return citizen;
}
/**
* @param citizen the citizen to set
*/
public void setCitizen(String citizen) {
this.citizen = citizen;
}
@Override
public String toString() {
return "BlueprintEntity [" + (citizen != null ? "citizen=" + citizen + ", " : "")
+ (MMtype != null ? "MMtype=" + MMtype + ", " : "")
+ (MMLevel != null ? "MMLevel=" + MMLevel + ", " : "")
+ (MMStance != null ? "MMStance=" + MMStance + ", " : "")
+ (MMpower != null ? "MMpower=" + MMpower + ", " : "") + (color != null ? "color=" + color + ", " : "")
+ (type != null ? "type=" + type + ", " : "")
+ (customName != null ? "customName=" + customName + ", " : "")
+ (tamed != null ? "tamed=" + tamed + ", " : "") + (chest != null ? "chest=" + chest + ", " : "")
+ (adult != null ? "adult=" + adult + ", " : "")
+ (domestication != null ? "domestication=" + domestication + ", " : "")
+ (inventory != null ? "inventory=" + inventory + ", " : "")
+ (style != null ? "style=" + style + ", " : "") + (level != null ? "level=" + level + ", " : "")
+ (profession != null ? "profession=" + profession + ", " : "")
+ (experience != null ? "experience=" + experience + ", " : "")
+ (villagerType != null ? "villagerType=" + villagerType : "") + "]";
}
}

View File

@ -0,0 +1,165 @@
package world.bentobox.bentobox.hooks;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.Nullable;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.npc.NPCRegistry;
import net.citizensnpcs.api.util.MemoryDataKey;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
/**
* Provides copy and pasting of Citizens in blueprints
*
* @author tastybento
* @since 3.1.0
*/
public class CitizensHook extends Hook {
MemoryDataKey dataKeyTest = new MemoryDataKey();
public CitizensHook() {
super("Citizens", Material.PLAYER_HEAD);
}
public String serializeNPC(NPC npc) {
if (npc == null) {
throw new IllegalArgumentException("NPC cannot be null.");
}
MemoryDataKey dataKey = new MemoryDataKey();
npc.save(dataKey); // Save NPC data into the MemoryDataKey
// Convert MemoryDataKey to a YAML string
YamlConfiguration yaml = new YamlConfiguration();
for (Entry<String, Object> en : dataKey.getValuesDeep().entrySet()) {
BentoBox.getInstance().logDebug("Serial key = " + en.getKey() + " = " + en.getValue());
yaml.set(en.getKey(), en.getValue());
}
dataKeyTest = dataKey;
return yaml.saveToString();
}
/**
* Get a map of serialized Citizens that are in a set of locations
* @param world world where this occurs
* @param vectorsToCopy list of locations in that world as vectors
* @param origin
* @return map
*/
public Map<Vector, List<BlueprintEntity>> getCitizensInArea(World world, List<Vector> vectorsToCopy,
@Nullable Vector origin) {
Map<Vector, List<BlueprintEntity>> bpEntities = new HashMap<>();
for (NPC npc : CitizensAPI.getNPCRegistry()) {
if (npc.isSpawned()) {
Location npcLocation = npc.getEntity().getLocation();
Vector spot = new Vector(npcLocation.getBlockX(), npcLocation.getBlockY(), npcLocation.getBlockZ());
if (npcLocation.getWorld().equals(world) && vectorsToCopy.contains(spot)) {
BlueprintEntity cit = new BlueprintEntity();
cit.setType(npc.getEntity().getType()); // Must be set to be pasted
cit.setCitizen(serializeNPC(npc));
// Retrieve or create the list, then add the entity
List<BlueprintEntity> entities = bpEntities.getOrDefault(spot, new ArrayList<>());
entities.add(cit);
// Create position
Vector origin2 = origin == null ? new Vector(0, 0, 0) : origin;
int x = spot.getBlockX() - origin2.getBlockX();
int y = spot.getBlockY() - origin2.getBlockY();
int z = spot.getBlockZ() - origin2.getBlockZ();
Vector pos = new Vector(x, y, z);
// Store
bpEntities.put(pos, entities); // Update the map
}
}
}
return bpEntities;
}
/**
* Spawn a Citizen
* @param serializedData serialized data
* @param location location
* @return true if spawn is successful
*/
public boolean spawnCitizen(EntityType type, String serializedData, Location location) {
BentoBox.getInstance().logDebug("spawn Citizen at " + location + " with this data " + serializedData);
if (serializedData == null || location == null) {
throw new IllegalArgumentException("Serialized data and location cannot be null.");
}
// Load the serialized data into a YamlConfiguration
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(new StringReader(serializedData));
// Create a new MemoryDataKey from the loaded data
MemoryDataKey dataKey = new MemoryDataKey();
for (String key : yaml.getKeys(true)) {
BentoBox.getInstance().logDebug("data key " + key + " = " + yaml.get(key));
if (key.equalsIgnoreCase("metadata") || key.equalsIgnoreCase("traits")
|| key.equalsIgnoreCase("traits.owner") || key.equalsIgnoreCase("traits.location")
|| key.equalsIgnoreCase("navigator")) {
continue;
}
dataKey.setRaw(key, yaml.get(key));
}
// Get the NPC details from the serialized data
String name = dataKey.getString("name");
//String type = dataKey.getString("traits.type");
BentoBox.getInstance().logDebug("Entity type = " + type + " name = " + name);
if (type == null) {
// No luck
return false;
}
// Create a new NPC and load the data
BentoBox.getInstance().logDebug("Create a new NPC and load the data");
NPCRegistry registry = CitizensAPI.getNPCRegistry();
try {
NPC npc = registry.createNPC(type, name);
npc.load(dataKey); // Load the serialized data into the NPC
for (Entry<String, Object> en : dataKey.getValuesDeep().entrySet()) {
BentoBox.getInstance().logDebug("loaded key " + en.getKey() + " = " + en.getValue());
}
boolean r = npc.spawn(location); // Spawn the NPC at the specified location
BentoBox.getInstance().logDebug("Spawn = " + r);
if (!r) {
npc.load(dataKeyTest);
BentoBox.getInstance().logDebug(npc.spawn(location)); // Spawn the NPC at the specified location
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean hook() {
boolean hooked = this.isPluginAvailable();
if (!hooked) {
BentoBox.getInstance().logError("Could not hook into Citizens");
}
return hooked; // The hook process shouldn't fail
}
@Override
public String getFailureCause() {
return null; // The hook process shouldn't fail
}
}

View File

@ -33,6 +33,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.hooks.CitizensHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
import world.bentobox.bentobox.nms.PasteHandler;
@ -169,8 +170,11 @@ public class DefaultPasteUtil {
* @param island - island
* @param location - location
* @param list - blueprint entities
* @return future boolean - true if Bukkit entity spawned, false another plugin entity spawned
*/
public static CompletableFuture<Void> setEntity(Island island, Location location, List<BlueprintEntity> list) {
BentoBox.getInstance().logDebug("List of entities to paste at " + location);
list.forEach(bpe -> BentoBox.getInstance().logDebug(bpe));
World world = location.getWorld();
assert world != null;
return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null)
@ -182,9 +186,20 @@ public class DefaultPasteUtil {
* @param k the blueprint entity definition
* @param location location
* @param island island
* @return true if Bukkit entity spawned, false if MythicMob entity spawned
* @return true if Bukkit entity spawned, false another plugin entity spawned
*/
static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) {
BentoBox.getInstance().logDebug("pasting entity " + k.getType() + " at " + location);
// Citizens entity
if (k.getCitizen() != null && plugin.getHooks().getHook("Citizens").filter(mmh -> mmh instanceof CitizensHook)
.map(mmh -> ((CitizensHook) mmh).spawnCitizen(k.getType(), k.getCitizen(), location)).orElse(false)) {
BentoBox.getInstance().logDebug("Citizen spawning done");
// Citizen has spawned.
return false;
} else {
BentoBox.getInstance().logDebug("Citizen spawning failed");
}
// Mythic Mobs entity
if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs")
.filter(mmh -> mmh instanceof MythicMobsHook)
.map(mmh -> ((MythicMobsHook) mmh).spawnMythicMob(k.getMythicMobsRecord(), location))