diff --git a/common/src/main/java/org/ayamemc/ayame/client/api/PlayerModelAPI.java b/common/src/main/java/org/ayamemc/ayame/client/api/PlayerModelAPI.java index ab075fe..5c4f3c3 100644 --- a/common/src/main/java/org/ayamemc/ayame/client/api/PlayerModelAPI.java +++ b/common/src/main/java/org/ayamemc/ayame/client/api/PlayerModelAPI.java @@ -23,7 +23,7 @@ import net.minecraft.world.entity.player.Player; import org.ayamemc.ayame.client.renderer.GeoPlayerRender; import org.ayamemc.ayame.model.AyameModelCache; -import org.ayamemc.ayame.model.AyameModelType; +import org.ayamemc.ayame.model.ModelType; public class PlayerModelAPI { /** @@ -32,7 +32,7 @@ public class PlayerModelAPI { * @param player 玩家 * @param model 模型 */ - public static void switchModel(Player player, AyameModelType model) { + public static void switchModel(Player player, ModelType model) { GeoPlayerRender.GeoPlayerModel.switchModel(player, model); } @@ -42,7 +42,7 @@ public static void switchModel(Player player, AyameModelType model) { * @param player 玩家 * @param model 模型 */ - public static void switchModelOnClient(Player player, AyameModelType model) { + public static void switchModelOnClient(Player player, ModelType model) { AyameModelCache.setPlayerModel(player, model); } } diff --git a/common/src/main/java/org/ayamemc/ayame/client/gui/screen/ModelSelectMenuScreen.java b/common/src/main/java/org/ayamemc/ayame/client/gui/screen/ModelSelectMenuScreen.java index 02d103e..ef7a51c 100644 --- a/common/src/main/java/org/ayamemc/ayame/client/gui/screen/ModelSelectMenuScreen.java +++ b/common/src/main/java/org/ayamemc/ayame/client/gui/screen/ModelSelectMenuScreen.java @@ -35,7 +35,7 @@ import net.minecraft.resources.ResourceLocation; import org.ayamemc.ayame.client.api.ModelResourceAPI; import org.ayamemc.ayame.model.AyameModelCache; -import org.ayamemc.ayame.model.AyameModelType; +import org.ayamemc.ayame.model.ModelType; import org.ayamemc.ayame.model.resource.IModelResource; import org.ayamemc.ayame.util.ConfigUtil; import org.jetbrains.annotations.NotNull; @@ -70,7 +70,7 @@ public class ModelSelectMenuScreen extends AyameMainScreen { protected static final Path MODEL_DIR = Path.of("config/ayame/models/"); public final boolean skipWarningOnce; public final List modelResources; - public @Nullable AyameModelType selectedModel = AyameModelCache.getPlayerModel(Minecraft.getInstance().player); + public @Nullable ModelType selectedModel = AyameModelCache.getPlayerModel(Minecraft.getInstance().player); public @Nullable CloseCallback closeCallback; public @Nullable SwitchModelCallback switchModelCallback; @@ -242,7 +242,7 @@ public void onClose() { */ @FunctionalInterface public interface CloseCallback { - void close(List modelResources, @Nullable AyameModelType selectedModel); + void close(List modelResources, @Nullable ModelType selectedModel); } /** @@ -250,6 +250,6 @@ public interface CloseCallback { */ @FunctionalInterface public interface SwitchModelCallback { - void switchModel(List modelResources, @Nullable AyameModelType selectedModel); + void switchModel(List modelResources, @Nullable ModelType selectedModel); } } diff --git a/common/src/main/java/org/ayamemc/ayame/client/renderer/GeoPlayerRender.java b/common/src/main/java/org/ayamemc/ayame/client/renderer/GeoPlayerRender.java index b7af69b..9d16f68 100644 --- a/common/src/main/java/org/ayamemc/ayame/client/renderer/GeoPlayerRender.java +++ b/common/src/main/java/org/ayamemc/ayame/client/renderer/GeoPlayerRender.java @@ -30,7 +30,7 @@ import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.player.Player; import org.ayamemc.ayame.model.AyameModelCache; -import org.ayamemc.ayame.model.AyameModelType; +import org.ayamemc.ayame.model.ModelType; import org.jetbrains.annotations.Nullable; import software.bernie.geckolib.cache.object.BakedGeoModel; import software.bernie.geckolib.model.GeoModel; @@ -71,9 +71,9 @@ public GeoPlayerModel() { /** * 将玩家模型切换为对应外观,TODO: 同时告诉服务器 * - * @param model 传入{@link AyameModelType}类型的模型资源 + * @param model 传入{@link ModelType}类型的模型资源 */ - public static void switchModel(Player player, AyameModelType model) { + public static void switchModel(Player player, ModelType model) { AyameModelCache.setPlayerModel(player, model); } diff --git a/common/src/main/java/org/ayamemc/ayame/client/util/ModelResourceWriterUtil.java b/common/src/main/java/org/ayamemc/ayame/client/util/ModelResourceWriterUtil.java new file mode 100644 index 0000000..d238222 --- /dev/null +++ b/common/src/main/java/org/ayamemc/ayame/client/util/ModelResourceWriterUtil.java @@ -0,0 +1,105 @@ +/* + * Custom player model mod. Powered by GeckoLib. + * Copyright (C) 2024 CrystalNeko, HappyRespawnanchor, pertaz(Icon Designer) + * + * This file is part of Ayame. + * + * Ayame is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Ayame is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ayame. If not, see . + */ + +package org.ayamemc.ayame.client.util; + +import com.google.gson.JsonObject; +import com.mojang.blaze3d.platform.NativeImage; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import org.ayamemc.ayame.model.DefaultModelType; +import org.ayamemc.ayame.model.resource.IModelResource; +import org.jetbrains.annotations.NotNull; +import software.bernie.geckolib.cache.GeckoLibCache; +import software.bernie.geckolib.cache.object.BakedGeoModel; +import software.bernie.geckolib.loading.json.raw.Model; +import software.bernie.geckolib.loading.json.typeadapter.KeyFramesAdapter; +import software.bernie.geckolib.loading.object.BakedAnimations; +import software.bernie.geckolib.loading.object.BakedModelFactory; +import software.bernie.geckolib.loading.object.GeometryTree; + +import java.io.IOException; +import java.util.Map; +import static org.ayamemc.ayame.Ayame.MOD_ID; +/** + * 用于向GeckoLib缓存和贴图写入新模型的工具类 + * + * @see GeckoLibCache + */ +@Environment(EnvType.CLIENT) +public class ModelResourceWriterUtil { + /** + * @param modelRes 模型资源 + * @return 未完成的模型构建器 + */ + public static DefaultModelType.Builder addModelResource(@NotNull IModelResource modelRes) { + addBakedModel(modelRes.createModelResourceLocation(), modelRes); + addBakedAnimation(modelRes.createAnimationResourceLocation(), modelRes); + addTexture(modelRes.createTextureResourceLocation(), modelRes); + return DefaultModelType.Builder.create() + .setGeoModel(modelRes.createModelResourceLocation()) + .setAnimation(modelRes.createAnimationResourceLocation()) + .setTexture(modelRes.createTextureResourceLocation()); + } + /** + * 向模型缓存中添加新条目 + * + * @param resourceLocation 传入{@link ResourceLocation}类型的文件路径 + * @param modelRes 模型资源 + */ + public static void addBakedModel(ResourceLocation resourceLocation, @NotNull IModelResource modelRes) { + Map models = GeckoLibCache.getBakedModels(); + // 如果已经存在了 + if (models.containsKey(resourceLocation)) return; + Model m = KeyFramesAdapter.GEO_GSON.fromJson(GsonHelper.fromJson(KeyFramesAdapter.GEO_GSON, modelRes.getModelJson(modelRes.getDefault()).toString(), JsonObject.class), Model.class); + BakedGeoModel bakedGeoModel = BakedModelFactory.getForNamespace(MOD_ID).constructGeoModel(GeometryTree.fromModel(m)); + models.put(resourceLocation, bakedGeoModel); + } + /** + * 向动画缓存中添加新条目 + * + * @param resourceLocation 传入{@link ResourceLocation}类型的文件路径 + * @param modelRes 模型资源 + * @see ResourceLocation + */ + public static void addBakedAnimation(ResourceLocation resourceLocation, @NotNull IModelResource modelRes) { + Map animations = GeckoLibCache.getBakedAnimations(); + if (animations.containsKey(resourceLocation)) return; + BakedAnimations ani = KeyFramesAdapter.GEO_GSON.fromJson(GsonHelper.getAsJsonObject(modelRes.getAnimationJson(modelRes.getDefault()).toGson(), "animations"), BakedAnimations.class); + animations.put(resourceLocation, ani); + } + /** + * 注册贴图 + * + * @param resourceLocation 传入{@link ResourceLocation}类型的文件路径 + * @param modelRes 模型资源 + */ + public static void addTexture(ResourceLocation resourceLocation, @NotNull IModelResource modelRes) { + try { + Minecraft.getInstance().getTextureManager().register(resourceLocation, new DynamicTexture(NativeImage.read(modelRes.getTexture(modelRes.getDefault())))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/common/src/main/java/org/ayamemc/ayame/model/AyameModelCache.java b/common/src/main/java/org/ayamemc/ayame/model/AyameModelCache.java index b31bdfe..05d59a9 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/AyameModelCache.java +++ b/common/src/main/java/org/ayamemc/ayame/model/AyameModelCache.java @@ -31,9 +31,9 @@ */ public class AyameModelCache { // 考虑到未来可能涩及多线程操作,所以使用 ConcurrentHashMap - public static Map playerModelCache = new ConcurrentHashMap<>(); + public static Map playerModelCache = new ConcurrentHashMap<>(); - public static void setPlayerModel(Player player, AyameModelType model) { + public static void setPlayerModel(Player player, ModelType model) { playerModelCache.put(player, model); } @@ -48,7 +48,7 @@ public static void removePlayerModel(Player player) { * @return 玩家模型 */ @NotNull - public static AyameModelType getPlayerModel(Player player) { + public static ModelType getPlayerModel(Player player) { return playerModelCache.getOrDefault(player, DefaultModels.DEFAULT_MODEL); } diff --git a/common/src/main/java/org/ayamemc/ayame/model/DefaultAyameModelType.java b/common/src/main/java/org/ayamemc/ayame/model/DefaultModelType.java similarity index 87% rename from common/src/main/java/org/ayamemc/ayame/model/DefaultAyameModelType.java rename to common/src/main/java/org/ayamemc/ayame/model/DefaultModelType.java index 024730c..99bc642 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/DefaultAyameModelType.java +++ b/common/src/main/java/org/ayamemc/ayame/model/DefaultModelType.java @@ -32,8 +32,8 @@ * @param metaData 模型元数据 */ -public record DefaultAyameModelType(ResourceLocation geoModel, ResourceLocation animation, ResourceLocation texture, - IndexData.ModelMetaData metaData) implements AyameModelType { +public record DefaultModelType(ResourceLocation geoModel, ResourceLocation animation, ResourceLocation texture, + IndexData.ModelMetaData metaData) implements ModelType { @Override @@ -86,8 +86,8 @@ public Builder setMetaData(IndexData.ModelMetaData metaData) { return this; } - public DefaultAyameModelType build() { - return new DefaultAyameModelType(geoModel, animation, texture, metaData); + public DefaultModelType build() { + return new DefaultModelType(geoModel, animation, texture, metaData); } } } diff --git a/common/src/main/java/org/ayamemc/ayame/model/DefaultModels.java b/common/src/main/java/org/ayamemc/ayame/model/DefaultModels.java index 1a47a8c..40752b2 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/DefaultModels.java +++ b/common/src/main/java/org/ayamemc/ayame/model/DefaultModels.java @@ -26,16 +26,15 @@ import org.ayamemc.ayame.util.FileUtil; import java.nio.file.Path; -import java.util.List; import static org.ayamemc.ayame.util.ResourceLocationHelper.withAyameNamespace; public class DefaultModels { public static final String MODEL_PATH = "config/ayame/models/"; - public static final AyameModelType DEFAULT_MODEL = DefaultAyameModelType.Builder.create() - .setGeoModel(withAyameNamespace("geo/ayame/default")) - .setAnimation(withAyameNamespace("animations/ayame/default")) - .setTexture(withAyameNamespace("textures/ayame/default")) + public static final ModelType DEFAULT_MODEL = DefaultModelType.Builder.create() + .setGeoModel(withAyameNamespace("geo/ayame/default.json")) + .setAnimation(withAyameNamespace("animations/ayame/default.json")) + .setTexture(withAyameNamespace("textures/ayame/default.png")) .setMetaData(IndexData.ModelMetaData.Builder.create() .setName("default") .setAuthors(new String[]{"CrystalNeko"}) diff --git a/common/src/main/java/org/ayamemc/ayame/model/AyameModelType.java b/common/src/main/java/org/ayamemc/ayame/model/ModelType.java similarity index 97% rename from common/src/main/java/org/ayamemc/ayame/model/AyameModelType.java rename to common/src/main/java/org/ayamemc/ayame/model/ModelType.java index 8c1ffe7..0671c18 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/AyameModelType.java +++ b/common/src/main/java/org/ayamemc/ayame/model/ModelType.java @@ -26,7 +26,7 @@ /// /// 模型类型指的是使用了哪种模型,例如ayame的模型类型为`ayame`,兼容ysm的为`ysm`,值与{@link ModelMetaData#type()}的值对应 -public interface AyameModelType { +public interface ModelType { /** * 从geckolib缓存中获取模型资源 * diff --git a/common/src/main/java/org/ayamemc/ayame/model/resource/IModelResource.java b/common/src/main/java/org/ayamemc/ayame/model/resource/IModelResource.java index 6bc9338..bebfd92 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/resource/IModelResource.java +++ b/common/src/main/java/org/ayamemc/ayame/model/resource/IModelResource.java @@ -20,23 +20,44 @@ package org.ayamemc.ayame.model.resource; -import org.ayamemc.ayame.model.AyameModelType; +import net.minecraft.resources.ResourceLocation; +import org.ayamemc.ayame.model.AyameModelCache; +import org.ayamemc.ayame.model.DefaultModelType; +import org.ayamemc.ayame.model.ModelType; import org.ayamemc.ayame.model.IndexData; +import org.ayamemc.ayame.util.JsonInterpreter; import org.ayamemc.ayame.util.TODO; import java.io.File; -import java.nio.file.Path; +import java.io.InputStream; import java.util.List; +import static org.ayamemc.ayame.util.ResourceLocationHelper.withAyameNamespace; public interface IModelResource { // TODO 完成 IndexData.ModelMetaData getMetaData(); + default String getName(){ + return getMetaData().name(); + } List getModels(); + JsonInterpreter getModelJson(IndexData.ModelData model); + JsonInterpreter getAnimationJson(IndexData.ModelData model); + InputStream getTexture(IndexData.ModelData model); + default IndexData.ModelData getDefault() { return getModels().getFirst(); } + default ResourceLocation createModelResourceLocation(){ + return withAyameNamespace("geo/" + getName() + ".json"); + } + default ResourceLocation createAnimationResourceLocation(){ + return withAyameNamespace("animations/" + getName() + ".json"); + } + default ResourceLocation createTextureResourceLocation(){ + return withAyameNamespace("textures/" + getName() + ".png"); + } static IModelResource fromFile(File file){ throw new TODO("create model from file"); diff --git a/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceCache.java b/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceCache.java index d82496c..5adc9c1 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceCache.java +++ b/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceCache.java @@ -27,7 +27,7 @@ import java.util.concurrent.CopyOnWriteArrayList; /** - * 客户端模型资源的缓存 + * 模型资源的缓存 */ public class ModelResourceCache { /** diff --git a/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceRegistry.java b/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceRegistry.java index e532dfb..03c0468 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceRegistry.java +++ b/common/src/main/java/org/ayamemc/ayame/model/resource/ModelResourceRegistry.java @@ -25,6 +25,7 @@ import org.ayamemc.ayame.util.TODO; import org.jetbrains.annotations.ApiStatus; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipFile; @@ -105,5 +106,9 @@ public JsonInterpreter getIndexJson(){ public String getFormat(){ return getIndexJson().getString("format"); } + + public InputStream getContent(String path){ + return FileUtil.getInputStreamFromZip(zipFile, path); + } } } diff --git a/common/src/main/java/org/ayamemc/ayame/model/resource/SimpleModelResource.java b/common/src/main/java/org/ayamemc/ayame/model/resource/SimpleModelResource.java index bb4d661..1a9404f 100644 --- a/common/src/main/java/org/ayamemc/ayame/model/resource/SimpleModelResource.java +++ b/common/src/main/java/org/ayamemc/ayame/model/resource/SimpleModelResource.java @@ -21,7 +21,9 @@ package org.ayamemc.ayame.model.resource; import org.ayamemc.ayame.model.IndexData; +import org.ayamemc.ayame.util.JsonInterpreter; +import java.io.InputStream; import java.util.List; public class SimpleModelResource implements IModelResource{ @@ -50,4 +52,19 @@ public List getModels() { .build() ); } + + @Override + public JsonInterpreter getModelJson(IndexData.ModelData model) { + return JsonInterpreter.of(modelFile.getContent(model.model())); + } + + @Override + public JsonInterpreter getAnimationJson(IndexData.ModelData model) { + return JsonInterpreter.of(modelFile.getContent(model.animation())); + } + + @Override + public InputStream getTexture(IndexData.ModelData model) { + return modelFile.getContent(model.texture()); + } } diff --git a/fabric/src/main/java/org/ayamemc/ayame/fabric/client/event/AyameKeyMappingEventHandler.java b/fabric/src/main/java/org/ayamemc/ayame/fabric/client/event/AyameKeyMappingEventHandler.java index 4a24365..12de759 100644 --- a/fabric/src/main/java/org/ayamemc/ayame/fabric/client/event/AyameKeyMappingEventHandler.java +++ b/fabric/src/main/java/org/ayamemc/ayame/fabric/client/event/AyameKeyMappingEventHandler.java @@ -25,10 +25,15 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; import org.ayamemc.ayame.client.AyameClient; +import org.ayamemc.ayame.client.api.PlayerModelAPI; import org.ayamemc.ayame.client.gui.screen.ModelSelectMenuScreen; import org.ayamemc.ayame.client.handler.EventHandler; +import org.ayamemc.ayame.client.util.ModelResourceWriterUtil; import org.ayamemc.ayame.fabric.client.util.AyameTMSKeyMappings; +import org.ayamemc.ayame.mixin.PlayerMixin; +import org.ayamemc.ayame.model.DefaultModels; import org.ayamemc.ayame.util.JavaUtil; import org.ayamemc.ayame.util.TranslatableName; import org.jetbrains.annotations.Nullable;