Sponge API 13 ()

* Update to Sponge API 13

* Fix sponge including minecraft in the Plan jar

Co-authored-by: Antti Koponen <koponen942@outlook.com>

* Fix plugin startup issue due to net.kyori classes in META-INF

* Replace Gamemode mixin with the appropriate listener

---------

Co-authored-by: Antti Koponen <koponen942@outlook.com>
This commit is contained in:
Aurora Lahtela 2025-03-23 11:35:19 +02:00 committed by GitHub
parent 4cb090f17a
commit b2c842b7bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 94 additions and 152 deletions

@ -61,7 +61,7 @@ subprojects {
bukkitVersion = "1.13.2-R0.1-SNAPSHOT"
spigotVersion = "1.13.2-R0.1-SNAPSHOT"
paperVersion = "1.13.2-R0.1-SNAPSHOT"
spongeVersion = "8.1.0"
spongeVersion = "13.0.0"
nukkitVersion = "1.0-SNAPSHOT"
bungeeVersion = "1.16-R0.4"
velocityVersion = "3.0.0-SNAPSHOT"

@ -251,5 +251,6 @@
"são tomé and príncipe": "STP",
"turks and caicos islands": "TCA",
"norfolk island": "NFK",
"pitcairn islands": "PCN"
"pitcairn islands": "PCN",
"antarctica": "ATA"
}

@ -21,13 +21,6 @@ dependencies {
implementation project(path: ":extensions:adventure", configuration: "shadow")
}
jar {
// Add the sponge mixin into the manifest
manifest.attributes([
"MixinConfigs": "plan-sponge.mixins.json"
])
}
tasks.named("shadowJar", ShadowJar) {
// Exclude these files
exclude "**/*.svg"
@ -39,6 +32,7 @@ tasks.named("shadowJar", ShadowJar) {
exclude "**/module-info.class"
exclude "module-info.class"
exclude "META-INF/versions/" // Causes Sponge to crash
exclude "META-INF/services/net.kyori.**" // Causes ResolutionException on Sponge due to export to net.kyori module
exclude "mozilla/**/*"
// Exclude extra dependencies

@ -6,6 +6,10 @@ plugins {
id "org.spongepowered.gradle.vanilla" version "0.2.1-SNAPSHOT"
}
// Shadow plugin applied even though shadow is not used in the module,
// so that VanillaGradle doesn't include Minecraft in runtimeClassPath
apply plugin: "com.gradleup.shadow"
dependencies {
implementation project(":common")
@ -68,10 +72,10 @@ sponge {
}
minecraft {
version("1.16.5")
version("1.21.3")
platform(MinecraftPlatform.SERVER)
}
compileJava {
options.release = 11
tasks.withType(JavaCompile).configureEach {
options.release.set(21)
}

@ -70,7 +70,7 @@ public class SpongePlayerData implements PlatformPlayerData {
@Override
public Optional<String> getCurrentWorld() {
return Sponge.game().server().worldManager().worldDirectory(player.world().key())
return Optional.ofNullable(Sponge.game().server().worldManager().worldDirectory(player.world().key()))
.map(path -> path.getFileName().toString());
}

@ -30,9 +30,9 @@ import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransac
import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.Game;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.Order;
@ -151,11 +151,12 @@ public class PlayerOnlineListener {
@Listener(order = Order.DEFAULT)
public void beforeQuit(ServerSideConnectionEvent.Disconnect event) {
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new SpongePlayerData(event.player()))
.time(System.currentTimeMillis())
.build());
getPlayer(event)
.ifPresent(player -> leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(player)
.time(System.currentTimeMillis())
.build()));
}
@Listener(order = Order.POST)
@ -169,14 +170,22 @@ public class PlayerOnlineListener {
private void actOnQuitEvent(ServerSideConnectionEvent.Disconnect event) {
long time = System.currentTimeMillis();
Player player = event.player();
UUID playerUUID = player.uniqueId();
getPlayer(event)
.ifPresent(player -> {
UUID playerUUID = player.getUUID();
SpongeAFKListener.afkTracker.loggedOut(playerUUID, time);
leaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(player)
.time(System.currentTimeMillis())
.build());
});
}
SpongeAFKListener.afkTracker.loggedOut(playerUUID, time);
leaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new SpongePlayerData(event.player()))
.time(System.currentTimeMillis())
.build());
private @NotNull Optional<SpongePlayerData> getPlayer(ServerSideConnectionEvent.Disconnect event) {
return event.profile()
.map(GameProfile::uuid)
.flatMap(playerUUID -> game.server().player(playerUUID))
.map(SpongePlayerData::new);
}
}

@ -31,6 +31,7 @@ import org.spongepowered.api.event.entity.living.player.PlayerChangeClientSettin
import org.spongepowered.api.event.filter.cause.First;
import org.spongepowered.api.event.message.PlayerChatEvent;
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
import org.spongepowered.api.profile.GameProfile;
import javax.inject.Inject;
import java.util.Map;
@ -122,6 +123,6 @@ public class SpongeAFKListener {
@Listener(order = Order.POST)
public void onLeave(ServerSideConnectionEvent.Disconnect event) {
ignorePermissionInfo.remove(event.player().uniqueId());
event.profile().map(GameProfile::uuid).ifPresent(ignorePermissionInfo::remove);
}
}

@ -38,7 +38,7 @@ import org.spongepowered.api.entity.living.animal.Wolf;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.projectile.Projectile;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.cause.entity.damage.source.EntityDamageSource;
import org.spongepowered.api.event.cause.entity.damage.source.DamageSource;
import org.spongepowered.api.event.entity.DestructEntityEvent;
import org.spongepowered.api.item.ItemType;
import org.spongepowered.api.item.ItemTypes;
@ -83,7 +83,7 @@ public class SpongeDeathListener {
}
try {
List<EntityDamageSource> causes = event.cause().allOf(EntityDamageSource.class);
List<DamageSource> causes = event.cause().allOf(DamageSource.class);
Optional<Player> foundKiller = findKiller(causes, 0);
if (foundKiller.isEmpty()) {
return;
@ -107,28 +107,38 @@ public class SpongeDeathListener {
return new PlayerKill.Victim(victim.uniqueId(), victim.name());
}
public Optional<Player> findKiller(List<EntityDamageSource> causes, int depth) {
public Optional<Player> findKiller(List<DamageSource> causes, int depth) {
if (causes.isEmpty() || causes.size() < depth) {
return Optional.empty();
}
EntityDamageSource damageSource = causes.get(depth);
Entity killerEntity = damageSource.source();
DamageSource damageSource = causes.get(depth);
Optional<Entity> source = damageSource.source();
if (source.isEmpty()) source = damageSource.indirectSource();
if (source.isEmpty()) return Optional.empty();
if (killerEntity instanceof Player) return Optional.of((Player) killerEntity);
if (killerEntity instanceof Wolf) return getOwner((Wolf) killerEntity);
if (killerEntity instanceof Projectile) return getShooter((Projectile) killerEntity);
if (killerEntity instanceof EndCrystal) return findKiller(causes, depth + 1);
return Optional.empty();
Entity killerEntity = source.get();
return switch (killerEntity) {
case Player player -> Optional.of(player);
case Wolf wolf -> getOwner(wolf);
case Projectile projectile -> getShooter(projectile);
case EndCrystal endCrystal -> findKiller(causes, depth + 1);
default -> Optional.empty();
};
}
public String findWeapon(DestructEntityEvent.Death death) {
Optional<EntityDamageSource> damagedBy = death.cause().first(EntityDamageSource.class);
Optional<DamageSource> damagedBy = death.cause().first(DamageSource.class);
if (damagedBy.isPresent()) {
EntityDamageSource damageSource = damagedBy.get();
Entity killerEntity = damageSource.source();
DamageSource damageSource = damagedBy.get();
Optional<Entity> source = damageSource.source();
if (source.isEmpty()) source = damageSource.indirectSource();
if (source.isEmpty()) return "Unknown";
if (killerEntity instanceof Player) return getItemInHand((Player) killerEntity);
Entity killerEntity = source.get();
if (killerEntity instanceof Player player) return getItemInHand(player);
if (killerEntity instanceof Wolf) return "Wolf";
Optional<ResourceKey> entityType = killerEntity.type().findKey(RegistryTypes.ENTITY_TYPE);
@ -148,14 +158,14 @@ public class SpongeDeathListener {
private Optional<Player> getShooter(Projectile projectile) {
ProjectileSource source = projectile.shooter().map(Value::get).orElse(null);
if (source instanceof Player) {
return Optional.of((Player) source);
if (source instanceof Player player) {
return Optional.of(player);
}
return Optional.empty();
}
private Optional<Player> getOwner(Wolf wolf) {
return wolf.tamer().flatMap(uuid -> Sponge.game().server().player(uuid.get()));
return wolf.owner().flatMap(uuid -> Sponge.game().server().player(uuid.get()));
}
}

@ -22,23 +22,21 @@ import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.config.WorldAliasSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.StoreWorldNameTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameType;
import org.spongepowered.api.ResourceKey;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.DataTransactionResult;
import org.spongepowered.api.data.Keys;
import org.spongepowered.api.data.value.Value;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.data.ChangeDataHolderEvent;
import org.spongepowered.api.registry.RegistryTypes;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
/**
* Listener for GameMode change on Sponge.
@ -47,8 +45,6 @@ import java.util.function.Consumer;
*/
public class SpongeGMChangeListener {
public static final List<Consumer<Event>> EVENT_CONSUMERS = new ArrayList<>(); // Available to the mixin
private final WorldAliasSettings worldAliasSettings;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
@ -65,65 +61,31 @@ public class SpongeGMChangeListener {
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.errorLogger = errorLogger;
EVENT_CONSUMERS.add(this::onMixin);
}
public static class Event {
private final Player player;
private final GameType gameType;
public Event(Player player, GameType gameType) {
this.player = player;
this.gameType = gameType;
}
}
private void onMixin(Event event) {
ServerPlayer serverPlayer = Sponge.game().server()
.player(event.player.getUUID())
.orElse(null);
if (serverPlayer == null) {
// uh oh
errorLogger.error(
new RuntimeException("GameMode changed for player but no ServerPlayer was found"),
ErrorContext.builder()
.related(event.player, event.player.getGameProfile().getName())
.whatToDo("Report this, the gamemode change mixin might be broken")
.build()
);
@Listener(order = Order.POST)
public void onGMChange(ChangeDataHolderEvent.ValueChange event) {
ServerPlayer player = event.targetHolder() instanceof ServerPlayer serverPlayer ? serverPlayer : null;
if (player == null) {
return;
}
GameMode gameMode = GameModes.registry().value(ResourceKey.sponge(event.gameType.getName()));
actOnGMChangeEvent(serverPlayer, gameMode);
DataTransactionResult result = event.endResult();
Optional<Value.Immutable<GameMode>> gameModeValue = result.successfulValue(Keys.GAME_MODE);
if (gameModeValue.isEmpty()) {
return;
}
GameMode newMode = gameModeValue.get().get();
actOnGMChangeEvent(player, newMode);
}
// This listener can replace the mixin if this pr is merged:
// https://github.com/SpongePowered/Sponge/pull/3563
// @Listener(order = Order.POST)
// public void onGMChange(ChangeDataHolderEvent.ValueChange event) {
// ServerPlayer player = event.targetHolder() instanceof ServerPlayer ? (ServerPlayer) event.targetHolder() : null;
// if (player == null) {
// return;
// }
//
// DataTransactionResult result = event.endResult();
// Optional<Value.Immutable<GameMode>> gameModeValue = result.successfulValue(Keys.GAME_MODE);
// if (gameModeValue.isEmpty()) {
// return;
// }
//
// GameMode newMode = gameModeValue.get().get();
// actOnGMChangeEvent(player, newMode);
// }
private void actOnGMChangeEvent(ServerPlayer player, GameMode gameMode) {
UUID uuid = player.uniqueId();
long time = System.currentTimeMillis();
String gameModeText = gameMode.key(RegistryTypes.GAME_MODE).value().toUpperCase();
String worldName = Sponge.game().server().worldManager().worldDirectory(player.world().key())
String worldName = Optional.ofNullable(Sponge.game().server().worldManager().worldDirectory(player.world().key()))
.map(path -> path.getFileName().toString()).orElse("Unknown");
dbSystem.getDatabase().executeTransaction(new StoreWorldNameTransaction(serverInfo.getServerUUID(), worldName));

@ -76,7 +76,7 @@ public class SpongeWorldChangeListener {
UUID uuid = player.uniqueId();
String worldName = Sponge.game().server().worldManager().worldDirectory(event.destinationWorld().key())
String worldName = Optional.ofNullable(Sponge.game().server().worldManager().worldDirectory(event.destinationWorld().key()))
.map(path -> path.getFileName().toString()).orElse("Unknown");
String gameMode = getGameMode(player);

@ -1,36 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.mixin;
import com.djrapitops.plan.gathering.listeners.sponge.SpongeGMChangeListener;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayer.class)
public class GameModeChangeMixin {
@Inject(method = "setGameMode", at = @At("HEAD"))
private void onGameModeChange(GameType gameType, CallbackInfo ci) {
SpongeGMChangeListener.Event event = new SpongeGMChangeListener.Event((Player) (Object) this, gameType);
SpongeGMChangeListener.EVENT_CONSUMERS.forEach(consumer -> consumer.accept(event));
}
}

@ -39,6 +39,8 @@ import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
import org.spongepowered.api.network.ServerConnectionState;
import org.spongepowered.api.profile.GameProfile;
import javax.inject.Inject;
import java.util.*;
@ -129,13 +131,16 @@ public class SpongePingCounter extends TaskSystem.Task {
playerHistory.put(uuid, new ArrayList<>());
}
public void removePlayer(Player player) {
playerHistory.remove(player.uniqueId());
startRecording.remove(player.uniqueId());
public void removePlayer(UUID uuid) {
playerHistory.remove(uuid);
startRecording.remove(uuid);
}
private int getPing(ServerPlayer player) {
return player.connection().latency();
return player.connection().state()
.filter(state -> state instanceof ServerConnectionState.Game)
.map(state -> (ServerConnectionState.Game) state)
.map(ServerConnectionState.Game::latency).orElse(0);
}
@Listener
@ -150,7 +155,7 @@ public class SpongePingCounter extends TaskSystem.Task {
@Listener
public void onPlayerQuit(ServerSideConnectionEvent.Disconnect quitEvent) {
removePlayer(quitEvent.player());
quitEvent.profile().map(GameProfile::uuid).ifPresent(this::removePlayer);
}
public void clear() {

@ -1,8 +0,0 @@
{
"required": true,
"package": "com.djrapitops.plan.gathering.mixin",
"minVersion": "0.7.11",
"mixins": [
"GameModeChangeMixin"
]
}