package eu.mhsl.minecraft.pixelpics; import eu.mhsl.minecraft.pixelpics.assets.AssetReader; import eu.mhsl.minecraft.pixelpics.assets.BlockModelRegistry; import eu.mhsl.minecraft.pixelpics.assets.ResourcePack; import eu.mhsl.minecraft.pixelpics.assets.ResourcePackLoader; import eu.mhsl.minecraft.pixelpics.assets.SkinCache; import eu.mhsl.minecraft.pixelpics.assets.TextureCache; import eu.mhsl.minecraft.pixelpics.assets.font.BitmapFont; import eu.mhsl.minecraft.pixelpics.assets.font.FontLoader; import eu.mhsl.minecraft.pixelpics.commands.PixelPicsCommand; import eu.mhsl.minecraft.pixelpics.listeners.OnMapInitialize; import eu.mhsl.minecraft.pixelpics.render.RenderManager; import eu.mhsl.minecraft.pixelpics.render.entity.cem.BlockEntityBaker; import eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker; import eu.mhsl.minecraft.pixelpics.render.entity.cem.CemModelLoader; import eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer; import eu.mhsl.minecraft.pixelpics.render.tint.BiomeTintProvider; import eu.mhsl.minecraft.pixelpics.survival.CameraListener; import eu.mhsl.minecraft.pixelpics.survival.CraftingListener; import eu.mhsl.minecraft.pixelpics.survival.JoinListener; import eu.mhsl.minecraft.pixelpics.survival.SurvivalRecipes; import eu.mhsl.minecraft.pixelpics.utils.MapColorPalette; import java.io.File; import java.io.InputStream; import java.util.Objects; import java.util.Optional; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.plugin.java.JavaPlugin; public final class Main extends JavaPlugin { private static Main instance; private ResourcePack resourcePack; private DefaultScreenRenderer screenRenderer; private RenderManager renderManager; public final NamespacedKey pictureIdFlag = new NamespacedKey(this, "imageid"); /** Marks a {@code PLAYER_HEAD} as a camera (BYTE 1). */ public final NamespacedKey cameraMarker = new NamespacedKey(this, "camera"); /** Loaded film count stored on a camera (INTEGER, 0..{@code CameraItems.MAX_FILM}). */ public final NamespacedKey filmCountKey = new NamespacedKey(this, "filmcount"); /** Marks a {@code PLAYER_HEAD} as a film roll (BYTE 1). */ public final NamespacedKey filmMarker = new NamespacedKey(this, "film"); @Override public void onEnable() { instance = this; saveDefaultConfig(); initRenderManager(); Bukkit.getPluginManager().registerEvents(new OnMapInitialize(), this); Objects.requireNonNull(Bukkit.getPluginCommand("pixelPic")).setExecutor(new PixelPicsCommand()); Bukkit.getPluginManager().registerEvents(new CameraListener(), this); Bukkit.getPluginManager().registerEvents(new CraftingListener(), this); Bukkit.getPluginManager().registerEvents(new JoinListener(), this); SurvivalRecipes.register(); initRenderer(); } private void initRenderManager() { int cores = Runtime.getRuntime().availableProcessors(); int threads = getConfig().getInt("render.threads", 0); if (threads <= 0) threads = Math.max(1, cores - 2); threads = Math.min(threads, cores); int maxConcurrent = Math.max(1, getConfig().getInt("render.max-concurrent", 2)); int queueSize = Math.max(0, getConfig().getInt("render.queue-size", 8)); int timeoutSeconds = Math.max(1, getConfig().getInt("render.timeout-seconds", 30)); this.renderManager = new RenderManager(this, threads, maxConcurrent, queueSize, timeoutSeconds); getLogger() .info("Render pool: " + threads + " core(s), max " + maxConcurrent + " concurrent, queue " + queueSize + ", timeout " + timeoutSeconds + "s."); } private void initRenderer() { File resourcePackDir = new File(getDataFolder(), "resourcepack"); if (!resourcePackDir.exists() && !resourcePackDir.mkdirs()) { getLogger().warning("Could not create resource pack directory: " + resourcePackDir); } Optional pack = ResourcePackLoader.load(resourcePackDir, getLogger()); if (pack.isEmpty()) { getLogger() .severe("No resource pack found in " + resourcePackDir.getPath() + " — place a vanilla resource pack (directory with assets/minecraft/... or a .zip) there. " + "/pixelPic is disabled until a pack is available."); return; } this.resourcePack = pack.get(); AssetReader reader = new AssetReader(resourcePack); TextureCache textures = new TextureCache(resourcePack); BlockModelRegistry registry = new BlockModelRegistry(reader, textures); BiomeTintProvider tintProvider = new BiomeTintProvider(textures); CemModelLoader cemLoader = new CemModelLoader(); try (InputStream in = getResource("cem/cem_template_models.json")) { int n = in == null ? 0 : cemLoader.load(in, getLogger()); getLogger().info("Loaded " + n + " CEM entity models."); } catch (Exception e) { getLogger().severe("Failed to load CEM entity models: " + e.getMessage()); } SkinCache skinCache = new SkinCache(); BitmapFont font = FontLoader.load(resourcePack, textures, getLogger()); getLogger().info("Loaded sign font (" + (font.isEmpty() ? "no glyphs — text disabled" : "ok") + ")."); CemBaker entityBaker = new CemBaker(cemLoader, textures, skinCache); BlockEntityBaker blockEntityBaker = new BlockEntityBaker(cemLoader, textures, skinCache, font); this.screenRenderer = new DefaultScreenRenderer( registry, tintProvider, textures, entityBaker, blockEntityBaker, getLogger(), renderManager.tracePool()); // Warm the map palette on the main thread so off-thread dithering never triggers its first init. MapColorPalette.size(); getLogger().info("PixelPics renderer initialized with resource pack assets."); } @Override public void onDisable() { SurvivalRecipes.unregister(); if (renderManager != null) { renderManager.shutdown(); renderManager = null; } if (resourcePack != null) { resourcePack.close(); resourcePack = null; } } /** The renderer, or {@code null} when no resource pack is available (degraded mode). */ public DefaultScreenRenderer getScreenRenderer() { return this.screenRenderer; } /** The render queue/limiter; always available after {@code onEnable}. */ public RenderManager getRenderManager() { return this.renderManager; } public static Main getInstance() { return instance; } }