Convert to Gradle plugin, add support for modern Paper with reflection and add config.yml option to block bed usage.

This commit is contained in:
天クマ 2026-05-24 18:54:05 -03:00
commit 690fc1a4dc
39 changed files with 1062 additions and 105 deletions

View file

@ -1,51 +0,0 @@
package gd.rf.adrianvictor.realtime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.plugin.java.JavaPlugin;
import gd.rf.adrianvictor.lib.Log;
import gd.rf.adrianvictor.lib.ConfigurationEx;
public class Main extends JavaPlugin {
Log log;
ConfigurationEx config;
@Override
public void onDisable() {
log.info("Disabling!");
}
@Override
public void onEnable() {
log = new Log(this);
log.info("Starting!");
config = new ConfigurationEx(this, "config.yml", log);
config.loadConfig();
int ticksBetweenUpdate = config.getInt("ticksBetweenUpdate", 20);
List<String> worlds = config.getStringList("worlds", null);
Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
for (String worldName : worlds) {
World world = Bukkit.getServer().getWorld(worldName);
if (world == null) {
log.severe("World " + worldName + " specified in the config was not found, removing it.");
worlds.remove(worldName);
return;
}
ZonedDateTime time = ZonedDateTime.now(ZoneId.of(config.getString("timezone", ZoneId.systemDefault().toString())));
int hour = time.getHour();
int minute = time.getMinute();
long minecraftTime = (hour * 1000 + minute * 100 / 6 - 6000) % 24000;
if (minecraftTime < 0) {
minecraftTime = minecraftTime * 2;
}
world.setTime(minecraftTime);
}
}
}, 0L, ticksBetweenUpdate);
}
}

View file

@ -0,0 +1,12 @@
package org.adrianvictor.realtime.impl.legacy;
import org.adrianvictor.realtime.Main;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
public class Glimmer implements org.adrianvictor.realtime.reflection.Glimmer {
@Override
public void onStart() {
Bukkit.getServer().getPluginManager().registerEvent(Event.Type.PLAYER_BED_ENTER, new PlayerListener(), Event.Priority.Highest, Main.getInstance());
}
}

View file

@ -0,0 +1,11 @@
package org.adrianvictor.realtime.impl.legacy;
import org.adrianvictor.realtime.Events;
import org.bukkit.event.player.PlayerBedEnterEvent;
public class PlayerListener extends org.bukkit.event.player.PlayerListener {
@Override
public void onPlayerBedEnter(PlayerBedEnterEvent event) {
Events.onBedEnter(event);
}
}

View file

@ -0,0 +1,10 @@
package org.adrianvictor.realtime;
import org.bukkit.event.player.PlayerBedEnterEvent;
public class Events {
public static void onBedEnter(PlayerBedEnterEvent event) {
boolean isCancelled = Main.getInstance().getConfig().getBoolean("preventSleep", true);
event.setCancelled(isCancelled);
}
}

View file

@ -0,0 +1,51 @@
package org.adrianvictor.realtime;
import java.io.File;
import java.io.IOException;
import java.time.ZoneId;
import java.util.List;
import org.adrianvictor.lib.configuration.Configuration;
import org.adrianvictor.lib.configuration.exception.InvalidConfigurationException;
import org.adrianvictor.lib.configuration.provider.ConfigurationProvider;
import org.adrianvictor.realtime.reflection.VersionMatcher;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class Main extends JavaPlugin {
ConfigurationProvider config;
static JavaPlugin plugin;
@Override
public void onEnable() {
saveResource("config.yml", false);
plugin = this;
config = Configuration.create(org.adrianvictor.lib.Main.getVersionedServiceFactory());
try {
config.load(new File(getDataFolder(), "config.yml"));
} catch (IOException | InvalidConfigurationException e) {
throw new RuntimeException(e);
}
new VersionMatcher().loadGlim().onStart();
int ticksBetweenUpdate = config.getInt("ticksBetweenUpdate", 20);
List<String> worlds = config.getStringList("worlds", null);
ZoneId zone = ZoneId.of(config.getString("timezone", ZoneId.systemDefault().toString()));
Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(
this,
new Runnable(worlds, zone),
0L,
ticksBetweenUpdate
);
}
public static JavaPlugin getInstance() {
return plugin;
}
public ConfigurationProvider getConfigProvider() {
return config;
}
}

View file

@ -0,0 +1,38 @@
package org.adrianvictor.realtime;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
public class Runnable implements java.lang.Runnable {
List<String> worlds;
ZoneId zone;
public Runnable(List<String> worlds, ZoneId zone) {
this.worlds = worlds;
this.zone = zone;
}
@Override
public void run() {
for (String worldName : worlds) {
World world = Bukkit.getServer().getWorld(worldName);
if (world == null) {
// log.severe("World " + worldName + " specified in the config was not found, removing it.");
worlds.remove(worldName);
return;
}
ZonedDateTime time = ZonedDateTime.now(zone);
int hour = time.getHour();
int minute = time.getMinute();
long minecraftTime = (hour * 1000 + minute * 100 / 6 - 6000) % 24000;
if (minecraftTime < 0) {
minecraftTime = minecraftTime * 2;
}
world.setTime(minecraftTime);
}
}
}

View file

@ -0,0 +1,5 @@
package org.adrianvictor.realtime.reflection;
public interface Glimmer {
void onStart();
}

View file

@ -0,0 +1,202 @@
package org.adrianvictor.realtime.reflection;
import org.bukkit.Bukkit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class VersionMatcher {
public VersionMatcher() {}
private record Entry(String pattern, String classSuffix) {}
public String getVersion(String type, String serverVersion) {
if (type == null || serverVersion == null) {
return "";
}
Map<String, List<Entry>> map = populateMap();
List<Entry> entries = map.get(type.toLowerCase());
if (entries == null || entries.isEmpty()) {
return "";
}
for (Entry e : entries) {
if (safeMatches(e.pattern, serverVersion)) {
return e.pattern + "|" + e.classSuffix;
}
}
List<Entry> sorted = new ArrayList<>(entries);
Collections.sort(sorted, (a, b) -> compareVersions(a.pattern, b.pattern));
Entry oldest = sorted.get(0);
Entry newest = sorted.get(sorted.size() - 1);
int cmpOldest = compareVersions(serverVersion, oldest.pattern);
int cmpNewest = compareVersions(serverVersion, newest.pattern);
if (cmpOldest < 0) {
return oldest.pattern + "|" + oldest.classSuffix;
} else if (cmpNewest > 0) {
return newest.pattern + "|" + newest.classSuffix;
}
// should not happen because we already tried all patterns
return newest.pattern + "|" + newest.classSuffix;
}
private Map<String, List<Entry>> populateMap() {
Map<String, List<Entry>> map = new HashMap<>();
// RELEASE patterns, newest first (order does not matter for matching)
map.put("release", List.of(
new Entry("^1\\.21\\..*$", "r1_21")
));
// BETA patterns
map.put("beta", List.of(
new Entry("^1\\.7\\.3$", "b1_7_3")
));
return map;
}
private int compareVersions(String v1, String v2) {
String clean1 = v1.replaceAll("[^0-9.]", "");
String clean2 = v2.replaceAll("[^0-9.]", "");
String[] a1 = clean1.split("\\.");
String[] a2 = clean2.split("\\.");
int len = Math.max(a1.length, a2.length);
for (int i = 0; i < len; i++) {
int n1 = i < a1.length ? parseInt(a1[i]) : 0;
int n2 = i < a2.length ? parseInt(a2[i]) : 0;
if (n1 != n2) {
return n1 - n2;
}
}
return 0;
}
private int parseInt(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return 0;
}
}
public Glimmer loadGlim() {
String rawVersion = null;
try {
if (Bukkit.getServer() != null) {
rawVersion = Bukkit.getMinecraftVersion();
}
} catch (NoSuchMethodError ignored) {}
if (rawVersion == null || rawVersion.isEmpty()) {
try {
if (Bukkit.getServer() != null) {
String v = Bukkit.getVersion(); // e.g. "git-Bukkit-0.0.0-1060-... (MC: 1.7.3)"
int start = v.lastIndexOf("MC: ");
if (start != -1) {
rawVersion = v.substring(start + 4, v.length() - 1);
}
}
} catch (Exception ignored) {}
if (rawVersion == null || rawVersion.isEmpty()) {
rawVersion = "unknown";
}
}
String matchInfo = getVersion("release", rawVersion);
if (matchInfo.isEmpty()) {
matchInfo = getVersion("beta", rawVersion);
}
if (matchInfo.isEmpty()) {
// Fallback to b1.7.3 if everything fails, or throw error?
Glimmer fallback = tryInstantiate("org.adrianvictor.realtime.impl.b1_7_3");
if (fallback != null) return fallback;
throw new IllegalStateException("No suitable implementation found for version " + rawVersion);
}
String[] partsInfo = matchInfo.split("\\|");
String classSuffix = partsInfo[1];
Glimmer glimmer = tryInstantiate("org.adrianvictor.realtime.impl." + classSuffix);
if (glimmer != null) return glimmer;
// Backward search for older implementations
String[] versionParts = rawVersion.split("\\.");
int major = parseInt(versionParts[0]);
int minor = versionParts.length > 1 ? parseInt(versionParts[1]) : 0;
int patch = versionParts.length > 2 ? parseInt(versionParts[2]) : 0;
while (major >= 0) {
while (minor >= 0) {
while (patch >= 0) {
String className = String.format("org.adrianvictor.realtime.impl.r%d_%d_%d", major, minor, patch);
glimmer = tryInstantiate(className);
if (glimmer != null) return glimmer;
className = String.format("org.adrianvictor.realtime.impl.r%d_%d", major, minor);
glimmer = tryInstantiate(className);
if (glimmer != null) return glimmer;
patch--;
}
minor--;
patch = 20; // Search up to .20 patch of previous minor
}
major--;
minor = 30; // Search up to .30 minor of previous major
}
if (isNewEventSystem()) {
Glimmer modern = tryInstantiate("org.adrianvictor.realtime.impl.modern.Glimmer");
if (modern != null) return modern;
} else {
Glimmer legacy = tryInstantiate("org.adrianvictor.realtime.impl.legacy.Glimmer");
if (legacy != null) return legacy;
}
throw new IllegalStateException("No suitable implementation found for version " + rawVersion);
}
private boolean isNewEventSystem() {
try {
Class.forName("org.bukkit.event.EventHandler");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private Glimmer tryInstantiate(String className) {
try {
Class<?> clazz = Class.forName(className, true, getClass().getClassLoader());
if (!Glimmer.class.isAssignableFrom(clazz)) {
return null;
}
return (Glimmer) clazz.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException ignored) {
return null;
}
}
public static boolean safeMatches(String expression, String against) {
String cleanPattern = expression.trim();
Pattern pattern = Pattern.compile(cleanPattern, Pattern.CASE_INSENSITIVE);
return pattern.matcher(against).matches();
}
}

View file

@ -0,0 +1,5 @@
ticksBetweenUpdate: 0 # 20 ticks is 1 second
timezone: "America/Sao_Paulo" # comment this line to use system's default timezone
preventSleep: true # Players will sleep and time will sync afterwards, so it's better to disable unless you have a reason to
worlds:
- "world"

View file

@ -0,0 +1,8 @@
author: tenkuma
database: false
main: org.adrianvictor.realtime.Main
name: TimeKeeper
depend: [tenkumaLib]
url: https://adrianvictor.rf.gd
version: '1.0'
api-version: "1.13"

View file

@ -0,0 +1,13 @@
package org.adrianvictor.realtime.impl.modern;
import org.adrianvictor.realtime.Events;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerBedEnterEvent;
public class EventListener implements Listener {
@EventHandler
public void onPlayerBedEnter(PlayerBedEnterEvent event) {
Events.onBedEnter(event);
}
}

View file

@ -0,0 +1,11 @@
package org.adrianvictor.realtime.impl.modern;
import org.adrianvictor.realtime.Main;
import org.bukkit.Bukkit;
public class Glimmer implements org.adrianvictor.realtime.reflection.Glimmer {
@Override
public void onStart() {
Bukkit.getServer().getPluginManager().registerEvents(new EventListener(), Main.getInstance());
}
}