mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-19 15:09:15 +00:00
优化验证速度
This commit is contained in:
@@ -8,8 +8,8 @@ import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
|
||||
@@ -11,8 +11,8 @@ import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
|
||||
@@ -8,8 +8,8 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
|
||||
@@ -11,10 +11,10 @@ import net.momirealms.craftengine.core.entity.projectile.ProjectileMeta;
|
||||
import net.momirealms.craftengine.core.item.CustomItem;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.item.ItemBuildContext;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.NMSPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.ByteBufPacketEvent;
|
||||
import net.momirealms.craftengine.core.plugin.network.event.NMSPacketEvent;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
|
||||
@@ -94,12 +94,13 @@ resource-pack:
|
||||
resolution:
|
||||
type: merge_font
|
||||
# Validate if there is any error in the resource pack, such as missing textures or models.
|
||||
# Validation may not always be accurate due to the presence of overlays, and it is time-consuming for plugins to simulate multiple client versions for testing.
|
||||
# Validation may not always be accurate due to the presence of resource pack overlays.
|
||||
# If your resource pack is compliant with the standard, you can disable validation to improve the resource pack generation speed.
|
||||
validation:
|
||||
enable: true
|
||||
# Fix images that are not within the texture atlas. It is unreasonable to always rely on plugins to fix your mistakes.
|
||||
# Fix textures that are not within the atlas. It is unreasonable to always rely on plugins to fix your mistakes.
|
||||
# You should strive to make your resource pack more standardized after gaining some experience with resource packs.
|
||||
# When a model file mixes textures from both the blocks atlas and the items atlas, you must manually fix the issue.
|
||||
fix-atlas: true
|
||||
# Optimize your resource pack by reducing its size without any quality loss.
|
||||
optimization:
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package net.momirealms.craftengine.core.pack;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.jimfs.Configuration;
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
@@ -1186,12 +1184,12 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
return;
|
||||
}
|
||||
|
||||
Multimap<Key, Key> glyphToFonts = ArrayListMultimap.create(); // 图片到字体的映射
|
||||
Multimap<Key, Key> modelToItemDefinitions = ArrayListMultimap.create(); // 模型到物品的映射
|
||||
Multimap<Key, String> modelToBlockStates = ArrayListMultimap.create(); // 模型到方块的映射
|
||||
Multimap<Key, Key> textureToModels = ArrayListMultimap.create(); // 纹理到模型的映射
|
||||
Multimap<Key, Key> textureToEquipments = ArrayListMultimap.create(); // 纹理到盔甲的映射
|
||||
Multimap<Key, Key> oggToSoundEvents = ArrayListMultimap.create(); // 音频到声音的映射
|
||||
Multimap<Key, Key> glyphToFonts = HashMultimap.create(128, 32); // 图片到字体的映射
|
||||
Multimap<Key, Key> modelToItemDefinitions = HashMultimap.create(128, 4); // 模型到物品的映射
|
||||
Multimap<Key, String> modelToBlockStates = HashMultimap.create(128, 32); // 模型到方块的映射
|
||||
Multimap<Key, Key> textureToModels = HashMultimap.create(128, 8); // 纹理到模型的映射
|
||||
Multimap<Key, Key> textureToEquipments = HashMultimap.create(128, 8); // 纹理到盔甲的映射
|
||||
Multimap<Key, Key> oggToSoundEvents = HashMultimap.create(128, 4); // 音频到声音的映射
|
||||
|
||||
Map<Path, JsonObject> blockAtlasJsons = new LinkedHashMap<>();
|
||||
Map<Path, JsonObject> itemAtlasJsons = new LinkedHashMap<>();
|
||||
@@ -1239,16 +1237,17 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
/*
|
||||
|
||||
|
||||
构建Atlas文件
|
||||
构建Atlas文件,
|
||||
验证只使用默认的atlas,否则整个过程将会变成非常复杂
|
||||
|
||||
|
||||
*/
|
||||
Atlas blockAtlas = new Atlas(MiscUtils.make(new ArrayList<>(), k -> {
|
||||
Atlas blockAtlas = new Atlas(MiscUtils.make(new ArrayList<>(4), k -> {
|
||||
k.add(blockAtlasJsons.get(defaultBlockAtlas));
|
||||
k.add(this.vanillaBlockAtlas);
|
||||
return k;
|
||||
}));
|
||||
Atlas itemAtlas = new Atlas(MiscUtils.make(new ArrayList<>(), k -> {
|
||||
Atlas itemAtlas = new Atlas(MiscUtils.make(new ArrayList<>(4), k -> {
|
||||
k.add(itemAtlasJsons.get(defaultItemAtlas));
|
||||
k.add(this.vanillaItemAtlas);
|
||||
return k;
|
||||
@@ -1289,14 +1288,16 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
if (providers != null) {
|
||||
Key fontName = Key.of(namespace, FileUtils.pathWithoutExtension(file.getFileName().toString()));
|
||||
for (JsonElement provider : providers) {
|
||||
if (provider instanceof JsonObject providerJO && providerJO.has("type")) {
|
||||
String type = providerJO.get("type").getAsString();
|
||||
if (type.equals("bitmap") && providerJO.has("file")) {
|
||||
String pngFile = providerJO.get("file").getAsString();
|
||||
Key resourceLocation = Key.of(FileUtils.pathWithoutExtension(pngFile));
|
||||
glyphToFonts.put(resourceLocation, fontName);
|
||||
}
|
||||
}
|
||||
if (!(provider instanceof JsonObject providerJO)) continue;
|
||||
JsonPrimitive typePrimitive = providerJO.getAsJsonPrimitive("type");
|
||||
if (typePrimitive == null) continue;
|
||||
String type = typePrimitive.getAsString();
|
||||
if (!type.equals("bitmap")) continue;
|
||||
JsonPrimitive filePrimitive = providerJO.getAsJsonPrimitive("file");
|
||||
if (filePrimitive == null) continue;
|
||||
String pngFile = filePrimitive.getAsString();
|
||||
Key resourceLocation = Key.of(FileUtils.pathWithoutExtension(pngFile));
|
||||
glyphToFonts.put(resourceLocation, fontName);
|
||||
}
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
@@ -1383,9 +1384,11 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
if (layer.getValue() instanceof JsonArray equipmentLayer) {
|
||||
for (JsonElement lay : equipmentLayer) {
|
||||
if (lay instanceof JsonObject layObj) {
|
||||
Key rawTexture = Key.of(layObj.get("texture").getAsString());
|
||||
Key fullPath = Key.of(rawTexture.namespace(), "entity/equipment/" + type + "/" + rawTexture.value());
|
||||
textureToEquipments.put(fullPath, Key.of(namespace, equipmentId));
|
||||
if (layObj.get("texture") instanceof JsonPrimitive layerTexture) {
|
||||
Key rawTexture = Key.of(layerTexture.getAsString());
|
||||
Key fullPath = Key.of(rawTexture.namespace(), "entity/equipment/" + type + "/" + rawTexture.value());
|
||||
textureToEquipments.put(fullPath, Key.of(namespace, equipmentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1411,9 +1414,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
if (soundArray != null) {
|
||||
for (JsonElement sound : soundArray) {
|
||||
if (sound instanceof JsonPrimitive primitive) {
|
||||
if (primitive.isString()) {
|
||||
oggToSoundEvents.put(Key.of(primitive.getAsString()), soundKey);
|
||||
}
|
||||
oggToSoundEvents.put(Key.of(primitive.getAsString()), soundKey);
|
||||
} else if (sound instanceof JsonObject soundObj && soundObj.has("name")) {
|
||||
if (soundObj.has("type")) {
|
||||
String type = soundObj.get("type").getAsString();
|
||||
@@ -1481,12 +1482,12 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
*/
|
||||
|
||||
// 获取所有带贴图的模型以及自定义父模型
|
||||
Map<Key, TexturedModel> blockModels = new LinkedHashMap<>();
|
||||
Map<Key, TexturedModel> itemModels = new LinkedHashMap<>();
|
||||
Map<Key, TexturedModel> blockModels = new HashMap<>(256);
|
||||
Map<Key, TexturedModel> itemModels = new HashMap<>(256);
|
||||
// 此map仅用于缓存遇到过的路径上的模型
|
||||
Map<Key, TexturedModel> blockModelsCache = new LinkedHashMap<>();
|
||||
Map<Key, TexturedModel> itemModelsCache = new LinkedHashMap<>();
|
||||
Set<Key> checkedModels = new HashSet<>();
|
||||
Map<Key, TexturedModel> blockModelsCache = new HashMap<>(256);
|
||||
Map<Key, TexturedModel> itemModelsCache = new HashMap<>(256);
|
||||
Set<Key> checkedModels = new HashSet<>(256);
|
||||
|
||||
// 收集全部方块状态的模型贴图
|
||||
label: for (Map.Entry<Key, Collection<String>> entry : modelToBlockStates.asMap().entrySet()) {
|
||||
@@ -1510,7 +1511,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
// 提示方块状态缺少模型
|
||||
if (!VANILLA_MODELS.contains(modelPath)) {
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_block_model", entry.getValue().stream().distinct().toList().toString(), modelStringPath);
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_block_model", entry.getValue().toString(), modelStringPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1535,7 +1536,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
}
|
||||
if (!VANILLA_MODELS.contains(modelPath)) {
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_item_model", entry.getValue().stream().distinct().toList().toString(), modelStringPath);
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_item_model", entry.getValue().toString(), modelStringPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,9 +1548,9 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
|
||||
*/
|
||||
|
||||
Multimap<Key, Key> blockAtlasesToFix = LinkedHashMultimap.create();
|
||||
Multimap<Key, Key> itemAtlasesToFix = LinkedHashMultimap.create();
|
||||
Multimap<Key, Key> anyAtlasesToFix = LinkedHashMultimap.create();
|
||||
Multimap<Key, Key> blockAtlasesToFix = HashMultimap.create(64, 4);
|
||||
Multimap<Key, Key> itemAtlasesToFix = HashMultimap.create(64, 4);
|
||||
Multimap<Key, Key> anyAtlasesToFix = HashMultimap.create(64, 4);
|
||||
|
||||
// 验证方块贴图是否在图集里
|
||||
Iterator<Map.Entry<Key, TexturedModel>> iterator1 = blockModels.entrySet().iterator();
|
||||
@@ -1560,19 +1561,9 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
boolean shouldRemove = false;
|
||||
for (Map.Entry<String, Key> texture : textures.entrySet()) {
|
||||
Key spritePath = texture.getValue();
|
||||
boolean definedInBlockAtlas = blockAtlas.isDefined(spritePath);
|
||||
boolean definedInItemAtlas = itemAtlas.isDefined(spritePath);
|
||||
|
||||
// 双重定义
|
||||
if (definedInItemAtlas && definedInBlockAtlas) {
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.duplicated_sprite",
|
||||
entry.getKey().asString(), spritePath.asString(),
|
||||
"minecraft:textures/atlas/blocks.png", "minecraft:textures/atlas/items.png");
|
||||
shouldRemove = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 方块纹理不应该在item图集内,这样必然出问题
|
||||
boolean definedInItemAtlas = itemAtlas.isDefined(spritePath);
|
||||
if (definedInItemAtlas) {
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.multiple_atlases",
|
||||
entry.getKey().asString(), "minecraft:textures/atlas/blocks.png",
|
||||
@@ -1582,7 +1573,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
|
||||
// 未在方块图集内定义
|
||||
if (!definedInBlockAtlas) {
|
||||
if (!blockAtlas.isDefined(spritePath)) {
|
||||
// 如果尝试修复
|
||||
if (Config.fixTextureAtlas()) {
|
||||
// 只能在方块图集
|
||||
@@ -1649,14 +1640,13 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
textureToModels.put(spritePath, entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 那么就至少有一个定义
|
||||
if (definedInBlockAtlas) {
|
||||
blockAtlasInUse = true;
|
||||
}
|
||||
if (definedInItemAtlas) {
|
||||
itemAtlasInUse = true;
|
||||
} else {
|
||||
// 那么就至少有一个定义
|
||||
if (definedInBlockAtlas) {
|
||||
blockAtlasInUse = true;
|
||||
} else /* if (definedInItemAtlas) */ {
|
||||
itemAtlasInUse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1745,7 +1735,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
|
||||
if (!itemAtlasesToFix.isEmpty()) {
|
||||
List<JsonObject> sourcesToAdd = new ArrayList<>();
|
||||
List<JsonObject> sourcesToAdd = new ArrayList<>(itemAtlasesToFix.size());
|
||||
for (Key itemTexture : itemAtlasesToFix.keySet()) {
|
||||
itemAtlas.addSingle(itemTexture);
|
||||
JsonObject source = new JsonObject();
|
||||
@@ -1773,7 +1763,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
|
||||
if (!blockAtlasesToFix.isEmpty()) {
|
||||
List<JsonObject> sourcesToAdd = new ArrayList<>();
|
||||
List<JsonObject> sourcesToAdd = new ArrayList<>(blockAtlasesToFix.size());
|
||||
for (Key blockTexture : blockAtlasesToFix.keySet()) {
|
||||
blockAtlas.addSingle(blockTexture);
|
||||
JsonObject source = new JsonObject();
|
||||
@@ -1845,11 +1835,16 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo 验证 unstitch 和 paletted permutations
|
||||
}
|
||||
|
||||
// 经过这一步拿到的模型为包含全部父贴图的模型
|
||||
@SuppressWarnings("all")
|
||||
public TexturedModel getTexturedModel(Key path, JsonObject modelJson, Path[] rootPaths, Map<Key, TexturedModel> models) {
|
||||
TexturedModel texturedModel = new TexturedModel(modelJson);
|
||||
// 放这里防止parent互相引用造成死循环
|
||||
models.put(path, texturedModel);
|
||||
if (modelJson.has("parent")) {
|
||||
Key parentModelPath = Key.from(modelJson.get("parent").getAsString());
|
||||
TexturedModel parent = models.get(parentModelPath);
|
||||
@@ -1863,17 +1858,25 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
} else {
|
||||
// 否则只从缓存里拿一份用于归并
|
||||
if (VANILLA_MODELS.contains(parentModelPath)) {
|
||||
// 可能为空,因为存在built-in模型
|
||||
parent = PRESET_MODELS.get(parentModelPath);
|
||||
if (parent == null) {
|
||||
parent = TexturedModel.BUILTIN;
|
||||
}
|
||||
models.put(parentModelPath, parent);
|
||||
} else {
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_parent_model", path.asString(), parentModelStringPath);
|
||||
parent = TexturedModel.EMPTY;
|
||||
models.put(parentModelPath, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent != null) {
|
||||
if (parent == TexturedModel.EMPTY) {
|
||||
String parentModelStringPath = "assets/" + parentModelPath.namespace() + "/models/" + parentModelPath.value() + ".json";
|
||||
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_parent_model", path.asString(), parentModelStringPath);
|
||||
} else {
|
||||
texturedModel.addParent(parent);
|
||||
}
|
||||
}
|
||||
models.put(path, texturedModel);
|
||||
return texturedModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
public final class Atlas {
|
||||
private final Map<String, String> directory;
|
||||
private final String[] prefixes;
|
||||
// 已经被包含在图集内的贴图
|
||||
private final Set<Key> defined;
|
||||
// 单独添加的
|
||||
@@ -109,6 +110,7 @@ public final class Atlas {
|
||||
}
|
||||
}
|
||||
this.filtered = MiscUtils.anyOf(filtered);
|
||||
this.prefixes = this.directory.keySet().toArray(new String[0]);
|
||||
}
|
||||
|
||||
public Atlas(List<JsonObject> atlasJsons) {
|
||||
@@ -135,10 +137,8 @@ public final class Atlas {
|
||||
if (this.filtered.test(texture)) return false;
|
||||
if (this.defined.contains(texture)) return true;
|
||||
String path = texture.value();
|
||||
for (Map.Entry<String, String> entry : this.directory.entrySet()) {
|
||||
if (path.startsWith(entry.getKey())) {
|
||||
return true;
|
||||
}
|
||||
for (String prefix : this.prefixes) {
|
||||
if (path.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -156,8 +156,9 @@ public final class Atlas {
|
||||
String path = texture.value();
|
||||
// 路径匹配
|
||||
for (Map.Entry<String, String> entry : this.directory.entrySet()) {
|
||||
if (path.startsWith(entry.getKey())) {
|
||||
return Key.of(texture.namespace(), entry.getValue() + path.substring(entry.getKey().length()));
|
||||
String prefix = entry.getKey();
|
||||
if (path.startsWith(prefix)) {
|
||||
return Key.of(texture.namespace(), entry.getValue() + path.substring(prefix.length()));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.Map;
|
||||
|
||||
public class TexturedModel {
|
||||
public static final TexturedModel EMPTY = new TexturedModel(Map.of());
|
||||
public static final TexturedModel BUILTIN = new TexturedModel(Map.of());
|
||||
public final Map<String, Key> textures;
|
||||
|
||||
private TexturedModel(Map<String, Key> textures) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package net.momirealms.craftengine.core.pack.conflict.resolution;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import net.momirealms.craftengine.core.pack.conflict.PathContext;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
|
||||
Reference in New Issue
Block a user