feat: add global & server leveling, quests, achievements, guilds land-claiming, social & toggleable configs
This commit is contained in:
1 parent
4b79f69c20
commit
b4cef85b1f
40 files changed
+10261
-27
No files matched your search
@@ -0,0 +1,9 @@
|
||||
package net.scopenet.integration;
|
||||
|
||||
public record ChunkCheckResult(
|
||||
boolean claimed,
|
||||
boolean allowed,
|
||||
String guildName,
|
||||
String guildTag
|
||||
) {
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.function.*;
|
||||
public final class Integration implements AutoCloseable {
|
||||
public static final String UNAVAILABLE = "SCOPENET sign-in is unavailable. Please try again shortly.";
|
||||
public final Activity activity = new Activity();
|
||||
private final Settings settings;
|
||||
private final PanelClient client;
|
||||
private final JsonObject hello;
|
||||
private final Consumer<String> log;
|
||||
@@ -27,6 +28,7 @@ public final class Integration implements AutoCloseable {
|
||||
public Integration(Settings settings, String software, String mcVersion, String version,
|
||||
boolean onlineMode, int maxPlayers, Consumer<String> log, BiConsumer<UUID, String> kick) {
|
||||
if (!onlineMode) throw new IllegalArgumentException("SCOPENET requires online-mode=true and authlib-injector");
|
||||
this.settings = settings;
|
||||
this.client = new PanelClient(settings);
|
||||
this.log = log;
|
||||
this.kick = kick;
|
||||
@@ -72,7 +74,10 @@ public final class Integration implements AutoCloseable {
|
||||
try { sync(tps); }
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptedException) Thread.currentThread().interrupt();
|
||||
log.accept("SCOPENET sync failed; retained batch will be retried (" + e.getClass().getSimpleName() + ")");
|
||||
String msg = e.getMessage() != null && !e.getMessage().isBlank()
|
||||
? e.getClass().getSimpleName() + ": " + e.getMessage()
|
||||
: e.getClass().getSimpleName();
|
||||
log.accept("SCOPENET sync failed to connect to panel at " + settings.panel() + "; retained batch will be retried (" + msg + ")");
|
||||
} finally { syncing.set(false); }
|
||||
});
|
||||
}
|
||||
@@ -83,7 +88,19 @@ public final class Integration implements AutoCloseable {
|
||||
if (!response.has("server_id")) throw new IllegalStateException("Invalid hello response");
|
||||
registered = true;
|
||||
}
|
||||
if (pending == null) pending = activity.drain(tps);
|
||||
if (pending == null) {
|
||||
pending = activity.drain(tps);
|
||||
JsonObject features = new JsonObject();
|
||||
features.addProperty("leveling_enabled", settings.levelingEnabled());
|
||||
features.addProperty("global_xp_multiplier", settings.globalXpMultiplier());
|
||||
features.addProperty("server_xp_multiplier", settings.serverXpMultiplier());
|
||||
features.addProperty("quests_enabled", settings.questsEnabled());
|
||||
features.addProperty("achievements_enabled", settings.achievementsEnabled());
|
||||
features.addProperty("guilds_enabled", settings.guildsEnabled());
|
||||
features.addProperty("land_claiming_enabled", settings.landClaimingEnabled());
|
||||
features.addProperty("social_enabled", settings.socialEnabled());
|
||||
pending.add("features", features);
|
||||
}
|
||||
JsonObject response = client.post("sync", pending);
|
||||
if (!response.has("ok") || !response.get("ok").getAsBoolean() || !response.has("kick")
|
||||
|| !response.get("kick").isJsonArray()) throw new IllegalStateException("Invalid sync response");
|
||||
@@ -98,6 +115,14 @@ public final class Integration implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
public Settings settings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public ChunkCheckResult checkChunk(String dimension, int chunkX, int chunkZ, UUID uuid) {
|
||||
return client.checkChunk(dimension, chunkX, chunkZ, uuid);
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
closed = true;
|
||||
loginWorker.shutdownNow();
|
||||
|
||||
@@ -42,4 +42,26 @@ public final class PanelClient {
|
||||
return message != null && message.isJsonPrimitive() && message.getAsJsonPrimitive().isString()
|
||||
? message.getAsString() : "You do not have access to this server.";
|
||||
}
|
||||
|
||||
public ChunkCheckResult checkChunk(String dimension, int chunkX, int chunkZ, java.util.UUID uuid) {
|
||||
if (!settings.guildsEnabled() || !settings.landClaimingEnabled()) {
|
||||
return new ChunkCheckResult(false, true, null, null);
|
||||
}
|
||||
JsonObject req = new JsonObject();
|
||||
req.addProperty("dimension", dimension);
|
||||
req.addProperty("chunk_x", chunkX);
|
||||
req.addProperty("chunk_z", chunkZ);
|
||||
req.addProperty("uuid", uuid.toString());
|
||||
try {
|
||||
JsonObject res = post("guilds/check-chunk", req);
|
||||
boolean claimed = res.has("claimed") && res.get("claimed").getAsBoolean();
|
||||
boolean allowed = !res.has("allowed") || res.get("allowed").getAsBoolean();
|
||||
String guildName = res.has("guild_name") && !res.get("guild_name").isJsonNull() ? res.get("guild_name").getAsString() : null;
|
||||
String guildTag = res.has("guild_tag") && !res.get("guild_tag").isJsonNull() ? res.get("guild_tag").getAsString() : null;
|
||||
return new ChunkCheckResult(claimed, allowed, guildName, guildTag);
|
||||
} catch (Exception e) {
|
||||
return new ChunkCheckResult(false, true, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,19 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
public record Settings(URI panel, String token) {
|
||||
public record Settings(
|
||||
URI panel,
|
||||
String token,
|
||||
boolean levelingEnabled,
|
||||
double globalXpMultiplier,
|
||||
double serverXpMultiplier,
|
||||
boolean questsEnabled,
|
||||
boolean achievementsEnabled,
|
||||
boolean guildsEnabled,
|
||||
boolean landClaimingEnabled,
|
||||
boolean socialEnabled,
|
||||
boolean chatPrefixesEnabled
|
||||
) {
|
||||
public Settings {
|
||||
if (panel == null || panel.getHost() == null || panel.getRawUserInfo() != null
|
||||
|| panel.getRawQuery() != null || panel.getRawFragment() != null
|
||||
@@ -21,19 +33,79 @@ public record Settings(URI panel, String token) {
|
||||
}
|
||||
}
|
||||
|
||||
public Settings(URI panel, String token) {
|
||||
this(panel, token, true, 1.0, 1.5, true, true, true, true, true, true);
|
||||
}
|
||||
|
||||
public static Settings of(String panel, String token) {
|
||||
return new Settings(URI.create(panel.trim().replaceAll("/+$", "")), token);
|
||||
return of(panel, token, true, 1.0, 1.5, true, true, true, true, true, true);
|
||||
}
|
||||
|
||||
public static Settings of(
|
||||
String panel,
|
||||
String token,
|
||||
boolean levelingEnabled,
|
||||
double globalXpMultiplier,
|
||||
double serverXpMultiplier,
|
||||
boolean questsEnabled,
|
||||
boolean achievementsEnabled,
|
||||
boolean guildsEnabled,
|
||||
boolean landClaimingEnabled,
|
||||
boolean socialEnabled,
|
||||
boolean chatPrefixesEnabled
|
||||
) {
|
||||
return new Settings(
|
||||
URI.create(panel.trim().replaceAll("/+$", "")),
|
||||
token,
|
||||
levelingEnabled,
|
||||
globalXpMultiplier,
|
||||
serverXpMultiplier,
|
||||
questsEnabled,
|
||||
achievementsEnabled,
|
||||
guildsEnabled,
|
||||
landClaimingEnabled,
|
||||
socialEnabled,
|
||||
chatPrefixesEnabled
|
||||
);
|
||||
}
|
||||
|
||||
public static Settings load(Path path) throws IOException {
|
||||
if (!Files.exists(path)) {
|
||||
Files.createDirectories(path.toAbsolutePath().getParent());
|
||||
Files.writeString(path, "# SCOPENET server integration\npanel-url=https://panel.example.com\ntoken=\n", StandardCharsets.UTF_8);
|
||||
String defaultProps = """
|
||||
# SCOPENET server integration
|
||||
panel-url=https://panel.example.com
|
||||
token=
|
||||
|
||||
# Feature Toggles (set false to disable systems on this server)
|
||||
leveling.enabled=true
|
||||
leveling.global_xp_multiplier=1.0
|
||||
leveling.server_xp_multiplier=1.5
|
||||
quests.enabled=true
|
||||
achievements.enabled=true
|
||||
guilds.enabled=true
|
||||
guilds.land_claiming=true
|
||||
social.enabled=true
|
||||
social.chat_prefixes=true
|
||||
""";
|
||||
Files.writeString(path, defaultProps, StandardCharsets.UTF_8);
|
||||
}
|
||||
Properties properties = new Properties();
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
properties.load(reader);
|
||||
}
|
||||
return of(properties.getProperty("panel-url", ""), properties.getProperty("token", ""));
|
||||
return of(
|
||||
properties.getProperty("panel-url", ""),
|
||||
properties.getProperty("token", ""),
|
||||
Boolean.parseBoolean(properties.getProperty("leveling.enabled", "true")),
|
||||
Double.parseDouble(properties.getProperty("leveling.global_xp_multiplier", "1.0")),
|
||||
Double.parseDouble(properties.getProperty("leveling.server_xp_multiplier", "1.5")),
|
||||
Boolean.parseBoolean(properties.getProperty("quests.enabled", "true")),
|
||||
Boolean.parseBoolean(properties.getProperty("achievements.enabled", "true")),
|
||||
Boolean.parseBoolean(properties.getProperty("guilds.enabled", "true")),
|
||||
Boolean.parseBoolean(properties.getProperty("guilds.land_claiming", "true")),
|
||||
Boolean.parseBoolean(properties.getProperty("social.enabled", "true")),
|
||||
Boolean.parseBoolean(properties.getProperty("social.chat_prefixes", "true"))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -63,13 +63,16 @@ public final class Bridge {
|
||||
public static void stat(ServerPlayer player, String key, int amount) {
|
||||
Integration current = integration;
|
||||
if (current == null) return;
|
||||
if (!current.settings().levelingEnabled() && (key.equals("blocks_broken") || key.equals("blocks_placed") || key.equals("player_kills") || key.equals("mob_kills"))) {
|
||||
return;
|
||||
}
|
||||
current.activity.add(player.getUUID(), player.getName().getString(), key, amount);
|
||||
if (key.equals("deaths")) current.activity.event(player.getUUID(), player.getName().getString(), "death", null);
|
||||
}
|
||||
|
||||
public static void action(ServerPlayer player, String action, int amount) {
|
||||
Integration current = integration;
|
||||
if (current != null) current.activity.action(player.getUUID(), player.getName().getString(), action, amount);
|
||||
if (current != null && current.settings().levelingEnabled()) current.activity.action(player.getUUID(), player.getName().getString(), action, amount);
|
||||
}
|
||||
|
||||
public static void command(ServerPlayer player, String command) {
|
||||
|
||||
@@ -19,8 +19,22 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
saveDefaultConfig();
|
||||
try {
|
||||
Settings settings = Settings.of(
|
||||
getConfig().getString("panel-url", ""),
|
||||
getConfig().getString("token", ""),
|
||||
getConfig().getBoolean("leveling.enabled", true),
|
||||
getConfig().getDouble("leveling.global-xp-multiplier", 1.0),
|
||||
getConfig().getDouble("leveling.server-xp-multiplier", 1.5),
|
||||
getConfig().getBoolean("quests.enabled", true),
|
||||
getConfig().getBoolean("achievements.enabled", true),
|
||||
getConfig().getBoolean("guilds.enabled", true),
|
||||
getConfig().getBoolean("guilds.land-claiming", true),
|
||||
getConfig().getBoolean("social.enabled", true),
|
||||
getConfig().getBoolean("social.chat-prefixes", true)
|
||||
);
|
||||
|
||||
integration = new Integration(
|
||||
Settings.of(getConfig().getString("panel-url", ""), getConfig().getString("token", "")),
|
||||
settings,
|
||||
getServer().getName(), Bukkit.getBukkitVersion().split("-")[0], getDescription().getVersion(),
|
||||
Bukkit.getOnlineMode(), Bukkit.getMaxPlayers(), getLogger()::warning,
|
||||
(id, message) -> {
|
||||
@@ -33,6 +47,12 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
Bukkit.getScheduler().runTaskTimer(this, integration::tick, 1, 1);
|
||||
// Reloading an auth plugin cannot silently admit existing sessions.
|
||||
for (Player player : Bukkit.getOnlinePlayers()) player.kickPlayer("SCOPENET restarted. Please reconnect.");
|
||||
getLogger().info("SCOPENET integration initialized. Configured modules:");
|
||||
getLogger().info(" - Leveling: " + (settings.levelingEnabled() ? "ENABLED" : "DISABLED"));
|
||||
getLogger().info(" - Quests: " + (settings.questsEnabled() ? "ENABLED" : "DISABLED"));
|
||||
getLogger().info(" - Achievements: " + (settings.achievementsEnabled() ? "ENABLED" : "DISABLED"));
|
||||
getLogger().info(" - Guilds & Land Claiming: " + (settings.guildsEnabled() && settings.landClaimingEnabled() ? "ENABLED" : "DISABLED"));
|
||||
getLogger().info(" - Social: " + (settings.socialEnabled() ? "ENABLED" : "DISABLED"));
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("SCOPENET is blocking joins: " + e.getMessage());
|
||||
for (Player player : Bukkit.getOnlinePlayers()) player.kickPlayer(Integration.UNAVAILABLE);
|
||||
@@ -68,17 +88,43 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
}
|
||||
|
||||
private void add(Player player, String stat) {
|
||||
if (integration != null) integration.activity.add(player.getUniqueId(), player.getName(), stat, 1);
|
||||
if (integration != null && integration.settings().levelingEnabled()) {
|
||||
integration.activity.add(player.getUniqueId(), player.getName(), stat, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void broken(BlockBreakEvent event) {
|
||||
if (integration != null && integration.settings().guildsEnabled() && integration.settings().landClaimingEnabled()) {
|
||||
Chunk chunk = event.getBlock().getChunk();
|
||||
ChunkCheckResult check = integration.checkChunk(event.getBlock().getWorld().getName(), chunk.getX(), chunk.getZ(), event.getPlayer().getUniqueId());
|
||||
if (check.claimed() && !check.allowed()) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(ChatColor.RED + "This chunk is claimed by [" + check.guildTag() + "] " + check.guildName() + "!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
add(event.getPlayer(), "blocks_broken");
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void placed(BlockPlaceEvent event) {
|
||||
if (integration != null && integration.settings().guildsEnabled() && integration.settings().landClaimingEnabled()) {
|
||||
Chunk chunk = event.getBlock().getChunk();
|
||||
ChunkCheckResult check = integration.checkChunk(event.getBlock().getWorld().getName(), chunk.getX(), chunk.getZ(), event.getPlayer().getUniqueId());
|
||||
if (check.claimed() && !check.allowed()) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(ChatColor.RED + "This chunk is claimed by [" + check.guildTag() + "] " + check.guildName() + "!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
add(event.getPlayer(), "blocks_placed");
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void broken(BlockBreakEvent event) { add(event.getPlayer(), "blocks_broken"); }
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void placed(BlockPlaceEvent event) { add(event.getPlayer(), "blocks_placed"); }
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void chat(AsyncPlayerChatEvent event) { add(event.getPlayer(), "messages"); }
|
||||
public void chat(AsyncPlayerChatEvent event) {
|
||||
add(event.getPlayer(), "messages");
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void command(PlayerCommandPreprocessEvent event) {
|
||||
@@ -87,7 +133,7 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void statistic(PlayerStatisticIncrementEvent event) {
|
||||
if (integration == null) return;
|
||||
if (integration == null || !integration.settings().levelingEnabled()) return;
|
||||
String item = event.getMaterial() != null ? ":" + event.getMaterial().name()
|
||||
: event.getEntityType() != null ? ":" + event.getEntityType().name() : "";
|
||||
integration.activity.action(event.getPlayer().getUniqueId(), event.getPlayer().getName(),
|
||||
@@ -103,6 +149,7 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void advancement(PlayerAdvancementDoneEvent event) {
|
||||
if (integration == null || !integration.settings().achievementsEnabled()) return;
|
||||
audit(event.getPlayer(), "advancement", event.getAdvancement().getKey().toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,44 @@
|
||||
# Create a server under Servers in the panel, then paste its token here.
|
||||
# ==============================================================================
|
||||
# SCOPENET Minecraft Server Configuration
|
||||
# ==============================================================================
|
||||
# Connect to your SCOPENET Panel instance by pasting your server token below.
|
||||
# Create a server under 'Servers' in the web panel to generate a token.
|
||||
|
||||
panel-url: "https://panel.example.com"
|
||||
token: ""
|
||||
|
||||
# ==============================================================================
|
||||
# Feature Toggles
|
||||
# Every system below is fully toggleable. You do not need to force all features
|
||||
# on every server—turn on only what makes sense for your server's gameplay style.
|
||||
# ==============================================================================
|
||||
|
||||
leveling:
|
||||
# Global & Server leveling progression (tracks playtime, kills, mining, etc.)
|
||||
enabled: true
|
||||
# Multiplier for account-wide Global Level XP (1.0 = standard speed)
|
||||
global-xp-multiplier: 1.0
|
||||
# Multiplier for this server's specific Server Level XP (1.5 = 1.5x speed)
|
||||
server-xp-multiplier: 1.5
|
||||
|
||||
quests:
|
||||
# Daily and Weekly quests progression tracking
|
||||
enabled: true
|
||||
|
||||
achievements:
|
||||
# Minecraft-themed achievements and milestone unlock notifications
|
||||
enabled: true
|
||||
|
||||
guilds:
|
||||
# Guilds partying system and /guild commands
|
||||
enabled: true
|
||||
# FTB Chunks-style chunk land-claiming and protection against other guilds
|
||||
land-claiming: true
|
||||
# Allow guild members to damage each other (PvP friendly fire)
|
||||
friendly-fire: false
|
||||
|
||||
social:
|
||||
# Friends status, direct messaging bridge, and server game invites
|
||||
enabled: true
|
||||
# Show player guild tag and level title in chat formatting
|
||||
chat-prefixes: true
|
||||
Reference in new issue
Block a user