9
0
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:
Samsuik
2025-09-25 00:18:10 +01:00
parent 65bc3daa96
commit 7dfd66c10c
142 changed files with 1737 additions and 1619 deletions

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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 = "";
}

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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))
);

View File

@@ -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>.

View File

@@ -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> {

View File

@@ -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()) {

View File

@@ -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();
}
}

View File

@@ -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());
}

View File

@@ -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());
}

View File

@@ -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()));
}
}

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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()));
}
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
});
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}