mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-28 11:19:08 +00:00
more clean up
This commit is contained in:
@@ -17,7 +17,7 @@ public abstract class BaseMenuCommand extends BaseSubCommand {
|
||||
private static final String HEADER_MESSAGE = "<dark_purple>| <white><message>";
|
||||
private static final String COMMAND_MSG = "<dark_purple>| <dark_gray>*</dark_gray> /<light_purple><command>";
|
||||
|
||||
public BaseMenuCommand(String name) {
|
||||
public BaseMenuCommand(final String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public abstract class BaseMenuCommand extends BaseSubCommand {
|
||||
public abstract Iterable<Command> subCommands();
|
||||
|
||||
@Override
|
||||
public final void execute(CommandSender sender, String[] args) {
|
||||
public final void execute(final CommandSender sender, final String[] args) {
|
||||
if (args.length > 0) {
|
||||
for (final Command base : this.subCommands()) {
|
||||
if (base.getName().equalsIgnoreCase(args[0])) {
|
||||
@@ -43,7 +43,7 @@ public abstract class BaseMenuCommand extends BaseSubCommand {
|
||||
this.sendHelpMessage(sender);
|
||||
}
|
||||
|
||||
private void sendHelpMessage(CommandSender sender) {
|
||||
private void sendHelpMessage(final CommandSender sender) {
|
||||
sender.sendMessage(Component.text(".", NamedTextColor.DARK_PURPLE));
|
||||
for (final String header : this.header().split("\n")) {
|
||||
if (!header.isEmpty()) {
|
||||
@@ -61,7 +61,7 @@ public abstract class BaseMenuCommand extends BaseSubCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
public final List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException {
|
||||
if (!this.testPermissionSilent(sender) || args.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -11,19 +11,19 @@ import java.util.function.Function;
|
||||
|
||||
@NullMarked
|
||||
public abstract class BaseSubCommand extends Command {
|
||||
public BaseSubCommand(String name) {
|
||||
public BaseSubCommand(final String name) {
|
||||
super(name);
|
||||
this.description = "Sakura Command " + name;
|
||||
this.setPermission("bukkit.command." + name);
|
||||
}
|
||||
|
||||
public abstract void execute(CommandSender sender, String[] args);
|
||||
public abstract void execute(final CommandSender sender, final String[] args);
|
||||
|
||||
public void tabComplete(List<String> list, String[] args) throws IllegalArgumentException {}
|
||||
public void tabComplete(final List<String> completions, final String[] args) throws IllegalArgumentException {}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public final boolean execute(CommandSender sender, String label, String[] args) {
|
||||
public final boolean execute(final CommandSender sender, final String label, final String[] args) {
|
||||
if (this.testPermission(sender)) {
|
||||
this.execute(sender, args);
|
||||
}
|
||||
@@ -32,8 +32,8 @@ public abstract class BaseSubCommand extends Command {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
List<String> completions = new ArrayList<>(0);
|
||||
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException {
|
||||
final List<String> completions = new ArrayList<>(0);
|
||||
|
||||
if (this.testPermissionSilent(sender)) {
|
||||
this.tabComplete(completions, args);
|
||||
@@ -42,26 +42,26 @@ public abstract class BaseSubCommand extends Command {
|
||||
return completions;
|
||||
}
|
||||
|
||||
protected final Optional<Integer> parseInt(String[] args, int index) {
|
||||
protected final Optional<Integer> parseInt(final String[] args, final int index) {
|
||||
return this.parse(args, index, Integer::parseInt);
|
||||
}
|
||||
|
||||
protected final Optional<Long> parseLong(String[] args, int index) {
|
||||
protected final Optional<Long> parseLong(final String[] args, final int index) {
|
||||
return this.parse(args, index, Long::parseLong);
|
||||
}
|
||||
|
||||
protected final Optional<Float> parseFloat(String[] args, int index) {
|
||||
protected final Optional<Float> parseFloat(final String[] args, final int index) {
|
||||
return this.parse(args, index, Float::parseFloat);
|
||||
}
|
||||
|
||||
protected final Optional<Double> parseDouble(String[] args, int index) {
|
||||
protected final Optional<Double> parseDouble(final String[] args, final int index) {
|
||||
return this.parse(args, index, Double::parseDouble);
|
||||
}
|
||||
|
||||
protected final <T> Optional<T> parse(String[] args, int index, Function<String, T> func) {
|
||||
protected final <T> Optional<T> parse(final String[] args, final int index, final Function<String, T> parseFunction) {
|
||||
try {
|
||||
String arg = args[index];
|
||||
return Optional.of(func.apply(arg));
|
||||
final String toParse = args[index];
|
||||
return Optional.of(parseFunction.apply(toParse));
|
||||
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public abstract class PlayerOnlySubCommand extends BaseSubCommand {
|
||||
public PlayerOnlySubCommand(String name) {
|
||||
public PlayerOnlySubCommand(final String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public abstract void execute(Player player, String[] args);
|
||||
public abstract void execute(final Player player, final String[] args);
|
||||
|
||||
public final void execute(CommandSender sender, String[] args) {
|
||||
public final void execute(final CommandSender sender, final String[] args) {
|
||||
if (sender instanceof Player player) {
|
||||
this.execute(player, args);
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.util.*;
|
||||
|
||||
@NullMarked
|
||||
public final class SakuraCommand extends BaseMenuCommand {
|
||||
public SakuraCommand(String name) {
|
||||
public SakuraCommand(final String name) {
|
||||
super(name);
|
||||
this.description = "";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.samsuik.sakura.command;
|
||||
|
||||
import me.samsuik.sakura.command.subcommands.*;
|
||||
import me.samsuik.sakura.command.subcommands.debug.DebugCommand;
|
||||
import me.samsuik.sakura.command.subcommands.debug.DebugLocalConfiguration;
|
||||
import me.samsuik.sakura.command.subcommands.debug.DebugRedstoneCache;
|
||||
import me.samsuik.sakura.player.visibility.VisibilityTypes;
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
package me.samsuik.sakura.command.subcommands;
|
||||
|
||||
import me.samsuik.sakura.command.BaseSubCommand;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
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;
|
||||
|
||||
@NullMarked
|
||||
public final class ConfigCommand extends BaseSubCommand {
|
||||
public ConfigCommand(String name) {
|
||||
public ConfigCommand(final String name) {
|
||||
super(name);
|
||||
this.description = "Command for reloading the sakura configuration file";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
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));
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
sender.sendMessage(Component.text("Please note that this command is not supported and may cause issues.", NamedTextColor.RED));
|
||||
sender.sendMessage(Component.text("If you encounter any issues please use the /stop command to restart your server.", NamedTextColor.RED));
|
||||
|
||||
MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
|
||||
final MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
|
||||
server.sakuraConfigurations.reloadConfigs(server);
|
||||
server.server.reloadCount++;
|
||||
|
||||
Command.broadcastCommandMessage(sender, text("Sakura config reload complete.", GREEN));
|
||||
sender.sendMessage(Component.text("Sakura config reload complete.", NamedTextColor.GREEN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import org.jspecify.annotations.NullMarked;
|
||||
public final class FPSCommand extends PlayerOnlySubCommand {
|
||||
private final VisibilityGui visibilityGui = new VisibilityGui();
|
||||
|
||||
public FPSCommand(String name) {
|
||||
public FPSCommand(final String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Player player, String[] args) {
|
||||
public void execute(final Player player, final String[] args) {
|
||||
this.visibilityGui.showTo(player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,23 +23,26 @@ public final class TPSCommand extends BaseSubCommand {
|
||||
private static final int GRAPH_HEIGHT = 10;
|
||||
private static final Style GRAY_WITH_STRIKETHROUGH = Style.style(NamedTextColor.GRAY, TextDecoration.STRIKETHROUGH);
|
||||
|
||||
public TPSCommand(String name) {
|
||||
public TPSCommand(final String name) {
|
||||
super(name);
|
||||
this.description = "Displays the current ticks per second";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
ServerTickInformation tickInformation = MinecraftServer.getServer().latestTickInformation();
|
||||
long identifier = this.parseLong(args, 1).orElse(tickInformation.identifier());
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
final ServerTickInformation tickInformation = MinecraftServer.getServer().latestTickInformation();
|
||||
final long identifier = this.parseLong(args, 1).orElse(tickInformation.identifier());
|
||||
double scale = this.parseDouble(args, 0).orElse(-1.0);
|
||||
if (scale < 0.0) {
|
||||
// Scale the tps graph to the current server tps
|
||||
scale = this.dynamicScale(identifier);
|
||||
}
|
||||
|
||||
ImmutableList<ServerTickInformation> tickHistory = MinecraftServer.getServer().tickHistory(identifier - GRAPH_WIDTH, identifier);
|
||||
DetailedTPSGraph graph = new DetailedTPSGraph(GRAPH_WIDTH, GRAPH_HEIGHT, scale, tickHistory);
|
||||
BuiltComponentCanvas canvas = graph.plot();
|
||||
final ImmutableList<ServerTickInformation> tickHistory = MinecraftServer.getServer().tickHistory(identifier - GRAPH_WIDTH, identifier);
|
||||
final DetailedTPSGraph graph = new DetailedTPSGraph(GRAPH_WIDTH, GRAPH_HEIGHT, scale, tickHistory);
|
||||
final BuiltComponentCanvas canvas = graph.plot();
|
||||
|
||||
// Add the sidebars, header and footer
|
||||
canvas.appendLeft(Component.text(":", NamedTextColor.BLACK));
|
||||
canvas.appendRight(Component.text(":", NamedTextColor.BLACK));
|
||||
canvas.header(this.createHeaderComponent(tickInformation, identifier));
|
||||
@@ -47,24 +50,24 @@ public final class TPSCommand extends BaseSubCommand {
|
||||
.append(Component.text(Strings.repeat(" ", GRAPH_WIDTH - 1), GRAY_WITH_STRIKETHROUGH))
|
||||
.append(Component.text("*")));
|
||||
|
||||
for (Component component : canvas.components()) {
|
||||
for (final Component component : canvas.components()) {
|
||||
sender.sendMessage(component);
|
||||
}
|
||||
}
|
||||
|
||||
private double dynamicScale(long identifier) {
|
||||
ImmutableList<ServerTickInformation> tickHistory = MinecraftServer.getServer().tickHistory(identifier - 5, identifier);
|
||||
double averageTps = tickHistory.stream()
|
||||
private double dynamicScale(final long identifier) {
|
||||
final ImmutableList<ServerTickInformation> tickHistory = MinecraftServer.getServer().tickHistory(identifier - 5, identifier);
|
||||
final double averageTps = tickHistory.stream()
|
||||
.mapToDouble(ServerTickInformation::tps)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
return 20 / averageTps;
|
||||
return 20.0 / averageTps;
|
||||
}
|
||||
|
||||
private Component createHeaderComponent(ServerTickInformation tickInformation, long identifier) {
|
||||
int scrollAmount = GRAPH_WIDTH / 3 * 2;
|
||||
double memoryUsage = memoryUsage();
|
||||
TextComponent.Builder builder = Component.text();
|
||||
private Component createHeaderComponent(final ServerTickInformation tickInformation, final long identifier) {
|
||||
final int scrollAmount = GRAPH_WIDTH / 3 * 2;
|
||||
final double memoryUsage = memoryUsage();
|
||||
final TextComponent.Builder builder = Component.text();
|
||||
builder.color(NamedTextColor.DARK_GRAY);
|
||||
builder.append(Component.text("< ")
|
||||
.clickEvent(ClickEvent.runCommand("/tps -1 " + (identifier + scrollAmount))));
|
||||
@@ -83,10 +86,10 @@ public final class TPSCommand extends BaseSubCommand {
|
||||
}
|
||||
|
||||
private static double memoryUsage() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
double free = runtime.freeMemory();
|
||||
double max = runtime.maxMemory();
|
||||
double alloc = runtime.totalMemory();
|
||||
final Runtime runtime = Runtime.getRuntime();
|
||||
final double free = runtime.freeMemory();
|
||||
final double max = runtime.maxMemory();
|
||||
final double alloc = runtime.totalMemory();
|
||||
return (alloc - free) / max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,18 @@ import java.util.Arrays;
|
||||
public final class VisualCommand extends PlayerOnlySubCommand {
|
||||
private final VisibilityType type;
|
||||
|
||||
public VisualCommand(VisibilityType type, String... aliases) {
|
||||
public VisualCommand(final VisibilityType type, final String... aliases) {
|
||||
super(type.key() + "visibility");
|
||||
this.setAliases(Arrays.asList(aliases));
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Player player, String[] args) {
|
||||
VisibilitySettings settings = player.getVisibility();
|
||||
VisibilityState state = settings.toggle(type);
|
||||
public void execute(final Player player, final String[] args) {
|
||||
final VisibilitySettings settings = player.getVisibility();
|
||||
final VisibilityState state = settings.toggle(type);
|
||||
|
||||
String stateName = (state == VisibilityState.ON) ? "Enabled" : "Disabled";
|
||||
player.sendRichMessage(GlobalConfiguration.get().messages.fpsSettingChange,
|
||||
Placeholder.unparsed("name", this.type.key()),
|
||||
Placeholder.unparsed("state", stateName)
|
||||
);
|
||||
final String stateName = (state == VisibilityState.ON) ? "Enabled" : "Disabled";
|
||||
player.sendMessage(GlobalConfiguration.get().messages.fpsSettingChangeComponent(this.type.key(), stateName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.samsuik.sakura.command.subcommands;
|
||||
package me.samsuik.sakura.command.subcommands.debug;
|
||||
|
||||
import me.samsuik.sakura.command.BaseMenuCommand;
|
||||
import me.samsuik.sakura.command.SakuraCommands;
|
||||
@@ -7,13 +7,13 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class DebugCommand extends BaseMenuCommand {
|
||||
public DebugCommand(String name) {
|
||||
public DebugCommand(final String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String header() {
|
||||
return "Debug command for testing Sakura features and api";
|
||||
return "Command for debugging Sakura features and api";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -19,38 +19,40 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@NullMarked
|
||||
public final class DebugRedstoneCache extends PlayerOnlySubCommand {
|
||||
public DebugRedstoneCache(String name) {
|
||||
public DebugRedstoneCache(final String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Player player, String[] args) {
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
Level level = nmsPlayer.level();
|
||||
Set<Location> locations = new HashSet<>();
|
||||
for (RedstoneNetwork network : level.redstoneWireCache.getNetworkCache().values()) {
|
||||
byte randomColour = (byte) ThreadLocalRandom.current().nextInt(16);
|
||||
DyeColor dyeColour = DyeColor.getByWoolData(randomColour);
|
||||
Material material = Material.matchMaterial(dyeColour.name() + "_WOOL");
|
||||
public void execute(final Player player, final String[] args) {
|
||||
final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
final Level level = nmsPlayer.level();
|
||||
final Set<Location> redstoneWires = new HashSet<>();
|
||||
|
||||
// Display randomly coloured wool blocks in place of redstone wires in a network.
|
||||
for (final RedstoneNetwork network : level.redstoneWireCache.getNetworkCache().values()) {
|
||||
final byte randomColour = (byte) ThreadLocalRandom.current().nextInt(16);
|
||||
final DyeColor dyeColour = DyeColor.getByWoolData(randomColour);
|
||||
final Material material = Material.matchMaterial(dyeColour.name() + "_WOOL");
|
||||
|
||||
if (!network.isRegistered()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (BlockPos pos : network.getWirePositions()) {
|
||||
Location location = CraftLocation.toBukkit(pos, level);
|
||||
for (final BlockPos pos : network.getWirePositions()) {
|
||||
final Location location = CraftLocation.toBukkit(pos, level);
|
||||
if (player.getLocation().distance(location) >= 64.0) {
|
||||
continue;
|
||||
}
|
||||
player.sendBlockChange(location, material.createBlockData());
|
||||
locations.add(location);
|
||||
redstoneWires.add(location);
|
||||
}
|
||||
}
|
||||
|
||||
player.sendRichMessage("<red>Displaying %dx cached redstone wires".formatted(locations.size()));
|
||||
player.sendRichMessage("<red>Displaying %dx cached redstone wires".formatted(redstoneWires.size()));
|
||||
|
||||
level.levelTickScheduler.delayedTask(() -> {
|
||||
for (Location loc : locations) {
|
||||
level.levelTickScheduler.runTaskLater(() -> {
|
||||
for (final Location loc : redstoneWires) {
|
||||
player.sendBlockChange(loc, loc.getBlock().getBlockData());
|
||||
}
|
||||
}, 1200);
|
||||
|
||||
@@ -36,9 +36,17 @@ public final class GlobalConfiguration extends ConfigurationPart {
|
||||
public String fpsSettingChange = "<dark_gray>(<light_purple>S</light_purple>) <gray><state> <yellow><name>";
|
||||
public boolean tpsShowEntityAndChunkCount = true;
|
||||
|
||||
public Component fpsSettingChangeComponent(final String name, final String state) {
|
||||
return MiniMessage.miniMessage().deserialize(
|
||||
this.fpsSettingChange,
|
||||
Placeholder.unparsed("name", name),
|
||||
Placeholder.unparsed("state", state)
|
||||
);
|
||||
}
|
||||
|
||||
public Component durableBlockInteractionComponent(final int remaining, final int durability) {
|
||||
return MiniMessage.miniMessage().deserialize(
|
||||
GlobalConfiguration.get().messages.durableBlockInteraction,
|
||||
this.durableBlockInteraction,
|
||||
Placeholder.unparsed("remaining", String.valueOf(remaining)),
|
||||
Placeholder.unparsed("durability", String.valueOf(durability))
|
||||
);
|
||||
|
||||
@@ -7,7 +7,9 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class SakuraVersionInformation {
|
||||
private static final String VERSION_MESSAGE = """
|
||||
<dark_purple>.
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
package me.samsuik.sakura.configuration.serializer;
|
||||
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import me.samsuik.sakura.mechanics.MechanicVersion;
|
||||
import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget;
|
||||
import me.samsuik.sakura.mechanics.MinecraftVersionEncoding;
|
||||
import me.samsuik.sakura.mechanics.ServerType;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@NullMarked
|
||||
public final class MinecraftMechanicsTargetSerializer implements TypeSerializer<MinecraftMechanicsTarget> {
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import me.samsuik.sakura.configuration.transformation.global.V1_RelocateMessages;
|
||||
import me.samsuik.sakura.configuration.transformation.global.V2_ConvertIconToMaterial;
|
||||
import me.samsuik.sakura.configuration.transformation.world.*;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
@@ -13,6 +14,7 @@ import org.spongepowered.configurate.transformation.TransformAction;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
@NullMarked
|
||||
public final class ConfigurationTransformations {
|
||||
private static final List<NodePath> REMOVED_GLOBAL_PATHS = List.of(
|
||||
NodePath.path("cannons")
|
||||
@@ -48,6 +50,10 @@ public final class ConfigurationTransformations {
|
||||
versionedBuilder.build().apply(node);
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation transform(final NodePath path, final TransformAction transform) {
|
||||
return ConfigurationTransformation.builder().addAction(path, transform).build();
|
||||
}
|
||||
|
||||
public static TransformAction newValue(final Function<ConfigurationNode, Object> func) {
|
||||
return (k, v) -> {
|
||||
if (!v.virtual()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.samsuik.sakura.configuration.transformation.global;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
@@ -8,6 +9,7 @@ import java.util.Map;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V1_RelocateMessages {
|
||||
private static final int VERSION = 2; // targeted version is always ahead by one
|
||||
private static final Map<NodePath, NodePath> RELOCATION = Map.of(
|
||||
@@ -17,15 +19,15 @@ public final class V1_RelocateMessages {
|
||||
|
||||
private V1_RelocateMessages() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder();
|
||||
for (Map.Entry<NodePath, NodePath> entry : RELOCATION.entrySet()) {
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
final ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder();
|
||||
for (final Map.Entry<NodePath, NodePath> entry : RELOCATION.entrySet()) {
|
||||
transformationBuilder.addAction(entry.getKey(), relocate(entry.getValue()));
|
||||
}
|
||||
builder.addVersion(VERSION, transformationBuilder.build());
|
||||
}
|
||||
|
||||
private static TransformAction relocate(NodePath path) {
|
||||
private static TransformAction relocate(final NodePath path) {
|
||||
return (node, object) -> path.array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
package me.samsuik.sakura.configuration.transformation.global;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
@NullMarked
|
||||
public final class V2_ConvertIconToMaterial implements TransformAction {
|
||||
private static final int VERSION = 3; // targeted version is always ahead by one
|
||||
private static final NodePath PATH = NodePath.path("fps", "material");
|
||||
private static final NodePath FPS_MATERIAL_PATH = NodePath.path("fps", "material");
|
||||
private static final V2_ConvertIconToMaterial INSTANCE = new V2_ConvertIconToMaterial();
|
||||
|
||||
private V2_ConvertIconToMaterial() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build());
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(FPS_MATERIAL_PATH, INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
|
||||
if (value.raw() instanceof String stringValue) {
|
||||
value.raw(stringValue.toUpperCase());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
@@ -9,15 +10,18 @@ import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V10_DurableMaterialOnlyDamagedByTnt implements TransformAction {
|
||||
private static final int VERSION = 10;
|
||||
private static final V10_DurableMaterialOnlyDamagedByTnt INSTANCE = new V10_DurableMaterialOnlyDamagedByTnt();
|
||||
private static final NodePath EXPLOSION_PATH = path("cannons", "explosion");
|
||||
private static final NodePath DURABLE_MATERIALS_PATH = EXPLOSION_PATH.plus(path("durable-materials"));
|
||||
private static final NodePath REQUIRE_TNT_PATH = EXPLOSION_PATH.plus(path("require-tnt-to-damage-durable-materials"));
|
||||
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(EXPLOSION_PATH.plus(path("durable-materials")), INSTANCE)
|
||||
.addAction(EXPLOSION_PATH.plus(path("require-tnt-to-damage-durable-materials")), TransformAction.remove())
|
||||
.addAction(DURABLE_MATERIALS_PATH, INSTANCE)
|
||||
.addAction(REQUIRE_TNT_PATH, TransformAction.remove())
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V11_RemovePhysicsVersion {
|
||||
private static final int VERSION = 11;
|
||||
private static final NodePath PATH = path("cannons", "mechanics", "physics-version");
|
||||
private static final NodePath PHYSICS_VERSION_PATH = path("cannons", "mechanics", "physics-version");
|
||||
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(PATH, TransformAction.remove())
|
||||
.build());
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(PHYSICS_VERSION_PATH, TransformAction.remove()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
@@ -10,21 +12,20 @@ import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V2_VerticalKnockbackUseDefault implements TransformAction {
|
||||
private static final int VERSION = 2;
|
||||
private static final NodePath PATH = path("players", "knockback", "knockback-vertical");
|
||||
private static final NodePath KNOCKBACK_VERTICAL_PATH = path("players", "knockback", "knockback-vertical");
|
||||
private static final V2_VerticalKnockbackUseDefault INSTANCE = new V2_VerticalKnockbackUseDefault();
|
||||
|
||||
private V2_VerticalKnockbackUseDefault() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(PATH, INSTANCE)
|
||||
.build());
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(KNOCKBACK_VERTICAL_PATH, INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
|
||||
if (value.getDouble() == 0.4) {
|
||||
value.set(DoubleOr.Default.USE_DEFAULT);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
@@ -8,6 +9,7 @@ import java.util.Map;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.*;
|
||||
|
||||
@NullMarked
|
||||
public final class V3_RenameKnockback {
|
||||
private static final int VERSION = 3;
|
||||
private static final Map<NodePath, String> RENAME = Map.of(
|
||||
@@ -17,9 +19,9 @@ public final class V3_RenameKnockback {
|
||||
|
||||
private V3_RenameKnockback() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder();
|
||||
for (Map.Entry<NodePath, String> entry : RENAME.entrySet()) {
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
final ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder();
|
||||
for (final Map.Entry<NodePath, String> entry : RENAME.entrySet()) {
|
||||
transformationBuilder.addAction(entry.getKey(), rename(entry.getValue()));
|
||||
}
|
||||
builder.addVersion(VERSION, transformationBuilder.build());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
@@ -11,24 +13,23 @@ import java.util.Locale;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V4_RenameNonStrictMergeLevel implements TransformAction {
|
||||
private static final int VERSION = 4;
|
||||
private static final String OLD_LEVEL_NAME = "NON_STRICT";
|
||||
private static final String NEW_LEVEL_NAME = "LENIENT";
|
||||
private static final NodePath PATH = path("cannons", "merge-level");
|
||||
private static final NodePath MERGE_LEVEL_PATH = path("cannons", "merge-level");
|
||||
private static final V4_RenameNonStrictMergeLevel INSTANCE = new V4_RenameNonStrictMergeLevel();
|
||||
|
||||
private V4_RenameNonStrictMergeLevel() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(PATH, INSTANCE)
|
||||
.build());
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(MERGE_LEVEL_PATH, INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
|
||||
String level = value.getString();
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
|
||||
final String level = value.getString();
|
||||
if (level != null && OLD_LEVEL_NAME.equals(level.toUpperCase(Locale.ENGLISH))) {
|
||||
value.set(NEW_LEVEL_NAME);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
@@ -11,30 +13,29 @@ import java.util.List;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V5_CombineLoadChunksOptions implements TransformAction {
|
||||
private static final int VERSION = 5;
|
||||
private static final List<String> ENTITY_PATHS = List.of("tnt", "sand");
|
||||
private static final String OLD_NAME = "loads-chunks";
|
||||
private static final String NAME = "load-chunks";
|
||||
private static final NodePath PATH = path("cannons");
|
||||
private static final NodePath CANNONS_PATH = path("cannons");
|
||||
private static final V5_CombineLoadChunksOptions INSTANCE = new V5_CombineLoadChunksOptions();
|
||||
|
||||
private V5_CombineLoadChunksOptions() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(PATH, INSTANCE)
|
||||
.build());
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(CANNONS_PATH, INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
|
||||
boolean shouldLoadChunks = false;
|
||||
|
||||
for (String entity : ENTITY_PATHS) {
|
||||
NodePath entityPath = NodePath.path(entity, OLD_NAME);
|
||||
for (final String entity : ENTITY_PATHS) {
|
||||
final NodePath entityPath = NodePath.path(entity, OLD_NAME);
|
||||
if (value.hasChild(entityPath)) {
|
||||
ConfigurationNode node = value.node(entityPath);
|
||||
final ConfigurationNode node = value.node(entityPath);
|
||||
shouldLoadChunks |= node.getBoolean();
|
||||
node.raw(null);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
@@ -9,21 +11,20 @@ import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V6_FixIncorrectExtraKnockback implements TransformAction {
|
||||
private static final int VERSION = 6;
|
||||
private static final NodePath PATH = path("players", "knockback", "sprinting", "extra-knockback");
|
||||
private static final NodePath EXTRA_KNOCKBACK_PATH = path("players", "knockback", "sprinting", "extra-knockback");
|
||||
private static final V6_FixIncorrectExtraKnockback INSTANCE = new V6_FixIncorrectExtraKnockback();
|
||||
|
||||
private V6_FixIncorrectExtraKnockback() {}
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(PATH, INSTANCE)
|
||||
.build());
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(EXTRA_KNOCKBACK_PATH, INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
|
||||
if (value.getDouble() == 1.0) {
|
||||
value.set(0.5);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
@@ -11,9 +12,7 @@ public final class V7_FixTntDuplicationName {
|
||||
private static final NodePath OLD_PATH = path("technical", "allow-t-n-t-duplication");
|
||||
private static final String NEW_NAME = "allow-tnt-duplication";
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
.addAction(OLD_PATH, rename(NEW_NAME))
|
||||
.build());
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformations.transform(OLD_PATH, rename(NEW_NAME)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
import static me.samsuik.sakura.configuration.transformation.ConfigurationTransformations.move;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
@NullMarked
|
||||
public final class V8_RenameExplosionResistantItems {
|
||||
private static final int VERSION = 8;
|
||||
private static final NodePath BASE_PATH = path("entity", "items");
|
||||
private static final NodePath OLD_WHITELIST_PATH = BASE_PATH.plus(path("use-whitelist-for-explosion-resistant-items"));
|
||||
private static final NodePath NEW_WHITELIST_PATH = BASE_PATH.plus(path("blast-resistant", "whitelist-over-blacklist"));
|
||||
private static final NodePath OLD_ITEMS_PATH = BASE_PATH.plus(path("explosion-resistant-items"));
|
||||
private static final NodePath NEW_ITEMS_PATH = BASE_PATH.plus(path("blast-resistant", "items"));
|
||||
private static final NodePath ITEMS_PATH = path("entity", "items");
|
||||
private static final NodePath OLD_WHITELIST_PATH = ITEMS_PATH.plus(path("use-whitelist-for-explosion-resistant-items"));
|
||||
private static final NodePath NEW_WHITELIST_PATH = ITEMS_PATH.plus(path("blast-resistant", "whitelist-over-blacklist"));
|
||||
private static final NodePath OLD_ITEMS_PATH = ITEMS_PATH.plus(path("explosion-resistant-items"));
|
||||
private static final NodePath NEW_ITEMS_PATH = ITEMS_PATH.plus(path("blast-resistant", "items"));
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
final ConfigurationTransformation transform = ConfigurationTransformation.builder()
|
||||
.addAction(OLD_WHITELIST_PATH, move(NEW_WHITELIST_PATH))
|
||||
.addAction(OLD_ITEMS_PATH, move(NEW_ITEMS_PATH))
|
||||
.build());
|
||||
.build();
|
||||
builder.addVersion(VERSION, transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.samsuik.sakura.configuration.transformation.world;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
@@ -7,16 +8,18 @@ import static me.samsuik.sakura.configuration.transformation.ConfigurationTransf
|
||||
import static me.samsuik.sakura.configuration.transformation.ConfigurationTransformations.newValue;
|
||||
import static org.spongepowered.configurate.NodePath.*;
|
||||
|
||||
@NullMarked
|
||||
public final class V9_RenameAllowNonTntBreakingDurableBlocks {
|
||||
private static final int VERSION = 9;
|
||||
private static final NodePath PARENT = path("cannons", "explosion");
|
||||
private static final NodePath OLD_PATH = PARENT.plus(path("allow-non-tnt-breaking-durable-blocks"));
|
||||
private static final NodePath NEW_PATH = PARENT.plus(path("require-tnt-to-damage-durable-materials"));
|
||||
private static final NodePath EXPLOSION_PATH = path("cannons", "explosion");
|
||||
private static final NodePath OLD_PATH = EXPLOSION_PATH.plus(path("allow-non-tnt-breaking-durable-blocks"));
|
||||
private static final NodePath NEW_PATH = EXPLOSION_PATH.plus(path("require-tnt-to-damage-durable-materials"));
|
||||
|
||||
public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder()
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
final ConfigurationTransformation transform = ConfigurationTransformation.builder()
|
||||
.addAction(OLD_PATH, move(NEW_PATH))
|
||||
.addAction(NEW_PATH, newValue(v -> !v.getBoolean()))
|
||||
.build());
|
||||
.build();
|
||||
builder.addVersion(VERSION, transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,28 @@ import org.jspecify.annotations.NullMarked;
|
||||
import java.util.Optional;
|
||||
|
||||
@NullMarked
|
||||
public record EntityState(Vec3 position, Vec3 momentum, AABB bb, Vec3 stuckSpeed, Optional<BlockPos> supportingPos, boolean onGround, double fallDistance) {
|
||||
public static EntityState of(Entity entity) {
|
||||
public record EntityState(
|
||||
Vec3 position,
|
||||
Vec3 momentum,
|
||||
AABB bb,
|
||||
Vec3 stuckSpeed,
|
||||
Optional<BlockPos> supportingPos,
|
||||
boolean onGround,
|
||||
double fallDistance
|
||||
) {
|
||||
public static EntityState of(final Entity entity) {
|
||||
return new EntityState(
|
||||
entity.position(), entity.getDeltaMovement(), entity.getBoundingBox(),
|
||||
entity.stuckSpeedMultiplier, entity.mainSupportingBlockPos,
|
||||
entity.onGround(), entity.fallDistance
|
||||
entity.position(),
|
||||
entity.getDeltaMovement(),
|
||||
entity.getBoundingBox(),
|
||||
entity.stuckSpeedMultiplier,
|
||||
entity.mainSupportingBlockPos,
|
||||
entity.onGround(),
|
||||
entity.fallDistance
|
||||
);
|
||||
}
|
||||
|
||||
public void apply(Entity entity) {
|
||||
public void apply(final Entity entity) {
|
||||
entity.setPos(this.position);
|
||||
entity.setDeltaMovement(this.momentum);
|
||||
entity.setBoundingBox(this.bb);
|
||||
@@ -29,11 +41,6 @@ public record EntityState(Vec3 position, Vec3 momentum, AABB bb, Vec3 stuckSpeed
|
||||
entity.fallDistance = this.fallDistance;
|
||||
}
|
||||
|
||||
public void applyEntityPosition(Entity entity) {
|
||||
entity.setPos(this.position);
|
||||
entity.setBoundingBox(this.bb);
|
||||
}
|
||||
|
||||
public boolean comparePositionAndMotion(Entity entity) {
|
||||
return entity.position().equals(this.position)
|
||||
&& entity.getDeltaMovement().equals(this.momentum);
|
||||
|
||||
@@ -16,11 +16,11 @@ public final class EntityMergeHandler {
|
||||
* @param entity the entity being merged
|
||||
* @return success
|
||||
*/
|
||||
public boolean tryMerge(@Nullable Entity entity, @Nullable Entity previous) {
|
||||
public boolean tryMerge(final @Nullable Entity entity, final @Nullable Entity previous) {
|
||||
if (entity instanceof MergeableEntity mergeEntity && previous instanceof MergeableEntity) {
|
||||
MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
|
||||
MergeStrategy strategy = MergeStrategy.from(mergeEntityData.mergeLevel);
|
||||
Entity into = strategy.mergeEntity(entity, previous, this.trackedHistory);
|
||||
final MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
|
||||
final MergeStrategy strategy = MergeStrategy.from(mergeEntityData.mergeLevel);
|
||||
final Entity into = strategy.mergeEntity(entity, previous, this.trackedHistory);
|
||||
if (into instanceof MergeableEntity intoEntity && !into.isRemoved() && mergeEntity.isSafeToMergeInto(intoEntity, strategy.trackHistory())) {
|
||||
return this.mergeEntity(mergeEntity, intoEntity);
|
||||
}
|
||||
@@ -34,10 +34,10 @@ public final class EntityMergeHandler {
|
||||
*
|
||||
* @param entity provided entity
|
||||
*/
|
||||
public void removeEntity(@Nullable Entity entity) {
|
||||
public void removeEntity(final @Nullable Entity entity) {
|
||||
if (entity instanceof MergeableEntity mergeEntity) {
|
||||
MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
|
||||
MergeStrategy strategy = MergeStrategy.from(mergeEntityData.mergeLevel);
|
||||
final MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
|
||||
final MergeStrategy strategy = MergeStrategy.from(mergeEntityData.mergeLevel);
|
||||
if (mergeEntityData.hasMerged() && strategy.trackHistory()) {
|
||||
this.trackedHistory.trackHistory(entity, mergeEntityData);
|
||||
}
|
||||
@@ -45,11 +45,13 @@ public final class EntityMergeHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every 200 ticks and the tick is used remove any unneeded merge history.
|
||||
* Remove old entries from the tracked history.
|
||||
* <p>
|
||||
* This method is called every 200 ticks.
|
||||
*
|
||||
* @param tick server tick
|
||||
*/
|
||||
public void expire(long tick) {
|
||||
public void expire(final long tick) {
|
||||
this.trackedHistory.expire(tick);
|
||||
}
|
||||
|
||||
@@ -62,13 +64,13 @@ public final class EntityMergeHandler {
|
||||
* @param into the entity to merge into
|
||||
* @return if successful
|
||||
*/
|
||||
public boolean mergeEntity(MergeableEntity mergeEntity, MergeableEntity into) {
|
||||
MergeEntityData entities = mergeEntity.getMergeEntityData();
|
||||
MergeEntityData mergeInto = into.getMergeEntityData();
|
||||
public boolean mergeEntity(final MergeableEntity mergeEntity, final MergeableEntity into) {
|
||||
final MergeEntityData entities = mergeEntity.getMergeEntityData();
|
||||
final MergeEntityData mergeInto = into.getMergeEntityData();
|
||||
mergeInto.mergeWith(entities); // merge entities together
|
||||
|
||||
// discard the entity and update the bukkit handle
|
||||
Entity nmsEntity = (Entity) mergeEntity;
|
||||
final Entity nmsEntity = (Entity) mergeEntity;
|
||||
nmsEntity.discard();
|
||||
nmsEntity.updateBukkitHandle((Entity) into);
|
||||
return true;
|
||||
|
||||
@@ -5,13 +5,13 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public interface MergeCondition {
|
||||
default MergeCondition and(MergeCondition condition) {
|
||||
default MergeCondition and(final MergeCondition condition) {
|
||||
return (e,c,t) -> this.accept(e,c,t) && condition.accept(e,c,t);
|
||||
}
|
||||
|
||||
default MergeCondition or(MergeCondition condition) {
|
||||
default MergeCondition or(final MergeCondition condition) {
|
||||
return (e,c,t) -> this.accept(e,c,t) || condition.accept(e,c,t);
|
||||
}
|
||||
|
||||
boolean accept(Entity entity, int attempts, long sinceCreation);
|
||||
boolean accept(final Entity entity, final int attempts, final long sinceCreation);
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@ public final class MergeEntityData {
|
||||
public int count = 1;
|
||||
public MergeLevel mergeLevel = MergeLevel.NONE;
|
||||
|
||||
public MergeEntityData(Entity entity) {
|
||||
public MergeEntityData(final Entity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
private void updateEntityHandles(Entity entity) {
|
||||
for (MergeEntityData entityData : this.connected) {
|
||||
private void updateEntityHandles(final Entity entity) {
|
||||
for (final MergeEntityData entityData : this.connected) {
|
||||
entityData.entity.updateBukkitHandle(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeWith(MergeEntityData mergeEntityData) {
|
||||
public void mergeWith(final MergeEntityData mergeEntityData) {
|
||||
this.connected.add(mergeEntityData);
|
||||
this.connected.addAll(mergeEntityData.connected);
|
||||
this.count += mergeEntityData.count;
|
||||
@@ -38,7 +38,7 @@ public final class MergeEntityData {
|
||||
}
|
||||
|
||||
public LongOpenHashSet getOriginPositions() {
|
||||
LongOpenHashSet positions = new LongOpenHashSet();
|
||||
final LongOpenHashSet positions = new LongOpenHashSet();
|
||||
this.connected.forEach(entityData -> positions.add(entityData.entity.getPackedOriginPosition()));
|
||||
return positions;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import org.jspecify.annotations.NullMarked;
|
||||
public interface MergeableEntity {
|
||||
MergeEntityData getMergeEntityData();
|
||||
|
||||
boolean isSafeToMergeInto(MergeableEntity entity, boolean ticksLived);
|
||||
boolean isSafeToMergeInto(final MergeableEntity entity, final boolean ticksLived);
|
||||
|
||||
default boolean tryToRespawnEntity() {
|
||||
MergeEntityData mergeData = this.getMergeEntityData();
|
||||
int originalCount = mergeData.count;
|
||||
final MergeEntityData mergeData = this.getMergeEntityData();
|
||||
final int originalCount = mergeData.count;
|
||||
if (originalCount > 1) {
|
||||
mergeData.count = 0;
|
||||
this.respawnEntity(originalCount);
|
||||
@@ -19,5 +19,5 @@ public interface MergeableEntity {
|
||||
return false;
|
||||
}
|
||||
|
||||
void respawnEntity(int count);
|
||||
void respawnEntity(final int count);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import me.samsuik.sakura.utils.TickExpiry;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.item.FallingBlockEntity;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -16,52 +15,49 @@ import org.jspecify.annotations.Nullable;
|
||||
public final class TrackedMergeHistory {
|
||||
private final Long2ObjectMap<PositionHistory> historyMap = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
public boolean hasPreviouslyMergedAndMeetsCondition(Entity entity, Entity into, MergeCondition condition) {
|
||||
public boolean hasPreviouslyMergedAndMeetsCondition(final Entity entity, final Entity into, final MergeCondition condition) {
|
||||
return this.hasPreviouslyMerged(entity, into) && this.hasMetCondition(entity, condition);
|
||||
}
|
||||
|
||||
public boolean hasPreviouslyMerged(Entity entity, Entity into) {
|
||||
PositionHistory positions = this.getHistory(into, false);
|
||||
public boolean hasPreviouslyMerged(final Entity entity, final Entity into) {
|
||||
final PositionHistory positions = this.getHistory(into, false);
|
||||
return positions != null && positions.hasPosition(entity);
|
||||
}
|
||||
|
||||
public boolean hasMetCondition(Entity entity, MergeCondition condition) {
|
||||
PositionHistory positions = this.getHistory(entity, false);
|
||||
public boolean hasMetCondition(final Entity entity, final MergeCondition condition) {
|
||||
final PositionHistory positions = this.getHistory(entity, false);
|
||||
return positions != null && positions.hasMetConditions(entity, condition);
|
||||
}
|
||||
|
||||
private boolean shouldTrackAllPositions(Entity entity, MergeEntityData mergeEntityData) {
|
||||
private boolean shouldTrackAllPositions(final Entity entity, final MergeEntityData mergeEntityData) {
|
||||
return entity instanceof FallingBlockEntity
|
||||
|| mergeEntityData.mergeLevel == MergeLevel.LENIENT
|
||||
|| entity.level().sakuraConfig().cannons.mechanics.tntSpread == TNTSpread.ALL;
|
||||
}
|
||||
|
||||
public void trackHistory(Entity entity, MergeEntityData mergeEntityData) {
|
||||
PositionHistory positions = this.getHistory(entity, true);
|
||||
LongOpenHashSet originPositions = mergeEntityData.getOriginPositions();
|
||||
long gameTime = entity.level().getGameTime();
|
||||
boolean retainHistory = positions.hasTicksPassed(gameTime, 160);
|
||||
public void trackHistory(final Entity entity, final MergeEntityData mergeEntityData) {
|
||||
final PositionHistory positionHistory = this.getHistory(entity, true);
|
||||
final LongOpenHashSet positions = mergeEntityData.getOriginPositions();
|
||||
final long gameTime = entity.level().getGameTime();
|
||||
final boolean retainHistory = positionHistory.hasTicksPassed(gameTime, 160);
|
||||
|
||||
if (!retainHistory && this.shouldTrackAllPositions(entity, mergeEntityData)) {
|
||||
originPositions.forEach(pos -> this.historyMap.put(pos, positions));
|
||||
positions.forEach(pos -> this.historyMap.put(pos, positionHistory));
|
||||
}
|
||||
positions.trackPositions(originPositions, retainHistory);
|
||||
|
||||
positionHistory.trackPositions(positions, retainHistory);
|
||||
}
|
||||
|
||||
public void expire(long gameTime) {
|
||||
this.historyMap.values().removeIf(p -> p.expiry().isExpired(gameTime));
|
||||
public void expire(final long gameTime) {
|
||||
this.historyMap.values().removeIf(history -> history.expiry().isExpired(gameTime));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Contract("_, false -> _; _, true -> !null")
|
||||
private PositionHistory getHistory(Entity entity, boolean create) {
|
||||
long originPosition = entity.getPackedOriginPosition();
|
||||
PositionHistory history = this.historyMap.get(originPosition);
|
||||
//noinspection ConstantValue
|
||||
if (create && history == null) {
|
||||
history = new PositionHistory(entity.level().getGameTime());
|
||||
this.historyMap.put(originPosition, history);
|
||||
}
|
||||
return history;
|
||||
private @Nullable PositionHistory getHistory(final Entity entity, final boolean create) {
|
||||
final long position = entity.getPackedOriginPosition();
|
||||
return this.historyMap.computeIfAbsent(position, p -> {
|
||||
return create ? new PositionHistory(entity.level().getGameTime()) : null;
|
||||
});
|
||||
}
|
||||
|
||||
private static final class PositionHistory {
|
||||
@@ -70,7 +66,7 @@ public final class TrackedMergeHistory {
|
||||
private final long created;
|
||||
private int cycles = 0;
|
||||
|
||||
public PositionHistory(long gameTime) {
|
||||
public PositionHistory(final long gameTime) {
|
||||
this.expiry = new TickExpiry(gameTime, 200);
|
||||
this.created = gameTime;
|
||||
}
|
||||
@@ -79,12 +75,12 @@ public final class TrackedMergeHistory {
|
||||
return this.expiry;
|
||||
}
|
||||
|
||||
public boolean hasPosition(Entity entity) {
|
||||
public boolean hasPosition(final Entity entity) {
|
||||
this.expiry.refresh(entity.level().getGameTime());
|
||||
return this.positions.contains(entity.getPackedOriginPosition());
|
||||
}
|
||||
|
||||
public void trackPositions(LongOpenHashSet positions, boolean retain) {
|
||||
public void trackPositions(final LongOpenHashSet positions, final boolean retain) {
|
||||
if (retain) {
|
||||
this.positions.retainAll(positions);
|
||||
} else {
|
||||
@@ -93,16 +89,16 @@ public final class TrackedMergeHistory {
|
||||
this.cycles++;
|
||||
}
|
||||
|
||||
public boolean hasMetConditions(@NotNull Entity entity, @NotNull MergeCondition condition) {
|
||||
long gameTime = entity.level().getGameTime();
|
||||
public boolean hasMetConditions(final Entity entity, final MergeCondition condition) {
|
||||
final long gameTime = entity.level().getGameTime();
|
||||
return condition.accept(entity, this.cycles, this.timeSinceCreation(gameTime));
|
||||
}
|
||||
|
||||
public boolean hasTicksPassed(long gameTime, int ticks) {
|
||||
public boolean hasTicksPassed(final long gameTime, final int ticks) {
|
||||
return this.timeSinceCreation(gameTime) > ticks;
|
||||
}
|
||||
|
||||
private long timeSinceCreation(long gameTime) {
|
||||
private long timeSinceCreation(final long gameTime) {
|
||||
return gameTime - this.created;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.samsuik.sakura.entity.merge.strategy;
|
||||
|
||||
import me.samsuik.sakura.entity.merge.TrackedMergeHistory;
|
||||
import me.samsuik.sakura.utils.collections.FixedSizeCustomObjectTable;
|
||||
import me.samsuik.sakura.utils.collections.BlockPosToEntityTable;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -9,10 +9,7 @@ import org.jspecify.annotations.Nullable;
|
||||
@NullMarked
|
||||
final class LenientStrategy implements MergeStrategy {
|
||||
static final LenientStrategy INSTANCE = new LenientStrategy();
|
||||
|
||||
private final FixedSizeCustomObjectTable<Entity> entityTable = new FixedSizeCustomObjectTable<>(512, entity -> {
|
||||
return entity.blockPosition().hashCode();
|
||||
});
|
||||
private final BlockPosToEntityTable entityTable = new BlockPosToEntityTable(512);
|
||||
|
||||
@Override
|
||||
public boolean trackHistory() {
|
||||
@@ -20,8 +17,7 @@ final class LenientStrategy implements MergeStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) {
|
||||
public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) {
|
||||
if (entity.compareState(previous)) {
|
||||
return previous;
|
||||
}
|
||||
@@ -30,7 +26,7 @@ final class LenientStrategy implements MergeStrategy {
|
||||
this.entityTable.clear();
|
||||
}
|
||||
|
||||
final Entity nextEntity = this.entityTable.getAndWrite(entity);
|
||||
final Entity nextEntity = this.entityTable.put(entity);
|
||||
if (nextEntity == null || entity == nextEntity || !nextEntity.level().equals(entity.level())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ public interface MergeStrategy {
|
||||
* @param previous last entity to tick
|
||||
* @return success
|
||||
*/
|
||||
@Nullable
|
||||
Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory);
|
||||
@Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory);
|
||||
|
||||
/**
|
||||
* Gets the {@link MergeStrategy} for the {@link MergeLevel}.
|
||||
@@ -36,7 +35,7 @@ public interface MergeStrategy {
|
||||
* @param level provided level
|
||||
* @return strategy
|
||||
*/
|
||||
static MergeStrategy from(MergeLevel level) {
|
||||
static MergeStrategy from(final MergeLevel level) {
|
||||
return switch (level) {
|
||||
case NONE -> NoneStrategy.INSTANCE;
|
||||
case STRICT -> StrictStrategy.INSTANCE;
|
||||
|
||||
@@ -15,8 +15,7 @@ final class NoneStrategy implements MergeStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) {
|
||||
public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ final class SpawnStrategy implements MergeStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) {
|
||||
public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) {
|
||||
final Entity mergeInto;
|
||||
if (entity.tickCount == 1 && mergeHistory.hasPreviouslyMergedAndMeetsCondition(entity, previous, CONDITION)) {
|
||||
mergeInto = previous;
|
||||
|
||||
@@ -15,8 +15,7 @@ final class StrictStrategy implements MergeStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) {
|
||||
public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) {
|
||||
return entity.compareState(previous) ? previous : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package me.samsuik.sakura.explosion;
|
||||
|
||||
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sort explosion rays for better cache utilisation.
|
||||
* <pre>
|
||||
* x + Vanilla Sorted
|
||||
* z @ z 8 5
|
||||
* - x 6 7 6 4
|
||||
* 4 @ 5 7 @ 3
|
||||
* 2 3 8 2
|
||||
* 1 1
|
||||
* </pre>
|
||||
*/
|
||||
@NullMarked
|
||||
public final class SortExplosionRays {
|
||||
private static final Comparator<double[]> EXPLOSION_RAY_COMPARATOR = Comparator.comparingDouble(vec -> {
|
||||
final double sign = Math.signum(vec[0]);
|
||||
final double dir = (sign - 1) / 2;
|
||||
return sign + 8 + vec[2] * dir;
|
||||
});
|
||||
|
||||
public static double[] sortExplosionRays(final DoubleArrayList rayCoords) {
|
||||
final List<double[]> explosionRays = new ArrayList<>();
|
||||
for (int index = 0; index < rayCoords.size(); index += 3) {
|
||||
final double[] vector = new double[3];
|
||||
rayCoords.getElements(index, vector, 0, 3);
|
||||
explosionRays.add(vector);
|
||||
}
|
||||
|
||||
rayCoords.clear();
|
||||
explosionRays.sort(EXPLOSION_RAY_COMPARATOR);
|
||||
|
||||
final double[] rays = new double[explosionRays.size() * 3];
|
||||
for (int i = 0; i < explosionRays.size() * 3; i++) {
|
||||
rays[i] = explosionRays.get(i / 3)[i % 3];
|
||||
}
|
||||
return rays;
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,13 @@ import net.minecraft.world.level.ServerExplosion;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@NullMarked
|
||||
public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplosion {
|
||||
private static final double ENTITY_DISPATCH_DISTANCE = Math.pow(32.0, 2.0);
|
||||
|
||||
@@ -28,7 +30,17 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
protected final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
|
||||
private final Consumer<SpecialisedExplosion<T>> applyEffects;
|
||||
|
||||
public SpecialisedExplosion(ServerLevel level, T entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 center, float power, boolean createFire, BlockInteraction destructionType, Consumer<SpecialisedExplosion<T>> applyEffects) {
|
||||
public SpecialisedExplosion(
|
||||
final ServerLevel level,
|
||||
final T entity,
|
||||
final @Nullable DamageSource damageSource,
|
||||
final @Nullable ExplosionDamageCalculator behavior,
|
||||
final Vec3 center,
|
||||
final float power,
|
||||
final boolean createFire,
|
||||
final BlockInteraction destructionType,
|
||||
final Consumer<SpecialisedExplosion<T>> applyEffects
|
||||
) {
|
||||
super(level, entity, damageSource, behavior, center, power, createFire, destructionType);
|
||||
this.cause = entity;
|
||||
this.impactPosition = center;
|
||||
@@ -39,18 +51,16 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
return (double) this.cause.getBbHeight() * 0.0625D;
|
||||
}
|
||||
|
||||
protected abstract int getExplosionCount();
|
||||
|
||||
protected abstract void startExplosion();
|
||||
protected abstract void beginExplosion();
|
||||
|
||||
@Override
|
||||
public final void explode() {
|
||||
this.createBlockCache();
|
||||
this.startExplosion(); // search for blocks, impact entities, finalise if necessary
|
||||
this.beginExplosion(); // search for blocks, impact entities, finalise if necessary
|
||||
this.clearBlockCache();
|
||||
}
|
||||
|
||||
protected final boolean requiresImpactEntities(List<BlockPos> blocks, Vec3 center) {
|
||||
protected final boolean requiresImpactEntities(final List<BlockPos> blocks, final Vec3 center) {
|
||||
if (this.impactPosition.distanceToSqr(center) > ENTITY_DISPATCH_DISTANCE) {
|
||||
this.impactPosition = center;
|
||||
return true;
|
||||
@@ -58,9 +68,9 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
return !blocks.isEmpty();
|
||||
}
|
||||
|
||||
protected final boolean finalizeExplosionAndParticles(List<BlockPos> blocks) {
|
||||
protected final boolean finalizeExplosionAndParticles(final List<BlockPos> blocks) {
|
||||
this.wasCanceled = false;
|
||||
List<BlockPos> explodedPositions = new ObjectArrayList<>(blocks);
|
||||
final List<BlockPos> explodedPositions = new ObjectArrayList<>(blocks);
|
||||
this.interactWithBlocks(explodedPositions);
|
||||
|
||||
if (!this.wasCanceled) {
|
||||
@@ -71,11 +81,9 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
return !explodedPositions.isEmpty() && !this.wasCanceled;
|
||||
}
|
||||
|
||||
protected void postExplosion(List<BlockPos> foundBlocks, boolean destroyedBlocks) {
|
||||
// optimisation: Keep the block cache across explosions and invalidate any found blocks.
|
||||
// This can help reduce block retrievals while block searching when there's a durable block,
|
||||
// and when ray tracing for obstructions. This is disabled by default because plugins can
|
||||
// change blocks in the explosion event.
|
||||
protected void postExplosion(final List<BlockPos> foundBlocks, final boolean destroyedBlocks) {
|
||||
// Reuse the block cache between explosions. This can help a lot when searching for blocks and raytracing.
|
||||
// This is disabled by default as it's incompatible with plugins that modify blocks in the explosion event.
|
||||
if (this.level().sakuraConfig().cannons.explosion.useBlockCacheAcrossExplosions && !foundBlocks.isEmpty() && !destroyedBlocks) {
|
||||
this.markBlocksInCacheAsExplodable(foundBlocks);
|
||||
} else {
|
||||
@@ -86,61 +94,77 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
}
|
||||
|
||||
protected final void recalculateExplosionPosition() {
|
||||
this.recalculateExplosionPosition(this.cause);
|
||||
}
|
||||
|
||||
protected final void recalculateExplosionPosition(T entity) {
|
||||
double x = entity.getX();
|
||||
double y = entity.getY() + this.getExplosionOffset();
|
||||
double z = entity.getZ();
|
||||
final double x = this.cause.getX();
|
||||
final double y = this.cause.getY() + this.getExplosionOffset();
|
||||
final double z = this.cause.getZ();
|
||||
this.center = new Vec3(x, y, z);
|
||||
}
|
||||
|
||||
protected final void forEachEntitySliceInBounds(AABB bb, Consumer<Entity[]> sliceConsumer) {
|
||||
int minSection = WorldUtil.getMinSection(this.level());
|
||||
int maxSection = WorldUtil.getMaxSection(this.level());
|
||||
protected final void locateAndImpactEntitiesInBounds(final AABB bounds, final List<Vec3> explosions) {
|
||||
final double radius = this.radius() * 2.0f;
|
||||
final double change = Math.max(bounds.getXsize(), Math.max(bounds.getYsize(), bounds.getZsize()));
|
||||
final double maxDistanceSqr = Math.pow(radius + change, 2.0);
|
||||
final boolean positionChanged = change != 0.0;
|
||||
|
||||
int minChunkX = Mth.floor(bb.minX) >> 4;
|
||||
int minChunkY = Mth.clamp(Mth.floor(bb.minY) >> 4, minSection, maxSection);
|
||||
int minChunkZ = Mth.floor(bb.minZ) >> 4;
|
||||
int maxChunkX = Mth.floor(bb.maxX) >> 4;
|
||||
int maxChunkY = Mth.clamp(Mth.floor(bb.maxY) >> 4, minSection, maxSection);
|
||||
int maxChunkZ = Mth.floor(bb.maxZ) >> 4;
|
||||
this.forEachEntitySliceInBounds(bounds.inflate(radius), entities -> {
|
||||
if (positionChanged) {
|
||||
this.impactEntitiesMoving(entities, explosions, bounds.getCenter(), radius, maxDistanceSqr);
|
||||
} else {
|
||||
this.impactEntitiesFromPosition(entities, explosions.getFirst(), explosions.size(), radius);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
EntityLookup entityLookup = this.level().moonrise$getEntityLookup();
|
||||
for (int chunkX = minChunkX; chunkX <= maxChunkX; ++chunkX) {
|
||||
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; ++chunkZ) {
|
||||
ChunkEntitySlices chunk = entityLookup.getChunk(chunkX, chunkZ);
|
||||
protected final void impactEntitiesMoving(
|
||||
final @Nullable Entity[] entities,
|
||||
final List<Vec3> explosions,
|
||||
final Vec3 center,
|
||||
final double radius,
|
||||
final double maxDistanceSqr
|
||||
) {
|
||||
for (int index = 0; index < entities.length; ++index) {
|
||||
final Entity entity = entities[index];
|
||||
if (entity == null) break; // end of the entity slice
|
||||
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
// Check if the entity is in range
|
||||
if (entity.distanceToSqr(center.x, center.y, center.z) > maxDistanceSqr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) {
|
||||
sliceConsumer.accept(chunk.getSectionEntities(chunkY));
|
||||
}
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int count = 0; count < explosions.size(); count++) {
|
||||
this.impactEntity(entity, explosions.get(count), 1, radius);
|
||||
}
|
||||
|
||||
// Entities can be removed from the world mid-explosion.
|
||||
if (entities[index] != entity) {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void impactEntitiesFromPosition(Entity[] entities, Vec3 position, int potential, double radius) {
|
||||
for (int i = 0; i < entities.length; ++i) {
|
||||
Entity entity = entities[i];
|
||||
if (entity == null) break;
|
||||
protected final void impactEntitiesFromPosition(
|
||||
final @Nullable Entity[] entities,
|
||||
final Vec3 position,
|
||||
final int potential,
|
||||
final double radius
|
||||
) {
|
||||
for (int index = 0; index < entities.length; ++index) {
|
||||
final Entity entity = entities[index];
|
||||
if (entity == null) break; // end of the entity slice
|
||||
|
||||
if (entity != this.source && !entity.ignoreExplosion(this)) {
|
||||
this.impactEntity(entity, position, potential, radius);
|
||||
}
|
||||
this.impactEntity(entity, position, potential, radius);
|
||||
|
||||
if (entities[i] != entity) {
|
||||
i--;
|
||||
// Entities can be removed from the world mid-explosion.
|
||||
if (entities[index] != entity) {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void impactEntity(Entity entity, Vec3 pos, int potential, double radius) {
|
||||
if (this.excludeSourceFromDamage && entity == this.source) {
|
||||
return; // for paper api
|
||||
protected final void impactEntity(final Entity entity, final Vec3 pos, final int potential, final double radius) {
|
||||
if (this.excludeSourceFromDamage && this.source == entity || entity.ignoreExplosion(this)) {
|
||||
return; // Make sure the entity can be affected by explosions.
|
||||
}
|
||||
if (entity.isPrimedTNT || entity.isFallingBlock) {
|
||||
this.impactCannonEntity(entity, pos, potential, radius);
|
||||
@@ -151,27 +175,25 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
}
|
||||
}
|
||||
|
||||
protected final void impactCannonEntity(Entity entity, Vec3 pos, int potential, double radius) {
|
||||
protected final void impactCannonEntity(final Entity entity, final Vec3 pos, final int potential, final double radius) {
|
||||
double distanceFromBottom = Math.sqrt(entity.distanceToSqr(pos)) / radius;
|
||||
|
||||
if (distanceFromBottom <= 1.0) {
|
||||
double x = entity.getX() - pos.x;
|
||||
double y = entity.getEyeY() - pos.y; // Sakura - configure cannon physics
|
||||
double y = entity.getEyeY() - pos.y;
|
||||
double z = entity.getZ() - pos.z;
|
||||
double distance = Math.sqrt(x * x + y * y + z * z);
|
||||
// Sakura start - configure cannon physics
|
||||
|
||||
if (this.mechanicsTarget.before(MechanicVersion.v1_17)) {
|
||||
distanceFromBottom = (float) distanceFromBottom;
|
||||
distance = (float) distance;
|
||||
}
|
||||
// Sakura end - configure cannon physics
|
||||
|
||||
if (distance != 0.0D) {
|
||||
x /= distance;
|
||||
y /= distance;
|
||||
z /= distance;
|
||||
double density = this.getBlockDensity(pos, entity); // Paper - Optimize explosions
|
||||
double exposure = (1.0D - distanceFromBottom) * density;
|
||||
final double density = this.getBlockDensity(pos, entity); // Paper - Optimize explosions
|
||||
final double exposure = (1.0D - distanceFromBottom) * density;
|
||||
|
||||
if (exposure == 0.0) {
|
||||
return;
|
||||
@@ -186,14 +208,13 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
}
|
||||
}
|
||||
|
||||
protected final void applyEntityVelocity(Entity entity, double x, double y, double z, int potential) {
|
||||
Vec3 movement = entity.getDeltaMovement();
|
||||
|
||||
protected final void applyEntityVelocity(final Entity entity, final double x, final double y, final double z, final int potential) {
|
||||
final Vec3 movement = entity.getDeltaMovement();
|
||||
double moveX = movement.x();
|
||||
double moveY = movement.y();
|
||||
double moveZ = movement.z();
|
||||
|
||||
for (int i = 0; i < potential; ++i) {
|
||||
for (int count = 0; count < potential; ++count) {
|
||||
moveX += x;
|
||||
moveY += y;
|
||||
moveZ += z;
|
||||
@@ -202,4 +223,31 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
|
||||
entity.setDeltaMovement(moveX, moveY, moveZ);
|
||||
entity.hasImpulse = true;
|
||||
}
|
||||
|
||||
protected final void forEachEntitySliceInBounds(final AABB bb, final Consumer<Entity[]> sliceConsumer) {
|
||||
final int minSection = WorldUtil.getMinSection(this.level());
|
||||
final int maxSection = WorldUtil.getMaxSection(this.level());
|
||||
|
||||
final int minChunkX = Mth.floor(bb.minX) >> 4;
|
||||
final int minChunkY = Mth.clamp(Mth.floor(bb.minY) >> 4, minSection, maxSection);
|
||||
final int minChunkZ = Mth.floor(bb.minZ) >> 4;
|
||||
final int maxChunkX = Mth.floor(bb.maxX) >> 4;
|
||||
final int maxChunkY = Mth.clamp(Mth.floor(bb.maxY) >> 4, minSection, maxSection);
|
||||
final int maxChunkZ = Mth.floor(bb.maxZ) >> 4;
|
||||
|
||||
final EntityLookup entityLookup = this.level().moonrise$getEntityLookup();
|
||||
for (int chunkX = minChunkX; chunkX <= maxChunkX; ++chunkX) {
|
||||
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; ++chunkZ) {
|
||||
final ChunkEntitySlices chunk = entityLookup.getChunk(chunkX, chunkZ);
|
||||
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) {
|
||||
sliceConsumer.accept(chunk.getSectionEntities(chunkY));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ import net.minecraft.world.level.ExplosionDamageCalculator;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@NullMarked
|
||||
public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
private static final int ALL_DIRECTIONS = 0b111;
|
||||
private static final int FOUND_ALL_BLOCKS = ALL_DIRECTIONS + 12;
|
||||
@@ -30,21 +32,56 @@ public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
private int swinging = 0;
|
||||
private boolean moved = false;
|
||||
|
||||
public TntExplosion(ServerLevel level, PrimedTnt tnt, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 center, float power, boolean createFire, BlockInteraction destructionType, Consumer<SpecialisedExplosion<PrimedTnt>> applyEffects) {
|
||||
public TntExplosion(
|
||||
final ServerLevel level,
|
||||
final PrimedTnt tnt,
|
||||
final @Nullable DamageSource damageSource,
|
||||
final @Nullable ExplosionDamageCalculator behavior,
|
||||
final Vec3 center,
|
||||
final float power,
|
||||
final boolean createFire,
|
||||
final BlockInteraction destructionType,
|
||||
final Consumer<SpecialisedExplosion<PrimedTnt>> applyEffects
|
||||
) {
|
||||
super(level, tnt, damageSource, behavior, center, power, createFire, destructionType, applyEffects);
|
||||
this.originalPosition = center;
|
||||
this.bounds = new AABB(center, center);
|
||||
}
|
||||
|
||||
// Sakura start - configure cannon physics
|
||||
@Override
|
||||
protected double getExplosionOffset() {
|
||||
return this.mechanicsTarget.before(MechanicVersion.v1_10) ? (double) 0.49f : super.getExplosionOffset();
|
||||
return this.mechanicsTarget.before(MechanicVersion.v1_10)
|
||||
? (double) 0.49f
|
||||
: super.getExplosionOffset();
|
||||
}
|
||||
// Sakura end - configure cannon physics
|
||||
|
||||
@Override
|
||||
protected int getExplosionCount() {
|
||||
private void mergeEntitiesBeforeExploding() {
|
||||
final IteratorSafeOrderedReferenceSet<Entity> entities = this.level().entityTickList.entities;
|
||||
int index = entities.indexOf(this.cause);
|
||||
|
||||
entities.createRawIterator();
|
||||
// iterate over the entityTickList to find entities that are exploding in the same position.
|
||||
while ((index = entities.advanceRawIterator(index)) != Integer.MAX_VALUE) {
|
||||
final Entity foundEntity = entities.rawGet(index);
|
||||
|
||||
// Make sure the found entity is alive and it's a mergeable entity.
|
||||
if (!(foundEntity instanceof MergeableEntity mergeEntity) || foundEntity.isRemoved()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the found entity is the same type and has the same state as the explosion source.
|
||||
if (!foundEntity.compareState(this.cause) || !mergeEntity.isSafeToMergeInto(this.cause, true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Merge the found entity into the explosion source
|
||||
this.level().mergeHandler.mergeEntity(mergeEntity, this.cause);
|
||||
}
|
||||
entities.finishRawIterator();
|
||||
}
|
||||
|
||||
private int mergeAndGetExplosionPotential() {
|
||||
// Try to merge entities before exploding
|
||||
if (this.cause.getMergeEntityData().mergeLevel == MergeLevel.NONE) {
|
||||
this.mergeEntitiesBeforeExploding();
|
||||
}
|
||||
@@ -52,34 +89,31 @@ public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startExplosion() {
|
||||
for (int i = this.getExplosionCount() - 1; i >= 0; --i) {
|
||||
boolean lastCycle = i == 0;
|
||||
List<BlockPos> toBlow = this.midExplosion(lastCycle); // search for blocks and impact entities
|
||||
boolean destroyedBlocks = this.finalizeExplosionAndParticles(toBlow); // call events, break blocks and send particles
|
||||
protected void beginExplosion() {
|
||||
for (int remaining = this.mergeAndGetExplosionPotential() - 1; remaining >= 0; --remaining) {
|
||||
final boolean lastCycle = remaining == 0;
|
||||
final List<BlockPos> toBlow = this.midExplosion(lastCycle); // search for blocks and impact entities
|
||||
final boolean destroyedBlocks = this.finalizeExplosionAndParticles(toBlow); // call events, break blocks and send particles
|
||||
|
||||
if (!lastCycle) {
|
||||
EntityState entityState = this.nextSourceVelocity();
|
||||
final EntityState entityState = this.nextSourceVelocity();
|
||||
this.postExplosion(toBlow, destroyedBlocks);
|
||||
this.updateExplosionPosition(entityState, destroyedBlocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<BlockPos> midExplosion(boolean lastCycle) {
|
||||
final List<BlockPos> explodedPositions;
|
||||
if (this.swinging < FOUND_ALL_BLOCKS) {
|
||||
explodedPositions = this.calculateExplodedPositions();
|
||||
} else {
|
||||
explodedPositions = List.of();
|
||||
}
|
||||
private List<BlockPos> midExplosion(final boolean lastCycle) {
|
||||
final List<BlockPos> explodedPositions = this.swinging < FOUND_ALL_BLOCKS
|
||||
? this.calculateExplodedPositions()
|
||||
: List.of();
|
||||
|
||||
Vec3 center = this.center;
|
||||
final Vec3 center = this.center;
|
||||
this.bounds = this.bounds.expand(center);
|
||||
this.explosions.add(center);
|
||||
|
||||
if (lastCycle || this.requiresImpactEntities(explodedPositions, center)) {
|
||||
this.locateAndImpactEntitiesInBounds();
|
||||
this.locateAndImpactEntitiesInBounds(this.bounds, this.explosions);
|
||||
this.bounds = new AABB(center, center);
|
||||
this.explosions.clear();
|
||||
}
|
||||
@@ -88,7 +122,7 @@ public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postExplosion(List<BlockPos> foundBlocks, boolean destroyedBlocks) {
|
||||
protected void postExplosion(final List<BlockPos> foundBlocks, final boolean destroyedBlocks) {
|
||||
super.postExplosion(foundBlocks, destroyedBlocks);
|
||||
if (this.swinging >= ALL_DIRECTIONS) {
|
||||
// Increment "swinging" if no blocks have been found, and it has swung in every direction.
|
||||
@@ -101,10 +135,10 @@ public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSwingingState(Vec3 momentum, Vec3 previousMomentum) {
|
||||
for (Direction.Axis axis : Direction.Axis.VALUES) {
|
||||
double current = momentum.get(axis);
|
||||
double previous = previousMomentum.get(axis);
|
||||
private void updateSwingingState(final Vec3 momentum, final Vec3 previousMomentum) {
|
||||
for (final Direction.Axis axis : Direction.Axis.VALUES) {
|
||||
final double current = momentum.get(axis);
|
||||
final double previous = previousMomentum.get(axis);
|
||||
if (current == previous || current * previous <= 0.0) {
|
||||
this.swinging |= 1 << axis.ordinal();
|
||||
}
|
||||
@@ -116,25 +150,26 @@ public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
}
|
||||
|
||||
private EntityState nextSourceVelocity() {
|
||||
Vec3 origin = this.getCauseOrigin(); // valid position to use while creating a temporary entity
|
||||
PrimedTnt tnt = new PrimedTnt(this.level(), origin.x(), origin.y(), origin.z(), null);
|
||||
final Vec3 origin = this.getCauseOrigin(); // valid position to use while creating a temporary entity
|
||||
final PrimedTnt tnt = new PrimedTnt(this.level(), origin.x(), origin.y(), origin.z(), null);
|
||||
this.cause.entityState().apply(tnt);
|
||||
this.impactCannonEntity(tnt, this.center, 1, this.radius() * 2.0f);
|
||||
return EntityState.of(tnt);
|
||||
}
|
||||
|
||||
private void updateExplosionPosition(EntityState entityState, boolean destroyedBlocks) {
|
||||
private void updateExplosionPosition(final EntityState entityState, final boolean destroyedBlocks) {
|
||||
// Before setting entity state, otherwise we might cause issues.
|
||||
Vec3 entityMomentum = this.cause.entityState().momentum();
|
||||
final Vec3 entityMomentum = this.cause.entityState().momentum();
|
||||
final boolean hasMoved;
|
||||
if (this.moved) {
|
||||
hasMoved = true;
|
||||
} else if (this.center.equals(this.cause.position())) {
|
||||
hasMoved = false;
|
||||
} else {
|
||||
double newMomentum = entityState.momentum().lengthSqr();
|
||||
double oldMomentum = entityMomentum.lengthSqr();
|
||||
hasMoved = oldMomentum <= Math.pow(this.radius() * 2.0 + 1.0, 2.0) || newMomentum <= oldMomentum;
|
||||
final double newMomentumSqr = entityState.momentum().lengthSqr();
|
||||
final double oldMomentumSqr = entityMomentum.lengthSqr();
|
||||
final double maxExplosionRadiusSqr = Math.pow(this.radius() * 2.0 + 1.0, 2.0);
|
||||
hasMoved = oldMomentumSqr <= maxExplosionRadiusSqr || newMomentumSqr <= oldMomentumSqr;
|
||||
}
|
||||
|
||||
// Keep track of entity state
|
||||
@@ -156,59 +191,4 @@ public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
|
||||
this.swinging = ALL_DIRECTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeEntitiesBeforeExploding() {
|
||||
IteratorSafeOrderedReferenceSet<Entity> entities = this.level().entityTickList.entities;
|
||||
int index = entities.indexOf(this.cause);
|
||||
|
||||
entities.createRawIterator();
|
||||
// iterate over the entityTickList to find entities that are exploding in the same position.
|
||||
while ((index = entities.advanceRawIterator(index)) != Integer.MAX_VALUE) {
|
||||
Entity foundEntity = entities.rawGet(index);
|
||||
if (!(foundEntity instanceof MergeableEntity mergeEntity) || foundEntity.isRemoved() || !foundEntity.compareState(this.cause) || !mergeEntity.isSafeToMergeInto(this.cause, true))
|
||||
break;
|
||||
this.level().mergeHandler.mergeEntity(mergeEntity, this.cause);
|
||||
}
|
||||
entities.finishRawIterator();
|
||||
}
|
||||
|
||||
private void locateAndImpactEntitiesInBounds() {
|
||||
double radius = this.radius() * 2.0f;
|
||||
AABB bb = this.bounds;
|
||||
|
||||
Vec3 center = bb.getCenter();
|
||||
double change = Math.max(bb.getXsize(), Math.max(bb.getYsize(), bb.getZsize()));
|
||||
double maxDistanceSqr = Math.pow(radius + change, 2.0);
|
||||
boolean positionChanged = change != 0.0;
|
||||
|
||||
this.forEachEntitySliceInBounds(bb.inflate(radius), entities -> {
|
||||
if (positionChanged) {
|
||||
this.impactEntitiesSwinging(entities, center, radius, maxDistanceSqr);
|
||||
} else {
|
||||
this.impactEntitiesFromPosition(entities, this.explosions.getFirst(), this.explosions.size(), radius);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void impactEntitiesSwinging(Entity[] entities, Vec3 center, double radius, double maxDistanceSqr) {
|
||||
for (int i = 0; i < entities.length; ++i) {
|
||||
Entity entity = entities[i];
|
||||
if (entity == null) break;
|
||||
|
||||
if (entity != this.source && !entity.ignoreExplosion(this) && entity.distanceToSqr(center.x, center.y, center.z) <= maxDistanceSqr) {
|
||||
this.impactEntitySwinging(entity, radius);
|
||||
}
|
||||
|
||||
if (entities[i] != entity) {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void impactEntitySwinging(Entity entity, double radius) {
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0; i < this.explosions.size(); i++) {
|
||||
this.impactEntity(entity, this.explosions.get(i), 1, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,15 +28,15 @@ public final class DensityData {
|
||||
}
|
||||
|
||||
public boolean hasPosition(Vec3 explosion, AABB entity) {
|
||||
return this.isExplosionPosition(explosion) && this.entity.isAABBInBounds(entity);
|
||||
return this.isExplosionPosition(explosion) && this.entity.containsInclusive(entity);
|
||||
}
|
||||
|
||||
public boolean isKnownPosition(Vec3 point) {
|
||||
return this.entity.isVec3InBounds(point);
|
||||
return this.entity.containsInclusive(point);
|
||||
}
|
||||
|
||||
public boolean isExplosionPosition(Vec3 explosion) {
|
||||
return this.source.isVec3InBounds(explosion);
|
||||
return this.source.containsInclusive(explosion);
|
||||
}
|
||||
|
||||
public void expand(Vec3 explosion, Entity entity) {
|
||||
|
||||
@@ -11,18 +11,18 @@ import java.util.concurrent.TimeUnit;
|
||||
public final class DurableBlockManager {
|
||||
private final Cache<BlockPos, DurableBlock> durableBlocks = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(1, TimeUnit.MINUTES)
|
||||
.maximumSize(65534)
|
||||
.maximumSize(Short.MAX_VALUE)
|
||||
.build();
|
||||
|
||||
public boolean damage(BlockPos pos, DurableMaterial material) {
|
||||
DurableBlock block = this.durableBlocks.getIfPresent(pos);
|
||||
public boolean damage(final BlockPos blockPos, final DurableMaterial material) {
|
||||
DurableBlock block = this.durableBlocks.getIfPresent(blockPos);
|
||||
if (block == null) {
|
||||
this.durableBlocks.put(pos, block = new DurableBlock(material.durability()));
|
||||
this.durableBlocks.put(blockPos, block = new DurableBlock(material.durability()));
|
||||
}
|
||||
return block.damage();
|
||||
}
|
||||
|
||||
public int durability(BlockPos pos, DurableMaterial material) {
|
||||
public int durability(final BlockPos pos, final DurableMaterial material) {
|
||||
final DurableBlock block = this.durableBlocks.getIfPresent(pos);
|
||||
return block != null ? block.durability() : material.durability();
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public final class DurableBlockManager {
|
||||
private static final class DurableBlock {
|
||||
private int durability;
|
||||
|
||||
public DurableBlock(int durability) {
|
||||
public DurableBlock(final int durability) {
|
||||
this.durability = durability;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,69 +21,68 @@ public final class BlockChangeTracker {
|
||||
private final Level level;
|
||||
private long identifier = Long.MIN_VALUE;
|
||||
|
||||
public BlockChangeTracker(Level level) {
|
||||
public BlockChangeTracker(final Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public long listenForChangesOnce(BlockChangeFilter filter, Set<BlockPos> positions, Runnable callback) {
|
||||
LongConsumer singleUseCallback = (identifier) -> {
|
||||
public long listenForChangesOnce(final BlockChangeFilter filter, final Set<BlockPos> positions, final Runnable callback) {
|
||||
final LongConsumer singleUseCallback = (identifier) -> {
|
||||
callback.run();
|
||||
this.stopListening(identifier);
|
||||
};
|
||||
return this.listenForChanges(filter, positions, singleUseCallback);
|
||||
}
|
||||
|
||||
public long listenForChanges(BlockChangeFilter filter, Set<BlockPos> positions, LongConsumer callback) {
|
||||
long identifier = this.identifier++;
|
||||
Listener listener = new Listener(
|
||||
filter, positions, identifier, callback
|
||||
);
|
||||
for (ChunkPos chunkPos : getChunkPositions(positions)) {
|
||||
public long listenForChanges(final BlockChangeFilter filter, final Set<BlockPos> positions, final LongConsumer callback) {
|
||||
final long identifier = this.identifier++;
|
||||
final Listener listener = new Listener(filter, positions, identifier, callback);
|
||||
for (final ChunkPos chunkPos : getChunkPositions(positions)) {
|
||||
this.addListenerToChunk(chunkPos, listener);
|
||||
}
|
||||
this.identifiersInUse.put(identifier, listener);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void stopListening(long identifier) {
|
||||
Listener listener = this.identifiersInUse.remove(identifier);
|
||||
public void stopListening(final long identifier) {
|
||||
final Listener listener = this.identifiersInUse.remove(identifier);
|
||||
//noinspection ConstantValue
|
||||
if (listener != null) {
|
||||
for (ChunkPos chunkPos : getChunkPositions(listener.positions())) {
|
||||
for (final ChunkPos chunkPos : getChunkPositions(listener.positions())) {
|
||||
this.removeListenerFronChunk(chunkPos, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeListenerFronChunk(ChunkPos chunkPos, Listener listener) {
|
||||
long chunkKey = chunkPos.toLong();
|
||||
List<Listener> listeners = this.chunkListeners.computeIfPresent(chunkKey, (k, present) -> {
|
||||
private void removeListenerFronChunk(final ChunkPos chunkPos, final Listener listener) {
|
||||
final long chunkKey = chunkPos.toLong();
|
||||
final List<Listener> listeners = this.chunkListeners.computeIfPresent(chunkKey, (k, present) -> {
|
||||
present.remove(listener);
|
||||
return present.isEmpty() ? null : present;
|
||||
});
|
||||
this.updateListeners(chunkPos, Objects.requireNonNullElse(listeners, Collections.emptyList()));
|
||||
}
|
||||
|
||||
private void addListenerToChunk(ChunkPos chunkPos, Listener listener) {
|
||||
long chunkKey = chunkPos.toLong();
|
||||
List<Listener> listeners = this.chunkListeners.computeIfAbsent(chunkKey, i -> new ArrayList<>());
|
||||
private void addListenerToChunk(final ChunkPos chunkPos, final Listener listener) {
|
||||
final long chunkKey = chunkPos.toLong();
|
||||
final List<Listener> listeners = this.chunkListeners.computeIfAbsent(chunkKey, i -> new ArrayList<>());
|
||||
listeners.add(listener);
|
||||
this.updateListeners(chunkPos, listeners);
|
||||
}
|
||||
|
||||
private void updateListeners(ChunkPos chunkPos, List<Listener> listeners) {
|
||||
LevelChunk chunk = ((ServerLevel) this.level).chunkSource.getChunkAtIfLoadedImmediately(chunkPos.x, chunkPos.z);
|
||||
private void updateListeners(final ChunkPos chunkPos, final List<Listener> listeners) {
|
||||
final LevelChunk chunk = ((ServerLevel) this.level).chunkSource.getChunkAtIfLoadedImmediately(chunkPos.x, chunkPos.z);
|
||||
if (chunk != null) {
|
||||
chunk.updateBlockChangeListeners(List.copyOf(listeners));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Listener> getListenersForChunk(ChunkPos chunkPos) {
|
||||
public List<Listener> getListenersForChunk(final ChunkPos chunkPos) {
|
||||
return List.copyOf(this.chunkListeners.getOrDefault(chunkPos.toLong(), Collections.emptyList()));
|
||||
}
|
||||
|
||||
private static Set<ChunkPos> getChunkPositions(Set<BlockPos> positions) {
|
||||
Set<ChunkPos> chunkPositions = new ObjectOpenHashSet<>();
|
||||
for (BlockPos pos : positions) {
|
||||
private static Set<ChunkPos> getChunkPositions(final Set<BlockPos> positions) {
|
||||
final Set<ChunkPos> chunkPositions = new ObjectOpenHashSet<>();
|
||||
for (final BlockPos pos : positions) {
|
||||
chunkPositions.add(new ChunkPos(pos));
|
||||
}
|
||||
return chunkPositions;
|
||||
@@ -97,16 +96,15 @@ public final class BlockChangeTracker {
|
||||
|| newBlock.isSignalSource() != oldBlock.isSignalSource();
|
||||
};
|
||||
|
||||
boolean test(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock);
|
||||
boolean test(final Level level, final BlockPos pos, final BlockState newBlock, final BlockState oldBlock);
|
||||
}
|
||||
|
||||
public record Listener(BlockChangeFilter filter, Set<BlockPos> positions,
|
||||
long identifier, LongConsumer callback) {
|
||||
public record Listener(BlockChangeFilter filter, Set<BlockPos> positions, long identifier, LongConsumer callback) {
|
||||
public void call() {
|
||||
this.callback.accept(this.identifier);
|
||||
}
|
||||
|
||||
public boolean test(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
|
||||
public boolean test(final Level level, final BlockPos pos, final BlockState newBlock, final BlockState oldBlock) {
|
||||
return this.filter.test(level, pos, newBlock, oldBlock)
|
||||
&& this.positions.contains(pos);
|
||||
}
|
||||
|
||||
@@ -1,82 +1,90 @@
|
||||
package me.samsuik.sakura.listener;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
@NullMarked
|
||||
public final class LevelTickScheduler {
|
||||
private final Int2ObjectMap<List<TickTask>> tickTasks = new Int2ObjectLinkedOpenHashMap<>();
|
||||
private final Object2IntMap<TickTask> taskIntervals = new Object2IntOpenHashMap<>();
|
||||
private final Long2ObjectMap<List<TickTask>> scheduledTasks = new Long2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectMap<List<TickTask>> repeatingTasks = new Int2ObjectOpenHashMap<>();
|
||||
private final Reference2IntMap<TickTask> taskIntervals = new Reference2IntOpenHashMap<>();
|
||||
private final Deque<TickTask> removeLater = new ArrayDeque<>();
|
||||
private final Level level;
|
||||
|
||||
public void delayedTask(Runnable runnable, int delay) {
|
||||
this.registerNewTask(new TickTask() {
|
||||
private int cycles = 0;
|
||||
|
||||
@Override
|
||||
public void run(long tick) {
|
||||
if (this.cycles++ >= delay) {
|
||||
runnable.run();
|
||||
LevelTickScheduler.this.removeLater.add(this);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
public LevelTickScheduler(final Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public void registerNewTask(Runnable runnable, int interval) {
|
||||
this.registerNewTask(tick -> runnable.run(), interval);
|
||||
public void runTaskLater(final Runnable task, final int delay) {
|
||||
this.runTaskLater(tick -> task.run(), delay);
|
||||
}
|
||||
|
||||
public void registerNewTask(TickTask task, int interval) {
|
||||
int safeInterval = Math.max(interval + 1, 1);
|
||||
this.tickTasks.computeIfAbsent(safeInterval, i -> new ArrayList<>())
|
||||
public void runTaskLater(final TickTask task, final int delay) {
|
||||
final long runAt = this.level.getGameTime() + delay;
|
||||
this.scheduledTasks.computeIfAbsent(runAt, i -> new ObjectArrayList<>())
|
||||
.add(task);
|
||||
this.taskIntervals.put(task, safeInterval);
|
||||
}
|
||||
|
||||
private void removeTasks() {
|
||||
public void repeatingTask(final Runnable task, final int interval) {
|
||||
this.repeatingTask(tick -> task.run(), interval);
|
||||
}
|
||||
|
||||
public void repeatingTask(final TickTask task, final int interval) {
|
||||
final int taskInterval = Math.max(interval, 1);
|
||||
this.repeatingTasks.computeIfAbsent(taskInterval, i -> new ObjectArrayList<>())
|
||||
.add(task);
|
||||
this.taskIntervals.put(task, taskInterval);
|
||||
}
|
||||
|
||||
public void removeTask(final TickTask task) {
|
||||
final int taskInterval = this.taskIntervals.removeInt(task);
|
||||
this.repeatingTasks.computeIfPresent(taskInterval, (i, tasks) -> {
|
||||
tasks.remove(task);
|
||||
return tasks.isEmpty() ? null : tasks;
|
||||
});
|
||||
}
|
||||
|
||||
private void runTasks(final List<TickTask> tasks, final long gameTime) {
|
||||
for (final TickTask tickTask : tasks) {
|
||||
tickTask.run(gameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
final long gameTime = this.level.getGameTime();
|
||||
for (final long tick : this.scheduledTasks.keySet()) {
|
||||
if (tick > gameTime) {
|
||||
continue;
|
||||
}
|
||||
final List<TickTask> tasks = this.scheduledTasks.remove(tick);
|
||||
this.runTasks(tasks, gameTime);
|
||||
this.removeLater.addAll(tasks);
|
||||
}
|
||||
|
||||
for (final int interval : this.repeatingTasks.keySet()) {
|
||||
if (gameTime % interval == 0) {
|
||||
this.runTasks(this.repeatingTasks.get(interval), gameTime);
|
||||
}
|
||||
}
|
||||
|
||||
TickTask tickTask;
|
||||
while ((tickTask = this.removeLater.poll()) != null) {
|
||||
this.removeTask(tickTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTask(TickTask task) {
|
||||
int interval = this.taskIntervals.removeInt(task);
|
||||
if (interval > 0) {
|
||||
this.tickTasks.computeIfPresent(interval, (i, tasks) -> {
|
||||
tasks.remove(task);
|
||||
return tasks.isEmpty() ? null : tasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void runTasks(List<TickTask> tasks, long gameTime) {
|
||||
for (TickTask tickTask : tasks) {
|
||||
tickTask.run(gameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void levelTick(Level level) {
|
||||
long gameTime = level.getGameTime();
|
||||
for (int interval : this.tickTasks.keySet()) {
|
||||
if (gameTime % interval == 0) {
|
||||
this.runTasks(this.tickTasks.get(interval), gameTime);
|
||||
}
|
||||
}
|
||||
this.removeTasks();
|
||||
}
|
||||
|
||||
public interface TickTask {
|
||||
void run(long tick);
|
||||
void run(final long tick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public final class EntityBehaviour {
|
||||
public static void pre1_21_6$changeEntityPosition(
|
||||
public static void changeEntityPosition(
|
||||
final Entity entity,
|
||||
final Vec3 position,
|
||||
final Vec3 relativeMovement,
|
||||
|
||||
@@ -7,8 +7,8 @@ import net.minecraft.world.level.material.FluidState;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class LiquidBehaviour {
|
||||
public static boolean canLiquidSolidify(
|
||||
public final class LegacyBlockFormation {
|
||||
public static boolean canLiquidFormBlock(
|
||||
final Level level,
|
||||
final BlockPos pos,
|
||||
final FluidState fluidState,
|
||||
@@ -21,21 +21,23 @@ import net.minecraft.world.item.enchantment.Enchantment;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.item.enchantment.ItemEnchantments;
|
||||
import org.apache.commons.lang3.mutable.MutableFloat;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
@NullMarked
|
||||
public final class CombatUtil {
|
||||
public static boolean overrideBlockingAndHalveDamage(ItemStack stack, LivingEntity entity) {
|
||||
public static boolean overrideBlockingAndHalveDamage(final ItemStack stack, final LivingEntity entity) {
|
||||
return stack.getItem() instanceof BlockableSwordItem swordItem && swordItem.isSafeToOverrideBlocking(stack)
|
||||
|| stack.is(Items.SHIELD) && !DataComponentHelper.itemHasComponent(stack, DataComponents.BLOCKS_ATTACKS)
|
||||
&& entity.level().sakuraConfig().players.combat.shieldDamageReduction;
|
||||
}
|
||||
|
||||
public static double getLegacyAttackDifference(ItemStack itemstack) {
|
||||
ItemAttributeModifiers defaultModifiers = itemstack.getItem().components().get(DataComponents.ATTRIBUTE_MODIFIERS);
|
||||
public static double getLegacyAttackDifference(final ItemStack itemstack) {
|
||||
final ItemAttributeModifiers defaultModifiers = itemstack.getItem().components().get(DataComponents.ATTRIBUTE_MODIFIERS);
|
||||
if (defaultModifiers != null && !defaultModifiers.modifiers().isEmpty()) { // exists
|
||||
double baseAttack = 0.0;
|
||||
for (ItemAttributeModifiers.Entry entry : defaultModifiers.modifiers()) {
|
||||
for (final ItemAttributeModifiers.Entry entry : defaultModifiers.modifiers()) {
|
||||
if (!entry.slot().test(EquipmentSlot.MAINHAND) || !entry.attribute().is(Attributes.ATTACK_DAMAGE))
|
||||
continue;
|
||||
if (entry.modifier().operation() != AttributeModifier.Operation.ADD_VALUE)
|
||||
@@ -43,19 +45,20 @@ public final class CombatUtil {
|
||||
baseAttack += entry.modifier().amount();
|
||||
}
|
||||
|
||||
OptionalDouble legacyAttack = LegacyDamageMapping.itemAttackDamage(itemstack.getItem());
|
||||
final OptionalDouble legacyAttack = LegacyDamageMapping.itemAttackDamage(itemstack.getItem());
|
||||
if (baseAttack != 0.0 && legacyAttack.isPresent()) {
|
||||
return legacyAttack.getAsDouble() - baseAttack;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public static float calculateLegacySharpnessDamage(LivingEntity entity, ItemStack itemstack, DamageSource damageSource) {
|
||||
Holder<Enchantment> enchantment = getEnchantmentHolder(Enchantments.SHARPNESS);
|
||||
ItemEnchantments itemEnchantments = itemstack.getEnchantments();
|
||||
int enchantmentLevel = itemEnchantments.getLevel(enchantment);
|
||||
MutableFloat damage = new MutableFloat();
|
||||
public static float calculateLegacySharpnessDamage(final LivingEntity entity, final ItemStack itemstack, final DamageSource damageSource) {
|
||||
final Holder<Enchantment> enchantment = getEnchantmentHolder(Enchantments.SHARPNESS);
|
||||
final ItemEnchantments itemEnchantments = itemstack.getEnchantments();
|
||||
final int enchantmentLevel = itemEnchantments.getLevel(enchantment);
|
||||
final MutableFloat damage = new MutableFloat();
|
||||
|
||||
if (entity.level() instanceof ServerLevel level) {
|
||||
enchantment.value().modifyDamage(level, enchantmentLevel, itemstack, entity, damageSource, damage);
|
||||
@@ -64,9 +67,9 @@ public final class CombatUtil {
|
||||
return enchantmentLevel * 1.25F - damage.getValue();
|
||||
}
|
||||
|
||||
private static Holder<Enchantment> getEnchantmentHolder(ResourceKey<Enchantment> enchantmentKey) {
|
||||
RegistryAccess registryAccess = MinecraftServer.getServer().registryAccess();
|
||||
HolderLookup.RegistryLookup<Enchantment> enchantments = registryAccess.lookupOrThrow(Registries.ENCHANTMENT);
|
||||
private static Holder<Enchantment> getEnchantmentHolder(final ResourceKey<Enchantment> enchantmentKey) {
|
||||
final RegistryAccess registryAccess = MinecraftServer.getServer().registryAccess();
|
||||
final HolderLookup.RegistryLookup<Enchantment> enchantments = registryAccess.lookupOrThrow(Registries.ENCHANTMENT);
|
||||
return enchantments.getOrThrow(enchantmentKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,21 @@ import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.component.ItemAttributeModifiers;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
@NullMarked
|
||||
public final class LegacyDamageMapping {
|
||||
private static final Reference2DoubleMap<Item> LEGACY_ITEM_DAMAGE_MAP = new Reference2DoubleOpenHashMap<>();
|
||||
|
||||
public static OptionalDouble itemAttackDamage(Item item) {
|
||||
double result = LEGACY_ITEM_DAMAGE_MAP.getDouble(item);
|
||||
public static OptionalDouble itemAttackDamage(final Item item) {
|
||||
final double result = LEGACY_ITEM_DAMAGE_MAP.getDouble(item);
|
||||
return result == Double.MIN_VALUE ? OptionalDouble.empty() : OptionalDouble.of(result);
|
||||
}
|
||||
|
||||
private interface ItemDamageRemapper {
|
||||
double apply(Item item, double attackDamage);
|
||||
double apply(final Item item, final double attackDamage);
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -38,36 +40,30 @@ public final class LegacyDamageMapping {
|
||||
LEGACY_ITEM_DAMAGE_MAP.put(Items.DIAMOND_AXE, 6.0);
|
||||
LEGACY_ITEM_DAMAGE_MAP.put(Items.NETHERITE_AXE, 7.0);
|
||||
|
||||
Reference2ObjectMap<TagKey<Item>, ItemDamageRemapper> remapUsingItemTags = new Reference2ObjectArrayMap<>();
|
||||
final Reference2ObjectMap<TagKey<Item>, ItemDamageRemapper> remapUsingItemTags = new Reference2ObjectArrayMap<>();
|
||||
remapUsingItemTags.put(ItemTags.SWORDS, (item, attack) -> 1.0);
|
||||
remapUsingItemTags.put(ItemTags.PICKAXES, (item, attack) -> 1.0);
|
||||
remapUsingItemTags.put(ItemTags.SHOVELS, (item, attack) -> -0.5);
|
||||
remapUsingItemTags.put(ItemTags.HOES, (item, attack) -> -attack);
|
||||
|
||||
for (Item item : BuiltInRegistries.ITEM) {
|
||||
ItemAttributeModifiers modifiers = item.components().get(DataComponents.ATTRIBUTE_MODIFIERS);
|
||||
|
||||
for (final Item item : BuiltInRegistries.ITEM) {
|
||||
final ItemAttributeModifiers modifiers = item.components().get(DataComponents.ATTRIBUTE_MODIFIERS);
|
||||
if (modifiers == null || LEGACY_ITEM_DAMAGE_MAP.containsKey(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Holder.Reference<Item> itemHolder = item.builtInRegistryHolder();
|
||||
assert itemHolder.is(ItemTags.AXES) : "missing axe mapping";
|
||||
|
||||
double attackDamage = modifiers.modifiers().stream()
|
||||
final double attackDamage = modifiers.modifiers().stream()
|
||||
.filter(e -> e.attribute().is(Attributes.ATTACK_DAMAGE))
|
||||
.mapToDouble(e -> e.modifier().amount())
|
||||
.sum();
|
||||
|
||||
if (attackDamage > 0.0) {
|
||||
double adjustment = 0.0;
|
||||
for (TagKey<Item> key : remapUsingItemTags.keySet()) {
|
||||
if (itemHolder.is(key)) {
|
||||
ItemDamageRemapper remapper = remapUsingItemTags.get(key);
|
||||
adjustment = remapper.apply(item, attackDamage);
|
||||
}
|
||||
}
|
||||
|
||||
final Holder.Reference<Item> itemHolder = item.builtInRegistryHolder();
|
||||
final double adjustment = remapUsingItemTags.keySet().stream()
|
||||
.filter(itemHolder::is)
|
||||
.mapToDouble(itemTagKey -> remapUsingItemTags.get(itemTagKey).apply(item, attackDamage))
|
||||
.findFirst()
|
||||
.orElse(0.0);
|
||||
LEGACY_ITEM_DAMAGE_MAP.put(item, attackDamage + adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,28 +13,28 @@ public abstract class FeatureGui {
|
||||
private final int size;
|
||||
private final Component title;
|
||||
|
||||
public FeatureGui(int size, Component title) {
|
||||
public FeatureGui(final int size, final Component title) {
|
||||
this.size = size;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
protected abstract void fillInventory(Inventory inventory);
|
||||
protected abstract void fillInventory(final Inventory inventory);
|
||||
|
||||
protected abstract void afterFill(Player player, FeatureGuiInventory inventory);
|
||||
protected abstract void afterFill(final Player player, final FeatureGuiInventory inventory);
|
||||
|
||||
public final void showTo(Player bukkitPlayer) {
|
||||
FeatureGuiInventory featureInventory = new FeatureGuiInventory(this, this.size, this.title);
|
||||
public final void showTo(final Player bukkitPlayer) {
|
||||
final FeatureGuiInventory featureInventory = new FeatureGuiInventory(this, this.size, this.title);
|
||||
this.fillInventory(featureInventory.getInventory());
|
||||
this.afterFill(bukkitPlayer, featureInventory);
|
||||
bukkitPlayer.openInventory(featureInventory.getInventory());
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void clickEvent(InventoryClickEvent event) {
|
||||
Inventory clicked = event.getClickedInventory();
|
||||
public static void clickEvent(final InventoryClickEvent event) {
|
||||
final Inventory clicked = event.getClickedInventory();
|
||||
if (clicked != null && clicked.getHolder(false) instanceof FeatureGuiInventory featureInventory) {
|
||||
event.setCancelled(true);
|
||||
for (GuiComponent component : featureInventory.getComponents().reversed()) {
|
||||
event.setCancelled(true); // cancel the event first to let components uncancel the event if desired.
|
||||
for (final GuiComponent component : featureInventory.getComponents().reversed()) {
|
||||
if (component.interaction(event, featureInventory)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public final class FeatureGuiInventory implements InventoryHolder {
|
||||
private final Multimap<NamespacedKey, GuiComponent> componentsUnderKey = HashMultimap.create();
|
||||
private final Object2ObjectMap<GuiComponent, NamespacedKey> componentKeys = new Object2ObjectLinkedOpenHashMap<>();
|
||||
|
||||
public FeatureGuiInventory(FeatureGui gui, int size, Component component) {
|
||||
public FeatureGuiInventory(final FeatureGui gui, final int size, final Component component) {
|
||||
this.inventory = Bukkit.createInventory(this, size, component);
|
||||
this.gui = gui;
|
||||
}
|
||||
@@ -42,36 +42,36 @@ public final class FeatureGuiInventory implements InventoryHolder {
|
||||
return ImmutableList.copyOf(this.componentKeys.keySet());
|
||||
}
|
||||
|
||||
public ImmutableList<GuiComponent> findComponents(NamespacedKey key) {
|
||||
public ImmutableList<GuiComponent> findComponents(final NamespacedKey key) {
|
||||
return ImmutableList.copyOf(this.componentsUnderKey.get(key));
|
||||
}
|
||||
|
||||
public Optional<GuiComponent> findFirst(NamespacedKey key) {
|
||||
Collection<GuiComponent> components = this.componentsUnderKey.get(key);
|
||||
public Optional<GuiComponent> findFirst(final NamespacedKey key) {
|
||||
final Collection<GuiComponent> components = this.componentsUnderKey.get(key);
|
||||
return components.stream().findFirst();
|
||||
}
|
||||
|
||||
public void removeComponents(NamespacedKey key) {
|
||||
Collection<GuiComponent> removed = this.componentsUnderKey.removeAll(key);
|
||||
for (GuiComponent component : removed) {
|
||||
public void removeComponents(final NamespacedKey key) {
|
||||
final Collection<GuiComponent> removed = this.componentsUnderKey.removeAll(key);
|
||||
for (final GuiComponent component : removed) {
|
||||
this.componentKeys.remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
public void addComponent(GuiComponent component, NamespacedKey key) {
|
||||
public void addComponent(final GuiComponent component, final NamespacedKey key) {
|
||||
Preconditions.checkArgument(!this.componentKeys.containsKey(component), "component has already been added");
|
||||
this.componentKeys.put(component, key);
|
||||
this.componentsUnderKey.put(key, component);
|
||||
this.inventoryUpdate(component);
|
||||
}
|
||||
|
||||
public void removeComponent(GuiComponent component) {
|
||||
NamespacedKey key = this.componentKeys.remove(component);
|
||||
public void removeComponent(final GuiComponent component) {
|
||||
final NamespacedKey key = this.componentKeys.remove(component);
|
||||
this.componentsUnderKey.remove(key, component);
|
||||
}
|
||||
|
||||
public void replaceComponent(GuiComponent component, GuiComponent replacement) {
|
||||
NamespacedKey key = this.componentKeys.remove(component);
|
||||
public void replaceComponent(final GuiComponent component, final GuiComponent replacement) {
|
||||
final NamespacedKey key = this.componentKeys.remove(component);
|
||||
Preconditions.checkNotNull(key, "component does not exist");
|
||||
this.componentKeys.put(replacement, key);
|
||||
this.componentsUnderKey.remove(key, component);
|
||||
@@ -84,7 +84,7 @@ public final class FeatureGuiInventory implements InventoryHolder {
|
||||
this.componentsUnderKey.clear();
|
||||
}
|
||||
|
||||
private void inventoryUpdate(GuiComponent component) {
|
||||
private void inventoryUpdate(final GuiComponent component) {
|
||||
component.creation(this.inventory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class ItemStackUtil {
|
||||
public static ItemStack itemWithBlankName(Material material) {
|
||||
public static ItemStack itemWithBlankName(final Material material) {
|
||||
return itemWithName(material, Component.empty());
|
||||
}
|
||||
|
||||
public static ItemStack itemWithName(Material material, Component component) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
item.editMeta(m -> m.itemName(component));
|
||||
public static ItemStack itemWithName(final Material material, final Component component) {
|
||||
final ItemStack item = new ItemStack(material);
|
||||
item.editMeta(meta -> meta.itemName(component));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public interface GuiClickEvent {
|
||||
void doSomething(InventoryClickEvent event, FeatureGuiInventory inventory);
|
||||
void doSomething(final InventoryClickEvent event, final FeatureGuiInventory inventory);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public interface GuiComponent {
|
||||
boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory);
|
||||
boolean interaction(final InventoryClickEvent event, final FeatureGuiInventory featureInventory);
|
||||
|
||||
void creation(Inventory inventory);
|
||||
void creation(final Inventory inventory);
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ public final class ItemButton implements GuiComponent {
|
||||
private final int slot;
|
||||
private final GuiClickEvent whenClicked;
|
||||
|
||||
public ItemButton(ItemStack bukkitItem, int slot, GuiClickEvent whenClicked) {
|
||||
public ItemButton(final ItemStack bukkitItem, final int slot, final GuiClickEvent whenClicked) {
|
||||
this.bukkitItem = bukkitItem;
|
||||
this.slot = slot;
|
||||
this.whenClicked = whenClicked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory) {
|
||||
public boolean interaction(final InventoryClickEvent event, final FeatureGuiInventory featureInventory) {
|
||||
if (event.getSlot() == this.slot) {
|
||||
this.whenClicked.doSomething(event, featureInventory);
|
||||
return true;
|
||||
@@ -28,7 +28,7 @@ public final class ItemButton implements GuiComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void creation(Inventory inventory) {
|
||||
public void creation(final Inventory inventory) {
|
||||
inventory.setItem(this.slot, this.bukkitItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public final class ItemSwitch implements GuiComponent {
|
||||
private final int selected;
|
||||
private final GuiClickEvent whenClicked;
|
||||
|
||||
public ItemSwitch(List<ItemStack> items, int slot, int selected, GuiClickEvent whenClicked) {
|
||||
public ItemSwitch(final List<ItemStack> items, final int slot, final int selected, final GuiClickEvent whenClicked) {
|
||||
Preconditions.checkArgument(!items.isEmpty());
|
||||
this.items = Collections.unmodifiableList(items);
|
||||
this.slot = slot;
|
||||
@@ -26,10 +26,10 @@ public final class ItemSwitch implements GuiComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory) {
|
||||
public boolean interaction(final InventoryClickEvent event, final FeatureGuiInventory featureInventory) {
|
||||
if (this.slot == event.getSlot()) {
|
||||
int next = (this.selected + 1) % this.items.size();
|
||||
ItemSwitch itemSwitch = new ItemSwitch(this.items, this.slot, next, this.whenClicked);
|
||||
final int next = (this.selected + 1) % this.items.size();
|
||||
final ItemSwitch itemSwitch = new ItemSwitch(this.items, this.slot, next, this.whenClicked);
|
||||
featureInventory.replaceComponent(this, itemSwitch);
|
||||
this.whenClicked.doSomething(event, featureInventory);
|
||||
return true;
|
||||
@@ -38,7 +38,7 @@ public final class ItemSwitch implements GuiComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void creation(Inventory inventory) {
|
||||
public void creation(final Inventory inventory) {
|
||||
inventory.setItem(this.slot, this.items.get(this.selected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ public final class BlockableSwordItem extends Item {
|
||||
.hasConsumeParticles(false)
|
||||
.build();
|
||||
|
||||
public BlockableSwordItem(Properties properties) {
|
||||
public BlockableSwordItem(final Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyComponentsSentToClient(PatchedDataComponentMap components) {
|
||||
public void modifyComponentsSentToClient(final PatchedDataComponentMap components) {
|
||||
if (!hasCustomAnimationOrDisabled(components)) {
|
||||
// When updating to 1.22 change CONSUMABLE to BLOCK_ATTACKS
|
||||
components.set(DataComponents.CONSUMABLE, BLOCKING_ANIMATION);
|
||||
@@ -37,7 +37,7 @@ public final class BlockableSwordItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(Level level, Player player, InteractionHand hand) {
|
||||
public InteractionResult use(final Level level, final Player player, final InteractionHand hand) {
|
||||
final ItemStack stack = player.getItemInHand(hand);
|
||||
if (hasCustomAnimationOrDisabled(stack.getComponents())) {
|
||||
return super.use(level, player, hand);
|
||||
@@ -48,7 +48,7 @@ public final class BlockableSwordItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUseDuration(ItemStack stack, LivingEntity entity) {
|
||||
public int getUseDuration(final ItemStack stack, final LivingEntity entity) {
|
||||
if (hasCustomAnimationOrDisabled(stack.getComponents())) {
|
||||
return super.getUseDuration(stack, entity);
|
||||
} else {
|
||||
@@ -56,11 +56,11 @@ public final class BlockableSwordItem extends Item {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSafeToOverrideBlocking(ItemStack stack) {
|
||||
public boolean isSafeToOverrideBlocking(final ItemStack stack) {
|
||||
return !hasCustomAnimationOrDisabled(stack.getComponents());
|
||||
}
|
||||
|
||||
private static boolean hasCustomAnimationOrDisabled(DataComponentMap componentMap) {
|
||||
private static boolean hasCustomAnimationOrDisabled(final DataComponentMap componentMap) {
|
||||
final GlobalConfiguration config = GlobalConfiguration.get();
|
||||
return (config == null || !config.players.combat.blockWithSwords)
|
||||
|| componentMap.has(DataComponents.CONSUMABLE)
|
||||
|
||||
@@ -5,19 +5,21 @@ import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class DataComponentHelper {
|
||||
public static int bucketMaxStackSize() {
|
||||
GlobalConfiguration config = GlobalConfiguration.get();
|
||||
final GlobalConfiguration config = GlobalConfiguration.get();
|
||||
return config == null || !config.players.bucketStackSize.isDefined() ? -1 : config.players.bucketStackSize.intValue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalAssignedToNull")
|
||||
public static boolean itemHasComponent(ItemStack stack, DataComponentType<?> component) {
|
||||
public static boolean itemHasComponent(final ItemStack stack, final DataComponentType<?> component) {
|
||||
return stack.getComponentsPatch().get(component) != null;
|
||||
}
|
||||
|
||||
public static DataComponentMap copyComponentsAndModifyMaxStackSize(DataComponentMap componentMap, int maxItemSize) {
|
||||
public static DataComponentMap copyComponentsAndModifyMaxStackSize(final DataComponentMap componentMap, final int maxItemSize) {
|
||||
if (maxItemSize > 0 && maxItemSize <= 99) {
|
||||
return DataComponentMap.builder()
|
||||
.addAll(componentMap)
|
||||
|
||||
@@ -32,12 +32,12 @@ public final class LegacyGoldenAppleItem extends Item {
|
||||
)
|
||||
.build();
|
||||
|
||||
public LegacyGoldenAppleItem(Properties settings) {
|
||||
public LegacyGoldenAppleItem(final Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(Level level, Player player, InteractionHand hand) {
|
||||
public InteractionResult use(final Level level, final Player player, final InteractionHand hand) {
|
||||
final ItemStack stack = player.getItemInHand(hand);
|
||||
if (isItemConsumableOrDisabled(stack, level)) {
|
||||
return super.use(level, player, hand);
|
||||
@@ -47,7 +47,7 @@ public final class LegacyGoldenAppleItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity entity) {
|
||||
public ItemStack finishUsingItem(final ItemStack stack, final Level level, final LivingEntity entity) {
|
||||
if (isItemConsumableOrDisabled(stack, level)) {
|
||||
return super.finishUsingItem(stack, level, entity);
|
||||
} else {
|
||||
@@ -55,7 +55,7 @@ public final class LegacyGoldenAppleItem extends Item {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isItemConsumableOrDisabled(ItemStack stack, Level level) {
|
||||
private static boolean isItemConsumableOrDisabled(final ItemStack stack, final Level level) {
|
||||
return DataComponentHelper.itemHasComponent(stack, DataComponents.CONSUMABLE)
|
||||
|| !level.sakuraConfig().players.combat.oldEnchantedGoldenApple;
|
||||
}
|
||||
|
||||
@@ -8,20 +8,20 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class MilkBucketItem extends Item {
|
||||
public MilkBucketItem(Properties properties) {
|
||||
public MilkBucketItem(final Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyComponentsAfterLoad(ItemStack stack) {
|
||||
int maxStackSize = DataComponentHelper.bucketMaxStackSize();
|
||||
public void verifyComponentsAfterLoad(final ItemStack stack) {
|
||||
final int maxStackSize = DataComponentHelper.bucketMaxStackSize();
|
||||
if (maxStackSize > 0 && maxStackSize < 100 && stackableMilkBuckets()) {
|
||||
stack.set(DataComponents.MAX_STACK_SIZE, maxStackSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean stackableMilkBuckets() {
|
||||
GlobalConfiguration config = GlobalConfiguration.get();
|
||||
final GlobalConfiguration config = GlobalConfiguration.get();
|
||||
return config != null && config.players.stackableMilkBuckets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class StackableBucketItem extends BucketItem {
|
||||
public StackableBucketItem(Fluid content, Properties properties) {
|
||||
public StackableBucketItem(final Fluid content, final Properties properties) {
|
||||
super(content, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyComponentsAfterLoad(ItemStack stack) {
|
||||
public void verifyComponentsAfterLoad(final ItemStack stack) {
|
||||
// It's also possible to override the components method and modify the stack size through the DataComponentHelper
|
||||
int maxStackSize = DataComponentHelper.bucketMaxStackSize();
|
||||
final int maxStackSize = DataComponentHelper.bucketMaxStackSize();
|
||||
if (maxStackSize > 0 && maxStackSize < 100) {
|
||||
stack.set(DataComponents.MAX_STACK_SIZE, maxStackSize);
|
||||
}
|
||||
|
||||
@@ -4,22 +4,22 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import net.minecraft.world.level.storage.ValueInput;
|
||||
import net.minecraft.world.level.storage.ValueOutput;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class PlayerVisibilitySettings implements VisibilitySettings {
|
||||
private static final String SETTINGS_COMPOUND_TAG = "clientVisibilitySettings";
|
||||
private final Reference2ObjectMap<VisibilityType, VisibilityState> visibilityStates = new Reference2ObjectOpenHashMap<>();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VisibilityState get(@NonNull VisibilityType type) {
|
||||
VisibilityState state = this.visibilityStates.get(type);
|
||||
public VisibilityState get(final VisibilityType type) {
|
||||
final VisibilityState state = this.visibilityStates.get(type);
|
||||
//noinspection ConstantValue
|
||||
return state != null ? state : type.getDefault();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VisibilityState set(@NonNull VisibilityType type, @NonNull VisibilityState state) {
|
||||
public VisibilityState set(final VisibilityType type, final VisibilityState state) {
|
||||
if (type.isDefault(state)) {
|
||||
this.visibilityStates.remove(type);
|
||||
} else {
|
||||
@@ -28,10 +28,9 @@ public final class PlayerVisibilitySettings implements VisibilitySettings {
|
||||
return state;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VisibilityState currentState() {
|
||||
int modifiedCount = this.visibilityStates.size();
|
||||
final int modifiedCount = this.visibilityStates.size();
|
||||
if (modifiedCount == 0) {
|
||||
return VisibilityState.ON;
|
||||
} else if (modifiedCount != VisibilityTypes.types().size()) {
|
||||
@@ -46,18 +45,18 @@ public final class PlayerVisibilitySettings implements VisibilitySettings {
|
||||
return !this.visibilityStates.isEmpty();
|
||||
}
|
||||
|
||||
public void loadData(@NonNull ValueInput input) {
|
||||
public void loadData(final ValueInput input) {
|
||||
input.child(SETTINGS_COMPOUND_TAG).ifPresent(settings -> {
|
||||
for (VisibilityType type : VisibilityTypes.types()) {
|
||||
String typeKey = type.key();
|
||||
String stateName = settings.getStringOr(typeKey, type.getDefault().name());
|
||||
for (final VisibilityType type : VisibilityTypes.types()) {
|
||||
final String typeKey = type.key();
|
||||
final String stateName = settings.getStringOr(typeKey, type.getDefault().name());
|
||||
this.set(type, VisibilityState.valueOf(stateName));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void saveData(@NonNull ValueOutput output) {
|
||||
ValueOutput settings = output.child(SETTINGS_COMPOUND_TAG);
|
||||
public void saveData(final ValueOutput output) {
|
||||
final ValueOutput settings = output.child(SETTINGS_COMPOUND_TAG);
|
||||
this.visibilityStates.forEach((type, state) -> settings.putString(type.key(), state.name()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +25,16 @@ public final class VisibilityGui extends FeatureGui {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillInventory(Inventory inventory) {
|
||||
protected void fillInventory(final Inventory inventory) {
|
||||
for (int slot = 0; slot < inventory.getSize(); ++slot) {
|
||||
// x, y from top left of the inventory
|
||||
int x = slot % 9;
|
||||
int y = slot / 9;
|
||||
final int x = slot % 9;
|
||||
final int y = slot / 9;
|
||||
// from center
|
||||
int rx = x - 4;
|
||||
int ry = y - 2;
|
||||
double d = Math.sqrt(rx * rx + ry * ry);
|
||||
if (d <= 3.25) {
|
||||
final int rx = x - 4;
|
||||
final int ry = y - 2;
|
||||
|
||||
if (Math.sqrt(rx * rx + ry * ry) <= 3.25) {
|
||||
inventory.setItem(slot, itemWithBlankName(GlobalConfiguration.get().fps.material));
|
||||
} else if (x % 8 == 0) {
|
||||
inventory.setItem(slot, itemWithBlankName(Material.BLACK_STAINED_GLASS_PANE));
|
||||
@@ -45,16 +45,17 @@ public final class VisibilityGui extends FeatureGui {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterFill(Player player, FeatureGuiInventory inventory) {
|
||||
VisibilitySettings settings = player.getVisibility();
|
||||
IntArrayFIFOQueue slots = this.availableSlots();
|
||||
protected void afterFill(final Player player, final FeatureGuiInventory inventory) {
|
||||
final VisibilitySettings settings = player.getVisibility();
|
||||
final IntArrayFIFOQueue slots = this.availableSlots();
|
||||
this.updateToggleButton(settings, player, inventory);
|
||||
for (VisibilityType type : VisibilityTypes.types()) {
|
||||
VisibilityState state = settings.get(type);
|
||||
int index = type.states().indexOf(state);
|
||||
int slot = slots.dequeueInt();
|
||||
for (final VisibilityType type : VisibilityTypes.types()) {
|
||||
final VisibilityState state = settings.get(type);
|
||||
final int index = type.states().indexOf(state);
|
||||
final int slot = slots.dequeueInt();
|
||||
|
||||
ItemSwitch itemSwitch = new ItemSwitch(
|
||||
// Switch between the visibility states
|
||||
final ItemSwitch itemSwitch = new ItemSwitch(
|
||||
VisibilityGuiItems.GUI_ITEMS.get(type),
|
||||
slot, index,
|
||||
(e, inv) -> {
|
||||
@@ -67,10 +68,10 @@ public final class VisibilityGui extends FeatureGui {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateToggleButton(VisibilitySettings settings, Player player, FeatureGuiInventory inventory) {
|
||||
private void updateToggleButton(final VisibilitySettings settings, final Player player, final FeatureGuiInventory inventory) {
|
||||
inventory.removeComponents(TOGGLE_BUTTON_KEY);
|
||||
VisibilityState settingsState = settings.currentState();
|
||||
ItemButton button = new ItemButton(
|
||||
final VisibilityState settingsState = settings.currentState();
|
||||
final ItemButton button = new ItemButton(
|
||||
VisibilityGuiItems.TOGGLE_BUTTON_ITEMS.get(settingsState),
|
||||
(2 * 9) + 8,
|
||||
(e, inv) -> {
|
||||
@@ -83,7 +84,7 @@ public final class VisibilityGui extends FeatureGui {
|
||||
}
|
||||
|
||||
private IntArrayFIFOQueue availableSlots() {
|
||||
IntArrayFIFOQueue slots = new IntArrayFIFOQueue();
|
||||
final IntArrayFIFOQueue slots = new IntArrayFIFOQueue();
|
||||
for (int row = 1; row < 4; ++row) {
|
||||
for (int column = 3; column < 6; ++column) {
|
||||
if ((column + row) % 2 == 0) {
|
||||
|
||||
@@ -9,29 +9,28 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@NullMarked
|
||||
public final class VisibilityGuiItems {
|
||||
static final Reference2ObjectMap<VisibilityType, ImmutableList<ItemStack>> GUI_ITEMS = new Reference2ObjectOpenHashMap<>();
|
||||
static final Reference2ObjectMap<VisibilityState, ItemStack> TOGGLE_BUTTON_ITEMS = new Reference2ObjectOpenHashMap<>();
|
||||
|
||||
static {
|
||||
Reference2ObjectMap<VisibilityType, ItemStack> items = new Reference2ObjectOpenHashMap<>();
|
||||
|
||||
final Reference2ObjectMap<VisibilityType, ItemStack> items = new Reference2ObjectOpenHashMap<>();
|
||||
items.put(VisibilityTypes.TNT, ItemStackUtil.itemWithName(Material.TNT, Component.text("Tnt", NamedTextColor.RED)));
|
||||
items.put(VisibilityTypes.SAND, ItemStackUtil.itemWithName(Material.SAND, Component.text("Sand", NamedTextColor.YELLOW)));
|
||||
items.put(VisibilityTypes.EXPLOSIONS, ItemStackUtil.itemWithName(Material.COBWEB, Component.text("Explosions", NamedTextColor.WHITE)));
|
||||
items.put(VisibilityTypes.SPAWNERS, ItemStackUtil.itemWithName(Material.SPAWNER, Component.text("Spawners", NamedTextColor.DARK_GRAY)));
|
||||
items.put(VisibilityTypes.PISTONS, ItemStackUtil.itemWithName(Material.PISTON, Component.text("Pistons", NamedTextColor.GOLD)));
|
||||
|
||||
for (VisibilityType type : VisibilityTypes.types()) {
|
||||
ItemStack item = items.get(type);
|
||||
|
||||
ImmutableList<ItemStack> stateItems = type.states().stream()
|
||||
.map(s -> createItemForState(item, s))
|
||||
for (final VisibilityType type : VisibilityTypes.types()) {
|
||||
final ItemStack item = items.get(type);
|
||||
final ImmutableList<ItemStack> stateItems = type.states().stream()
|
||||
.map(state -> createItemForState(item, state))
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
|
||||
GUI_ITEMS.put(type, stateItems);
|
||||
}
|
||||
|
||||
@@ -40,16 +39,16 @@ public final class VisibilityGuiItems {
|
||||
TOGGLE_BUTTON_ITEMS.put(VisibilityState.OFF, ItemStackUtil.itemWithName(Material.RED_STAINED_GLASS_PANE, Component.text("Disabled", NamedTextColor.RED)));
|
||||
}
|
||||
|
||||
private static String lowercaseThenCapitalise(String name) {
|
||||
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
|
||||
private static String lowercaseThenCapitalise(final String name) {
|
||||
final String lowercaseName = name.toLowerCase(Locale.ENGLISH);
|
||||
return StringUtils.capitalize(lowercaseName);
|
||||
}
|
||||
|
||||
private static ItemStack createItemForState(ItemStack in, VisibilityState state) {
|
||||
String capitalisedName = lowercaseThenCapitalise(state.name());
|
||||
Component component = Component.text(" | " + capitalisedName, NamedTextColor.GRAY);
|
||||
ItemStack itemCopy = in.clone();
|
||||
itemCopy.editMeta(m -> m.itemName(m.itemName().append(component)));
|
||||
private static ItemStack createItemForState(final ItemStack in, final VisibilityState state) {
|
||||
final String capitalisedName = lowercaseThenCapitalise(state.name());
|
||||
final Component component = Component.text(" | " + capitalisedName, NamedTextColor.GRAY);
|
||||
final ItemStack itemCopy = in.clone();
|
||||
itemCopy.editMeta(meta -> meta.itemName(meta.itemName().append(component)));
|
||||
return itemCopy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.samsuik.sakura.redstone;
|
||||
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import me.samsuik.sakura.listener.BlockChangeTracker;
|
||||
@@ -22,15 +21,20 @@ import java.util.List;
|
||||
@NullMarked
|
||||
public final class RedstoneNetwork {
|
||||
private final List<RedstoneWireUpdate> wireUpdates;
|
||||
private final List<BlockPos> updates;
|
||||
private final List<BlockPos> neighborUpdates;
|
||||
private final Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower;
|
||||
private final LongArrayList listeners = new LongArrayList();
|
||||
private final BitSet redundantUpdates = new BitSet();
|
||||
private final TickExpiry expiry;
|
||||
|
||||
public RedstoneNetwork(List<RedstoneWireUpdate> wireUpdates, List<BlockPos> updates, Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower, TickExpiry expiry) {
|
||||
public RedstoneNetwork(
|
||||
final List<RedstoneWireUpdate> wireUpdates,
|
||||
final List<BlockPos> neighborUpdates,
|
||||
final Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower,
|
||||
final TickExpiry expiry
|
||||
) {
|
||||
this.wireUpdates = new ObjectArrayList<>(wireUpdates);
|
||||
this.updates = new ObjectArrayList<>(updates);
|
||||
this.neighborUpdates = new ObjectArrayList<>(neighborUpdates);
|
||||
this.originalWirePower = new Object2ObjectOpenHashMap<>(originalWirePower);
|
||||
this.expiry = expiry;
|
||||
}
|
||||
@@ -49,21 +53,21 @@ public final class RedstoneNetwork {
|
||||
return !this.listeners.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasWire(BlockPos pos) {
|
||||
public boolean hasWire(final BlockPos pos) {
|
||||
return this.originalWirePower.containsKey(pos);
|
||||
}
|
||||
|
||||
public void invalidate(Level level) {
|
||||
for (long identifier : this.listeners) {
|
||||
public void invalidate(final Level level) {
|
||||
for (final long identifier : this.listeners) {
|
||||
level.blockChangeTracker.stopListening(identifier);
|
||||
}
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
private void markNeighboringWiresForShapeUpdates(BlockPos pos, Object2ObjectMap<BlockPos, RedstoneWireUpdate> wires) {
|
||||
for (Direction direction : NeighborUpdater.UPDATE_ORDER) {
|
||||
BlockPos neighborPos = pos.relative(direction);
|
||||
RedstoneWireUpdate wireUpdate = wires.get(neighborPos);
|
||||
private void markNeighboringWiresForShapeUpdates(final BlockPos pos, final Object2ObjectMap<BlockPos, RedstoneWireUpdate> wires) {
|
||||
for (final Direction direction : NeighborUpdater.UPDATE_ORDER) {
|
||||
final BlockPos neighborPos = pos.relative(direction);
|
||||
final RedstoneWireUpdate wireUpdate = wires.get(neighborPos);
|
||||
//noinspection ConstantValue
|
||||
if (wireUpdate != null) {
|
||||
wireUpdate.updateShapes();
|
||||
@@ -71,16 +75,17 @@ public final class RedstoneNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean prepareAndRegisterListeners(Level level, RedstoneNetworkSource networkSource) {
|
||||
Object2ObjectLinkedOpenHashMap<BlockPos, RedstoneWireUpdate> processedWires = new Object2ObjectLinkedOpenHashMap<>();
|
||||
boolean skipWireUpdates = networkSource.redstoneImplementation() != WorldConfiguration.Misc.RedstoneImplementation.VANILLA;
|
||||
for (RedstoneWireUpdate wireUpdate : this.wireUpdates.reversed()) {
|
||||
BlockPos wirePos = wireUpdate.getPosition();
|
||||
public boolean prepareAndRegisterListeners(final Level level, final RedstoneNetworkSource networkSource) {
|
||||
final Object2ObjectLinkedOpenHashMap<BlockPos, RedstoneWireUpdate> processedWires = new Object2ObjectLinkedOpenHashMap<>();
|
||||
final boolean skipWireUpdates = networkSource.isVanilla();
|
||||
|
||||
for (final RedstoneWireUpdate wireUpdate : this.wireUpdates.reversed()) {
|
||||
final BlockPos wirePos = wireUpdate.getPosition();
|
||||
//noinspection ConstantValue
|
||||
if (processedWires.putAndMoveToFirst(wirePos, wireUpdate) == null) {
|
||||
// It's possible for the block below the redstone to break while the network is updating
|
||||
BlockState state = level.getBlockState(wirePos);
|
||||
if (state.is(Blocks.PISTON_HEAD) || state.is(BlockTags.TRAPDOORS)) {
|
||||
final BlockState blockStateBelow = level.getBlockState(wirePos.below());
|
||||
if (blockStateBelow.is(Blocks.PISTON_HEAD) || blockStateBelow.is(BlockTags.TRAPDOORS)) {
|
||||
return false;
|
||||
}
|
||||
} else if (skipWireUpdates && this.originalWirePower.get(wirePos).firstPower() != wireUpdate.getPower()) {
|
||||
@@ -90,13 +95,14 @@ public final class RedstoneNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
for (int updateIndex = 0; updateIndex < this.updates.size(); ++updateIndex) {
|
||||
BlockPos updatePos = this.updates.get(updateIndex);
|
||||
BlockState state = level.getBlockState(updatePos);
|
||||
for (int updateIndex = 0; updateIndex < this.neighborUpdates.size(); ++updateIndex) {
|
||||
final BlockPos updatePos = this.neighborUpdates.get(updateIndex);
|
||||
final BlockState state = level.getBlockState(updatePos);
|
||||
final Block block = state.getBlock();
|
||||
|
||||
// Never apply updates to redstone wires
|
||||
if (state.is(Blocks.REDSTONE_WIRE)) {
|
||||
this.updates.set(updateIndex, null);
|
||||
this.neighborUpdates.set(updateIndex, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -106,7 +112,6 @@ public final class RedstoneNetwork {
|
||||
}
|
||||
|
||||
// Look for blocks that actually need shape updates
|
||||
Block block = state.getBlock();
|
||||
if (state.is(Blocks.OBSERVER) || state.liquid() || block instanceof FallingBlock || block instanceof LiquidBlockContainer) {
|
||||
this.markNeighboringWiresForShapeUpdates(updatePos, processedWires);
|
||||
}
|
||||
@@ -116,20 +121,20 @@ public final class RedstoneNetwork {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void allowNeighborUpdates() {
|
||||
for (int updateIndex = 0; updateIndex < this.updates.size(); ++updateIndex) {
|
||||
private void allowRedundantNeighborUpdates() {
|
||||
for (int updateIndex = 0; updateIndex < this.neighborUpdates.size(); ++updateIndex) {
|
||||
if (!this.redundantUpdates.get(updateIndex)) {
|
||||
continue;
|
||||
}
|
||||
BlockPos pos = this.updates.get(updateIndex);
|
||||
final BlockPos pos = this.neighborUpdates.get(updateIndex);
|
||||
if (!this.originalWirePower.containsKey(pos)) {
|
||||
this.redundantUpdates.clear(updateIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addBlockListeners(Level level) {
|
||||
ObjectOpenHashSet<BlockPos> positions = new ObjectOpenHashSet<>(this.updates);
|
||||
private void addBlockListeners(final Level level) {
|
||||
ObjectOpenHashSet<BlockPos> positions = new ObjectOpenHashSet<>(this.neighborUpdates);
|
||||
positions.addAll(this.originalWirePower.keySet());
|
||||
positions.remove(null);
|
||||
|
||||
@@ -139,13 +144,13 @@ public final class RedstoneNetwork {
|
||||
));
|
||||
|
||||
this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
|
||||
BlockChangeTracker.BlockChangeFilter.ANY, positions, this::allowNeighborUpdates
|
||||
BlockChangeTracker.BlockChangeFilter.ANY, positions, this::allowRedundantNeighborUpdates
|
||||
));
|
||||
}
|
||||
|
||||
private boolean verifyWiresInNetwork(Level level) {
|
||||
for (Object2ObjectMap.Entry<BlockPos, RedstoneOriginalPower> wireEntry : this.originalWirePower.object2ObjectEntrySet()) {
|
||||
BlockState state = level.getBlockState(wireEntry.getKey());
|
||||
private boolean verifyWiresInNetwork(final Level level) {
|
||||
for (final Object2ObjectMap.Entry<BlockPos, RedstoneOriginalPower> wireEntry : this.originalWirePower.object2ObjectEntrySet()) {
|
||||
final BlockState state = level.getBlockState(wireEntry.getKey());
|
||||
if (!state.is(Blocks.REDSTONE_WIRE)) {
|
||||
this.invalidate(level);
|
||||
return false;
|
||||
@@ -159,41 +164,51 @@ public final class RedstoneNetwork {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void performUpdates(Level level, Orientation orientation, RedStoneWireBlock wireBlock, int updateFrom, int updateTo) {
|
||||
private void performUpdates(
|
||||
final Level level,
|
||||
final Orientation orientation,
|
||||
final RedStoneWireBlock wireBlock,
|
||||
final int updateFrom,
|
||||
final int updateTo
|
||||
) {
|
||||
for (int updateIndex = updateFrom; updateIndex < updateTo; ++updateIndex) {
|
||||
if (this.redundantUpdates.get(updateIndex)) {
|
||||
continue;
|
||||
}
|
||||
BlockPos updatePos = this.updates.get(updateIndex);
|
||||
final BlockPos updatePos = this.neighborUpdates.get(updateIndex);
|
||||
//noinspection ConstantValue
|
||||
if (updatePos != null) {
|
||||
level.getBlockState(updatePos).handleNeighborChanged(level, updatePos, wireBlock, orientation, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyFromCache(Level level) {
|
||||
public boolean applyFromCache(final Level level) {
|
||||
this.expiry.refresh(level.getGameTime());
|
||||
if (!this.isRegistered() || !this.verifyWiresInNetwork(level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, null, null);
|
||||
RedStoneWireBlock wireBlock = (RedStoneWireBlock) Blocks.REDSTONE_WIRE;
|
||||
final Orientation defaultOrientation = ExperimentalRedstoneUtils.initialOrientation(level, null, null);
|
||||
final RedStoneWireBlock wireBlock = (RedStoneWireBlock) Blocks.REDSTONE_WIRE;
|
||||
int updateFrom = 0;
|
||||
|
||||
for (RedstoneWireUpdate wireUpdate : this.wireUpdates) {
|
||||
// Apply all wire updates in the network
|
||||
for (final RedstoneWireUpdate wireUpdate : this.wireUpdates) {
|
||||
if (wireUpdate.canSkipWireUpdate()) {
|
||||
updateFrom = wireUpdate.getUpdateIndex();
|
||||
continue;
|
||||
}
|
||||
|
||||
int updateTo = wireUpdate.getUpdateIndex();
|
||||
this.performUpdates(level, orientation, wireBlock, updateFrom, updateTo);
|
||||
final int updateTo = wireUpdate.getUpdateIndex();
|
||||
this.performUpdates(level, defaultOrientation, wireBlock, updateFrom, updateTo);
|
||||
updateFrom = updateTo;
|
||||
|
||||
BlockPos wirePos = wireUpdate.getPosition();
|
||||
BlockState state = level.getBlockState(wirePos);
|
||||
BlockState newState = state.setValue(RedStoneWireBlock.POWER, wireUpdate.getPower());
|
||||
final BlockPos wirePos = wireUpdate.getPosition();
|
||||
final BlockState state = level.getBlockState(wirePos);
|
||||
final BlockState newState = state.setValue(RedStoneWireBlock.POWER, wireUpdate.getPower());
|
||||
|
||||
// Update the wire power and apply shape updates when needed
|
||||
if (level.setBlock(wirePos, newState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE)) {
|
||||
if (wireUpdate.needsShapeUpdate()) {
|
||||
wireBlock.turbo.updateNeighborShapes(level, wirePos, newState);
|
||||
@@ -201,7 +216,7 @@ public final class RedstoneNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
this.performUpdates(level, orientation, wireBlock, updateFrom, this.updates.size());
|
||||
this.performUpdates(level, defaultOrientation, wireBlock, updateFrom, this.neighborUpdates.size());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,27 @@ import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public record RedstoneNetworkSource(WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation,
|
||||
BlockPos position, @Nullable Orientation orientation,
|
||||
int updateDepth, int newPower, int oldPower) {
|
||||
|
||||
public static RedstoneNetworkSource createNetworkSource(Level level, CachedLocalConfiguration localConfiguration, BlockPos pos,
|
||||
@Nullable Orientation orientation, int newPower, int oldPower) {
|
||||
int updateDepth = level.neighborUpdater.getUpdateDepth();
|
||||
public record RedstoneNetworkSource(
|
||||
WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation,
|
||||
BlockPos position,
|
||||
@Nullable Orientation orientation,
|
||||
int updateDepth,
|
||||
int newPower,
|
||||
int oldPower
|
||||
) {
|
||||
public static RedstoneNetworkSource createNetworkSource(
|
||||
final Level level,
|
||||
final CachedLocalConfiguration localConfiguration,
|
||||
final BlockPos pos,
|
||||
final @Nullable Orientation orientation,
|
||||
final int newPower,
|
||||
final int oldPower
|
||||
) {
|
||||
final int updateDepth = level.neighborUpdater.getUpdateDepth();
|
||||
return new RedstoneNetworkSource(localConfiguration.paperRedstoneImplementation(), pos, orientation, updateDepth, newPower, oldPower);
|
||||
}
|
||||
|
||||
public boolean isVanilla() {
|
||||
return this.redstoneImplementation == WorldConfiguration.Misc.RedstoneImplementation.VANILLA;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.redstone.NeighborUpdater;
|
||||
import net.minecraft.world.level.redstone.Orientation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -24,15 +25,20 @@ public final class RedstoneWireCache {
|
||||
private final Level level;
|
||||
private @Nullable RedstoneNetwork updatingNetwork;
|
||||
|
||||
public RedstoneWireCache(Level level) {
|
||||
public RedstoneWireCache(final Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public Map<RedstoneNetworkSource, RedstoneNetwork> getNetworkCache() {
|
||||
return this.networkCache;
|
||||
}
|
||||
|
||||
public boolean isWireUpdating(BlockPos pos) {
|
||||
public boolean isApplyingCache() {
|
||||
return this.updatingNetwork != null;
|
||||
}
|
||||
|
||||
public boolean isUpdatingRedstoneWire(final BlockPos pos) {
|
||||
return this.updatingNetwork != null && this.updatingNetwork.hasWire(pos);
|
||||
}
|
||||
|
||||
@@ -40,62 +46,52 @@ public final class RedstoneWireCache {
|
||||
return this.networkSource != null;
|
||||
}
|
||||
|
||||
public boolean tryApplyFromCache(BlockPos pos, @Nullable Orientation orientation, int newPower, int oldPower) {
|
||||
final CachedLocalConfiguration localConfiguration = this.level.localConfig().at(pos);
|
||||
if (!localConfiguration.redstoneBehaviour.cache() || this.isTrackingWireUpdates()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore any wire changes while updating the network.
|
||||
if (this.updatingNetwork != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(this.level, localConfiguration, pos, orientation, newPower, oldPower);
|
||||
RedstoneNetwork network = this.networkCache.get(networkSource);
|
||||
if (network != null) {
|
||||
try {
|
||||
this.updatingNetwork = network;
|
||||
return network.applyFromCache(this.level);
|
||||
} finally {
|
||||
this.updatingNetwork = null;
|
||||
}
|
||||
} else {
|
||||
// Start tracking power and neighbor updates
|
||||
this.networkSource = networkSource;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void trackWirePower(BlockPos pos, int newPower, int oldPower) {
|
||||
public void trackWirePower(final BlockPos pos, final int newPower, final int oldPower) {
|
||||
if (this.isTrackingWireUpdates()) {
|
||||
this.originalWirePower.putIfAbsent(pos, new RedstoneOriginalPower(oldPower, newPower));
|
||||
this.wireUpdates.add(new RedstoneWireUpdate(pos, newPower, this.updates.size()));
|
||||
}
|
||||
}
|
||||
|
||||
public void trackNeighbor(BlockPos pos) {
|
||||
public void trackNeighbor(final BlockPos pos) {
|
||||
if (this.isTrackingWireUpdates()) {
|
||||
this.updates.add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void trackNeighborsAt(BlockPos pos) {
|
||||
public void trackNeighborsAt(final BlockPos pos) {
|
||||
if (this.isTrackingWireUpdates()) {
|
||||
for (Direction neighbor : NeighborUpdater.UPDATE_ORDER) {
|
||||
for (final Direction neighbor : NeighborUpdater.UPDATE_ORDER) {
|
||||
this.updates.add(pos.relative(neighbor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void expire(long tick) {
|
||||
this.networkCache.values().removeIf(network -> {
|
||||
if (network.getExpiry().isExpired(tick)) {
|
||||
network.invalidate(this.level);
|
||||
return true;
|
||||
}
|
||||
public boolean applyFromCache(final BlockPos pos, final @Nullable Orientation orientation, final int newPower, final int oldPower) {
|
||||
final CachedLocalConfiguration localConfiguration = this.level.localConfig().at(pos);
|
||||
if (!localConfiguration.redstoneBehaviour.cache() || this.isTrackingWireUpdates()) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
final RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(
|
||||
this.level, localConfiguration, pos, orientation, newPower, oldPower
|
||||
);
|
||||
final RedstoneNetwork network = this.networkCache.get(networkSource);
|
||||
|
||||
// Try to apply a network cache if one does not exist then start tracking wire updates
|
||||
if (network != null) {
|
||||
try {
|
||||
this.updatingNetwork = network;
|
||||
return network.applyFromCache(this.level);
|
||||
} finally {
|
||||
this.updatingNetwork = null;
|
||||
this.networkSource = null; // applying a cache while tracking can cause issues
|
||||
}
|
||||
} else {
|
||||
// Start tracking wire updates
|
||||
this.networkSource = networkSource;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTracking() {
|
||||
@@ -103,9 +99,9 @@ public final class RedstoneWireCache {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache expires if it has not been used in 600 ticks
|
||||
TickExpiry expiration = new TickExpiry(this.level.getGameTime(), 600);
|
||||
RedstoneNetwork redstoneNetwork = new RedstoneNetwork(
|
||||
// The cache will expire if it has not been used in 600 ticks
|
||||
final TickExpiry expiration = new TickExpiry(this.level.getGameTime(), 600);
|
||||
final RedstoneNetwork redstoneNetwork = new RedstoneNetwork(
|
||||
this.wireUpdates, this.updates, this.originalWirePower, expiration
|
||||
);
|
||||
|
||||
@@ -118,4 +114,15 @@ public final class RedstoneWireCache {
|
||||
this.originalWirePower.clear();
|
||||
this.networkSource = null;
|
||||
}
|
||||
|
||||
public void expire(final long tick) {
|
||||
this.networkCache.values().removeIf(network -> {
|
||||
if (network.getExpiry().isExpired(tick)) {
|
||||
network.invalidate(this.level);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public final class RedstoneWireUpdate {
|
||||
private boolean updateShape;
|
||||
private boolean skipWire;
|
||||
|
||||
public RedstoneWireUpdate(BlockPos position, int power, int updateIndex) {
|
||||
public RedstoneWireUpdate(final BlockPos position, final int power, final int updateIndex) {
|
||||
this.position = position;
|
||||
this.power = power;
|
||||
this.updateIndex = updateIndex;
|
||||
|
||||
@@ -8,16 +8,24 @@ import net.kyori.adventure.text.format.TextColor;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public record ServerTickInformation(long identifier, double tps, double averageTick, long longestTick, float targetTickRate, int chunks, int entities) {
|
||||
public static final ServerTickInformation FILLER = new ServerTickInformation(0, 0.0, 0.0, 0, 0.0f, 0, 0);
|
||||
public record ServerTickInformation(
|
||||
long identifier,
|
||||
double tps,
|
||||
double averageTick,
|
||||
long longestTick,
|
||||
float targetTickRate,
|
||||
int chunks,
|
||||
int entities
|
||||
) {
|
||||
public static final ServerTickInformation UNKNOWN = new ServerTickInformation(0, 0.0, 0.0, 0, 0.0f, 0, 0);
|
||||
|
||||
public TextColor colour() {
|
||||
float lag = (float) this.tps / this.targetTickRate;
|
||||
return GraphComponents.colour(lag);
|
||||
final float tpsLoss = (float) this.tps / this.targetTickRate;
|
||||
return GraphComponents.colour(tpsLoss);
|
||||
}
|
||||
|
||||
public Component hoverComponent(TextColor colour) {
|
||||
TextComponent.Builder builder = Component.text();
|
||||
public Component hoverComponent(final TextColor colour) {
|
||||
final TextComponent.Builder builder = Component.text();
|
||||
builder.append(Component.text("TPS: ")
|
||||
.append(Component.text("%.1f".formatted(this.tps), colour)));
|
||||
builder.appendNewline();
|
||||
|
||||
@@ -21,51 +21,53 @@ public final class TickInformationCollector {
|
||||
return this.collectedInformation.getLast();
|
||||
}
|
||||
|
||||
public void levelData(Collection<ServerLevel> levels, double tps) {
|
||||
public void levelData(final Collection<ServerLevel> levels, final double tps) {
|
||||
int chunks = 0;
|
||||
int entities = 0;
|
||||
for (ServerLevel level : levels) {
|
||||
for (final ServerLevel level : levels) {
|
||||
chunks += level.chunkSource.getFullChunksCount();
|
||||
entities += level.entityTickList.entities.size();
|
||||
}
|
||||
|
||||
double averageTick = this.tickSamples.longStream()
|
||||
final double averageTick = this.tickSamples.longStream()
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
long longestTick = this.tickSamples.longStream()
|
||||
final long longestTick = this.tickSamples.longStream()
|
||||
.max()
|
||||
.orElse(0);
|
||||
float targetTickRate = MinecraftServer.getServer().tickRateManager().tickrate();
|
||||
|
||||
ServerTickInformation tickInformation = new ServerTickInformation(
|
||||
final float targetTickRate = MinecraftServer.getServer().tickRateManager().tickrate();
|
||||
final ServerTickInformation tickInformation = new ServerTickInformation(
|
||||
this.identifier++, tps, averageTick, longestTick, targetTickRate, chunks, entities
|
||||
);
|
||||
|
||||
this.collectedInformation.add(tickInformation);
|
||||
this.tickSamples.clear();
|
||||
|
||||
// Keep only the last 10 minutes of tick information
|
||||
if (this.collectedInformation.size() > TEN_MINUTES) {
|
||||
this.collectedInformation.subList(0, 60).clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void tickDuration(long timeTaken) {
|
||||
public void tickDuration(final long timeTaken) {
|
||||
this.tickSamples.add(timeTaken);
|
||||
}
|
||||
|
||||
public ImmutableList<ServerTickInformation> collect(long from, long to) {
|
||||
List<ServerTickInformation> collected = new ObjectArrayList<>();
|
||||
for (ServerTickInformation tickInformation : this.collectedInformation.reversed()) {
|
||||
public ImmutableList<ServerTickInformation> collect(final long from, final long to) {
|
||||
final List<ServerTickInformation> collected = new ObjectArrayList<>();
|
||||
for (final ServerTickInformation tickInformation : this.collectedInformation.reversed()) {
|
||||
if (tickInformation.identifier() >= from && tickInformation.identifier() < to) {
|
||||
collected.add(tickInformation);
|
||||
}
|
||||
}
|
||||
long ahead = to - this.identifier;
|
||||
long missing = to - from - collected.size();
|
||||
for (int i = 0; i < missing; ++i) {
|
||||
int ind = (i < ahead) ? 0 : collected.size();
|
||||
collected.add(ind, ServerTickInformation.FILLER);
|
||||
|
||||
final long ahead = to - this.identifier;
|
||||
final long missing = to - from - collected.size();
|
||||
for (int remaining = 0; remaining < missing; ++remaining) {
|
||||
final int index = (remaining < ahead) ? 0 : collected.size();
|
||||
collected.add(index, ServerTickInformation.UNKNOWN);
|
||||
}
|
||||
|
||||
return ImmutableList.copyOf(collected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,23 @@ import java.util.List;
|
||||
public final class BuiltComponentCanvas {
|
||||
private final List<Component> components;
|
||||
|
||||
BuiltComponentCanvas(List<Component> components) {
|
||||
BuiltComponentCanvas(final List<Component> components) {
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
public void appendLeft(Component component) {
|
||||
public void appendLeft(final Component component) {
|
||||
this.components.replaceAll(component::append);
|
||||
}
|
||||
|
||||
public void appendRight(Component component) {
|
||||
public void appendRight(final Component component) {
|
||||
this.components.replaceAll(row -> row.append(component));
|
||||
}
|
||||
|
||||
public void header(Component component) {
|
||||
public void header(final Component component) {
|
||||
this.components.addFirst(component);
|
||||
}
|
||||
|
||||
public void footer(Component component) {
|
||||
public void footer(final Component component) {
|
||||
this.components.add(component);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public final class ComponentCanvas {
|
||||
private final int height;
|
||||
private final Component[][] components;
|
||||
|
||||
public ComponentCanvas(int width, int height) {
|
||||
public ComponentCanvas(final int width, final int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
// [x, y] is flipped as it makes converting the components into a list easier
|
||||
@@ -24,7 +24,7 @@ public final class ComponentCanvas {
|
||||
public void flip() {
|
||||
for (int y = 0; y < this.height; ++y) {
|
||||
if (y >= this.height / 2) {
|
||||
Component[] row = this.components[y];
|
||||
final Component[] row = this.components[y];
|
||||
int relocatingRow = this.height - 1 - y;
|
||||
this.components[y] = this.components[relocatingRow];
|
||||
this.components[relocatingRow] = row;
|
||||
@@ -32,7 +32,7 @@ public final class ComponentCanvas {
|
||||
}
|
||||
}
|
||||
|
||||
public void fill(Component component) {
|
||||
public void fill(final Component component) {
|
||||
for (int x = 0; x < this.width; ++x) {
|
||||
for (int y = 0; y < this.height; ++y) {
|
||||
this.set(x, y, component);
|
||||
@@ -40,12 +40,12 @@ public final class ComponentCanvas {
|
||||
}
|
||||
}
|
||||
|
||||
public Component get(int x, int y) {
|
||||
Component component = this.components[y][x];
|
||||
public Component get(final int x, final int y) {
|
||||
final Component component = this.components[y][x];
|
||||
return Preconditions.checkNotNull(component, "missing component at x:{} y:{}", x, y);
|
||||
}
|
||||
|
||||
public void set(int x, int y, Component component) {
|
||||
public void set(final int x, final int y, final Component component) {
|
||||
this.components[y][x] = component;
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ public final class ComponentCanvas {
|
||||
}
|
||||
|
||||
private List<Component> joinComponents() {
|
||||
List<Component> componentList = new ObjectArrayList<>(this.height);
|
||||
for (Component[] row : this.components) {
|
||||
final List<Component> componentList = new ObjectArrayList<>(this.height);
|
||||
for (final Component[] row : this.components) {
|
||||
componentList.add(Component.join(JoinConfiguration.noSeparators(), row));
|
||||
}
|
||||
return componentList;
|
||||
|
||||
@@ -7,13 +7,18 @@ import java.util.List;
|
||||
|
||||
@NullMarked
|
||||
public final class DetailedTPSGraph extends TPSGraph {
|
||||
public DetailedTPSGraph(int width, int height, double scale, List<ServerTickInformation> tickInformation) {
|
||||
public DetailedTPSGraph(
|
||||
final int width,
|
||||
final int height,
|
||||
final double scale,
|
||||
final List<ServerTickInformation> tickInformation
|
||||
) {
|
||||
super(width, height, scale, tickInformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BuiltComponentCanvas plot() {
|
||||
ComponentCanvas canvas = new ComponentCanvas(this.width, this.height);
|
||||
final ComponentCanvas canvas = new ComponentCanvas(this.width, this.height);
|
||||
canvas.fill(GraphComponents.BACKGROUND);
|
||||
|
||||
this.basicOutline(canvas);
|
||||
@@ -24,12 +29,12 @@ public final class DetailedTPSGraph extends TPSGraph {
|
||||
return canvas.build();
|
||||
}
|
||||
|
||||
private void basicOutline(ComponentCanvas canvas) {
|
||||
private void basicOutline(final ComponentCanvas canvas) {
|
||||
for (int x = 0; x < this.width; ++x) {
|
||||
int row = this.rowFromColumn(x);
|
||||
int nextRow = this.rowFromColumn(x + 1);
|
||||
int minRow = Math.min(row, nextRow);
|
||||
int maxRow = Math.max(row, nextRow);
|
||||
final int row = this.rowFromColumn(x);
|
||||
final int nextRow = this.rowFromColumn(x + 1);
|
||||
final int minRow = Math.min(row, nextRow);
|
||||
final int maxRow = Math.max(row, nextRow);
|
||||
|
||||
if (maxRow - minRow >= 2) {
|
||||
canvas.set(x, minRow, GraphComponents.TOP_DOTTED_LINE);
|
||||
@@ -44,13 +49,13 @@ public final class DetailedTPSGraph extends TPSGraph {
|
||||
}
|
||||
}
|
||||
|
||||
private void prettifyOutline(ComponentCanvas canvas) {
|
||||
private void prettifyOutline(final ComponentCanvas canvas) {
|
||||
for (int x = 0; x < this.width; ++x) {
|
||||
int row = this.rowFromColumn(x);
|
||||
int nextRow = this.rowFromColumn(x + 1);
|
||||
int prevRow = this.rowFromColumn(x - 1);
|
||||
int minRow = Math.min(row, nextRow);
|
||||
int maxRow = Math.max(row, nextRow);
|
||||
final int row = this.rowFromColumn(x);
|
||||
final int nextRow = this.rowFromColumn(x + 1);
|
||||
final int prevRow = this.rowFromColumn(x - 1);
|
||||
final int minRow = Math.min(row, nextRow);
|
||||
final int maxRow = Math.max(row, nextRow);
|
||||
|
||||
if (maxRow - minRow >= 2) {
|
||||
this.prettifyVerticalOutline(canvas, x, row, nextRow, prevRow, minRow, maxRow);
|
||||
@@ -60,7 +65,15 @@ public final class DetailedTPSGraph extends TPSGraph {
|
||||
}
|
||||
}
|
||||
|
||||
private void prettifyVerticalOutline(ComponentCanvas canvas, int x, int row, int nextRow, int prevRow, int minRow, int maxRow) {
|
||||
private void prettifyVerticalOutline(
|
||||
final ComponentCanvas canvas,
|
||||
final int x,
|
||||
final int row,
|
||||
final int nextRow,
|
||||
final int prevRow,
|
||||
final int minRow,
|
||||
final int maxRow
|
||||
) {
|
||||
if (minRow == nextRow) {
|
||||
canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_LEFT);
|
||||
} else if (prevRow <= minRow) {
|
||||
@@ -79,9 +92,15 @@ public final class DetailedTPSGraph extends TPSGraph {
|
||||
}
|
||||
}
|
||||
|
||||
private void prettifySlopes(ComponentCanvas canvas, int x, int row, int nextRow, int prevRow) {
|
||||
int slopeDirection = nextRow - prevRow;
|
||||
int slopeChange = Math.abs(slopeDirection);
|
||||
private void prettifySlopes(
|
||||
final ComponentCanvas canvas,
|
||||
final int x,
|
||||
final int row,
|
||||
final int nextRow,
|
||||
final int prevRow
|
||||
) {
|
||||
final int slopeDirection = nextRow - prevRow;
|
||||
final int slopeChange = Math.abs(slopeDirection);
|
||||
|
||||
if (slopeChange >= 2 && Math.max(nextRow, prevRow) == row + 1) {
|
||||
canvas.set(x, row, slopeDirection < 0 ? GraphComponents.TL_TO_BR : GraphComponents.BL_TO_TR);
|
||||
|
||||
@@ -5,9 +5,11 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@NullMarked
|
||||
public final class GraphComponents {
|
||||
private static final Style STRIKE_THROUGH_STYLE = Style.style(TextDecoration.STRIKETHROUGH);
|
||||
private static final Style REMOVE_STRIKE_THROUGH_STYLE = Style.style(TextDecoration.STRIKETHROUGH.withState(false));
|
||||
@@ -29,14 +31,14 @@ public final class GraphComponents {
|
||||
NamedTextColor.RED, NamedTextColor.DARK_GRAY, TextColor.color(40, 40, 40)
|
||||
);
|
||||
|
||||
public static TextColor colour(float num) {
|
||||
float segment = 1.0f / COLOURS.size();
|
||||
float a = (1.0f - num) / segment;
|
||||
float t = a % 1.0f;
|
||||
int startIndex = Math.clamp((int) a, 0, COLOURS.size() - 2);
|
||||
int endIndex = startIndex + 1;
|
||||
TextColor startColour = COLOURS.get(startIndex);
|
||||
TextColor endColour = COLOURS.get(endIndex);
|
||||
public static TextColor colour(final float num) {
|
||||
final float segment = 1.0f / COLOURS.size();
|
||||
final float a = (1.0f - num) / segment;
|
||||
final float t = a % 1.0f;
|
||||
final int startIndex = Math.clamp((int) a, 0, COLOURS.size() - 2);
|
||||
final int endIndex = startIndex + 1;
|
||||
final TextColor startColour = COLOURS.get(startIndex);
|
||||
final TextColor endColour = COLOURS.get(endIndex);
|
||||
return TextColor.lerp(t, startColour, endColour);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public abstract class TPSGraph {
|
||||
protected final int height;
|
||||
protected final double scale;
|
||||
|
||||
public TPSGraph(int width, int height, double scale, List<ServerTickInformation> tickInformation) {
|
||||
public TPSGraph(final int width, final int height, final double scale, final List<ServerTickInformation> tickInformation) {
|
||||
Preconditions.checkArgument(tickInformation.size() == width);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
@@ -28,23 +28,23 @@ public abstract class TPSGraph {
|
||||
|
||||
public abstract BuiltComponentCanvas plot();
|
||||
|
||||
protected final int rowFromColumn(int x) {
|
||||
int clamped = Math.clamp(x, 0, this.width - 1);
|
||||
ServerTickInformation tickInformation = this.tickInformation.get(clamped);
|
||||
protected final int rowFromColumn(final int x) {
|
||||
final int clamped = Math.clamp(x, 0, this.width - 1);
|
||||
final ServerTickInformation tickInformation = this.tickInformation.get(clamped);
|
||||
return this.rowFromTPS(tickInformation.tps());
|
||||
}
|
||||
|
||||
protected final int rowFromTPS(double tps) {
|
||||
int row = Mth.floor((tps / 3) * this.scale);
|
||||
protected final int rowFromTPS(final double tps) {
|
||||
final int row = Mth.floor((tps / 3) * this.scale);
|
||||
return Mth.clamp(row, 0, this.height - 1);
|
||||
}
|
||||
|
||||
protected final void addColourAndHoverInformation(ComponentCanvas canvas) {
|
||||
protected final void addColourAndHoverInformation(final ComponentCanvas canvas) {
|
||||
for (int x = 0; x < this.width; ++x) {
|
||||
ServerTickInformation tickInformation = this.tickInformation.get(x);
|
||||
TextColor colourFromTPS = tickInformation.colour();
|
||||
Component hoverComponent = tickInformation.hoverComponent(colourFromTPS);
|
||||
HoverEvent<Component> hoverEvent = HoverEvent.showText(hoverComponent);
|
||||
final ServerTickInformation tickInformation = this.tickInformation.get(x);
|
||||
final TextColor colourFromTPS = tickInformation.colour();
|
||||
final Component hoverComponent = tickInformation.hoverComponent(colourFromTPS);
|
||||
final HoverEvent<Component> hoverEvent = HoverEvent.showText(hoverComponent);
|
||||
|
||||
for (int y = 0; y < this.height; ++y) {
|
||||
Component component = canvas.get(x, y);
|
||||
|
||||
@@ -19,19 +19,19 @@ public final class BlockPosIterator extends AbstractIterator<BlockPos> {
|
||||
private final int endZ;
|
||||
private @Nullable MutableBlockPos pos = null;
|
||||
|
||||
public static Iterable<BlockPos> iterable(AABB bb) {
|
||||
public static Iterable<BlockPos> iterable(final AABB bb) {
|
||||
return () -> new BlockPosIterator(bb);
|
||||
}
|
||||
|
||||
public static Iterable<BlockPos> traverseArea(Vec3 vec, AABB boundingBox) {
|
||||
double toTravel = Math.min(16.0 / vec.length(), 1.0);
|
||||
Vec3 movement = vec.scale(toTravel);
|
||||
AABB fromBB = boundingBox.move(-vec.x, -vec.y, -vec.z);
|
||||
AABB searchArea = fromBB.expandTowards(movement);
|
||||
return me.samsuik.sakura.utils.BlockPosIterator.iterable(searchArea);
|
||||
public static Iterable<BlockPos> traverseArea(final Vec3 vec, final AABB boundingBox) {
|
||||
final double toTravel = Math.min(16.0 / vec.length(), 1.0);
|
||||
final Vec3 movement = vec.scale(toTravel);
|
||||
final AABB fromBB = boundingBox.move(-vec.x, -vec.y, -vec.z);
|
||||
final AABB searchArea = fromBB.expandTowards(movement);
|
||||
return BlockPosIterator.iterable(searchArea);
|
||||
}
|
||||
|
||||
public BlockPosIterator(AABB bb) {
|
||||
public BlockPosIterator(final AABB bb) {
|
||||
this.startX = Mth.floor(bb.minX);
|
||||
this.startY = Mth.floor(bb.minY);
|
||||
this.startZ = Mth.floor(bb.minZ);
|
||||
@@ -42,7 +42,7 @@ public final class BlockPosIterator extends AbstractIterator<BlockPos> {
|
||||
|
||||
@Override
|
||||
protected BlockPos computeNext() {
|
||||
MutableBlockPos pos = this.pos;
|
||||
final MutableBlockPos pos = this.pos;
|
||||
if (pos == null) {
|
||||
return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ);
|
||||
} else {
|
||||
|
||||
@@ -8,17 +8,17 @@ public final class TickExpiry {
|
||||
private long tick;
|
||||
private final int expiration;
|
||||
|
||||
public TickExpiry(long tick, int expiration) {
|
||||
Preconditions.checkArgument(expiration > 0, "expiration must be greater than 0");
|
||||
public TickExpiry(final long tick, final int expiration) {
|
||||
Preconditions.checkArgument(expiration > 0, "Expiration cannot be lower or equal to 0");
|
||||
this.tick = tick;
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public void refresh(long tick) {
|
||||
public void refresh(final long tick) {
|
||||
this.tick = tick;
|
||||
}
|
||||
|
||||
public boolean isExpired(long tick) {
|
||||
public boolean isExpired(final long tick) {
|
||||
return this.tick < tick - this.expiration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.samsuik.sakura.utils.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@NullMarked
|
||||
public final class BlockPosToEntityTable {
|
||||
private final @Nullable Entity[] entities;
|
||||
private final int mask;
|
||||
|
||||
public BlockPosToEntityTable(final int expectedSize) {
|
||||
if (expectedSize < 0) {
|
||||
throw new IllegalArgumentException("Table size cannot be negative");
|
||||
} else {
|
||||
final int size = HashCommon.nextPowerOfTwo(expectedSize - 1);
|
||||
this.entities = new Entity[size];
|
||||
this.mask = (size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Entity get(final BlockPos blockPos) {
|
||||
return this.entities[this.key(blockPos)];
|
||||
}
|
||||
|
||||
public @Nullable Entity put(final Entity entity) {
|
||||
return this.put(entity.blockPosition(), entity);
|
||||
}
|
||||
|
||||
public @Nullable Entity put(final BlockPos blockPos, final @Nullable Entity entity) {
|
||||
final int index = this.key(blockPos);
|
||||
final Entity present = this.entities[index];
|
||||
this.entities[index] = entity;
|
||||
return present;
|
||||
}
|
||||
|
||||
public @Nullable Entity remove(final BlockPos blockPos) {
|
||||
return this.put(blockPos, null);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
Arrays.fill(this.entities, null);
|
||||
}
|
||||
|
||||
private int key(final BlockPos blockPos) {
|
||||
return blockPos.hashCode() & this.mask;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package me.samsuik.sakura.utils.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
public final class FixedSizeCustomObjectTable<T> {
|
||||
private final ToIntFunction<T> keyFunction;
|
||||
private final T[] contents;
|
||||
private final int mask;
|
||||
|
||||
public FixedSizeCustomObjectTable(int size, @NotNull ToIntFunction<T> keyFunction) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("Table size cannot be negative");
|
||||
} else {
|
||||
int n = HashCommon.nextPowerOfTwo(size - 1);
|
||||
this.keyFunction = keyFunction;
|
||||
this.contents = (T[]) new Object[n];
|
||||
this.mask = (n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int key(T value) {
|
||||
return this.keyFunction.applyAsInt(value);
|
||||
}
|
||||
|
||||
public @Nullable T get(T value) {
|
||||
return this.get(this.key(value));
|
||||
}
|
||||
|
||||
public @Nullable T get(int key) {
|
||||
return this.contents[key & this.mask];
|
||||
}
|
||||
|
||||
public void write(int key, T value) {
|
||||
this.contents[key & this.mask] = value;
|
||||
}
|
||||
|
||||
public @Nullable T getAndWrite(T value) {
|
||||
int key = this.key(value);
|
||||
T found = this.get(key);
|
||||
this.write(key, value);
|
||||
return found;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
int size = this.contents.length;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
this.contents[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package me.samsuik.sakura.utils.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
public final class OrderedComparatorList<T> extends ObjectArrayList<T> {
|
||||
private final Comparator<T> comparator;
|
||||
private boolean binarySearch = true;
|
||||
|
||||
public OrderedComparatorList(int capacity, Comparator<T> comparator) {
|
||||
super(capacity);
|
||||
this.comparator = Comparator.nullsLast(comparator);
|
||||
}
|
||||
|
||||
public OrderedComparatorList(Comparator<T> comparator) {
|
||||
this(DEFAULT_INITIAL_CAPACITY, comparator);
|
||||
}
|
||||
|
||||
private void validateBounds(int index, T t, boolean up) {
|
||||
if (index != 0 && this.comparator.compare(get(index - 1), t) > 0) {
|
||||
this.binarySearch = false;
|
||||
} else if (up && index < size() - 1 && this.comparator.compare(get(index + 1), t) < 0) {
|
||||
this.binarySearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
this.validateBounds(size(), t, false);
|
||||
return super.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T t) {
|
||||
this.validateBounds(index, t, true);
|
||||
super.add(index, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(final Object k) {
|
||||
if (this.binarySearch) {
|
||||
return Math.max(Arrays.binarySearch(this.a, (T) k, this.comparator), -1);
|
||||
} else {
|
||||
return super.indexOf(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,68 @@
|
||||
package me.samsuik.sakura.utils.collections;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.AbstractObjectCollection;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectCollection;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
@NullMarked
|
||||
public final class TrackedEntityChunkMap extends Int2ObjectOpenHashMap<ChunkMap.TrackedEntity> {
|
||||
private final ObjectArrayList<ChunkMap.TrackedEntity> entityList = new UnorderedIndexedList<>();
|
||||
private final ReferenceList<ChunkMap.TrackedEntity> trackedEntityList = new ReferenceList<>();
|
||||
|
||||
@Override
|
||||
public ChunkMap.TrackedEntity put(int k, ChunkMap.TrackedEntity trackedEntity) {
|
||||
ChunkMap.TrackedEntity tracked = super.put(k, trackedEntity);
|
||||
public ChunkMap.TrackedEntity put(final int index, final ChunkMap.TrackedEntity trackedEntity) {
|
||||
final ChunkMap.TrackedEntity tracked = super.put(index, trackedEntity);
|
||||
//noinspection ConstantValue
|
||||
if (tracked != null) {
|
||||
this.entityList.remove(trackedEntity);
|
||||
this.trackedEntityList.remove(trackedEntity);
|
||||
}
|
||||
this.entityList.add(trackedEntity);
|
||||
this.trackedEntityList.add(trackedEntity);
|
||||
return tracked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkMap.TrackedEntity remove(int k) {
|
||||
ChunkMap.TrackedEntity tracked = super.remove(k);
|
||||
this.entityList.remove(tracked);
|
||||
public ChunkMap.TrackedEntity remove(final int index) {
|
||||
final ChunkMap.TrackedEntity tracked = super.remove(index);
|
||||
this.trackedEntityList.remove(tracked);
|
||||
return tracked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectCollection<ChunkMap.TrackedEntity> values() {
|
||||
return this.entityList;
|
||||
return new AbstractObjectCollection<>() {
|
||||
@Override
|
||||
public ObjectIterator<ChunkMap.TrackedEntity> iterator() {
|
||||
return new TrackedEntityIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return TrackedEntityChunkMap.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final class TrackedEntityIterator implements ObjectIterator<ChunkMap.TrackedEntity> {
|
||||
private final Iterator<ChunkMap.TrackedEntity> backingItr = TrackedEntityChunkMap.this.trackedEntityList.iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.backingItr.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkMap.TrackedEntity next() {
|
||||
return this.backingItr.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.backingItr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package me.samsuik.sakura.utils.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
public final class UnorderedIndexedList<T> extends ObjectArrayList<T> {
|
||||
private final Int2IntOpenHashMap elementToIndex;
|
||||
|
||||
public UnorderedIndexedList() {
|
||||
this(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
public UnorderedIndexedList(int capacity) {
|
||||
super(capacity);
|
||||
this.elementToIndex = new Int2IntOpenHashMap();
|
||||
this.elementToIndex.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(final T t) {
|
||||
this.elementToIndex.put(t.hashCode(), size());
|
||||
return super.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(final int index) {
|
||||
final int tail = size() - 1;
|
||||
final T at = a[index];
|
||||
|
||||
if (index != tail) {
|
||||
final T tailObj = a[tail];
|
||||
if (tailObj != null)
|
||||
this.elementToIndex.put(tailObj.hashCode(), index);
|
||||
this.a[index] = tailObj;
|
||||
}
|
||||
|
||||
if (at != null)
|
||||
this.elementToIndex.remove(at.hashCode());
|
||||
this.a[tail] = null;
|
||||
this.size = tail;
|
||||
return at;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.elementToIndex.clear();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(final Object k) {
|
||||
if (k == null) return -1;
|
||||
// entities uses their id as a hashcode
|
||||
return this.elementToIndex.get(k.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(final int index, final T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user