9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-24 17:39:30 +00:00

refactor images

This commit is contained in:
XiaoMoMi
2025-04-04 00:45:44 +08:00
parent 419d3a262b
commit 6cadf524e4
26 changed files with 397 additions and 281 deletions

View File

@@ -53,4 +53,14 @@ command.search_recipe.not_found: "<red>No recipe found for this item</red>"
command.search_usage.not_found: "<red>No usage found for this item</red>"
command.search_recipe.no_item: "<red>Please hold an item before running this command</red>"
command.search_usage.no_item: "<red>Please hold an item before running this command</red>"
command.totem.not_totem: "<red>'<arg:0>' is not type of totem_of_undying</red>"
command.totem.not_totem: "<red>'<arg:0>' is not type of totem_of_undying</red>"
warning.config.image.lack_height: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'height' argument.</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' violates the bitmap image rule: 'height' should be no lower than 'ascent'.</yellow>"
warning.config.image.no_file: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'file' argument.</yellow>"
warning.config.image.invalid_resource_location: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' has a 'file' argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.image.invalid_font_name: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' has a 'font' argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.image.lack_char: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'char' argument.</yellow>"
warning.config.image.codepoint_in_use: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is using a character[<arg:3>(<arg:4>)] in font <arg:2> that has been used by another image '<arg:5>'.</yellow>"
warning.config.image.invalid_codepoint_grid: "<yellow>Issue found in file <arg:0> - Image '<arg:1>' has an invalid 'chars' codepoint grind.</yellow>"
warning.config.image.file_not_exist: "<yellow>Issue found in file <arg:0> - PNG file <arg:2> not found for image '<arg:1>'.</yellow>"

View File

@@ -6,7 +6,6 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
@@ -14,7 +13,6 @@ import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.Vec3d;

View File

@@ -8,7 +8,6 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.PushReaction;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
@@ -29,7 +28,10 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.world.GenericGameEvent;
import org.bukkit.inventory.ItemStack;

View File

@@ -13,14 +13,13 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.loot.number.NumberProvider;
import net.momirealms.craftengine.core.loot.number.NumberProviders;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.loot.number.NumberProvider;
import net.momirealms.craftengine.core.loot.number.NumberProviders;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.util.context.ContextKey;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.Vec3i;
import net.momirealms.craftengine.shared.block.BlockBehavior;
@@ -29,7 +28,6 @@ import org.bukkit.World;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;

View File

@@ -4,8 +4,8 @@ import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.font.AbstractImageManager;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.AbstractFontManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.util.CharacterUtils;
import org.bukkit.Bukkit;
@@ -19,11 +19,11 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Consumer;
public class BukkitImageManager extends AbstractImageManager implements Listener {
public class BukkitFontManager extends AbstractFontManager implements Listener {
private final BukkitCraftEngine plugin;
private final Object serializer;
public BukkitImageManager(BukkitCraftEngine plugin) {
public BukkitFontManager(BukkitCraftEngine plugin) {
super(plugin);
this.plugin = plugin;
try {
@@ -63,7 +63,7 @@ public class BukkitImageManager extends AbstractImageManager implements Listener
public void onCommand(PlayerCommandPreprocessEvent event) {
if (!ConfigManager.filterCommand()) return;
if (!this.isDefaultFontInUse()) return;
if (event.getPlayer().hasPermission(ImageManager.BYPASS_COMMAND)) {
if (event.getPlayer().hasPermission(FontManager.BYPASS_COMMAND)) {
return;
}
runIfContainsIllegalCharacter(event.getMessage(), event::setMessage);
@@ -74,7 +74,7 @@ public class BukkitImageManager extends AbstractImageManager implements Listener
Player player = event.player();
if (player == null) return;
if (!this.isDefaultFontInUse()) return;
if (player.hasPermission(ImageManager.BYPASS_CHAT)) {
if (player.hasPermission(FontManager.BYPASS_CHAT)) {
return;
}
try {
@@ -101,7 +101,7 @@ public class BukkitImageManager extends AbstractImageManager implements Listener
boolean hasIllegal = false;
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (!isIllegalCharacter(codepoint)) {
if (!isIllegalCodepoint(codepoint)) {
newCodepoints[i] = codepoint;
} else {
newCodepoints[i] = '*';

View File

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitHitBoxTypes;
import net.momirealms.craftengine.bukkit.font.BukkitImageManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.behavior.BukkitItemBehaviors;
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
@@ -154,7 +154,7 @@ public class BukkitCraftEngine extends CraftEngine {
super.worldManager = new BukkitWorldManager(this);
super.soundManager = new BukkitSoundManager(this);
super.vanillaLootManager = new BukkitVanillaLootManager(this);
this.imageManager = new BukkitImageManager(this);
this.fontManager = new BukkitFontManager(this);
super.enable();
// tick task
if (VersionHelper.isFolia()) {
@@ -216,7 +216,7 @@ public class BukkitCraftEngine extends CraftEngine {
// register template parser
this.packManager.registerConfigSectionParser(this.templateManager);
// register font parser
this.packManager.registerConfigSectionParser(this.imageManager);
this.packManager.registerConfigSectionParsers(this.fontManager.parsers());
// register item parser
this.packManager.registerConfigSectionParser(this.itemManager);
// register furniture parser

View File

@@ -7,6 +7,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
import net.momirealms.craftengine.core.util.Tristate;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
@@ -79,6 +80,12 @@ public class BukkitSenderFactory extends SenderFactory<BukkitCraftEngine, Comman
return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender;
}
@SuppressWarnings("unchecked")
@Override
protected <C extends CommandSender> C consoleCommandSender() {
return (C) Bukkit.getConsoleSender();
}
@Override
public void close() {
super.close();

View File

@@ -18,7 +18,7 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
@@ -777,10 +777,10 @@ public class PacketConsumers {
if (!ConfigManager.filterAnvil()) return;
String message = (String) Reflections.field$ServerboundRenameItemPacket$name.get(packet);
if (message != null && !message.isEmpty()) {
ImageManager manager = CraftEngine.instance().imageManager();
FontManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
// check bypass
if (((BukkitServerPlayer) user).hasPermission(ImageManager.BYPASS_ANVIL)) {
if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_ANVIL)) {
return;
}
runIfContainsIllegalCharacter(message, manager, (s) -> {
@@ -801,10 +801,10 @@ public class PacketConsumers {
try {
if (!ConfigManager.filterSign()) return;
String[] lines = (String[]) Reflections.field$ServerboundSignUpdatePacket$lines.get(packet);
ImageManager manager = CraftEngine.instance().imageManager();
FontManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
// check bypass
if (((BukkitServerPlayer) user).hasPermission(ImageManager.BYPASS_SIGN)) {
if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_SIGN)) {
return;
}
for (int i = 0; i < lines.length; i++) {
@@ -827,10 +827,10 @@ public class PacketConsumers {
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> EDIT_BOOK = (user, event, packet) -> {
try {
if (!ConfigManager.filterBook()) return;
ImageManager manager = CraftEngine.instance().imageManager();
FontManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
// check bypass
if (((BukkitServerPlayer) user).hasPermission(ImageManager.BYPASS_BOOK)) {
if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_BOOK)) {
return;
}
@@ -879,7 +879,7 @@ public class PacketConsumers {
}
};
private static Pair<Boolean, String> processClientString(String original, ImageManager manager) {
private static Pair<Boolean, String> processClientString(String original, FontManager manager) {
if (original.isEmpty()) {
return Pair.of(false, original);
}
@@ -888,7 +888,7 @@ public class PacketConsumers {
boolean hasIllegal = false;
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (manager.isIllegalCharacter(codepoint)) {
if (manager.isIllegalCodepoint(codepoint)) {
newCodepoints[i] = '*';
hasIllegal = true;
} else {
@@ -898,14 +898,14 @@ public class PacketConsumers {
return hasIllegal ? Pair.of(true, new String(newCodepoints, 0, newCodepoints.length)) : Pair.of(false, original);
}
private static void runIfContainsIllegalCharacter(String string, ImageManager manager, Consumer<String> callback) {
private static void runIfContainsIllegalCharacter(String string, FontManager manager, Consumer<String> callback) {
if (string.isEmpty()) return;
int[] codepoints = CharacterUtils.charsToCodePoints(string.toCharArray());
int[] newCodepoints = new int[codepoints.length];
boolean hasIllegal = false;
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (!manager.isIllegalCharacter(codepoint)) {
if (!manager.isIllegalCodepoint(codepoint)) {
newCodepoints[i] = codepoint;
} else {
newCodepoints[i] = '*';

View File

@@ -0,0 +1,243 @@
package net.momirealms.craftengine.core.font;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public abstract class AbstractFontManager implements FontManager {
private final CraftEngine plugin;
// namespace:font font
private final Map<Key, Font> fonts = new HashMap<>();
// namespace:id image
private final Map<Key, BitmapImage> images = new HashMap<>();
private final Set<Integer> illegalChars = new HashSet<>();
private final ImageParser imageParser;
private final EmojiParser emojiParser;
private OffsetFont offsetFont;
public AbstractFontManager(CraftEngine plugin) {
this.plugin = plugin;
this.imageParser = new ImageParser();
this.emojiParser = new EmojiParser();
}
@Override
public void load() {
this.offsetFont = Optional.ofNullable(plugin.configManager().settings().getSection("offset-characters"))
.map(OffsetFont::new)
.orElse(null);
}
@Override
public void unload() {
this.fonts.clear();
this.images.clear();
this.illegalChars.clear();
}
@Override
public ConfigSectionParser[] parsers() {
return new ConfigSectionParser[] {this.imageParser, this.emojiParser};
}
@Override
public void delayedLoad() {
Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> this.illegalChars.addAll(font.codepointsInUse()));
}
@Override
public boolean isDefaultFontInUse() {
return !this.illegalChars.isEmpty();
}
@Override
public boolean isIllegalCodepoint(int codepoint) {
return this.illegalChars.contains(codepoint);
}
@Override
public Collection<Font> fonts() {
return new ArrayList<>(this.fonts.values());
}
@Override
public Optional<BitmapImage> bitmapImageByCodepoint(Key font, int codepoint) {
return fontById(font).map(f -> f.bitmapImageByCodepoint(codepoint));
}
@Override
public Optional<BitmapImage> bitmapImageByImageId(Key id) {
return Optional.ofNullable(this.images.get(id));
}
@Override
public int codepointByImageId(Key key, int x, int y) {
BitmapImage image = this.images.get(key);
if (image == null) return -1;
return image.codepointAt(x, y);
}
@Override
public String createOffsets(int offset, FontTagFormatter tagFormatter) {
return Optional.ofNullable(this.offsetFont).map(it -> it.createOffset(offset, tagFormatter)).orElse("");
}
@Override
public Optional<Font> fontById(Key id) {
return Optional.ofNullable(this.fonts.get(id));
}
private Font getOrCreateFont(Key key) {
return this.fonts.computeIfAbsent(key, Font::new);
}
public class EmojiParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"emoji", "emojis"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.EMOJI;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
}
}
public class ImageParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"images", "image"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.IMAGE;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
Object heightObj = section.get("height");
if (heightObj == null) {
TranslationManager.instance().log("warning.config.image.lack_height", path.toString(), id.toString());
return;
}
int height = MiscUtils.getAsInt(heightObj);
int ascent = MiscUtils.getAsInt(section.getOrDefault("ascent", height - 1));
if (height < ascent) {
TranslationManager.instance().log("warning.config.image.height_smaller_than_ascent", path.toString(), id.toString());
return;
}
Object file = section.get("file");
if (file == null) {
TranslationManager.instance().log("warning.config.image.no_file", path.toString(), id.toString());
return;
}
String resourceLocation = file.toString().replace("\\", "/");
if (!ResourceLocation.isValid(resourceLocation)) {
TranslationManager.instance().log("warning.config.image.invalid_resource_location", path.toString(), id.toString(), resourceLocation);
return;
}
String fontName = (String) section.getOrDefault("font", "minecraft:default");
if (!ResourceLocation.isValid(fontName)) {
TranslationManager.instance().log("warning.config.image.invalid_font_name", path.toString(), id.toString(), fontName);
return;
}
Key fontKey = Key.withDefaultNamespace(fontName, id.namespace());
Font font = getOrCreateFont(fontKey);
List<char[]> chars;
if (section.containsKey("chars")) {
chars = MiscUtils.getAsStringList(section.get("chars")).stream().map(it -> {
if (it.startsWith("\\u")) {
return CharacterUtils.decodeUnicodeToChars(it);
} else {
return it.toCharArray();
}
}).toList();
} else {
String character = (String) section.get("char");
if (character == null) {
TranslationManager.instance().log("warning.config.image.lack_char", path.toString(), id.toString());
return;
}
if (character.length() == 1) {
chars = List.of(character.toCharArray());
} else {
chars = List.of(CharacterUtils.decodeUnicodeToChars(character));
}
}
int size = -1;
int[][] codepointGrid = new int[chars.size()][];
for (int i = 0; i < chars.size(); ++i) {
int[] codepoints = CharacterUtils.charsToCodePoints(chars.get(i));
for (int codepoint : codepoints) {
if (font.isCodepointInUse(codepoint)) {
BitmapImage image = font.bitmapImageByCodepoint(codepoint);
TranslationManager.instance().log("warning.config.image.codepoint_in_use",
path.toString(),
id.toString(),
fontKey.toString(),
CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)),
new String(Character.toChars(codepoint)),
image.id().toString()
);
return;
}
}
codepointGrid[i] = codepoints;
if (size == -1) size = codepoints.length;
if (size != codepoints.length) {
TranslationManager.instance().log("warning.config.image.invalid_codepoint_grid", path.toString(), id.toString());
return;
}
}
if (!resourceLocation.endsWith(".png")) resourceLocation += ".png";
Key namespacedPath = Key.of(resourceLocation);
Path targetImagePath = pack.resourcePackFolder()
.resolve("assets")
.resolve(namespacedPath.namespace())
.resolve("textures")
.resolve(namespacedPath.value());
if (!Files.exists(targetImagePath)) {
TranslationManager.instance().log("warning.config.image.file_not_exist", path.toString(), id.toString(), targetImagePath.toString());
// DO NOT RETURN, JUST GIVE WARNINGS
}
BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, resourceLocation, codepointGrid);
for (int[] y : codepointGrid) {
for (int x : y) {
font.addBitMapImage(x, bitmapImage);
}
}
AbstractFontManager.this.images.put(id, bitmapImage);
}
}
}

View File

@@ -1,172 +0,0 @@
package net.momirealms.craftengine.core.font;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.PreConditions;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.BiFunction;
public abstract class AbstractImageManager implements ImageManager {
private final CraftEngine plugin;
// namespace:font font
private final HashMap<Key, Font> fonts = new HashMap<>();
// namespace:id image
private final HashMap<Key, BitmapImage> images = new HashMap<>();
private final Set<Integer> illegalChars = new HashSet<>();
private OffsetFont offsetFont;
public AbstractImageManager(CraftEngine plugin) {
this.plugin = plugin;
}
@Override
public void load() {
this.offsetFont = Optional.ofNullable(plugin.configManager().settings().getSection("offset-characters"))
.map(OffsetFont::new)
.orElse(null);
}
@Override
public void unload() {
this.fonts.clear();
this.images.clear();
this.illegalChars.clear();
}
@Override
public void delayedLoad() {
Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> {
this.illegalChars.addAll(font.codepointsInUse());
});
}
@Override
public boolean isDefaultFontInUse() {
return !this.illegalChars.isEmpty();
}
@Override
public boolean isIllegalCharacter(int codepoint) {
return this.illegalChars.contains(codepoint);
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
int height = MiscUtils.getAsInt(section.get("height"));
int ascent = MiscUtils.getAsInt(section.get("ascent"));
if (PreConditions.runIfTrue(height < ascent,
() -> this.plugin.logger().warn(path, "Illegal ascent found at " + id + ". Height should be no lower than ascent"))) return;
String file = (String) section.get("file");
if (PreConditions.isNull(file,
() -> this.plugin.logger().warn(path, "`file` option is not set in image " + id))) return;
String fontName = (String) section.getOrDefault("font", "minecraft:default");
if (PreConditions.isNull(fontName,
() -> this.plugin.logger().warn(path, "`font` option is not set in image " + id))) return;
Key fontKey = Key.withDefaultNamespace(fontName, id.namespace());
// get the font
Font font = this.getOrCreateFont(fontKey);
List<char[]> chars;
if (section.containsKey("chars")) {
chars = MiscUtils.getAsStringList(section.get("chars")).stream().map(it -> {
if (it.startsWith("\\u")) {
return CharacterUtils.decodeUnicodeToChars(it);
} else {
return it.toCharArray();
}
}).toList();
} else {
String character = (String) section.get("char");
if (PreConditions.isNull(character,
() -> this.plugin.logger().warn(path, "`char` option is not set in image " + id))) return;
if (character.length() == 1) {
chars = List.of(character.toCharArray());
} else {
chars = List.of(CharacterUtils.decodeUnicodeToChars(character));
}
}
int size = -1;
int[][] codepointGrid = new int[chars.size()][];
for (int i = 0; i < chars.size(); ++i) {
int[] codepoints = CharacterUtils.charsToCodePoints(chars.get(i));
for (int codepoint : codepoints) {
if (PreConditions.runIfTrue(font.isCodepointInUse(codepoint),
() -> this.plugin.logger().warn(path, String.format("Codepoint [%s (%s)] is already used in font [%s]", CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)), new String(Character.toChars(codepoint)), font.key().toString())))) return;
}
codepointGrid[i] = codepoints;
if (size == -1) size = codepoints.length;
if (PreConditions.runIfTrue(size != codepoints.length,
() -> this.plugin.logger().warn(path, "Illegal chars format found at " + id))) return;
}
if (PreConditions.runIfTrue(size == -1,
() -> this.plugin.logger().warn(path, "Illegal chars format found at " + id))) return;
if (!file.endsWith(".png")) file += ".png";
file = file.replace("\\", "/");
Key namespacedPath = Key.of(file);
Path targetImageFile = pack.resourcePackFolder()
.resolve("assets")
.resolve(namespacedPath.namespace())
.resolve("textures")
.resolve(namespacedPath.value());
if (PreConditions.runIfTrue(!Files.exists(targetImageFile),
() -> this.plugin.logger().warn(targetImageFile, "PNG file not found for image " + id))) return;
BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, file, codepointGrid);
for (int[] y : codepointGrid) {
for (int x : y) {
font.registerCodepoint(x, bitmapImage);
}
}
this.images.put(id, bitmapImage);
}
@Override
public Collection<Font> fontsInUse() {
return new ArrayList<>(this.fonts.values());
}
@Override
public Optional<BitmapImage> bitmapImageByCodepoint(Key font, int codepoint) {
return getFontInUse(font).map(f -> f.getImageByCodepoint(codepoint));
}
@Override
public Optional<BitmapImage> bitmapImageByImageId(Key id) {
return Optional.ofNullable(this.images.get(id));
}
@Override
public int codepointByImageId(Key key, int x, int y) {
BitmapImage image = this.images.get(key);
if (image == null) return -1;
return image.codepointAt(x, y);
}
@Override
public String createOffsets(int offset, BiFunction<String, String, String> tagFormatter) {
return Optional.ofNullable(this.offsetFont).map(it -> it.createOffset(offset, tagFormatter)).orElse("");
}
@Override
public Optional<Font> getFontInUse(Key key) {
return Optional.ofNullable(fonts.get(key));
}
private Font getOrCreateFont(Key key) {
return this.fonts.computeIfAbsent(key, Font::new);
}
}

View File

@@ -7,15 +7,15 @@ import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.Key;
public class BitmapImage implements FontProvider {
private final Key imageId;
private final Key id;
private final Key font;
private final int height;
private final int ascent;
private final String file;
private final int[][] codepointGrid;
public BitmapImage(Key imageId, Key font, int height, int ascent, String file, int[][] codepointGrid) {
this.imageId = imageId;
public BitmapImage(Key id, Key font, int height, int ascent, String file, int[][] codepointGrid) {
this.id = id;
this.font = font;
this.height = height;
this.ascent = ascent;
@@ -39,16 +39,16 @@ public class BitmapImage implements FontProvider {
return font;
}
public Key imageId() {
return imageId;
public Key id() {
return id;
}
public int[][] codepointGrid() {
return codepointGrid;
return codepointGrid.clone();
}
public int codepointAt(int row, int column) {
if (row < 0 || row >= codepointGrid.length || column < 0 || column >= codepointGrid[row].length) {
if (!isValidCoordinate(row, column)) {
throw new IndexOutOfBoundsException("Invalid index: (" + row + ", " + column + ")");
}
return codepointGrid[row][column];
@@ -69,16 +69,16 @@ public class BitmapImage implements FontProvider {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
BitmapImage image = (BitmapImage) object;
return imageId.equals(image.imageId);
return id.equals(image.id);
}
@Override
public int hashCode() {
return imageId.hashCode();
return id.hashCode();
}
@Override
public JsonObject getJson() {
public JsonObject get() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("type", "bitmap");
jsonObject.addProperty("height", height);

View File

@@ -4,12 +4,12 @@ import net.momirealms.craftengine.core.util.Key;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class Font {
private final Key key;
private final HashMap<Integer, BitmapImage> idToCodepoint = new LinkedHashMap<>();
private final Map<Integer, BitmapImage> idToCodepoint = new LinkedHashMap<>();
public Font(Key key) {
this.key = key;
@@ -24,11 +24,11 @@ public class Font {
return Collections.unmodifiableCollection(this.idToCodepoint.keySet());
}
public BitmapImage getImageByCodepoint(int codepoint) {
public BitmapImage bitmapImageByCodepoint(int codepoint) {
return this.idToCodepoint.get(codepoint);
}
public void registerCodepoint(int codepoint, BitmapImage image) {
public void addBitMapImage(int codepoint, BitmapImage image) {
this.idToCodepoint.put(codepoint, image);
}
@@ -37,7 +37,7 @@ public class Font {
}
public Collection<BitmapImage> bitmapImages() {
return idToCodepoint.values().stream().distinct().toList();
return this.idToCodepoint.values().stream().distinct().toList();
}
@Override

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.core.font;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.CharacterUtils;
@@ -9,10 +8,8 @@ import net.momirealms.craftengine.core.util.Key;
import java.util.Collection;
import java.util.Optional;
import java.util.function.BiFunction;
public interface ImageManager extends Reloadable, ConfigSectionParser {
String[] CONFIG_SECTION_NAME = new String[] {"images", "image"};
public interface FontManager extends Reloadable {
Key DEFAULT_FONT = Key.of("minecraft:default");
String BYPASS_BOOK = "craftengine.filter.bypass.book";
String BYPASS_SIGN = "craftengine.filter.bypass.sign";
@@ -20,29 +17,23 @@ public interface ImageManager extends Reloadable, ConfigSectionParser {
String BYPASS_COMMAND = "craftengine.filter.bypass.command";
String BYPASS_ANVIL = "craftengine.filter.bypass.anvil";
default String[] sectionId() {
return CONFIG_SECTION_NAME;
}
default int loadingSequence() {
return LoadingSequence.FONT;
}
ConfigSectionParser[] parsers();
boolean isDefaultFontInUse();
boolean isIllegalCharacter(int codepoint);
boolean isIllegalCodepoint(int codepoint);
Collection<Font> fontsInUse();
Collection<Font> fonts();
Optional<BitmapImage> bitmapImageByCodepoint(Key font, int codepoint);
default Optional<BitmapImage> getBitmapImageByChars(Key font, char[] chars) {
default Optional<BitmapImage> bitmapImageByChars(Key font, char[] chars) {
return bitmapImageByCodepoint(font, CharacterUtils.charsToCodePoint(chars));
}
Optional<BitmapImage> bitmapImageByImageId(Key imageId);
Optional<Font> getFontInUse(Key font);
Optional<Font> fontById(Key font);
int codepointByImageId(Key imageId, int x, int y);
@@ -50,15 +41,15 @@ public interface ImageManager extends Reloadable, ConfigSectionParser {
return this.codepointByImageId(imageId, 0, 0);
}
default char[] getCharsByImageId(Key imageId) {
return getCharsByImageId(imageId, 0, 0);
default char[] charsByImageId(Key imageId) {
return charsByImageId(imageId, 0, 0);
}
default char[] getCharsByImageId(Key imageId, int x, int y) {
default char[] charsByImageId(Key imageId, int x, int y) {
return Character.toChars(this.codepointByImageId(imageId, x, y));
}
String createOffsets(int offset, BiFunction<String, String, String> tagFormatter);
String createOffsets(int offset, FontTagFormatter tagFormatter);
default String createMiniMessageOffsets(int offset) {
return createOffsets(offset, FormatUtils::miniMessageFont);

View File

@@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.font;
import com.google.gson.JsonObject;
public interface FontProvider {
import java.util.function.Supplier;
JsonObject getJson();
public interface FontProvider extends Supplier<JsonObject> {
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.font;
import java.util.function.BiFunction;
public interface FontTagFormatter extends BiFunction<String, String, String> {
}

View File

@@ -1,13 +0,0 @@
package net.momirealms.craftengine.core.font.emoji;
import net.momirealms.craftengine.core.plugin.CraftEngine;
public abstract class AbstractEmojiManager implements EmojiManager {
protected CraftEngine plugin;
public AbstractEmojiManager(CraftEngine plugin) {
this.plugin = plugin;
}
}

View File

@@ -1,17 +0,0 @@
package net.momirealms.craftengine.core.font.emoji;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
public interface EmojiManager extends Reloadable, ConfigSectionParser {
String[] CONFIG_SECTION_NAME = new String[] {"emoji", "emojis"};
default String[] sectionId() {
return CONFIG_SECTION_NAME;
}
default int loadingSequence() {
return LoadingSequence.EMOJI;
}
}

View File

@@ -1024,7 +1024,7 @@ public abstract class AbstractPackManager implements PackManager {
private void generateFonts(Path generatedPackPath) {
// generate image font json
for (Font font : plugin.imageManager().fontsInUse()) {
for (Font font : plugin.imageManager().fonts()) {
Key namespacedKey = font.key();
Path fontPath = generatedPackPath.resolve("assets")
.resolve(namespacedKey.namespace())
@@ -1058,7 +1058,7 @@ public abstract class AbstractPackManager implements PackManager {
}
for (BitmapImage image : font.bitmapImages()) {
providers.add(image.getJson());
providers.add(image.get());
}
try (FileWriter fileWriter = new FileWriter(fontPath.toFile())) {

View File

@@ -7,7 +7,7 @@ public class LoadingSequence {
public static final int BLOCK = 30;
public static final int ITEM = 40;
public static final int FURNITURE = 50;
public static final int FONT = 60;
public static final int IMAGE = 60;
public static final int RECIPE = 70;
public static final int CATEGORY = 80;
public static final int SOUND = 90;

View File

@@ -14,6 +14,12 @@ public interface PackManager extends Reloadable {
boolean registerConfigSectionParser(ConfigSectionParser parser);
default void registerConfigSectionParsers(ConfigSectionParser[] parsers) {
for (ConfigSectionParser parser : parsers) {
registerConfigSectionParser(parser);
}
}
boolean unregisterConfigSectionParser(String id);
default void unregisterConfigSectionParser(ConfigSectionParser parser) {

View File

@@ -0,0 +1,39 @@
package net.momirealms.craftengine.core.pack;
public class ResourceLocation {
public static boolean isValid(final String resourceLocation) {
int index = resourceLocation.indexOf(":");
if (index == -1) {
return isValidPath(resourceLocation);
} else {
return isValidNamespace(resourceLocation.substring(0, index)) && isValidPath(resourceLocation.substring(index + 1));
}
}
public static boolean validPathChar(char character) {
return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '/' || character == '.';
}
private static boolean validNamespaceChar(char character) {
return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '.';
}
private static boolean isValidNamespace(String namespace) {
for(int i = 0; i < namespace.length(); ++i) {
if (!validNamespaceChar(namespace.charAt(i))) {
return false;
}
}
return true;
}
private static boolean isValidPath(String path) {
for(int i = 0; i < path.length(); ++i) {
if (!validPathChar(path.charAt(i))) {
return false;
}
}
return true;
}
}

View File

@@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.RecipeManager;
import net.momirealms.craftengine.core.loot.VanillaLootManager;
@@ -45,7 +45,7 @@ public abstract class CraftEngine implements Plugin {
protected SchedulerAdapter<?> scheduler;
protected NetworkManager networkManager;
protected ClassPathAppender classPathAppender;
protected ImageManager imageManager;
protected FontManager fontManager;
protected PackManager packManager;
protected ConfigManager configManager;
protected ItemManager<?> itemManager;
@@ -106,7 +106,7 @@ public abstract class CraftEngine implements Plugin {
this.translationManager.reload();
this.templateManager.reload();
this.furnitureManager.reload();
this.imageManager.reload();
this.fontManager.reload();
this.itemManager.reload();
this.soundManager.reload();
this.recipeManager.reload();
@@ -124,7 +124,7 @@ public abstract class CraftEngine implements Plugin {
this.furnitureManager.delayedLoad();
this.itemBrowserManager.delayedLoad();
this.soundManager.delayedLoad();
this.imageManager.delayedLoad();
this.fontManager.delayedLoad();
// reset debugger
if (ConfigManager.debug()) {
this.debugger = (s) -> logger.info("[Debug] " + s.get());
@@ -153,7 +153,7 @@ public abstract class CraftEngine implements Plugin {
this.worldManager.delayedInit();
this.packManager.delayedInit();
this.furnitureManager.delayedInit();
this.imageManager.delayedInit();
this.fontManager.delayedInit();
this.vanillaLootManager.delayedInit();
this.delayedEnable();
});
@@ -164,7 +164,7 @@ public abstract class CraftEngine implements Plugin {
if (this.senderFactory != null) this.senderFactory.close();
if (this.commandManager != null) this.commandManager.unregisterFeatures();
if (this.networkManager != null) this.networkManager.shutdown();
if (this.imageManager != null) this.imageManager.disable();
if (this.fontManager != null) this.fontManager.disable();
if (this.packManager != null) this.packManager.disable();
if (this.itemManager != null) this.itemManager.disable();
if (this.blockManager != null) this.blockManager.disable();
@@ -241,8 +241,8 @@ public abstract class CraftEngine implements Plugin {
}
@Override
public ImageManager imageManager() {
return imageManager;
public FontManager imageManager() {
return fontManager;
}
@Override

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.RecipeManager;
import net.momirealms.craftengine.core.loot.VanillaLootManager;
@@ -55,7 +55,7 @@ public interface Plugin extends Reloadable {
NetworkManager networkManager();
ImageManager imageManager();
FontManager imageManager();
ConfigManager configManager();

View File

@@ -10,9 +10,11 @@ import java.util.UUID;
public abstract class SenderFactory<P extends Plugin, T> {
private final P plugin;
private final Sender console;
public SenderFactory(P plugin) {
this.plugin = plugin;
this.console = wrap(consoleCommandSender());
}
protected P plugin() {
@@ -35,10 +37,16 @@ public abstract class SenderFactory<P extends Plugin, T> {
protected abstract boolean isConsole(T sender);
protected abstract <C extends T> C consoleCommandSender();
protected boolean consoleHasAllPermissions() {
return true;
}
public Sender console() {
return console;
}
public <C extends T> Sender wrap(C sender) {
Objects.requireNonNull(sender, "sender");
return new AbstractSender<>(this.plugin, this, sender);

View File

@@ -45,4 +45,6 @@ public interface TranslationManager extends Reloadable, ConfigSectionParser {
default String[] sectionId() {
return CONFIG_SECTION_NAME;
}
void log(String id, String... args);
}

View File

@@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.Plugin;
import net.momirealms.craftengine.core.plugin.PluginProperties;
import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor;
import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.Pair;
import org.jetbrains.annotations.Nullable;
@@ -235,6 +236,13 @@ public class TranslationManagerImpl implements TranslationManager {
return clientLangManager;
}
@Override
public void log(String id, String... args) {
String translation = miniMessageTranslation(id);
if (translation == null) translation = id;
this.plugin.senderFactory().console().sendMessage(AdventureHelper.miniMessage().deserialize(translation, new IndexedArgumentTag(Arrays.stream(args).map(Component::text).toList())));
}
private Map<String, Object> updateLangFile(Map<String, Object> previous, Path translationFile) throws IOException {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);