mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2025-01-02 22:47:43 +01:00
Citizen's hook WIP
This commit is contained in:
parent
55daa72e55
commit
9ab9d7f11b
21
pom.xml
21
pom.xml
@ -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>
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {}
|
||||
|
||||
/**
|
||||
|
@ -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 : "") + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
165
src/main/java/world/bentobox/bentobox/hooks/CitizensHook.java
Normal file
165
src/main/java/world/bentobox/bentobox/hooks/CitizensHook.java
Normal 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
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user