Minigame support

This commit is contained in:
NotAlexNoyle committed 2026-08-05 12:17:19 -04:00
1 parent 5fbe850a14
commit b7da0a1628
2 files changed
+138 -1

No files matched your search

@@ -66,7 +66,14 @@ public final class LoginMigrationService {
boolean normalizeGamemode = managedWorld && !mayKeepGamemode
&& plugin.getConfig().getBoolean("login-safety.normalize-non-staff-gamemode", true);
// A sanctioned spectator is working, not an abandoned autopsy state.
boolean autopsyMigration = managedWorld && !staff && !mayKeepGamemode && originalGamemode == GameMode.SPECTATOR
// A minigame puts its own players into spectator legitimately, so a disconnect
// from an arena looks exactly
// like the abandoned season 1 state this migration exists to rescue. Migrating
// one of those would burn the
// player's one-shot flag and record the arena as their /spawnback location.
boolean minigameArena = MinigameArenas.contains(player);
boolean autopsyMigration = managedWorld && !staff && !mayKeepGamemode && !minigameArena
&& originalGamemode == GameMode.SPECTATOR
&& plugin.getConfig().getBoolean("login-safety.autopsy-migration.enabled", true)
&& !migrationStore.hasMigrated(player.getUniqueId());
boolean vulnerableAfterLogin = normalizeGamemode || originalGamemode == GameMode.SURVIVAL
@@ -0,0 +1,130 @@
package spawnog.login;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Locale;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
// Reports whether a player is inside a minigame arena.
//
// Autopsy migration exists for OG:SMP season 1 players who were left in spectator mode on the SMP. A minigame puts
// its own players into spectator legitimately -- Spleef does it to everyone it eliminates, and Splegg runs whole
// matches in worlds it owns -- so without this check a routine minigame disconnect looks identical to the state the
// migration is meant to rescue. Migrating one of those players burns their one-shot migration flag and records the
// arena as their /spawnback location.
//
// Every lookup is reflective and optional: no minigame is a dependency, and an absent one simply answers "no".
public final class MinigameArenas {
private static final String SPLEEF_PLUGIN = "Spleef-OG";
private static final String SPLEEF_API = "net.trueog.spleefog.api.SpleefAPI";
private static final String SPLEGG_PLUGIN = "Splegg-OG";
private static final String DUELS_PLUGIN = "Duels-OG";
private MinigameArenas() {
}
public static boolean contains(Player player) {
return player != null && (inSpleef(player) || inSplegg(player) || inDuel(player));
}
private static boolean inSpleef(Player player) {
Plugin plugin = enabled(SPLEEF_PLUGIN);
if (plugin == null)
return false;
try {
Class<?> api = Class.forName(SPLEEF_API, true, plugin.getClass().getClassLoader());
if (Boolean.TRUE.equals(api.getMethod("isInSpleef", Player.class).invoke(null, player)))
return true;
// Survives a crash, where the live session is gone but the saved pre-match
// state is not. That is
// exactly the login on which a minigame casualty would otherwise be mistaken
// for an SMP one.
Method pending = api.getMethod("hasPendingRecovery", java.util.UUID.class);
return Boolean.TRUE.equals(pending.invoke(null, player.getUniqueId()));
} catch (ReflectiveOperationException | RuntimeException ex) {
return false;
}
}
// Splegg owns the worlds its games run in, so the player's world answers this
// even after the game has ended.
private static boolean inSplegg(Player player) {
Plugin plugin = enabled(SPLEGG_PLUGIN);
if (plugin == null)
return false;
String world = player.getWorld().getName();
try {
Object configured = plugin.getClass().getMethod("getInGameWorlds").invoke(plugin);
if (configured instanceof List<?> worlds) {
for (Object name : worlds) {
if (name != null && world.equalsIgnoreCase(name.toString()))
return true;
}
}
Object prefix = plugin.getClass().getMethod("getGameWorldPrefix").invoke(plugin);
// Per-game worlds are named <prefix><gameId>-<map>.
return prefix != null && !prefix.toString().isEmpty()
&& world.toLowerCase(Locale.ROOT).startsWith(prefix.toString().toLowerCase(Locale.ROOT));
} catch (ReflectiveOperationException | RuntimeException ex) {
return false;
}
}
private static boolean inDuel(Player player) {
Plugin plugin = enabled(DUELS_PLUGIN);
if (plugin == null)
return false;
try {
Object arenaManager = plugin.getClass().getMethod("getArenaManager").invoke(plugin);
if (arenaManager == null)
return false;
Method query = arenaManager.getClass().getMethod("isInMatch", Player.class);
query.setAccessible(true);
return Boolean.TRUE.equals(query.invoke(arenaManager, player));
} catch (ReflectiveOperationException | RuntimeException ex) {
return false;
}
}
private static Plugin enabled(String name) {
Plugin plugin = Bukkit.getPluginManager().getPlugin(name);
return plugin != null && plugin.isEnabled() ? plugin : null;
}
}