9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-25 18:09:17 +00:00

Cooking Tutorial

1. Wet the drys
2. Dry the wets
3. Wet the drys
4. Dry the wets
5. Wet the drys
6. Now dust the wets
This commit is contained in:
Dreeam
2025-03-28 03:11:27 -04:00
parent e97f007991
commit 236010caba
218 changed files with 3186 additions and 276 deletions

View File

@@ -0,0 +1,19 @@
// Gale - JettPack - reduce array allocations
package me.titaniumtown;
public final class ArrayConstants {
private ArrayConstants() {}
public static final Object[] emptyObjectArray = new Object[0];
public static final short[] emptyShortArray = new short[0];
public static final int[] emptyIntArray = new int[0];
public static final int[] zeroSingletonIntArray = new int[]{0};
public static final byte[] emptyByteArray = new byte[0];
public static final String[] emptyStringArray = new String[0];
public static final long[] emptyLongArray = new long[0];
public static final org.bukkit.entity.Entity[] emptyBukkitEntityArray = new org.bukkit.entity.Entity[0];
public static final net.minecraft.world.entity.Entity[] emptyEntityArray = new net.minecraft.world.entity.Entity[0];
//public static final net.minecraft.server.level.ServerLevel[] emptyServerLevelArray = new net.minecraft.server.level.ServerLevel[0];
}

View File

@@ -0,0 +1,209 @@
// Gale - Lithium - faster chunk serialization
package net.caffeinemc.mods.lithium.common.world.chunk;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.IdMap;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.VarInt;
import net.minecraft.world.level.chunk.MissingPaletteEntryException;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PaletteResize;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
/**
* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
*/
public class LithiumHashPalette<T> implements Palette<T> {
private static final int ABSENT_VALUE = -1;
private final IdMap<T> idList;
private final PaletteResize<T> resizeHandler;
private final int indexBits;
private final Reference2IntOpenHashMap<T> table;
private T[] entries;
private int size = 0;
private LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntOpenHashMap<T> table, int size) {
this.idList = idList;
this.resizeHandler = resizeHandler;
this.indexBits = indexBits;
this.entries = entries;
this.table = table;
this.size = size;
}
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, List<T> list) {
this(idList, bits, resizeHandler);
for (T t : list) {
this.addEntry(t);
}
}
@SuppressWarnings("unchecked")
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler) {
this.idList = idList;
this.indexBits = bits;
this.resizeHandler = resizeHandler;
int capacity = 1 << bits;
this.entries = (T[]) new Object[capacity];
this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
this.table.defaultReturnValue(ABSENT_VALUE);
}
@Override
public int idFor(@NotNull T obj) {
int id = this.table.getInt(obj);
if (id == ABSENT_VALUE) {
id = this.computeEntry(obj);
}
return id;
}
@Override
public boolean maybeHas(@NotNull Predicate<T> predicate) {
for (int i = 0; i < this.size; ++i) {
if (predicate.test(this.entries[i])) {
return true;
}
}
return false;
}
private int computeEntry(T obj) {
int id = this.addEntry(obj);
if (id >= 1 << this.indexBits) {
if (this.resizeHandler == null) {
throw new IllegalStateException("Cannot grow");
} else {
id = this.resizeHandler.onResize(this.indexBits + 1, obj);
}
}
return id;
}
private int addEntry(T obj) {
int nextId = this.size;
if (nextId >= this.entries.length) {
this.resize(this.size);
}
this.table.put(obj, nextId);
this.entries[nextId] = obj;
this.size++;
return nextId;
}
private void resize(int neededCapacity) {
this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
}
@Override
public @NotNull T valueFor(int id) {
T[] entries = this.entries;
T entry = null;
if (id >= 0 && id < entries.length) {
entry = entries[id];
}
if (entry != null) {
return entry;
} else {
throw this.missingPaletteEntryCrash(id);
}
}
private ReportedException missingPaletteEntryCrash(int id) {
try {
throw new MissingPaletteEntryException(id);
} catch (MissingPaletteEntryException e) {
CrashReport crashReport = CrashReport.forThrowable(e, "[Lithium] Getting Palette Entry");
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk section");
crashReportCategory.setDetail("IndexBits", this.indexBits);
crashReportCategory.setDetail("Entries", this.entries.length + " Elements: " + Arrays.toString(this.entries));
crashReportCategory.setDetail("Table", this.table.size() + " Elements: " + this.table);
return new ReportedException(crashReport);
}
}
@Override
public void read(FriendlyByteBuf buf) {
this.clear();
int entryCount = buf.readVarInt();
for (int i = 0; i < entryCount; ++i) {
this.addEntry(this.idList.byIdOrThrow(buf.readVarInt()));
}
}
@Override
public void write(FriendlyByteBuf buf) {
int size = this.size;
buf.writeVarInt(size);
for (int i = 0; i < size; ++i) {
buf.writeVarInt(this.idList.getId(this.valueFor(i)));
}
}
@Override
public int getSerializedSize() {
int size = VarInt.getByteSize(this.size);
for (int i = 0; i < this.size; ++i) {
size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
}
return size;
}
@Override
public int getSize() {
return this.size;
}
@Override
public @NotNull Palette<T> copy(@NotNull PaletteResize<T> resizeHandler) {
return new LithiumHashPalette<>(this.idList, resizeHandler, this.indexBits, this.entries.clone(), this.table.clone(), this.size);
}
private void clear() {
Arrays.fill(this.entries, null);
this.table.clear();
this.size = 0;
}
public List<T> getElements() {
T[] copy = Arrays.copyOf(this.entries, this.size);
return Arrays.asList(copy);
}
public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> list) {
return new LithiumHashPalette<>(idList, bits, listener, list);
}
}

View File

@@ -0,0 +1,179 @@
// Gale - Gale commands - /gale command
package org.galemc.gale.command;
import io.papermc.paper.command.CommandUtil;
import it.unimi.dsi.fastutil.Pair;
import net.minecraft.Util;
import org.galemc.gale.command.subcommands.InfoCommand;
import org.galemc.gale.command.subcommands.ReloadCommand;
import org.galemc.gale.command.subcommands.VersionCommand;
import org.jetbrains.annotations.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static net.kyori.adventure.text.Component.newline;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public final class GaleCommand extends Command {
public static final String COMMAND_LABEL = "gale";
public static final String BASE_PERM = GaleCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL;
private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.TRUE);
// subcommand label -> subcommand
private static final GaleSubcommand RELOAD_SUBCOMMAND = new ReloadCommand();
private static final GaleSubcommand VERSION_SUBCOMMAND = new VersionCommand();
private static final GaleSubcommand INFO_SUBCOMMAND = new InfoCommand();
private static final Map<String, GaleSubcommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, GaleSubcommand> commands = new HashMap<>();
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
commands.put(Set.of(InfoCommand.LITERAL_ARGUMENT), INFO_SUBCOMMAND);
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
// alias -> subcommand label
private static final Map<String, String> ALIASES = Util.make(() -> {
final Map<String, Set<String>> aliases = new HashMap<>();
aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver"));
aliases.put(InfoCommand.LITERAL_ARGUMENT, Set.of("about"));
return aliases.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
private String createUsageMessage(Collection<String> arguments) {
return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]";
}
public GaleCommand() {
super(COMMAND_LABEL);
this.description = "Gale related commands";
this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet());
final List<Permission> permissions = SUBCOMMANDS.values().stream().map(GaleSubcommand::getPermission).filter(Objects::nonNull).toList();
this.setPermission(BASE_PERM);
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
pluginManager.addPermission(basePermission);
for (final Permission permission : permissions) {
pluginManager.addPermission(permission);
}
}
@Override
public List<String> tabComplete(
final CommandSender sender,
final String alias,
final String[] args,
final @Nullable Location location
) throws IllegalArgumentException {
if (args.length <= 1) {
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
subCommandArguments.add(subCommandEntry.getKey());
}
}
return CommandUtil.getListMatchingLast(sender, args, subCommandArguments);
}
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand != null && subCommand.second().testPermission(sender)) {
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
}
return Collections.emptyList();
}
private boolean testHasOnePermission(CommandSender sender) {
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
return true;
}
}
return false;
}
@Override
public boolean execute(
final CommandSender sender,
final String commandLabel,
final String[] args
) {
// Check if the sender has the base permission and at least one specific permission
if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) {
sender.sendMessage(Bukkit.permissionMessage());
return true;
}
// Determine the usage message with the subcommands they can perform
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
if (subCommandEntry.getValue().testPermission(sender)) {
subCommandArguments.add(subCommandEntry.getKey());
}
}
String specificUsageMessage = this.createUsageMessage(subCommandArguments);
// If they did not give a subcommand
if (args.length == 0) {
INFO_SUBCOMMAND.execute(sender, InfoCommand.LITERAL_ARGUMENT, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
sender.sendMessage(newline().append(text("Command usage: " + specificUsageMessage, GRAY)));
return false;
}
// If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand == null || !subCommand.second().testPermission(sender)) {
sender.sendMessage(text("Usage: " + specificUsageMessage, RED));
return false;
}
// Execute the subcommand
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
}
private static @Nullable Pair<String, GaleSubcommand> resolveCommand(String label) {
label = label.toLowerCase(Locale.ENGLISH);
@Nullable GaleSubcommand subCommand = SUBCOMMANDS.get(label);
if (subCommand == null) {
final @Nullable String command = ALIASES.get(label);
if (command != null) {
label = command;
subCommand = SUBCOMMANDS.get(command);
}
}
if (subCommand != null) {
return Pair.of(label, subCommand);
}
return null;
}
}

View File

@@ -0,0 +1,33 @@
// Gale - Gale commands
package org.galemc.gale.command;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.bukkit.command.Command;
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
import java.util.HashMap;
import java.util.Map;
@DefaultQualifier(NonNull.class)
public final class GaleCommands {
public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.GALE_ROOT + ".command";
private GaleCommands() {
}
private static final Map<String, Command> COMMANDS = new HashMap<>();
static {
COMMANDS.put(GaleCommand.COMMAND_LABEL, new GaleCommand());
}
public static void registerCommands(final MinecraftServer server) {
COMMANDS.forEach((s, command) ->
server.server.getCommandMap().register(s, "Gale", command)
);
}
}

View File

@@ -0,0 +1,27 @@
// Gale - Gale commands
package org.galemc.gale.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import java.util.Collections;
import java.util.List;
@DefaultQualifier(NonNull.class)
public interface GaleSubcommand {
boolean execute(CommandSender sender, String subCommand, String[] args);
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
return Collections.emptyList();
}
boolean testPermission(CommandSender sender);
@Nullable Permission getPermission();
}

View File

@@ -0,0 +1,32 @@
// Gale - Gale commands
package org.galemc.gale.command;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
public abstract class PermissionedGaleSubcommand implements GaleSubcommand {
public final Permission permission;
protected PermissionedGaleSubcommand(Permission permission) {
this.permission = permission;
}
protected PermissionedGaleSubcommand(String permission, PermissionDefault permissionDefault) {
this(new Permission(permission, permissionDefault));
}
@Override
public boolean testPermission(CommandSender sender) {
return sender.hasPermission(this.permission);
}
@Override
public @Nullable Permission getPermission() {
return this.permission;
}
}

View File

@@ -0,0 +1,42 @@
// Gale - Gale commands - /gale info command
package org.galemc.gale.command.subcommands;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.TextDecoration;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.galemc.gale.command.GaleSubcommand;
import org.jetbrains.annotations.Nullable;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import static net.kyori.adventure.text.Component.text;
@DefaultQualifier(NonNull.class)
public final class InfoCommand implements GaleSubcommand {
public final static String LITERAL_ARGUMENT = "info";
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
sender.sendMessage(
text("Gale is a performant Minecraft server system. Find us on: ")
.append(text("https://github.com/GaleMC/Gale")
.decorate(TextDecoration.UNDERLINED)
.clickEvent(ClickEvent.openUrl("https://github.com/GaleMC/Gale")))
);
return true;
}
@Override
public boolean testPermission(CommandSender sender) {
return true;
}
@Override
public @Nullable Permission getPermission() {
return null;
}
}

View File

@@ -0,0 +1,46 @@
// Gale - Gale commands - /gale reload command
package org.galemc.gale.command.subcommands;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.galemc.gale.command.GaleCommand;
import org.galemc.gale.command.PermissionedGaleSubcommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.permissions.PermissionDefault;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
@DefaultQualifier(NonNull.class)
public final class ReloadCommand extends PermissionedGaleSubcommand {
public final static String LITERAL_ARGUMENT = "reload";
public static final String PERM = GaleCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
public ReloadCommand() {
super(PERM, PermissionDefault.OP);
}
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
this.doReload(sender);
return true;
}
private void doReload(final CommandSender sender) {
Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED));
Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED));
MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
server.galeConfigurations.reloadConfigs(server);
server.server.reloadCount++;
Command.broadcastCommandMessage(sender, text("Gale config reload complete.", GREEN));
}
}

View File

@@ -0,0 +1,39 @@
// Gale - Gale commands - /gale version command
package org.galemc.gale.command.subcommands;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.galemc.gale.command.GaleCommand;
import org.galemc.gale.command.PermissionedGaleSubcommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
@DefaultQualifier(NonNull.class)
public final class VersionCommand extends PermissionedGaleSubcommand {
public final static String LITERAL_ARGUMENT = "version";
public static final String PERM = GaleCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
public VersionCommand() {
super(PERM, PermissionDefault.TRUE);
}
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
if (ver != null) {
ver.execute(sender, GaleCommand.COMMAND_LABEL, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
}
return true;
}
@Override
public boolean testPermission(CommandSender sender) {
return super.testPermission(sender) && sender.hasPermission("bukkit.command.version");
}
}

View File

@@ -0,0 +1,305 @@
// Gale - Gale configuration
package org.galemc.gale.configuration;
import com.google.common.collect.Table;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import io.papermc.paper.configuration.Configuration;
import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.Configurations;
import io.papermc.paper.configuration.NestedSetting;
import io.papermc.paper.configuration.PaperConfigurations;
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
import io.papermc.paper.configuration.serializer.ComponentSerializer;
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
import io.papermc.paper.configuration.serializer.collections.TableSerializer;
import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
import io.papermc.paper.configuration.transformation.Transformations;
import io.papermc.paper.configuration.type.BooleanOrDefault;
import io.papermc.paper.configuration.type.Duration;
import io.papermc.paper.configuration.type.EngineMode;
import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
import io.papermc.paper.configuration.type.number.DoubleOr;
import io.papermc.paper.configuration.type.number.IntOr;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.NodePath;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import org.spongepowered.configurate.transformation.TransformAction;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import static io.leangen.geantyref.GenericTypeReflector.erase;
@SuppressWarnings("Convert2Diamond")
public class GaleConfigurations extends Configurations<GaleGlobalConfiguration, GaleWorldConfiguration> {
private static final Logger LOGGER = LogUtils.getLogger();
static final String GLOBAL_CONFIG_FILE_NAME = "gale-global.yml";
static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "gale-world-defaults.yml";
static final String WORLD_CONFIG_FILE_NAME = "gale-world.yml";
public static final String CONFIG_DIR = "config";
private static final String GLOBAL_HEADER = String.format("""
This is the global configuration file for Gale.
As you can see, there's a lot to configure. Some options may impact gameplay, so use
with caution, and make sure you know what each option does before configuring.
If you need help with the configuration or have any questions related to Gale,
join us in our Discord, or check the GitHub Wiki pages.
The world configuration options are inside
their respective world folder. The files are named %s
Wiki: https://github.com/GaleMC/Gale/wiki
Discord: https://discord.gg/gwezNT8c24""", WORLD_CONFIG_FILE_NAME);
private static final String WORLD_DEFAULTS_HEADER = """
This is the world defaults configuration file for Gale.
As you can see, there's a lot to configure. Some options may impact gameplay, so use
with caution, and make sure you know what each option does before configuring.
If you need help with the configuration or have any questions related to Gale,
join us in our Discord, or check the GitHub Wiki pages.
Configuration options here apply to all worlds, unless you specify overrides inside
the world-specific config file inside each world folder.
Wiki: https://github.com/GaleMC/Gale/wiki
Discord: https://discord.gg/gwezNT8c24""";
private static final Function<ContextMap, String> WORLD_HEADER = map -> String.format("""
This is a world configuration file for Gale.
This file may start empty but can be filled with settings to override ones in the %s/%s
World: %s (%s)""",
CONFIG_DIR,
WORLD_DEFAULTS_CONFIG_FILE_NAME,
map.require(WORLD_NAME),
map.require(WORLD_KEY)
);
private static final String MOVED_NOTICE = """
The global and world default configuration files have moved to %s
and the world-specific configuration file has been moved inside
the respective world folder.
See https://github.com/GaleMC/Gale/wiki for more information.
""";
public GaleConfigurations(final Path globalFolder) {
super(globalFolder, GaleGlobalConfiguration.class, GaleWorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME);
}
@Override
protected YamlConfigurationLoader.Builder createLoaderBuilder() {
return super.createLoaderBuilder()
.defaultOptions(GaleConfigurations::defaultOptions);
}
private static ConfigurationOptions defaultOptions(ConfigurationOptions options) {
return options.serializers(builder -> builder
.register(MapSerializer.TYPE, new MapSerializer(false))
.register(new EnumValueSerializer())
.register(new ComponentSerializer())
);
}
@Override
protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder());
}
private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig());
}
@Override
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
return super.createGlobalLoaderBuilder(registryAccess)
.defaultOptions((options) -> defaultGlobalOptions(registryAccess, options));
}
private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) {
return options
.header(GLOBAL_HEADER)
.serializers(builder -> builder.register(new PacketClassSerializer())
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
);
}
@Override
public GaleGlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
GaleGlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess);
GaleGlobalConfiguration.set(configuration);
return configuration;
}
@Override
protected ContextMap.Builder createDefaultContextMap(final RegistryAccess registryAccess) {
return super.createDefaultContextMap(registryAccess)
.put(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY, PaperConfigurations.SPIGOT_WORLD_DEFAULTS);
}
@Override
protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) {
return super.createWorldObjectMapperFactoryBuilder(contextMap)
.addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get()))
.addNodeResolver(new NestedSetting.Factory())
.addDiscoverer(InnerClassFieldDiscoverer.galeWorldConfig(contextMap));
}
@Override
protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
final RegistryAccess access = contextMap.require(REGISTRY_ACCESS);
return super.createWorldConfigLoaderBuilder(contextMap)
.defaultOptions(options -> options
.header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap))
.serializers(serializers -> serializers
.register(new TypeToken<Reference2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2IntMap<?>>(Reference2IntOpenHashMap::new, Integer.TYPE))
.register(new TypeToken<Reference2LongMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2LongMap<?>>(Reference2LongOpenHashMap::new, Long.TYPE))
.register(new TypeToken<Table<?, ?, ?>>() {}, new TableSerializer())
.register(new StringRepresentableSerializer())
.register(IntOr.Default.SERIALIZER)
.register(IntOr.Disabled.SERIALIZER)
.register(DoubleOr.Default.SERIALIZER)
.register(BooleanOrDefault.SERIALIZER)
.register(Duration.SERIALIZER)
.register(EngineMode.SERIALIZER)
.register(FallbackValueSerializer.create(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer))
.register(new RegistryValueSerializer<>(new TypeToken<EntityType<?>>() {}, access, Registries.ENTITY_TYPE, true))
.register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true))
.register(new RegistryHolderSerializer<>(new TypeToken<ConfiguredFeature<?, ?>>() {}, access, Registries.CONFIGURED_FEATURE, false))
.register(new RegistryHolderSerializer<>(Item.class, access, Registries.ITEM, true))
)
);
}
@Override
protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException {
final ConfigurationNode version = node.node(Configuration.VERSION_FIELD);
final String world = contextMap.require(WORLD_NAME);
if (version.virtual()) {
LOGGER.warn("The Gale world config file for {} didn't have a version set, assuming latest", world);
version.raw(GaleWorldConfiguration.CURRENT_VERSION);
}
if (GaleRemovedConfigurations.REMOVED_WORLD_PATHS.length > 0) {
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
for (NodePath path : GaleRemovedConfigurations.REMOVED_WORLD_PATHS) {
builder.addAction(path, TransformAction.remove());
}
builder.build().apply(node);
}
// ADD FUTURE TRANSFORMS HERE
}
@Override
protected void applyGlobalConfigTransformations(ConfigurationNode node) throws ConfigurateException {
if (GaleRemovedConfigurations.REMOVED_GLOBAL_PATHS.length > 0) {
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
for (NodePath path : GaleRemovedConfigurations.REMOVED_GLOBAL_PATHS) {
builder.addAction(path, TransformAction.remove());
}
builder.build().apply(node);
}
// ADD FUTURE TRANSFORMS HERE
}
private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = Collections.emptyList();
@Override
protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException {
final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
// ADD FUTURE TRANSFORMS HERE (these transforms run after the defaults have been merged into the node)
DEFAULT_AWARE_TRANSFORMATIONS.forEach(transform -> transform.apply(builder, contextMap, defaultsNode));
ConfigurationTransformation transformation;
try {
transformation = builder.build(); // build throws IAE if no actions were provided (bad zml)
} catch (IllegalArgumentException ignored) {
return;
}
transformation.apply(worldNode);
}
@Override
public GaleWorldConfiguration createWorldConfig(final ContextMap contextMap) {
final String levelName = contextMap.require(WORLD_NAME);
try {
return super.createWorldConfig(contextMap);
} catch (IOException exception) {
throw new RuntimeException("Could not create Gale world config for " + levelName, exception);
}
}
@Override
protected boolean isConfigType(final Type type) {
return ConfigurationPart.class.isAssignableFrom(erase(type));
}
public void reloadConfigs(MinecraftServer server) {
try {
this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GaleGlobalConfiguration.get()));
this.initializeWorldDefaultsConfiguration(server.registryAccess());
for (ServerLevel level : server.getAllLevels()) {
this.createWorldConfig(PaperConfigurations.createWorldContextMap(level), reloader(this.worldConfigClass, level.galeConfig()));
}
} catch (Exception ex) {
throw new RuntimeException("Could not reload Gale configuration files", ex);
}
}
public static GaleConfigurations setup(final Path configDir) throws Exception {
try {
PaperConfigurations.createDirectoriesSymlinkAware(configDir);
return new GaleConfigurations(configDir);
} catch (final IOException ex) {
throw new RuntimeException("Could not setup GaleConfigurations", ex);
}
}
@Override
protected int globalConfigVersion() {
return GaleGlobalConfiguration.CURRENT_VERSION;
}
@Override
protected int worldConfigVersion() {
return getWorldConfigurationCurrentVersion();
}
@Override
public int getWorldConfigurationCurrentVersion() {
return GaleWorldConfiguration.CURRENT_VERSION;
}
}

View File

@@ -0,0 +1,139 @@
// Gale - Gale configuration
package org.galemc.gale.configuration;
import io.papermc.paper.configuration.Configuration;
import io.papermc.paper.configuration.ConfigurationPart;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import org.spongepowered.configurate.objectmapping.meta.PostProcess;
import org.spongepowered.configurate.objectmapping.meta.Setting;
import org.bukkit.plugin.java.JavaPluginLoader;
import java.util.Locale;
import java.util.function.Consumer;
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
public class GaleGlobalConfiguration extends ConfigurationPart {
static final int CURRENT_VERSION = 1;
private static GaleGlobalConfiguration instance;
public static GaleGlobalConfiguration get() {
return instance;
}
static void set(GaleGlobalConfiguration instance) {
GaleGlobalConfiguration.instance = instance;
}
@Setting(Configuration.VERSION_FIELD)
public int version = CURRENT_VERSION;
public SmallOptimizations smallOptimizations;
public class SmallOptimizations extends ConfigurationPart {
public ReducedIntervals reducedIntervals;
public class ReducedIntervals extends ConfigurationPart {
public int increaseTimeStatistics = 20; // Gale - Hydrinity - increase time statistics in intervals
public int updateEntityLineOfSight = 4; // Gale - Petal - reduce line of sight updates
@PostProcess
public void postProcess() {
net.minecraft.world.entity.player.Player.increaseTimeStatisticsInterval = Math.max(1, increaseTimeStatistics); // Gale - Hydrinity - increase time statistics in intervals - store as static field for fast access
}
}
}
public GameplayMechanics gameplayMechanics;
public class GameplayMechanics extends ConfigurationPart {
public boolean enableBookWriting = true; // Gale - Pufferfish - make book writing configurable
}
public Misc misc;
public class Misc extends ConfigurationPart {
public boolean verifyChatOrder = true; // Gale - Pufferfish - make chat order verification configurable
public int premiumAccountSlowLoginTimeout = -1; // Gale - make slow login timeout configurable
public boolean ignoreNullLegacyStructureData = false; // Gale - MultiPaper - ignore null legacy structure data
public Keepalive keepalive;
public class Keepalive extends ConfigurationPart {
public boolean sendMultiple = true; // Gale - Purpur - send multiple keep-alive packets
}
// Gale start - YAPFA - last tick time - in TPS command
public LastTickTimeInTpsCommand lastTickTimeInTpsCommand;
public class LastTickTimeInTpsCommand extends ConfigurationPart {
public boolean enabled = false;
public boolean addOversleep = false;
}
// Gale end - YAPFA - last tick time - in TPS command
}
public LogToConsole logToConsole;
public class LogToConsole extends ConfigurationPart { // Gale - EMC - softly log invalid pool element errors
public boolean invalidStatistics = true; // Gale - EMC - do not log invalid statistics
public boolean ignoredAdvancements = true; // Gale - Purpur - do not log ignored advancements
public boolean setBlockInFarChunk = true; // Gale - Purpur - do not log setBlock in far chunks
public boolean unrecognizedRecipes = false; // Gale - Purpur - do not log unrecognized recipes
public boolean legacyMaterialInitialization = false; // Gale - Purpur - do not log legacy Material initialization
public boolean nullIdDisconnections = true; // Gale - Pufferfish - do not log disconnections with null id
public boolean playerLoginLocations = true; // Gale - JettPack - make logging login location configurable
public Chat chat;
public class Chat extends ConfigurationPart {
public boolean emptyMessageWarning = false; // Gale - do not log empty message warnings
public boolean expiredMessageWarning = false; // Gale - do not log expired message warnings
public boolean notSecureMarker = true; // Gale - do not log Not Secure marker
}
// Gale start - Purpur - do not log plugin library loads
public PluginLibraryLoader pluginLibraryLoader;
public class PluginLibraryLoader extends ConfigurationPart {
public boolean downloads = true;
public boolean startLoadLibrariesForPlugin = true;
public boolean libraryLoaded = true;
@PostProcess
public void postProcess() {
JavaPluginLoader.logDownloads = this.downloads;
JavaPluginLoader.logStartLoadLibrariesForPlugin = this.startLoadLibrariesForPlugin;
JavaPluginLoader.logLibraryLoaded = this.libraryLoaded;
}
}
// Gale end - Purpur - do not log plugin library loads
// Gale start - EMC - softly log invalid pool element errors
public String invalidPoolElementErrorLogLevel = "info";
public transient Consumer<String> invalidPoolElementErrorStringConsumer;
@PostProcess
public void postProcess() {
this.invalidPoolElementErrorStringConsumer = switch (this.invalidPoolElementErrorLogLevel.toLowerCase(Locale.ROOT)) {
case "none" -> $ -> {};
case "info", "log" -> PoolElementStructurePiece.LOGGER::info;
case "warn", "warning" -> PoolElementStructurePiece.LOGGER::warn;
default -> PoolElementStructurePiece.LOGGER::error;
};
}
// Gale end - EMC - softly log invalid pool element errors
}
}

View File

@@ -0,0 +1,13 @@
// Gale - Gale configuration
package org.galemc.gale.configuration;
import org.spongepowered.configurate.NodePath;
interface GaleRemovedConfigurations {
NodePath[] REMOVED_WORLD_PATHS = {};
NodePath[] REMOVED_GLOBAL_PATHS = {};
}

View File

@@ -0,0 +1,143 @@
// Gale - Gale configuration
package org.galemc.gale.configuration;
import com.mojang.logging.LogUtils;
import io.papermc.paper.configuration.Configuration;
import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.PaperConfigurations;
import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.spigotmc.SpigotWorldConfig;
import org.spongepowered.configurate.objectmapping.meta.Setting;
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
public class GaleWorldConfiguration extends ConfigurationPart {
private static final Logger LOGGER = LogUtils.getLogger();
public static final int CURRENT_VERSION = 1;
private transient final SpigotWorldConfig spigotConfig;
private transient final ResourceLocation worldKey;
public GaleWorldConfiguration(SpigotWorldConfig spigotConfig, ResourceLocation worldKey) {
this.spigotConfig = spigotConfig;
this.worldKey = worldKey;
}
public boolean isDefault() {
return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY);
}
@Setting(Configuration.VERSION_FIELD)
public int version = CURRENT_VERSION;
public SmallOptimizations smallOptimizations;
public class SmallOptimizations extends ConfigurationPart {
public boolean saveFireworks = true; // Gale - EMC - make saving fireworks configurable
public boolean useOptimizedSheepOffspringColor = true; // Gale - carpet-fixes - optimize sheep offspring color
// Gale start - Airplane - reduce projectile chunk loading
public MaxProjectileChunkLoads maxProjectileChunkLoads;
public class MaxProjectileChunkLoads extends ConfigurationPart {
public int perTick = 10;
public PerProjectile perProjectile;
public class PerProjectile extends ConfigurationPart {
public int max = 10;
public boolean resetMovementAfterReachLimit = false;
public boolean removeFromWorldAfterReachLimit = false;
}
}
// Gale end - Airplane - reduce projectile chunk loading
public ReducedIntervals reducedIntervals;
public class ReducedIntervals extends ConfigurationPart {
public int acquirePoiForStuckEntity = 60; // Gale - Airplane - reduce acquire POI for stuck entities
public int checkStuckInWall = 10; // Gale - Pufferfish - reduce in wall checks
public int villagerItemRepickup = 100; // Gale - EMC - reduce villager item re-pickup
public CheckNearbyItem checkNearbyItem;
public class CheckNearbyItem extends ConfigurationPart {
// Gale start - EMC - reduce hopper item checks
public Hopper hopper;
public class Hopper extends ConfigurationPart {
public int interval = 1;
public Minecart minecart;
public class Minecart extends ConfigurationPart {
public int interval = 1;
public TemporaryImmunity temporaryImmunity;
public class TemporaryImmunity extends ConfigurationPart {
public int duration = 100;
public int nearbyItemMaxAge = 1200;
public int checkForMinecartNearItemInterval = 20;
public boolean checkForMinecartNearItemWhileInactive = true;
public double maxItemHorizontalDistance = 24.0;
public double maxItemVerticalDistance = 4.0;
}
}
}
// Gale end - EMC - reduce hopper item checks
}
}
public LoadChunks loadChunks;
public class LoadChunks extends ConfigurationPart {
public boolean toSpawnPhantoms = false; // Gale - MultiPaper - don't load chunks to spawn phantoms
public boolean toActivateClimbingEntities = false; // Gale - don't load chunks to activate climbing entities
}
}
public GameplayMechanics gameplayMechanics;
public class GameplayMechanics extends ConfigurationPart {
public Fixes fixes;
public class Fixes extends ConfigurationPart {
public boolean broadcastCritAnimationsAsTheEntityBeingCritted = false; // Gale - MultiPaper - broadcast crit animations as the entity being critted
// Gale start - Purpur - fix MC-238526
@Setting("mc-238526")
public boolean mc238526 = false;
// Gale end - Purpur - fix MC-238526
// Gale start - Purpur - fix MC-121706
@Setting("mc-121706")
public boolean mc121706 = false;
// Gale end - Purpur - fix MC-121706
}
public boolean arrowMovementResetsDespawnCounter = true; // Gale - Purpur - make arrow movement resetting despawn counter configurable
public boolean entitiesCanRandomStrollIntoNonTickingChunks = true; // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
public double entityWakeUpDurationRatioStandardDeviation = 0.2; // Gale - variable entity wake-up duration
public boolean hideFlamesOnEntitiesWithFireResistance = false; // Gale - Slice - hide flames on entities with fire resistance
public boolean tryRespawnEnderDragonAfterEndCrystalPlace = true; // Gale - Pufferfish - make ender dragon respawn attempt after placing end crystals configurable
}
}

View File

@@ -0,0 +1,154 @@
// Gale - branding changes - version fetcher
package org.galemc.gale.version;
import com.destroystokyo.paper.PaperVersionFetcher;
import com.destroystokyo.paper.VersionHistoryManager;
import com.destroystokyo.paper.util.VersionFetcher;
import com.google.common.base.Charsets;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.mojang.logging.LogUtils;
import io.papermc.paper.ServerBuildInfo;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Optional;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.TextColor.color;
/**
* An abstract version fetcher, derived from {@link PaperVersionFetcher}.
* This class was then made to be a superclass of both {@link PaperVersionFetcher}
* and {@link GaleVersionFetcher}.
* <br>
* Changes to {@link PaperVersionFetcher} are indicated by Gale marker comments.
*/
public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
protected static final Logger LOGGER = LogUtils.getClassLogger();
protected static final int DISTANCE_ERROR = -1;
protected static final int DISTANCE_UNKNOWN = -2;
protected static final ServerBuildInfo BUILD_INFO = ServerBuildInfo.buildInfo();
// Gale start - branding changes - version fetcher
protected final String gitHubBranchName;
protected final String downloadPage;
protected final String organizationDisplayName;
protected final String projectDisplayName;
protected final String gitHubOrganizationName;
protected final String gitHubRepoName;
protected AbstractPaperVersionFetcher(String githubBranchName, String downloadPage, String organizationDisplayName, String projectDisplayName, String gitHubOrganizationName, String gitHubRepoName) {
this.gitHubBranchName = githubBranchName;
this.downloadPage = downloadPage;
this.organizationDisplayName = organizationDisplayName;
this.projectDisplayName = projectDisplayName;
this.gitHubOrganizationName = gitHubOrganizationName;
this.gitHubRepoName = gitHubRepoName;
}
// Gale end - branding changes - version fetcher
@Override
public long getCacheTime() {
return 720000;
}
@Override
public @NotNull Component getVersionMessage(final @NotNull String serverVersion) {
final Component updateMessage;
final ServerBuildInfo build = ServerBuildInfo.buildInfo();
if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) {
updateMessage = text("You are running a development version without access to version information", color(0xFF5300));
} else {
updateMessage = getUpdateStatusMessage(this.gitHubOrganizationName + "/" + this.gitHubRepoName, build); // Gale - branding changes - version fetcher
}
final @Nullable Component history = this.getHistory();
return history != null ? Component.textOfChildren(updateMessage, Component.newline(), history) : updateMessage;
}
// Gale start - branding changes - version fetcher
protected boolean canFetchDistanceFromSiteApi() {
return false;
}
protected int fetchDistanceFromSiteApi(int jenkinsBuild) {
return -1;
}
// Gale end - branding changes - version fetcher
private Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) {
int distance = DISTANCE_ERROR;
// Gale start - branding changes - version fetcher
final Optional<String> gitBranch = build.gitBranch();
final Optional<String> gitCommit = build.gitCommit();
if (gitBranch.isPresent() && gitCommit.isPresent()) {
distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get());
}
// Gale end - branding changes - version fetcher
return switch (distance) {
case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW);
case 0 -> text("You are running the latest version", NamedTextColor.GREEN);
case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW);
default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
.append(Component.newline())
.append(text("Download the new version at: ")
.append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher
.hoverEvent(text("Click to open", NamedTextColor.WHITE))
.clickEvent(ClickEvent.openUrl(this.downloadPage)))); // Gale - branding changes - version fetcher
};
}
// Contributed by Techcable <Techcable@outlook.com> in GH-65
private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) {
try {
final HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.github.com/repos/%s/compare/%s...%s".formatted(repo, branch, hash)).toURL().openConnection();
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)
return DISTANCE_UNKNOWN; // Unknown commit
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
final JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
final String status = obj.get("status").getAsString();
return switch (status) {
case "identical" -> 0;
case "behind" -> obj.get("behind_by").getAsInt();
default -> DISTANCE_ERROR;
};
} catch (final JsonSyntaxException | NumberFormatException e) {
LOGGER.error("Error parsing json from GitHub's API", e);
return DISTANCE_ERROR;
}
} catch (final IOException e) {
LOGGER.error("Error while parsing version", e);
return DISTANCE_ERROR;
}
}
private @Nullable Component getHistory() {
final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
if (data == null) {
return null;
}
final @Nullable String oldVersion = data.getOldVersion();
if (oldVersion == null) {
return null;
}
return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
}
}

View File

@@ -0,0 +1,38 @@
// Gale - semantic version
package org.galemc.gale.version;
import org.jetbrains.annotations.NotNull;
/**
* A holder for the Gale semantic version.
*/
public final class GaleSemanticVersion {
private GaleSemanticVersion() {
throw new RuntimeException();
}
/**
* A semantic version in the format "<code>major.minor.patch</code>", for example "<code>1.5.1</code>".
* The <code>major</code> version is incremented when a large and overarching set of features, with a large
* and overarching common goal or effect, has been added compared to the first release with that major version.
* The <code>minor</code> version is incremented for each build that has a different intended feature set
* (for example, some features or part of them were added or removed).
* The <code>patch</code> version is incremented for small changes that do not affect the goal of any feature,
* such as bug fixes, performance improvements or changes in wording.
*/
public static final @NotNull String version = "0.6.15";
/**
* The "<code>major.minor</code>" portion of the {@link #version}.
*/
public static final @NotNull String majorMinorVersion;
static {
int firstDotIndex = version.indexOf('.');
int secondDotIndex = version.indexOf('.', firstDotIndex + 1);
majorMinorVersion = version.substring(0, secondDotIndex);
}
}

View File

@@ -0,0 +1,17 @@
// Gale - branding changes - version fetcher
package org.galemc.gale.version;
public class GaleVersionFetcher extends AbstractPaperVersionFetcher {
public GaleVersionFetcher() {
super(
"ver/1.21.4",
"https://github.com/Dreeam-qwq/Gale",
"GaleMC",
"Gale",
"GaleMC",
"Gale");
}
}

View File

@@ -0,0 +1,50 @@
// Gale - virtual thread support
package org.galemc.gale.virtualthread;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
/**
* An implementation of {@link VirtualThreadService} that can create virtual threads directly.
*
* @author Martijn Muijsers
*/
final class DirectVirtualThreadService extends VirtualThreadService {
private DirectVirtualThreadService() {
super();
}
@Override
public @NotNull ThreadFactory createFactory() {
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
//throw new UnsupportedOperationException();
return Thread.ofVirtual().factory();
}
@Override
public @NotNull Thread start(@NotNull Runnable task) {
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
//throw new UnsupportedOperationException();
Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
return Thread.ofVirtual().start(task);
}
/**
* @return A functional {@link DirectVirtualThreadService}.
* @throws Throwable If creating virtual threads directly is not supported by the current runtime.
* This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
*/
static @NotNull DirectVirtualThreadService create() throws Throwable {
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
//throw new UnsupportedOperationException();
var service = new DirectVirtualThreadService();
// Run some tests to verify
service.runTest();
// If we end up here, it works
return service;
}
}

View File

@@ -0,0 +1,75 @@
// Gale - virtual thread support
package org.galemc.gale.virtualthread;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
/**
* An implementation of {@link VirtualThreadService} that can create virtual threads using Java reflection.
*
* @author Martijn Muijsers
*/
final class ReflectionVirtualThreadService extends VirtualThreadService {
/**
* The {@link Thread}<code>#ofVirtual()</code> method.
*/
private final @NotNull Method Thread_ofVirtual_method;
/**
* The {@link Thread}<code>.Builder#factory()</code> method.
*/
private final @NotNull Method Thread_Builder_factory_method;
/**
* The {@link Thread}<code>.Builder#start(Runnable)</code> method.
*/
private final @NotNull Method Thread_Builder_start_method;
private ReflectionVirtualThreadService() throws Throwable {
this.Thread_ofVirtual_method = Objects.requireNonNull(Thread.class.getMethod("ofVirtual"));
// The Thread.Builder class
var Thread_Builder_class = Objects.requireNonNull(Class.forName("java.lang.Thread$Builder"));
this.Thread_Builder_factory_method = Objects.requireNonNull(Thread_Builder_class.getMethod("factory"));
this.Thread_Builder_start_method = Objects.requireNonNull(Thread_Builder_class.getMethod("start", Runnable.class));
}
@Override
public @NotNull ThreadFactory createFactory() {
try {
return (ThreadFactory) this.Thread_Builder_factory_method.invoke(this.Thread_ofVirtual_method.invoke(null));
} catch (Exception e) {
// This should not be possible because it was tested in create()
throw new RuntimeException(e);
}
}
@Override
public @NotNull Thread start(@NotNull Runnable task) {
Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
try {
return (Thread) this.Thread_Builder_start_method.invoke(this.Thread_ofVirtual_method.invoke(null), task);
} catch (Exception e) {
// This should not be possible because it was tested in create()
throw new RuntimeException(e);
}
}
/**
* @return A functional {@link ReflectionVirtualThreadService}.
* @throws Throwable If creating virtual threads via reflection is not supported by the current runtime.
* This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
*/
static @NotNull ReflectionVirtualThreadService create() throws Throwable {
// This will already throw something if the reflection fails
var service = new ReflectionVirtualThreadService();
// Run some tests to verify
service.runTest();
// If we end up here, it works
return service;
}
}

View File

@@ -0,0 +1,104 @@
// Gale - virtual thread support
package org.galemc.gale.virtualthread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.ThreadFactory;
/**
* An abstract service to create virtual threads.
*
* @author Martijn Muijsers
*/
public sealed abstract class VirtualThreadService permits ReflectionVirtualThreadService, DirectVirtualThreadService {
/**
* @return A {@link ThreadFactory} that produces virtual threads.
*/
public abstract @NotNull ThreadFactory createFactory();
/**
* @param task The runnable for the thread to execute.
* @return A virtual thread that has been started with the given task.
*/
public abstract @NotNull Thread start(Runnable task);
/**
* Runs a test on the {@link #createFactory} and {@link #start} methods,
* which certainly throws some {@link Throwable} if something goes wrong.
*/
protected void runTest() throws Throwable {
// This will definitely throw something if it doesn't work
try {
this.start(() -> {}).join();
} catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
try {
var thread = this.createFactory().newThread(() -> {});
thread.start();
thread.join();
} catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
// If we end up here, it works
}
private static boolean initialized = false;
/**
* The {@link VirtualThreadService} for the current runtime,
* or null if virtual threads are not supported, or if not {@link #initialized} yet.
*/
private static @Nullable VirtualThreadService implementation;
/**
* @return Whether virtual threads are supported on the current runtime.
*/
public static boolean isSupported() {
return get() != null;
}
/**
* @return The {@link VirtualThreadService} for the current runtime,
* or null if virtual threads are not {@linkplain #isSupported() supported}.
* <p>
* This method is thread-safe only after the first time it has been fully run.
*/
public static @Nullable VirtualThreadService get() {
if (!initialized) {
initialized = true;
try {
implementation = DirectVirtualThreadService.create();
} catch (Throwable ignored) {
try {
implementation = ReflectionVirtualThreadService.create();
} catch (Throwable ignored2) {}
}
}
return implementation;
}
/**
* The minimum major version of Java that is known to support using virtual threads
* (although possibly behind a feature preview flag).
*/
public static final int minimumJavaMajorVersionWithFeaturePreview = 19;
/**
* The minimum major version of Java that is known to support using virtual threads
* even without any feature preview flags.
*/
public static final int minimumJavaMajorVersionWithoutFeaturePreview = 21;
public static int getJavaMajorVersion() {
var version = System.getProperty("java.version");
if (version.startsWith("1.")) {
return version.charAt(2) - '0';
}
if (version.contains("-")) {
version = version.substring(0, version.indexOf("-"));
}
int dotIndex = version.indexOf(".");
return Integer.parseInt(dotIndex == -1 ? version : version.substring(0, dotIndex));
}
}