version 0.0.4

This commit is contained in:
LostInLinearPast 2025-10-26 22:09:49 +08:00
parent 34d572f928
commit 16995e5f9d
70 changed files with 6632 additions and 461 deletions

122
.gitignore vendored
View File

@ -1,119 +1,5 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
.idea
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Cache of project
.gradletasknamecache
**/build/
# Common working directory
run/
runs/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
build
run
run-data

View File

@ -11,6 +11,7 @@ buildscript {
plugins {
id 'eclipse'
id 'idea'
id 'maven-publish'
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
id 'org.parchmentmc.librarian.forgegradle' version '1.+'
}
@ -25,14 +26,14 @@ base {
}
java {
withSourcesJar()
withJavadocJar()
toolchain.languageVersion = JavaLanguageVersion.of(17)
}
minecraft {
mappings channel: mapping_channel, version: mapping_version
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
copyIdeResources = true
runs {
@ -52,6 +53,8 @@ minecraft {
var client = client {
property 'forge.enabledGameTestNamespaces', mod_id
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${project.projectDir}/build/createSrgToMcp/output.srg"
}
client_0 {
@ -66,6 +69,8 @@ minecraft {
server {
property 'forge.enabledGameTestNamespaces', mod_id
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${project.projectDir}/build/createSrgToMcp/output.srg"
args '--nogui'
}
@ -116,6 +121,14 @@ dependencies {
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
compileOnly 'org.spongepowered:mixin:0.8.5'
implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1"))
implementation(jarJar("io.github.llamalad7:mixinextras-forge:0.4.1") {
jarJar.ranged(it, "[0.4.1,)")
})
implementation fg.deobf("dev.kosmx.player-anim:player-animation-lib-forge:1.0.2-rc1+1.20")
implementation fg.deobf("io.github.kosmx.bendy-lib:bendy-lib-forge:4.0.0")
implementation fg.deobf("curse.maven:freecam-by-zergatul-618947:5402097")
}
tasks.named('processResources', ProcessResources).configure {
@ -156,6 +169,75 @@ tasks.named('jar', Jar).configure {
finalizedBy 'reobfJar'
}
tasks.withType(Javadoc).configureEach {
options.addStringOption('Xdoclint:none', '-quiet')
options.addStringOption('Xdoclint:-missing', '-quiet')
options.encoding = 'UTF-8'
failOnError = false
}
tasks.register('deobfJar', Jar) {
from(sourceSets.main.output)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes(["Specification-Title" : mod_id,
"Specification-Vendor" : mod_authors,
"Specification-Version" : "1",
"Implementation-Title" : project.name,
"Implementation-Version" : project.jar.archiveVersion,
"Implementation-Vendor" : mod_authors,
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")])
}
dependsOn classes
}
publishing {
publications {
mavenJava(MavenPublication) {
artifact deobfJar
artifact javadocJar
artifact sourcesJar
groupId = project.group
artifactId = mod_id
version = project.version
pom {
name = mod_id
description = mod_description
url = mod_url
licenses {
license {
name = mod_license
}
}
developers {
developer {
id = "{mod_id}"
name = mod_authors
}
}
}
}
}
repositories {
maven {
name = 'LTDNexus'
url = 'https://nexus.bot.leisuretimedock.top/repository/maven-releases/'
credentials {
username = System.getenv('LTDNexusUsername') ?: ''
password = System.getenv('LTDNexusPassword') ?: ''
}
}
}
}
gradle.buildFinished {
def javadocJar = file("build/libs/${mod_id}-${mod_version}-javadoc.jar")
def sourcesJar = file("build/libs/${mod_id}-${mod_version}-sources.jar")
if (javadocJar.exists()) ant.delete(file: javadocJar)
if (sourcesJar.exists()) ant.delete(file: sourcesJar)
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}

View File

@ -12,9 +12,9 @@ mapping_version=2023.09.03-1.20.1
mod_id=sccore
mod_name=SnowyCrescentCore
mod_license=GNU AGPL 3.0
mod_version=1.20.1-0.0.2
mod_version=1.20.1-0.0.4
mod_group_id=com.linearpast
mod_authors=LostInLinearPast
mod_description=A lib.
mod_description=A lib about capability and player animator.
mod_credits=
mod_url=https://qm.qq.com/q/k0NVzvUdlC
mod_url=https://github.com/Linearpast/SnowyCrescentCore

View File

@ -1,28 +1,47 @@
package com.linearpast.sccore;
import com.linearpast.sccore.animation.registry.AnimationRegistry;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.example.ModCaps;
import com.linearpast.sccore.network.Channel;
import com.linearpast.sccore.core.ModChannel;
import com.linearpast.sccore.core.ModCommands;
import com.linearpast.sccore.core.ModConfigs;
import com.linearpast.sccore.example.animation.ModAnimation;
import com.linearpast.sccore.example.capability.ModCapability;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLEnvironment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod(SnowyCrescentCore.MODID)
public class SnowyCrescentCore {
public static final Logger log = LoggerFactory.getLogger(SnowyCrescentCore.class);
public static final String MODID = "sccore";
public static final String ENABLE_EXAMPLES_PROPERTY_KEY = "sccore.enable_examples";
public SnowyCrescentCore() {
IEventBus forgeBus = MinecraftForge.EVENT_BUS;
CapabilityUtils.registerHandler(forgeBus);
Channel.register();
ModLoadingContext modLoadingContext = ModLoadingContext.get();
modLoadingContext.registerConfig(ModConfig.Type.COMMON, ModConfigs.Common.SPEC);
if(!FMLEnvironment.production){
ModCaps.register();
ModCaps.addListenerToEvent(forgeBus);
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
IEventBus forgeBus = MinecraftForge.EVENT_BUS;
CapabilityUtils.registerHandler(forgeBus);
ModChannel.register();
AnimationRegistry.register();
AnimationRegistry.addAnimationListener(forgeBus, modBus);
ModCommands.registerCommands(forgeBus, modBus);
if(!FMLEnvironment.production || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY)) {
ModCapability.register();
ModCapability.addListenerToEvent(forgeBus);
ModAnimation.register(forgeBus, modBus);
}
}
}

View File

@ -0,0 +1,116 @@
package com.linearpast.sccore.animation;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.data.Animation;
import com.linearpast.sccore.animation.entity.AnimationRideEntity;
import com.linearpast.sccore.animation.mixin.IMixinKeyframeAnimationPlayer;
import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket;
import com.linearpast.sccore.animation.network.toserver.PlayAnimationRequestPacket;
import com.linearpast.sccore.animation.network.toserver.PlayAnimationRidePacket;
import com.linearpast.sccore.core.ModChannel;
import dev.kosmx.playerAnim.api.layered.IAnimation;
import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer;
import dev.kosmx.playerAnim.api.layered.ModifierLayer;
import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import dev.kosmx.playerAnim.core.util.Ease;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class AnimationPlayer {
public static void requestAnimationToServer(ResourceLocation layer, @Nullable ResourceLocation animation) {
ModChannel.sendToServer(new PlayAnimationRequestPacket(layer, animation));
}
public static boolean serverPlayAnimation(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) {
IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null);
if(data == null) return false;
if(animation != null) {
return data.mergeAnimation(layer, animation);
} else {
return data.removeAnimation(layer);
}
}
public static boolean playAnimationWithRide(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation, boolean force){
if(animation != null) {
IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null);
if(data == null) return false;
data.setRideAnimLayer(layer);
return AnimationRideEntity.create(serverPlayer, layer, animation, force);
} else {
serverPlayer.unRide();
return true;
}
}
public static void requestAnimationRideToServer(ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) {
ModChannel.sendToServer(new PlayAnimationRidePacket(layer, animation, force));
}
public static void clearAnimation(ServerPlayer serverPlayer) {
IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null);
if(data == null) return;
data.clearAnimations();
}
public static void syncAnimation(ServerPlayer player, ServerPlayer target, ResourceLocation layer) {
ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID(), layer), player);
ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID(), layer), target);
}
@SuppressWarnings("unchecked")
@OnlyIn(Dist.CLIENT)
public static void syncAnimation(AbstractClientPlayer clientPlayer, AbstractClientPlayer target, ResourceLocation layer) {
try {
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
.getPlayerAssociatedData(clientPlayer).get(layer);
ModifierLayer<IAnimation> targetModifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
.getPlayerAssociatedData(target).get(layer);
if(modifierLayer == null || targetModifierLayer == null) return;
IMixinKeyframeAnimationPlayer animation = (IMixinKeyframeAnimationPlayer) modifierLayer.getAnimation();
KeyframeAnimationPlayer targetAnimation = (KeyframeAnimationPlayer) targetModifierLayer.getAnimation();
if(animation == null || targetAnimation == null) return;
int currentTick = targetAnimation.getCurrentTick();
animation.sccore$setCurrentTick(currentTick);
} catch (Exception ignored) {}
}
@SuppressWarnings("unchecked")
@OnlyIn(Dist.CLIENT)
public static void playAnimation(AbstractClientPlayer clientPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) {
try {
ModifierLayer<IAnimation> modifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
.getPlayerAssociatedData(clientPlayer).get(layer);
if(animation == null) {
if(modifierLayer != null) {
modifierLayer.replaceAnimationWithFade(
AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE),
null
);
}
return;
}
Animation anim = AnimationUtils.getAnimation(animation);
if(anim == null) return;
if(modifierLayer == null) return;
KeyframeAnimation keyframeAnimation = anim.getAnimation();
if(keyframeAnimation == null) return;
Objects.requireNonNull(modifierLayer).replaceAnimationWithFade(
AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE),
new KeyframeAnimationPlayer(keyframeAnimation)
);
}catch (Exception e) {
SnowyCrescentCore.log.error("Failed to play animation : {}", animation, e);
}
}
}

View File

@ -0,0 +1,387 @@
package com.linearpast.sccore.animation;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.data.Animation;
import com.linearpast.sccore.animation.data.Ride;
import com.linearpast.sccore.animation.entity.AnimationRideEntity;
import com.linearpast.sccore.animation.event.AnimationLayerRegistry;
import com.linearpast.sccore.animation.event.EntityRendererRegistry;
import com.linearpast.sccore.animation.event.client.CameraAnglesModify;
import com.linearpast.sccore.animation.event.client.ClientPlayerTick;
import com.linearpast.sccore.animation.network.toserver.RefreshAnimationPacket;
import com.linearpast.sccore.animation.registry.AnimationEntities;
import com.linearpast.sccore.animation.registry.AnimationRegistry;
import com.linearpast.sccore.core.ModChannel;
import com.linearpast.sccore.core.ModLazyRun;
import dev.kosmx.playerAnim.api.layered.IAnimation;
import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer;
import dev.kosmx.playerAnim.api.layered.ModifierLayer;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.eventbus.api.IEventBus;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
/**
* Animation Util. May be you can call it Api.
*/
public class AnimationUtils {
public static final String AnimModId = "playeranimator";
public static final ModLazyRun ANIMATION_RUNNER = new ModLazyRun(AnimModId) {
@Override
public void addCommonListener(IEventBus forgeBus, IEventBus modBus) {
AnimationEntities.register(modBus);
modBus.addListener(AnimationLayerRegistry::onCommonSetUp);
}
@Override
public void addClientListener(IEventBus forgeBus, IEventBus modBus) {
forgeBus.addListener(CameraAnglesModify::changeCameraView);
modBus.addListener(AnimationLayerRegistry::onClientSetup);
modBus.addListener(EntityRendererRegistry::registerEntityRenderer);
forgeBus.addListener(ClientPlayerTick::onPlayerTick);
forgeBus.addListener(ClientPlayerTick::delayRuns);
}
};
/**
* <pre>
* Play animation.
* If run in Dist.CLIENT, the serverPlayer can be null.
* If animation be null, it will remove animation on layer.
* </pre>
* @param serverPlayer Target player
* @param layer Target layer
* @param animation Animation
* @return If success
*/
public static boolean playAnimation(@Nullable ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> {
if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) {
if(serverPlayer != null) {
return AnimationPlayer.serverPlayAnimation(serverPlayer, layer, animation);
}else {
AnimationPlayer.requestAnimationToServer(layer, animation);
return true;
}
}
return false;
});
}
/**
* <pre>
* Play animation with ride. Player will ride an entity, then play animation.
* When player unride, animation will be remove.
* If run in Dist.CLIENT, the serverPlayer can be null.
* If animation be null, it will call function: {@link ServerPlayer#unRide()}
* If player is riding and the "force" is false, it will return false
* </pre>
* @param serverPlayer Target player
* @param layer Target layer
* @param animation Animation
* @param force If force to ride and play animation
* @return If success
*/
public static boolean playAnimationWithRide(@Nullable ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> {
if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) {
Animation anim = AnimationUtils.getAnimation(animation);
if(anim != null && anim.getRide() == null) return false;
if(serverPlayer != null) {
if(serverPlayer.getVehicle() != null && force) serverPlayer.unRide();
else if(serverPlayer.getVehicle() != null) return false;
AnimationPlayer.playAnimationWithRide(serverPlayer, layer, animation, true);
} else {
AnimationPlayer.requestAnimationRideToServer(layer, animation, force);
return true;
}
}
return false;
});
}
/**
* Remove animation.
* @see AnimationUtils#playAnimation
* @param serverPlayer Target player
* @param layer Target layer
* @return If success
*/
public static boolean removeAnimation(@Nullable ServerPlayer serverPlayer, ResourceLocation layer) {
return playAnimation(serverPlayer, layer, null);
}
/**
* Get animation which is playing now on player. <br>
* If layer is null, it will return the first playing animation which can be found.
* @param player Target player
* @param layer Target layer
* @return Playing animation resource location
*/
@Nullable
public static ResourceLocation getAnimationPlaying(Player player, @Nullable ResourceLocation layer) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if(data == null) return null;
if(layer == null){
for (ResourceLocation value : data.getAnimations().values()) {
if(value != null) return value;
}
} else if (isAnimationLayerPresent(layer)) {
if(data.isAnimationPresent(layer)){
return data.getAnimation(layer);
}
}
return null;
});
}
/**
* Test if layer exist animation which is playing.
* <p>
* Only in dist client
* @param player Target player
* @param layer Target layer
* @return If layer exist animation which is playing
*/
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("unchecked")
public static boolean isClientAnimationPlaying(AbstractClientPlayer player, @Nullable ResourceLocation layer) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> {
try {
Set<ResourceLocation> resourceLocations = new HashSet<>();
if(layer == null) resourceLocations = AnimationLayerRegistry.getAnimLayers().keySet();
else resourceLocations.add(layer);
for (ResourceLocation location : resourceLocations) {
ModifierLayer<IAnimation> animationModifierLayer = (ModifierLayer<IAnimation>) PlayerAnimationAccess
.getPlayerAssociatedData(player).get(location);
if(animationModifierLayer == null) continue;
KeyframeAnimationPlayer animation = (KeyframeAnimationPlayer) animationModifierLayer.getAnimation();
if(animation == null) return false;
int currentTick = animation.getCurrentTick();
int stopTick = animation.getStopTick();
return currentTick <= stopTick;
}
} catch (Exception ignored) {}
return false;
});
}
/**
* Sync animation tick to client
* @param player Player
* @param target Target player
* @param layer Target layer
*/
public static void syncAnimation(ServerPlayer player, ServerPlayer target, ResourceLocation layer) {
ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target, layer));
}
/**
* Sync animation tick on client
* @param player Player
* @param target Target player
* @param layer Target layer
*/
@OnlyIn(Dist.CLIENT)
public static void syncAnimation(AbstractClientPlayer player, AbstractClientPlayer target, ResourceLocation layer) {
ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target, layer));
}
/**
* Join animation.
* @param player Will join player
* @param target Joined player
* @param force If force
* @return If success
*/
public static boolean joinAnimation(ServerPlayer player, ServerPlayer target, boolean force) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> {
Entity vehicle = target.getVehicle();
if(!(vehicle instanceof AnimationRideEntity rideEntity)) return false;
int playerCount = rideEntity.getPlayers().size();
Animation animation = rideEntity.getAnimation();
if(animation == null) return false;
Ride ride = animation.getRide();
if(ride == null) return false;
int maxCount = ride.getComponentAnimations().size();
if(playerCount >= maxCount) return false;
return player.startRiding(vehicle, force);
});
}
/**
* <pre>
* Start animation together...
* The max participants' count is depend on your animation.
* Max count = Size of {@link Ride#getComponentAnimations()}
* </pre>
* @param player Leader
* @param layer Target layer
* @param animation Animation location
* @param force If force start leader
* @param participants Participants
*/
public static void startAnimationTogether(
ServerPlayer player,
ResourceLocation layer,
ResourceLocation animation,
boolean force,
ServerPlayer ... participants
) {
AnimationUtils.playAnimationWithRide(player, layer, animation, force);
for (ServerPlayer participant : participants) {
AnimationUtils.joinAnimation(participant, player, force);
}
}
/**
* Detach animation
* @param player Player
*/
public static void detachAnimation(ServerPlayer player) {
ANIMATION_RUNNER.testLoadedAndRun(() -> {
if(player.getVehicle() instanceof AnimationRideEntity) {
player.unRide();
}
});
}
/**
* Clear Player animations.
* @param serverPlayer Target player
*/
public static void clearAnimation(ServerPlayer serverPlayer) {
ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.clearAnimation(serverPlayer));
}
/**
* Test if layer exist and has been register.
* @param layer Target layer
* @return If layer exist and has been register
*/
public static boolean isAnimationLayerPresent(ResourceLocation layer) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationLayerRegistry.isLayerPresent(layer));
}
/**
* Test if animation exist and has been register.
* @param location Animation resource location
* @return If animation exist and has been register
*/
public static boolean isAnimationPresent(ResourceLocation location) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.isAnimationPresent(location));
}
/**
* Register an animation through static function
* @param location Animation resource location
* @param animation Animation data
*/
public static void registerAnimation(ResourceLocation location, Animation animation) {
ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationRegistry.registerAnimation(location, animation));
}
/**
* Register an animation layer through static function. <br>
* The number is bigger and the priority is higher. <br>
* It must run before these events : <br>
* {@link net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent} <br>
* {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}
* @param location Layer location key
* @param priority Layer priority,
*/
public static void registerAnimationLayer(ResourceLocation location, int priority) {
ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationLayerRegistry.registerPlayerAnimation(location, priority));
}
/**
* Get animation data from animation resource location. <br>
* You will get null if you use it too early. <br>
* Suggest only use it in game has loaded.
* @param location Animation resource location
* @return Animation data
*/
@Nullable
public static Animation getAnimation(ResourceLocation location) {
return AnimationRegistry.getAnimation(location);
}
/**
* Get the LyingType when there are animations which playing on player. <br>
* And It will return the first which be found.
* @param player Target player
* @return The first LyingType it find.
*/
@Nullable
public static Animation.LyingType getSideView(Player player) {
return ANIMATION_RUNNER.testLoadedAndCall(() -> {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if(data == null) return null;
Animation.LyingType lyingType = null;
for (ResourceLocation value : data.getAnimations().values()) {
Animation animation = getAnimation(value);
if(animation == null) return null;
Animation.LyingType type = animation.getLyingType();
if(type == null) continue;
switch (type) {
case FRONT,BACK -> {}
case LEFT,RIGHT -> lyingType = animation.getLyingType();
}
}
return lyingType;
});
}
/**
* Get the HeightModifier when there are animations which playing on player. <br>
* And It will return the first which be found.
* @param player Target player
* @return The first HeightModifier it find.
*/
public static float getHeightModifier(Player player) {
Float result = ANIMATION_RUNNER.testLoadedAndCall(() -> {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if (data == null) return 1.0f;
float heightModifier = 1.0f;
for (ResourceLocation value : data.getAnimations().values()) {
Animation animation = getAnimation(value);
if (animation == null) continue;
float animationHeightModifier = animation.getHeightModifier();
heightModifier = Math.min(heightModifier, animationHeightModifier);
}
return heightModifier;
});
return result == null ? 1.0f : result;
}
/**
* Test if animation is playing <br>
* if not, it will remove the animation resource location on client
* <p>
* Only in dist client
* @param clientPlayer Target player
*/
@OnlyIn(Dist.CLIENT)
public static void refreshAnimation(AbstractClientPlayer clientPlayer) {
IAnimationCapability data = AnimationDataCapability.getCapability(clientPlayer).orElse(null);
if(data == null) return;
Set<ResourceLocation> oldLayers = new HashSet<>(data.getAnimations().keySet());
oldLayers.forEach(layer -> {
if (isClientAnimationPlaying(clientPlayer, layer)) {
oldLayers.remove(layer);
}
});
ModChannel.sendToServer(new RefreshAnimationPacket(oldLayers));
}
}

View File

@ -0,0 +1,151 @@
package com.linearpast.sccore.animation.capability;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.event.AnimationLayerRegistry;
import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync;
import com.linearpast.sccore.capability.network.SimpleCapabilityPacket;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class AnimationDataCapability extends SimplePlayerCapabilitySync implements IAnimationCapability {
public static final ResourceLocation key = new ResourceLocation(SnowyCrescentCore.MODID, "animation_data");
public static final String AnimMap = "AnimMap";
public static final String RideAnimLayer = "RideAnimLayer";
private final Map<ResourceLocation, ResourceLocation> animMap = new HashMap<>();
private ResourceLocation rideAnimLayer;
@Override
public void mergeAnimations(Map<ResourceLocation, ResourceLocation> animations) {
animations.forEach((key, value) -> {
if (AnimationLayerRegistry.getAnimLayers().containsKey(key)) {
if (AnimationUtils.isAnimationPresent(value)) {
this.animMap.put(key, value);
setDirty(true);
}
}
});
}
@Override
public boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation) {
if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) {
if (AnimationUtils.isAnimationPresent(animation)) {
this.animMap.put(layer, animation);
setDirty(true);
return true;
}
}
return false;
}
@Override
public boolean removeAnimation(ResourceLocation layer) {
ResourceLocation remove = this.animMap.remove(layer);
if(remove != null) {
setDirty(true);
return true;
}
return false;
}
@Nullable
@Override
public ResourceLocation getAnimation(ResourceLocation layer) {
return animMap.get(layer);
}
@Override
public Map<ResourceLocation, ResourceLocation> getAnimations() {
return animMap;
}
@Override
public void clearAnimations() {
this.animMap.clear();
setDirty(true);
}
@Override
public boolean isAnimationPresent(ResourceLocation layer) {
return animMap.containsKey(layer);
}
@Override
public void setRideAnimLayer(ResourceLocation rideAnimLayer) {
this.rideAnimLayer = rideAnimLayer;
setDirty(true);
}
@Override
public ResourceLocation getRideAnimLayer() {
return rideAnimLayer;
}
@Override
public void copyFrom(ICapabilitySync<?> oldData) {
IAnimationCapability data = (IAnimationCapability) oldData;
this.animMap.clear();
this.animMap.putAll(data.getAnimations());
}
@Override
public CompoundTag toTag(CompoundTag tag) {
if(!animMap.isEmpty()) {
CompoundTag animMapTag = new CompoundTag();
animMap.forEach((string, animation) ->
animMapTag.putString(string.toString(), animation.toString())
);
tag.put(AnimMap, animMapTag);
}
if(rideAnimLayer != null) tag.putString(RideAnimLayer, rideAnimLayer.toString());
return tag;
}
@Override
public void fromTag(CompoundTag tag) {
this.animMap.clear();
if(tag.contains(AnimMap)) {
CompoundTag animMapTag = tag.getCompound(AnimMap);
animMapTag.getAllKeys().forEach(key -> this.animMap.put(
new ResourceLocation(key),
new ResourceLocation(animMapTag.getString(key))
));
}
if(tag.contains(RideAnimLayer)) this.rideAnimLayer = new ResourceLocation(tag.getString(RideAnimLayer));
}
@Override
public SimpleCapabilityPacket<Player> getDefaultPacket() {
return new AnimationCapabilityPacket(serializeNBT());
}
@Override
public void attachInit(Player player) {
Map<ResourceLocation, ResourceLocation> map = new HashMap<>(this.animMap);
map.forEach((key, value) -> {
if(!AnimationUtils.isAnimationLayerPresent(key)) this.animMap.remove(key);
if(!AnimationUtils.isAnimationPresent(value)) this.animMap.remove(key);
if(key.equals(rideAnimLayer)) this.animMap.remove(key);
});
this.rideAnimLayer = null;
}
public static Optional<IAnimationCapability> getCapability(Player player){
return Optional.ofNullable(CapabilityUtils.getPlayerCapability(
player, AnimationDataCapability.key, IAnimationCapability.class
));
}
}

View File

@ -0,0 +1,21 @@
package com.linearpast.sccore.animation.capability.inter;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public interface IAnimationCapability extends ICapabilitySync<Player> {
void mergeAnimations(Map<ResourceLocation, ResourceLocation> animations);
boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation);
boolean removeAnimation(ResourceLocation layer);
@Nullable ResourceLocation getAnimation(ResourceLocation layer);
Map<ResourceLocation, ResourceLocation> getAnimations();
void clearAnimations();
boolean isAnimationPresent(ResourceLocation layer);
void setRideAnimLayer(ResourceLocation layer);
ResourceLocation getRideAnimLayer();
}

View File

@ -0,0 +1,48 @@
package com.linearpast.sccore.animation.command;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.command.argument.AnimationArgument;
import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument;
import com.linearpast.sccore.animation.command.client.AnimationRefreshCommand;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraftforge.registries.DeferredRegister;
import static net.minecraft.commands.Commands.literal;
public class AnimationCommands {
public static void commonCommandRegister(LiteralArgumentBuilder<CommandSourceStack> builder) {
if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()){
LiteralArgumentBuilder<CommandSourceStack> anim = literal("anim");
PlayAnimationCommand.register(anim);
builder.then(anim);
}
}
public static void clientCommandRegister(LiteralArgumentBuilder<CommandSourceStack> builder) {
if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()) {
LiteralArgumentBuilder<CommandSourceStack> anim = literal("anim");
AnimationRefreshCommand.register(anim);
builder.then(anim);
}
}
public static void registerArguments(DeferredRegister<ArgumentTypeInfo<?, ?>> register) {
register.register("animation",
() -> ArgumentTypeInfos.registerByClass(
AnimationArgument.class,
SingletonArgumentInfo.contextFree(AnimationArgument::animation)
)
);
register.register("layer",
() -> ArgumentTypeInfos.registerByClass(
AnimationLayerArgument.class,
SingletonArgumentInfo.contextFree(AnimationLayerArgument::layer)
)
);
}
}

View File

@ -0,0 +1,186 @@
package com.linearpast.sccore.animation.command;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.command.argument.AnimationArgument;
import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument;
import com.linearpast.sccore.animation.entity.AnimationRideEntity;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
public class PlayAnimationCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> animCommand){
animCommand.then(literal("play").then(argument("players", EntityArgument.players()).then(
argument("layer", AnimationLayerArgument.layer())
.then(argument("animation", AnimationArgument.animation())
.requires(cs -> cs.hasPermission(2))
.executes(context -> playAnimation(context, false))
.then(argument("withRide", BoolArgumentType.bool())
.executes(context -> playAnimation(
context, BoolArgumentType.getBool(context, "withRide")
))
.then(argument("forced", BoolArgumentType.bool())
.executes(context -> playAnimation(
context, BoolArgumentType.getBool(context, "withRide"))
)
)
)
)
)));
animCommand.then(literal("remove")
.requires(cs -> cs.hasPermission(2))
.executes(PlayAnimationCommand::clearAnimation)
.then(argument("players", EntityArgument.players())
.requires(cs -> cs.hasPermission(2))
.executes(PlayAnimationCommand::clearAnimation)
.then(argument("layer", AnimationLayerArgument.layer())
.executes(PlayAnimationCommand::removeAnimation)
)
)
);
}
private static int playAnimation(CommandContext<CommandSourceStack> ctx, boolean withRide) {
CommandSourceStack source = ctx.getSource();
try {
Collection<ServerPlayer> players = EntityArgument.getPlayers(ctx, "players");
String animation = AnimationArgument.getAnimation(ctx, "animation").replace("$", ":");
String layer = AnimationLayerArgument.getLayer(ctx, "layer").replace("$", ":");
ResourceLocation layerLocation = new ResourceLocation(layer);
ResourceLocation animLocation = new ResourceLocation(animation);
boolean animationPresent = AnimationUtils.isAnimationPresent(animLocation);
boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation);
if(!animationPresent) {
source.sendFailure(Component.literal("Animation is not present.").withStyle(ChatFormatting.RED));
return 0;
}
if(!layerPresent) {
source.sendFailure(Component.literal("Layer is not present.").withStyle(ChatFormatting.RED));
return 0;
}
Set<ServerPlayer> playerSet = Set.copyOf(players);
playerSet.forEach(player -> {
if(withRide) {
boolean forced = false;
try { forced = BoolArgumentType.getBool(ctx, "forced");}
catch (Exception ignored) {}
if(AnimationUtils.playAnimationWithRide(player, layerLocation, animLocation, forced)) {
players.remove(player);
}
} else {
if (AnimationUtils.playAnimation(player, layerLocation, animLocation)) {
players.remove(player);
}
}
});
MutableComponent fail = Component.literal("Fail to play animation with: ");
int successNum = playerSet.size() - players.size();
if(successNum > 0) {
MutableComponent success = Component.literal("Success to play animation with ")
.append(successNum + "")
.append(" player");
if(successNum > 1) success.append("s.");
else success.append(".");
source.sendSuccess(() -> success.withStyle(ChatFormatting.GREEN), true);
}
List<ServerPlayer> list = players.stream().toList();
if(!list.isEmpty()) {
for (int i = 0; i < list.size(); i++) {
fail.append(list.get(i).getName());
if (i < list.size() - 1) {
fail.append(", ");
}
}
fail.append(".");
source.sendFailure(fail.withStyle(ChatFormatting.RED));
}
} catch (Exception e) {
source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED));
return 0;
}
return 1;
}
private static int removeAnimation(CommandContext<CommandSourceStack> ctx) {
CommandSourceStack source = ctx.getSource();
try {
Collection<ServerPlayer> players = EntityArgument.getPlayers(ctx, "players");
String layer = AnimationLayerArgument.getLayer(ctx, "layer").replace("$", ":");
ResourceLocation layerLocation = new ResourceLocation(layer);
boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation);
if(!layerPresent) {
source.sendFailure(Component.literal("Layer is not present.").withStyle(ChatFormatting.RED));
return 0;
}
Set<ServerPlayer> playerSet = Set.copyOf(players);
playerSet.forEach(player -> {
if(player.getVehicle() instanceof AnimationRideEntity rideEntity && rideEntity.getLayer().equals(layerLocation)) {
player.unRide();
players.remove(player);
}
if (AnimationUtils.removeAnimation(player, layerLocation)) {
players.remove(player);
}
});
MutableComponent fail = Component.literal("Fail to remove animation with: ");
int successNum = playerSet.size() - players.size();
if(successNum > 0) {
MutableComponent success = Component.literal("Success to remove animation with ")
.append(successNum + "")
.append(" player");
if(successNum > 1) success.append("s.");
else success.append(".");
source.sendSuccess(() -> success.withStyle(ChatFormatting.GREEN), true);
}
List<ServerPlayer> list = players.stream().toList();
if(!list.isEmpty()) {
for (int i = 0; i < list.size(); i++) {
fail.append(list.get(i).getName());
if (i < list.size() - 1) {
fail.append(", ");
}
}
fail.append(".");
source.sendFailure(fail.withStyle(ChatFormatting.RED));
}
} catch (Exception e) {
source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED));
return 0;
}
return 1;
}
private static int clearAnimation(CommandContext<CommandSourceStack> ctx) {
CommandSourceStack source = ctx.getSource();
try {
Collection<ServerPlayer> players;
try {players = EntityArgument.getPlayers(ctx, "players");}
catch (Exception ignored) { players = Set.of(source.getPlayerOrException()); }
Set.copyOf(players).forEach(player -> {
if(player.getVehicle() instanceof AnimationRideEntity) {
player.unRide();
}
AnimationUtils.clearAnimation(player);
});
source.sendSuccess(() -> Component.literal("Animation cleared.").withStyle(ChatFormatting.GREEN), true);
} catch (Exception e) {
source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED));
return 0;
}
return 1;
}
}

View File

@ -0,0 +1,68 @@
package com.linearpast.sccore.animation.command.argument;
import com.linearpast.sccore.animation.registry.AnimationRegistry;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class AnimationArgument implements ArgumentType<String> {
private static final Supplier<Collection<String>> EXAMPLES = () -> AnimationRegistry.getAnimations().keySet().stream()
.map(ResourceLocation::toString).collect(Collectors.toSet());
private static final DynamicCommandExceptionType UNKNOWN_TYPE = new DynamicCommandExceptionType(
animation -> Component.literal("Unknow animation : " + animation.toString())
);
private final Set<String> animationNames;
public AnimationArgument() {
this.animationNames = AnimationRegistry.getAnimations().keySet().stream()
.map(ResourceLocation::toString).collect(Collectors.toSet());
}
public static AnimationArgument animation() {
return new AnimationArgument();
}
public static String getAnimation(CommandContext<CommandSourceStack> context, String name) {
return context.getArgument(name, String.class);
}
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(animationNames, builder);
}
public Collection<String> getExamples() {
return EXAMPLES.get();
}
public String parse(StringReader reader) throws CommandSyntaxException {
int start = reader.getCursor();
while (reader.canRead() && canRead(reader.peek())) {
reader.skip();
}
String s = reader.getString().substring(start, reader.getCursor());
if (!animationNames.contains(s)) {
throw UNKNOWN_TYPE.create(s);
} else {
return s;
}
}
private static boolean canRead(char peek) {
boolean origin = StringReader.isAllowedInUnquotedString(peek);
return origin || peek == ':';
}
}

View File

@ -0,0 +1,69 @@
package com.linearpast.sccore.animation.command.argument;
import com.linearpast.sccore.animation.event.AnimationLayerRegistry;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class AnimationLayerArgument implements ArgumentType<String> {
private static final Supplier<Collection<String>> EXAMPLES = () -> AnimationLayerRegistry.getAnimLayers().keySet().stream()
.map(ResourceLocation::toString).collect(Collectors.toSet());
private static final DynamicCommandExceptionType UNKNOWN_TYPE = new DynamicCommandExceptionType(
layer -> Component.literal("Unknow layer : " + layer.toString())
);
private final Set<String> animationLayers;
public AnimationLayerArgument() {
this.animationLayers = AnimationLayerRegistry.getAnimLayers().keySet().stream()
.map(ResourceLocation::toString).collect(Collectors.toSet());
}
public static AnimationLayerArgument layer() {
return new AnimationLayerArgument();
}
public static String getLayer(CommandContext<CommandSourceStack> context, String name) {
return context.getArgument(name, String.class);
}
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(animationLayers, builder);
}
public Collection<String> getExamples() {
return EXAMPLES.get();
}
public String parse(StringReader reader) throws CommandSyntaxException {
int start = reader.getCursor();
while (reader.canRead() && canRead(reader.peek())) {
reader.skip();
}
String s = reader.getString().substring(start, reader.getCursor());
if (!animationLayers.contains(s)) {
throw UNKNOWN_TYPE.create(s);
} else {
return s;
}
}
private static boolean canRead(char peek) {
boolean origin = StringReader.isAllowedInUnquotedString(peek);
return origin || peek == ':';
}
}

View File

@ -0,0 +1,36 @@
package com.linearpast.sccore.animation.command.client;
import com.linearpast.sccore.animation.AnimationUtils;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import static net.minecraft.commands.Commands.literal;
@OnlyIn(Dist.CLIENT)
public class AnimationRefreshCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> animCommand){
animCommand.then(literal("refresh").executes(AnimationRefreshCommand::refresh));
}
private static int refresh(CommandContext<CommandSourceStack> ctx){
CommandSourceStack source = ctx.getSource();
try {
Minecraft instance = Minecraft.getInstance();
LocalPlayer player = instance.player;
if(player == null) throw new RuntimeException();
AnimationUtils.refreshAnimation(player);
source.sendSuccess(() -> Component.literal("Animation refreshed.").withStyle(ChatFormatting.GREEN), true);
} catch (Exception e) {
source.sendFailure(Component.literal("Run command failure.").withStyle(ChatFormatting.RED));
return 0;
}
return 1;
}
}

View File

@ -0,0 +1,120 @@
package com.linearpast.sccore.animation.data;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
public class Animation {
private final ResourceLocation name;
private KeyframeAnimation animation;
private float heightModifier = 1.0f;
private float camYaw;
private float camPitch;
private float camRoll;
private float camY;
private @Nullable Animation.LyingType lyingType;
private @Nullable Ride ride;
public Animation(ResourceLocation name) {
this.name = name;
}
public enum LyingType {
RIGHT,
LEFT,
FRONT,
BACK
}
public Animation withLyingType(@Nullable Animation.LyingType lyingType) {
this.lyingType = lyingType;
if(lyingType == null) return this;
this.camY = -1.3f;
this.camPitch = -90.0f;
this.heightModifier = 0.3f;
switch (lyingType) {
case RIGHT -> {
this.camRoll = 90.0f;
this.camYaw = 90.0f;
}
case LEFT -> {
this.camRoll = -90.0f;
this.camYaw = -90.0f;
}
case BACK -> this.camPitch = 90.0f;
}
return this;
}
public Animation withHeightModifier(float heightModifier) {
this.heightModifier = heightModifier;
return this;
}
public Animation withCamYaw(float camYaw) {
this.camYaw = camYaw;
return this;
}
public Animation withCamPitch(float camPitch) {
this.camPitch = camPitch;
return this;
}
public Animation withCamRoll(float camRoll) {
this.camRoll = camRoll;
return this;
}
public Animation withCamY(float camY) {
this.camY = camY;
return this;
}
public Animation withRide(Ride ride) {
this.ride = ride;
return this;
}
public void setLyingType(@Nullable LyingType lyingType) {
this.lyingType = lyingType;
}
public float getCamRoll() {
return camRoll;
}
public float getCamPitch() {
return camPitch;
}
public float getCamYaw() {
return camYaw;
}
@Nullable
public KeyframeAnimation getAnimation() {
return PlayerAnimationRegistry.getAnimation(name);
}
public @Nullable Animation.LyingType getLyingType() {
return lyingType;
}
public float getCamY() {
return camY;
}
public float getHeightModifier() {
return heightModifier;
}
public @Nullable Ride getRide() {
return ride;
}
public ResourceLocation getName() {
return name;
}
}

View File

@ -0,0 +1,87 @@
package com.linearpast.sccore.animation.data;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import java.util.ArrayList;
import java.util.List;
public class Ride {
private final List<ResourceLocation> componentAnimations = new ArrayList<>();
private Vec3 offset = Vec3.ZERO;
private int existTick;
private float xRot;
private float yRot;
public static Ride create() {
return new Ride();
}
public Ride withOffset(Vec3 offset) {
this.offset = offset;
return this;
}
public Ride withExistTick(int existTick) {
this.existTick = existTick;
return this;
}
public Ride withXRot(float xRot) {
this.xRot = xRot;
return this;
}
public Ride withYRot(float yRot) {
this.yRot = yRot;
return this;
}
public Ride setComponentAnimations(List<ResourceLocation> animations) {
this.componentAnimations.clear();
this.componentAnimations.addAll(animations);
return this;
}
public Ride addComponentAnimation(ResourceLocation animation) {
this.componentAnimations.add(animation);
return this;
}
public float getXRot() {
return xRot;
}
public void setXRot(float xRot) {
this.xRot = xRot;
}
public float getYRot() {
return yRot;
}
public void setYRot(float yRot) {
this.yRot = yRot;
}
public Vec3 getOffset() {
return offset;
}
public void setOffset(Vec3 offset) {
this.offset = offset;
}
public int getExistTick() {
return existTick;
}
public void setExistTick(int existTick) {
this.existTick = existTick;
}
public List<ResourceLocation> getComponentAnimations() {
return componentAnimations;
}
}

View File

@ -0,0 +1,213 @@
package com.linearpast.sccore.animation.entity;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.data.Animation;
import com.linearpast.sccore.animation.data.Ride;
import com.linearpast.sccore.animation.registry.AnimationEntities;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkHooks;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AnimationRideEntity extends Entity {
public AnimationRideEntity(Level pLevel) {
super(AnimationEntities.RIDE.get(), pLevel);
this.noPhysics = true;
}
private static final String Animation = "Animation";
private static final String PlayerUUID = "PlayerUUID";
private static final String Layer = "Layer";
private final Set<ServerPlayer> players = new HashSet<>();
private Animation animation;
private ServerPlayer player;
private ResourceLocation layer;
public AnimationRideEntity(ServerPlayer pPlayer, ResourceLocation layer, Animation animation) {
this(pPlayer.level());
this.player = pPlayer;
this.layer = layer;
this.animation = animation;
}
public ResourceLocation getLayer() {
return layer;
}
public Set<ServerPlayer> getPlayers() {
return players;
}
public Animation getAnimation() {
return animation;
}
@Override
protected void defineSynchedData() {}
@Override
protected void readAdditionalSaveData(@NotNull CompoundTag pCompound) {
String string = pCompound.getString(Animation);
if(!string.isEmpty()) {
ResourceLocation rl = new ResourceLocation(string);
Animation anim = AnimationUtils.getAnimation(rl);
if(anim != null) {
this.animation = anim;
}
}
if(pCompound.contains(PlayerUUID)) {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if(server != null) {
this.player = server.getPlayerList().getPlayer(pCompound.getUUID(PlayerUUID));
}
}
if(pCompound.contains(Layer)) {
this.layer = new ResourceLocation(pCompound.getString(Layer));
}
}
@Override
protected void addAdditionalSaveData(@NotNull CompoundTag pCompound) {
pCompound.putUUID(PlayerUUID, player.getUUID());
pCompound.putString(Layer, layer.toString());
if(animation != null) {
pCompound.putString(Animation, animation.getName().toString());
}
}
@Override
public void tick() {
super.tick();
if(!this.level().isClientSide) {
Ride ride = animation == null ? null : animation.getRide();
if(!this.getPassengers().contains(player) || (ride != null && ride.getExistTick() > 0 && this.tickCount >= ride.getExistTick())) {
if(player != null) {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if(data != null) {
AnimationUtils.removeAnimation(player, layer);
data.setRideAnimLayer(null);
}
}
if(ride != null) players.forEach(player -> {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
AnimationUtils.removeAnimation(player, layer);
if(data != null) data.setRideAnimLayer(null);
});
this.remove(RemovalReason.DISCARDED);
}
}
}
@Override
public double getPassengersRidingOffset() {
return 0.0;
}
@Override
protected boolean canRide(@NotNull Entity entity) {
return true;
}
@Override
public boolean shouldRiderSit() {
return false;
}
@Override
public @NotNull Packet<ClientGamePacketListener> getAddEntityPacket(){
return NetworkHooks.getEntitySpawningPacket(this);
}
public static boolean create(ServerPlayer pPlayer, ResourceLocation layer, ResourceLocation animation, boolean force) {
Animation anim = AnimationUtils.getAnimation(animation);
if(anim == null) return false;
if(anim.getRide() == null) return false;
AnimationRideEntity seat = new AnimationRideEntity(pPlayer, layer, anim);
float xRot = anim.getRide().getXRot();
float yRot = anim.getRide().getYRot();
if(xRot == 0 && yRot == 0) seat.setRot(pPlayer.getXRot(), pPlayer.getYRot());
else seat.setRot(yRot, xRot);
Vec3 pos = pPlayer.position();
pos.add(anim.getRide().getOffset());
seat.setPos(pos.x, pos.y + 0.35f, pos.z);
pPlayer.level().addFreshEntity(seat);
pPlayer.startRiding(seat, force);
return AnimationUtils.playAnimation(pPlayer, layer, animation);
}
@Override
protected void positionRider(@NotNull Entity pPassenger, @NotNull MoveFunction pCallback) {
super.positionRider(pPassenger, pCallback);
pPassenger.setYBodyRot(this.getYRot());
}
@Override
public void onPassengerTurned(@NotNull Entity pEntityToUpdate) {
pEntityToUpdate.setYBodyRot(this.getYRot());
}
@Override
public @NotNull Vec3 getDismountLocationForPassenger(@NotNull LivingEntity entity) {
Ride ride = animation.getRide();
if(ride != null) {
Vec3 position = entity.position();
return position.subtract(ride.getOffset());
}
return entity.position();
}
@Override
protected void addPassenger(@NotNull Entity entity) {
int passengerNum = getPassengers().size();
super.addPassenger(entity);
if(passengerNum == 0) return;
if(entity instanceof ServerPlayer serverPlayer) {
Ride ride = animation.getRide();
if(ride == null) return;
List<ResourceLocation> componentAnimations = ride.getComponentAnimations();
if(componentAnimations.isEmpty()) return;
if(passengerNum > componentAnimations.size()) return;
ResourceLocation location = componentAnimations.get(passengerNum - 1);
AnimationUtils.playAnimation(serverPlayer, layer, location);
AnimationUtils.syncAnimation(serverPlayer, player, layer);
players.add(serverPlayer);
}
}
@Override
protected void removePassenger(@NotNull Entity entity) {
super.removePassenger(entity);
if(entity instanceof ServerPlayer serverPlayer) {
AnimationUtils.removeAnimation(serverPlayer, layer);
AnimationDataCapability.getCapability(serverPlayer).ifPresent(data -> data.setRideAnimLayer(null));
players.remove(serverPlayer);
}
}
@Override
protected boolean canAddPassenger(@NotNull Entity pPassenger) {
boolean isServerPlayer = pPassenger instanceof ServerPlayer;
int size = players.size();
Ride ride = animation.getRide();
if(ride == null) return false;
int maxSize = ride.getComponentAnimations().size();
return isServerPlayer && size < maxSize;
}
}

View File

@ -0,0 +1,33 @@
package com.linearpast.sccore.animation.entity.renderer;
import com.linearpast.sccore.animation.entity.AnimationRideEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
public class AnimationRideRenderer extends EntityRenderer<AnimationRideEntity> {
public AnimationRideRenderer(EntityRendererProvider.Context pContext) {
super(pContext);
}
@Override
public @NotNull ResourceLocation getTextureLocation(@NotNull AnimationRideEntity pEntity) {
return new ResourceLocation("");
}
@Override
public boolean shouldRender(@NotNull AnimationRideEntity pLivingEntity, @NotNull Frustum pCamera, double pCamX, double pCamY, double pCamZ) {
return false;
}
@Override
public void render(@NotNull AnimationRideEntity pEntity, float pEntityYaw, float pPartialTick, @NotNull PoseStack pPoseStack, @NotNull MultiBufferSource pBuffer, int pPackedLight) {}
@Override
protected void renderNameTag(@NotNull AnimationRideEntity pEntity, @NotNull Component pDisplayName, @NotNull PoseStack pPoseStack, @NotNull MultiBufferSource pBuffer, int pPackedLight) {}
}

View File

@ -0,0 +1,49 @@
package com.linearpast.sccore.animation.event;
import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent;
import dev.kosmx.playerAnim.api.layered.IAnimation;
import dev.kosmx.playerAnim.api.layered.ModifierLayer;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import java.util.HashMap;
import java.util.Map;
public class AnimationLayerRegistry {
private static final Map<ResourceLocation, Integer> animLayers = new HashMap<>();
public static void onClientSetup(FMLClientSetupEvent event) {
onCommonSetUp(null);
event.enqueueWork(() -> animLayers.forEach((location, integer) ->
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(location, integer,
AnimationLayerRegistry::registerPlayerAnimation
)
));
}
public static void onCommonSetUp(FMLCommonSetupEvent event) {
AnimationLayerRegisterEvent layerEvent = new AnimationLayerRegisterEvent();
ModLoader.get().postEvent(layerEvent);
animLayers.putAll(layerEvent.getLayers());
}
public static void registerPlayerAnimation(ResourceLocation location, int priority) {
animLayers.put(location, priority);
}
public static Map<ResourceLocation, Integer> getAnimLayers() {
return animLayers;
}
public static boolean isLayerPresent(ResourceLocation layer) {
return animLayers.containsKey(layer);
}
private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) {
return new ModifierLayer<>();
}
}

View File

@ -0,0 +1,11 @@
package com.linearpast.sccore.animation.event;
import com.linearpast.sccore.animation.entity.renderer.AnimationRideRenderer;
import com.linearpast.sccore.animation.registry.AnimationEntities;
import net.minecraftforge.client.event.EntityRenderersEvent;
public class EntityRendererRegistry {
public static void registerEntityRenderer(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(AnimationEntities.RIDE.get(), AnimationRideRenderer::new);
}
}

View File

@ -0,0 +1,68 @@
package com.linearpast.sccore.animation.event.client;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.data.Animation;
import dev.kosmx.playerAnim.core.util.MathHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.ViewportEvent;
import java.util.Comparator;
@OnlyIn(Dist.CLIENT)
public class CameraAnglesModify {
private static float targetYaw = 0.0F;
private static float targetPitch = 0.0F;
private static float targetRoll = 0.0F;
private static float currentYaw = 0.0F;
private static float currentPitch = 0.0F;
private static float currentRoll = 0.0F;
public static void changeCameraView(ViewportEvent.ComputeCameraAngles event){
Minecraft minecraft = Minecraft.getInstance();
if (minecraft.options.getCameraType().isFirstPerson()) {
LocalPlayer player = minecraft.player;
if (player != null) {
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if(data == null) return;
Animation animation = null;
try {
animation = data.getAnimations().values().stream()
.map(AnimationUtils::getAnimation)
.min(Comparator.comparingDouble(anim -> {
if (anim == null) return 1.0f;
return anim.getHeightModifier();
})).orElse(null);
}catch (Exception ignored){}
if(animation != null) {
targetPitch = animation.getCamPitch();
targetYaw = animation.getCamYaw();
targetRoll = animation.getCamRoll();
} else {
targetYaw = 0.0F;
targetPitch = 0.0F;
targetRoll = 0.0F;
}
}
float var3 = Minecraft.getInstance().getDeltaFrameTime();
float var4 = var3 / 5.0F;
if (var4 == 0.0F) {
var4 = 0.0022857143F;
}
currentPitch = MathHelper.lerp(var4, currentPitch, targetPitch);
currentYaw = MathHelper.lerp(var4, currentYaw, targetYaw);
currentRoll = MathHelper.lerp(var4, currentRoll, targetRoll);
event.setPitch(event.getPitch() + currentPitch);
event.setYaw(event.getYaw() + currentYaw);
event.setRoll(event.getRoll() + currentRoll);
}
}
}

View File

@ -0,0 +1,37 @@
package com.linearpast.sccore.animation.event.client;
import com.linearpast.sccore.animation.AnimationUtils;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.event.TickEvent;
import java.util.HashMap;
import java.util.Map;
@OnlyIn(Dist.CLIENT)
public class ClientPlayerTick {
public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
if (event.side.isClient() && event.phase == TickEvent.Phase.START) {
Player player = event.player;
if(player.tickCount % 10 != 0) return;
if (!(player instanceof AbstractClientPlayer clientPlayer)) return;
AnimationUtils.refreshAnimation(clientPlayer);
}
}
public static Map<Runnable, Map.Entry<Integer, Integer>> runs = new HashMap<>();
public static void delayRuns(TickEvent.PlayerTickEvent event) {
if (event.side.isClient() && event.phase == TickEvent.Phase.END) {
Map.copyOf(runs).forEach((runnable, countMap) -> {
if(countMap.getValue() >= countMap.getKey()) {
runnable.run();
runs.remove(runnable);
} else {
countMap.setValue(countMap.getValue() + 1);
}
});
}
}
}

View File

@ -0,0 +1,29 @@
package com.linearpast.sccore.animation.event.create;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import java.util.HashMap;
import java.util.Map;
/**
* You can listen this event to register an animation layer <br>
* Generally, the static function is better.
* @see com.linearpast.sccore.animation.AnimationUtils#registerAnimationLayer
*/
public class AnimationLayerRegisterEvent extends Event implements IModBusEvent {
private final Map<ResourceLocation, Integer> layers = new HashMap<>();
public AnimationLayerRegisterEvent() {
}
public Map<ResourceLocation, Integer> getLayers() {
return layers;
}
public void putLayer(ResourceLocation key, Integer value) {
layers.put(key, value);
}
}

View File

@ -0,0 +1,9 @@
package com.linearpast.sccore.animation.mixin;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public interface IMixinKeyframeAnimationPlayer {
void sccore$setCurrentTick(int tick);
}

View File

@ -0,0 +1,76 @@
package com.linearpast.sccore.animation.network.toclient;
import com.linearpast.sccore.animation.AnimationPlayer;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync;
import com.linearpast.sccore.capability.network.SimpleCapabilityPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class AnimationCapabilityPacket extends SimpleCapabilityPacket<Player> {
public AnimationCapabilityPacket(CompoundTag data) {
super(data);
}
public AnimationCapabilityPacket(FriendlyByteBuf buf) {
super(buf);
}
@Override
public void handler(NetworkEvent.Context context) {
context.setPacketHandled(true);
Minecraft instance = Minecraft.getInstance();
ClientLevel level = instance.level;
if (level == null) return;
CompoundTag nbt = getData();
Player player = level.getPlayerByUUID(nbt.getUUID(SimplePlayerCapabilitySync.OwnerUUID));
try {
IAnimationCapability data = getCapability(player);
testPlayAnimations((AbstractClientPlayer) player, nbt, data);
syncData(nbt, data);
}catch (Exception ignored) {}
}
@Override
public @Nullable IAnimationCapability getCapability(Player player) {
return AnimationDataCapability.getCapability(player).orElse(null);
}
private void testPlayAnimations(AbstractClientPlayer player, CompoundTag tag, IAnimationCapability data) {
Set<ResourceLocation> oldLayerSet = new HashSet<>();
if(data != null) oldLayerSet.addAll(data.getAnimations().keySet());
CompoundTag animMap = tag.getCompound(AnimationDataCapability.AnimMap);
for (String newLayerString : animMap.getAllKeys()) {
ResourceLocation newLayerLocation = new ResourceLocation(newLayerString);
String newAnimString = animMap.getString(newLayerString);
ResourceLocation newAnimLocation = newAnimString.isEmpty() ? null : new ResourceLocation(newAnimString);
ResourceLocation oldAnimLocation = data == null ? null : data.getAnimation(newLayerLocation);
if (!Objects.equals(newAnimLocation, oldAnimLocation)) {
AnimationPlayer.playAnimation(player, newLayerLocation, newAnimLocation);
}
oldLayerSet.remove(newLayerLocation);
}
for (ResourceLocation oldLayerLocation : oldLayerSet) {
AnimationPlayer.playAnimation(player, oldLayerLocation, null);
}
if(!tag.contains(AnimationDataCapability.RideAnimLayer)) {
if(data != null && data.getRideAnimLayer() != null) {
AnimationUtils.playAnimation(null, data.getRideAnimLayer(), null);
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.linearpast.sccore.animation.network.toclient;
import com.linearpast.sccore.animation.AnimationPlayer;
import com.linearpast.sccore.animation.event.client.ClientPlayerTick;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkEvent;
import java.util.AbstractMap;
import java.util.UUID;
import java.util.function.Supplier;
public class SyncAnimationPacket {
private final UUID playerUUID;
private final UUID targetUUID;
private final ResourceLocation layer;
public SyncAnimationPacket(UUID playerUUID, UUID targetUUID, ResourceLocation layer) {
this.playerUUID = playerUUID;
this.targetUUID = targetUUID;
this.layer = layer;
}
public SyncAnimationPacket(FriendlyByteBuf buf) {
this.playerUUID = buf.readUUID();
this.targetUUID = buf.readUUID();
this.layer = buf.readResourceLocation();
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(this.playerUUID);
buf.writeUUID(this.targetUUID);
buf.writeResourceLocation(this.layer);
}
public void handle(Supplier<NetworkEvent.Context> supplier) {
NetworkEvent.Context context = supplier.get();
context.enqueueWork(() -> {
context.setPacketHandled(true);
Minecraft instance = Minecraft.getInstance();
ClientLevel level = instance.level;
if(level == null) return;
AbstractClientPlayer player = (AbstractClientPlayer) level.getPlayerByUUID(playerUUID);
AbstractClientPlayer target = (AbstractClientPlayer) level.getPlayerByUUID(targetUUID);
ClientPlayerTick.runs.put(
() -> AnimationPlayer.syncAnimation(player, target, layer),
new AbstractMap.SimpleEntry<>(5, 0)
);
});
}
}

View File

@ -0,0 +1,46 @@
package com.linearpast.sccore.animation.network.toserver;
import com.linearpast.sccore.animation.AnimationPlayer;
import com.linearpast.sccore.animation.event.AnimationLayerRegistry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
public class PlayAnimationRequestPacket {
private final ResourceLocation layer;
private @Nullable ResourceLocation animation;
public PlayAnimationRequestPacket(ResourceLocation layer, @Nullable ResourceLocation animation) {
this.layer = layer;
this.animation = animation;
}
public PlayAnimationRequestPacket(FriendlyByteBuf buf){
this.layer = buf.readResourceLocation();
try {
this.animation = buf.readResourceLocation();
} catch (Exception e) {
this.animation = null;
}
}
public void encode(FriendlyByteBuf buf){
buf.writeResourceLocation(layer);
if(animation != null){
buf.writeResourceLocation(animation);
}
}
public void handle(Supplier<NetworkEvent.Context> supplier){
NetworkEvent.Context context = supplier.get();
context.enqueueWork(() -> {
context.setPacketHandled(true);
if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) {
ServerPlayer sender = context.getSender();
if(sender == null) return;
AnimationPlayer.serverPlayAnimation(sender, layer, animation);
}
});
}
}

View File

@ -0,0 +1,54 @@
package com.linearpast.sccore.animation.network.toserver;
import com.linearpast.sccore.animation.AnimationPlayer;
import com.linearpast.sccore.animation.event.AnimationLayerRegistry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
public class PlayAnimationRidePacket {
private final ResourceLocation layer;
private @Nullable ResourceLocation animation;
private final boolean force;
public PlayAnimationRidePacket(ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) {
this.layer = layer;
this.animation = animation;
this.force = force;
}
public PlayAnimationRidePacket(FriendlyByteBuf buf) {
this.layer = buf.readResourceLocation();
this.force = buf.readBoolean();
try {
this.animation = buf.readResourceLocation();
} catch (Exception e) {
this.animation = null;
}
}
public void encode(FriendlyByteBuf buf) {
buf.writeResourceLocation(layer);
buf.writeBoolean(force);
if(animation != null) {
buf.writeResourceLocation(animation);
}
}
public void handle(Supplier<NetworkEvent.Context> supplier) {
NetworkEvent.Context context = supplier.get();
context.enqueueWork(() -> {
context.setPacketHandled(true);
if (AnimationLayerRegistry.getAnimLayers().containsKey(layer)) {
ServerPlayer sender = context.getSender();
if(sender == null) return;
AnimationPlayer.playAnimationWithRide(sender, layer, animation, force);
}
});
}
}

View File

@ -0,0 +1,45 @@
package com.linearpast.sccore.animation.network.toserver;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
public class RefreshAnimationPacket {
private final Set<ResourceLocation> needRemoves;
public RefreshAnimationPacket(Set<ResourceLocation> needRemoves) {
this.needRemoves = needRemoves;
}
public RefreshAnimationPacket(FriendlyByteBuf buf) {
int i = buf.readInt();
needRemoves = new HashSet<>();
for (int j = 0; j < i; ++j) {
needRemoves.add(buf.readResourceLocation());
}
}
public void encode(FriendlyByteBuf buf) {
buf.writeInt(needRemoves.size());
for (ResourceLocation needRemove : needRemoves) {
buf.writeResourceLocation(needRemove);
}
}
public void handle(Supplier<NetworkEvent.Context> supplier) {
NetworkEvent.Context context = supplier.get();
context.enqueueWork(() -> {
context.setPacketHandled(true);
ServerPlayer sender = context.getSender();
if(sender == null) return;
IAnimationCapability data = AnimationDataCapability.getCapability(sender).orElse(null);
if(data == null) return;
needRemoves.forEach(data::removeAnimation);
});
}
}

View File

@ -0,0 +1,33 @@
package com.linearpast.sccore.animation.registry;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.entity.AnimationRideEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobCategory;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class AnimationEntities {
public static final DeferredRegister<EntityType<?>> REGISTER = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, SnowyCrescentCore.MODID);
public static final RegistryObject<EntityType<AnimationRideEntity>> RIDE = register(
"animation_ride_entity", EntityType.Builder.<AnimationRideEntity>of((type, world) -> new AnimationRideEntity(world), MobCategory.MISC)
.sized(0.0F, 0.0F)
.setCustomClientFactory((spawnEntity, world) ->
new AnimationRideEntity(world)
)
);
private static <T extends Entity> RegistryObject<EntityType<T>> register(String name, EntityType.Builder<T> builder) {
return REGISTER.register(name, () -> builder.build(name));
}
public static void register(IEventBus modBus){
REGISTER.register(modBus);
}
}

View File

@ -0,0 +1,98 @@
package com.linearpast.sccore.animation.registry;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.data.Animation;
import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket;
import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket;
import com.linearpast.sccore.animation.network.toserver.PlayAnimationRequestPacket;
import com.linearpast.sccore.animation.network.toserver.PlayAnimationRidePacket;
import com.linearpast.sccore.animation.network.toserver.RefreshAnimationPacket;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry;
import com.linearpast.sccore.capability.network.CapabilityChannel;
import com.linearpast.sccore.core.ModChannel;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.network.NetworkDirection;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class AnimationRegistry {
private static final Map<ResourceLocation, Animation> animations = new HashMap<>();
public static Map<ResourceLocation, Animation> getAnimations() {
return animations;
}
@Nullable
public static Animation getAnimation(ResourceLocation location) {
return animations.get(location);
}
public static void registerAnimation(ResourceLocation location, Animation animation) {
animations.put(location, animation);
}
public static boolean isAnimationPresent(ResourceLocation location) {
return animations.containsKey(location);
}
public static void addAnimationListener(IEventBus forgeBus, IEventBus modBus) {
AnimationUtils.ANIMATION_RUNNER.testLoadedAndAddListener(forgeBus, modBus);
}
private static void registerAnimationCapability() {
CapabilityChannel channel = CapabilityUtils.createChannel();
CapabilityUtils.registerPlayerCapabilityWithNetwork(
AnimationDataCapability.key,
new PlayerCapabilityRegistry.CapabilityRecord<>(
AnimationDataCapability.class,
CapabilityManager.get(new CapabilityToken<>() {}),
IAnimationCapability.class
),
channel,
ModChannel.getAndAddCid(),
AnimationCapabilityPacket.class,
AnimationCapabilityPacket::new,
AnimationCapabilityPacket::encode,
AnimationCapabilityPacket::handle
);
}
private static void registerChannel() {
ModChannel.INSTANCE.messageBuilder(SyncAnimationPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT)
.decoder(SyncAnimationPacket::new)
.encoder(SyncAnimationPacket::encode)
.consumerMainThread(SyncAnimationPacket::handle)
.add();
ModChannel.INSTANCE.messageBuilder(PlayAnimationRequestPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER)
.decoder(PlayAnimationRequestPacket::new)
.encoder(PlayAnimationRequestPacket::encode)
.consumerMainThread(PlayAnimationRequestPacket::handle)
.add();
ModChannel.INSTANCE.messageBuilder(PlayAnimationRidePacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER)
.decoder(PlayAnimationRidePacket::new)
.encoder(PlayAnimationRidePacket::encode)
.consumerMainThread(PlayAnimationRidePacket::handle)
.add();
ModChannel.INSTANCE.messageBuilder(RefreshAnimationPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER)
.decoder(RefreshAnimationPacket::new)
.encoder(RefreshAnimationPacket::encode)
.consumerMainThread(RefreshAnimationPacket::handle)
.add();
}
public static void register(){
AnimationUtils.ANIMATION_RUNNER.testLoadedAndRun(() -> {
registerAnimationCapability();
registerChannel();
});
}
}

View File

@ -7,7 +7,7 @@ import com.linearpast.sccore.capability.data.player.PlayerCapabilityHandler;
import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry;
import com.linearpast.sccore.capability.network.CapabilityChannel;
import com.linearpast.sccore.capability.network.ICapabilityPacket;
import com.linearpast.sccore.network.Channel;
import com.linearpast.sccore.core.ModChannel;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
@ -24,15 +24,16 @@ import java.util.function.Supplier;
public class CapabilityUtils {
/**
* 同时注册玩家capability和对应的网络包
* @param key capability的唯一name
* @param capabilityRecord capability的注册数据
* @param channelRegister 应该提前创建好实例传入参阅{@link CapabilityUtils#createChannel}
* @param cid 网络频道索引
* @param clazz 网络包的类
* @param decoder 网络包的decode
* @param encoder 网络包的encode
* @param handler 网络包的handle
* Simultaneously register player capability and corresponding network packets
* @param key The unique name of capability
* @param capabilityRecord Registration data for capability
* @param channelRegister You should create an instance in advance to pass in, see: {@link CapabilityUtils#createChannel}
* @param cid Network Channel Index
* @param clazz Class of network packets
* @param decoder Decoding of network packets
* @param encoder Encoding of network packets
* @param handler Handle of network packets
* @param <T> extend {@code ICapabilityPacket<?>}
*/
public static <T extends ICapabilityPacket<?>> void registerPlayerCapabilityWithNetwork(
ResourceLocation key, PlayerCapabilityRegistry.CapabilityRecord<? extends ICapabilitySync<Player>> capabilityRecord,
@ -48,15 +49,16 @@ public class CapabilityUtils {
}
/**
* 同时注册实体capability和对应的网络包
* @param key capability的唯一name
* @param capabilityRecord capability的注册数据
* @param channelRegister 应该提前创建好实例传入参阅{@link CapabilityUtils#createChannel}
* @param cid 网络频道索引
* @param clazz 网络包的类
* @param decoder 网络包的decode
* @param encoder 网络包的encode
* @param handler 网络包的handle
* Simultaneously register entity capability and corresponding network packets
* @param key The unique name of capability
* @param capabilityRecord Registration data for capability
* @param channelRegister You should create an instance in advance to pass in, see: {@link CapabilityUtils#createChannel}
* @param cid Network Channel Index
* @param clazz Class of network packets
* @param decoder Decoding of network packets
* @param encoder Encoding of network packets
* @param handler Handle of network packets
* @param <T> {@code ICapabilityPacket<?>}
*/
public static <T extends ICapabilityPacket<?>> void registerEntityCapabilityWithNetwork(
ResourceLocation key, EntityCapabilityRegistry.CapabilityRecord<? extends ICapabilitySync<? extends Entity>> capabilityRecord,
@ -72,46 +74,47 @@ public class CapabilityUtils {
}
/**
* 通过此方法注册玩家capability仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
* 事件结束之前有效
* @param key capability的唯一name
* @param capabilityRecord 使用record存储了应该注册的capability的各项数据参阅{@link PlayerCapabilityRegistry.CapabilityRecord}
* See {@link PlayerCapabilityRegistry#registerCapability(ResourceLocation, PlayerCapabilityRegistry.CapabilityRecord)}
* @param key The unique name of capability.
* @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link PlayerCapabilityRegistry.CapabilityRecord}
* @param <T> extends {@code ICapabilitySync<Player>}
*/
public static <T extends ICapabilitySync<Player>> void registerPlayerCapability(ResourceLocation key, PlayerCapabilityRegistry.CapabilityRecord<T> capabilityRecord){
PlayerCapabilityRegistry.registerCapability(key, capabilityRecord);
}
/**
* 通过此方法注册实体capability仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
* 事件结束之前有效
* @param key capability的唯一name
* @param capabilityRecord 使用record存储了应该注册的capability的各项数据参阅{@link PlayerCapabilityRegistry.CapabilityRecord}
* See {@link EntityCapabilityRegistry#registerCapability(ResourceLocation, EntityCapabilityRegistry.CapabilityRecord)}
* @param key The unique name of capability.
* @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link EntityCapabilityRegistry.CapabilityRecord}
* @param <T> extends {@code ICapabilitySync<Entity>}
*/
public static <T extends ICapabilitySync<Entity>> void registerEntityCapability(ResourceLocation key, EntityCapabilityRegistry.CapabilityRecord<T> capabilityRecord){
EntityCapabilityRegistry.registerCapability(key, capabilityRecord);
}
/**
* 通过这个方法返回一个新的PlayerCapabilityChannel实例一般只有注册不同channel的网络包才会使用
* @param channel 你自己模组的channel
* @return 新的实例
* Return a new PlayerCapabilityChannel instance through this method<br>
* Generally, only network packets registered on different channels will be used
* @param channel Your own mod channel
* @return newInstances
*/
public static CapabilityChannel createChannel(SimpleChannel channel) {
return new CapabilityChannel(channel);
}
/**
* 通过这个方法返回本模组的Channel的PlayerCapabilityChannel实例
* @return 新的实例
* Return the PlayerCapabilityChannel instance of the Channel in SCCore through this method
* @return newInstances
*/
public static CapabilityChannel createChannel() {
return new CapabilityChannel(Channel.INSTANCE);
return new CapabilityChannel(ModChannel.INSTANCE);
}
/**
* 通过此方法监听capability事件从而启用所有功能<br>
* 重复调用不会发生任何事
* @param forgeBus forge事件总线
* By using this method to listen for capability events, all functions will be enabled<br>
* Repeated calls will not cause anything
* @param forgeBus Forge event bus
*/
public static void registerHandler(IEventBus forgeBus){
PlayerCapabilityHandler.register(forgeBus);
@ -119,11 +122,13 @@ public class CapabilityUtils {
}
/**
* 请通过该方法获取capability
* @param entity 目标实体类型 {@code <E extends Entity>}
* @param key capability的唯一名
* @param clazz 应返回的capability类型若为null则会返回 {@code ICapabilitySync<E>}
* @return 返回对应的capability
* Please obtain capability through this method
* @param entity Target entity, type: {@code <E extends Entity>}
* @param key The unique name of capability
* @param clazz The capability type that should be returned. If it is null, will return: {@code ICapabilitySync<E>}
* @param <E> extend {@code Entity}
* @param <T> extend {@code ICapabilitySync<E>}
* @return Return the corresponding capability
*/
@SuppressWarnings("unchecked")
@Nullable
@ -142,11 +147,13 @@ public class CapabilityUtils {
}
/**
* 请通过该方法获取capability
* @param entity 目标实体类型 {@code <E extends Entity>}
* @param key capability的唯一名
* @param clazz 应返回的capability类型若为null则会返回 {@code ICapabilitySync<E>}
* @return 返回对应的capability
* Please obtain capability through this method
* @param entity Target entity, type: {@code <E extends Player>}
* @param key The unique name of capability
* @param clazz The capability type that should be returned. If it is null, will return: {@code ICapabilitySync<E>}
* @param <E> extend {@code Player}
* @param <T> extend {@code ICapabilitySync<E>}
* @return Return the corresponding capability
*/
@SuppressWarnings("unchecked")
@Nullable
@ -165,10 +172,10 @@ public class CapabilityUtils {
}
/**
* 获取一个未转换类型的Cap
* @param entity 目标
* @param key cap的唯一名
* @return 未转换类型的cap
* Get a capability of an unconverted type
* @param entity Target
* @param key The unique name of capability
* @return An unconverted capability
*/
@Nullable
public static ICapabilitySync<?> getCapability(Entity entity, ResourceLocation key) {

View File

@ -1,23 +1,25 @@
package com.linearpast.sccore.capability.data;
import com.linearpast.sccore.capability.network.SimpleCapabilityPacket;
import com.linearpast.sccore.network.Channel;
import com.linearpast.sccore.core.ModChannel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.common.util.INBTSerializable;
public interface ICapabilitySync<T extends Entity> extends INBTSerializable<CompoundTag> {
/**
* You should call it to set it to true in the setter of each property to trigger automatic synchronization
* @param dirty dirty
*/
void setDirty(boolean dirty);
boolean isDirty();
CompoundTag toTag(CompoundTag tag);
void fromTag(CompoundTag tag);
/**
* 该方法重写时应该在最后调用super方法
* @param oldData 旧数据
* @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()}
* When this method is overridden, the super method should be called at the end
* @param oldData old data
* @param listenDone Whether to execute the completion method at the end: {@link ICapabilitySync#onCopyDone()}
*/
default void copyFrom(ICapabilitySync<?> oldData, boolean listenDone) {
this.setDirty(oldData.isDirty());
@ -25,40 +27,40 @@ public interface ICapabilitySync<T extends Entity> extends INBTSerializable<Comp
}
/**
* 当copy结束之后如果某些值需要被重定义你应该重写这个方法 <br>
* 多用于玩家 跨越维度/死亡 时重置数据
* After the copy is completed, if certain values need to be redefined, you should override this method <br>
* Commonly used for resetting data when players cross dimensions or die
*/
default void onCopyDone(){}
/**
* 一般情况下建议重写否则会以sccore的Channel实例发送<br>
* 服务端给全体玩家发送客户端同步数据
* In general, it is recommended to rewrite it, otherwise it will be sent as a Channel instance of SCCore<br>
* The server sends client synchronized data to all players
*/
default void sendToClient(){
Channel.sendAllPlayer(getDefaultPacket());
ModChannel.sendAllPlayer(getDefaultPacket());
}
/**
* 一般情况下建议重写否则会以sccore的Channel实例发送<br>
* 服务端给单个玩家发送客户端同步数据
* @param player 发送给的目标玩家
* In general, it is recommended to rewrite it, otherwise it will be sent as a Channel instance of SCCore<br>
* The server sends client synchronized data to a single player
* @param player Target player
*/
default void sendToClient(ServerPlayer player){
Channel.sendToPlayer(getDefaultPacket(), player);
ModChannel.sendToPlayer(getDefaultPacket(), player);
}
/**
* 重写该方法为你的Capability设定一个网络包类目前仅有客户端 <br>
* 当调用sendToClient方法时会从这里获取网络包直接发送 <br>
* 一般情况下你应该extends SimpleCapabilityPacket然后重写该方法返回你的子类
* @return 网络包类SimpleCapabilityPacket
* Rewrite this method to set a network packet class for your Capability <br>
* When calling the sendToClient method, network packets will be obtained from here and sent directly <br>
* In general, you should extend SimpleCapacityPackage and then override the method to return your subclass
* @return SimpleCapacityPacket, a network packet class
*/
SimpleCapabilityPacket<T> getDefaultPacket();
/**
* 当玩家登录 / 实体加入世界时的cao初始化时会调用 <br>
* 必须实现但可为空方法
* @param entity 目标
* When players log in or entity join the world, the capability initialization will be called <br>
* Must be implemented, but can be an empty method
* @param entity Target
*/
void attachInit(T entity);
}

View File

@ -18,8 +18,10 @@ public class EntityCapabilityHandler {
private static boolean isRegistered = false;
/**
* 应在Forge主线中调用以监听capability注册 <br>
* 建议在Mod构造方法里调用
* It should be called in the Forge mainline to listen to the capability registration <br>
* Suggest calling it in the Mod constructor method <br>
* Normally SCCore will call it, so you should not call it
* @param forgeBus forge event bus
*/
public static void register(IEventBus forgeBus) {
if (isRegistered) return;
@ -30,7 +32,10 @@ public class EntityCapabilityHandler {
isRegistered = true;
}
//注册 capability
/**
* Register capability
* @param event event
*/
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void registerCapability(RegisterCapabilitiesEvent event) {
EntityCapabilityRegistry.getCapabilityMap().values().forEach(record ->
@ -38,20 +43,24 @@ public class EntityCapabilityHandler {
);
}
//附加 capability
/**
* Attach capability to entity
* @param event event
*/
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
if(event.getObject() instanceof Entity entity) {
EntityCapabilityRegistry.getCapabilityMap().forEach((key, record) -> {
if(record.target().isInstance(entity)) {
try {
ICapabilitySync<?> capabilitySync = (ICapabilitySync<?>) record.aClass().getDeclaredConstructor().newInstance();
event.addCapability(key, new EntityCapabilityProvider<>(key, capabilitySync));
} catch (Exception e) {
log.error("Failed to instantiate capability sync class {}. Your capability register is wrong.", record.aClass(), e);
}
}
});
EntityCapabilityRegistry.getCapabilityMap().forEach((key, record) ->
record.targets().forEach(target -> {
if(target.isInstance(entity)) {
try {
ICapabilitySync<?> capabilitySync = (ICapabilitySync<?>) record.aClass().getDeclaredConstructor().newInstance();
event.addCapability(key, new EntityCapabilityProvider<>(key, capabilitySync));
} catch (Exception e) {
log.error("Failed to instantiate capability sync class {}. Your capability register is wrong.", record.aClass(), e);
}
}
}));
}
}
}

View File

@ -13,13 +13,19 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* cap的最终 序列化反序列化获取方法
* @param <C> 继承 {@link ICapabilitySync}
* The final serialization, deserialization, and retrieval methods of capability
* @param <C> extends {@link ICapabilitySync}
*/
@AutoRegisterCapability
public class EntityCapabilityProvider<C extends ICapabilitySync<? extends Entity>> implements ICapabilitySerializable<CompoundTag> {
private final C instance;
private final ResourceLocation resourceLocation;
/**
* Constructor
* @param resourceLocation key
* @param instance instance
*/
public EntityCapabilityProvider(ResourceLocation resourceLocation, C instance) {
this.resourceLocation = resourceLocation;
this.instance = instance;

View File

@ -7,46 +7,47 @@ import net.minecraftforge.common.capabilities.Capability;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class EntityCapabilityRegistry {
public static final EntityCapabilityRegistry CAPABILITIES = new EntityCapabilityRegistry();
private final Map<ResourceLocation, CapabilityRecord<?>> capabilityRecordMap = new HashMap<>();
/**
* 通过此方法注册capability仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
* 事件结束之前有效
* @param key capability的唯一name
* @param capabilityRecord 使用record存储了应该注册的capability的各项数据参阅{@link CapabilityRecord}
* Registering entity capabilities through this method only applies to {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}<br>
* @param key The unique name of capability.
* @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link EntityCapabilityRegistry.CapabilityRecord}
*/
public static void registerCapability(ResourceLocation key, CapabilityRecord<?> capabilityRecord) {
CAPABILITIES.capabilityRecordMap.put(key, capabilityRecord);
}
/**
* 通过此方法获取对应的capability数据
* @param key 根据key获取
* @return capability 数据
* Obtain corresponding capability data through this method
* @param key Obtain based on key
* @return capability
*/
public static CapabilityRecord<?> getCapabilityRecord(ResourceLocation key){
return CAPABILITIES.capabilityRecordMap.get(key);
}
/**
* 获取所有key对应的cap数据集
* @return map
*/
public static Map<ResourceLocation, CapabilityRecord<?>> getCapabilityMap(){
return CAPABILITIES.capabilityRecordMap;
}
/**
* 记录capability的注册数据
* @param aClass 最终会附加给实体的实例的类应该是实现了clazz的类
* @param capability 注册时一般默认{@code CapabilityManager.get(new CapabilityToken<>(){})}即可
* @param interfaceClass instance类对应的实例对应的接口类比如ICapabilitySync.class
* @param target capability附加的目标类型
* Record the registration data of capability
* @param aClass The instance that will ultimately be attached to the entity. Should be an instance of ICapabilitySync
* @param capability In general, it is not necessary to initialize it, default: <span>{@code CapabilityManager.get(new CapabilityToken<>(){})}</span>
* @param interfaceClass The interface class corresponding to the instance, such as: ICapabilitySync.class.
* @param targets Targets types attached to capability
*/
public record CapabilityRecord<T extends ICapabilitySync<? extends Entity>>(Class<?> aClass, Capability<T> capability, Class<T> interfaceClass, Class<? extends Entity> target) {
public record CapabilityRecord<T extends ICapabilitySync<? extends Entity>>(
Class<?> aClass,
Capability<T> capability,
Class<T> interfaceClass,
Set<Class<? extends Entity>> targets
) {
}
}

View File

@ -12,10 +12,10 @@ import net.minecraftforge.event.entity.player.PlayerEvent;
public class EntityCapabilityRemainder {
/**
* 玩家追踪实体事件<br>
* 当有其他实体被加载时客户端需要对方的capability该事件可以主动发送<br>
* 会调用{@link ICapabilitySync#sendToClient(ServerPlayer)}
* @param event 追踪事件实例
* Player start tracking an entity event<br>
* When other entities are loaded, the client requires the capabilities of the other party, and this event can be actively sent<br>
* Will call{@link ICapabilitySync#sendToClient(ServerPlayer)}
* @param event event
*/
public static void onEntityBeTracked(PlayerEvent.StartTracking event) {
if (event.getEntity() instanceof ServerPlayer attacker) {
@ -28,10 +28,10 @@ public class EntityCapabilityRemainder {
}
/**
* 实体Tick事件<br>
* 如果capability是dirty的就会调用{@link ICapabilitySync#sendToClient()} <br>
* 为了性能每秒才触发一次同步
* @param event 事件实例
* Entity Tick Event<br>
* If the capability is dirty, it will call {@link ICapabilitySync#sendToClient()} <br>
* For performance reasons, synchronization is only triggered once per second
* @param event event
*/
public static void capabilitySync(LivingEvent.LivingTickEvent event) {
LivingEntity entity = event.getEntity();
@ -50,8 +50,8 @@ public class EntityCapabilityRemainder {
}
/**
* 实体加入level的事件初始化
* @param event 实体加入事件
* Event of entity joining level, initialization
* @param event event
*/
public static void onEntityJoin(EntityJoinLevelEvent event) {
Entity entity = event.getEntity();

View File

@ -5,9 +5,9 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
/**
* 实现时建议手动添加<br>
* key 作为cap的唯一标识 <br>
* getCapability(Entity entity) 获取cap的简化方法<br>
* It is recommended to manually add it during implementation: <br>
* {@code key} ---- As the unique identifier of capability. <br>
* {@code getCapability(Entity entity)} ---- Simplified method for obtaining capability<br>
*
* <pre>
* {@code
@ -23,6 +23,9 @@ import net.minecraft.world.entity.Entity;
*
*/
public abstract class SimpleEntityCapabilitySync<T extends Entity> implements ICapabilitySync<T> {
/**
* Id
*/
public static final String Id = "Id";
private boolean dirty;
@ -34,28 +37,36 @@ public abstract class SimpleEntityCapabilitySync<T extends Entity> implements IC
}
/**
* 你应该在每个属性的setter里调用它设置为true以触发自动同步
* @param dirty 是否应该同步是否为脏
* You should call it to set it to true in the setter of each property to trigger automatic synchronization
* @param dirty dirty
*/
@Override
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
/**
* get id
* @return Id
*/
public Integer getId() {
return id;
}
/**
* set id
* @param id id
*/
public void setId(Integer id) {
this.id = id;
setDirty(true);
}
/**
* 从参数实例中复制数据到当前实例 <br>
* 你不应该重写它你应该实现 {@link SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)}
* @param oldData 旧数据
* @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()}
* Copy data from parameter instance to current instance <br>
*You shouldn't rewrite it, you should implement: {@link SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)}
* @param oldData old data
* @param listenDone Whether to execute the completion method at the end: {@link ICapabilitySync#onCopyDone()}
*/
@Override
public void copyFrom(ICapabilitySync<?> oldData, boolean listenDone) {
@ -66,14 +77,14 @@ public abstract class SimpleEntityCapabilitySync<T extends Entity> implements IC
}
/**
* 触发数据复制时会执行的方法
* @param oldData 从这个数据中复制到当前实例
* The method that will be executed when triggering data replication
* @param oldData Copy from this data to the current instance
*/
public abstract void copyFrom(ICapabilitySync<?> oldData);
/**
* 序列化为tag <br>
* 你不应该重写它你应该实现{@link ICapabilitySync#toTag(CompoundTag)}
* Serialize to tag <br>
* You shouldn't rewrite it, you should implement: {@link SimpleEntityCapabilitySync#toTag(CompoundTag)}
* @return tag
*/
@Override
@ -85,8 +96,8 @@ public abstract class SimpleEntityCapabilitySync<T extends Entity> implements IC
}
/**
* 反序列化为实例对象 <br>
* 你应该不需要重写它你应该实现{@link ICapabilitySync#fromTag(CompoundTag)}
* Deserialize to instance object <br>
* You don't need to rewrite it, you should implement: {@link SimpleEntityCapabilitySync#fromTag(CompoundTag)}
* @param nbt nbt
*/
@Override
@ -95,4 +106,19 @@ public abstract class SimpleEntityCapabilitySync<T extends Entity> implements IC
if(nbt.contains(Id)) this.id = nbt.getInt(Id);
fromTag(nbt);
}
/**
* In the serializeNBT method of SimpleElementCapability Sync, it will be called <br>
* Actually equivalent to serializeNBT()
* @param tag data tag
* @return tag
*/
public abstract CompoundTag toTag(CompoundTag tag);
/**
* In the deserializeNBT method of SimpleElementCapability Sync, it will be called <br>
* Actually equivalent to deserializeNBT()
* @param tag data tag
*/
public abstract void fromTag(CompoundTag tag);
}

View File

@ -18,8 +18,10 @@ public class PlayerCapabilityHandler {
private static boolean isRegistered = false;
/**
* 应在Forge主线中调用以监听capability注册 <br>
* 建议在Mod构造方法里调用
* It should be called in the Forge mainline to listen to the capability registration <br>
* Suggest calling it in the Mod constructor method <br>
* Normally SCCore will call it, so you should not call it
* @param forgeBus forge event bus
*/
public static void register(IEventBus forgeBus) {
if (isRegistered) return;
@ -32,7 +34,10 @@ public class PlayerCapabilityHandler {
isRegistered = true;
}
//注册 capability
/**
* Register capability
* @param event event
*/
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void registerCapability(RegisterCapabilitiesEvent event) {
PlayerCapabilityRegistry.getCapabilityMap().values().forEach(record ->
@ -40,7 +45,10 @@ public class PlayerCapabilityHandler {
);
}
//附加 capability
/**
* Attach capability to entity
* @param event event
*/
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
if(event.getObject() instanceof Player) {

View File

@ -11,10 +11,20 @@ import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The final serialization, deserialization, and retrieval methods of capability
* @param <C> extends {@link ICapabilitySync}
*/
@AutoRegisterCapability
public class PlayerCapabilityProvider<C extends ICapabilitySync<?>> implements ICapabilitySerializable<CompoundTag> {
private final C instance;
private final ResourceLocation resourceLocation;
/**
* Constructor
* @param resourceLocation key
* @param instance instance
*/
public PlayerCapabilityProvider(ResourceLocation resourceLocation, C instance) {
this.resourceLocation = resourceLocation;
this.instance = instance;

View File

@ -13,19 +13,18 @@ public class PlayerCapabilityRegistry {
private final Map<ResourceLocation, CapabilityRecord<?>> capabilityRecordMap = new HashMap<>();
/**
* 通过此方法注册capability仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
* 事件结束之前有效
* @param key capability的唯一name
* @param capabilityRecord 使用record存储了应该注册的capability的各项数据参阅{@link CapabilityRecord}
* Registering player capabilities through this method only applies to {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}<br>
* @param key The unique name of capability.
* @param capabilityRecord Record is used to store various data of the capabilities that should be registered, refer to: {@link PlayerCapabilityRegistry.CapabilityRecord}
*/
public static void registerCapability(ResourceLocation key, CapabilityRecord<?> capabilityRecord) {
CAPABILITIES.capabilityRecordMap.put(key, capabilityRecord);
}
/**
* 通过此方法获取对应的capability数据
* @param key 根据key获取
* @return capability 数据
* Obtain corresponding capability data through this method
* @param key Obtain based on key
* @return capability
*/
public static CapabilityRecord<?> getCapabilityRecord(ResourceLocation key){
return CAPABILITIES.capabilityRecordMap.get(key);
@ -36,10 +35,10 @@ public class PlayerCapabilityRegistry {
}
/**
* 记录capability的注册数据
* @param aClass 最终会附加给玩家的实例应该是ICapabilitySync的实例
* @param capability 一般情况下不需要初始化它默认CapabilityManager.get(new CapabilityToken<>(){})
* @param interfaceClass instance实例对应的接口类比如ICapabilitySync.class
* Record the registration data of capability
* @param aClass The instance that will ultimately be attached to the player. Should be an instance of ICapabilitySync
* @param capability In general, it is not necessary to initialize it, default: <span>{@code CapabilityManager.get(new CapabilityToken<>(){})}</span>
* @param interfaceClass The interface class corresponding to the instance, such as: ICapabilitySync.class.
*/
public record CapabilityRecord<T extends ICapabilitySync<? extends Player>>(Class<?> aClass, Capability<T> capability, Class<T> interfaceClass) { }
}

View File

@ -2,22 +2,15 @@ package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import java.util.Optional;
/**
* 用于维护数据同步
*/
public class PlayerCapabilityRemainder {
/**
* 玩家跨越维度/死亡时应该转移数据到新身体上
* @param event Clone事件
* Players should transfer data to a new body when crossing dimensions or dying
* @param event event
*/
public static void onPlayerClone(PlayerEvent.Clone event) {
Player entity = event.getEntity();
@ -37,8 +30,8 @@ public class PlayerCapabilityRemainder {
}
/**
* 玩家重生时应该更新自己的capability
* @param event 重生事件实例
* Players should update their capabilities when they are reborn
* @param event event
*/
public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
if(event.getEntity() instanceof ServerPlayer newPlayer){
@ -51,10 +44,10 @@ public class PlayerCapabilityRemainder {
}
/**
* 玩家追踪实体事件<br>
* 当有其他玩家被加载时客户端需要对方的capability该事件可以主动发送<br>
* 会调用{@link ICapabilitySync#sendToClient(ServerPlayer)}
* @param event 追踪事件实例
* Player start tracking an player event<br>
* When other entities are loaded, the client requires the capabilities of the other party, and this event can be actively sent<br>
* Will call{@link ICapabilitySync#sendToClient(ServerPlayer)}
* @param event event
*/
public static void onEntityBeTracked(PlayerEvent.StartTracking event) {
if (event.getTarget() instanceof Player target && event.getEntity() instanceof ServerPlayer attacker) {
@ -67,9 +60,9 @@ public class PlayerCapabilityRemainder {
}
/**
* 玩家Tick事件<br>
* 如果capability是dirty的就会调用{@link ICapabilitySync#sendToClient()}
* @param event 事件实例
* Player Tick Event<br>
* If the capability is dirty, it will call {@link ICapabilitySync#sendToClient()} <br>
* @param event event
*/
public static void capabilitySync(TickEvent.PlayerTickEvent event) {
if(!event.player.level().isClientSide){
@ -85,10 +78,9 @@ public class PlayerCapabilityRemainder {
}
/**
* 玩家登录事件 <br>
* 重初始化登录玩家的cap <br>
* 将服务端所有玩家的cap发送给该玩家以初始化该玩家的客户端侧的RemotePlayer数据<br>
* 上一行的这个行为可能会导致卡顿它的必要性还未知可以发pr或issue提议删除它
* Player login event <br>
* Reinitialize the login player's capability <br>
* @param event event
*/
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
Player player = event.getEntity();
@ -103,16 +95,5 @@ public class PlayerCapabilityRemainder {
data.setDirty(false);
data.sendToClient();
});
Optional.ofNullable(serverPlayer.getServer()).map(MinecraftServer::getPlayerList).map(PlayerList::getPlayers).ifPresent(
serverPlayers -> serverPlayers.forEach(p -> {
if(!p.getUUID().equals(serverPlayer.getUUID())) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync<?> data = CapabilityUtils.getCapability(player, key);
if(data == null) return;
data.sendToClient(serverPlayer);
});
}
})
);
}
}

View File

@ -1,15 +1,16 @@
package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import com.linearpast.sccore.capability.data.entity.SimpleEntityCapabilitySync;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player;
import java.util.UUID;
/**
* 实现时建议手动添加<br>
* key 作为cap的唯一标识 <br>
* getCapability(Player player) 获取cap的简化方法<br>
* It is recommended to manually add it during implementation: <br>
* {@code key} ---- As the unique identifier of capability. <br>
* {@code getCapability(Player player)} ---- Simplified method for obtaining capability<br>
*
* <pre>
* {@code
@ -50,10 +51,10 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync<Play
}
/**
* 从参数实例中复制数据到当前实例 <br>
* 你不应该重写它你应该实现 {@link SimplePlayerCapabilitySync#copyFrom(ICapabilitySync)}
* @param oldData 旧数据
* @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()}
* Copy data from parameter instance to current instance <br>
*You shouldn't rewrite it, you should implement: {@link SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)}
* @param oldData old data
* @param listenDone Whether to execute the completion method at the end: {@link ICapabilitySync#onCopyDone()}
*/
@Override
public void copyFrom(ICapabilitySync<?> oldData, boolean listenDone) {
@ -64,14 +65,14 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync<Play
}
/**
* 触发数据复制时会执行的方法
* @param oldData 从这个数据中复制到当前实例
* The method that will be executed when triggering data replication
* @param oldData Copy from this data to the current instance
*/
public abstract void copyFrom(ICapabilitySync<?> oldData);
/**
* 序列化为tag <br>
* 你不应该重写它你应该实现{@link ICapabilitySync#toTag(CompoundTag)}
* Serialize to tag <br>
* You shouldn't rewrite it, you should implement: {@link SimplePlayerCapabilitySync#toTag(CompoundTag)}
* @return tag
*/
@Override
@ -83,8 +84,8 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync<Play
}
/**
* 反序列化为实例对象 <br>
* 你不应该重写它你应该实现{@link ICapabilitySync#fromTag(CompoundTag)} )}
* Deserialize to instance object <br>
* You don't need to rewrite it, you should implement: {@link SimplePlayerCapabilitySync#fromTag(CompoundTag)}
* @param nbt nbt
*/
@Override
@ -93,4 +94,19 @@ public abstract class SimplePlayerCapabilitySync implements ICapabilitySync<Play
if(nbt.contains(OwnerUUID)) this.ownerUUID = nbt.getUUID(OwnerUUID);
fromTag(nbt);
}
/**
* In the serializeNBT method of SimpleElementCapability Sync, it will be called <br>
* Actually equivalent to serializeNBT()
* @param tag data tag
* @return tag
*/
public abstract CompoundTag toTag(CompoundTag tag);
/**
* In the deserializeNBT method of SimpleElementCapability Sync, it will be called <br>
* Actually equivalent to deserializeNBT()
* @param tag data tag
*/
public abstract void fromTag(CompoundTag tag);
}

View File

@ -10,31 +10,32 @@ import java.util.function.Function;
import java.util.function.Supplier;
/**
* 在Mod主类构造方法逻辑中调用createChannel有两种<br>
* Call createChannel in the Mod main class construction method logic. There are two ways:<br>
* <pre>
* 1. {@link com.linearpast.sccore.capability.CapabilityUtils#createChannel(SimpleChannel)}
* 若如此做则必须重写Cap实体类中的所有sendToPlayer方法并在重写中调用使用你的Channel
* If you do this, you must override all the sendToPlayer methods in the Capability class, and call your Channel in the override
* </pre>
* <pre>
* 2. {@link com.linearpast.sccore.capability.CapabilityUtils#createChannel()}
* 若如此做则网络包会以SnowyCrescentCore的Channel注册
* If this is done, the network package will be registered with SCCore's Channel
* </pre>
* 所添加的网络包必须实现ICapabilityPacket接口
* The added network packet must implement the ICapabilityPacket interface
*/
public class CapabilityChannel {
private final SimpleChannel channel;
public CapabilityChannel(SimpleChannel channel) {
this.channel = channel;
}
/**
* 通过此方法预设添加一个网络包等待实例register
* @param clazz 网络包类
* @param cid 索引
* @param decoder 解码器
* @param encoder 编码器
* @param handler 句柄
* @param <T> 网络包接口
* Add a network packet through this method and register
* @param clazz Network packet class
* @param cid index
* @param decoder decoder
* @param encoder encoder
* @param handler handler
* @param <T> extend {@code ICapabilityPacket<?>}
*/
public <T extends ICapabilityPacket<?>> void register(
Class<T> clazz,

View File

@ -11,13 +11,13 @@ import java.util.function.Supplier;
public interface ICapabilityPacket<T extends Entity> {
/**
* 解码网络包
* Decoding network packets
* @param buf FriendlyByteBuf
*/
void encode(FriendlyByteBuf buf);
/**
* 网络包处理事件一般情况下不需要重写它默认的行为足够使用
* Network packet processing events generally do not need to be rewritten, and the default behavior is sufficient for use
* @param supplier supplier
*/
default void handle(Supplier<NetworkEvent.Context> supplier){
@ -26,30 +26,30 @@ public interface ICapabilityPacket<T extends Entity> {
}
/**
* 网络包处理事件应该在这重写
* Network packet processing events should be rewritten here
* @param context NetworkEvent.Context
*/
void handler(NetworkEvent.Context context);
/**
* 在网络包中获取对应的capability一般在 {@link ICapabilityPacket#syncData}后执行
* @param entity 目标实体
* @return 返回Capability
* Retrieve the corresponding capability from the network packet, usually executed after {@link ICapabilityPacket#syncData}
* @param entity Target
* @return capability
*/
@Nullable ICapabilitySync getCapability(T entity);
@Nullable ICapabilitySync<?> getCapability(T entity);
/**
* 获取Tag
* @return Tag
* Get tag
* @return tag
*/
CompoundTag getData();
/**
* 网络包中将tag转换为capability data默认直接反序列化
* Convert tags to capability data in network packets and deserialize them directly by default
* @param dataTag tag
* @param data 应被写入数据的data
* @param data The data that should be written into the data
*/
default void syncData(CompoundTag dataTag, ICapabilitySync data){
default void syncData(CompoundTag dataTag, ICapabilitySync<?> data){
if(data == null) return;
data.deserializeNBT(dataTag);
}

View File

@ -13,19 +13,35 @@ import net.minecraftforge.network.NetworkEvent;
public abstract class SimpleCapabilityPacket<T extends Entity> implements ICapabilityPacket<T> {
private final CompoundTag data;
/**
* Constructor
* @param data data tag
*/
public SimpleCapabilityPacket(CompoundTag data) {
this.data = data;
}
/**
* decoder
* @param buf buf
*/
public SimpleCapabilityPacket(FriendlyByteBuf buf) {
this.data = buf.readNbt();
}
/**
* encoder
* @param buf buf
*/
@Override
public void encode(FriendlyByteBuf buf) {
buf.writeNbt(data);
}
/**
* Default network packet handle, generally sufficient for use
* @param context NetworkEvent.Context
*/
@SuppressWarnings("unchecked")
@Override
public void handler(NetworkEvent.Context context) {
@ -43,7 +59,7 @@ public abstract class SimpleCapabilityPacket<T extends Entity> implements ICapab
}
if(entity == null) return;
try {
ICapabilitySync data = getCapability((T) entity);
ICapabilitySync<?> data = getCapability((T) entity);
syncData(nbt, data);
}catch (Exception ignored) {}
}

View File

@ -1,4 +1,4 @@
package com.linearpast.sccore.network;
package com.linearpast.sccore.core;
import com.linearpast.sccore.SnowyCrescentCore;
import net.minecraft.resources.ResourceLocation;
@ -8,8 +8,8 @@ import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
public class Channel {
public static int cid = 0;
public class ModChannel {
private static int cid = 0;
private static final String PROTOCOL_VERSION = ModList.get()
.getModContainerById(SnowyCrescentCore.MODID)
.map(c -> c.getModInfo().getVersion().toString())
@ -25,12 +25,8 @@ public class Channel {
}
public static int getCid() {
return cid;
}
public static void setCid(int cid) {
Channel.cid = cid;
public static int getAndAddCid() {
return cid++;
}
public static <MSG> void sendAllPlayer(MSG message){

View File

@ -0,0 +1,64 @@
package com.linearpast.sccore.core;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.command.AnimationCommands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.HashSet;
import java.util.Set;
import static net.minecraft.commands.Commands.literal;
public class ModCommands {
static final Set<String> animationCommand = new HashSet<>(Set.of(SnowyCrescentCore.MODID, "sc", "scc"));
public static void addCommandAlias(String alias) {
animationCommand.add(alias);
}
public static Set<String> getAnimationCommand() {
return animationCommand;
}
public static void registerCommands(IEventBus forgeBus, IEventBus modBus) {
forgeBus.addListener(ModCommands::commonCommandRegister);
forgeBus.addListener(ModCommands::clientCommandRegister);
Arguments.register(modBus);
}
public static void commonCommandRegister(RegisterCommandsEvent event) {
animationCommand.forEach(string -> {
LiteralArgumentBuilder<CommandSourceStack> builder = literal(string);
CommandBuildContext buildContext = event.getBuildContext();
AnimationCommands.commonCommandRegister(builder);
event.getDispatcher().register(builder);
});
}
public static void clientCommandRegister(RegisterClientCommandsEvent event) {
animationCommand.forEach(string -> {
LiteralArgumentBuilder<CommandSourceStack> builder = literal(string);
AnimationCommands.clientCommandRegister(builder);
event.getDispatcher().register(builder);
});
}
public static class Arguments {
public static final DeferredRegister<ArgumentTypeInfo<?, ?>> REGISTRY = DeferredRegister.create(
ForgeRegistries.Keys.COMMAND_ARGUMENT_TYPES, SnowyCrescentCore.MODID
);
public static void register(IEventBus eventBus) {
AnimationCommands.registerArguments(REGISTRY);
REGISTRY.register(eventBus);
}
}
}

View File

@ -0,0 +1,20 @@
package com.linearpast.sccore.core;
import net.minecraftforge.common.ForgeConfigSpec;
public class ModConfigs {
public static class Common {
public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
public static final ForgeConfigSpec SPEC;
public static final ForgeConfigSpec.ConfigValue<Boolean> enableExample;
static {
BUILDER.push("Development");
enableExample = BUILDER.comment("Enable some example for lib.")
.define("enableExample", false);
BUILDER.pop();
SPEC = BUILDER.build();
}
}
}

View File

@ -0,0 +1,52 @@
package com.linearpast.sccore.core;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLLoader;
import java.util.concurrent.Callable;
public abstract class ModLazyRun {
private final String modId;
public ModLazyRun(String modId) {
this.modId = modId;
}
public boolean testLoadedAndRun(Runnable runnable){
if(isModLoaded()) runnable.run();
else return false;
return true;
}
public <T> T testLoadedAndCall(Callable<T> callable) {
try {
if(isModLoaded()) return callable.call();
} catch (Exception ignored) {}
return null;
}
public <T> T testLoadedAndCall(Callable<T> callable, Callable<T> elseCall) {
try {
if(isModLoaded()) return callable.call();
else return elseCall.call();
}catch(Exception e) {
return null;
}
}
public void addCommonListener(IEventBus forgeBus, IEventBus modBus){}
public void addClientListener(IEventBus forgeBus, IEventBus modBus){}
public void testLoadedAndAddListener(IEventBus forgeBus, IEventBus modBus) {
if(isModLoaded()){
addCommonListener(forgeBus, modBus);
if(FMLLoader.getDist() == Dist.CLIENT){
addClientListener(forgeBus, modBus);
}
}
}
public boolean isModLoaded() {
return ModList.get().isLoaded(modId);
};
}

View File

@ -1,63 +0,0 @@
package com.linearpast.sccore.example;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.entity.EntityCapabilityRegistry;
import com.linearpast.sccore.capability.network.CapabilityChannel;
import com.linearpast.sccore.example.cap.ISheepData;
import com.linearpast.sccore.example.cap.SheepDataCapability;
import com.linearpast.sccore.example.event.PlayerAttackEvent;
import com.linearpast.sccore.network.Channel;
import net.minecraft.world.entity.animal.Sheep;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
import net.minecraftforge.eventbus.api.IEventBus;
public class ModCaps {
/**
* 注册实体capability的示例<br>
* 请参阅 {@link CapabilityUtils}
*/
public static void register(){
//如果你想将网络包注册到你自己的mod中createChannel(INSTANCE)
//然后别忘记在capability类里面重写所有的sendToClient方法
CapabilityChannel channel = CapabilityUtils.createChannel();
//不可与其他网络包重复的任意整数
int cid = Channel.getCid();
//注册实体cap和它的网络包
//若注册玩家的请用registerPlayerCapabilityWithNetwork
CapabilityUtils.registerEntityCapabilityWithNetwork(
//一个resourceLocation任意命名不重复即可
SheepDataCapability.key,
//需要注册cap的数据
new EntityCapabilityRegistry.CapabilityRecord<>(
//registry将会 new 一个此类的实例你可以在该类中重写无参构造以让它初始化
SheepDataCapability.class,
//固定写法一般情况你无需修改它
CapabilityManager.get(new CapabilityToken<>() {}),
//第一个参数类的接口可以为抽象类或不用接口
//你可以用它自己: SheepDataCapability.class
ISheepData.class,
//注册的cap应附加在什么实体上
Sheep.class
),
channel,
//索引使用后+1防止后续网络频道冲突
cid++,
//网络包的class
SheepDataCapability.SheepCapabilityPacket.class,
//网络包的decode方法
SheepDataCapability.SheepCapabilityPacket::new,
//网络包的encode方法
SheepDataCapability.SheepCapabilityPacket::encode,
//网络包的handle方法
SheepDataCapability.SheepCapabilityPacket::handle
);
//这是为了还给Channel一个增加后的cid以防止后续网络包索引重复
Channel.setCid(cid);
}
//测试cap是否成功添加的监听事件
public static void addListenerToEvent(IEventBus forgeBus){
forgeBus.addListener(PlayerAttackEvent::onPlayerAttack);
}
}

View File

@ -0,0 +1,70 @@
package com.linearpast.sccore.example.animation;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.data.Animation;
import com.linearpast.sccore.animation.data.Ride;
import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent;
import com.linearpast.sccore.example.animation.event.ExampleCommandEvent;
import com.linearpast.sccore.example.animation.event.ExamplePlayerAttackEvent;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.loading.FMLEnvironment;
/**
* @see AnimationUtils
*/
public class ModAnimation {
/**
* This is an animation layer
*/
public static final ResourceLocation normalLayers = new ResourceLocation(SnowyCrescentCore.MODID, "normal_layers");
/**
* <pre>
* They are animations
* {@code new ResourceLocation(modid, name)}
* Resource from "assets/{modid}/player_animation/{name}.json"</pre>
*/
public static final ResourceLocation AmLyingToRightLying = new ResourceLocation(SnowyCrescentCore.MODID, "am_lying_to_right_lying");
public static final ResourceLocation AmStandToLying = new ResourceLocation(SnowyCrescentCore.MODID, "am_stand_to_lying");
public static final ResourceLocation WaltzGentleman = new ResourceLocation(SnowyCrescentCore.MODID, "waltz_gentleman");
public static final ResourceLocation WaltzLady = new ResourceLocation(SnowyCrescentCore.MODID, "waltz_lady");
public static void register(IEventBus forgeBus, IEventBus modBus) {
//You must define corresponding Animation to register
Animation amLTRL = new Animation(AmLyingToRightLying)
.withLyingType(Animation.LyingType.RIGHT)
.withRide(Ride.create().addComponentAnimation(AmStandToLying));
Animation amSTL = new Animation(AmStandToLying)
.withLyingType(Animation.LyingType.FRONT);
Animation waltzGentleman = new Animation(WaltzGentleman)
.withRide(Ride.create().addComponentAnimation(WaltzLady));
Animation waltzLady = new Animation(WaltzLady)
.withCamYaw(180);
//You can use it to register an Animation
AnimationUtils.registerAnimation(AmLyingToRightLying, amLTRL);
AnimationUtils.registerAnimation(AmStandToLying, amSTL);
AnimationUtils.registerAnimation(WaltzGentleman, waltzGentleman);
AnimationUtils.registerAnimation(WaltzLady, waltzLady);
//Register by event
//Or use AnimationUtils.registerAnimationLayer(ResourceLocation layer, int priority);
modBus.addListener(ModAnimation::onLayerRegister);
//Try to play animation
forgeBus.addListener(ExamplePlayerAttackEvent::onPlayerAttack);
forgeBus.addListener(ExampleCommandEvent::inviteDance);
if(FMLEnvironment.dist == Dist.CLIENT){
forgeBus.addListener(ExamplePlayerAttackEvent::onInputEvent);
}
}
public static void onLayerRegister(AnimationLayerRegisterEvent event) {
event.putLayer(normalLayers, 42);
}
}

View File

@ -0,0 +1,122 @@
package com.linearpast.sccore.example.animation.event;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.command.argument.AnimationArgument;
import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.event.RegisterCommandsEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
public class ExampleCommandEvent {
record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){}
private static final Map<UUID, Map<UUID, InviteRecord>> invites = new HashMap<>();
public static void inviteDance(RegisterCommandsEvent event) {
LiteralArgumentBuilder<CommandSourceStack> builder = literal("dance").then(literal("invite")
.then(argument("player", EntityArgument.player())
.then(argument("layer", AnimationLayerArgument.layer())
.then(argument("anim", AnimationArgument.animation())
.executes(ExampleCommandEvent::inviteDance)
.then(argument("force", BoolArgumentType.bool())
.executes(ExampleCommandEvent::inviteDance)
)
)
)
)
.then(literal("accept")
.then(argument("player", EntityArgument.player())
.executes(ExampleCommandEvent::acceptInvite)
)
)
);
event.getDispatcher().register(builder);
}
private static int inviteDance(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
try {
//get info
boolean force = false;
try {
force = BoolArgumentType.getBool(context, "force");
} catch (Exception ignored) {}
ServerPlayer player = source.getPlayerOrException();
ServerPlayer target = EntityArgument.getPlayer(context, "player");
String layerString = AnimationLayerArgument.getLayer(context, "layer");
String animString = AnimationArgument.getAnimation(context, "anim");
ResourceLocation layer = new ResourceLocation(layerString);
ResourceLocation anim = new ResourceLocation(animString);
boolean finalForce = force;
//test info present
boolean animationPresent = AnimationUtils.isAnimationPresent(anim);
boolean animationLayerPresent = AnimationUtils.isAnimationLayerPresent(layer);
if(!animationLayerPresent || !animationPresent) throw new Exception();
//update static cache
Map<UUID, InviteRecord> inviteRecordMap = invites.getOrDefault(player.getUUID(), new HashMap<>());
inviteRecordMap.put(target.getUUID(), new InviteRecord(System.currentTimeMillis(), layer, anim, finalForce));
invites.put(player.getUUID(), inviteRecordMap);
//send message
Component name = player.getName();
Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent(
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/dance invite accept " + player.getName().getString())
);
target.sendSystemMessage(name.copy().append("邀请你跳一支舞。").append(Component.literal("单击此处同意.").setStyle(pStyle)));
source.sendSuccess(() -> Component.literal("命令执行成功. 已发送邀请").withStyle(ChatFormatting.GREEN), true);
} catch (Exception e) {
source.sendFailure(Component.literal("Command run fail.").withStyle(ChatFormatting.RED));
return 0;
}
return 1;
}
private static int acceptInvite(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
try {
ServerPlayer target = source.getPlayerOrException();
ServerPlayer player = EntityArgument.getPlayer(context, "player");
Map<UUID, InviteRecord> inviteRecordMap = invites.getOrDefault(player.getUUID(), null);
if(inviteRecordMap == null) throw new Exception();
InviteRecord inviteRecord = inviteRecordMap.getOrDefault(target.getUUID(), null);
if(inviteRecord == null) throw new Exception();
long now = System.currentTimeMillis();
if(now - inviteRecord.time > 120000) {
source.sendFailure(Component.literal("邀请已超时2分钟.").withStyle(ChatFormatting.RED));
player.sendSystemMessage(target.getName().copy().append("接受了你的舞蹈邀请. 但是邀请超时了2分钟.").withStyle(ChatFormatting.RED));
return 0;
}
if(player.position().distanceToSqr(target.position()) > 64) {
source.sendFailure(Component.literal("你们距离太远了8格.").withStyle(ChatFormatting.RED));
player.sendSystemMessage(target.getName().copy().append("接受了你的舞蹈邀请. 但你们距离太远了8格.").withStyle(ChatFormatting.RED));
return 0;
}
inviteRecordMap.remove(target.getUUID());
invites.put(player.getUUID(), inviteRecordMap);
AnimationUtils.startAnimationTogether(player, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce, target);
source.sendSuccess(() -> Component.literal("已接受邀请.").withStyle(ChatFormatting.GREEN), true);
player.sendSystemMessage(target.getName().copy().append("已接受你的舞蹈邀请.").withStyle(ChatFormatting.GREEN));
} catch (Exception e) {
source.sendFailure(Component.literal("Command run fail.").withStyle(ChatFormatting.RED));
return 0;
}
return 1;
}
}

View File

@ -0,0 +1,65 @@
package com.linearpast.sccore.example.animation.event;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.example.animation.ModAnimation;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.animal.Sheep;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
public class ExamplePlayerAttackEvent {
/**
* when attack sheep, will play stand to lying animation <br>
* when attack other player, will play animation together
* @param event event
*/
public static void onPlayerAttack(AttackEntityEvent event) {
Entity target = event.getTarget();
Player entity = event.getEntity();
if(entity instanceof ServerPlayer player) {
if(target instanceof Sheep){
ResourceLocation playing = AnimationUtils.getAnimationPlaying(player, ModAnimation.normalLayers);
if(playing == null) {
AnimationUtils.playAnimation(player, ModAnimation.normalLayers, ModAnimation.AmStandToLying);
} else {
AnimationUtils.playAnimation(player, ModAnimation.normalLayers, null);
}
}
if(target instanceof ServerPlayer serverPlayer) {
AnimationUtils.startAnimationTogether(
serverPlayer,
ModAnimation.normalLayers,
ModAnimation.AmLyingToRightLying,
true,
player
);
}
}
}
/**
* when press "/", this will run
* @param event event
*/
@OnlyIn(Dist.CLIENT)
public static void onInputEvent(InputEvent.Key event) {
Minecraft instance = Minecraft.getInstance();
LocalPlayer player = instance.player;
if (player == null) return;
if(instance.options.keyCommand.isDown()) {
ResourceLocation playing = AnimationUtils.getAnimationPlaying(player, ModAnimation.normalLayers);
if(playing == null) {
AnimationUtils.playAnimationWithRide(null, ModAnimation.normalLayers, ModAnimation.AmLyingToRightLying, true);
}
}
}
}

View File

@ -1,13 +0,0 @@
package com.linearpast.sccore.example.cap;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.world.entity.animal.Sheep;
/**
* 接口继承ICapabilitySync是必需的但是接口是非必需的你可以在注册时直接使用cap类本身 <br>
* 用于共享一些可能会用到的cap的公共方法
*/
public interface ISheepData extends ICapabilitySync<Sheep> {
Integer getValue();
void setValue(Integer value);
}

View File

@ -0,0 +1,64 @@
package com.linearpast.sccore.example.capability;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.entity.EntityCapabilityRegistry;
import com.linearpast.sccore.capability.network.CapabilityChannel;
import com.linearpast.sccore.core.ModChannel;
import com.linearpast.sccore.example.capability.data.ISheepData;
import com.linearpast.sccore.example.capability.data.SheepDataCapability;
import com.linearpast.sccore.example.capability.event.PlayerAttackEvent;
import net.minecraft.world.entity.animal.Sheep;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
import net.minecraftforge.eventbus.api.IEventBus;
import java.util.Set;
public class ModCapability {
/**
* Example of Registered Entity Capability<br>
* @see CapabilityUtils#registerEntityCapability
* @see CapabilityUtils#registerPlayerCapability
* @see CapabilityUtils#registerEntityCapabilityWithNetwork
* @see CapabilityUtils#registerPlayerCapabilityWithNetwork
*/
public static void register(){
//If you want to register network packets in your own mod, to use : createChannel(INSTANCE)
//And don't forget to rewrite all the sendToClient methods in the capability class
CapabilityChannel channel = CapabilityUtils.createChannel();
//Register the entity capability and its network packet
//If you want register about player, please use CapabilityUtils.registerPlayerCapabilityWithNetwork()
CapabilityUtils.registerEntityCapabilityWithNetwork(
//A resourceLocation, named arbitrarily without repetition
SheepDataCapability.key,
//Data that needs to be registered for capability
new EntityCapabilityRegistry.CapabilityRecord<>(
//Registry will create a new instance of this class
//And you can override the parameterless construct in this class to initialize it
SheepDataCapability.class,
//Fixed writing style, generally you don't need to modify it
CapabilityManager.get(new CapabilityToken<>() {}),
//The interface of the first parameter class can be an abstract class or not require an interface
//You can use it yourself: SheepDataCapability.class
ISheepData.class,
//What entities should the registered capability be attached to
Set.of(Sheep.class)
),
channel,
//Index+1 after use to prevent subsequent network channel conflicts
ModChannel.getAndAddCid(),
//Class of network packet
SheepDataCapability.SheepCapabilityPacket.class,
//Decoder method for network packet
SheepDataCapability.SheepCapabilityPacket::new,
//Encoder method for network packet
SheepDataCapability.SheepCapabilityPacket::encode,
//Handler method for network packet
SheepDataCapability.SheepCapabilityPacket::handle
);
}
public static void addListenerToEvent(IEventBus forgeBus){
forgeBus.addListener(PlayerAttackEvent::onPlayerAttack);
}
}

View File

@ -0,0 +1,13 @@
package com.linearpast.sccore.example.capability.data;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.world.entity.animal.Sheep;
/**
* The interface inheritance ICapabilitySync is required, but the interface is not necessary (you can directly use the cap class itself during registration) <br>
* Common methods for sharing caps that may be used.
*/
public interface ISheepData extends ICapabilitySync<Sheep> {
Integer getValue();
void setValue(Integer value);
}

View File

@ -1,4 +1,4 @@
package com.linearpast.sccore.example.cap;
package com.linearpast.sccore.example.capability.data;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.capability.CapabilityUtils;
@ -8,56 +8,61 @@ import com.linearpast.sccore.capability.network.SimpleCapabilityPacket;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.animal.Sheep;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
/**
* cap的实体类 <br>
* 继承SimpleEntityCapabilitySync意味着自动托管一个id的同步 <br>
* 实现的IsheepData仅含有属性value的getter和setter <br>
* The entity class of cap <br>
* Inheriting SimpleElementCapability Sync means automatically hosting synchronization of an ID <br>
* The IsheepData implemented only contains the property 'value' as a getter and setter <br>
* @see SimpleEntityCapabilitySync
*/
public class SheepDataCapability extends SimpleEntityCapabilitySync<Sheep> implements ISheepData {
//代表cap的key注册获取时都需要它
public static final ResourceLocation key = new ResourceLocation(SnowyCrescentCore.MODID, "sheep_data");
//只是为了统一管理()序列化时的keyName
public static final String Value = "Value";
//最后附加到实体实例变量
private Integer value;
//getter
@Override
public Integer getValue() {
return value;
}
//setter
@Override
public void setValue(Integer value) {
this.value = value;
setDirty(true);
}
//在SimpleEntityCapabilitySync的serializeNBT方法中会调用
//实际上相当于serializeNBT
/**
* @param tag data tag
* @return tag
* @see SimpleEntityCapabilitySync#toTag(CompoundTag)
*/
@Override
public CompoundTag toTag(CompoundTag tag) {
if(value != null) tag.putInt(Value, value);
return tag;
}
//在SimpleEntityCapabilitySync的deserializeNBT方法中会调用
//实际上相当于deserializeNBT
/**
* @see SimpleEntityCapabilitySync#fromTag(CompoundTag)
* @param tag data tag
*/
@Override
public void fromTag(CompoundTag tag) {
this.value = null;
if(tag.contains(Value)) this.value = tag.getInt(Value);
}
//从旧实例中复制数据到新实例的方法
/**
* @see SimpleEntityCapabilitySync#copyFrom(ICapabilitySync)
* @param oldData Copy from this data to the current instance
*/
@Override
public void copyFrom(ICapabilitySync<?> oldData) {
SheepDataCapability data = (SheepDataCapability) oldData;
@ -65,43 +70,54 @@ public class SheepDataCapability extends SimpleEntityCapabilitySync<Sheep> imple
}
/**
* 网络包你可以在里面重写任意方法关于方法的作用请参阅<br>
* Network packet, you can rewrite any method inside. For the function of methods, please refer to<br>
* {@link com.linearpast.sccore.capability.network.ICapabilityPacket} <br>
* 可以不写在内部类中作者是觉得它内容太少写里面显得更紧凑美观
* It is not necessary to include it in the internal class. I feel that the content is too limited and writing it inside makes it more compact and beautiful
* @see SimpleCapabilityPacket
*/
public static class SheepCapabilityPacket extends SimpleCapabilityPacket<Sheep> {
//网络包构造方法
public SheepCapabilityPacket(CompoundTag data) {
super(data);
}
//这实际上是decoder
public SheepCapabilityPacket(FriendlyByteBuf buf) {
super(buf);
}
//仅用在网络包内部的getCap
@Override
public @Nullable SheepDataCapability getCapability(Sheep entity) {
return SheepDataCapability.getCapability(entity).orElse(null);
}
}
//获取默认网络包会在sendToClient的时候调用以发送
/**
* Get the default network packet, which will be called when sendToClient sends it
* @return network packet
* @see ICapabilitySync#getDefaultPacket()
*/
@Override
public SimpleCapabilityPacket<Sheep> getDefaultPacket() {
return new SheepCapabilityPacket(serializeNBT());
}
//该方法会在cap初始化时调用比如玩家登录
//该例中当羊加入level时会调用该方法以初始化cap
/**
* This method will be called during cap initialization, such as player login <br>
* In this example, when the sheep joins the level, this method will be called to initialize the capability
* @param entity Target
* @see ICapabilitySync#attachInit(Entity)
*/
@Override
public void attachInit(Sheep entity) {
}
//在其他地方需要用到cap的时候调用这个
//目的是为了简化cap utils的方法
/**
* It is not necessary. <br>
* Call this when capability is needed in other places <br>
* The purpose is to simplify the method of capability get
* @param sheep Target
* @return Optional capability
*/
public static Optional<SheepDataCapability> getCapability(Sheep sheep){
return Optional.ofNullable(CapabilityUtils.getEntityCapability(
sheep, SheepDataCapability.key, SheepDataCapability.class

View File

@ -1,6 +1,6 @@
package com.linearpast.sccore.example.event;
package com.linearpast.sccore.example.capability.event;
import com.linearpast.sccore.example.cap.SheepDataCapability;
import com.linearpast.sccore.example.capability.data.SheepDataCapability;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
@ -9,7 +9,6 @@ import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
public class PlayerAttackEvent {
//简单的测试一下cap是否生效
public static void onPlayerAttack(AttackEntityEvent event) {
Entity target = event.getTarget();
Player entity = event.getEntity();
@ -22,7 +21,7 @@ public class PlayerAttackEvent {
data.setValue(value);
Integer id = data.getId();
player.sendSystemMessage(Component.literal(
"" + value + "攻击了id为\"" + id + "\"的羊"
value + "th attack on sheep with ID " + id
));
});
}

View File

@ -0,0 +1,54 @@
package com.linearpast.sccore.mixin;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.animation.AnimationUtils;
import net.minecraftforge.fml.ModList;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Set;
public class SCCoreMixinPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String s) {
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (targetClassName.startsWith("runData\\.")) {
return "runData".equals(System.getProperty("gradle.task"));
}
if (mixinClassName.startsWith("com\\.linearpast\\." + SnowyCrescentCore.MODID + "\\.mixin\\.animation")) {
return ModList.get().isLoaded(AnimationUtils.AnimModId);
}
return true;
}
@Override
public void acceptTargets(Set<String> set, Set<String> set1) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {
}
@Override
public void postApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {
}
}

View File

@ -0,0 +1,97 @@
package com.linearpast.sccore.mixin.animation;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.capability.AnimationDataCapability;
import com.linearpast.sccore.animation.capability.inter.IAnimationCapability;
import com.linearpast.sccore.animation.data.Animation;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Entity.class)
public abstract class MixinEntity {
@Shadow
private float eyeHeight;
@Shadow public abstract float getEyeHeight(Pose pPose);
@Shadow private AABB bb;
@Shadow public abstract void setPose(Pose pPose);
@Inject(
method = "getEyeHeight()F",
at = @At(value = "HEAD"),
cancellable = true
)
private void redefinedEyeHeight(CallbackInfoReturnable<Float> cir){
Entity self = Entity.class.cast(this);
if(self instanceof Player player){
IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null);
if(data == null) return;
float camYModifier = 0.0f;
for (ResourceLocation value : data.getAnimations().values()) {
Animation animation = AnimationUtils.getAnimation(value);
if(animation == null) continue;
float animationCamY = animation.getCamY();
camYModifier = Math.min(camYModifier, animationCamY);
}
this.eyeHeight = this.getEyeHeight(Pose.STANDING) + camYModifier;
cir.setReturnValue(this.eyeHeight);
}
}
@Inject(
method = "getBoundingBox",
at = @At(value = "RETURN"),
cancellable = true
)
private void redefinedBoundingBox(CallbackInfoReturnable<AABB> cir){
Entity self = Entity.class.cast(this);
if(self instanceof Player player){
float heightModifier = AnimationUtils.getHeightModifier(player);
if(heightModifier == 1.0f) return;
double modifyHeight = 1.8f * heightModifier;
cir.setReturnValue(this.bb.setMaxY(modifyHeight + this.bb.minY));
}
}
@ModifyReturnValue(
method = "getBbHeight",
at = @At(value = "RETURN")
)
private float redefinedBbHeight(float original){
Entity self = Entity.class.cast(this);
if(self instanceof Player player){
float heightModifier = AnimationUtils.getHeightModifier(player);
if(heightModifier == 1.0f) return original;
return original * heightModifier;
}
return original;
}
@Inject(
method = "getPose",
at = @At(value = "HEAD"),
cancellable = true
)
private void redefinedPose(CallbackInfoReturnable<Pose> cir){
Entity self = Entity.class.cast(this);
if(self instanceof Player player){
float heightModifier = AnimationUtils.getHeightModifier(player);
if(heightModifier == 1.0f) return;
setPose(Pose.STANDING);
cir.setReturnValue(Pose.STANDING);
}
}
}

View File

@ -0,0 +1,60 @@
package com.linearpast.sccore.mixin.animation.client;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.data.Animation;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
public abstract class MixinEntity {
@Shadow public abstract void setXRot(float pXRot);
@Shadow private float xRot;
@Shadow public abstract void setYRot(float pYRot);
@Shadow private float yRot;
@Shadow public abstract float getXRot();
@Shadow public float xRotO;
@Shadow public float yRotO;
@Inject(
method = "turn",
at = {@At(value = "HEAD")},
cancellable = true
)
private void turnPosePlayer(double pYRot, double pXRot, CallbackInfo ci) {
Entity self = Entity.class.cast(this);
if(self instanceof Player player){
Animation.LyingType lyingType = AnimationUtils.getSideView(player);
if(lyingType != null && Minecraft.getInstance().options.getCameraType().isFirstPerson()) {
float f = (float)pXRot * 0.15F;
float f1 = (float)pYRot * 0.15F;
switch (lyingType) {
case LEFT -> {
this.setXRot(this.xRot + f1 * -1.0f);
this.setYRot(this.yRot + f);
}
case RIGHT -> {
this.setXRot(this.xRot + f1);
this.setYRot(this.yRot + f * -1.0f);
}
}
this.setXRot(Mth.clamp(this.getXRot(), 0.0f, 90.0f));
this.xRotO = this.xRot;
this.yRotO = this.yRot;
ci.cancel();
}
}
}
}

View File

@ -0,0 +1,46 @@
package com.linearpast.sccore.mixin.animation.client;
import com.linearpast.sccore.animation.AnimationUtils;
import com.linearpast.sccore.animation.data.Animation;
import net.minecraft.client.model.AgeableListModel;
import net.minecraft.client.model.ArmedModel;
import net.minecraft.client.model.HeadedModel;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(HumanoidModel.class)
public abstract class MixinHumanoidModel<T extends LivingEntity> extends AgeableListModel<T> implements ArmedModel, HeadedModel {
@Shadow @Final public ModelPart head;
@Inject(
method = "setupAnim(Lnet/minecraft/world/entity/LivingEntity;FFFFF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/geom/ModelPart;copyFrom(Lnet/minecraft/client/model/geom/ModelPart;)V")
)
private void modifyHeadRot(T pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch, CallbackInfo ci){
if(pEntity instanceof Player player){
Animation.LyingType lyingType = AnimationUtils.getSideView(player);
if(lyingType != null) {
float pitch = pHeadPitch - 90.0f;
float yaw = pNetHeadYaw * -1.0f;
switch (lyingType) {
case LEFT: {
pitch *= -1.0f;
yaw *= -1.0f;
}
case RIGHT: {
this.head.yRot = pitch * 0.017453292F;
this.head.xRot = yaw * 0.017453292F;
}
}
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.linearpast.sccore.mixin.animation.client;
import com.linearpast.sccore.animation.mixin.IMixinKeyframeAnimationPlayer;
import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@Mixin(KeyframeAnimationPlayer.class)
public class MixinKeyframeAnimationPlayer implements IMixinKeyframeAnimationPlayer {
@Shadow(remap = false)
private int currentTick;
@Override
@Unique
public void sccore$setCurrentTick(int tick) {
this.currentTick = tick;
}
}

View File

@ -25,4 +25,11 @@ 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"

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

BIN
src/main/resources/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -4,9 +4,14 @@
"package": "com.linearpast.sccore.mixin",
"compatibilityLevel": "JAVA_8",
"refmap": "sccore.refmap.json",
"plugin": "com.linearpast.sccore.mixin.SCCoreMixinPlugin",
"mixins": [
"animation.MixinEntity"
],
"client": [
"animation.client.MixinEntity",
"animation.client.MixinHumanoidModel",
"animation.client.MixinKeyframeAnimationPlayer"
],
"injectors": {
"defaultRequire": 1