深色模式
开发者快速开始:完整 Paper 集成项目
第三方只能依赖公开 API,不能编译依赖 sealattributes-core、sealattributes-paper 或反射内部类。坐标:
text
cn.seal:sealattributes-api:1.0.9API baseline 是 1.3。API JAR 是 compileOnly,不能放进服务器 plugins/,也不要 shade。
项目结构
text
class-boost/
├─ settings.gradle.kts
├─ build.gradle.kts
└─ src/main/
├─ java/example/classboost/ClassBoostPlugin.java
└─ resources/plugin.ymlsettings.gradle.kts:
kotlin
rootProject.name = "class-boost"build.gradle.kts:
kotlin
plugins { java }
group = "example"
version = "1.0.0"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
mavenLocal() // 或团队存放 SealAttributes API 的私有 Maven 仓库
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
compileOnly("cn.seal:sealattributes-api:1.0.9")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}plugin.yml:
yaml
name: ClassBoost
version: 1.0.0
main: example.classboost.ClassBoostPlugin
api-version: '1.21'
depend: [SealAttributes]
commands:
classboost:
description: 切换示例生命加成
usage: /classboostClassBoostPlugin.java:
java
package example.classboost;
import cn.sealplugins.attributes.api.ProviderBindingResult;
import cn.sealplugins.attributes.api.SealAttributesApi;
import cn.sealplugins.attributes.api.SealAttributesApiProvider;
import cn.sealplugins.attributes.api.definition.AttributeId;
import cn.sealplugins.attributes.api.source.ContributionOperation;
import cn.sealplugins.attributes.api.source.PublishResult;
import cn.sealplugins.attributes.api.source.SourceContribution;
import cn.sealplugins.attributes.api.source.SourceKey;
import cn.sealplugins.attributes.api.source.SourcePersistence;
import cn.sealplugins.attributes.api.source.SourceSnapshot;
import cn.sealplugins.attributes.api.subject.QueryResult;
import cn.sealplugins.attributes.api.subject.SubjectKey;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
public final class ClassBoostPlugin extends JavaPlugin {
private static final SourceKey SOURCE =
new SourceKey("classboost", "class/warrior");
private final Map<UUID, Long> revisions = new ConcurrentHashMap<>();
private SealAttributesApi api;
@Override public void onEnable() {
RegisteredServiceProvider<SealAttributesApiProvider> service =
Bukkit.getServicesManager().getRegistration(SealAttributesApiProvider.class);
if (service == null) throw new IllegalStateException("SealAttributes service unavailable");
ProviderBindingResult binding =
service.getProvider().bind("classboost", ClassBoostPlugin.class);
if (!binding.bound()) {
throw new IllegalStateException("SealAttributes bind failed: " + binding.getStatus());
}
api = binding.getApi().orElseThrow();
getLogger().info("Bound SealAttributes API " + api.apiVersion()
+ ", ownerEpoch=" + api.ownerEpoch());
}
@Override public boolean onCommand(
CommandSender sender, Command command, String label, String[] args
) {
if (!(sender instanceof Player player)) return false;
QueryResult<SubjectKey> current = api.queries().currentSubject(player.getUniqueId());
if (!current.found()) {
player.sendMessage("属性尚未 READY,请稍后重试。");
return true;
}
SubjectKey subject = current.getValue().orElseThrow();
long revision = revisions.merge(player.getUniqueId(), 1L, Long::sum);
SourceSnapshot snapshot = new SourceSnapshot(
SOURCE,
revision,
List.of(new SourceContribution(
AttributeId.of("sealattributes", "max_health"),
ContributionOperation.ADD,
50.0
)),
SourcePersistence.RUNTIME,
Optional.empty()
);
PublishResult result = api.sources().publish(subject, snapshot);
player.sendMessage("发布结果:" + result.getStatus());
return true;
}
}构建与安装:
powershell
.\gradlew.bat clean build把 class-boost-1.0.0.jar 放入测试服 plugins/,API JAR 不放。登录执行 /classboost,预期返回 ACCEPTED 类成功状态,/sa sources <玩家> 出现 classboost:class/warrior,最大生命增加 50。
绑定状态与生命周期
没有全局 singleton。每个插件用自己的主类绑定唯一小写 provider。状态包括:BOUND、INVALID_NAMESPACE、OWNER_NOT_FOUND、NAMESPACE_CONFLICT、PLATFORM_NOT_READY、CLOSED。
owner epoch 是租约:调用插件停用后,旧 facade、publisher、订阅都会失效并返回 typed stale/closed 状态,绝不会自动附着到新实例。不要把 facade 放在静态跨重载单例中。
必需联动用 depend: [SealAttributes]。可选联动才用 softdepend,并在每次启用检查服务。平台可能在第三方 onEnable 后才完成运行态激活,因此来源发布前要处理 NOT_READY;定义、公式、计划、Gate 和 extension 则必须在启动注册窗口内注册并检查 RegistrationStatus。
Kotlin 调用提示
Java Optional 和 getter 在 Kotlin 可直接使用:
kotlin
val binding = provider.bind("classboost", ClassBoostPlugin::class.java)
check(binding.bound()) { "bind failed: ${binding.status}" }
val api = binding.api.orElseThrow()不要在服务端线程对 CompletionStage 调用 join();持久来源完成后用 thenAccept,需要回 Bukkit 时再由插件调度器切回合法上下文。