finish cap

version 0.0.1
This commit is contained in:
LostInLinearPast 2025-10-17 23:32:34 +08:00
parent cb6ceef152
commit d496ad72c2
27 changed files with 498 additions and 410 deletions

View File

@ -1,6 +1,5 @@
buildscript {
repositories {
// These repositories are only for Gradle plugins, put any other repositories in the repository block further below
maven { url = 'https://repo.spongepowered.org/repository/maven-public/' }
mavenCentral()
}
@ -13,6 +12,7 @@ plugins {
id 'eclipse'
id 'idea'
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
id 'org.parchmentmc.librarian.forgegradle' version '1.+'
}
apply plugin: 'org.spongepowered.mixin'
@ -29,62 +29,18 @@ java {
}
minecraft {
// The mappings can be changed at any time and must be in the following format.
// Channel: Version:
// official MCVersion Official field/method names from Mojang mapping files
// parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official
//
// You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
//
// Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge
// Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started
//
// Use non-default mappings at your own risk. They may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: mapping_channel, version: mapping_version
// When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game.
// In most cases, it is not necessary to enable.
// enableEclipsePrepareRuns = true
// enableIdeaPrepareRuns = true
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game.
// It is REQUIRED to be set to true for this template to function.
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
copyIdeResources = true
// When true, this property will add the folder name of all declared run configurations to generated IDE run configurations.
// The folder name can be set on a run configuration using the "folderName" property.
// By default, the folder name of a run configuration is the name of the Gradle project containing it.
// generateRunFolders = true
// This property enables access transformers for use in development.
// They will be applied to the Minecraft artifact.
// The access transformer file can be anywhere in the project.
// However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge.
// This default location is a best practice to automatically put the file in the right place in the final jar.
// See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information.
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
// applies to all the run configs below
configureEach {
workingDirectory project.file('run')
workingDirectory(file('run'))
// Recommended logging data for a userdev environment
// The markers can be added/remove as needed separated by commas.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
@ -94,28 +50,32 @@ minecraft {
}
}
client {
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
var client = client {
property 'forge.enabledGameTestNamespaces', mod_id
}
client_0 {
parent(client)
args("--username", "player_0")
}
client_1 {
parent(client)
args("--username", "player_1")
}
server {
property 'forge.enabledGameTestNamespaces', mod_id
args '--nogui'
}
// This run config launches GameTestServer and runs all registered gametests, then exits.
// By default, the server will crash when no gametests are provided.
// The gametest system is also enabled by default for other run configs under the /test command.
gameTestServer {
property 'forge.enabledGameTestNamespaces', mod_id
}
data {
// example of overriding the workingDirectory set in configureEach above
workingDirectory project.file('run-data')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
workingDirectory(file('run-data'))
property('gradle.task', 'runData')
args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
}
}
@ -123,61 +83,57 @@ minecraft {
mixin {
add sourceSets.main, "${mod_id}.refmap.json"
config "${mod_id}.mixins.json"
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
repositories {
// Put repositories for dependencies here
// ForgeGradle automatically adds the Forge maven and Maven Central for you
// If you have mod jar dependencies in ./libs, you can declare them as a repository like so.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver
// flatDir {
// dir 'libs'
// }
flatDir {
dir 'libs'
}
maven { // TOP
url "https://cursemaven.com"
}
maven { //mirror
name = "ModMaven"
url = "https://modmaven.dev"
}
maven {
name "KosmX's maven"
url 'https://maven.kosmx.dev/'
}
maven {
name = "Illusive Soulworks maven"
url = "https://maven.theillusivec4.top/"
}
}
jarJar.enable()
dependencies {
// Specify the version of Minecraft to use.
// Any artifact can be supplied so long as it has a "userdev" classifier artifact and is a compatible patcher artifact.
// The "userdev" classifier will be requested and setup by ForgeGradle.
// If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"],
// then special handling is done to allow a setup of a vanilla dependency without the use of an external repository.
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
// Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}")
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}")
// runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}")
// Example mod dependency using a mod jar from ./libs with a flat dir repository
// This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
// The group id is ignored when searching -- in this case, it is "blank"
// implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}")
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
compileOnly 'org.spongepowered:mixin:0.8.5'
}
// This block of code expands all declared replace properties in the specified resource targets.
// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments.
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
tasks.named('processResources', ProcessResources).configure {
var replaceProperties = [minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range,
forge_version : forge_version, forge_version_range: forge_version_range,
loader_version_range: loader_version_range,
mod_id : mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version,
mod_authors : mod_authors, mod_description: mod_description,]
var replaceProperties = [
minecraft_version: minecraft_version,
minecraft_version_range: minecraft_version_range,
forge_version: forge_version,
forge_version_range: forge_version_range,
loader_version_range: loader_version_range,
mod_id: mod_id,
mod_name: mod_name,
mod_license: mod_license,
mod_version: mod_version,
mod_authors: mod_authors,
mod_description: mod_description,
mod_credits: mod_credits,
mod_url: mod_url,
]
inputs.properties replaceProperties
@ -186,22 +142,20 @@ tasks.named('processResources', ProcessResources).configure {
}
}
// Example for how to get properties into the manifest for reading at runtime.
tasks.named('jar', Jar).configure {
manifest {
attributes(["Specification-Title" : mod_id,
"Specification-Vendor" : mod_authors,
"Specification-Version" : "1", // We are version 1 of ourselves
"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")])
}
// This is the preferred method to reobfuscate your jar file
finalizedBy 'reobfJar'
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.encoding = 'UTF-8'
}

View File

@ -1,49 +1,20 @@
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
# The Minecraft version must agree with the Forge version to get a valid artifact
minecraft_version=1.20.1
# The Minecraft version range can use any release version of Minecraft as bounds.
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
# as they do not follow standard versioning conventions.
minecraft_version_range=[1.20.1,1.21)
# The Forge version must agree with the Minecraft version to get a valid artifact
forge_version=47.3.4
# The Forge version range can use any version of Forge as bounds or match the loader version range
forge_version_range=[47,)
# The loader version range can only use the major version of Forge/FML as bounds
loader_version_range=[47,)
# The mapping channel to use for mappings.
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
#
# | Channel | Version | |
# |-----------|----------------------|--------------------------------------------------------------------------------|
# | official | MCVersion | Official field/method names from Mojang mapping files |
# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official |
#
# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
#
# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge.
# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started
mapping_channel=official
# The mapping version to query from the mapping channel.
# This must match the format required by the mapping channel.
mapping_version=1.20.1
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
# Must match the String constant located in the main mod class annotated with @Mod.
mod_id=snowy_crescent_core
# The human-readable display name for the mod.
mapping_channel=parchment
mapping_version=2023.09.03-1.20.1
mod_id=sccore
mod_name=SnowyCrescentCore
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=GNU AGPL 3.0
# The mod version. See https://semver.org/
mod_version=1.20.1-0.0.1
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
mod_group_id=com.linearpast
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=LostInLinearPast
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description=A lib
mod_description=A lib.
mod_credits=
mod_url=https://qm.qq.com/q/k0NVzvUdlC

View File

@ -5,6 +5,7 @@ pluginManagement {
name = 'MinecraftForge'
url = 'https://maven.minecraftforge.net/'
}
maven { url = 'https://maven.parchmentmc.org' }
}
}

View File

@ -1,19 +1,28 @@
package com.linearpast.snowy_crescent_core;
package com.linearpast.sccore;
import com.linearpast.snowy_crescent_core.capability.PlayerCapabilityUtils;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.example.ModCaps;
import com.linearpast.sccore.network.Channel;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.loading.FMLEnvironment;
@Mod(SnowyCrescentCore.MODID)
public class SnowyCrescentCore {
public static final String MODID = "snowy_crescent_core";
public static final String MODID = "sccore";
public SnowyCrescentCore() {
IEventBus forgeBus = MinecraftForge.EVENT_BUS;
PlayerCapabilityUtils.registerHandler(forgeBus);
CapabilityUtils.registerHandler(forgeBus);
Channel.register();
if(!FMLEnvironment.production){
ModCaps.register();
ModCaps.addListenerToEvent(forgeBus);
}
}
}

View File

@ -1,12 +1,16 @@
package com.linearpast.sccore.capability;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import com.linearpast.sccore.capability.data.entity.EntityCapabilityHandler;
import com.linearpast.sccore.capability.data.entity.EntityCapabilityRegistry;
import com.linearpast.sccore.capability.data.player.PlayerCapabilityHandler;
import com.linearpast.sccore.capability.network.ICapabilityPacket;
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 net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.network.NetworkEvent;
@ -17,20 +21,24 @@ import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class PlayerCapabilityUtils {
public class CapabilityUtils {
/**
* 同时注册capability和对应的网络包
* 同时注册玩家capability和对应的网络包
* @param key capability的唯一name
* @param capabilityRecord capability的注册数据
* @param channelRegister 应该提前创建好实例传入参阅{@link PlayerCapabilityUtils#createChannel}
* @param channelRegister 应该提前创建好实例传入参阅{@link CapabilityUtils#createChannel}
* @param cid 网络频道索引
* @param decoder 网络包的new方法调用
* @param clazz 网络包的类
* @param decoder 网络包的decode
* @param encoder 网络包的encode
* @param handler 网络包的handle
*/
public static <T extends ICapabilityPacket> void registerCapabilityWithNetwork(
public static <T extends ICapabilityPacket<?>> void registerPlayerCapabilityWithNetwork(
ResourceLocation key, PlayerCapabilityRegistry.CapabilityRecord<? extends ICapabilitySync> capabilityRecord,
CapabilityChannel channelRegister,
Class<T> clazz, int cid,
int cid,
Class<T> clazz,
Function<FriendlyByteBuf, T> decoder,
BiConsumer<T, FriendlyByteBuf> encoder,
BiConsumer<T, Supplier<NetworkEvent.Context>> handler
@ -40,15 +48,49 @@ public class PlayerCapabilityUtils {
}
/**
* 通过此方法注册capability仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
* 同时注册实体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
*/
public static <T extends ICapabilityPacket<?>> void registerEntityCapabilityWithNetwork(
ResourceLocation key, EntityCapabilityRegistry.CapabilityRecord<? extends ICapabilitySync> capabilityRecord,
CapabilityChannel channelRegister,
int cid,
Class<T> clazz,
Function<FriendlyByteBuf, T> decoder,
BiConsumer<T, FriendlyByteBuf> encoder,
BiConsumer<T, Supplier<NetworkEvent.Context>> handler
) {
EntityCapabilityRegistry.registerCapability(key, capabilityRecord);
channelRegister.register(clazz, cid, decoder, encoder, handler);
}
/**
* 通过此方法注册玩家capability仅当 {@link net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent}
* 事件结束之前有效
* @param key capability的唯一name
* @param capabilityRecord 使用record存储了应该注册的capability的各项数据参阅{@link PlayerCapabilityRegistry.CapabilityRecord}
*/
public static <T extends ICapabilitySync> void registerCapability(ResourceLocation key, PlayerCapabilityRegistry.CapabilityRecord<T> capabilityRecord){
public static <T extends ICapabilitySync> 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}
*/
public static <T extends ICapabilitySync> void registerEntityCapability(ResourceLocation key, EntityCapabilityRegistry.CapabilityRecord<T> capabilityRecord){
EntityCapabilityRegistry.registerCapability(key, capabilityRecord);
}
/**
* 通过这个方法返回一个新的PlayerCapabilityChannel实例一般只有注册不同channel的网络包才会使用
* @param channel 你自己模组的channel
@ -73,10 +115,11 @@ public class PlayerCapabilityUtils {
*/
public static void registerHandler(IEventBus forgeBus){
PlayerCapabilityHandler.register(forgeBus);
EntityCapabilityHandler.register(forgeBus);
}
/**
* 请通过该方法获取capability
* 请通过该方法获取玩家的capability
* @param player 目标玩家
* @param key capability key
* @param clazz 应返回的capability类型
@ -85,13 +128,36 @@ public class PlayerCapabilityUtils {
@Nullable
public static <T extends ICapabilitySync> T getPlayerCapability(Player player, ResourceLocation key, Class<T> clazz) {
if(player == null) return null;
ICapabilitySync playerCapability = player.getCapability(
ICapabilitySync capabilitySync = player.getCapability(
PlayerCapabilityRegistry.getCapabilityMap().get(key).capability()
).resolve().orElse(null);
if(playerCapability == null) return null;
if(capabilitySync == null) return null;
try {
if(clazz.isInstance(playerCapability))
return clazz.cast(playerCapability);
if(clazz.isInstance(capabilitySync))
return clazz.cast(capabilitySync);
else return null;
}catch(ClassCastException e){
return null;
}
}
/**
* 请通过该方法获取实体的capability
* @param entity 目标实体
* @param key capability key
* @param clazz 应返回的capability类型
* @return 返回对应的capability
*/
@Nullable
public static <T extends ICapabilitySync> T getEntityCapability(Entity entity, ResourceLocation key, Class<T> clazz) {
if(entity == null) return null;
ICapabilitySync capabilitySync = entity.getCapability(
EntityCapabilityRegistry.getCapabilityMap().get(key).capability()
).resolve().orElse(null);
if(capabilitySync == null) return null;
try {
if(clazz.isInstance(capabilitySync))
return clazz.cast(capabilitySync);
else return null;
}catch(ClassCastException e){
return null;

View File

@ -4,16 +4,15 @@ import com.linearpast.sccore.capability.network.SimpleCapabilityPacket;
import com.linearpast.sccore.network.Channel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.common.util.INBTSerializable;
import java.util.UUID;
public interface ICapabilitySync extends INBTSerializable<CompoundTag> {
void setDirty(boolean dirty);
boolean isDirty();
void setOwnerUUID(UUID uuid);
UUID getOwnerUUID();
CompoundTag toTag(CompoundTag tag);
void fromTag(CompoundTag tag);
/**
* 该方法重写时应该在最后调用super方法
@ -21,18 +20,18 @@ public interface ICapabilitySync extends INBTSerializable<CompoundTag> {
* @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()}
*/
default void copyFrom(ICapabilitySync oldData, boolean listenDone) {
this.setOwnerUUID(oldData.getOwnerUUID());
this.setDirty(oldData.isDirty());
if(listenDone) onCopyDone();
}
/**
* 当copy的时候如果某些值需要被重定义你应该重写这个方法
* 当copy结束之后如果某些值需要被重定义你应该重写这个方法 <br>
* 多用于玩家 跨越维度/死亡 时重置数据
*/
default void onCopyDone(){}
/**
* 一般情况下建议重写否则会以sccore的Channel发送
* 一般情况下建议重写否则会以sccore的Channel实例发送<br>
* 服务端给全体玩家发送客户端同步数据
*/
default void sendToClient(){
@ -40,7 +39,7 @@ public interface ICapabilitySync extends INBTSerializable<CompoundTag> {
}
/**
* 一般情况下建议重写否则会以sccore的Channel发送
* 一般情况下建议重写否则会以sccore的Channel实例发送<br>
* 服务端给单个玩家发送客户端同步数据
* @param player 发送给的目标玩家
*/
@ -54,5 +53,6 @@ public interface ICapabilitySync extends INBTSerializable<CompoundTag> {
* 一般情况下你应该extends SimpleCapabilityPacket然后重写该方法返回你的子类
* @return 网络包类SimpleCapabilityPacket
*/
SimpleCapabilityPacket getDefaultPacket();
SimpleCapabilityPacket<? extends Entity> getDefaultPacket();
}

View File

@ -1,17 +1,20 @@
package com.linearpast.sccore.capability.data.player;
package com.linearpast.sccore.capability.data.entity;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.capability.PlayerCapabilityRegistry;
import net.minecraft.world.entity.player.Player;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod.EventBusSubscriber(modid = SnowyCrescentCore.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class PlayerCapabilityHandler {
public class EntityCapabilityHandler {
private static final Logger log = LoggerFactory.getLogger(EntityCapabilityHandler.class);
private static boolean isRegistered = false;
/**
@ -21,29 +24,34 @@ public class PlayerCapabilityHandler {
public static void register(IEventBus forgeBus) {
if (isRegistered) return;
//remainder
forgeBus.addListener(EventPriority.HIGHEST, PlayerCapabilityRemainder::capabilitySync);
forgeBus.addListener(EventPriority.HIGHEST, PlayerCapabilityRemainder::onPlayerClone);
forgeBus.addListener(EventPriority.HIGHEST, PlayerCapabilityRemainder::onPlayerRespawn);
forgeBus.addListener(EventPriority.HIGHEST, PlayerCapabilityRemainder::onEntityBeTracked);
forgeBus.addListener(EventPriority.HIGHEST, PlayerCapabilityRemainder::onPlayerLogin);
forgeBus.addListener(EventPriority.HIGHEST, EntityCapabilityRemainder::capabilitySync);
forgeBus.addListener(EventPriority.HIGHEST, EntityCapabilityRemainder::onEntityBeTracked);
forgeBus.addListener(EventPriority.HIGHEST, EntityCapabilityRemainder::onEntityJoin);
isRegistered = true;
}
//注册 capability
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void registerCapability(RegisterCapabilitiesEvent event) {
PlayerCapabilityRegistry.getCapabilityMap().values().forEach(record ->
event.register(record.clazz())
EntityCapabilityRegistry.getCapabilityMap().values().forEach(record ->
event.register(record.interfaceClass())
);
}
//附加 capability
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
if(event.getObject() instanceof Player) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, record) ->
event.addCapability(key, new PlayerCapabilityProvider(key, record.instance()))
);
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);
}
}
});
}
}
}

View File

@ -1,4 +1,4 @@
package com.linearpast.sccore.capability.data.player;
package com.linearpast.sccore.capability.data.entity;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.core.Direction;
@ -11,11 +11,15 @@ import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* cap的最终 序列化反序列化获取方法
* @param <C> 继承 {@link ICapabilitySync}
*/
@AutoRegisterCapability
public class PlayerCapabilityProvider<C extends ICapabilitySync> implements ICapabilitySerializable<CompoundTag> {
public class EntityCapabilityProvider<C extends ICapabilitySync> implements ICapabilitySerializable<CompoundTag> {
private final C instance;
private final ResourceLocation resourceLocation;
public PlayerCapabilityProvider(ResourceLocation resourceLocation, C instance) {
public EntityCapabilityProvider(ResourceLocation resourceLocation, C instance) {
this.resourceLocation = resourceLocation;
this.instance = instance;
}
@ -23,7 +27,7 @@ public class PlayerCapabilityProvider<C extends ICapabilitySync> implements ICap
@SuppressWarnings("unchecked")
@Override
public @NotNull <R> LazyOptional<R> getCapability(@NotNull Capability<R> cap, @Nullable Direction side) {
Capability<C> iCapabilitySyncCapability = (Capability<C>) PlayerCapabilityRegistry.getCapabilityRecord(resourceLocation).capability();
Capability<C> iCapabilitySyncCapability = (Capability<C>) EntityCapabilityRegistry.getCapabilityRecord(resourceLocation).capability();
return iCapabilitySyncCapability.orEmpty(cap, LazyOptional.of(() -> instance));
}

View File

@ -1,14 +1,15 @@
package com.linearpast.sccore.capability.data.player;
package com.linearpast.sccore.capability.data.entity;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.common.capabilities.Capability;
import java.util.HashMap;
import java.util.Map;
public class PlayerCapabilityRegistry {
public static final PlayerCapabilityRegistry CAPABILITIES = new PlayerCapabilityRegistry();
public class EntityCapabilityRegistry {
public static final EntityCapabilityRegistry CAPABILITIES = new EntityCapabilityRegistry();
private final Map<ResourceLocation, CapabilityRecord<?>> capabilityRecordMap = new HashMap<>();
/**
@ -30,15 +31,22 @@ public class PlayerCapabilityRegistry {
return CAPABILITIES.capabilityRecordMap.get(key);
}
/**
* 获取所有key对应的cap数据集
* @return map
*/
public static Map<ResourceLocation, CapabilityRecord<?>> getCapabilityMap(){
return CAPABILITIES.capabilityRecordMap;
}
/**
* 记录capability的注册数据
* @param instance 最终会附加给玩家的实例应该是ICapabilitySync的实例
* @param capability 一般情况下不需要初始化它默认CapabilityManager.get(new CapabilityToken<>(){})
* @param clazz instance实例对应的接口类比如ICapabilitySync.class
* @param aClass 最终会附加给实体的实例的类应该是实现了clazz的类
* @param capability 注册时一般默认{@code CapabilityManager.get(new CapabilityToken<>(){})}即可
* @param interfaceClass instance类对应的实例对应的接口类比如ICapabilitySync.class
* @param target capability附加的目标类型
*/
public record CapabilityRecord<T extends ICapabilitySync>(T instance, Capability<T> capability, Class<T> clazz) { }
public record CapabilityRecord<T extends ICapabilitySync>(Class<?> aClass, Capability<T> capability, Class<T> interfaceClass, Class<? extends Entity> target) {
}
}

View File

@ -1,63 +1,26 @@
package com.linearpast.sccore.capability.data.player;
package com.linearpast.sccore.capability.data.entity;
import com.linearpast.sccore.capability.PlayerCapabilityRegistry;
import com.linearpast.sccore.capability.PlayerCapabilityUtils;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.server.MinecraftServer;
import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry;
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.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import java.util.Optional;
public class PlayerCapabilityRemainder {
/**
* 玩家跨越维度/死亡时应该转移数据到新身体上
* @param event Clone事件
*/
public static void onPlayerClone(PlayerEvent.Clone event) {
Player entity = event.getEntity();
if(entity instanceof ServerPlayer newPlayer) {
Player original = event.getOriginal();
original.reviveCaps();
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync originData = PlayerCapabilityUtils.getPlayerCapability(original, key, ICapabilitySync.class);
ICapabilitySync newData = PlayerCapabilityUtils.getPlayerCapability(newPlayer, key, ICapabilitySync.class);
if(originData != null && newData != null) {
newData.copyFrom(originData, true);
newData.sendToClient();
}
});
original.invalidateCaps();
}
}
/**
* 玩家重生时应该更新自己的capability
* @param event 重生事件实例
*/
public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
if(event.getEntity() instanceof ServerPlayer newPlayer){
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(newPlayer, key, ICapabilitySync.class);
if(data == null) return;
data.sendToClient(newPlayer);
});
}
}
public class EntityCapabilityRemainder {
/**
* 玩家追踪实体事件<br>
* 当有其他玩家被加载时客户端需要对方的capability该事件可以主动发送<br>
* 当有其他实体被加载时客户端需要对方的capability该事件可以主动发送<br>
* 会调用{@link ICapabilitySync#sendToClient(ServerPlayer)}
* @param event 追踪事件实例
*/
public static void onEntityBeTracked(PlayerEvent.StartTracking event) {
if (event.getTarget() instanceof Player target && event.getEntity() instanceof ServerPlayer attacker) {
if (event.getEntity() instanceof ServerPlayer attacker) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(target, key, ICapabilitySync.class);
ICapabilitySync data = CapabilityUtils.getEntityCapability(event.getTarget(), key, ICapabilitySync.class);
if(data == null) return;
data.sendToClient(attacker);
});
@ -65,46 +28,42 @@ public class PlayerCapabilityRemainder {
}
/**
* 玩家Tick事件<br>
* 如果capability是dirty的就会调用{@link ICapabilitySync#sendToClient()}
* 实体Tick事件<br>
* 如果capability是dirty的就会调用{@link ICapabilitySync#sendToClient()} <br>
* 为了性能每秒才触发一次同步
* @param event 事件实例
*/
public static void capabilitySync(TickEvent.PlayerTickEvent event) {
if(!event.player.level().isClientSide){
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(event.player, key, ICapabilitySync.class);
if(data == null) return;
if(data.isDirty()) {
data.setDirty(false);
data.sendToClient();
}
});
public static void capabilitySync(LivingEvent.LivingTickEvent event) {
LivingEntity entity = event.getEntity();
if(!entity.level().isClientSide){
if (entity.tickCount % 20 == 0) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = CapabilityUtils.getEntityCapability(entity, key, ICapabilitySync.class);
if(data == null) return;
if(data.isDirty()) {
data.setDirty(false);
data.sendToClient();
}
});
}
}
}
/**
* 玩家登录事件<br>
* 实体加入level的事件初始化
* @param event 实体加入事件
*/
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
Player player = event.getEntity();
if(!(player instanceof ServerPlayer serverPlayer)) return;
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(player, key, ICapabilitySync.class);
public static void onEntityJoin(EntityJoinLevelEvent event) {
Entity entity = event.getEntity();
if(entity.level().isClientSide) return;
EntityCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = CapabilityUtils.getEntityCapability(entity, key, ICapabilitySync.class);
if(data == null) return;
data.setOwnerUUID(serverPlayer.getUUID());
if(data instanceof SimpleEntityCapabilitySync capabilitySync){
capabilitySync.setId(entity.getId());
}
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 = PlayerCapabilityUtils.getPlayerCapability(player, key, ICapabilitySync.class);
if(data == null) return;
data.sendToClient(serverPlayer);
});
}
})
);
}
}

View File

@ -4,14 +4,17 @@ import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.nbt.CompoundTag;
/**
* 实现时建议手动添加一个字段以及方法例如
* 实现时建议手动添加<br>
* key 作为cap的唯一标识 <br>
* getCapability(Entity entity) 获取cap的简化方法<br>
*
* <pre>
* {@code
* public static final ResourceLocation key =
* new ResourceLocation(MyMod.MODID, "my_data");
* public static Optional<MyDataCapability> getCapability(Player player){
* return Optional.ofNullable(EntityCapabilityHandler.getPlayerCapability(
* player, MyDataCapability.key, MyDataCapability.class
* new ResourceLocation(MyMod.MODID, "sheep_data");
* public static Optional<SheepDataCapability> getCapability(Sheep sheep){
* return Optional.ofNullable(CapabilityUtils.getEntityCapability(
* player, SheepDataCapability.key, SheepDataCapability.class
* ));
* }
* }
@ -29,6 +32,10 @@ public abstract class SimpleEntityCapabilitySync implements ICapabilitySync {
return dirty;
}
/**
* 你应该在每个属性的setter里调用它设置为true以触发自动同步
* @param dirty 是否应该同步是否为脏
*/
@Override
public void setDirty(boolean dirty) {
this.dirty = dirty;

View File

@ -1,7 +1,7 @@
package com.linearpast.sccore.capability.data;
package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.capability.PlayerCapabilityRegistry;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
@ -9,9 +9,12 @@ import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod.EventBusSubscriber(modid = SnowyCrescentCore.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class PlayerCapabilityHandler {
private static final Logger log = LoggerFactory.getLogger(PlayerCapabilityHandler.class);
private static boolean isRegistered = false;
/**
@ -33,7 +36,7 @@ public class PlayerCapabilityHandler {
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void registerCapability(RegisterCapabilitiesEvent event) {
PlayerCapabilityRegistry.getCapabilityMap().values().forEach(record ->
event.register(record.clazz())
event.register(record.interfaceClass())
);
}
@ -41,9 +44,14 @@ public class PlayerCapabilityHandler {
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
if(event.getObject() instanceof Player) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, record) ->
event.addCapability(key, new PlayerCapabilityProvider(key, record.instance()))
);
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, record) -> {
try {
ICapabilitySync capabilitySync = (ICapabilitySync) record.aClass().getDeclaredConstructor().newInstance();
event.addCapability(key, new PlayerCapabilityProvider<>(key, capabilitySync));
} catch (Exception e) {
log.error("Failed to instantiate capability sync class {}. Your capability register is wrong.", record.aClass(), e);
}
});
}
}
}

View File

@ -1,6 +1,6 @@
package com.linearpast.sccore.capability.data;
package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
@ -12,10 +12,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@AutoRegisterCapability
public class CapabilityProvider<C extends ICapabilitySync> implements ICapabilitySerializable<CompoundTag> {
public class PlayerCapabilityProvider<C extends ICapabilitySync> implements ICapabilitySerializable<CompoundTag> {
private final C instance;
private final ResourceLocation resourceLocation;
public CapabilityProvider(ResourceLocation resourceLocation, C instance) {
public PlayerCapabilityProvider(ResourceLocation resourceLocation, C instance) {
this.resourceLocation = resourceLocation;
this.instance = instance;
}

View File

@ -1,4 +1,4 @@
package com.linearpast.sccore.capability;
package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.resources.ResourceLocation;
@ -7,8 +7,8 @@ import net.minecraftforge.common.capabilities.Capability;
import java.util.HashMap;
import java.util.Map;
public class CapabilityRegistry {
public static final CapabilityRegistry CAPABILITIES = new CapabilityRegistry();
public class PlayerCapabilityRegistry {
public static final PlayerCapabilityRegistry CAPABILITIES = new PlayerCapabilityRegistry();
private final Map<ResourceLocation, CapabilityRecord<?>> capabilityRecordMap = new HashMap<>();
/**
@ -36,9 +36,9 @@ public class CapabilityRegistry {
/**
* 记录capability的注册数据
* @param instance 最终会附加给玩家的实例应该是ICapabilitySync的实例
* @param aClass 最终会附加给玩家的实例应该是ICapabilitySync的实例
* @param capability 一般情况下不需要初始化它默认CapabilityManager.get(new CapabilityToken<>(){})
* @param clazz instance实例对应的接口类比如ICapabilitySync.class
* @param interfaceClass instance实例对应的接口类比如ICapabilitySync.class
*/
public record CapabilityRecord<T extends ICapabilitySync>(T instance, Capability<T> capability, Class<T> clazz) { }
public record CapabilityRecord<T extends ICapabilitySync>(Class<?> aClass, Capability<T> capability, Class<T> interfaceClass) { }
}

View File

@ -1,7 +1,7 @@
package com.linearpast.sccore.capability.data;
package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.capability.PlayerCapabilityRegistry;
import com.linearpast.sccore.capability.PlayerCapabilityUtils;
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;
@ -11,6 +11,9 @@ import net.minecraftforge.event.entity.player.PlayerEvent;
import java.util.Optional;
/**
* 用于维护数据同步
*/
public class PlayerCapabilityRemainder {
/**
* 玩家跨越维度/死亡时应该转移数据到新身体上
@ -22,8 +25,8 @@ public class PlayerCapabilityRemainder {
Player original = event.getOriginal();
original.reviveCaps();
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync originData = PlayerCapabilityUtils.getPlayerCapability(original, key, ICapabilitySync.class);
ICapabilitySync newData = PlayerCapabilityUtils.getPlayerCapability(newPlayer, key, ICapabilitySync.class);
ICapabilitySync originData = CapabilityUtils.getPlayerCapability(original, key, ICapabilitySync.class);
ICapabilitySync newData = CapabilityUtils.getPlayerCapability(newPlayer, key, ICapabilitySync.class);
if(originData != null && newData != null) {
newData.copyFrom(originData, true);
newData.sendToClient();
@ -40,7 +43,7 @@ public class PlayerCapabilityRemainder {
public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
if(event.getEntity() instanceof ServerPlayer newPlayer){
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(newPlayer, key, ICapabilitySync.class);
ICapabilitySync data = CapabilityUtils.getPlayerCapability(newPlayer, key, ICapabilitySync.class);
if(data == null) return;
data.sendToClient(newPlayer);
});
@ -56,7 +59,7 @@ public class PlayerCapabilityRemainder {
public static void onEntityBeTracked(PlayerEvent.StartTracking event) {
if (event.getTarget() instanceof Player target && event.getEntity() instanceof ServerPlayer attacker) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(target, key, ICapabilitySync.class);
ICapabilitySync data = CapabilityUtils.getPlayerCapability(target, key, ICapabilitySync.class);
if(data == null) return;
data.sendToClient(attacker);
});
@ -71,7 +74,7 @@ public class PlayerCapabilityRemainder {
public static void capabilitySync(TickEvent.PlayerTickEvent event) {
if(!event.player.level().isClientSide){
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(event.player, key, ICapabilitySync.class);
ICapabilitySync data = CapabilityUtils.getPlayerCapability(event.player, key, ICapabilitySync.class);
if(data == null) return;
if(data.isDirty()) {
data.setDirty(false);
@ -82,15 +85,20 @@ public class PlayerCapabilityRemainder {
}
/**
* 玩家登录事件<br>
* 玩家登录事件 <br>
* 重初始化登录玩家的cap <br>
* 将服务端所有玩家的cap发送给该玩家以初始化该玩家的客户端侧的RemotePlayer数据<br>
* 上一行的这个行为可能会导致卡顿它的必要性还未知可以发pr或issue提议删除它
*/
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
Player player = event.getEntity();
if(!(player instanceof ServerPlayer serverPlayer)) return;
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(player, key, ICapabilitySync.class);
ICapabilitySync data = CapabilityUtils.getPlayerCapability(player, key, ICapabilitySync.class);
if(data == null) return;
data.setOwnerUUID(serverPlayer.getUUID());
if(data instanceof SimplePlayerCapabilitySync capabilitySync) {
capabilitySync.setOwnerUUID(serverPlayer.getUUID());
}
data.setDirty(false);
data.sendToClient();
});
@ -98,7 +106,7 @@ public class PlayerCapabilityRemainder {
serverPlayers -> serverPlayers.forEach(p -> {
if(!p.getUUID().equals(serverPlayer.getUUID())) {
PlayerCapabilityRegistry.getCapabilityMap().forEach((key, value) -> {
ICapabilitySync data = PlayerCapabilityUtils.getPlayerCapability(player, key, ICapabilitySync.class);
ICapabilitySync data = CapabilityUtils.getPlayerCapability(player, key, ICapabilitySync.class);
if(data == null) return;
data.sendToClient(serverPlayer);
});

View File

@ -1,17 +1,21 @@
package com.linearpast.sccore.capability.data;
package com.linearpast.sccore.capability.data.player;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.nbt.CompoundTag;
import java.util.UUID;
/**
* 实现时建议手动添加一个字段以及方法例如
* 实现时建议手动添加<br>
* key 作为cap的唯一标识 <br>
* getCapability(Player player) 获取cap的简化方法<br>
*
* <pre>
* {@code
* public static final ResourceLocation key =
* new ResourceLocation(MyMod.MODID, "my_data");
* public static Optional<MyDataCapability> getCapability(Player player){
* return Optional.ofNullable(PlayerCapabilityHandler.getPlayerCapability(
* return Optional.ofNullable(CapabilityUtils.getPlayerCapability(
* player, MyDataCapability.key, MyDataCapability.class
* ));
* }
@ -19,7 +23,7 @@ import java.util.UUID;
* </pre>
*
*/
public abstract class SimpleCapabilitySync implements ICapabilitySync {
public abstract class SimplePlayerCapabilitySync implements ICapabilitySync {
public static final String OwnerUUID = "OwnerUUID";
private boolean dirty;
@ -35,35 +39,56 @@ public abstract class SimpleCapabilitySync implements ICapabilitySync {
this.dirty = dirty;
}
@Override
public UUID getOwnerUUID() {
return ownerUUID;
}
@Override
public void setOwnerUUID(UUID ownerUUID) {
this.ownerUUID = ownerUUID;
setDirty(true);
}
/**
* 序列化为tag
* 从参数实例中复制数据到当前实例 <br>
* 你不应该重写它你应该实现 {@link SimplePlayerCapabilitySync#copyFrom(ICapabilitySync)}
* @param oldData 旧数据
* @param listenDone 最后是否执行完成方法 {@link ICapabilitySync#onCopyDone()}
*/
@Override
public void copyFrom(ICapabilitySync oldData, boolean listenDone) {
SimplePlayerCapabilitySync data = (SimplePlayerCapabilitySync) oldData;
this.setOwnerUUID(data.getOwnerUUID());
ICapabilitySync.super.copyFrom(oldData, listenDone);
}
/**
* 触发数据复制时会执行的方法
* @param oldData 从这个数据中复制到当前实例
*/
public abstract void copyFrom(ICapabilitySync oldData);
/**
* 序列化为tag <br>
* 你不应该重写它你应该实现{@link ICapabilitySync#toTag(CompoundTag)}
* @return tag
*/
@Override
public CompoundTag serializeNBT() {
CompoundTag tag = new CompoundTag();
if(ownerUUID != null) tag.putUUID(OwnerUUID, ownerUUID);
tag = toTag(tag);
return tag;
}
/**
* 反序列化为实例对象
* 反序列化为实例对象 <br>
* 你不应该重写它你应该实现{@link ICapabilitySync#fromTag(CompoundTag)} )}
* @param nbt nbt
*/
@Override
public void deserializeNBT(CompoundTag nbt) {
this.ownerUUID = null;
if(nbt.contains(OwnerUUID)) this.ownerUUID = nbt.getUUID(OwnerUUID);
fromTag(nbt);
}
}

View File

@ -5,20 +5,25 @@ import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.simple.SimpleChannel;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 你应该在你的Channel中使用<br>
* PlayerCapabilityChannel.create(你的Channel).add(索引, 网络包的new方法) <br>
* 所添加的网络包必须实现了ICapabilityPacket接口
* 在Mod主类构造方法逻辑中调用createChannel有两种<br>
* <pre>
* 1. {@link com.linearpast.sccore.capability.CapabilityUtils#createChannel(SimpleChannel)}
* 若如此做则必须重写Cap实体类中的所有sendToPlayer方法并在重写中调用使用你的Channel
* </pre>
* <pre>
* 2. {@link com.linearpast.sccore.capability.CapabilityUtils#createChannel()}
* 若如此做则网络包会以SnowyCrescentCore的Channel注册
* </pre>
* 所添加的网络包必须实现ICapabilityPacket接口
*/
public class PlayerCapabilityChannel {
public class CapabilityChannel {
private final SimpleChannel channel;
public PlayerCapabilityChannel(SimpleChannel channel) {
public CapabilityChannel(SimpleChannel channel) {
this.channel = channel;
}
@ -31,7 +36,7 @@ public class PlayerCapabilityChannel {
* @param handler 句柄
* @param <T> 网络包接口
*/
public <T extends ICapabilityPacket> void register(
public <T extends ICapabilityPacket<?>> void register(
Class<T> clazz,
int cid,
Function<FriendlyByteBuf, T> decoder,

View File

@ -1,15 +1,15 @@
package com.linearpast.snowy_crescent_core.capability.network;
package com.linearpast.sccore.capability.network;
import com.linearpast.snowy_crescent_core.capability.data.ICapabilitySync;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
public interface ICapabilityPacket {
public interface ICapabilityPacket<T extends Entity> {
/**
* 解码网络包
* @param buf FriendlyByteBuf
@ -33,10 +33,10 @@ public interface ICapabilityPacket {
/**
* 在网络包中获取对应的capability一般在 {@link ICapabilityPacket#syncData}后执行
* @param player 目标玩家
* @param entity 目标实体
* @return 返回Capability
*/
@Nullable ICapabilitySync getCapability(Player player);
@Nullable ICapabilitySync getCapability(T entity);
/**
* 获取Tag

View File

@ -1,9 +1,15 @@
package com.linearpast.snowy_crescent_core.capability.network;
package com.linearpast.sccore.capability.network;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import com.linearpast.sccore.capability.data.entity.SimpleEntityCapabilitySync;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent;
public abstract class SimpleCapabilityPacket implements ICapabilityPacket {
public abstract class SimpleCapabilityPacket<T extends Entity> implements ICapabilityPacket<T> {
private final CompoundTag data;
public SimpleCapabilityPacket(CompoundTag data) {
@ -19,6 +25,21 @@ public abstract class SimpleCapabilityPacket implements ICapabilityPacket {
buf.writeNbt(data);
}
@SuppressWarnings("unchecked")
@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();
Entity entity = level.getEntity(nbt.getInt(SimpleEntityCapabilitySync.Id));
try {
ICapabilitySync data = getCapability((T) entity);
syncData(nbt, data);
}catch (Exception ignored) {}
}
@Override
public CompoundTag getData() {
return data;

View File

@ -1,38 +1,60 @@
package com.linearpast.sccore.test;
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 com.linearpast.sccore.test.cap.ISheepData;
import com.linearpast.sccore.test.cap.SheepDataCapability;
import com.linearpast.sccore.test.event.PlayerAttackEvent;
import com.linearpast.sccore.test.network.SheepCapabilityPacket;
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>
*/
public static void register(){
//如果你想将网络包注册到你自己的mod中createChannel(INSTANCE)
//然后别忘记在capability类里面重写所有的sendToClient方法
CapabilityChannel channel = CapabilityUtils.createChannel();
//不可与其他网络包重复的任意整数
int cid = Channel.getCid();
//注册实体cap和它的网络包
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, SheepCapabilityPacket.class, cid++,
SheepCapabilityPacket::new,
SheepCapabilityPacket::encode,
SheepCapabilityPacket::handle
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

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

View File

@ -1,58 +1,100 @@
package com.linearpast.sccore.test.cap;
package com.linearpast.sccore.example.cap;
import com.linearpast.sccore.SnowyCrescentCore;
import com.linearpast.sccore.capability.CapabilityUtils;
import com.linearpast.sccore.capability.data.ICapabilitySync;
import com.linearpast.sccore.capability.data.entity.SimpleEntityCapabilitySync;
import com.linearpast.sccore.capability.network.SimpleCapabilityPacket;
import com.linearpast.sccore.test.network.SheepCapabilityPacket;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
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>
*/
public class SheepDataCapability extends SimpleEntityCapabilitySync 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
@Override
public CompoundTag toTag(CompoundTag tag) {
if(value != null) tag.putInt(Value, value);
return tag;
}
//在SimpleEntityCapabilitySync的deserializeNBT方法中会调用
//实际上相当于deserializeNBT
@Override
public void fromTag(CompoundTag tag) {
this.value = null;
if(tag.contains(Value)) this.value = tag.getInt(Value);
}
//从旧实例中复制数据到新实例的方法
@Override
public void copyFrom(ICapabilitySync oldData) {
SheepDataCapability data = (SheepDataCapability) oldData;
this.value = data.getValue();
}
/**
* 网络包你可以在里面重写任意方法关于方法的作用请参阅<br>
* {@link com.linearpast.sccore.capability.network.ICapabilityPacket} <br>
* 可以不写在内部类中作者是觉得它内容太少写里面显得更紧凑美观
*/
public static class SheepCapabilityPacket extends SimpleCapabilityPacket<Sheep> {
//网络包构造方法
public SheepCapabilityPacket(CompoundTag data) {
super(data);
}
//这实际上是decoder
public SheepCapabilityPacket(FriendlyByteBuf buf) {
super(buf);
}
//仅用在网络包内部的getCap
@Override
public @Nullable ICapabilitySync getCapability(Sheep entity) {
return SheepDataCapability.getCapability(entity).orElse(null);
}
}
//获取默认网络包会在sendToClient的时候调用以发送
@Override
public SimpleCapabilityPacket<Sheep> getDefaultPacket() {
return new SheepCapabilityPacket(serializeNBT());
}
//在其他地方需要用到cap的时候调用这个
//目的是为了简化cap utils的方法
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.test.event;
package com.linearpast.sccore.example.event;
import com.linearpast.sccore.test.cap.SheepDataCapability;
import com.linearpast.sccore.example.cap.SheepDataCapability;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
@ -9,21 +9,22 @@ 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();
if(entity instanceof ServerPlayer player) {
if(target instanceof Sheep sheep){
SheepDataCapability iSheepData = SheepDataCapability.getCapability(sheep).orElse(null);
if(iSheepData == null) return;
Integer value = iSheepData.getValue();
if(value == null) value = 0;
value++;
iSheepData.setValue(value);
Integer id = iSheepData.getId();
player.sendSystemMessage(Component.literal(
"" + value + "攻击了id为\"" + id + "\"的羊"
));
SheepDataCapability.getCapability(sheep).ifPresent(data -> {
Integer value = data.getValue();
if(value == null) value = 0;
value++;
data.setValue(value);
Integer id = data.getId();
player.sendSystemMessage(Component.literal(
"" + value + "攻击了id为\"" + id + "\"的羊"
));
});
}
}
}

View File

@ -1,6 +1,6 @@
package com.linearpast.snowy_crescent_core.network;
package com.linearpast.sccore.network;
import com.linearpast.snowy_crescent_core.SnowyCrescentCore;
import com.linearpast.sccore.SnowyCrescentCore;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.fml.ModList;

View File

@ -1,63 +1,28 @@
# This is an example mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here: https://github.com/toml-lang/toml
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader = "javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion = "${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
modLoader = "javafml"
loaderVersion = "${loader_version_range}"
license = "${mod_license}"
# A URL to refer people to when problems occur with this mod
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId = "${mod_id}" #mandatory
# The version number of the mod
version = "${mod_version}" #mandatory
# A display name for the mod
displayName = "${mod_name}" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
displayURL = "https://qm.qq.com/q/gkwAGdv9UQ" #optional
# A file name (in the root of the mod JAR) containing a logo for display
#logoFile="snowy_crescent_core.png" #optional
# A text field displayed in the mod UI
#credits="Thanks for this example mod goes to Java" #optional
# A text field displayed in the mod UI
authors = "${mod_authors}" #optional
# Display Test controls the display for your mod in the server connection screen
# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod.
# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod.
# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component.
# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value.
# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself.
#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional)
issueTrackerURL="${mod_url}"
# The description text for the mod (multi line!) (#mandatory)
[[mods]]
modId = "${mod_id}"
version = "${mod_version}"
displayName = "${mod_name}"
displayURL="${mod_url}"
logoFile="logo.png"
credits="${mod_credits}"
authors = "${mod_authors}"
description = '''${mod_description}'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies."${mod_id}"]] #optional
# the modid of the dependency
modId = "forge" #mandatory
# Does this dependency have to exist - if not, ordering below must be specified
mandatory = true #mandatory
# The version range of the dependency
versionRange = "${forge_version_range}" #mandatory
# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory
# BEFORE - This mod is loaded BEFORE the dependency
# AFTER - This mod is loaded AFTER the dependency
[[dependencies."${mod_id}"]]
modId = "forge"
mandatory = true
versionRange = "${forge_version_range}"
ordering = "NONE"
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
side = "BOTH"# Here's another dependency
side = "BOTH"
[[dependencies."${mod_id}"]]
modId = "minecraft"
mandatory = true
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange = "${minecraft_version_range}"
ordering = "NONE"
side = "BOTH"
side = "BOTH"

View File

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

View File

@ -1,9 +1,9 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.linearpast.snowy_crescent_core.mixin",
"package": "com.linearpast.sccore.mixin",
"compatibilityLevel": "JAVA_8",
"refmap": "snowy_crescent_core.refmap.json",
"refmap": "sccore.refmap.json",
"mixins": [
],
"client": [