完善了ssc数据驱动加载的逻辑

放入data/<mod_id>/ssc_animations(避免与其它动画模组路径冲突)
将示例中的硬编码注册改为从数据json中读取
This commit is contained in:
叁玖领域 2026-01-12 14:24:17 +08:00
parent 667a786c77
commit ab8373d62e
30 changed files with 4340 additions and 162 deletions

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-28T18:32:27.7132485 Languages: zh_cn
33a56369fb517ec3b528128539e1e160666d9602 assets/sccore/lang/zh_cn.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-11-28T18:32:27.715234 Languages: en_us
941682565bf7998e144253f88be175d198e43566 assets/sccore/lang/en_us.json

View File

@ -0,0 +1 @@
public net.minecraft.client.Camera f_90552_ # position

View File

@ -0,0 +1,42 @@
modLoader = "javafml"
loaderVersion = "${loader_version_range}"
license = "${mod_license}"
issueTrackerURL="${mod_url}"
[[mods]]
modId = "${mod_id}"
version = "${mod_version}"
displayName = "${mod_name}"
displayURL="${mod_url}"
logoFile="logo.png"
credits="${mod_credits}"
authors = "${mod_authors}"
description = '''${mod_description}'''
[[dependencies."${mod_id}"]]
modId = "forge"
mandatory = true
versionRange = "${forge_version_range}"
ordering = "NONE"
side = "BOTH"
[[dependencies."${mod_id}"]]
modId = "minecraft"
mandatory = true
versionRange = "${minecraft_version_range}"
ordering = "NONE"
side = "BOTH"
[[dependencies."${mod_id}"]]
modId = "playeranimator"
mandatory = false
versionRange = "[1.0.1,)"
ordering = "AFTER"
side = "BOTH"
[[dependencies."${mod_id}"]]
modId = "bendylib"
mandatory = false
versionRange = "[4.0.0,)"
ordering = "AFTER"
side = "BOTH"

View File

@ -0,0 +1,32 @@
{
"translation.sccore.command.animation.accept_apply_success": "%s has accepted the application of %s.",
"translation.sccore.command.animation.accept_invite_success": "Invitation accepted.",
"translation.sccore.command.animation.accept_message_click": "Click here to accept.",
"translation.sccore.command.animation.accept_request_success": "Request accepted.",
"translation.sccore.command.animation.animation_cooldown": "You cannot perform this operation: Cooling down (%s second(s)).",
"translation.sccore.command.animation.animation_expire": "You cannot perform this operation: It has expired.",
"translation.sccore.command.animation.animation_json_path": "%s",
"translation.sccore.command.animation.animation_operation_cancelled": "Exception: Operation cancelled.",
"translation.sccore.command.animation.animation_operation_unsupported": "Error: Unsupported operation.",
"translation.sccore.command.animation.animation_out_range": "You cannot perform this operation: The distance is not within %s blocks.",
"translation.sccore.command.animation.animation_resource_not_found": "Error: Resource not found, please check if there are any errors in the resource or operation.",
"translation.sccore.command.animation.animation_to_json": "The animation %s has been stored in the path on %s:",
"translation.sccore.command.animation.applied_join_message": "%S§b§l Apply for §r to join your animation. ",
"translation.sccore.command.animation.apply_join_message": "Application sent.",
"translation.sccore.command.animation.apply_success": "%s has accepted your animation application.",
"translation.sccore.command.animation.clear_animations": "Animation cleared.",
"translation.sccore.command.animation.command_run_fail": "Command run fail.",
"translation.sccore.command.animation.command_run_success": "Command run success.",
"translation.sccore.command.animation.invite_message": "Invitation sent.",
"translation.sccore.command.animation.invite_success": "%s has accepted your animation invitation.",
"translation.sccore.command.animation.invited_message": "%s§c§l invites§r you to animation: %s. ",
"translation.sccore.command.animation.list_animation_resource": "The %2$s on %1$s has : %s",
"translation.sccore.command.animation.play_animation_fail": "Fail to play animation with: %s",
"translation.sccore.command.animation.play_animation_success": "Successfully played animation on %s player(s).",
"translation.sccore.command.animation.refresh_animations": "Animation refreshed.",
"translation.sccore.command.animation.remove_animation_fail": "Fail to remove animation with: %s",
"translation.sccore.command.animation.remove_animation_success": "Successfully removed animation on %s player(s).",
"translation.sccore.command.animation.request_message": "Request sent.",
"translation.sccore.command.animation.request_success": "%s has accepted your animation request.",
"translation.sccore.command.animation.requested_message": "%s§d§l requests§r you to animation: %s. "
}

View File

@ -0,0 +1,32 @@
{
"translation.sccore.command.animation.accept_apply_success": "%s 接受了 %s 的申请。",
"translation.sccore.command.animation.accept_invite_success": "已接受邀请。",
"translation.sccore.command.animation.accept_message_click": "单击此处同意。",
"translation.sccore.command.animation.accept_request_success": "已接受请求。",
"translation.sccore.command.animation.animation_cooldown": "你不能执行该操作: 冷却中(%s秒)。",
"translation.sccore.command.animation.animation_expire": "你不能执行该操作: 已过期。",
"translation.sccore.command.animation.animation_json_path": "%s",
"translation.sccore.command.animation.animation_operation_cancelled": "异常: 操作被取消。",
"translation.sccore.command.animation.animation_operation_unsupported": "错误: 不支持这样做。",
"translation.sccore.command.animation.animation_out_range": "你不能执行该操作: 距离不在%s格以内。",
"translation.sccore.command.animation.animation_resource_not_found": "错误: 资源未找到,请检查资源或操作是否有误。",
"translation.sccore.command.animation.animation_to_json": "动画%s已经存储到%s路径",
"translation.sccore.command.animation.applied_join_message": "%s§b§l 申请§r加入动画。",
"translation.sccore.command.animation.apply_join_message": "已发送申请。",
"translation.sccore.command.animation.apply_success": "%s 接受了你的动画申请。",
"translation.sccore.command.animation.clear_animations": "动画已清除。",
"translation.sccore.command.animation.command_run_fail": "命令执行失败。",
"translation.sccore.command.animation.command_run_success": "命令执行成功。",
"translation.sccore.command.animation.invite_message": "已发送邀请。",
"translation.sccore.command.animation.invite_success": "%s 接受了你的动画邀请。",
"translation.sccore.command.animation.invited_message": "%s§c§l 邀请§r你进行动画%s。",
"translation.sccore.command.animation.list_animation_resource": "%s侧的%s有%s",
"translation.sccore.command.animation.play_animation_fail": "在这些玩家上播放动画失败:%s",
"translation.sccore.command.animation.play_animation_success": "在%s个玩家上播放动画成功。",
"translation.sccore.command.animation.refresh_animations": "动画同步状态已刷新。",
"translation.sccore.command.animation.remove_animation_fail": "在这些玩家上移除动画失败:%s",
"translation.sccore.command.animation.remove_animation_success": "在%s个玩家上移除动画成功。",
"translation.sccore.command.animation.request_message": "已发送请求。",
"translation.sccore.command.animation.request_success": "%s 接受了你的动画请求。",
"translation.sccore.command.animation.requested_message": "%s§d§l 请求§r你进行动画%s。"
}

View File

@ -0,0 +1,112 @@
{
"name": "am_lying_to_right_lying",
"author": "LostInLinearPast",
"description": "fix in 1.20.1 from CreatorGalaxy",
"emote":{
"isLoop": "false",
"returnTick": 2,
"beginTick":0,
"endTick":6,
"stopTick":2147483647,
"degrees":false,
"moves":[
{
"tick":1,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"yaw":0.0
}
},
{
"tick":6,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"yaw":-0.08066412806510925
}
},
{
"tick":1,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"roll":-0.0
}
},
{
"tick":6,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"roll":-1.568853497505188
}
},
{
"tick":1,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"pitch":1.5707963705062866
}
},
{
"tick":6,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"pitch":1.5704461336135864
}
},
{
"tick":1,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"y":-0.623153030872345
}
},
{
"tick":6,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"y":-0.4366978108882904
}
},
{
"tick":1,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"x":0.0
}
},
{
"tick":6,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"x":0.0
}
},
{
"tick":1,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"z":-0.0
}
},
{
"tick":6,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"z":-0.0
}
}
]
}
}

View File

@ -0,0 +1,64 @@
{
"name": "am_stand_to_lying",
"author": "LostInLinearPast",
"description": "fix in 1.20.1 from CreatorGalaxy",
"emote":{
"isLoop": "false",
"returnTick": 2,
"beginTick":0,
"endTick":5,
"stopTick":2147483647,
"degrees":false,
"moves":[
{
"tick":5,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"yaw":0.0
}
},
{
"tick":5,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"roll":-0.0
}
},
{
"tick":5,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"pitch":1.5707963705062866
}
},
{
"tick":5,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"y":-0.623153030872345
}
},
{
"tick":5,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"x":0.0
}
},
{
"tick":5,
"easing": "EASEINOUTQUAD",
"turn": 0,
"torso":{
"z":-0.0
}
}
]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,6 @@
{
"pack": {
"description": "SnowyCrescentCore resources",
"pack_format": 15
}
}

View File

@ -0,0 +1,20 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.linearpast.sccore.mixin",
"compatibilityLevel": "JAVA_8",
"refmap": "sccore.refmap.json",
"plugin": "com.linearpast.sccore.mixin.SCCoreMixinPlugin",
"mixins": [
"animation.MixinEntity",
"animation.client.MixinPlayerAnimationFactoryHolder"
],
"client": [
"animation.client.MixinEntity",
"animation.client.MixinHumanoidModel",
"animation.client.MixinKeyframeAnimationPlayer"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-11-28T18:32:27.7132485 Languages: zh_cn
33a56369fb517ec3b528128539e1e160666d9602 assets/sccore/lang/zh_cn.json
// 1.20.1 2026-01-12T12:57:53.3841242 Languages: zh_cn
5a0677ff9ef6fd59ce316b70b9e796c12aa2ca3a assets/sccore/lang/zh_cn.json

View File

@ -0,0 +1,3 @@
// 1.20.1 2026-01-12T13:25:00.4308564 Animations: sccore
784975521a546e0773d97b6cd53e6b38e45e6241 data/sccore/scc_animations/waltz_gentleman.anim.json
8f8fb0ecd48254b9fddacec6decffe8f9de56e23 data/sccore/scc_animations/waltz_lady.anim.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-11-28T18:32:27.715234 Languages: en_us
941682565bf7998e144253f88be175d198e43566 assets/sccore/lang/en_us.json
// 1.20.1 2026-01-12T12:57:53.3876407 Languages: en_us
cb12ddd7129449cbee2282fc61d9d50dc514d62a assets/sccore/lang/en_us.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2026-01-12T13:25:00.4276551 Animation Layer Data: sccore
183c6a30f08de7f1a086b30b922164ba302e7ad2 data/sccore/scc_animations/animation.layer.json

View File

@ -9,7 +9,7 @@
"translation.sccore.command.animation.animation_operation_cancelled": "Exception: Operation cancelled.",
"translation.sccore.command.animation.animation_operation_unsupported": "Error: Unsupported operation.",
"translation.sccore.command.animation.animation_out_range": "You cannot perform this operation: The distance is not within %s blocks.",
"translation.sccore.command.animation.animation_resource_not_found": "Error: Resource not found, please check if there are any errors in the resource or operation.",
"translation.sccore.command.animation.animation_resource_not_found": "Error: Resource not found, please check if there are any errors in the resource or operation : %s",
"translation.sccore.command.animation.animation_to_json": "The animation %s has been stored in the path on %s:",
"translation.sccore.command.animation.applied_join_message": "%S§b§l Apply for §r to join your animation. ",
"translation.sccore.command.animation.apply_join_message": "Application sent.",

View File

@ -9,7 +9,7 @@
"translation.sccore.command.animation.animation_operation_cancelled": "异常: 操作被取消。",
"translation.sccore.command.animation.animation_operation_unsupported": "错误: 不支持这样做。",
"translation.sccore.command.animation.animation_out_range": "你不能执行该操作: 距离不在%s格以内。",
"translation.sccore.command.animation.animation_resource_not_found": "错误: 资源未找到,请检查资源或操作是否有误",
"translation.sccore.command.animation.animation_resource_not_found": "错误: 资源未找到,请检查资源或操作是否有误: %s",
"translation.sccore.command.animation.animation_to_json": "动画%s已经存储到%s路径",
"translation.sccore.command.animation.applied_join_message": "%s§b§l 申请§r加入动画。",
"translation.sccore.command.animation.apply_join_message": "已发送申请。",

View File

@ -0,0 +1,6 @@
[
{
"key": "sccore:normal_layers",
"priority": 42
}
]

View File

@ -0,0 +1,25 @@
{
"camPosOffset": {
"relative": true,
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"heightModifier": 1.0,
"key": "sccore:waltz_gentleman",
"name": "Waltz-Gentleman",
"priority": 0,
"withRide": {
"componentsAnimation": [
"sccore:waltz_lady"
],
"existTick": 0,
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xRot": 0.0,
"yRot": 0.0
}
}

View File

@ -0,0 +1,20 @@
{
"camYaw": 180.0,
"heightModifier": 1.0,
"key": "sccore:waltz_lady",
"name": "Waltz-Lady",
"priority": 0,
"withRide": {
"componentsAnimation": [
"sccore:waltz_gentleman"
],
"existTick": 0,
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xRot": 0.0,
"yRot": 0.0
}
}

View File

@ -0,0 +1,172 @@
package com.linearpast.sccore.animation.data.util;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.linearpast.sccore.SnowyCrescentCore;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public abstract class SCCAnimationLayerProvider implements DataProvider {
private final DataGenerator generator;
private final String modId;
/**
* Constructor for animation layer data provider
*
* @param generator Data generator instance
* @param modId Mod ID for namespace
*/
public SCCAnimationLayerProvider(DataGenerator generator, String modId) {
this.generator = generator;
this.modId = modId;
}
@Override
public @NotNull CompletableFuture<?> run(@NotNull CachedOutput output) {
// Create layer data
Map<ResourceLocation, Integer> layers = createLayerData().build();
// Convert to JSON array
JsonArray jsonArray = createJsonArray(layers);
// Save file
Path outputPath = getOutputPath();
try {
return DataProvider.saveStable(output, jsonArray, outputPath);
} catch (Exception e) {
SnowyCrescentCore.log.error("Failed to save animation layer data", e);
return CompletableFuture.failedFuture(e);
}
}
/**
* Create layer data
* Define all animation layers and their priorities here
*
* @return Map of layer ResourceLocations to priority values Builder
*/
protected abstract LayerBuilder createLayerData();
/**
* Convert layer data to JSON array format compatible with AnimLayerJson
*
* @param layers Layer data map
* @return JSON array representation
*/
private JsonArray createJsonArray(Map<ResourceLocation, Integer> layers) {
return getJsonElements(layers);
}
/**
* Get output path for layer configuration file
* Path: data/<modId>/scc_animations/animation.layer.json
*
* @return Full output path
*/
private Path getOutputPath() {
return generator.getPackOutput().getOutputFolder()
.resolve("data")
.resolve(modId)
.resolve("scc_animations")
.resolve("animation.layer.json");
}
@Override
public @NotNull String getName() {
return "Animation Layer Data: " + modId;
}
/**
* Helper class for building animation layer data
* Provides fluent API for creating layer configurations
*/
public static class LayerBuilder {
private final Map<ResourceLocation, Integer> layers = new LinkedHashMap<>();
/**
* Create layer builder
*
*/
public static LayerBuilder create() {
return new LayerBuilder();
}
/**
* Constructor for layer builder
*
*/
private LayerBuilder() {}
/**
* Add a base layer with specified priority
*
* @param name Layer name
* @param priority Priority value (lower numbers = higher priority)
* @return LayerBuilder instance for chaining
*/
public LayerBuilder addBaseLayer(ResourceLocation name, int priority) {
layers.put(name, priority);
return this;
}
/**
* Add a custom layer with specified priority
*
* @param name Layer name
* @param priority Priority value (lower numbers = higher priority)
* @return LayerBuilder instance for chaining
*/
public LayerBuilder addCustomLayer(ResourceLocation name, int priority) {
layers.put(name, priority);
return this;
}
/**
* Build the layer map
*
* @return Unmodifiable map of layers
*/
public Map<ResourceLocation, Integer> build() {
return new LinkedHashMap<>(layers);
}
/**
* Build JSON array representation
*
* @return JSON array of layers
*/
public JsonArray buildJsonArray() {
return getJsonElements(layers);
}
}
/**
* Convert layer map to JSON array
* Layers are sorted by priority (ascending)
*
* @param layers Layer data map
* @return JSON array representation
*/
@NotNull
static JsonArray getJsonElements(Map<ResourceLocation, Integer> layers) {
JsonArray jsonArray = new JsonArray();
layers.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEachOrdered(entry -> {
JsonObject layerObject = new JsonObject();
layerObject.addProperty("key", entry.getKey().toString());
layerObject.addProperty("priority", entry.getValue());
jsonArray.add(layerObject);
});
return jsonArray;
}
}

View File

@ -0,0 +1,170 @@
/*
* *
* * Copyright (c) 2026 R3944Realms. All rights reserved.
* *
* * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
* * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
* *
* * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
*
*/
package com.linearpast.sccore.animation.data.util;
import com.google.gson.JsonObject;
import com.linearpast.sccore.animation.data.GenericAnimationData;
import com.linearpast.sccore.animation.data.Ride;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* Abstract base class for animation data providers
* Handles generation of animation JSON files in the scc_animations directory
*/
public abstract class SCCAnimationProvider implements DataProvider {
private final DataGenerator generator;
private final String modId;
/**
* Constructor for animation provider
*
* @param generator Data generator instance
* @param modId Mod ID for namespace
*/
protected SCCAnimationProvider(DataGenerator generator, String modId) {
this.generator = generator;
this.modId = modId;
}
@Override
public @NotNull CompletableFuture<?> run(@NotNull CachedOutput output) {
List<CompletableFuture<?>> futures = new ArrayList<>();
Path outputFolder = generator.getPackOutput().getOutputFolder();
// Register animations and save them as JSON files
registerAnimations(animation -> {
ResourceLocation key = animation.getKey();
Path path = outputFolder
.resolve("data")
.resolve(key.getNamespace())
.resolve("scc_animations")
.resolve(key.getPath() + ".anim.json");
JsonObject json = convertToJson(animation);
futures.add(DataProvider.saveStable(output, json, path));
});
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
@Override
public @NotNull String getName() {
return "Animations: " + modId;
}
/**
* Register animations to be generated
* Implement this method to define which animations to create
*
* @param consumer Consumer that accepts GenericAnimationData instances
*/
protected abstract void registerAnimations(Consumer<GenericAnimationData> consumer);
/**
* Convert GenericAnimationData to JSON object
*
* @param animation Animation data to convert
* @return JSON object representation
*/
private JsonObject convertToJson(GenericAnimationData animation) {
JsonObject json = getJsonObject(animation);
// Add camera position offset if present
Vec3 camOffset = animation.getCamPosOffset();
if (!camOffset.equals(Vec3.ZERO) || animation.isCamPosOffsetRelative()) {
JsonObject camOffsetJson = new JsonObject();
camOffsetJson.addProperty("x", camOffset.x);
camOffsetJson.addProperty("y", camOffset.y);
camOffsetJson.addProperty("z", camOffset.z);
camOffsetJson.addProperty("relative", animation.isCamPosOffsetRelative());
json.add("camPosOffset", camOffsetJson);
}
// Add ride configuration if present
Ride ride = animation.getRide();
if (ride != null) {
JsonObject rideJson = new JsonObject();
JsonObject offsetJson = new JsonObject();
Vec3 offset = ride.getOffset();
offsetJson.addProperty("x", offset.x);
offsetJson.addProperty("y", offset.y);
offsetJson.addProperty("z", offset.z);
rideJson.add("offset", offsetJson);
rideJson.addProperty("xRot", ride.getXRot());
rideJson.addProperty("yRot", ride.getYRot());
rideJson.addProperty("existTick", ride.getExistTick());
if (!ride.getComponentAnimations().isEmpty()) {
com.google.gson.JsonArray componentsArray = new com.google.gson.JsonArray();
ride.getComponentAnimations().forEach(component ->
componentsArray.add(component.toString())
);
rideJson.add("componentsAnimation", componentsArray);
}
json.add("withRide", rideJson);
}
return json;
}
/**
* Create base JSON object with common animation properties
*
* @param animation Animation data
* @return Base JSON object
*/
private static @NotNull JsonObject getJsonObject(GenericAnimationData animation) {
JsonObject json = new JsonObject();
ResourceLocation key = animation.getKey();
json.addProperty("key", key.toString());
if (animation.getName() != null) {
json.addProperty("name", animation.getName());
}
if (animation.getLyingType() != null) {
json.addProperty("lyingType", animation.getLyingType().getName());
}
json.addProperty("heightModifier", animation.getHeightModifier());
json.addProperty("priority", animation.getCamComputePriority());
if (animation.getCamPitch() != 0) {
json.addProperty("camPitch", animation.getCamPitch());
}
if (animation.getCamRoll() != 0) {
json.addProperty("camRoll", animation.getCamRoll());
}
if (animation.getCamYaw() != 0) {
json.addProperty("camYaw", animation.getCamYaw());
}
return json;
}
}

View File

@ -0,0 +1,17 @@
package com.linearpast.sccore.animation.event.server;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.register.AnimationRegistry;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = SnowyCrescentCore.MODID, value = Dist.DEDICATED_SERVER, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ResourceReloadListener {
@SubscribeEvent
public static void init (AddReloadListenerEvent event) {
event.addListener(AnimationRegistry.AnimationDataManager.INSTANCE);
event.addListener(AnimationRegistry.LayerDataManager.INSTANCE);
}
}

View File

@ -1,6 +1,11 @@
package com.linearpast.sccore.animation.register;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.RawAnimationDataCapability;
@ -33,15 +38,22 @@ import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@ -74,74 +86,266 @@ public class AnimationRegistry {
layers.putAll(layerMap);
}
@SubscribeEvent
public static void onAddReloadListeners(AddReloadListenerEvent event) {
event.addListener(AnimationDataManager.INSTANCE);
event.addListener(LayerDataManager.INSTANCE);
}
@SubscribeEvent
public static void onServerStarted(ServerStartedEvent event) {
Path dataPackPath = event.getServer().getWorldPath(LevelResource.DATAPACK_DIR);
// Load legacy animations from datapacks
loadLegacyDataPackAnimations(event.getServer());
// Load animations from registration events
loadFromRegistrationEvents();
SnowyCrescentCore.log.info("Animation loading completed. Total animations: {}, Total layers: {}",
animations.size(), layers.size());
}
/**
* Load legacy datapack animations from world/datapacks/animation directory
*/
private static void loadLegacyDataPackAnimations(MinecraftServer server) {
Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR);
Path animationPath = dataPackPath.resolve("animation");
if (!Files.exists(animationPath)) {
try {
Files.createDirectories(animationPath);
} catch (IOException e) { return; }
}
FileUtils.safeUnzip(dataPackPath.resolve("animation.zip").toString(), animationPath.toAbsolutePath().toString());
Set<Path> animZipPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".anim.zip")
);
Set<Path> layerZipPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".layer.zip")
);
for (Path zipPath : animZipPaths) {
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
}
for (Path zipPath : layerZipPaths) {
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
}
Set<Path> animPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".anim.json")
);
Set<Path> layerPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.getFileName().toString().equals("animation.layer.json")
);
Set<GenericAnimationData> animationsSet = new HashSet<>();
Map<ResourceLocation, Integer> layersMap = new HashMap<>();
for (Path path : animPaths) {
try {
AnimJson.Reader reader = AnimJson.Reader.stream(path);
GenericAnimationData anim = reader.parse();
animationsSet.add(anim);
} catch (Exception ignored) {
SnowyCrescentCore.log.error("Failed to parse animation JSON: {}", path.toString());
}
}
for (Path path : layerPaths) {
try {
AnimLayerJson.Reader reader = AnimLayerJson.Reader.stream(path);
Map<ResourceLocation, Integer> parse = reader.parse();
layersMap.putAll(parse);
} catch (Exception ignored) {
SnowyCrescentCore.log.error("Failed to parse layer JSON: {}", path.toString());
} catch (IOException e) {
SnowyCrescentCore.log.error("Failed to create legacy animation directory", e);
return;
}
}
animations.clear();
try {
// Handle zip files
if (Files.exists(dataPackPath.resolve("animation.zip"))) {
FileUtils.safeUnzip(dataPackPath.resolve("animation.zip").toString(),
animationPath.toAbsolutePath().toString());
}
Set<Path> animZipPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".anim.zip")
);
Set<Path> layerZipPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".layer.zip")
);
for (Path zipPath : animZipPaths) {
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
}
for (Path zipPath : layerZipPaths) {
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
}
// Load animation JSON files
Set<Path> animPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.toString().endsWith(".anim.json")
);
Set<Path> layerPaths = FileUtils.getAllFile(
dataPackPath.resolve("animation"),
path -> path.getFileName().toString().equals("animation.layer.json")
);
for (Path path : animPaths) {
try {
AnimJson.Reader reader = AnimJson.Reader.stream(path);
GenericAnimationData anim = reader.parse();
animations.put(anim.getKey(), anim);
SnowyCrescentCore.log.info("Loaded legacy animation: {} -> {}",
anim.getKey(), anim.getName());
} catch (Exception e) {
SnowyCrescentCore.log.error("Failed to parse legacy animation JSON: {}", path, e);
}
}
for (Path path : layerPaths) {
try {
AnimLayerJson.Reader reader = AnimLayerJson.Reader.stream(path);
Map<ResourceLocation, Integer> parse = reader.parse();
layers.putAll(parse);
SnowyCrescentCore.log.info("Loaded {} legacy layer configurations from {}",
parse.size(), path);
} catch (Exception e) {
SnowyCrescentCore.log.error("Failed to parse legacy layer JSON: {}", path, e);
}
}
} catch (Exception e) {
SnowyCrescentCore.log.error("Error loading legacy animations", e);
}
}
/**
* Load animations from registration events
*/
private static void loadFromRegistrationEvents() {
AnimationRegisterEvent.Animation animationRegisterEvent = new AnimationRegisterEvent.Animation();
MinecraftForge.EVENT_BUS.post(animationRegisterEvent);
Map<ResourceLocation, GenericAnimationData> animationMap = animationRegisterEvent.getAnimations();
animations.putAll(animationMap);
animations.putAll(animationsSet.stream().collect(Collectors.toMap(GenericAnimationData::getKey, animation -> animation)));
layers.clear();
AnimationRegisterEvent.Layer layerRegisterEvent = new AnimationRegisterEvent.Layer();
MinecraftForge.EVENT_BUS.post(layerRegisterEvent);
Map<ResourceLocation, Integer> layerMap = layerRegisterEvent.getLayers();
layers.putAll(layerMap);
layers.putAll(layersMap);
SnowyCrescentCore.log.info("Loaded {} animations and {} layers from registration events",
animationMap.size(), layerMap.size());
}
/**
* Animation data manager using SimpleJsonResourceReloadListener
*/
public static class AnimationDataManager extends SimpleJsonResourceReloadListener {
public static final AnimationDataManager INSTANCE = new AnimationDataManager();
private AnimationDataManager() {
super(new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.create(), "scc_animations");
}
@Override
protected void apply(@Nonnull Map<ResourceLocation, JsonElement> resources,
@Nonnull ResourceManager resourceManager,
@Nonnull ProfilerFiller profiler) {
SnowyCrescentCore.log.info("Loading animations from data packs...");
Map<ResourceLocation, JsonElement> sorted = new LinkedHashMap<>();
// Sort resources by priority
resourceManager.listPacks().forEach(packResources -> {
Set<String> namespaces = packResources.getNamespaces(PackType.SERVER_DATA);
namespaces.forEach(namespace ->
packResources.listResources(PackType.SERVER_DATA, namespace, "scc_animations",
(resourceLocation, inputStreamIoSupplier) -> {
String path = resourceLocation.getPath();
if (path.endsWith(".anim.json")) {
ResourceLocation rl = new ResourceLocation(namespace,
path.substring("scc_animations/".length(), path.length() - ".json".length()));
JsonElement el = resources.get(rl);
if (el != null) {
rl = new ResourceLocation(namespace,
path.substring("scc_animations/".length(), path.length() - ".anim.json".length()));
sorted.put(rl, el);
}
}
}
)
);
});
int loadedCount = 0;
for (Map.Entry<ResourceLocation, JsonElement> entry : sorted.entrySet()) {
ResourceLocation animKey = entry.getKey();
try {
JsonObject json = GsonHelper.convertToJsonObject(entry.getValue(), "animation");
// Parse animation using existing AnimJson.Reader
AnimJson.Reader animReader = AnimJson.Reader.stream(json);
GenericAnimationData anim = animReader.parse();
// Ensure the key matches
if (!anim.getKey().equals(animKey)) {
SnowyCrescentCore.log.warn("Animation key mismatch: file={}, expected={}, actual={}",
entry.getKey(), animKey, anim.getKey());
anim = anim.withName(animKey.getPath()); // Create a copy with correct key if possible
}
animations.put(animKey, anim);
loadedCount++;
SnowyCrescentCore.log.debug("Loaded animation: {} -> {}", animKey, anim.getName());
} catch (IllegalArgumentException | JsonParseException e) {
SnowyCrescentCore.log.error("Parsing error loading animation {}", animKey, e);
}
}
SnowyCrescentCore.log.info("Loaded {} animations from data packs", loadedCount);
}
}
/**
* Layer data manager using SimpleJsonResourceReloadListener
*/
public static class LayerDataManager extends SimpleJsonResourceReloadListener {
public static final LayerDataManager INSTANCE = new LayerDataManager();
private LayerDataManager() {
super(new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.create(), "scc_animations");
}
@Override
protected void apply(@Nonnull Map<ResourceLocation, JsonElement> resources,
@Nonnull ResourceManager resourceManager,
@Nonnull ProfilerFiller profiler) {
SnowyCrescentCore.log.info("Loading layer configurations from data packs...");
Map<ResourceLocation, JsonElement> sorted = new LinkedHashMap<>();
// Sort resources by priority
resourceManager.listPacks().forEach(packResources -> {
Set<String> namespaces = packResources.getNamespaces(PackType.SERVER_DATA);
namespaces.forEach(namespace ->
packResources.listResources(PackType.SERVER_DATA, namespace, "scc_animations",
(resourceLocation, inputStreamIoSupplier) -> {
String path = resourceLocation.getPath();
if (path.endsWith("animation.layer.json")) {
ResourceLocation rl = new ResourceLocation(namespace, "animation.layer");
JsonElement el = resources.get(rl);
if (el != null) {
rl = new ResourceLocation(namespace, "animation_layer");
sorted.put(rl, el);
}
}
}
)
);
});
int loadedCount = 0;
for (Map.Entry<ResourceLocation, JsonElement> entry : sorted.entrySet()) {
try {
JsonElement json = entry.getValue();
AnimLayerJson.Reader layerReader = AnimLayerJson.Reader.stream(json);
Map<ResourceLocation, Integer> parsedLayers = layerReader.parse();
// Merge layer configurations
parsedLayers.forEach((layerKey, priority) -> {
if (layers.containsKey(layerKey)) {
SnowyCrescentCore.log.debug("Overriding layer {} with priority {}", layerKey, priority);
}
layers.put(layerKey, priority);
});
loadedCount += parsedLayers.size();
SnowyCrescentCore.log.debug("Loaded {} layer configurations from {}",
parsedLayers.size(), entry.getKey().getNamespace());
} catch (IllegalArgumentException | JsonParseException e) {
SnowyCrescentCore.log.error("Parsing error loading layer configuration", e);
}
}
SnowyCrescentCore.log.info("Loaded {} layer configurations from data packs", loadedCount);
}
}
@SubscribeEvent
@ -149,29 +353,64 @@ public class AnimationRegistry {
if (event.getEntity() instanceof ServerPlayer serverPlayer) {
MinecraftServer server = serverPlayer.getServer();
if(server == null) return;
Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR);
Path animationPath = dataPackPath.resolve("animation");
if (!Files.exists(animationPath)) {
try {Files.createDirectories(animationPath);}
catch (IOException e) { return; }
}
ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.ANIM_CACHE_CLEAR), serverPlayer);
for (GenericAnimationData value : animations.values()) {
JsonElement json = AnimJson.Writer.stream(value).toJson();
String string = json.toString();
ModChannel.sendToPlayer(new AnimationJsonPacket(string, false), serverPlayer);
}
ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.ANIM_REGISTER), serverPlayer);
ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.LAYER_CACHE_CLEAR), serverPlayer);
Map<String, JsonElement> jsonElementMap = AnimLayerJson.Writer.stream(animationPath).allToJson();
jsonElementMap.forEach((key, value) ->
ModChannel.sendToPlayer(new AnimationJsonPacket(value.toString(), true), serverPlayer)
);
ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.LAYER_REGISTER), serverPlayer);
}
// Send animations to client
sendAnimationsToClient(serverPlayer);
}
}
/**
* Send all animations and layers to the client
*/
private static void sendAnimationsToClient(ServerPlayer player) {
SnowyCrescentCore.log.debug("Sending animations to player: {}", player.getName().getString());
// Clear client cache first
ModChannel.sendToPlayer(new AnimationClientStatusPacket(
AnimationClientStatusPacket.Status.ANIM_CACHE_CLEAR), player);
// Send all animations
for (GenericAnimationData anim : animations.values()) {
JsonElement json = AnimJson.Writer.stream(anim).toJson();
String jsonString = json.toString();
ModChannel.sendToPlayer(new AnimationJsonPacket(jsonString, false), player);
}
// Register animations on client
ModChannel.sendToPlayer(new AnimationClientStatusPacket(
AnimationClientStatusPacket.Status.ANIM_REGISTER), player);
// Clear layer cache
ModChannel.sendToPlayer(new AnimationClientStatusPacket(
AnimationClientStatusPacket.Status.LAYER_CACHE_CLEAR), player);
// Send layer configurations
JsonElement layerJson = convertLayersToJson(layers);
ModChannel.sendToPlayer(new AnimationJsonPacket(layerJson.toString(), true), player);
// Register layers on client
ModChannel.sendToPlayer(new AnimationClientStatusPacket(
AnimationClientStatusPacket.Status.LAYER_REGISTER), player);
SnowyCrescentCore.log.debug("Sent {} animations and {} layers to player {}",
animations.size(), layers.size(), player.getName().getString());
}
/**
* Convert layers map to JSON
*/
private static JsonElement convertLayersToJson(Map<ResourceLocation, Integer> layers) {
JsonArray jsonArray = new JsonArray();
for (Map.Entry<ResourceLocation, Integer> entry : layers.entrySet()) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("key", entry.getKey().toString());
jsonObject.addProperty("priority", entry.getValue());
jsonArray.add(jsonObject);
}
return jsonArray;
}
@OnlyIn(Dist.CLIENT)
public static class ClientCache {
@ -188,7 +427,6 @@ public class AnimationRegistry {
layersCache.put(location, priority);
}
@SuppressWarnings({"JavaReflectionMemberAccess", "UnstableApiUsage", "unchecked"})
public static void animationStatusUpdate(AnimationClientStatusPacket.Status status) {
switch (status) {
case ANIM_CACHE_CLEAR -> animationsCache.clear();
@ -206,94 +444,155 @@ public class AnimationRegistry {
registerLayers(layersCache);
layersCache.forEach((key, value) -> PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
key, value, player -> {
if(Minecraft.getInstance().player == null) return ClientCache.registerPlayerAnimation(player);
Map<ResourceLocation, IAnimation> animationMap = modifierLayers.getOrDefault(player.getUUID(), new HashMap<>());
if(animationMap.containsKey(key)) return animationMap.get(key);
if (Minecraft.getInstance().player == null) {
return ClientCache.registerPlayerAnimation(player);
}
Map<ResourceLocation, IAnimation> animationMap =
modifierLayers.getOrDefault(player.getUUID(), new HashMap<>());
if (animationMap.containsKey(key)) {
return animationMap.get(key);
}
IAnimation iAnimation = ClientCache.registerPlayerAnimation(player);
animationMap.put(key, iAnimation);
modifierLayers.put(player.getUUID(), animationMap);
return iAnimation;
})
);
// Update existing players' animation stacks
ClientLevel level = Minecraft.getInstance().level;
if(level == null) {
SnowyCrescentCore.log.error("{} : Level is null", ClientCache.class.getName());
if (level == null) {
SnowyCrescentCore.log.error("Level is null, cannot update animation layers");
return;
}
try {
for (AbstractClientPlayer player : level.players()) {
try {
Class<?> playerClass = Player.class;
Field animationStackField = playerClass.getDeclaredField("animationStack");
animationStackField.setAccessible(true);
Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack");
createAnimationStack.setAccessible(true);
AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player);
AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player);
Field layersField = AnimationStack.class.getDeclaredField("layers");
layersField.setAccessible(true);
ArrayList<Pair<Integer, IAnimation>> oldArrayList = new ArrayList<>((ArrayList<Pair<Integer, IAnimation>>) layersField.get(oldAnimationStack));
ArrayList<Pair<Integer, IAnimation>> newArrayList = new ArrayList<>((ArrayList<Pair<Integer, IAnimation>>) layersField.get(newAnimationStack));
ArrayList<Pair<Integer, IAnimation>> result = new ArrayList<>();
for (Pair<Integer, IAnimation> oldAnimationPair : List.copyOf(oldArrayList)) {
for (Pair<Integer, IAnimation> newAnimationPair : List.copyOf(newArrayList)) {
if(Objects.equals(oldAnimationPair.getLeft(), newAnimationPair.getLeft())) {
KeyframeAnimation oldData = Optional.ofNullable((KeyframeAnimationPlayer) ((ModifierLayer<?>) oldAnimationPair.getRight()).getAnimation())
.map(KeyframeAnimationPlayer::getData).orElse(null);
KeyframeAnimation newData = Optional.ofNullable((KeyframeAnimationPlayer) ((ModifierLayer<?>) newAnimationPair.getRight()).getAnimation())
.map(KeyframeAnimationPlayer::getData).orElse(null);
if(Objects.equals(oldData, newData)) oldArrayList.remove(oldAnimationPair);
}
}
}
result.addAll(oldArrayList);
result.addAll(newArrayList);
layersField.set(newAnimationStack, result);
animationStackField.set(player, newAnimationStack);
Field animationApplierField = playerClass.getDeclaredField("animationApplier");
animationApplierField.setAccessible(true);
animationApplierField.set(player, new AnimationApplier(newAnimationStack));
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if(data == null) continue;
RawAnimationDataCapability rawData = RawAnimationDataCapability.getCapability(player).orElse(null);
if(rawData == null) continue;
Map<ResourceLocation, ResourceLocation> dataAnimations = new HashMap<>();
dataAnimations.putAll(data.getAnimations());
dataAnimations.putAll(rawData.getAnimations());
ResourceLocation riderAnimLayer = data.getRiderAnimLayer();
if(riderAnimLayer != null) {
dataAnimations.put(riderAnimLayer, data.getRiderAnimation());
}
for (ResourceLocation location : dataAnimations.keySet()) {
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
.getPlayerAssociatedData(player).get(location);
if(modifierLayer == null) continue;
KeyframeAnimation keyframeAnimation;
ResourceLocation animationLocation = dataAnimations.get(location);
GenericAnimationData anim = animations.get(animationLocation);
if(anim == null) {
RawAnimationData rawAnim = RawAnimationService.INSTANCE.getAnimation(animationLocation);
if(rawAnim == null) return;
keyframeAnimation = rawAnim.getAnimation();
} else keyframeAnimation = anim.getAnimation();
if(keyframeAnimation == null) continue;
modifierLayer.replaceAnimationWithFade(
AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE),
new KeyframeAnimationPlayer(keyframeAnimation)
);
}
}catch (Exception e){
SnowyCrescentCore.log.error("Failed to register on {} animation layer: {}", player, e.getMessage(), e);
}
updatePlayerAnimationStack(player);
}
} catch (Exception ignored) {}
} catch (Exception e) {
SnowyCrescentCore.log.error("Failed to update player animation layers", e);
}
}
}
}
/**
* Update a player's animation stack with new layers
*/
@SuppressWarnings({"unchecked", "JavaReflectionMemberAccess"})
private static void updatePlayerAnimationStack(AbstractClientPlayer player) {
try {
Class<?> playerClass = Player.class;
Field animationStackField = playerClass.getDeclaredField("animationStack");
animationStackField.setAccessible(true);
Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack");
createAnimationStack.setAccessible(true);
AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player);
AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player);
Field layersField = AnimationStack.class.getDeclaredField("layers");
layersField.setAccessible(true);
ArrayList<Pair<Integer, IAnimation>> oldArrayList =
new ArrayList<>((ArrayList<Pair<Integer, IAnimation>>) layersField.get(oldAnimationStack));
ArrayList<Pair<Integer, IAnimation>> newArrayList =
new ArrayList<>((ArrayList<Pair<Integer, IAnimation>>) layersField.get(newAnimationStack));
// Merge layers, keeping unique ones
ArrayList<Pair<Integer, IAnimation>> result = new ArrayList<>();
// Add old layers that aren't replaced by new ones
for (Pair<Integer, IAnimation> oldPair : oldArrayList) {
boolean isReplaced = false;
for (Pair<Integer, IAnimation> newPair : newArrayList) {
if (Objects.equals(oldPair.getLeft(), newPair.getLeft())) {
isReplaced = true;
break;
}
}
if (!isReplaced) {
result.add(oldPair);
}
}
// Add all new layers
result.addAll(newArrayList);
// Set the merged layers
layersField.set(newAnimationStack, result);
animationStackField.set(player, newAnimationStack);
// Update animation applier
Field animationApplierField = playerClass.getDeclaredField("animationApplier");
animationApplierField.setAccessible(true);
//noinspection UnstableApiUsage
animationApplierField.set(player, new AnimationApplier(newAnimationStack));
// Restore any playing animations
restorePlayingAnimations(player);
} catch (Exception e) {
SnowyCrescentCore.log.error("Failed to update animation stack for player: {}", player, e);
}
}
/**
* Restore animations that were playing before the update
*/
private static void restorePlayingAnimations(AbstractClientPlayer player) {
try {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if (data == null) return;
RawAnimationDataCapability rawData = RawAnimationDataCapability.getCapability(player).orElse(null);
if (rawData == null) return;
Map<ResourceLocation, ResourceLocation> dataAnimations = new HashMap<>();
dataAnimations.putAll(data.getAnimations());
dataAnimations.putAll(rawData.getAnimations());
ResourceLocation riderAnimLayer = data.getRiderAnimLayer();
if (riderAnimLayer != null && data.getRiderAnimation() != null) {
dataAnimations.put(riderAnimLayer, data.getRiderAnimation());
}
for (Map.Entry<ResourceLocation, ResourceLocation> entry : dataAnimations.entrySet()) {
ResourceLocation layerKey = entry.getKey();
ResourceLocation animKey = entry.getValue();
@SuppressWarnings("unchecked")
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>)
PlayerAnimationAccess.getPlayerAssociatedData(player).get(layerKey);
if (modifierLayer == null) continue;
KeyframeAnimation keyframeAnimation;
GenericAnimationData anim = animations.get(animKey);
if (anim == null) {
RawAnimationData rawAnim = RawAnimationService.INSTANCE.getAnimation(animKey);
if (rawAnim == null) continue;
keyframeAnimation = rawAnim.getAnimation();
} else {
keyframeAnimation = anim.getAnimation();
}
if (keyframeAnimation == null) continue;
modifierLayer.replaceAnimationWithFade(
AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE),
new KeyframeAnimationPlayer(keyframeAnimation)
);
}
} catch (Exception e) {
SnowyCrescentCore.log.error("Failed to restore playing animations for player: {}", player, e);
}
}
private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) {
return new ModifierLayer<>();
}
}
}
}

View File

@ -1,6 +1,8 @@
package com.linearpast.sccore.core.datagen;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.core.datagen.provider.ModAnimationLayerProvider;
import com.linearpast.sccore.core.datagen.provider.ModAnimationProvider;
import com.linearpast.sccore.core.datagen.provider.ModLangProvider;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.DataGenerator;
@ -24,5 +26,7 @@ public class DataGenEvent {
generator.addProvider(event.includeClient(), new ModLangProvider(packOutput, ModLangProvider.Lang.EN_US));
generator.addProvider(event.includeClient(), new ModLangProvider(packOutput, ModLangProvider.Lang.ZH_CN));
generator.addProvider(true, new ModAnimationProvider(generator));
generator.addProvider(true, new ModAnimationLayerProvider(generator));
}
}

View File

@ -0,0 +1,20 @@
package com.linearpast.sccore.core.datagen.provider;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.data.util.SCCAnimationLayerProvider;
import com.linearpast.sccore.example.animation.ModAnimation;
import net.minecraft.data.DataGenerator;
public class ModAnimationLayerProvider extends SCCAnimationLayerProvider {
public ModAnimationLayerProvider(DataGenerator generator) {
super(generator, SnowyCrescentCore.MODID);
}
@Override
protected LayerBuilder createLayerData() {
return LayerBuilder.create()
.addCustomLayer(ModAnimation.normalLayers, 42);
}
}

View File

@ -0,0 +1,39 @@
package com.linearpast.sccore.core.datagen.provider;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.data.GenericAnimationData;
import com.linearpast.sccore.animation.data.Ride;
import com.linearpast.sccore.animation.data.util.SCCAnimationProvider;
import net.minecraft.data.DataGenerator;
import net.minecraft.world.phys.Vec3;
import java.util.function.Consumer;
import static com.linearpast.sccore.example.animation.ModAnimation.WaltzGentleman;
import static com.linearpast.sccore.example.animation.ModAnimation.WaltzLady;
public class ModAnimationProvider extends SCCAnimationProvider {
public ModAnimationProvider(DataGenerator generator) {
super(generator, SnowyCrescentCore.MODID);
}
@Override
protected void registerAnimations(Consumer<GenericAnimationData> consumer) {
{
GenericAnimationData waltzGentleman = (GenericAnimationData) GenericAnimationData
.create(WaltzGentleman)
.withName("Waltz-Gentleman")
.addCamPosOffset(new Vec3(0.0, 0.0, 1.0))
.withCamPosOffsetRelative(true)
.withRide(Ride.create().addComponentAnimation(WaltzLady));
GenericAnimationData waltzLady = (GenericAnimationData) GenericAnimationData
.create(WaltzLady)
.withName("Waltz-Lady")
.withCamYaw(180)
.withRide(Ride.create().addComponentAnimation(WaltzGentleman));
consumer.accept(waltzGentleman);
consumer.accept(waltzLady);
}
}
}

View File

@ -38,7 +38,7 @@ public class ModAnimation {
* @param event event
*/
public static void onLayerRegister(AnimationRegisterEvent.Layer event) {
event.registerLayer(normalLayers, 42);
// event.registerLayer(normalLayers, 42);
}
/**
@ -54,23 +54,23 @@ public class ModAnimation {
// GenericAnimationData amSTL = GenericAnimationData.create(AmStandToLying)
// .withName("Stand-to-Lying")
// .withLyingType(GenericAnimationData.LyingType.FRONT);
GenericAnimationData waltzGentleman = (GenericAnimationData) GenericAnimationData
.create(WaltzGentleman)
.withName("Waltz-Gentleman")
.addCamPosOffset(new Vec3(0.0,0.0,1.0))
.withCamPosOffsetRelative(true)
.withRide(Ride.create().addComponentAnimation(WaltzLady));
GenericAnimationData waltzLady = (GenericAnimationData) GenericAnimationData
.create(WaltzLady)
.withName("Waltz-Lady")
.withCamYaw(180)
.withRide(Ride.create().addComponentAnimation(WaltzGentleman));
// GenericAnimationData waltzGentleman = (GenericAnimationData) GenericAnimationData
// .create(WaltzGentleman)
// .withName("Waltz-Gentleman")
// .addCamPosOffset(new Vec3(0.0,0.0,1.0))
// .withCamPosOffsetRelative(true)
// .withRide(Ride.create().addComponentAnimation(WaltzLady));
// GenericAnimationData waltzLady = (GenericAnimationData) GenericAnimationData
// .create(WaltzLady)
// .withName("Waltz-Lady")
// .withCamYaw(180)
// .withRide(Ride.create().addComponentAnimation(WaltzGentleman));
//You can use it to invite an Animation
// event.registerAnimation(AmLyingToRightLying, amLTRL);
// event.registerAnimation(AmStandToLying, amSTL);
event.registerAnimation(WaltzGentleman, waltzGentleman);
event.registerAnimation(WaltzLady, waltzLady);
// event.registerAnimation(WaltzGentleman, waltzGentleman);
// event.registerAnimation(WaltzLady, waltzLady);
}
public static void onRawAnimationRegister(AnimationRegisterEvent.RawAnimation event) {