完善了ssc数据驱动加载的逻辑
放入data/<mod_id>/ssc_animations(避免与其它动画模组路径冲突) 将示例中的硬编码注册改为从数据json中读取
This commit is contained in:
parent
667a786c77
commit
ab8373d62e
|
|
@ -0,0 +1,2 @@
|
||||||
|
// 1.20.1 2025-11-28T18:32:27.7132485 Languages: zh_cn
|
||||||
|
33a56369fb517ec3b528128539e1e160666d9602 assets/sccore/lang/zh_cn.json
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
// 1.20.1 2025-11-28T18:32:27.715234 Languages: en_us
|
||||||
|
941682565bf7998e144253f88be175d198e43566 assets/sccore/lang/en_us.json
|
||||||
1
out/production/resources/META-INF/accesstransformer.cfg
Normal file
1
out/production/resources/META-INF/accesstransformer.cfg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
public net.minecraft.client.Camera f_90552_ # position
|
||||||
42
out/production/resources/META-INF/mods.toml
Normal file
42
out/production/resources/META-INF/mods.toml
Normal 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"
|
||||||
32
out/production/resources/assets/sccore/lang/en_us.json
Normal file
32
out/production/resources/assets/sccore/lang/en_us.json
Normal 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. "
|
||||||
|
}
|
||||||
32
out/production/resources/assets/sccore/lang/zh_cn.json
Normal file
32
out/production/resources/assets/sccore/lang/zh_cn.json
Normal 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。"
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
BIN
out/production/resources/logo.png
Normal file
BIN
out/production/resources/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
6
out/production/resources/pack.mcmeta
Normal file
6
out/production/resources/pack.mcmeta
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"description": "SnowyCrescentCore resources",
|
||||||
|
"pack_format": 15
|
||||||
|
}
|
||||||
|
}
|
||||||
20
out/production/resources/sccore.mixins.json
Normal file
20
out/production/resources/sccore.mixins.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
// 1.20.1 2025-11-28T18:32:27.7132485 Languages: zh_cn
|
// 1.20.1 2026-01-12T12:57:53.3841242 Languages: zh_cn
|
||||||
33a56369fb517ec3b528128539e1e160666d9602 assets/sccore/lang/zh_cn.json
|
5a0677ff9ef6fd59ce316b70b9e796c12aa2ca3a assets/sccore/lang/zh_cn.json
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
// 1.20.1 2025-11-28T18:32:27.715234 Languages: en_us
|
// 1.20.1 2026-01-12T12:57:53.3876407 Languages: en_us
|
||||||
941682565bf7998e144253f88be175d198e43566 assets/sccore/lang/en_us.json
|
cb12ddd7129449cbee2282fc61d9d50dc514d62a assets/sccore/lang/en_us.json
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"translation.sccore.command.animation.animation_operation_cancelled": "Exception: Operation cancelled.",
|
"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_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_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.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.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_join_message": "Application sent.",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"translation.sccore.command.animation.animation_operation_cancelled": "异常: 操作被取消。",
|
"translation.sccore.command.animation.animation_operation_cancelled": "异常: 操作被取消。",
|
||||||
"translation.sccore.command.animation.animation_operation_unsupported": "错误: 不支持这样做。",
|
"translation.sccore.command.animation.animation_operation_unsupported": "错误: 不支持这样做。",
|
||||||
"translation.sccore.command.animation.animation_out_range": "你不能执行该操作: 距离不在%s格以内。",
|
"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.animation_to_json": "动画%s已经存储到%s路径:",
|
||||||
"translation.sccore.command.animation.applied_join_message": "%s§b§l 申请§r加入动画。",
|
"translation.sccore.command.animation.applied_join_message": "%s§b§l 申请§r加入动画。",
|
||||||
"translation.sccore.command.animation.apply_join_message": "已发送申请。",
|
"translation.sccore.command.animation.apply_join_message": "已发送申请。",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "sccore:normal_layers",
|
||||||
|
"priority": 42
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package com.linearpast.sccore.animation.register;
|
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.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
import com.linearpast.sccore.SnowyCrescentCore;
|
import com.linearpast.sccore.SnowyCrescentCore;
|
||||||
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
|
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
|
||||||
import com.linearpast.sccore.animation.capability.RawAnimationDataCapability;
|
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.resources.ResourceLocation;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
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.entity.player.Player;
|
||||||
import net.minecraft.world.level.storage.LevelResource;
|
import net.minecraft.world.level.storage.LevelResource;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.event.AddReloadListenerEvent;
|
||||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||||
import net.minecraftforge.event.server.ServerStartedEvent;
|
import net.minecraftforge.event.server.ServerStartedEvent;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
@ -74,74 +86,266 @@ public class AnimationRegistry {
|
||||||
layers.putAll(layerMap);
|
layers.putAll(layerMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onAddReloadListeners(AddReloadListenerEvent event) {
|
||||||
|
event.addListener(AnimationDataManager.INSTANCE);
|
||||||
|
event.addListener(LayerDataManager.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onServerStarted(ServerStartedEvent event) {
|
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");
|
Path animationPath = dataPackPath.resolve("animation");
|
||||||
|
|
||||||
if (!Files.exists(animationPath)) {
|
if (!Files.exists(animationPath)) {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(animationPath);
|
Files.createDirectories(animationPath);
|
||||||
} catch (IOException e) { return; }
|
} catch (IOException e) {
|
||||||
|
SnowyCrescentCore.log.error("Failed to create legacy animation directory", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Handle zip files
|
||||||
|
if (Files.exists(dataPackPath.resolve("animation.zip"))) {
|
||||||
|
FileUtils.safeUnzip(dataPackPath.resolve("animation.zip").toString(),
|
||||||
|
animationPath.toAbsolutePath().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtils.safeUnzip(dataPackPath.resolve("animation.zip").toString(), animationPath.toAbsolutePath().toString());
|
|
||||||
Set<Path> animZipPaths = FileUtils.getAllFile(
|
Set<Path> animZipPaths = FileUtils.getAllFile(
|
||||||
dataPackPath.resolve("animation"),
|
dataPackPath.resolve("animation"),
|
||||||
path -> path.toString().endsWith(".anim.zip")
|
path -> path.toString().endsWith(".anim.zip")
|
||||||
);
|
);
|
||||||
|
|
||||||
Set<Path> layerZipPaths = FileUtils.getAllFile(
|
Set<Path> layerZipPaths = FileUtils.getAllFile(
|
||||||
dataPackPath.resolve("animation"),
|
dataPackPath.resolve("animation"),
|
||||||
path -> path.toString().endsWith(".layer.zip")
|
path -> path.toString().endsWith(".layer.zip")
|
||||||
);
|
);
|
||||||
|
|
||||||
for (Path zipPath : animZipPaths) {
|
for (Path zipPath : animZipPaths) {
|
||||||
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
|
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Path zipPath : layerZipPaths) {
|
for (Path zipPath : layerZipPaths) {
|
||||||
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
|
FileUtils.safeUnzip(zipPath.toString(), animationPath.toAbsolutePath().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load animation JSON files
|
||||||
Set<Path> animPaths = FileUtils.getAllFile(
|
Set<Path> animPaths = FileUtils.getAllFile(
|
||||||
dataPackPath.resolve("animation"),
|
dataPackPath.resolve("animation"),
|
||||||
path -> path.toString().endsWith(".anim.json")
|
path -> path.toString().endsWith(".anim.json")
|
||||||
);
|
);
|
||||||
|
|
||||||
Set<Path> layerPaths = FileUtils.getAllFile(
|
Set<Path> layerPaths = FileUtils.getAllFile(
|
||||||
dataPackPath.resolve("animation"),
|
dataPackPath.resolve("animation"),
|
||||||
path -> path.getFileName().toString().equals("animation.layer.json")
|
path -> path.getFileName().toString().equals("animation.layer.json")
|
||||||
);
|
);
|
||||||
Set<GenericAnimationData> animationsSet = new HashSet<>();
|
|
||||||
Map<ResourceLocation, Integer> layersMap = new HashMap<>();
|
|
||||||
for (Path path : animPaths) {
|
for (Path path : animPaths) {
|
||||||
try {
|
try {
|
||||||
AnimJson.Reader reader = AnimJson.Reader.stream(path);
|
AnimJson.Reader reader = AnimJson.Reader.stream(path);
|
||||||
GenericAnimationData anim = reader.parse();
|
GenericAnimationData anim = reader.parse();
|
||||||
animationsSet.add(anim);
|
animations.put(anim.getKey(), anim);
|
||||||
} catch (Exception ignored) {
|
SnowyCrescentCore.log.info("Loaded legacy animation: {} -> {}",
|
||||||
SnowyCrescentCore.log.error("Failed to parse animation JSON: {}", path.toString());
|
anim.getKey(), anim.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
SnowyCrescentCore.log.error("Failed to parse legacy animation JSON: {}", path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Path path : layerPaths) {
|
for (Path path : layerPaths) {
|
||||||
try {
|
try {
|
||||||
AnimLayerJson.Reader reader = AnimLayerJson.Reader.stream(path);
|
AnimLayerJson.Reader reader = AnimLayerJson.Reader.stream(path);
|
||||||
Map<ResourceLocation, Integer> parse = reader.parse();
|
Map<ResourceLocation, Integer> parse = reader.parse();
|
||||||
layersMap.putAll(parse);
|
layers.putAll(parse);
|
||||||
} catch (Exception ignored) {
|
SnowyCrescentCore.log.info("Loaded {} legacy layer configurations from {}",
|
||||||
SnowyCrescentCore.log.error("Failed to parse layer JSON: {}", path.toString());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
animations.clear();
|
/**
|
||||||
|
* Load animations from registration events
|
||||||
|
*/
|
||||||
|
private static void loadFromRegistrationEvents() {
|
||||||
AnimationRegisterEvent.Animation animationRegisterEvent = new AnimationRegisterEvent.Animation();
|
AnimationRegisterEvent.Animation animationRegisterEvent = new AnimationRegisterEvent.Animation();
|
||||||
MinecraftForge.EVENT_BUS.post(animationRegisterEvent);
|
MinecraftForge.EVENT_BUS.post(animationRegisterEvent);
|
||||||
Map<ResourceLocation, GenericAnimationData> animationMap = animationRegisterEvent.getAnimations();
|
Map<ResourceLocation, GenericAnimationData> animationMap = animationRegisterEvent.getAnimations();
|
||||||
animations.putAll(animationMap);
|
animations.putAll(animationMap);
|
||||||
animations.putAll(animationsSet.stream().collect(Collectors.toMap(GenericAnimationData::getKey, animation -> animation)));
|
|
||||||
|
|
||||||
layers.clear();
|
|
||||||
AnimationRegisterEvent.Layer layerRegisterEvent = new AnimationRegisterEvent.Layer();
|
AnimationRegisterEvent.Layer layerRegisterEvent = new AnimationRegisterEvent.Layer();
|
||||||
MinecraftForge.EVENT_BUS.post(layerRegisterEvent);
|
MinecraftForge.EVENT_BUS.post(layerRegisterEvent);
|
||||||
Map<ResourceLocation, Integer> layerMap = layerRegisterEvent.getLayers();
|
Map<ResourceLocation, Integer> layerMap = layerRegisterEvent.getLayers();
|
||||||
layers.putAll(layerMap);
|
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
|
@SubscribeEvent
|
||||||
|
|
@ -149,29 +353,64 @@ public class AnimationRegistry {
|
||||||
if (event.getEntity() instanceof ServerPlayer serverPlayer) {
|
if (event.getEntity() instanceof ServerPlayer serverPlayer) {
|
||||||
MinecraftServer server = serverPlayer.getServer();
|
MinecraftServer server = serverPlayer.getServer();
|
||||||
if(server == null) return;
|
if(server == null) return;
|
||||||
Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR);
|
|
||||||
Path animationPath = dataPackPath.resolve("animation");
|
// Send animations to client
|
||||||
if (!Files.exists(animationPath)) {
|
sendAnimationsToClient(serverPlayer);
|
||||||
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 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)
|
@OnlyIn(Dist.CLIENT)
|
||||||
public static class ClientCache {
|
public static class ClientCache {
|
||||||
|
|
@ -188,7 +427,6 @@ public class AnimationRegistry {
|
||||||
layersCache.put(location, priority);
|
layersCache.put(location, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"JavaReflectionMemberAccess", "UnstableApiUsage", "unchecked"})
|
|
||||||
public static void animationStatusUpdate(AnimationClientStatusPacket.Status status) {
|
public static void animationStatusUpdate(AnimationClientStatusPacket.Status status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ANIM_CACHE_CLEAR -> animationsCache.clear();
|
case ANIM_CACHE_CLEAR -> animationsCache.clear();
|
||||||
|
|
@ -206,89 +444,150 @@ public class AnimationRegistry {
|
||||||
registerLayers(layersCache);
|
registerLayers(layersCache);
|
||||||
layersCache.forEach((key, value) -> PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
layersCache.forEach((key, value) -> PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
||||||
key, value, player -> {
|
key, value, player -> {
|
||||||
if(Minecraft.getInstance().player == null) return ClientCache.registerPlayerAnimation(player);
|
if (Minecraft.getInstance().player == null) {
|
||||||
Map<ResourceLocation, IAnimation> animationMap = modifierLayers.getOrDefault(player.getUUID(), new HashMap<>());
|
return ClientCache.registerPlayerAnimation(player);
|
||||||
if(animationMap.containsKey(key)) return animationMap.get(key);
|
}
|
||||||
|
Map<ResourceLocation, IAnimation> animationMap =
|
||||||
|
modifierLayers.getOrDefault(player.getUUID(), new HashMap<>());
|
||||||
|
if (animationMap.containsKey(key)) {
|
||||||
|
return animationMap.get(key);
|
||||||
|
}
|
||||||
IAnimation iAnimation = ClientCache.registerPlayerAnimation(player);
|
IAnimation iAnimation = ClientCache.registerPlayerAnimation(player);
|
||||||
animationMap.put(key, iAnimation);
|
animationMap.put(key, iAnimation);
|
||||||
modifierLayers.put(player.getUUID(), animationMap);
|
modifierLayers.put(player.getUUID(), animationMap);
|
||||||
return iAnimation;
|
return iAnimation;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update existing players' animation stacks
|
||||||
ClientLevel level = Minecraft.getInstance().level;
|
ClientLevel level = Minecraft.getInstance().level;
|
||||||
if (level == null) {
|
if (level == null) {
|
||||||
SnowyCrescentCore.log.error("{} : Level is null", ClientCache.class.getName());
|
SnowyCrescentCore.log.error("Level is null, cannot update animation layers");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (AbstractClientPlayer player : level.players()) {
|
for (AbstractClientPlayer player : level.players()) {
|
||||||
|
updatePlayerAnimationStack(player);
|
||||||
|
}
|
||||||
|
} 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 {
|
try {
|
||||||
Class<?> playerClass = Player.class;
|
Class<?> playerClass = Player.class;
|
||||||
Field animationStackField = playerClass.getDeclaredField("animationStack");
|
Field animationStackField = playerClass.getDeclaredField("animationStack");
|
||||||
animationStackField.setAccessible(true);
|
animationStackField.setAccessible(true);
|
||||||
|
|
||||||
Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack");
|
Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack");
|
||||||
createAnimationStack.setAccessible(true);
|
createAnimationStack.setAccessible(true);
|
||||||
|
|
||||||
AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player);
|
AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player);
|
||||||
AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player);
|
AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player);
|
||||||
|
|
||||||
Field layersField = AnimationStack.class.getDeclaredField("layers");
|
Field layersField = AnimationStack.class.getDeclaredField("layers");
|
||||||
layersField.setAccessible(true);
|
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>> 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<>();
|
ArrayList<Pair<Integer, IAnimation>> result = new ArrayList<>();
|
||||||
for (Pair<Integer, IAnimation> oldAnimationPair : List.copyOf(oldArrayList)) {
|
|
||||||
for (Pair<Integer, IAnimation> newAnimationPair : List.copyOf(newArrayList)) {
|
// Add old layers that aren't replaced by new ones
|
||||||
if(Objects.equals(oldAnimationPair.getLeft(), newAnimationPair.getLeft())) {
|
for (Pair<Integer, IAnimation> oldPair : oldArrayList) {
|
||||||
KeyframeAnimation oldData = Optional.ofNullable((KeyframeAnimationPlayer) ((ModifierLayer<?>) oldAnimationPair.getRight()).getAnimation())
|
boolean isReplaced = false;
|
||||||
.map(KeyframeAnimationPlayer::getData).orElse(null);
|
for (Pair<Integer, IAnimation> newPair : newArrayList) {
|
||||||
KeyframeAnimation newData = Optional.ofNullable((KeyframeAnimationPlayer) ((ModifierLayer<?>) newAnimationPair.getRight()).getAnimation())
|
if (Objects.equals(oldPair.getLeft(), newPair.getLeft())) {
|
||||||
.map(KeyframeAnimationPlayer::getData).orElse(null);
|
isReplaced = true;
|
||||||
if(Objects.equals(oldData, newData)) oldArrayList.remove(oldAnimationPair);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isReplaced) {
|
||||||
|
result.add(oldPair);
|
||||||
}
|
}
|
||||||
result.addAll(oldArrayList);
|
}
|
||||||
|
|
||||||
|
// Add all new layers
|
||||||
result.addAll(newArrayList);
|
result.addAll(newArrayList);
|
||||||
|
|
||||||
|
// Set the merged layers
|
||||||
layersField.set(newAnimationStack, result);
|
layersField.set(newAnimationStack, result);
|
||||||
animationStackField.set(player, newAnimationStack);
|
animationStackField.set(player, newAnimationStack);
|
||||||
|
|
||||||
|
// Update animation applier
|
||||||
Field animationApplierField = playerClass.getDeclaredField("animationApplier");
|
Field animationApplierField = playerClass.getDeclaredField("animationApplier");
|
||||||
animationApplierField.setAccessible(true);
|
animationApplierField.setAccessible(true);
|
||||||
|
//noinspection UnstableApiUsage
|
||||||
animationApplierField.set(player, new AnimationApplier(newAnimationStack));
|
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);
|
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
|
||||||
if(data == null) continue;
|
if (data == null) return;
|
||||||
|
|
||||||
RawAnimationDataCapability rawData = RawAnimationDataCapability.getCapability(player).orElse(null);
|
RawAnimationDataCapability rawData = RawAnimationDataCapability.getCapability(player).orElse(null);
|
||||||
if(rawData == null) continue;
|
if (rawData == null) return;
|
||||||
|
|
||||||
Map<ResourceLocation, ResourceLocation> dataAnimations = new HashMap<>();
|
Map<ResourceLocation, ResourceLocation> dataAnimations = new HashMap<>();
|
||||||
dataAnimations.putAll(data.getAnimations());
|
dataAnimations.putAll(data.getAnimations());
|
||||||
dataAnimations.putAll(rawData.getAnimations());
|
dataAnimations.putAll(rawData.getAnimations());
|
||||||
|
|
||||||
ResourceLocation riderAnimLayer = data.getRiderAnimLayer();
|
ResourceLocation riderAnimLayer = data.getRiderAnimLayer();
|
||||||
if(riderAnimLayer != null) {
|
if (riderAnimLayer != null && data.getRiderAnimation() != null) {
|
||||||
dataAnimations.put(riderAnimLayer, data.getRiderAnimation());
|
dataAnimations.put(riderAnimLayer, data.getRiderAnimation());
|
||||||
}
|
}
|
||||||
for (ResourceLocation location : dataAnimations.keySet()) {
|
|
||||||
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
|
for (Map.Entry<ResourceLocation, ResourceLocation> entry : dataAnimations.entrySet()) {
|
||||||
.getPlayerAssociatedData(player).get(location);
|
ResourceLocation layerKey = entry.getKey();
|
||||||
|
ResourceLocation animKey = entry.getValue();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>)
|
||||||
|
PlayerAnimationAccess.getPlayerAssociatedData(player).get(layerKey);
|
||||||
if (modifierLayer == null) continue;
|
if (modifierLayer == null) continue;
|
||||||
|
|
||||||
KeyframeAnimation keyframeAnimation;
|
KeyframeAnimation keyframeAnimation;
|
||||||
ResourceLocation animationLocation = dataAnimations.get(location);
|
GenericAnimationData anim = animations.get(animKey);
|
||||||
GenericAnimationData anim = animations.get(animationLocation);
|
|
||||||
if (anim == null) {
|
if (anim == null) {
|
||||||
RawAnimationData rawAnim = RawAnimationService.INSTANCE.getAnimation(animationLocation);
|
RawAnimationData rawAnim = RawAnimationService.INSTANCE.getAnimation(animKey);
|
||||||
if(rawAnim == null) return;
|
if (rawAnim == null) continue;
|
||||||
keyframeAnimation = rawAnim.getAnimation();
|
keyframeAnimation = rawAnim.getAnimation();
|
||||||
} else keyframeAnimation = anim.getAnimation();
|
} else {
|
||||||
|
keyframeAnimation = anim.getAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
if (keyframeAnimation == null) continue;
|
if (keyframeAnimation == null) continue;
|
||||||
|
|
||||||
modifierLayer.replaceAnimationWithFade(
|
modifierLayer.replaceAnimationWithFade(
|
||||||
AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE),
|
AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE),
|
||||||
new KeyframeAnimationPlayer(keyframeAnimation)
|
new KeyframeAnimationPlayer(keyframeAnimation)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
SnowyCrescentCore.log.error("Failed to register on {} animation layer: {}", player, e.getMessage(), e);
|
SnowyCrescentCore.log.error("Failed to restore playing animations for player: {}", player, e);
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.linearpast.sccore.core.datagen;
|
package com.linearpast.sccore.core.datagen;
|
||||||
|
|
||||||
import com.linearpast.sccore.SnowyCrescentCore;
|
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 com.linearpast.sccore.core.datagen.provider.ModLangProvider;
|
||||||
import net.minecraft.core.HolderLookup;
|
import net.minecraft.core.HolderLookup;
|
||||||
import net.minecraft.data.DataGenerator;
|
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.EN_US));
|
||||||
generator.addProvider(event.includeClient(), new ModLangProvider(packOutput, ModLangProvider.Lang.ZH_CN));
|
generator.addProvider(event.includeClient(), new ModLangProvider(packOutput, ModLangProvider.Lang.ZH_CN));
|
||||||
|
generator.addProvider(true, new ModAnimationProvider(generator));
|
||||||
|
generator.addProvider(true, new ModAnimationLayerProvider(generator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ public class ModAnimation {
|
||||||
* @param event event
|
* @param event event
|
||||||
*/
|
*/
|
||||||
public static void onLayerRegister(AnimationRegisterEvent.Layer 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)
|
// GenericAnimationData amSTL = GenericAnimationData.create(AmStandToLying)
|
||||||
// .withName("Stand-to-Lying")
|
// .withName("Stand-to-Lying")
|
||||||
// .withLyingType(GenericAnimationData.LyingType.FRONT);
|
// .withLyingType(GenericAnimationData.LyingType.FRONT);
|
||||||
GenericAnimationData waltzGentleman = (GenericAnimationData) GenericAnimationData
|
// GenericAnimationData waltzGentleman = (GenericAnimationData) GenericAnimationData
|
||||||
.create(WaltzGentleman)
|
// .create(WaltzGentleman)
|
||||||
.withName("Waltz-Gentleman")
|
// .withName("Waltz-Gentleman")
|
||||||
.addCamPosOffset(new Vec3(0.0,0.0,1.0))
|
// .addCamPosOffset(new Vec3(0.0,0.0,1.0))
|
||||||
.withCamPosOffsetRelative(true)
|
// .withCamPosOffsetRelative(true)
|
||||||
.withRide(Ride.create().addComponentAnimation(WaltzLady));
|
// .withRide(Ride.create().addComponentAnimation(WaltzLady));
|
||||||
GenericAnimationData waltzLady = (GenericAnimationData) GenericAnimationData
|
// GenericAnimationData waltzLady = (GenericAnimationData) GenericAnimationData
|
||||||
.create(WaltzLady)
|
// .create(WaltzLady)
|
||||||
.withName("Waltz-Lady")
|
// .withName("Waltz-Lady")
|
||||||
.withCamYaw(180)
|
// .withCamYaw(180)
|
||||||
.withRide(Ride.create().addComponentAnimation(WaltzGentleman));
|
// .withRide(Ride.create().addComponentAnimation(WaltzGentleman));
|
||||||
|
|
||||||
//You can use it to invite an Animation
|
//You can use it to invite an Animation
|
||||||
// event.registerAnimation(AmLyingToRightLying, amLTRL);
|
// event.registerAnimation(AmLyingToRightLying, amLTRL);
|
||||||
// event.registerAnimation(AmStandToLying, amSTL);
|
// event.registerAnimation(AmStandToLying, amSTL);
|
||||||
event.registerAnimation(WaltzGentleman, waltzGentleman);
|
// event.registerAnimation(WaltzGentleman, waltzGentleman);
|
||||||
event.registerAnimation(WaltzLady, waltzLady);
|
// event.registerAnimation(WaltzLady, waltzLady);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onRawAnimationRegister(AnimationRegisterEvent.RawAnimation event) {
|
public static void onRawAnimationRegister(AnimationRegisterEvent.RawAnimation event) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user