9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2026-01-06 15:52:03 +00:00

Merge pull request #127 from jhqwqmc/dev

feat(core): 添加进度
This commit is contained in:
XiaoMoMi
2025-04-21 21:16:27 +08:00
committed by GitHub
13 changed files with 317 additions and 4 deletions

View File

@@ -114,4 +114,5 @@ warning.config.model.generation.texture.invalid_resource_location: "<yellow>Issu
warning.config.model.generation.parent.invalid_resource_location: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' has a parent argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.emoji.lack_keywords: "<yellow>Issue found in file <arg:0> - The emoji '<arg:1>' is missing the required 'keywords' argument.</yellow>"
warning.config.emoji.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated emoji '<arg:1>'.</yellow>"
warning.config.emoji.invalid_image: "<yellow>Issue found in file <arg:0> - The emoji '<arg:1>' has an invalid 'image' argument '<arg:2>'.</yellow>"
warning.config.emoji.invalid_image: "<yellow>Issue found in file <arg:0> - The emoji '<arg:1>' has an invalid 'image' argument '<arg:2>'.</yellow>"
warning.config.advancement.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated advancement '<arg:1>'.</yellow>"

View File

@@ -114,4 +114,5 @@ warning.config.model.generation.texture.invalid_resource_location: "<yellow>在
warning.config.model.generation.parent.invalid_resource_location: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 的父模型参数 [<arg:2>] 包含非法字符请参考资源路径规范https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"
warning.config.emoji.lack_keywords: "<yellow>在文件 <arg:0> 中发现问题 - 表情 '<arg:1>' 缺少必要的 'keywords' 配置</yellow>"
warning.config.emoji.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 表情 '<arg:1>' 重复定义</yellow>"
warning.config.emoji.invalid_image: "<yellow>在文件 <arg:0> 中发现问题 - 表情 '<arg:1>' 使用了无效的 'image' 图片参数 '<arg:2>'.</yellow>"
warning.config.emoji.invalid_image: "<yellow>在文件 <arg:0> 中发现问题 - 表情 '<arg:1>' 使用了无效的 'image' 图片参数 '<arg:2>'.</yellow>"
warning.config.advancement.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 进度 '<arg:1>' 重复定义</yellow>"

View File

@@ -0,0 +1,62 @@
package net.momirealms.craftengine.bukkit.advancement;
import com.google.gson.JsonElement;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.core.advancement.AbstractAdvancementManager;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class BukkitAdvancementManager extends AbstractAdvancementManager {
private final BukkitCraftEngine plugin;
private final AdvancementParser advancementParser;
private final Map<Key, JsonElement> advancements = new HashMap<>();
public BukkitAdvancementManager(BukkitCraftEngine plugin) {
super(plugin);
this.plugin = plugin;
this.advancementParser = new AdvancementParser();
}
public void unload() {
advancements.clear();
}
@Override
public ConfigSectionParser parser() {
return this.advancementParser;
}
public class AdvancementParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"advancements", "advancement"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.ADVANCEMENT;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (advancements.containsKey(id)) {
TranslationManager.instance().log("warning.config.advancement.duplicated", path.toString(), id.toString());
return;
}
JsonElement jsonTree = GsonHelper.get().toJsonTree(section);
FastNMS.INSTANCE.registerAdvancement(id.decompose(), jsonTree);
advancements.put(id, jsonTree);
}
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.plugin;
import net.momirealms.antigrieflib.AntiGriefLib;
import net.momirealms.craftengine.bukkit.advancement.BukkitAdvancementManager;
import net.momirealms.craftengine.bukkit.api.event.CraftEngineReloadEvent;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
@@ -156,6 +157,7 @@ public class BukkitCraftEngine extends CraftEngine {
super.soundManager = new BukkitSoundManager(this);
super.vanillaLootManager = new BukkitVanillaLootManager(this);
super.fontManager = new BukkitFontManager(this);
super.advancementManager = new BukkitAdvancementManager(this);
super.onPluginEnable();
// compatibility
// register expansion
@@ -245,6 +247,11 @@ public class BukkitCraftEngine extends CraftEngine {
return (BukkitBlockManager) blockManager;
}
@Override
public BukkitAdvancementManager advancementManager() {
return (BukkitAdvancementManager) advancementManager;
}
@Override
public BukkitFurnitureManager furnitureManager() {
return (BukkitFurnitureManager) furnitureManager;

View File

@@ -0,0 +1,11 @@
package net.momirealms.craftengine.core.advancement;
import net.momirealms.craftengine.core.plugin.CraftEngine;
public abstract class AbstractAdvancementManager implements AdvancementManager {
private final CraftEngine plugin;
public AbstractAdvancementManager(CraftEngine plugin) {
this.plugin = plugin;
}
}

View File

@@ -0,0 +1,9 @@
package net.momirealms.craftengine.core.advancement;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
public interface AdvancementManager extends Manageable {
ConfigSectionParser parser();
}

View File

@@ -14,4 +14,5 @@ public class LoadingSequence {
public static final int JUKEBOX_SONG = 100;
public static final int VANILLA_LOOTS = 110;
public static final int EMOJI = 120;
public static final int ADVANCEMENT = 130;
}

View File

@@ -19,6 +19,7 @@ public class ResourcePackHosts {
public static final Key ALIST = Key.of("craftengine:alist");
public static final Key DROPBOX = Key.of("craftengine:dropbox");
public static final Key ONEDRIVE = Key.of("craftengine:onedrive");
public static final Key GITLAB = Key.of("craftengine:gitlab");
static {
register(NONE, NoneHost.FACTORY);
@@ -29,6 +30,7 @@ public class ResourcePackHosts {
register(ALIST, AlistHost.FACTORY);
register(DROPBOX, DropboxHost.FACTORY);
register(ONEDRIVE, OneDriveHost.FACTORY);
register(GITLAB, GitLabHost.FACTORY);
}
public static void register(Key key, ResourcePackHostFactory factory) {

View File

@@ -0,0 +1,199 @@
package net.momirealms.craftengine.core.pack.host.impl;
import com.google.gson.reflect.TypeToken;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.HashUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class GitLabHost implements ResourcePackHost {
public static final Factory FACTORY = new Factory();
private final String gitlabUrl;
private final String accessToken;
private final String projectId;
private final ProxySelector proxy;
private String url;
private String sha1;
private UUID uuid;
public GitLabHost(String gitlabUrl, String accessToken, String projectId, ProxySelector proxy) {
this.gitlabUrl = gitlabUrl;
this.accessToken = accessToken;
this.projectId = projectId;
this.proxy = proxy;
this.readCacheFromDisk();
}
public void readCacheFromDisk() {
Path cachePath = CraftEngine.instance().dataFolderPath().resolve("gitlab.cache");
if (!Files.exists(cachePath)) return;
try (InputStream is = Files.newInputStream(cachePath)) {
Map<String, String> cache = GsonHelper.get().fromJson(
new InputStreamReader(is),
new TypeToken<Map<String, String>>(){}.getType()
);
this.url = cache.get("url");
this.sha1 = cache.get("sha1");
String uuidString = cache.get("uuid");
if (uuidString != null && !uuidString.isEmpty()) {
this.uuid = UUID.fromString(uuidString);
}
CraftEngine.instance().logger().info("[GitLab] Loaded cached resource pack info");
} catch (Exception e) {
CraftEngine.instance().logger().warn(
"[GitLab] Failed to read cache file: " + e.getMessage());
}
}
public void saveCacheToDisk() {
Map<String, String> cache = new HashMap<>();
cache.put("url", this.url);
cache.put("sha1", this.sha1);
cache.put("uuid", this.uuid != null ? this.uuid.toString() : "");
Path cachePath = CraftEngine.instance().dataFolderPath().resolve("gitlab.cache");
try {
Files.writeString(
cachePath,
GsonHelper.get().toJson(cache),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
);
} catch (IOException e) {
CraftEngine.instance().logger().warn(
"[GitLab] Failed to save cache: " + e.getMessage());
}
}
@Override
public boolean canUpload() {
return true;
}
@Override
public Key type() {
return ResourcePackHosts.GITLAB;
}
@Override
public CompletableFuture<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
if (url == null) return CompletableFuture.completedFuture(Collections.emptyList());
return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(this.url, this.uuid, this.sha1)));
}
@Override
public CompletableFuture<Void> upload(Path resourcePackPath) {
CompletableFuture<Void> future = new CompletableFuture<>();
CraftEngine.instance().scheduler().executeAsync(() -> {
this.sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath);
this.uuid = UUID.nameUUIDFromBytes(this.sha1.getBytes(StandardCharsets.UTF_8));
try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) {
String boundary = UUID.randomUUID().toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(this.gitlabUrl + "/api/v4/projects/" + this.projectId + "/uploads"))
.header("PRIVATE-TOKEN", this.accessToken)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(buildMultipartBody(resourcePackPath, boundary))
.build();
long uploadStart = System.currentTimeMillis();
CraftEngine.instance().logger().info(
"[GitLab] Initiating resource pack upload...");
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
long uploadTime = System.currentTimeMillis() - uploadStart;
CraftEngine.instance().logger().info(
"[GitLab] Upload request completed in " + uploadTime + "ms");
if (response.statusCode() == 200 || response.statusCode() == 201) {
Map<String, Object> json = GsonHelper.parseJsonToMap(response.body());
if (json.containsKey("full_path")) {
this.url = this.gitlabUrl + json.get("full_path");
future.complete(null);
saveCacheToDisk();
return;
}
}
CraftEngine.instance().logger().warn("[GitLab] Upload failed: " + response.body());
future.completeExceptionally(new RuntimeException("Upload failed: " + response.body()));
})
.exceptionally(ex -> {
CraftEngine.instance().logger().warn(
"[GitLab] Upload error: " + ex.getMessage());
future.completeExceptionally(ex);
return null;
});
} catch (IOException e) {
CraftEngine.instance().logger().warn(
"[GitLab] Failed to upload resource pack: " + e.getMessage());
}
});
return future;
}
private HttpRequest.BodyPublisher buildMultipartBody(Path filePath, String boundary) throws IOException {
List<byte[]> parts = new ArrayList<>();
String filePartHeader = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath.getFileName() + "\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
parts.add(filePartHeader.getBytes());
parts.add(Files.readAllBytes(filePath));
parts.add("\r\n".getBytes());
String endBoundary = "--" + boundary + "--\r\n";
parts.add(endBoundary.getBytes());
return HttpRequest.BodyPublishers.ofByteArrays(parts);
}
public static class Factory implements ResourcePackHostFactory {
@Override
public ResourcePackHost create(Map<String, Object> arguments) {
boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false);
String gitlabUrl = (String) arguments.get("gitlab-url");
if (gitlabUrl == null || gitlabUrl.isEmpty()) {
throw new IllegalArgumentException("'gitlab-url' cannot be empty for GitLab host");
}
if (gitlabUrl.endsWith("/")) {
gitlabUrl = gitlabUrl.substring(0, gitlabUrl.length() - 1);
}
String accessToken = useEnv ? System.getenv("CE_GITLAB_ACCESS_TOKEN") : (String) arguments.get("access-token");
if (accessToken == null || accessToken.isEmpty()) {
throw new IllegalArgumentException("Missing required 'access-token' configuration");
}
String projectId = (String) arguments.get("project-id");
if (projectId == null || projectId.isEmpty()) {
throw new IllegalArgumentException("Missing required 'project-id' configuration");
}
projectId = URLEncoder.encode(projectId, StandardCharsets.UTF_8).replace("/", "%2F");
ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy"));
return new GitLabHost(gitlabUrl, accessToken, projectId, proxy);
}
}
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.advancement.AdvancementManager;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.font.FontManager;
@@ -66,6 +67,7 @@ public abstract class CraftEngine implements Plugin {
protected GuiManager guiManager;
protected SoundManager soundManager;
protected VanillaLootManager vanillaLootManager;
protected AdvancementManager advancementManager;
private final Consumer<CraftEngine> reloadEventDispatcher;
private boolean isReloading;
@@ -142,6 +144,7 @@ public abstract class CraftEngine implements Plugin {
this.vanillaLootManager.reload();
this.guiManager.reload();
this.packManager.reload();
this.advancementManager.reload();
if (reloadRecipe) {
this.recipeManager.reload();
}
@@ -161,6 +164,7 @@ public abstract class CraftEngine implements Plugin {
this.itemBrowserManager.delayedLoad();
// collect illegal characters from minecraft:default font
this.fontManager.delayedLoad();
this.advancementManager.delayedLoad();
if (reloadRecipe) {
// convert data pack recipes
this.recipeManager.delayedLoad();
@@ -210,6 +214,7 @@ public abstract class CraftEngine implements Plugin {
this.packManager.delayedInit();
this.fontManager.delayedInit();
this.vanillaLootManager.delayedInit();
this.advancementManager.delayedInit();
// reload the plugin
try {
this.reloadPlugin(Runnable::run, Runnable::run, true);
@@ -228,6 +233,7 @@ public abstract class CraftEngine implements Plugin {
public void onPluginDisable() {
if (this.networkManager != null) this.networkManager.disable();
if (this.fontManager != null) this.fontManager.disable();
if (this.advancementManager != null) this.advancementManager.disable();
if (this.packManager != null) this.packManager.disable();
if (this.itemManager != null) this.itemManager.disable();
if (this.blockManager != null) this.blockManager.disable();
@@ -268,6 +274,8 @@ public abstract class CraftEngine implements Plugin {
this.packManager.registerConfigSectionParsers(this.soundManager.parsers());
// register vanilla loot parser
this.packManager.registerConfigSectionParser(this.vanillaLootManager.parser());
// register advancement parser
this.packManager.registerConfigSectionParser(this.advancementManager.parser());
}
protected abstract void platformDelayedEnable();
@@ -391,6 +399,11 @@ public abstract class CraftEngine implements Plugin {
return fontManager;
}
@Override
public AdvancementManager advancementManager() {
return advancementManager;
}
@Override
public TranslationManager translationManager() {
return translationManager;

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.advancement.AdvancementManager;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -61,6 +62,8 @@ public interface Plugin {
FontManager fontManager();
AdvancementManager advancementManager();
Config config();
TranslationManager translationManager();

View File

@@ -28,6 +28,10 @@ public record Key(String namespace, String value) {
return of(decompose(namespacedId, "minecraft"));
}
public String[] decompose() {
return new String[] { namespace, value };
}
@Override
public int hashCode() {
return toString().hashCode();

View File

@@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=0.0.49
project_version=0.0.50-beta.1
config_version=29
lang_version=6
lang_version=7
project_group=net.momirealms
latest_supported_version=1.21.5
latest_minecraft_version=1.21.5