Make beta implementation use Project Poseidon from Maven, default to 1 tick refresh rate to avoid lag, fix tenkumaLib ConfigurationProvider usage in Main, switch from reflection to hybrid abstraction/reflection.

This commit is contained in:
天クマ 2026-05-26 20:14:55 -03:00
commit 029b37db7e
9 changed files with 100 additions and 242 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ out/
build
run/
runBeta/

View file

@ -21,5 +21,15 @@
<option name="name" value="maven" />
<option name="url" value="https://repo.papermc.io/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://repository.johnymuffin.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://repository.johnymuffin.com/repository/maven-releases/" />
</remote-repository>
</component>
</project>

View file

@ -9,6 +9,7 @@ version = System.getenv("TKEEPER_VERSION_NAME") ?: "unknown"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repository.johnymuffin.com/repository/maven-releases/")
flatDir {
dirs("libs")
}
@ -66,7 +67,7 @@ mcVersions.forEach { ver ->
dependencies {
add("compileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("modernCompileOnly", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
add("legacyCompileOnly", files("libs/tenkumaLib/libs/craftbukkit-1060.jar"))
add("legacyImplementation", "com.legacyminecraft.poseidon:poseidon-craftbukkit:1.1.12-260503-0121-a9af58a")
compileOnly(project(":tenkumaLib"))
@ -76,7 +77,6 @@ dependencies {
testImplementation("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
testImplementation("com.github.seeseemelk:MockBukkit-v1.21:3.102.0")
// Allow tests to see the versioned implementations
mcVersions.forEach { ver ->
testImplementation(sourceSets[ver].output)
}
@ -111,26 +111,26 @@ tasks.register("buildAll") {
dependsOn(tasks.withType<Jar>())
}
tasks.register<Jar>("bundleAll") {
dependsOn(configurations.runtimeClasspath)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(sourceSets["main"].output)
mcVersions.forEach { ver ->
from(sourceSets[ver].output)
}
from({
configurations.runtimeClasspath.get()
.filter { it.name.endsWith("jar") }
.map { zipTree(it) }
})
archiveClassifier.set("all-implementations")
archiveVersion.set(project.version.toString())
}
//tasks.register<Jar>("bundleAll") {
// dependsOn(configurations.runtimeClasspath)
//
// duplicatesStrategy = DuplicatesStrategy.EXCLUDE
//
// from(sourceSets["main"].output)
//
// mcVersions.forEach { ver ->
// from(sourceSets[ver].output)
// }
//
// from({
// configurations.runtimeClasspath.get()
// .filter { it.name.endsWith("jar") }
// .map { zipTree(it) }
// })
//
// archiveClassifier.set("all-implementations")
// archiveVersion.set(project.version.toString())
//}
/* ----------------------------------------- */
/* JAVA SETTINGS */
@ -151,14 +151,34 @@ tasks.withType<JavaCompile> {
tasks.runServer {
minecraftVersion("1.21")
val bundleAll = tasks.named<Jar>("bundleAll")
val modern = tasks.named<Jar>("jarModern")
val tLibBundleAll = project(":tenkumaLib").tasks.named<Jar>("bundleAll")
dependsOn(bundleAll)
dependsOn(modern)
dependsOn(tLibBundleAll)
pluginJars(
bundleAll.flatMap { it.archiveFile },
modern.flatMap { it.archiveFile },
tLibBundleAll.flatMap { it.archiveFile }
)
}
tasks.register<Copy>("copyLegacyPlugin") {
from(project(":tenkumaLib").tasks.named<Jar>("bundleAll"))
from(tasks.named<Jar>("jarLegacy"))
into("runBeta/plugins")
}
tasks.register<JavaExec>("runBetaServer") {
group = "verification"
description = "Runs the beta server with legacy TimeKeeper."
dependsOn("copyLegacyPlugin")
jvmArgs = listOf("-Djline.terminal=jline.UnsupportedTerminal", "-Dfile.encoding=UTF-8", "-Djava.awt.headless=true")
classpath = files("runBeta/poseidon-craftbukkit.jar")
mainClass.set("org.bukkit.craftbukkit.Main")
workingDir = file("runBeta")
standardInput = file("/dev/null").inputStream()
}

View file

@ -4,7 +4,7 @@ import org.bukkit.event.player.PlayerBedEnterEvent;
public class Events {
public static void onBedEnter(PlayerBedEnterEvent event) {
boolean isCancelled = Main.getInstance().getConfig().getBoolean("preventSleep", true);
boolean isCancelled = Main.getInstance().getConfigProvider().getBoolean("preventSleep", true);
event.setCancelled(isCancelled);
}
}

View file

@ -0,0 +1,19 @@
package org.adrianvictor.realtime;
import org.adrianvictor.realtime.reflection.Glimmer;
public class GlimmerFactory {
public static Glimmer create() {
try {
return (Glimmer) Class.forName("org.adrianvictor.realtime.impl.modern.Glimmer")
.getDeclaredConstructor().newInstance();
} catch (Exception e) {
try {
return (Glimmer) Class.forName("org.adrianvictor.realtime.impl.legacy.Glimmer")
.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
throw new RuntimeException("Failed to instantiate Glimmer implementation", ex);
}
}
}
}

View file

@ -1,19 +1,20 @@
package org.adrianvictor.realtime;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneId;
import java.util.List;
import org.adrianvictor.lib.configuration.exception.InvalidConfigurationException;
import org.adrianvictor.lib.configuration.provider.ConfigurationProvider;
import org.adrianvictor.lib.file.provider.FileProvider;
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;
static Main plugin;
@Override
public void onEnable() {
@ -22,19 +23,22 @@ public class Main extends JavaPlugin {
} catch (IOException e) {
throw new RuntimeException(e);
}
plugin = this;
config = ConfigurationProvider.get();
try {
config.load(new File(getDataFolder(), "config.yml"));
File configFile = new File(getDataFolder(), "config.yml");
String configString = Files.readString(Path.of(configFile.getPath()));
config.load(configString);
} catch (IOException | InvalidConfigurationException e) {
throw new RuntimeException(e);
}
new VersionMatcher().loadGlim().onStart();
GlimmerFactory.create().onStart();
int ticksBetweenUpdate = config.getInt("ticksBetweenUpdate", 20);
List<String> worlds = config.getStringList("worlds", null);
List<String> worlds = config.getStringList("worlds", List.of("world"));
ZoneId zone = ZoneId.of(config.getString("timezone", ZoneId.systemDefault().toString()));
Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(
@ -45,7 +49,7 @@ public class Main extends JavaPlugin {
);
}
public static JavaPlugin getInstance() {
public static Main getInstance() {
return plugin;
}

View file

@ -1,5 +1,6 @@
package org.adrianvictor.realtime;
import org.adrianvictor.lib.logging.provider.LoggerProvider;
import org.bukkit.Bukkit;
import org.bukkit.World;
@ -21,18 +22,23 @@ public class Runnable implements java.lang.Runnable {
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.");
LoggerProvider.get().info("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);
// Minecraft time: 0 is 6:00 AM. 18000 is Midnight (0:00).
// (hour * 1000) + (minute * 1000 / 60) gives time in ticks from 0:00.
// Subtract 6000 to align 6:00 AM with 0 ticks.
long ticksSinceMidnight = (hour * 1000L) + (minute * 1000L / 60L);
long minecraftTime = (ticksSinceMidnight - 6000L + 24000L) % 24000L;
long currentFullTime = world.getFullTime();
long day = currentFullTime / 24000L;
world.setFullTime(day * 24000L + minecraftTime);
}
}
}

View file

@ -1,202 +0,0 @@
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

@ -1,4 +1,4 @@
ticksBetweenUpdate: 0 # 20 ticks is 1 second
ticksBetweenUpdate: 1 # 20 ticks is 1 second, 0 lags the server
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: