9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-30 12:19:08 +00:00

Start on updating 1.21.4

This commit is contained in:
Samsuik
2025-01-14 23:02:38 +00:00
parent 6225f7d928
commit 24d7230079
101 changed files with 189 additions and 112 deletions

View File

@@ -0,0 +1,941 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 21 Sep 2021 23:54:25 +0100
Subject: [PATCH] Client Visibility Settings
diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
index 3e85c19db0655034c203eaab8d2e6b5504da5da8..cbb2e57f9ab3b48a6e5f792711c4c6fd2d34d445 100644
--- a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
+++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
@@ -1,6 +1,9 @@
package me.samsuik.sakura.command;
import me.samsuik.sakura.command.subcommands.ConfigCommand;
+import me.samsuik.sakura.command.subcommands.FPSCommand;
+import me.samsuik.sakura.command.subcommands.VisualCommand;
+import me.samsuik.sakura.player.visibility.VisibilityTypes;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command;
@@ -12,6 +15,9 @@ public final class SakuraCommands {
static {
COMMANDS.put("sakura", new SakuraCommand("sakura"));
COMMANDS.put("config", new ConfigCommand("config"));
+ COMMANDS.put("fps", new FPSCommand("fps"));
+ COMMANDS.put("tntvisibility", new VisualCommand(VisibilityTypes.TNT, "tnttoggle"));
+ COMMANDS.put("sandvisibility", new VisualCommand(VisibilityTypes.SAND, "sandtoggle"));
}
public static void registerCommands(MinecraftServer server) {
diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..a85f84ebfef4ed3f3dc2a337a057d541f64c9d0e
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java
@@ -0,0 +1,24 @@
+package me.samsuik.sakura.command.subcommands;
+
+import me.samsuik.sakura.command.BaseSubCommand;
+import me.samsuik.sakura.player.visibility.VisibilityGui;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class FPSCommand extends BaseSubCommand {
+ private final VisibilityGui visibilityGui = new VisibilityGui();
+
+ public FPSCommand(String name) {
+ super(name);
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (sender instanceof Player player) {
+ this.visibilityGui.showTo(player);
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd9ebbb7bbb90c87faf75fcc4b487e93573f1387
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java
@@ -0,0 +1,41 @@
+package me.samsuik.sakura.command.subcommands;
+
+import me.samsuik.sakura.command.BaseSubCommand;
+import me.samsuik.sakura.configuration.GlobalConfiguration;
+import me.samsuik.sakura.player.visibility.VisibilitySettings;
+import me.samsuik.sakura.player.visibility.VisibilityState;
+import me.samsuik.sakura.player.visibility.VisibilityType;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import java.util.Arrays;
+
+@DefaultQualifier(NonNull.class)
+public final class VisualCommand extends BaseSubCommand {
+ private final VisibilityType type;
+
+ public VisualCommand(VisibilityType type, String... aliases) {
+ super(type.key() + "visibility");
+ this.setAliases(Arrays.asList(aliases));
+ this.type = type;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (!(sender instanceof Player player)) {
+ return;
+ }
+
+ VisibilitySettings settings = player.getVisibility();
+ 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)
+ );
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/FeatureGui.java b/src/main/java/me/samsuik/sakura/player/gui/FeatureGui.java
new file mode 100644
index 0000000000000000000000000000000000000000..49f53c01d61cbe551aa68a91f53782e8f01d9c67
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/FeatureGui.java
@@ -0,0 +1,44 @@
+package me.samsuik.sakura.player.gui;
+
+import me.samsuik.sakura.player.gui.components.GuiComponent;
+import net.kyori.adventure.text.Component;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public abstract class FeatureGui {
+ private final int size;
+ private final Component title;
+
+ public FeatureGui(int size, Component title) {
+ this.size = size;
+ this.title = title;
+ }
+
+ protected abstract void fillInventory(Inventory inventory);
+
+ protected abstract void afterFill(Player player, FeatureGuiInventory inventory);
+
+ public final void showTo(Player bukkitPlayer) {
+ 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();
+ if (clicked != null && clicked.getHolder(false) instanceof FeatureGuiInventory featureInventory) {
+ event.setCancelled(true);
+ for (GuiComponent component : featureInventory.getComponents().reversed()) {
+ if (component.interaction(event, featureInventory)) {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/FeatureGuiInventory.java b/src/main/java/me/samsuik/sakura/player/gui/FeatureGuiInventory.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c86501b8a392ed04c46646bfac0cad9b2ca33bc
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/FeatureGuiInventory.java
@@ -0,0 +1,90 @@
+package me.samsuik.sakura.player.gui;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import me.samsuik.sakura.player.gui.components.GuiComponent;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+import org.bukkit.NamespacedKey;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.Collection;
+import java.util.Optional;
+
+@NullMarked
+public final class FeatureGuiInventory implements InventoryHolder {
+ private final Inventory inventory;
+ private final FeatureGui gui;
+ private final Multimap<NamespacedKey, GuiComponent> componentsUnderKey = HashMultimap.create();
+ private final Object2ObjectMap<GuiComponent, NamespacedKey> componentKeys = new Object2ObjectLinkedOpenHashMap<>();
+
+ public FeatureGuiInventory(FeatureGui gui, int size, Component component) {
+ this.inventory = Bukkit.createInventory(this, size, component);
+ this.gui = gui;
+ }
+
+ @Override
+ public Inventory getInventory() {
+ return this.inventory;
+ }
+
+ public FeatureGui getGui() {
+ return this.gui;
+ }
+
+ public ImmutableList<GuiComponent> getComponents() {
+ return ImmutableList.copyOf(this.componentKeys.keySet());
+ }
+
+ public ImmutableList<GuiComponent> findComponents(NamespacedKey key) {
+ return ImmutableList.copyOf(this.componentsUnderKey.get(key));
+ }
+
+ public Optional<GuiComponent> findFirst(NamespacedKey key) {
+ 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) {
+ this.componentKeys.remove(component);
+ }
+ }
+
+ public void addComponent(GuiComponent component, 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);
+ this.componentsUnderKey.remove(key, component);
+ }
+
+ public void replaceComponent(GuiComponent component, GuiComponent replacement) {
+ NamespacedKey key = this.componentKeys.remove(component);
+ Preconditions.checkNotNull(key, "component does not exist");
+ this.componentKeys.put(replacement, key);
+ this.componentsUnderKey.remove(key, component);
+ this.componentsUnderKey.put(key, replacement);
+ this.inventoryUpdate(replacement);
+ }
+
+ public void removeAllComponents() {
+ this.componentKeys.clear();
+ this.componentsUnderKey.clear();
+ }
+
+ private void inventoryUpdate(GuiComponent component) {
+ component.creation(this.inventory);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java b/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..eddf7632f194142c48f4012734128a10e422eea6
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java
@@ -0,0 +1,19 @@
+package me.samsuik.sakura.player.gui;
+
+import net.kyori.adventure.text.Component;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public class ItemStackUtil {
+ public static ItemStack itemWithBlankName(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));
+ return item;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/components/GuiClickEvent.java b/src/main/java/me/samsuik/sakura/player/gui/components/GuiClickEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..39065d5003bfa60e7453079eb3202bea1b605d4a
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/components/GuiClickEvent.java
@@ -0,0 +1,10 @@
+package me.samsuik.sakura.player.gui.components;
+
+import me.samsuik.sakura.player.gui.FeatureGuiInventory;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public interface GuiClickEvent {
+ void doSomething(InventoryClickEvent event, FeatureGuiInventory inventory);
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/components/GuiComponent.java b/src/main/java/me/samsuik/sakura/player/gui/components/GuiComponent.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a2ff0ca3462254bc4e01192b48c321c6f34e909
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/components/GuiComponent.java
@@ -0,0 +1,13 @@
+package me.samsuik.sakura.player.gui.components;
+
+import me.samsuik.sakura.player.gui.FeatureGuiInventory;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public interface GuiComponent {
+ boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory);
+
+ void creation(Inventory inventory);
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/components/ItemButton.java b/src/main/java/me/samsuik/sakura/player/gui/components/ItemButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d49d3390b2e01fcffd638abdde569d1788f3256
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/components/ItemButton.java
@@ -0,0 +1,34 @@
+package me.samsuik.sakura.player.gui.components;
+
+import me.samsuik.sakura.player.gui.FeatureGuiInventory;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public final class ItemButton implements GuiComponent {
+ private final ItemStack bukkitItem;
+ private final int slot;
+ private final GuiClickEvent whenClicked;
+
+ public ItemButton(ItemStack bukkitItem, int slot, GuiClickEvent whenClicked) {
+ this.bukkitItem = bukkitItem;
+ this.slot = slot;
+ this.whenClicked = whenClicked;
+ }
+
+ @Override
+ public boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory) {
+ if (event.getSlot() == this.slot) {
+ this.whenClicked.doSomething(event, featureInventory);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void creation(Inventory inventory) {
+ inventory.setItem(this.slot, this.bukkitItem);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/gui/components/ItemSwitch.java b/src/main/java/me/samsuik/sakura/player/gui/components/ItemSwitch.java
new file mode 100644
index 0000000000000000000000000000000000000000..243f1cbabf7054d7d5092f21e923e4dea5085123
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/gui/components/ItemSwitch.java
@@ -0,0 +1,44 @@
+package me.samsuik.sakura.player.gui.components;
+
+import com.google.common.base.Preconditions;
+import me.samsuik.sakura.player.gui.FeatureGuiInventory;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.Collections;
+import java.util.List;
+
+@NullMarked
+public final class ItemSwitch implements GuiComponent {
+ private final List<ItemStack> items;
+ private final int slot;
+ private final int selected;
+ private final GuiClickEvent whenClicked;
+
+ public ItemSwitch(List<ItemStack> items, int slot, int selected, GuiClickEvent whenClicked) {
+ Preconditions.checkArgument(!items.isEmpty());
+ this.items = Collections.unmodifiableList(items);
+ this.slot = slot;
+ this.selected = selected;
+ this.whenClicked = whenClicked;
+ }
+
+ @Override
+ public boolean interaction(InventoryClickEvent event, 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);
+ featureInventory.replaceComponent(this, itemSwitch);
+ this.whenClicked.doSomething(event, featureInventory);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void creation(Inventory inventory) {
+ inventory.setItem(this.slot, this.items.get(this.selected));
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/visibility/PlayerVisibilitySettings.java b/src/main/java/me/samsuik/sakura/player/visibility/PlayerVisibilitySettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..938fe2143d0db2a0760022433c951155b5e088c3
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/visibility/PlayerVisibilitySettings.java
@@ -0,0 +1,65 @@
+package me.samsuik.sakura.player.visibility;
+
+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import net.minecraft.nbt.CompoundTag;
+import org.jetbrains.annotations.NotNull;
+import org.jspecify.annotations.NonNull;
+
+public final class PlayerVisibilitySettings implements VisibilitySettings {
+ private static final String SETTINGS_COMPOUND_TAG = "clientVisibilitySettings";
+ private final Reference2ObjectMap<VisibilityType, VisibilityState> visibilityStates = new Reference2ObjectOpenHashMap<>();
+
+ @Override
+ public @NonNull VisibilityState get(@NonNull VisibilityType type) {
+ VisibilityState state = this.visibilityStates.get(type);
+ return state != null ? state : type.getDefault();
+ }
+
+ @Override
+ public @NotNull VisibilityState set(@NonNull VisibilityType type, @NonNull VisibilityState state) {
+ if (type.isDefault(state)) {
+ this.visibilityStates.remove(type);
+ } else {
+ this.visibilityStates.put(type, state);
+ }
+ return state;
+ }
+
+ @Override
+ public @NonNull VisibilityState currentState() {
+ int modifiedCount = this.visibilityStates.size();
+ if (modifiedCount == 0) {
+ return VisibilityState.ON;
+ } else if (modifiedCount != VisibilityTypes.types().size()) {
+ return VisibilityState.MODIFIED;
+ } else {
+ return VisibilityState.OFF;
+ }
+ }
+
+ @Override
+ public boolean playerModified() {
+ return !this.visibilityStates.isEmpty();
+ }
+
+ public void loadData(@NonNull CompoundTag tag) {
+ if (!tag.contains(SETTINGS_COMPOUND_TAG, CompoundTag.TAG_COMPOUND)) {
+ return;
+ }
+
+ CompoundTag settingsTag = tag.getCompound(SETTINGS_COMPOUND_TAG);
+ for (VisibilityType type : VisibilityTypes.types()) {
+ if (settingsTag.contains(type.key(), CompoundTag.TAG_STRING)) {
+ VisibilityState state = VisibilityState.valueOf(settingsTag.getString(type.key()));
+ this.visibilityStates.put(type, state);
+ }
+ }
+ }
+
+ public void saveData(@NonNull CompoundTag tag) {
+ CompoundTag settingsTag = new CompoundTag();
+ this.visibilityStates.forEach((t, s) -> settingsTag.putString(t.key(), s.name()));
+ tag.put(SETTINGS_COMPOUND_TAG, settingsTag);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/visibility/VisibilityGui.java b/src/main/java/me/samsuik/sakura/player/visibility/VisibilityGui.java
new file mode 100644
index 0000000000000000000000000000000000000000..67b821b7a614704cb736afe286e967e1c955a289
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/visibility/VisibilityGui.java
@@ -0,0 +1,96 @@
+package me.samsuik.sakura.player.visibility;
+
+import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
+import me.samsuik.sakura.configuration.GlobalConfiguration;
+import me.samsuik.sakura.player.gui.FeatureGui;
+import me.samsuik.sakura.player.gui.FeatureGuiInventory;
+import me.samsuik.sakura.player.gui.components.ItemButton;
+import me.samsuik.sakura.player.gui.components.ItemSwitch;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.jspecify.annotations.NullMarked;
+
+import static me.samsuik.sakura.player.gui.ItemStackUtil.itemWithBlankName;
+
+@NullMarked
+public final class VisibilityGui extends FeatureGui {
+ private static final NamespacedKey TOGGLE_BUTTON_KEY = new NamespacedKey("sakura", "toggle_button");
+ private static final NamespacedKey MENU_ITEMS_KEY = new NamespacedKey("sakura", "menu_items");
+
+ public VisibilityGui() {
+ super(45, Component.text("FPS Settings"));
+ }
+
+ @Override
+ protected void fillInventory(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;
+ // from center
+ int rx = x - 4;
+ int ry = y - 2;
+ double d = Math.sqrt(rx * rx + ry * ry);
+ if (d <= 3.25) {
+ inventory.setItem(slot, itemWithBlankName(GlobalConfiguration.get().fps.material));
+ } else if (x % 8 == 0) {
+ inventory.setItem(slot, itemWithBlankName(Material.BLACK_STAINED_GLASS_PANE));
+ } else {
+ inventory.setItem(slot, itemWithBlankName(Material.WHITE_STAINED_GLASS_PANE));
+ }
+ }
+ }
+
+ @Override
+ protected void afterFill(Player player, FeatureGuiInventory inventory) {
+ VisibilitySettings settings = player.getVisibility();
+ 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();
+
+ ItemSwitch itemSwitch = new ItemSwitch(
+ VisibilityGuiItems.GUI_ITEMS.get(type),
+ slot, index,
+ (e, inv) -> {
+ settings.cycle(type);
+ this.updateToggleButton(settings, player, inv);
+ }
+ );
+
+ inventory.addComponent(itemSwitch, MENU_ITEMS_KEY);
+ }
+ }
+
+ private void updateToggleButton(VisibilitySettings settings, Player player, FeatureGuiInventory inventory) {
+ inventory.removeComponents(TOGGLE_BUTTON_KEY);
+ VisibilityState settingsState = settings.currentState();
+ ItemButton button = new ItemButton(
+ VisibilityGuiItems.TOGGLE_BUTTON_ITEMS.get(settingsState),
+ (2 * 9) + 8,
+ (e, inv) -> {
+ settings.toggleAll();
+ inventory.removeAllComponents();
+ this.afterFill(player, inv);
+ }
+ );
+ inventory.addComponent(button, TOGGLE_BUTTON_KEY);
+ }
+
+ private IntArrayFIFOQueue availableSlots() {
+ IntArrayFIFOQueue slots = new IntArrayFIFOQueue();
+ for (int row = 1; row < 4; ++row) {
+ for (int column = 3; column < 6; ++column) {
+ if ((column + row) % 2 == 0) {
+ slots.enqueue((row * 9) + column);
+ }
+ }
+ }
+ return slots;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/visibility/VisibilityGuiItems.java b/src/main/java/me/samsuik/sakura/player/visibility/VisibilityGuiItems.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca26410b394602f3aa0e50efc8109ad9c6ec0611
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/visibility/VisibilityGuiItems.java
@@ -0,0 +1,55 @@
+package me.samsuik.sakura.player.visibility;
+
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import me.samsuik.sakura.player.gui.ItemStackUtil;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Locale;
+
+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<>();
+
+ 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))
+ .collect(ImmutableList.toImmutableList());
+
+ GUI_ITEMS.put(type, stateItems);
+ }
+
+ TOGGLE_BUTTON_ITEMS.put(VisibilityState.ON, ItemStackUtil.itemWithName(Material.GREEN_STAINED_GLASS_PANE, Component.text("Enabled", NamedTextColor.GREEN)));
+ TOGGLE_BUTTON_ITEMS.put(VisibilityState.MODIFIED, ItemStackUtil.itemWithName(Material.MAGENTA_STAINED_GLASS_PANE, Component.text("Modified", NamedTextColor.LIGHT_PURPLE)));
+ 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);
+ 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)));
+ return itemCopy;
+ }
+}
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
index 1a37654aff9a9c86c9f7af10a1cf721371f0c5ec..82644b34a77dc5e5af38260b7b07b3ec9aceae33 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
@@ -19,7 +19,7 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet<ClientGamePa
private static final int POS_IN_SECTION_BITS = 12;
private final SectionPos sectionPos;
private final short[] positions;
- private final BlockState[] states;
+ public final BlockState[] states; // Sakura - private -> public
public ClientboundSectionBlocksUpdatePacket(SectionPos sectionPos, ShortSet positions, LevelChunkSection section) {
this.sectionPos = sectionPos;
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 1f9f023a43c16cc472deea5285b01c19136e019f..cf1e4cb1cdbda7dd1668ca40a788173942572a9e 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1891,6 +1891,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
gameprofilerfiller.pop();
worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
worldserver.localConfig().expire(currentTick); // Sakura - add local config
+ worldserver.explosionPositions.clear(); // Sakura - client visibility settings
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index cfeeddf2cb4ff50dbc29c6913e78ca1dee076790..d6c3f39cc9359f5d0fc5082949a7c860c38881cc 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -179,6 +179,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.handleLegacyStructureIndex(pos);
}
// Paper end - rewrite chunk system
+ private final it.unimi.dsi.fastutil.longs.Long2IntMap minimalEntities; // Sakura - client visibility settings; minimal tnt/sand
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
@@ -222,6 +223,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.poiManager = new PoiManager(new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, iregistrycustom, world.getServer(), world);
this.setServerViewDistance(viewDistance);
this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system
+ // Sakura start - client visibility settings; minimal tnt/sand
+ this.minimalEntities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap();
+ this.minimalEntities.defaultReturnValue(Integer.MIN_VALUE);
+ // Sakura end - client visibility settings; minimal tnt/sand
}
private void setChunkUnsaved(ChunkPos pos) {
@@ -973,6 +978,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
tracker.serverEntity.sendChanges();
}
}
+
+ this.minimalEntities.clear(); // Sakura - client visibility settings; minimal tnt/sand
}
// Paper end - optimise entity tracker
@@ -1210,6 +1217,32 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return !this.seenBy.isEmpty();
}
// Paper end - optimise entity tracker
+ // Sakura start - client visibility settings; entity visibility
+ private boolean checkEntityVisibility(final ServerPlayer player) {
+ final Entity entity = this.entity;
+ final me.samsuik.sakura.player.visibility.VisibilitySettings settings = player.visibilitySettings;
+ if (!settings.playerModified() || !(entity.isPrimedTNT || entity.isFallingBlock)) {
+ return true;
+ }
+ final me.samsuik.sakura.player.visibility.VisibilityType type;
+ if (entity.isPrimedTNT) {
+ type = me.samsuik.sakura.player.visibility.VisibilityTypes.TNT;
+ } else {
+ type = me.samsuik.sakura.player.visibility.VisibilityTypes.SAND;
+ }
+ final me.samsuik.sakura.player.visibility.VisibilityState state = settings.get(type);
+ if (state == me.samsuik.sakura.player.visibility.VisibilityState.MINIMAL) {
+ final long key = entity.blockPosition().asLong() ^ entity.getType().hashCode();
+ final long visibleEntity = ChunkMap.this.minimalEntities.get(key);
+ if (visibleEntity != Integer.MIN_VALUE) {
+ return entity.getId() == visibleEntity;
+ } else {
+ ChunkMap.this.minimalEntities.put(key, entity.getId());
+ }
+ }
+ return state != me.samsuik.sakura.player.visibility.VisibilityState.OFF;
+ }
+ // Sakura end - client visibility settings; entity visibility
public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
@@ -1287,6 +1320,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
// Paper end - Configurable entity tracking range by Y
+ flag = flag && this.checkEntityVisibility(player); // Sakura start - client visibility settings; entity visibility
// CraftBukkit start - respect vanish API
if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 876dc65f91bf09865563c8d583b78e4fdafd82b7..9ff9f5a5d9d156b3a2a939e67c412470b2631d8d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -595,6 +595,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.lagCompensationTick = (System.nanoTime() - net.minecraft.server.MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
}
// Paper end - lag compensation
+ // Sakura start - client visibility settings
+ public final LongSet explosionPositions = new it.unimi.dsi.fastutil.longs.LongOpenHashSet();
+
+ public final boolean checkExplosionVisibility(final Vec3 position, final ServerPlayer player) {
+ final me.samsuik.sakura.player.visibility.VisibilitySettings settings = player.visibilitySettings;
+ if (settings.isDisabled(me.samsuik.sakura.player.visibility.VisibilityTypes.EXPLOSIONS)) {
+ return false;
+ } else if (settings.isToggled(me.samsuik.sakura.player.visibility.VisibilityTypes.EXPLOSIONS)) {
+ final BlockPos blockPosition = BlockPos.containing(position);
+ final long encodedPosition = blockPosition.asLong();
+ return this.explosionPositions.add(encodedPosition);
+ }
+ return true;
+ }
+ // Sakura end - client visibility settings
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
@@ -1906,7 +1921,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
if (entityplayer.distanceToSqr(vec3d) < 4096.0D) {
Optional<Vec3> optional = Optional.ofNullable((Vec3) serverexplosion.getHitPlayers().get(entityplayer));
- entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, holder));
+ // Sakura start - client visibility settings; let players toggle explosion particles
+ ParticleOptions particle = particleparam2;
+ Vec3 position = vec3d;
+ // In 1.22 and later this should be replaced with sending the motion through a PlayerPositionPacket.
+ // The problem here is SetEntityMotion is capped to 3.9 b/pt and the only other alternate mean was
+ // implemented in 1.21.3. I believe it's best to just wait on this issue and deal with this hack.
+ if (!this.checkExplosionVisibility(vec3d, entityplayer)) {
+ position = new Vec3(0.0, -1024.0, 0.0);
+ particle = net.minecraft.core.particles.ParticleTypes.SMOKE;
+ }
+ entityplayer.connection.send(new ClientboundExplodePacket(position, optional, particle, holder));
+ // Sakura end - client visibility settings; let players toggle explosion particles
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index fc7f7a34babd095a51b5321f600aef65a2a9d123..2a9659158d39d4d5505328afd7a2d8dc9ecf456f 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -357,6 +357,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
return this.viewDistanceHolder;
}
// Paper end - rewrite chunk system
+ public final me.samsuik.sakura.player.visibility.PlayerVisibilitySettings visibilitySettings = new me.samsuik.sakura.player.visibility.PlayerVisibilitySettings(); // Sakura - client visibility settings
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index b0bc66dc7248aae691dcab68b925b52a1695e63f..2f9413b9442cc7f95e1974a772812fe397d4c4bd 100644
--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -101,6 +101,22 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
protected final org.bukkit.craftbukkit.CraftServer cserver;
public boolean processedDisconnect;
+ // Sakura start - client visibility settings
+ private @Nullable Packet<?> recreatePacket(final Packet<?> packet) {
+ final me.samsuik.sakura.player.visibility.VisibilitySettings settings = this.player.visibilitySettings;
+ if (packet instanceof net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket bedPacket) {
+ if (settings.isToggled(me.samsuik.sakura.player.visibility.VisibilityTypes.SPAWNERS) && bedPacket.getType() == net.minecraft.world.level.block.entity.BlockEntityType.MOB_SPAWNER) {
+ return null;
+ }
+ } else if (packet instanceof net.minecraft.network.protocol.game.ClientboundBlockEventPacket bePacket) {
+ if (settings.isToggled(me.samsuik.sakura.player.visibility.VisibilityTypes.PISTONS) && bePacket.getBlock() instanceof net.minecraft.world.level.block.piston.PistonBaseBlock) {
+ return null;
+ }
+ }
+ return packet;
+ }
+ // Sakura end - client visibility settings
+
public CraftPlayer getCraftPlayer() {
return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
// CraftBukkit end
@@ -309,6 +325,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet;
this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld());
}
+ // Sakura start - client visibility settings
+ if (this.player.visibilitySettings.playerModified()) {
+ packet = this.recreatePacket(packet);
+ if (packet == null) return;
+ }
+ // Sakura end - client visibility settings
// CraftBukkit end
if (packet.isTerminal()) {
this.close();
@@ -322,8 +344,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
CrashReport crashreport = CrashReport.forThrowable(throwable, "Sending packet");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Packet being sent");
+ // Sakura start - this has to be effectively final as we're modifying the packet above
+ var packetFinal = packet;
crashreportsystemdetails.setDetail("Packet class", () -> {
- return packet.getClass().getCanonicalName();
+ return packetFinal.getClass().getCanonicalName();
+ // Sakura end
});
throw new ReportedException(crashreport);
}
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 84fa24880d02dc7ba1ec8bda3575be38447fd4b2..52f79a8d1b7890ffba3495ca9390f0edc27e6f99 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3314,6 +3314,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
event.setCancelled(cancelled);
AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224
+ me.samsuik.sakura.player.gui.FeatureGui.clickEvent(event); // Sakura - client visibility settings
this.cserver.getPluginManager().callEvent(event);
if (this.player.containerMenu != oldContainer) {
return;
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 7ac7d0729705cb02f22277be3c467aed4f69ec0e..34a8c152a4570802d1e96430a6de9d937375fedd 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -568,6 +568,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
}
// Paper end - optimise entity tracker
+ // Sakura start - visibility api and command
+ public boolean isPrimedTNT;
+ public boolean isFallingBlock;
+ // Sakura end - visibility api and command
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 410c4e4a42640ebe8a9c233eb2064aad76e45a27..e270eaec13afb16ed80e7c3fd8ea35ee58291263 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -78,6 +78,7 @@ public class FallingBlockEntity extends Entity {
this.blockState = Blocks.SAND.defaultBlockState();
this.dropItem = true;
this.fallDamageMax = 40;
+ this.isFallingBlock = true; // Sakura
}
public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 809f5e847e2f5bb594c130cebd2cb897ea768d82..eebd53ac889da265cb259ba3cb8c1ce4ef34d931 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -63,6 +63,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
super(type, world);
this.explosionPower = 4.0F;
this.blocksBuilding = true;
+ this.isPrimedTNT = true; // Sakura
}
public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 6a647cab8b2e476987931486e290703b8726f2c7..5455f78d4417f3f8b2e4820619ad24d91054d986 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2403,6 +2403,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
handle.keepLevel = data.getBoolean("keepLevel");
}
}
+
+ // Sakura start - client visibility settings; load from nbt
+ if (nbttagcompound.contains("sakura", 10)) {
+ CompoundTag sakuraTag = nbttagcompound.getCompound("sakura");
+ this.getHandle().visibilitySettings.loadData(sakuraTag);
+ }
+ // Sakura end - client visibility settings; load from nbt
}
public void setExtraData(CompoundTag nbttagcompound) {
@@ -2432,6 +2439,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
paper.putLong("LastLogin", handle.loginTime);
paper.putLong("LastSeen", System.currentTimeMillis());
// Paper end
+ // Sakura start - client visibility settings; save to nbt
+ CompoundTag sakuraTag = nbttagcompound.getCompound("sakura");
+ this.getHandle().visibilitySettings.saveData(sakuraTag);
+ nbttagcompound.put("sakura", sakuraTag);
+ // Sakura end - client visibility settings; save to nbt
}
@Override
@@ -3091,6 +3103,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.getHandle().allowsListing();
}
+ // Sakura start - client visibility settings; api
+ @Override
+ public final me.samsuik.sakura.player.visibility.VisibilitySettings getVisibility() {
+ return this.getHandle().visibilitySettings;
+ }
+ // Sakura end - client visibility settings; api
+
// Paper start
@Override
public net.kyori.adventure.text.Component displayName() {

View File

@@ -0,0 +1,147 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 11 Sep 2021 19:19:41 +0100
Subject: [PATCH] Load Chunks on Movement
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
index e04bd54744335fb5398c6e4f7ce8b981f35bfb7d..651a45b795818bd7b1364b95c52570fd99dd35e4 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
@@ -1885,6 +1885,7 @@ public final class CollisionUtil {
public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1;
public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2;
public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3;
+ public static final int COLLISION_FLAG_ADD_TICKET = 1 << 4; // Sakura - load chunks on movement
public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb,
final List<VoxelShape> intoVoxel, final List<AABB> intoAABB,
@@ -1936,6 +1937,7 @@ public final class CollisionUtil {
final int maxChunkZ = maxBlockZ >> 4;
final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0;
+ final boolean addTicket = (collisionFlags & COLLISION_FLAG_ADD_TICKET) != 0; // Sakura - load chunks on movement
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
@@ -1954,6 +1956,13 @@ public final class CollisionUtil {
continue;
}
+ // Sakura start - load chunks on movement
+ if (addTicket && chunk.movementTicketNeedsUpdate() && chunkSource instanceof net.minecraft.server.level.ServerChunkCache chunkCache) {
+ final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(currChunkX, currChunkZ);
+ chunkCache.chunkMap.getDistanceManager().moonrise$getChunkHolderManager().addTicketAtLevel(net.minecraft.server.level.TicketType.ENTITY_MOVEMENT, currChunkX, currChunkZ, 31, chunkKey);
+ chunk.updatedMovementTicket();
+ }
+ // Sakura end - load chunks on movement
final LevelChunkSection[] sections = chunk.getSections();
// bound y
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
index 4d3f7d4a8e05a8d84aa5202134eda1ce9621712e..db159b9b4ffc032f5abe68bf294e58cce04e1baa 100644
--- a/src/main/java/net/minecraft/server/level/TicketType.java
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
@@ -26,6 +26,7 @@ public class TicketType<T> {
public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type
+ public static final TicketType<Long> ENTITY_MOVEMENT = TicketType.create("entity_movement", Long::compareTo, 10*20); // Sakura - load chunks on movement
public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
return new TicketType<>(name, argumentComparator, 0L);
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 29045a4857aadbc7f9ae0c612555743ad404682d..031d3fde74a08165dc6b8106246249583c2300c0 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -572,6 +572,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public boolean isPrimedTNT;
public boolean isFallingBlock;
// Sakura end - visibility api and command
+ // Sakura start - load chunks on movement
+ protected boolean loadChunks = false;
+
+ private int getExtraCollisionFlags() {
+ int flags = 0;
+
+ if (this.loadChunks) {
+ flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_LOAD_CHUNKS;
+ flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_ADD_TICKET;
+ }
+
+ return flags;
+ }
+ // Sakura end - load chunks on movement
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
@@ -1569,7 +1583,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder(
this.level, (Entity)(Object)this, initialCollisionBox, potentialCollisionsVoxel, potentialCollisionsBB,
- ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null
+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | this.getExtraCollisionFlags(), null // Sakura - load chunks on movement
);
potentialCollisionsBB.addAll(entityAABBs);
final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB);
@@ -5229,12 +5243,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@Override
public boolean shouldBeSaved() {
- return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Paper - rewrite chunk system
+ return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.loadChunks || this.isPassenger() ? false : !this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Sakura - load chunks on movement; used to determine whether a chunk should unload // Paper - rewrite chunk system
}
@Override
public boolean isAlwaysTicking() {
- return false;
+ return this.loadChunks; // Sakura - load chunks on movement; always tick in unloaded & lazy chunks
}
public boolean mayInteract(ServerLevel world, BlockPos pos) {
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index e270eaec13afb16ed80e7c3fd8ea35ee58291263..20fa9a70f2d51aaa7f9ea01150d65c1f73caa374 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -79,6 +79,7 @@ public class FallingBlockEntity extends Entity {
this.dropItem = true;
this.fallDamageMax = 40;
this.isFallingBlock = true; // Sakura
+ this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
}
public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index a7bcf197f062361c764d4e951ed88678994eca17..539219a4117c67278461ee483a457c005e6edcfc 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -64,6 +64,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
this.explosionPower = 4.0F;
this.blocksBuilding = true;
this.isPrimedTNT = true; // Sakura
+ this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
}
public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
index f87abb22dd161b2b74401086de80dc95c9ac2dbb..af1624aeda1ca72973b2b63ef05cd7367f678414 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -140,6 +140,17 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
private final int minSection;
private final int maxSection;
// Paper end - get block chunk optimisation
+ // Sakura start - load chunks on movement
+ private long lastMovementLoadTicket = 0;
+
+ public final boolean movementTicketNeedsUpdate() {
+ return net.minecraft.server.MinecraftServer.currentTick - this.lastMovementLoadTicket >= 100;
+ }
+
+ public final void updatedMovementTicket() {
+ this.lastMovementLoadTicket = net.minecraft.server.MinecraftServer.currentTick;
+ }
+ // Sakura end - load chunks on movement
public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups

View File

@@ -0,0 +1,162 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 19 Apr 2024 22:20:03 +0100
Subject: [PATCH] Optimise paper explosions
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index bbbd451ff184be8fa13bd93d53c89a9502f9951a..a92be80081178cc302a7f0b646bad7daa28f7c66 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -89,7 +89,7 @@ public class ServerExplosion implements Explosion {
}
}
- CACHED_RAYS = rayCoords.toDoubleArray();
+ CACHED_RAYS = sortExplosionRays(rayCoords); // Sakura - optimise paper explosions
}
private static final int CHUNK_CACHE_SHIFT = 2;
@@ -307,6 +307,39 @@ public class ServerExplosion implements Explosion {
return (float)missedRays / (float)totalRays;
}
// Paper end - collisions optimisations
+ // Sakura start - optimise paper explosions
+ /*
+ * Sort the explosion rays to better utilise the chunk and block cache.
+ * x + Vanilla Sorted
+ * z @ z 8 5
+ * - x 6 7 6 4
+ * 4 @ 5 7 @ 3
+ * 2 3 8 2
+ * 1 1
+ */
+ private static double[] sortExplosionRays(it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords) {
+ List<double[]> explosionRays = new ArrayList<>();
+
+ for (int i = 0; i < rayCoords.size(); i += 3) {
+ double[] ray = new double[3];
+ rayCoords.getElements(i, ray, 0, 3);
+ explosionRays.add(ray);
+ }
+
+ rayCoords.clear();
+ explosionRays.sort(java.util.Comparator.comparingDouble(vec -> {
+ double sign = Math.signum(vec[0]);
+ double dir = (sign - 1) / 2;
+ return sign + 8 + vec[2] * dir;
+ }));
+
+ 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;
+ }
+ // Sakura end - optimise paper explosions
public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
this.level = world;
@@ -389,6 +422,12 @@ public class ServerExplosion implements Explosion {
initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
}
+ // Sakura start - optimise paper explosions
+ if (!this.interactsWithBlocks() || initialCache.resistance > (this.radius * 1.3f)) {
+ return ret;
+ }
+ // Sakura end - optimise paper explosions
+
// only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of
// a 16x16x16 cube
// we can cache the rays and their normals as well, so that we eliminate the excess iterations / checks and
@@ -467,19 +506,55 @@ public class ServerExplosion implements Explosion {
// Paper end - collision optimisations
}
- private void hurtEntities() {
- float f = this.radius * 2.0F;
+ // Sakura start - optimise paper explosions
+ protected final AABB getExplosionBounds(float f) {
int i = Mth.floor(this.center.x - (double) f - 1.0D);
int j = Mth.floor(this.center.x + (double) f + 1.0D);
int k = Mth.floor(this.center.y - (double) f - 1.0D);
int l = Mth.floor(this.center.y + (double) f + 1.0D);
int i1 = Mth.floor(this.center.z - (double) f - 1.0D);
int j1 = Mth.floor(this.center.z + (double) f + 1.0D);
- List<Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate<Entity>) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
- Iterator iterator = list.iterator();
+ return new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1);
+ }
- while (iterator.hasNext()) {
- Entity entity = (Entity) iterator.next();
+ private void hurtEntities() {
+ float f = this.radius * 2.0F;
+
+ int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this.level);
+ int maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this.level);
+
+ int minChunkX = Mth.floor(this.center.x - f) >> 4;
+ int maxChunkX = Mth.floor(this.center.x + f) >> 4;
+ int minChunkY = Mth.clamp(Mth.floor(this.center.y - f) >> 4, minSection, maxSection);
+ int maxChunkY = Mth.clamp(Mth.floor(this.center.y + f) >> 4, minSection, maxSection);
+ int minChunkZ = Mth.floor(this.center.z - f) >> 4;
+ int maxChunkZ = Mth.floor(this.center.z + f) >> 4;
+
+ ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup entityLookup = this.level.moonrise$getEntityLookup();
+ for (int chunkX = minChunkX; chunkX <= maxChunkX; ++chunkX) {
+ for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; ++chunkZ) {
+ ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices chunk = entityLookup.getChunk(chunkX, chunkZ);
+ if (chunk == null) continue; // empty slice
+
+ for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) {
+ this.impactEntities(f, chunk.getSectionEntities(chunkY));
+ }
+ }
+ }
+ }
+
+ protected final void impactEntities(float f, Entity[] entities) {
+ for (int i = 0; i < entities.length; i++) {
+ Entity entity = entities[i];
+ if (entity == null) break; // end of entity section
+ this.impactEntity(f, entity);
+ if (entity != entities[i]) i--; // entities can be removed mid-explosion
+ }
+ }
+
+ protected final void impactEntity(float f, Entity entity) {
+ if (entity.isAlive() && !entity.isSpectator() && (!this.excludeSourceFromDamage || entity != this.source)) { // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
+ // Sakura end - optimise paper explosions
if (!entity.ignoreExplosion(this)) {
double d0 = Math.sqrt(entity.distanceToSqr(this.center)) / (double) f;
@@ -508,15 +583,16 @@ public class ServerExplosion implements Explosion {
// - Damaging EntityEnderDragon does nothing
// - EntityEnderDragon hitbock always covers the other parts and is therefore always present
if (entity instanceof EnderDragonPart) {
- continue;
+ return; // Sakura - optimise paper explosions
}
entity.lastDamageCancelled = false;
if (entity instanceof EnderDragon) {
+ AABB bounds = this.getExplosionBounds(f); // Sakura - optimise paper explosions
for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
// Calculate damage separately for each EntityComplexPart
- if (list.contains(entityComplexPart)) {
+ if (entityComplexPart.getBoundingBox().intersects(bounds)) { // Sakura - optimise paper explosions
entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
}
}
@@ -525,7 +601,7 @@ public class ServerExplosion implements Explosion {
}
if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
- continue;
+ return; // Sakura - optimise paper explosions
}
// CraftBukkit end
}

View File

@@ -0,0 +1,95 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Wed, 16 Aug 2023 22:34:49 +0100
Subject: [PATCH] Store Entity Data/State
diff --git a/src/main/java/me/samsuik/sakura/entity/EntityState.java b/src/main/java/me/samsuik/sakura/entity/EntityState.java
new file mode 100644
index 0000000000000000000000000000000000000000..aeb5f128b369753cb20b7310fa382ddf2b6e8f0f
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/EntityState.java
@@ -0,0 +1,41 @@
+package me.samsuik.sakura.entity;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+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, float fallDistance) {
+ public static EntityState of(Entity entity) {
+ return new EntityState(
+ entity.position(), entity.getDeltaMovement(), entity.getBoundingBox(),
+ entity.stuckSpeedMultiplier(), entity.mainSupportingBlockPos,
+ entity.onGround(), entity.fallDistance
+ );
+ }
+
+ public void apply(Entity entity) {
+ entity.setPos(this.position);
+ entity.setDeltaMovement(this.momentum);
+ entity.setBoundingBox(this.bb);
+ entity.makeStuckInBlock(Blocks.AIR.defaultBlockState(), this.stuckSpeed);
+ entity.onGround = this.onGround;
+ entity.mainSupportingBlockPos = this.supportingPos;
+ 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);
+ }
+}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 384b64cfbc2fef49fa22baf1f640ea8f440093cd..316ab62995a7804cf85a2cb986b22e15cd22b01c 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -586,6 +586,25 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return flags;
}
// Sakura end - load chunks on movement
+ // Sakura start - store entity data/state
+ private me.samsuik.sakura.entity.EntityState entityState = null;
+
+ public final Vec3 stuckSpeedMultiplier() {
+ return this.stuckSpeedMultiplier;
+ }
+
+ public final void storeEntityState() {
+ this.entityState = me.samsuik.sakura.entity.EntityState.of(this);
+ }
+
+ public final me.samsuik.sakura.entity.EntityState entityState() {
+ return this.entityState;
+ }
+
+ public final boolean compareState(Entity to) {
+ return to.entityState() != null && to.entityState().comparePositionAndMotion(this);
+ }
+ // Sakura end - store entity data/state
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 13e8ac95daa115720a2b1f52ad33d124351cbd6d..c69c04451c61bcc32e539baae160f05e3418841f 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1500,6 +1500,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
try {
+ entity.storeEntityState(); // Sakura - store entity data/state
tickConsumer.accept(entity);
} catch (Throwable throwable) {
if (throwable instanceof ThreadDeath) throw throwable; // Paper

View File

@@ -0,0 +1,818 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 9 Sep 2023 18:39:15 +0100
Subject: [PATCH] Merge Cannon Entities
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java b/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..917c2d4be95098a091ad19fb3e99f074188f00f9
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java
@@ -0,0 +1,76 @@
+package me.samsuik.sakura.entity.merge;
+
+import net.minecraft.world.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class EntityMergeHandler {
+ private final TrackedMergeHistory trackedHistory = new TrackedMergeHistory();
+
+ /**
+ * Tries to merge the provided entities using the {@link MergeStrategy}.
+ *
+ * @param previous the last entity to tick
+ * @param entity the entity being merged
+ * @return success
+ */
+ public boolean tryMerge(@Nullable Entity entity, @Nullable Entity previous) {
+ if (entity instanceof MergeableEntity mergeEntity && previous instanceof MergeableEntity) {
+ MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
+ MergeStrategy strategy = MergeStrategy.from(mergeEntityData.getMergeLevel());
+ 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);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Stores the merged data of the provided entities if the {@link MergeStrategy} requires it.
+ *
+ * @param entity provided entity
+ */
+ public void removeEntity(@Nullable Entity entity) {
+ if (entity instanceof MergeableEntity mergeEntity) {
+ MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
+ MergeStrategy strategy = MergeStrategy.from(mergeEntityData.getMergeLevel());
+ if (mergeEntityData.hasMerged() && strategy.trackHistory()) {
+ this.trackedHistory.trackHistory(entity, mergeEntityData);
+ }
+ }
+ }
+
+ /**
+ * Called every tick and provided the current server tick to remove any unneeded merge history.
+ *
+ * @param tick server tick
+ */
+ public void expire(int tick) {
+ if (tick % 200 == 0) {
+ this.trackedHistory.expire(tick);
+ }
+ }
+
+ /**
+ * Merges the first entity into the second. The entity merge count can be retrieved through the {@link MergeEntityData}.
+ * <p>
+ * This method also updates the bukkit handle so that plugins reference the first entity after the second entity has been removed.
+ *
+ * @param mergeEntity the first entity
+ * @param into the entity to merge into
+ * @return if successful
+ */
+ public boolean mergeEntity(@NotNull MergeableEntity mergeEntity, @NotNull MergeableEntity into) {
+ MergeEntityData entities = mergeEntity.getMergeEntityData();
+ MergeEntityData mergeInto = into.getMergeEntityData();
+ mergeInto.mergeWith(entities); // merge entities together
+
+ // discard the entity and update the bukkit handle
+ Entity nmsEntity = (Entity) mergeEntity;
+ nmsEntity.discard();
+ nmsEntity.updateBukkitHandle((Entity) into);
+ return true;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c0c224bc30c9c75d4b82508661117ce16197680
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java
@@ -0,0 +1,16 @@
+package me.samsuik.sakura.entity.merge;
+
+import net.minecraft.world.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+
+public interface MergeCondition {
+ default MergeCondition and(@NotNull MergeCondition condition) {
+ return (e,c,t) -> this.accept(e,c,t) && condition.accept(e,c,t);
+ }
+
+ default MergeCondition or(@NotNull MergeCondition condition) {
+ return (e,c,t) -> this.accept(e,c,t) || condition.accept(e,c,t);
+ }
+
+ boolean accept(@NotNull Entity entity, int attempts, long sinceCreation);
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java
new file mode 100644
index 0000000000000000000000000000000000000000..b13e3dcc333bcb53b4493e7087765fcca0a88604
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java
@@ -0,0 +1,52 @@
+package me.samsuik.sakura.entity.merge;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.minecraft.world.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public final class MergeEntityData {
+ private final Entity entity;
+ private List<MergeEntityData> connected = new ObjectArrayList<>();
+ private int count = 1;
+ private MergeLevel mergeLevel = MergeLevel.NONE;
+
+ public MergeEntityData(Entity entity) {
+ this.entity = entity;
+ }
+
+ public void mergeWith(@NotNull MergeEntityData mergeEntityData) {
+ this.connected.add(mergeEntityData);
+ this.connected.addAll(mergeEntityData.connected);
+ this.count += mergeEntityData.getCount();
+ mergeEntityData.setCount(0);
+ }
+
+ public LongOpenHashSet getOriginPositions() {
+ LongOpenHashSet positions = new LongOpenHashSet();
+ this.connected.forEach(entityData -> positions.add(entityData.entity.getPackedOriginPosition()));
+ return positions;
+ }
+
+ public boolean hasMerged() {
+ return !this.connected.isEmpty() && this.count != 0;
+ }
+
+ public void setMergeLevel(MergeLevel mergeLevel) {
+ this.mergeLevel = mergeLevel;
+ }
+
+ public MergeLevel getMergeLevel() {
+ return mergeLevel;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getCount() {
+ return this.count;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b490ddd7b98a41b14f67d0d754d89b5ef645d66
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java
@@ -0,0 +1,118 @@
+package me.samsuik.sakura.entity.merge;
+
+import me.samsuik.sakura.utils.collections.FixedSizeCustomObjectTable;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.entity.EntityTickList;
+import org.jetbrains.annotations.NotNull;
+
+public interface MergeStrategy {
+ /**
+ * If this merge strategy requires the merge history to be tracked.
+ *
+ * @return should track history
+ */
+ boolean trackHistory();
+
+ /**
+ * Tries to merge the first entity into another entity.
+ * <p>
+ * The first entity should always be positioned right after the second entity in the
+ * {@link EntityTickList}. This method should only
+ * be called before the first entity and after the second entity has ticked.
+ *
+ * @param entity current entity
+ * @param previous last entity to tick
+ * @return success
+ */
+ Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory);
+
+ /**
+ * Gets the {@link MergeStrategy} for the {@link MergeLevel}.
+ *
+ * @param level provided level
+ * @return strategy
+ */
+ static MergeStrategy from(MergeLevel level) {
+ return switch (level) {
+ case NONE -> None.INSTANCE;
+ case STRICT -> Strict.INSTANCE;
+ case LENIENT -> Lenient.INSTANCE;
+ case SPAWN -> Spawn.INSTANCE;
+ };
+ }
+
+ final class None implements MergeStrategy {
+ private static final None INSTANCE = new None();
+
+ @Override
+ public boolean trackHistory() {
+ return false;
+ }
+
+ @Override
+ public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
+ return null;
+ }
+ }
+
+ final class Strict implements MergeStrategy {
+ private static final Strict INSTANCE = new Strict();
+
+ @Override
+ public boolean trackHistory() {
+ return false;
+ }
+
+ @Override
+ public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
+ return entity.compareState(previous) ? previous : null;
+ }
+ }
+
+ final class Lenient implements MergeStrategy {
+ private static final Lenient INSTANCE = new Lenient();
+ private final FixedSizeCustomObjectTable<Entity> entityTable = new FixedSizeCustomObjectTable<>(512, entity -> {
+ return entity.blockPosition().hashCode();
+ });
+
+ @Override
+ public boolean trackHistory() {
+ return true;
+ }
+
+ @Override
+ public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
+ if (entity.compareState(previous)) {
+ return previous;
+ }
+
+ Entity nextEntity = this.entityTable.getAndWrite(entity);
+ if (nextEntity == null || !nextEntity.level().equals(entity.level())) {
+ return null;
+ }
+
+ return mergeHistory.hasPreviousMerged(entity, nextEntity) && entity.compareState(nextEntity) ? nextEntity : null;
+ }
+ }
+
+ final class Spawn implements MergeStrategy {
+ private static final Spawn INSTANCE = new Spawn();
+ private static final MergeCondition CONDITION = (e, shots, time) -> (shots > 16 || time >= 200);
+
+ @Override
+ public boolean trackHistory() {
+ return true;
+ }
+
+ @Override
+ public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
+ final Entity mergeInto;
+ if (entity.tickCount == 1 && mergeHistory.hasPreviousMerged(entity, previous) && mergeHistory.hasMetCondition(previous, CONDITION)) {
+ mergeInto = previous;
+ } else {
+ mergeInto = entity.compareState(previous) ? previous : null;
+ }
+ return mergeInto;
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..3061c3a48f7c68f64a3348b9583f4b41c16429a9
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java
@@ -0,0 +1,22 @@
+package me.samsuik.sakura.entity.merge;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface MergeableEntity {
+ @NotNull MergeEntityData getMergeEntityData();
+
+ boolean isSafeToMergeInto(@NotNull MergeableEntity entity, boolean ticksLived);
+
+ default boolean respawnEntity() {
+ MergeEntityData mergeData = this.getMergeEntityData();
+ int count = mergeData.getCount();
+ if (count > 1) {
+ mergeData.setCount(0);
+ this.respawnEntity(count);
+ return true;
+ }
+ return false;
+ }
+
+ void respawnEntity(int count);
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java b/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc68bccf135bdcc7b3db7976e452ccae01e03b89
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java
@@ -0,0 +1,83 @@
+package me.samsuik.sakura.entity.merge;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import me.samsuik.sakura.configuration.WorldConfiguration.Cannons.Mechanics.TNTSpread;
+import me.samsuik.sakura.utils.objects.Expiry;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+import org.jetbrains.annotations.NotNull;
+
+public final class TrackedMergeHistory {
+ private final Long2ObjectMap<PositionHistory> historyMap = new Long2ObjectOpenHashMap<>();
+
+ public boolean hasPreviousMerged(@NotNull Entity entity, @NotNull Entity into) {
+ PositionHistory positions = this.getHistory(into);
+ return positions != null && positions.has(entity.getPackedOriginPosition());
+ }
+
+ public void trackHistory(@NotNull Entity entity, @NotNull MergeEntityData mergeEntityData) {
+ long originPosition = entity.getPackedOriginPosition();
+ PositionHistory positions = this.historyMap.computeIfAbsent(originPosition, p -> new PositionHistory());
+ LongOpenHashSet originPositions = mergeEntityData.getOriginPositions();
+ boolean createHistory = positions.hasTimePassed(160);
+ if (createHistory && (entity instanceof FallingBlockEntity || entity.level().sakuraConfig().cannons.mechanics.tntSpread == TNTSpread.ALL)) {
+ originPositions.forEach(pos -> this.historyMap.put(pos, positions));
+ }
+ positions.trackPositions(originPositions, !createHistory);
+ }
+
+ public boolean hasMetCondition(@NotNull Entity entity, MergeCondition condition) {
+ PositionHistory positions = this.getHistory(entity);
+ return positions != null && positions.hasMetConditions(entity, condition);
+ }
+
+ private PositionHistory getHistory(Entity entity) {
+ long originPosition = entity.getPackedOriginPosition();
+ return this.historyMap.get(originPosition);
+ }
+
+ public void expire(int tick) {
+ this.historyMap.values().removeIf(p -> p.expiry().isExpired(tick));
+ }
+
+ private static final class PositionHistory {
+ private final LongOpenHashSet positions = new LongOpenHashSet();
+ private final Expiry expiry = new Expiry(MinecraftServer.currentTick, 200);
+ private final long created = MinecraftServer.currentTick;
+ private int cycles = 0;
+
+ public Expiry expiry() {
+ return this.expiry;
+ }
+
+ public boolean has(long position) {
+ this.expiry.refresh(MinecraftServer.currentTick);
+ return this.positions.contains(position);
+ }
+
+ public void trackPositions(LongOpenHashSet positions, boolean retain) {
+ if (retain) {
+ this.positions.retainAll(positions);
+ } else {
+ this.positions.addAll(positions);
+ }
+ this.cycles++;
+ }
+
+ public boolean hasMetConditions(@NotNull Entity entity, @NotNull MergeCondition condition) {
+ this.expiry.refresh(MinecraftServer.currentTick);
+ return condition.accept(entity, this.cycles, this.timeSinceCreation());
+ }
+
+ public boolean hasTimePassed(int ticks) {
+ return this.timeSinceCreation() > ticks;
+ }
+
+ private long timeSinceCreation() {
+ return MinecraftServer.currentTick - this.created;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 733541e91b0064d50de6c5c0985e9c472685c20c..c73aa5fd7da18219d1da63a0973f397316ba4210 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1905,6 +1905,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
worldserver.localConfig().expire(currentTick); // Sakura - add local config
worldserver.explosionPositions.clear(); // Sakura - client visibility settings
+ worldserver.mergeHandler.expire(currentTick); // Sakura - merge cannon entities
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index e1f0da0b20ec310470160718338920bbd5b088de..1e940c5b9ce60dd1dd7ec03e83766e26d1112d73 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -807,6 +807,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
+ Entity[] previousEntity = new Entity[1]; // Sakura - merge cannon entities
this.entityTickList.forEach((entity) -> {
if (!entity.isRemoved()) {
if (!tickratemanager.isEntityFrozen(entity)) {
@@ -824,6 +825,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
entity.stopRiding();
}
+ // Sakura start - merge cannon entities
+ Entity previous = previousEntity[0];
+ if (this.mergeHandler.tryMerge(entity, previous)) {
+ return;
+ } else {
+ previousEntity[0] = entity;
+ }
+ // Sakura end - merge cannon entities
+
gameprofilerfiller.push("tick");
this.guardEntityTick(this::tickNonPassenger, entity);
gameprofilerfiller.pop();
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 316ab62995a7804cf85a2cb986b22e15cd22b01c..fd1d02604a1e46545c7f2c8ad89968279e2fe1bf 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -605,6 +605,23 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return to.entityState() != null && to.entityState().comparePositionAndMotion(this);
}
// Sakura end - store entity data/state
+ // Sakura start - merge cannon entities
+ public final void updateBukkitHandle(Entity entity) {
+ if (this.bukkitEntity != null) {
+ this.bukkitEntity.setHandle(entity);
+ }
+ this.bukkitEntity = entity.getBukkitEntity();
+ }
+
+ public final long getPackedOriginPosition() {
+ org.bukkit.util.Vector origin = this.getOriginVector();
+ if (origin != null) {
+ return BlockPos.asLong(origin.getBlockX(), origin.getBlockY(), origin.getBlockZ());
+ } else {
+ return Long.MIN_VALUE;
+ }
+ }
+ // Sakura end - merge cannon entities
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
@@ -5233,6 +5250,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (this.removalReason != Entity.RemovalReason.UNLOADED_TO_CHUNK) { this.getPassengers().forEach(Entity::stopRiding); } // Paper - rewrite chunk system
this.levelCallback.onRemove(entity_removalreason);
this.onRemoval(entity_removalreason);
+ // Sakura start - merge cannon entities
+ if (entity_removalreason == RemovalReason.DISCARDED) {
+ this.level.mergeHandler.removeEntity(this);
+ }
+ // Sakura end - merge cannon entities
// Paper start - Folia schedulers
if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
// Players need to be special cased, because they are regularly removed from the world
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 81d02da6afd4b77c0ca60e9c8c5100ce6988753c..7dc5e5d91bb91c86ddca52a462903e8452396090 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -57,7 +57,7 @@ import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.event.entity.EntityRemoveEvent;
// CraftBukkit end
-public class FallingBlockEntity extends Entity {
+public class FallingBlockEntity extends Entity implements me.samsuik.sakura.entity.merge.MergeableEntity { // Sakura - merge cannon entities
private static final Logger LOGGER = LogUtils.getLogger();
public BlockState blockState;
@@ -73,6 +73,58 @@ public class FallingBlockEntity extends Entity {
protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
public boolean autoExpire = true; // Paper - Expand FallingBlock API
+ // Sakura start - merge cannon entities
+ private final me.samsuik.sakura.entity.merge.MergeEntityData mergeData = new me.samsuik.sakura.entity.merge.MergeEntityData(this);
+
+ @Override
+ public final me.samsuik.sakura.entity.merge.MergeEntityData getMergeEntityData() {
+ return this.mergeData;
+ }
+
+ @Override
+ public final boolean isSafeToMergeInto(me.samsuik.sakura.entity.merge.MergeableEntity entity, boolean ticksLived) {
+ return entity instanceof FallingBlockEntity fbe
+ && fbe.blockState.equals(this.blockState)
+ && (!ticksLived || fbe.time - 1 == this.time);
+ }
+
+ @Override
+ public final void respawnEntity(int count) {
+ while (count-- >= 1) {
+ // Unlike PrimedTnt we have to try respawn each stacked entity
+ FallingBlockEntity fallingBlock = new FallingBlockEntity(EntityType.FALLING_BLOCK, this.level());
+
+ // Try to stack the falling block
+ this.entityState().apply(fallingBlock);
+ fallingBlock.blockState = this.blockState;
+ fallingBlock.spawnReason = this.spawnReason;
+ fallingBlock.time = this.time - 1;
+ fallingBlock.tick();
+
+ // If you horizontal stack into a moving piston block this condition will be met.
+ if (!fallingBlock.isRemoved()) {
+ this.mergeData.setCount(count + 1);
+ fallingBlock.storeEntityState();
+ fallingBlock.entityState().apply(this);
+ break;
+ } else if (count == 0) {
+ this.discard(EntityRemoveEvent.Cause.DESPAWN);
+ }
+ }
+ }
+
+ @Nullable
+ public ItemEntity spawnAtLocation(ServerLevel level, ItemLike item) { // may be overridden by plugins
+ ItemEntity itemEntity = null;
+
+ for (int i = 0; i < this.mergeData.getCount(); ++i) {
+ itemEntity = super.spawnAtLocation(level, item);
+ }
+
+ return itemEntity;
+ }
+ // Sakura end - merge cannon entities
+
public FallingBlockEntity(EntityType<? extends FallingBlockEntity> type, Level world) {
super(type, world);
this.blockState = Blocks.SAND.defaultBlockState();
@@ -80,6 +132,7 @@ public class FallingBlockEntity extends Entity {
this.fallDamageMax = 40;
this.isFallingBlock = true; // Sakura
this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
+ this.mergeData.setMergeLevel(world.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
}
public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) {
@@ -222,6 +275,7 @@ public class FallingBlockEntity extends Entity {
return;
}
// CraftBukkit end
+ if (this.respawnEntity()) return; // Sakura - merge cannon entities
if (this.level().setBlock(blockposition, this.blockState, 3)) {
((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition)));
this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
@@ -342,6 +396,7 @@ public class FallingBlockEntity extends Entity {
nbt.putBoolean("CancelDrop", this.cancelDrop);
if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
+ nbt.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities
}
@Override
@@ -374,6 +429,11 @@ public class FallingBlockEntity extends Entity {
this.autoExpire = nbt.getBoolean("Paper.AutoExpire");
}
// Paper end - Expand FallingBlock API
+ // Sakura start - merge cannon entities
+ if (nbt.contains("merge_count", 3)) {
+ this.mergeData.setCount(nbt.getInt("merge_count"));
+ }
+ // Sakura end - merge cannon entities
}
public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 46b729ecf0c58bdbe7a4717e73b098dcffd910f1..f7d8e2ba0ee9b8dd27f20a3e75992b107d07fbf0 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -33,7 +33,7 @@ import org.bukkit.event.entity.EntityRemoveEvent;
import org.bukkit.event.entity.ExplosionPrimeEvent;
// CraftBukkit end
-public class PrimedTnt extends Entity implements TraceableEntity {
+public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sakura.entity.merge.MergeableEntity { // Sakura - merge cannon entities
private static final EntityDataAccessor<Integer> DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<BlockState> DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.BLOCK_STATE);
@@ -59,12 +59,49 @@ public class PrimedTnt extends Entity implements TraceableEntity {
public float explosionPower;
public boolean isIncendiary = false; // CraftBukkit - add field
+ // Sakura start - merge cannon entities
+ private final me.samsuik.sakura.entity.merge.MergeEntityData mergeData = new me.samsuik.sakura.entity.merge.MergeEntityData(this);
+
+ @Override
+ public final me.samsuik.sakura.entity.merge.MergeEntityData getMergeEntityData() {
+ return this.mergeData;
+ }
+
+ @Override
+ public final boolean isSafeToMergeInto(me.samsuik.sakura.entity.merge.MergeableEntity entity, boolean ticksLived) {
+ return entity instanceof PrimedTnt tnt
+ && tnt.getFuse() + 1 == this.getFuse()
+ // required to prevent issues with powdered snow
+ && (tnt.entityState().fallDistance() == this.fallDistance
+ || tnt.entityState().fallDistance() > 2.5f && this.fallDistance > 2.5f);
+ }
+
+ @Override
+ public final void respawnEntity(int count) {
+ PrimedTnt tnt = new PrimedTnt(EntityType.TNT, this.level());
+ tnt.updateBukkitHandle(this); // update handle for plugins
+ while (count-- > 1) {
+ this.setFuse(100); // Prevent unwanted explosions while ticking
+
+ // Cause an explosion to affect this entity
+ tnt.setPos(this.position());
+ tnt.setDeltaMovement(this.getDeltaMovement());
+ this.entityState().apply(this);
+ tnt.explode();
+ this.storeEntityState();
+
+ this.tick();
+ }
+ }
+ // Sakura end - merge cannon entities
+
public PrimedTnt(EntityType<? extends PrimedTnt> type, Level world) {
super(type, world);
this.explosionPower = 4.0F;
this.blocksBuilding = true;
this.isPrimedTNT = true; // Sakura
this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
+ this.mergeData.setMergeLevel(world.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
}
public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) {
@@ -125,6 +162,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (i <= 0) {
// CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
// this.discard();
+ this.respawnEntity(); // Sakura - merge cannon entities
if (!this.level().isClientSide) {
this.explode();
}
@@ -185,7 +223,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (this.explosionPower != 4.0F) {
nbt.putFloat("explosion_power", this.explosionPower);
}
-
+ nbt.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities
}
@Override
@@ -194,6 +232,11 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (nbt.contains("block_state", 10)) {
this.setBlockState(NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), nbt.getCompound("block_state")));
}
+ // Sakura start - merge cannon entities
+ if (nbt.contains("merge_count", 3)) {
+ this.mergeData.setCount(nbt.getInt("merge_count"));
+ }
+ // Sakura end - merge cannon entities
if (nbt.contains("explosion_power", 99)) {
this.explosionPower = Mth.clamp(nbt.getFloat("explosion_power"), 0.0F, 128.0F);
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index c69c04451c61bcc32e539baae160f05e3418841f..8f4a0912a5b5f17c22d2b4c0ef39e1c5566fa5f7 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -842,6 +842,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z);
}
// Paper end - optimise random ticking
+ public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.function.Supplier<me.samsuik.sakura.configuration.WorldConfiguration> sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files // Paper - create paper world config & Anti-Xray
// Paper start - getblock optimisations - cache world height/sections
diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
index 9afa811579ac2e556b5c5c23b3b49587439dfadc..c2eb63de04fc48bd2cc1aad8d9cba272c0829c80 100644
--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
@@ -89,7 +89,7 @@ public abstract class BasePressurePlateBlock extends Block {
}
private void checkPressed(@Nullable Entity entity, Level world, BlockPos pos, BlockState state, int output) {
- int j = this.getSignalStrength(world, pos);
+ int j = this.getSignalStrength(world, pos, output == 0); // Sakura - merge cannon entities
boolean flag = output > 0;
boolean flag1 = j > 0;
@@ -171,6 +171,12 @@ public abstract class BasePressurePlateBlock extends Block {
})); // CraftBukkit
}
+ // Sakura start - merge cannon entities
+ protected int getSignalStrength(Level world, BlockPos pos, boolean entityInside) {
+ return this.getSignalStrength(world, pos);
+ }
+ // Sakura end - merge cannon entities
+
protected abstract int getSignalStrength(Level world, BlockPos pos);
protected abstract int getSignalForState(BlockState state);
diff --git a/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
index 05bf23bd9ec951840ffceb68638458de02ad0063..f9c800e912ce966dba4f1026f2d5b702ae6549cd 100644
--- a/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
@@ -42,6 +42,11 @@ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
@Override
protected int getSignalStrength(Level world, BlockPos pos) {
+ // Sakura start - merge cannon entities
+ return this.getSignalStrength(world, pos, false);
+ }
+ protected final int getSignalStrength(Level world, BlockPos pos, boolean entityInside) {
+ // Sakura end - merge cannon entities
// CraftBukkit start
// int i = Math.min(getEntityCount(world, BlockPressurePlateWeighted.TOUCH_AABB.move(blockposition), Entity.class), this.maxWeight);
int i = 0;
@@ -57,7 +62,7 @@ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
// We only want to block turning the plate on if all events are cancelled
if (!cancellable.isCancelled()) {
- i++;
+ i += !entityInside && entity instanceof me.samsuik.sakura.entity.merge.MergeableEntity mergeEntity ? mergeEntity.getMergeEntityData().getCount() : 1; // Sakura - merge cannon entities
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
index 1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69..55f67c2ca07eca0d3e1522eebbb4ce37704bdd04 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
@@ -14,6 +14,28 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock {
super(server, entity);
}
+ // Sakura start - merge cannon entities
+ @Override
+ public @org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() {
+ return this.getHandle().getMergeEntityData().getMergeLevel();
+ }
+
+ @Override
+ public void setMergeLevel(@org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel level) {
+ this.getHandle().getMergeEntityData().setMergeLevel(level);
+ }
+
+ @Override
+ public int getStacked() {
+ return this.getHandle().getMergeEntityData().getCount();
+ }
+
+ @Override
+ public void setStacked(int stacked) {
+ this.getHandle().getMergeEntityData().setCount(stacked);
+ }
+ // Sakura end - merge cannon entities
+
@Override
public FallingBlockEntity getHandle() {
return (FallingBlockEntity) this.entity;
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
index a61aec087fa7cec27a803668bdc1b9e6eb336755..c6f36ab2368d0e2e4555d5f8edc0132dcb61a53c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
@@ -12,6 +12,28 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed {
super(server, entity);
}
+ // Sakura start - merge cannon entities
+ @Override
+ public @org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() {
+ return this.getHandle().getMergeEntityData().getMergeLevel();
+ }
+
+ @Override
+ public void setMergeLevel(@org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel level) {
+ this.getHandle().getMergeEntityData().setMergeLevel(level);
+ }
+
+ @Override
+ public int getStacked() {
+ return this.getHandle().getMergeEntityData().getCount();
+ }
+
+ @Override
+ public void setStacked(int stacked) {
+ this.getHandle().getMergeEntityData().setCount(stacked);
+ }
+ // Sakura end - merge cannon entities
+
@Override
public float getYield() {
return this.getHandle().explosionPower;

View File

@@ -0,0 +1,285 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 22 Apr 2024 23:01:26 +0100
Subject: [PATCH] Replace explosion density cache
diff --git a/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java b/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..35454e122e87892099226ba8fd8d444664be9037
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCache.java
@@ -0,0 +1,62 @@
+package me.samsuik.sakura.explosion.density;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.Vec3;
+
+/**
+ * This is a replacement for papers explosion density cache to be more lenient and efficient.
+ */
+public final class BlockDensityCache {
+ public static final float UNKNOWN_DENSITY = -1.0f;
+
+ private final Int2ObjectOpenHashMap<DensityData> densityDataMap = new Int2ObjectOpenHashMap<>();
+ private DensityData data;
+ private int key;
+ private boolean knownSource;
+
+ public float getDensity(Vec3 explosion, Entity entity) {
+ int key = getKey(explosion, entity);
+ DensityData data = this.densityDataMap.get(key);
+
+ if (data != null && data.hasPosition(explosion, entity.getBoundingBox())) {
+ return data.density();
+ } else {
+ this.knownSource = data != null && data.complete() && data.isExplosionPosition(explosion);
+ this.data = data;
+ this.key = key;
+ return UNKNOWN_DENSITY;
+ }
+ }
+
+ public float getKnownDensity(Vec3 point) {
+ if (this.knownSource && this.data.isKnownPosition(point)) {
+ return this.data.density();
+ } else {
+ return UNKNOWN_DENSITY;
+ }
+ }
+
+ public void putDensity(Vec3 explosion, Entity entity, float density) {
+ if (this.data == null || !this.data.complete()) {
+ this.densityDataMap.put(this.key, new DensityData(explosion, entity, density));
+ } else if (this.data.density() == density) {
+ this.data.expand(explosion, entity);
+ }
+ }
+
+ public void invalidate() {
+ this.densityDataMap.clear();
+ }
+
+ private static int getKey(Vec3 explosion, Entity entity) {
+ int key = Mth.floor(explosion.x());
+ key = 31 * key + Mth.floor(explosion.y());
+ key = 31 * key + Mth.floor(explosion.z());
+ key = 31 * key + entity.getBlockX();
+ key = 31 * key + entity.getBlockY();
+ key = 31 * key + entity.getBlockZ();
+ return key;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java b/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7e24638f07f243502004970ab4ce646cbe1e436
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/explosion/density/DensityData.java
@@ -0,0 +1,47 @@
+package me.samsuik.sakura.explosion.density;
+
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+public final class DensityData {
+ private AABB source;
+ private AABB known;
+ private AABB entity;
+ private final float density;
+ private final boolean complete;
+
+ public DensityData(Vec3 explosion, Entity entity, float density) {
+ this.source = new AABB(explosion, explosion);
+ this.known = new AABB(entity.position(), entity.position());
+ this.entity = entity.getBoundingBox();
+ this.density = density;
+ this.complete = Math.abs(density - 0.5f) == 0.5f;
+ }
+
+ public float density() {
+ return this.density;
+ }
+
+ public boolean complete() {
+ return this.complete;
+ }
+
+ public boolean hasPosition(Vec3 explosion, AABB entity) {
+ return this.isExplosionPosition(explosion) && this.entity.isAABBInBounds(entity);
+ }
+
+ public boolean isKnownPosition(Vec3 point) {
+ return this.entity.isVec3InBounds(point);
+ }
+
+ public boolean isExplosionPosition(Vec3 explosion) {
+ return this.source.isVec3InBounds(explosion);
+ }
+
+ public void expand(Vec3 explosion, Entity entity) {
+ this.source = this.source.expand(explosion);
+ this.known = this.known.expand(entity.position());
+ this.entity = this.entity.minmax(entity.getBoundingBox());
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index c73aa5fd7da18219d1da63a0973f397316ba4210..56357e253247d19c108a7f753058fa3eba0302ee 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1906,6 +1906,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
worldserver.localConfig().expire(currentTick); // Sakura - add local config
worldserver.explosionPositions.clear(); // Sakura - client visibility settings
worldserver.mergeHandler.expire(currentTick); // Sakura - merge cannon entities
+ worldserver.densityCache.invalidate(); // Sakura - explosion density cache
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 8f4a0912a5b5f17c22d2b4c0ef39e1c5566fa5f7..addab159f6fb73fe063f8a057c281d0594a99122 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -843,6 +843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
// Paper end - optimise random ticking
public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
+ public final me.samsuik.sakura.explosion.density.BlockDensityCache densityCache = new me.samsuik.sakura.explosion.density.BlockDensityCache(); // Sakura - explosion density cache
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.function.Supplier<me.samsuik.sakura.configuration.WorldConfiguration> sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files // Paper - create paper world config & Anti-Xray
// Paper start - getblock optimisations - cache world height/sections
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 076ef180360c54517b9acb3ef9e803be41b66f9d..4f9401f654630b5c219681efdda935006629eca7 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -297,7 +297,12 @@ public class ServerExplosion implements Explosion {
Math.fma(dz, diffZ, offZ)
);
- if (!this.clipsAnything(from, source, context, blockCache, blockPos)) {
+ // Sakura start - replace density cache
+ final float density = this.level.densityCache.getKnownDensity(from);
+ if (density != me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {
+ missedRays += (int) density;
+ } else if (!this.clipsAnything(from, source, context, blockCache, blockPos)) {
+ // Sakura end - replace density cache
++missedRays;
}
}
@@ -377,7 +382,16 @@ public class ServerExplosion implements Explosion {
double d10 = Mth.lerp(d7, axisalignedbb.minZ, axisalignedbb.maxZ);
Vec3 vec3d1 = new Vec3(d8 + d3, d9, d10 + d4);
- if (entity.level().clip(new ClipContext(vec3d1, pos, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)).getType() == HitResult.Type.MISS) {
+ // Sakura start - replace density cache
+ final net.minecraft.world.phys.HitResult.Type hitResult;
+ final float density = entity.level().densityCache.getKnownDensity(vec3d1);
+ if (density != me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {
+ hitResult = density != 0.0f ? net.minecraft.world.phys.HitResult.Type.MISS : net.minecraft.world.phys.HitResult.Type.BLOCK;
+ } else {
+ hitResult = entity.level().clip(new ClipContext(vec3d1, pos, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)).getType();
+ }
+ if (hitResult == HitResult.Type.MISS) {
+ // Sakura end - replace density cache
++i;
}
@@ -691,6 +705,12 @@ public class ServerExplosion implements Explosion {
return;
}
// CraftBukkit end
+ // Sakura start - explosion density cache
+ if (!positions.isEmpty() && !this.level.paperConfig().environment.optimizeExplosions) {
+ this.level.densityCache.invalidate();
+ }
+ // Sakura end - explosion density cache
+
Iterator iterator = positions.iterator();
while (iterator.hasNext()) {
@@ -868,14 +888,12 @@ public class ServerExplosion implements Explosion {
// Paper start - Optimize explosions
private float getBlockDensity(Vec3 vec3d, Entity entity) {
- if (!this.level.paperConfig().environment.optimizeExplosions) {
- return this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations
- }
- CacheKey key = new CacheKey(this, entity.getBoundingBox());
- Float blockDensity = this.level.explosionDensityCache.get(key);
- if (blockDensity == null) {
+ // Sakura start - replace density cache
+ float blockDensity = this.level.densityCache.getDensity(vec3d, entity);
+ if (blockDensity == me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {
blockDensity = this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations
- this.level.explosionDensityCache.put(key, blockDensity);
+ this.level.densityCache.putDensity(vec3d, entity, blockDensity);
+ // Sakura end - replace density cache
}
return blockDensity;
diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
index c2eb63de04fc48bd2cc1aad8d9cba272c0829c80..0d9f944a3777ac3a0f569832468c5c97b0fdf488 100644
--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
@@ -109,6 +109,11 @@ public abstract class BasePressurePlateBlock extends Block {
if (output != j) {
BlockState iblockdata1 = this.setSignalForState(state, j);
+ // Sakura start - explosion density cache
+ if (!world.paperConfig().environment.optimizeExplosions) {
+ world.densityCache.invalidate();
+ }
+ // Sakura end - explosion density cache
world.setBlock(pos, iblockdata1, 2);
this.updateNeighbours(world, pos);
world.setBlocksDirty(pos, state, iblockdata1);
diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
index c2589f42c467ca672417c24076313da51bb2dcbb..5ae5ef6edf3a6e2b8be9ce11ca46c7714accc4f3 100644
--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
@@ -175,6 +175,11 @@ public class TripWireHookBlock extends Block {
blockposition1 = pos.relative(enumdirection, j);
Direction enumdirection1 = enumdirection.getOpposite();
+ // Sakura start - explosion density cache
+ if (!world.paperConfig().environment.optimizeExplosions) {
+ world.densityCache.invalidate();
+ }
+ // Sakura end - explosion density cache
world.setBlock(blockposition1, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection1), 3);
TripWireHookBlock.notifyNeighbors(block, world, blockposition1, enumdirection1);
TripWireHookBlock.emitState(world, blockposition1, flag4, flag5, flag2, flag3);
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index 6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130..a867ec0347038c7246af3f3377eceda17e695ec3 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java
+++ b/src/main/java/net/minecraft/world/phys/AABB.java
@@ -551,4 +551,28 @@ public class AABB {
public static AABB ofSize(Vec3 center, double dx, double dy, double dz) {
return new AABB(center.x - dx / 2.0, center.y - dy / 2.0, center.z - dz / 2.0, center.x + dx / 2.0, center.y + dy / 2.0, center.z + dz / 2.0);
}
+
+ // Sakura start - explosion density cache
+ public final boolean isAABBInBounds(AABB bb) {
+ return this.minX <= bb.minX && this.maxX >= bb.maxX
+ && this.minY <= bb.minY && this.maxY >= bb.maxY
+ && this.minZ <= bb.minZ && this.maxZ >= bb.maxZ;
+ }
+
+ public final boolean isVec3InBounds(Vec3 p) {
+ return this.minX <= p.x && this.maxX >= p.x
+ && this.minY <= p.y && this.maxY >= p.y
+ && this.minZ <= p.z && this.maxZ >= p.z;
+ }
+
+ public final AABB expand(Vec3 pos) {
+ double minX = Math.min(this.minX, pos.x);
+ double minY = Math.min(this.minY, pos.y);
+ double minZ = Math.min(this.minZ, pos.z);
+ double maxX = Math.max(this.maxX, pos.x);
+ double maxY = Math.max(this.maxY, pos.y);
+ double maxZ = Math.max(this.maxZ, pos.z);
+ return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
+ }
+ // Sakura end - explosion density cache
}

View File

@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 3 May 2024 15:18:58 +0100
Subject: [PATCH] Optimise explosions in protected regions
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 10432b31acd047ac2aa8581f2ee863254ec8d1eb..b07c2eb8cc3cc54e8ab72c5ed3d6d7623416e7fb 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -345,6 +345,22 @@ public class ServerExplosion implements Explosion {
return rays;
}
// Sakura end - optimise paper explosions
+ // Sakura start - optimise explosion protected regions
+ protected final boolean isRegionUnprotected() {
+ // optimisation: We check if a plugin has cancelled the event or cleared the blockList.
+ // It tells us if the result was thrown away, so we can avoid the block searching logic.
+ // As a side effect the event is called twice which may interfere with some plugins.
+ if (this.source != null && this.level.sakuraConfig().cannons.explosion.optimiseProtectedRegions) {
+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z);
+ List<org.bukkit.block.Block> blocks = new ObjectArrayList<>(1);
+ blocks.add(location.getBlock());
+ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blocks, 0.0f, this.blockInteraction);
+ return !event.isCancelled() && !event.blockList().isEmpty();
+ }
+
+ return true;
+ }
+ // Sakura end - optimise explosion protected regions
public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
this.level = world;
@@ -441,6 +457,11 @@ public class ServerExplosion implements Explosion {
return ret;
}
// Sakura end - optimise paper explosions
+ // Sakura start - optimise protected explosions
+ if (!this.isRegionUnprotected()) {
+ return ret;
+ }
+ // Sakura end - optimise protected explosions
// only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of
// a 16x16x16 cube

View File

@@ -0,0 +1,661 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 3 May 2024 15:04:31 +0100
Subject: [PATCH] Specialised Explosions
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
index c21e00812f1aaa1279834a0562d360d6b89e146c..1e1329adde1457898a3002279b53b1bbb91c36d2 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
@@ -107,6 +107,12 @@ public final class IteratorSafeOrderedReferenceSet<E> {
}
}
+ // Sakura start - specialised explosions; add indexOf method
+ public int indexOf(final E element) {
+ return this.indexMap.getInt(element);
+ }
+ // Sakura end - specialised explosions; add indexOf method
+
public boolean remove(final E element) {
final int index = this.indexMap.removeInt(element);
if (index >= 0) {
diff --git a/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java b/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce04a5d9aaef3ca8ba8d7b988bdf0497285f90c1
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java
@@ -0,0 +1,203 @@
+package me.samsuik.sakura.explosion.special;
+
+import ca.spottedleaf.moonrise.common.util.WorldUtil;
+import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
+import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.Mth;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.ExplosionDamageCalculator;
+import net.minecraft.world.level.ServerExplosion;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplosion {
+ private static final double ENTITY_DISPATCH_DISTANCE = Math.pow(32.0, 2.0);
+
+ protected final T cause; // preferred over source
+ private Vec3 impactPosition;
+ 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) {
+ super(level, entity, damageSource, behavior, center, power, createFire, destructionType);
+ this.cause = entity;
+ this.impactPosition = center;
+ this.applyEffects = applyEffects;
+ }
+
+ protected double getExplosionOffset() {
+ return (double) this.cause.getBbHeight() * 0.0625D;
+ }
+
+ protected abstract int getExplosionCount();
+
+ protected abstract void startExplosion();
+
+ @Override
+ public final void explode() {
+ if (this.radius() < 0.1F) {
+ // (radius < 0.1F) in bukkit is assumed to not be able to find any blocks or entities.
+ for (int i = this.getExplosionCount() - 1; i >= 0; --i) {
+ this.finalizeExplosionAndParticles(List.of());
+ }
+ } else {
+ this.createBlockCache();
+ this.startExplosion(); // search for blocks, impact entities, finalise if necessary
+ this.clearBlockCache();
+ }
+ }
+
+ protected final boolean requiresImpactEntities(List<BlockPos> blocks, Vec3 center) {
+ if (this.impactPosition.distanceToSqr(center) > ENTITY_DISPATCH_DISTANCE) {
+ this.impactPosition = center;
+ return true;
+ }
+ return !blocks.isEmpty();
+ }
+
+ protected final boolean finalizeExplosionAndParticles(List<BlockPos> blocks) {
+ this.wasCanceled = false;
+ List<BlockPos> explodedPositions = new ObjectArrayList<>(blocks);
+ this.interactWithBlocks(explodedPositions);
+
+ if (!this.wasCanceled) {
+ this.applyEffects.accept(this);
+ this.getHitPlayers().clear();
+ }
+
+ return !explodedPositions.isEmpty();
+ }
+
+ 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.
+ if (this.level().sakuraConfig().cannons.explosion.useBlockCacheAcrossExplosions && !foundBlocks.isEmpty() && !destroyedBlocks) {
+ this.markBlocksInCacheAsExplodable(foundBlocks);
+ } else {
+ super.blockCache.clear();
+ }
+
+ java.util.Arrays.fill(this.directMappedBlockCache, null);
+ }
+
+ 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();
+ 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());
+
+ 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;
+
+ 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);
+
+ if (chunk == null) {
+ continue;
+ }
+
+ for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) {
+ sliceConsumer.accept(chunk.getSectionEntities(chunkY));
+ }
+ }
+ }
+ }
+
+ 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;
+
+ if (entity != this.source && !entity.ignoreExplosion(this)) {
+ this.impactEntity(entity, position, potential, radius);
+ }
+
+ if (entities[i] != entity) {
+ i--;
+ }
+ }
+ }
+
+ protected final void impactEntity(Entity entity, Vec3 pos, int potential, double radius) {
+ if (this.excludeSourceFromDamage && entity == this.source) {
+ return; // for paper api
+ }
+ if (entity.isPrimedTNT || entity.isFallingBlock) {
+ this.impactCannonEntity(entity, pos, potential, radius);
+ } else {
+ for (int i = 0; i < potential; ++i) {
+ super.impactEntity((float) radius, entity);
+ }
+ }
+ }
+
+ protected final void impactCannonEntity(Entity entity, Vec3 pos, int potential, double radius) {
+ double distanceFromBottom = Math.sqrt(entity.distanceToSqr(pos)) / radius;
+
+ if (distanceFromBottom <= 1.0) {
+ double x = entity.getX() - pos.x;
+ double y = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - pos.y;
+ double z = entity.getZ() - pos.z;
+ double distance = Math.sqrt(x * x + y * y + z * z);
+
+ 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;
+
+ if (exposure == 0.0) {
+ return;
+ }
+
+ x *= exposure;
+ y *= exposure;
+ z *= exposure;
+
+ this.applyEntityVelocity(entity, x, y, z, potential);
+ }
+ }
+ }
+
+ protected final void applyEntityVelocity(Entity entity, double x, double y, double z, int potential) {
+ Vec3 movement = entity.getDeltaMovement();
+
+ double moveX = movement.x();
+ double moveY = movement.y();
+ double moveZ = movement.z();
+
+ for (int i = 0; i < potential; ++i) {
+ moveX += x;
+ moveY += y;
+ moveZ += z;
+ }
+
+ entity.setDeltaMovement(moveX, moveY, moveZ);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java b/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebe5f0c8c2f09920b5f5ef734e63f5e7cd8bd3a1
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java
@@ -0,0 +1,201 @@
+package me.samsuik.sakura.explosion.special;
+
+import ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import me.samsuik.sakura.entity.EntityState;
+import me.samsuik.sakura.entity.merge.MergeLevel;
+import me.samsuik.sakura.entity.merge.MergeableEntity;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.item.PrimedTnt;
+import net.minecraft.world.level.ExplosionDamageCalculator;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.craftbukkit.util.CraftVector;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+public final class TntExplosion extends SpecialisedExplosion<PrimedTnt> {
+ private static final int ALL_DIRECTIONS = 0b111;
+ private static final int FOUND_ALL_BLOCKS = ALL_DIRECTIONS + 12;
+
+ private final Vec3 originalPosition;
+ private final List<Vec3> explosions = new ObjectArrayList<>();
+ private AABB bounds;
+ private int wrapped = 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) {
+ super(level, tnt, damageSource, behavior, center, power, createFire, destructionType, applyEffects);
+ this.originalPosition = center;
+ this.bounds = new AABB(center, center);
+ }
+
+ @Override
+ protected int getExplosionCount() {
+ if (this.cause.getMergeEntityData().getMergeLevel() == MergeLevel.NONE) {
+ this.mergeEntitiesBeforeExplosion();
+ }
+ return this.cause.getMergeEntityData().getCount();
+ }
+
+ @Override
+ protected void startExplosion() {
+ for (int i = this.getExplosionCount() - 1; i >= 0; --i) {
+ Vec3 previousMomentum = this.cause.entityState().momentum();
+ boolean lastCycle = i == 0;
+ List<BlockPos> toBlow = this.midExplosion(previousMomentum, lastCycle); // search for blocks and impact entities
+ boolean destroyedBlocks = this.finalizeExplosionAndParticles(toBlow);
+
+ if (!lastCycle) {
+ EntityState entityState = this.nextSourceVelocity();
+ this.postExplosion(toBlow, destroyedBlocks); // update wrapped, clear recent block cache
+ this.updateExplosionPosition(entityState, destroyedBlocks);
+ }
+ }
+ }
+
+ private List<BlockPos> midExplosion(Vec3 previousMomentum, boolean lastCycle) {
+ final List<BlockPos> explodedPositions;
+ if (this.wrapped < FOUND_ALL_BLOCKS) {
+ explodedPositions = this.calculateExplodedPositions();
+ } else {
+ explodedPositions = List.of();
+ }
+
+ if (this.wrapped < ALL_DIRECTIONS) {
+ Vec3 momentum = this.cause.entityState().momentum();
+ for (Direction.Axis axis : Direction.Axis.VALUES) {
+ double current = momentum.get(axis);
+ double previous = previousMomentum.get(axis);
+ if (current == previous || current * previous < 0) {
+ this.wrapped |= 1 << axis.ordinal();
+ }
+ }
+ }
+
+ this.bounds = this.bounds.expand(this.center);
+ this.explosions.add(this.center);
+
+ if (lastCycle || this.requiresImpactEntities(explodedPositions, this.center)) {
+ this.locateAndImpactEntitiesInBounds();
+ this.bounds = new AABB(this.center, this.center);
+ this.explosions.clear();
+ }
+
+ return explodedPositions;
+ }
+
+ @Override
+ protected void postExplosion(List<BlockPos> foundBlocks, boolean destroyedBlocks) {
+ super.postExplosion(foundBlocks, destroyedBlocks);
+ // Update wrapped, this is for tracking swinging and if blocks are found
+ if (this.wrapped >= ALL_DIRECTIONS) {
+ if (!destroyedBlocks && this.level().sakuraConfig().cannons.explosion.avoidRedundantBlockSearches) {
+ this.wrapped++;
+ } else {
+ this.wrapped = ALL_DIRECTIONS;
+ }
+ }
+ }
+
+ private Vector getCauseOrigin() {
+ Vector origin = this.cause.getOriginVector();
+ return origin == null ? CraftVector.toBukkit(this.center) : origin;
+ }
+
+ private EntityState nextSourceVelocity() {
+ Vector origin = this.getCauseOrigin(); // valid position to use while creating a temporary entity
+ PrimedTnt tnt = new PrimedTnt(this.level(), origin.getX(), origin.getY(), origin.getZ(), 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) {
+ // Before setting entity state, otherwise we might cause issues.
+ 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 = this.cause.entityState().momentum().lengthSqr();
+ hasMoved = oldMomentum <= Math.pow(this.radius() * 2.0 + 1.0, 2.0) || newMomentum <= oldMomentum;
+ }
+
+ // Keep track of entity state
+ entityState.apply(this.cause);
+ this.cause.storeEntityState();
+
+ // Ticking is always required after destroying a block.
+ if (destroyedBlocks || hasMoved) {
+ this.cause.setFuse(100);
+ this.cause.tick();
+ this.moved |= !this.center.equals(this.originalPosition);
+ this.recalculateExplosionPosition();
+ }
+ }
+
+ private void mergeEntitiesBeforeExplosion() {
+ 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)) != -1) {
+ 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 moved = (change != 0.0);
+
+ this.forEachEntitySliceInBounds(bb.inflate(radius), entities -> {
+ if (moved) {
+ 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.impactEntity(entity, radius);
+ }
+
+ if (entities[i] != entity) {
+ i--;
+ }
+ }
+ }
+
+ private void impactEntity(Entity entity, double radius) {
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < this.explosions.size(); i++) {
+ this.impactEntity(entity, this.explosions.get(i), 1, radius);
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 1e940c5b9ce60dd1dd7ec03e83766e26d1112d73..1973da88ac7c3a36cc2db48aeb7d4eab788be500 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1913,7 +1913,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
Explosion.BlockInteraction explosion_effect1 = explosion_effect;
Vec3 vec3d = new Vec3(d0, d1, d2);
- ServerExplosion serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1);
+ // Sakura start - specialised explosions
+ final ServerExplosion serverexplosion;
+ if (entity instanceof net.minecraft.world.entity.item.PrimedTnt tnt) {
+ serverexplosion = new me.samsuik.sakura.explosion.special.TntExplosion(this, tnt, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1, self -> {
+ this.notifyPlayersOfExplosion(self, self.center(), particleparam, particleparam1, holder);
+ });
+ } else {
+ serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1);
+ }
+ // Sakura end - specialised explosions
if (configurator != null) configurator.accept(serverexplosion);// Paper - Allow explosions to damage source
serverexplosion.explode();
@@ -1922,6 +1931,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
return serverexplosion;
}
// CraftBukkit end
+ // Sakura start - specialised explosions
+ this.notifyPlayersOfExplosion(serverexplosion, vec3d, particleparam, particleparam1, holder);
+ return serverexplosion;
+ }
+
+ private void notifyPlayersOfExplosion(ServerExplosion serverexplosion, Vec3 vec3d, ParticleOptions particleparam, ParticleOptions particleparam1, Holder<SoundEvent> holder) {
+ // Sakura end - specialised explosions
ParticleOptions particleparam2 = serverexplosion.isSmall() ? particleparam : particleparam1;
Iterator iterator = this.players.iterator();
@@ -1946,7 +1962,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
}
- return serverexplosion; // CraftBukkit
+ // Sakura - specialised explosions; return moved up into explode
}
private Explosion.BlockInteraction getDestroyType(GameRules.Key<GameRules.BooleanValue> decayRule) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index f7d8e2ba0ee9b8dd27f20a3e75992b107d07fbf0..187cb36e139ce66497e4e20ce75c0a5c4309632e 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -78,20 +78,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
@Override
public final void respawnEntity(int count) {
- PrimedTnt tnt = new PrimedTnt(EntityType.TNT, this.level());
- tnt.updateBukkitHandle(this); // update handle for plugins
- while (count-- > 1) {
- this.setFuse(100); // Prevent unwanted explosions while ticking
-
- // Cause an explosion to affect this entity
- tnt.setPos(this.position());
- tnt.setDeltaMovement(this.getDeltaMovement());
- this.entityState().apply(this);
- tnt.explode();
- this.storeEntityState();
-
- this.tick();
- }
+ this.mergeData.setCount(count); // Sakura - specialised explosions
}
// Sakura end - merge cannon entities
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 190eca0e25a8761f88d15a5eac02d9a67b5635de..74abe5fe92b3f1fe0139f4879fe5efa10d0623e0 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -52,9 +52,9 @@ public class ServerExplosion implements Explosion {
private final boolean fire;
private final Explosion.BlockInteraction blockInteraction;
private final ServerLevel level;
- private final Vec3 center;
+ protected Vec3 center; // Sakura - specialised explosions; private final -> protected
@Nullable
- private final Entity source;
+ protected final Entity source; // Sakura - specialised explosions; private -> protected
private final float radius;
private final DamageSource damageSource;
private final ExplosionDamageCalculator damageCalculator;
@@ -103,13 +103,13 @@ public class ServerExplosion implements Explosion {
// resistance = (res + 0.3F) * 0.3F;
// so for resistance = 0, we need res = -0.3F
private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f);
- private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache> blockCache = null;
+ protected it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache> blockCache = null; // Sakura - specialised explosions; private -> protected
private long[] chunkPosCache = null;
private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null;
- private ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] directMappedBlockCache;
+ protected ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] directMappedBlockCache; // Sakura - specialised explosions; private -> protected
private BlockPos.MutableBlockPos mutablePos;
- private ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z,
+ protected final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, // Sakura - specialised explosions; private -> protected
final long key, final boolean calculateResistance) {
ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache ret = this.blockCache.get(key);
if (ret != null) {
@@ -361,6 +361,38 @@ public class ServerExplosion implements Explosion {
return true;
}
// Sakura end - optimise explosion protected regions
+ // Sakura start - specialised explosions
+ protected final void createBlockCache() {
+ // Paper start - collision optimisations
+ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
+ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
+ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
+ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
+ this.directMappedBlockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
+ this.mutablePos = new BlockPos.MutableBlockPos();
+ // Paper end - collision optimisations
+ }
+
+ protected final void markBlocksInCacheAsExplodable(List<BlockPos> explodedPositions) {
+ for (BlockPos blow : explodedPositions) {
+ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache cache = this.blockCache.get(blow.asLong());
+ // May be null if the blockCache is cleared then retrieved from the recent block cache
+ if (cache != null) {
+ cache.shouldExplode = null;
+ }
+ }
+ }
+
+ protected final void clearBlockCache() {
+ // Paper start - collision optimisations
+ this.blockCache = null;
+ this.chunkPosCache = null;
+ this.chunkCache = null;
+ this.directMappedBlockCache = null;
+ this.mutablePos = null;
+ // Paper end - collision optimisations
+ }
+ // Sakura end - specialised explosions
public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
this.level = world;
@@ -432,7 +464,7 @@ public class ServerExplosion implements Explosion {
return this.center;
}
- private List<BlockPos> calculateExplodedPositions() {
+ protected final List<BlockPos> calculateExplodedPositions() { // Sakura - specialised explosions; private -> protected
// Paper start - collision optimisations
final ObjectArrayList<BlockPos> ret = new ObjectArrayList<>();
@@ -670,7 +702,10 @@ public class ServerExplosion implements Explosion {
Player entityhuman = (Player) entity;
if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback
- this.hitPlayers.put(entityhuman, vec3d);
+ // Sakura start - specialised explosions; tally player velocity
+ final Vec3 explosionImpact = vec3d;
+ this.hitPlayers.compute(entityhuman, (p, v) -> v != null ? v.add(explosionImpact) : explosionImpact);
+ // Sakura end - specialised explosions; tally player velocity
}
}
@@ -682,7 +717,7 @@ public class ServerExplosion implements Explosion {
}
- private void interactWithBlocks(List<BlockPos> positions) {
+ protected final void interactWithBlocks(List<BlockPos> positions) { // Sakura - specialised explosions; private -> protected
List<ServerExplosion.StackCollector> list1 = new ArrayList();
Util.shuffle(positions, this.level.random);
@@ -787,14 +822,7 @@ public class ServerExplosion implements Explosion {
return;
}
// CraftBukkit end
- // Paper start - collision optimisations
- this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
- this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
- java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
- this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
- this.directMappedBlockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
- this.mutablePos = new BlockPos.MutableBlockPos();
- // Paper end - collision optimisations
+ this.createBlockCache(); // Sakura - specialised explosions
this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center);
List<BlockPos> list = this.calculateExplodedPositions();
@@ -810,13 +838,7 @@ public class ServerExplosion implements Explosion {
if (this.fire) {
this.createFire(list);
}
- // Paper start - collision optimisations
- this.blockCache = null;
- this.chunkPosCache = null;
- this.chunkCache = null;
- this.directMappedBlockCache = null;
- this.mutablePos = null;
- // Paper end - collision optimisations
+ this.clearBlockCache(); // Sakura - specialised explosions
}
@@ -837,7 +859,7 @@ public class ServerExplosion implements Explosion {
}
- private boolean interactsWithBlocks() {
+ protected final boolean interactsWithBlocks() { // Sakura - specialised explosions; private -> protected
return this.blockInteraction != Explosion.BlockInteraction.KEEP;
}
@@ -908,7 +930,7 @@ public class ServerExplosion implements Explosion {
}
// Paper start - Optimize explosions
- private float getBlockDensity(Vec3 vec3d, Entity entity) {
+ protected final float getBlockDensity(Vec3 vec3d, Entity entity) { // Sakura - specialised explosions; private -> protected
// Sakura start - replace density cache
float blockDensity = this.level.densityCache.getDensity(vec3d, entity);
if (blockDensity == me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {

View File

@@ -0,0 +1,220 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Fri, 13 Oct 2023 14:36:19 +0100
Subject: [PATCH] Optimise cannon entity movement
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index fd1d02604a1e46545c7f2c8ad89968279e2fe1bf..953aa1b3d292dd8e8ed29926c269e6d6e79a83c0 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -1222,6 +1222,75 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return this.moveStartZ;
}
// Paper end - detailed watchdog information
+ // Sakura start - optimise cannon entity movement; stripped back movement method
+ public final void moveStripped(MoverType movementType, Vec3 movement) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
+ if (this.noPhysics) {
+ this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
+ } else {
+ if (movementType == MoverType.PISTON) {
+ movement = this.limitPistonMovement(movement);
+ if (movement.equals(Vec3.ZERO)) {
+ return;
+ }
+ }
+
+ ProfilerFiller gameprofilerfiller = Profiler.get();
+ gameprofilerfiller.push("move");
+ if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) {
+ movement = movement.multiply(this.stuckSpeedMultiplier);
+ this.stuckSpeedMultiplier = Vec3.ZERO;
+ this.setDeltaMovement(Vec3.ZERO);
+ }
+
+ Vec3 vec3d1 = this.sakura_collide(movement);
+ double d0 = vec3d1.lengthSqr();
+
+ if (d0 > 1.0E-7D || movement.lengthSqr() - d0 < 1.0E-7D) {
+ if (this.fallDistance != 0.0F && d0 >= 1.0D && !this.isFallingBlock) {
+ BlockHitResult clipResult = this.level().clip(new ClipContext(this.position(), this.position().add(vec3d1), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this));
+ if (clipResult.getType() != HitResult.Type.MISS) {
+ this.resetFallDistance();
+ }
+ }
+
+ this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z);
+ }
+
+ gameprofilerfiller.pop();
+ gameprofilerfiller.push("rest");
+ boolean movedX = !Mth.equal(movement.x, vec3d1.x);
+ boolean movedZ = !Mth.equal(movement.z, vec3d1.z);
+
+ this.horizontalCollision = movedX || movedZ;
+ this.verticalCollision = movement.y != vec3d1.y;
+ this.verticalCollisionBelow = this.verticalCollision && movement.y < 0.0D;
+
+ this.setOnGroundWithMovement(this.verticalCollisionBelow, this.horizontalCollision, vec3d1);
+ BlockPos blockPosBelow = this.getOnPosLegacy();
+ BlockState blockstate = this.level().getBlockState(blockPosBelow);
+
+ this.checkFallDamage(vec3d1.y, this.onGround(), blockstate, blockPosBelow);
+ if (this.isRemoved()) {
+ gameprofilerfiller.pop();
+ } else {
+ if (this.horizontalCollision) {
+ Vec3 vec3d2 = this.getDeltaMovement();
+ this.setDeltaMovement(movedX ? 0.0D : vec3d2.x, vec3d2.y, movedZ ? 0.0D : vec3d2.z);
+ }
+
+ Block block = blockstate.getBlock();
+ if (movement.y != vec3d1.y) { // remove y momentum
+ block.updateEntityMovementAfterFallOn(this.level(), this);
+ }
+
+ float f = this.getBlockSpeedFactor();
+ this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f));
+ gameprofilerfiller.pop();
+ }
+ }
+ }
+ // Sakura end - optimise cannon entity movement; stripped back movement method
public void move(MoverType type, Vec3 movement) {
final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
@@ -1588,6 +1657,107 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return offsetFactor;
}
+ // Sakura start - optimise cannon entity movement
+ private Vec3 sakura_collide(Vec3 movement) {
+ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) {
+ return movement;
+ }
+
+ List<VoxelShape> potentialCollisionsVoxel = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(0);
+ List<AABB> potentialCollisionsBB = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(4);
+ AABB currBoundingBox = this.getBoundingBox();
+
+ if (movement.lengthSqr() >= 12.0) { // axis scan on large movement
+ return this.collideAxisScan(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB);
+ } else {
+ return this.collideCube(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB);
+ }
+ }
+
+ private Vec3 collideCube(Vec3 movement, AABB currBoundingBox, List<VoxelShape> voxelList, List<AABB> bbList) {
+ final AABB bb;
+ if (movement.x() == 0.0 && movement.z() == 0.0) {
+ if (movement.y > 0.0) {
+ bb = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y);
+ } else {
+ bb = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y);
+ }
+ } else {
+ bb = currBoundingBox.expandTowards(movement.x, movement.y, movement.z);
+ }
+ this.collectCollisions(bb, voxelList, bbList);
+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, voxelList, bbList);
+ }
+
+ private Vec3 collideAxisScan(Vec3 movement, AABB currBoundingBox, List<VoxelShape> voxelList, List<AABB> bbList) {
+ double x = movement.x;
+ double y = movement.y;
+ double z = movement.z;
+
+ boolean xSmaller = Math.abs(x) < Math.abs(z);
+
+ if (y != 0.0) {
+ y = this.scanY(currBoundingBox, y, voxelList, bbList);
+ if (y != 0.0) {
+ currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetY(currBoundingBox, y);
+ }
+ }
+
+ if (xSmaller && z != 0.0) {
+ z = this.scanZ(currBoundingBox, z, voxelList, bbList);
+ if (z != 0.0) {
+ currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetZ(currBoundingBox, z);
+ }
+ }
+
+ if (x != 0.0) {
+ x = this.scanX(currBoundingBox, x, voxelList, bbList);
+ if (x != 0.0) {
+ currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetX(currBoundingBox, x);
+ }
+ }
+
+ if (!xSmaller && z != 0.0) {
+ z = this.scanZ(currBoundingBox, z, voxelList, bbList);
+ }
+
+ return new Vec3(x, y, z);
+ }
+
+ private void collectCollisions(AABB collisionBox, List<VoxelShape> voxelList, List<AABB> bbList) {
+ // Copied from the collide method below
+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder(
+ this.level, this, collisionBox, voxelList, bbList,
+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | this.getExtraCollisionFlags(), null // Sakura - load chunks on movement
+ );
+
+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions(
+ this.level, this, collisionBox, bbList, 0, null
+ );
+ }
+
+ private double scanX(AABB currBoundingBox, double x, List<VoxelShape> voxelList, List<AABB> bbList) {
+ AABB scanBox = currBoundingBox.expandTowards(x, 0.0, 0.0);
+ this.collectCollisions(scanBox, voxelList, bbList);
+ x = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performAABBCollisionsX(currBoundingBox, x, bbList);
+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performVoxelCollisionsX(currBoundingBox, x, voxelList);
+ }
+
+ private double scanY(AABB currBoundingBox, double y, List<VoxelShape> voxelList, List<AABB> bbList) {
+ AABB scanBox = currBoundingBox.expandTowards(0.0, y, 0.0);
+ this.collectCollisions(scanBox, voxelList, bbList);
+ y = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performAABBCollisionsY(currBoundingBox, y, bbList);
+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performVoxelCollisionsY(currBoundingBox, y, voxelList);
+ }
+
+ private double scanZ(AABB currBoundingBox, double z, List<VoxelShape> voxelList, List<AABB> bbList) {
+ AABB scanBox = currBoundingBox.expandTowards(0.0, 0.0, z);
+ this.collectCollisions(scanBox, voxelList, bbList);
+ z = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performAABBCollisionsZ(currBoundingBox, z, bbList);
+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performVoxelCollisionsZ(currBoundingBox, z, voxelList);
+ }
+ // Sakura end - optimise cannon entity movement
+
private Vec3 collide(Vec3 movement) {
// Paper start - optimise collisions
final boolean xZero = movement.x == 0.0;
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 7dc5e5d91bb91c86ddca52a462903e8452396090..a2683e26fc252f0ce933e3ea71fde9c84bcee0fd 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -213,7 +213,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
++this.time;
this.applyGravity();
- this.move(MoverType.SELF, this.getDeltaMovement());
+ this.moveStripped(MoverType.SELF, this.getDeltaMovement()); // Sakura - optimise cannon entity movement
this.applyEffectsFromBlocks();
// Paper start - Configurable falling blocks height nerf
if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 187cb36e139ce66497e4e20ce75c0a5c4309632e..d60d25c0d731738c647fee6e618d3e4942949dc7 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -130,7 +130,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot
this.handlePortal();
this.applyGravity();
- this.move(MoverType.SELF, this.getDeltaMovement());
+ this.moveStripped(MoverType.SELF, this.getDeltaMovement()); // Sakura - optimise cannon entity movement
this.applyEffectsFromBlocks();
// Paper start - Configurable TNT height nerf
if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {

View File

@@ -0,0 +1,117 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 27 Jun 2024 17:02:32 +0100
Subject: [PATCH] Add maxSearch to getEntities
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
index 39148fe540d616cdd4d63c4d1a02b422cab0f6eb..50a551d5bcac9b2b6644d7b43e625185090657a6 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
@@ -314,7 +314,14 @@ public final class ChunkEntitySlices {
public boolean getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate,
final int maxCount) {
- return this.allEntities.getEntitiesLimited(except, box, into, predicate, maxCount);
+ // Sakura start - add maxSearch to getEntities
+ return this.getEntities(except, box, into, predicate, maxCount, Integer.MAX_VALUE);
+ }
+
+ public boolean getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate,
+ final int maxCount, final int maxSearch) {
+ return this.allEntities.getEntitiesLimited(except, box, into, predicate, maxCount, maxSearch);
+ // Sakura end - add maxSearch to getEntities
}
public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
@@ -570,6 +577,13 @@ public final class ChunkEntitySlices {
public boolean getEntitiesLimited(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate,
final int maxCount) {
+ // Sakura start - add maxSearch to getEntities
+ return this.getEntitiesLimited(except, box, into, predicate, maxCount, Integer.MAX_VALUE);
+ }
+
+ public boolean getEntitiesLimited(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate,
+ final int maxCount, final int maxSearch) {
+ // Sakura end - add maxSearch to getEntities
if (this.count == 0) {
return false;
}
@@ -591,8 +605,14 @@ public final class ChunkEntitySlices {
final Entity[] storage = list.storage;
- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
- final Entity entity = storage[i];
+ // Sakura start - add maxSearch to getEntities
+ final int len = Math.min(storage.length, list.size());
+ final int offset = this.slices.world.random.nextInt(len);
+ for (int i = 0; i < len; ++i) {
+ final int pos = (i + offset) % len;
+ final Entity entity = storage[pos];
+ if (i > maxSearch) break;
+ // Sakura end - add maxSearch to getEntities
if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
continue;
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
index 7554c109c35397bc1a43dd80e87764fd78645bbf..d60f30f7afb15cc90c1bd4b816136d00b23a53e4 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
@@ -722,6 +722,13 @@ public abstract class EntityLookup implements LevelEntityGetter<Entity> {
public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate,
final int maxCount) {
+ // Sakura start - add maxSearch to getEntities
+ this.getEntities(except, box, into, predicate, maxCount, Integer.MAX_VALUE);
+ }
+
+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate,
+ final int maxCount, final int maxSearch) {
+ // Sakura end - add maxSearch to getEntities
final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
@@ -753,7 +760,7 @@ public abstract class EntityLookup implements LevelEntityGetter<Entity> {
continue;
}
- if (chunk.getEntities(except, box, into, predicate, maxCount)) {
+ if (chunk.getEntities(except, box, into, predicate, maxCount, maxSearch)) { // Sakura - add maxSearch to getEntities
return;
}
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index addab159f6fb73fe063f8a057c281d0594a99122..0301e304bb67d3a52de551442e425b83152fbabf 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1693,10 +1693,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE);
}
- // Paper start - rewrite chunk system
+ // Sakura start - add maxSearch to getEntities
public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
final AABB boundingBox, final Predicate<? super T> predicate,
final List<? super T> into, final int maxCount) {
+ this.getEntities(entityTypeTest, boundingBox, predicate, into, maxCount, Integer.MAX_VALUE);
+ }
+
+ // Paper start - rewrite chunk system
+ public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
+ final AABB boundingBox, final Predicate<? super T> predicate,
+ final List<? super T> into, final int maxCount, final int maxSearch) {
+ // Sakura end - add maxSearch to getEntities
Profiler.get().incrementCounter("getEntities");
if (entityTypeTest instanceof net.minecraft.world.entity.EntityType<T> byType) {
@@ -1713,7 +1721,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
if (entityTypeTest == null) {
if (maxCount != Integer.MAX_VALUE) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount);
+ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount, maxSearch); // Sakura - add maxSearch to getEntities
ca.spottedleaf.moonrise.common.PlatformHooks.get().addToGetEntities((Level)(Object)this, entityTypeTest, boundingBox, predicate, into, maxCount);
return;
} else {

View File

@@ -0,0 +1,24 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 23 Sep 2021 18:50:13 +0100
Subject: [PATCH] Optimise LivingEntity#pushEntities
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 96b4fbe4a4655777ff10b32e3257e2fac2aba12a..01179c4b01aa4898d08b392bd682587e3858b822 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3800,7 +3800,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
return;
}
// Paper end - don't run getEntities if we're not going to use its result
- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule
+ // Sakura start - use maxEntityCollision limit for entity retrieval
+ int limit = Math.max(i, this.level().paperConfig().collisions.maxEntityCollisions);
+ int search = limit * limit;
+ List<Entity> list = new ArrayList<>();
+ this.level().getEntities(null, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule), list, limit, search); // Paper - Climbing should not bypass cramming gamerule
+ // Sakura end - use maxEntityCollision limit for entity retrieval
if (!list.isEmpty()) {
// Paper - don't run getEntities if we're not going to use its result; moved up

View File

@@ -0,0 +1,155 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Wed, 15 Nov 2023 23:18:38 +0000
Subject: [PATCH] Explosion Durable Blocks
diff --git a/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java b/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..e52cae27c2bbf4fa02d49d1c69d8e1240fddfb81
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java
@@ -0,0 +1,43 @@
+package me.samsuik.sakura.explosion.durable;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import net.minecraft.core.BlockPos;
+
+import java.util.concurrent.TimeUnit;
+
+public final class DurableBlockManager {
+ private final Cache<BlockPos, DurableBlock> durableBlocks = CacheBuilder.newBuilder()
+ .expireAfterAccess(1, TimeUnit.MINUTES)
+ .maximumSize(65534)
+ .build();
+
+ public boolean damage(BlockPos pos, DurableMaterial material) {
+ DurableBlock block = this.durableBlocks.getIfPresent(pos);
+ if (block == null) {
+ this.durableBlocks.put(pos, block = new DurableBlock(material.durability()));
+ }
+ return block.damage();
+ }
+
+ public int durability(BlockPos pos, DurableMaterial material) {
+ final DurableBlock block = this.durableBlocks.getIfPresent(pos);
+ return block != null ? block.durability() : material.durability();
+ }
+
+ private static final class DurableBlock {
+ private int durability;
+
+ public DurableBlock(int durability) {
+ this.durability = durability;
+ }
+
+ public int durability() {
+ return this.durability;
+ }
+
+ public boolean damage() {
+ return --this.durability <= 0;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java
index 4377fa2400c4320e0023ece230090a2a3b4b2ab6..9b66ac859619d3e79d5ab33939006b178699d474 100644
--- a/src/main/java/net/minecraft/world/item/BlockItem.java
+++ b/src/main/java/net/minecraft/world/item/BlockItem.java
@@ -47,8 +47,32 @@ public class BlockItem extends Item {
this.block = block;
}
+ // Sakura start - explosion durable blocks
+ private void sendBlockDurabilityToPlayer(UseOnContext context) {
+ Player player = context.getPlayer();
+ BlockState state = context.getLevel().getBlockState(context.getClickedPos());
+ Block block = state.getBlock();
+ me.samsuik.sakura.explosion.durable.DurableMaterial material = context.getLevel().localConfig().config(context.getClickedPos()).durableMaterials.get(block);
+
+ if (material != null) {
+ int remaining = context.getLevel().durabilityManager.durability(context.getClickedPos(), material);
+ int durability = material.durability();
+
+ player.getBukkitEntity().sendRichMessage(
+ me.samsuik.sakura.configuration.GlobalConfiguration.get().messages.durableBlockInteraction,
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.unparsed("remaining", String.valueOf(remaining)),
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.unparsed("durability", String.valueOf(durability))
+ );
+ }
+ }
+
@Override
public InteractionResult useOn(UseOnContext context) {
+ if (this.getBlock() == net.minecraft.world.level.block.Blocks.POTATOES && context.getPlayer() != null) {
+ this.sendBlockDurabilityToPlayer(context);
+ }
+ // Sakura end - explosion durable blocks
+
InteractionResult enuminteractionresult = this.place(new BlockPlaceContext(context));
return !enuminteractionresult.consumesAction() && context.getItemInHand().has(DataComponents.CONSUMABLE) ? super.use(context.getLevel(), context.getPlayer(), context.getHand()) : enuminteractionresult;
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 0301e304bb67d3a52de551442e425b83152fbabf..9fb78a9bd08521dff83b1f28be2421aa37af5898 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -844,6 +844,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - optimise random ticking
public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
public final me.samsuik.sakura.explosion.density.BlockDensityCache densityCache = new me.samsuik.sakura.explosion.density.BlockDensityCache(); // Sakura - explosion density cache
+ public final me.samsuik.sakura.explosion.durable.DurableBlockManager durabilityManager = new me.samsuik.sakura.explosion.durable.DurableBlockManager(); // Sakura - explosion durable blocks
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.function.Supplier<me.samsuik.sakura.configuration.WorldConfiguration> sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files // Paper - create paper world config & Anti-Xray
// Paper start - getblock optimisations - cache world height/sections
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 74abe5fe92b3f1fe0139f4879fe5efa10d0623e0..278ca240491096514f4c48be56bcfe2d2356520e 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -134,7 +134,7 @@ public class ServerExplosion implements Explosion {
BlockState blockState = ((ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk)chunk).moonrise$getBlock(x, y, z);
FluidState fluidState = blockState.getFluidState();
- Optional<Float> resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState);
+ Optional<Float> resistance = !calculateResistance ? Optional.empty() : this.calculateBlockResistance(blockState, fluidState, pos); // Sakura - explosion durable blocks
ret = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache(
key, pos, blockState, fluidState,
@@ -393,6 +393,20 @@ public class ServerExplosion implements Explosion {
// Paper end - collision optimisations
}
// Sakura end - specialised explosions
+ // Sakura start - explosion durable blocks
+ private Optional<Float> calculateBlockResistance(BlockState blockState, FluidState fluidState, BlockPos pos) {
+ if (!blockState.isAir()) {
+ Block block = blockState.getBlock();
+ me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().config(pos).durableMaterials.get(block);
+
+ if (material != null && material.resistance() >= 0.0f && (this.level.sakuraConfig().cannons.explosion.allowNonTntBreakingDurableBlocks || this.source instanceof net.minecraft.world.entity.item.PrimedTnt)) {
+ return Optional.of(material.resistance());
+ }
+ }
+
+ return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState);
+ }
+ // Sakura end - explosion durable blocks
public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
this.level = world;
@@ -783,6 +797,16 @@ public class ServerExplosion implements Explosion {
}
}
// CraftBukkit end
+ // Sakura start - explosion durable blocks
+ if (this.level.sakuraConfig().cannons.explosion.allowNonTntBreakingDurableBlocks || this.source instanceof net.minecraft.world.entity.item.PrimedTnt) {
+ me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().config(blockposition).durableMaterials.get(block);
+
+ if (material != null && material.durability() >= 0 && !this.level.durabilityManager.damage(blockposition, material)) {
+ iterator.remove();
+ continue;
+ }
+ }
+ // Sakura end - explosion durable blocks
this.level.getBlockState(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> {
ServerExplosion.addOrAppendStack(list1, itemstack, blockposition1);

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 16 Nov 2023 00:59:04 +0000
Subject: [PATCH] Destroy Waterlogged Blocks
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 8268f8df624a73eaf0b8baad9aa16bab2ed0f0bc..cc97e3a88ea99362ec043edf13c1f54c2c75daaa 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -402,6 +402,11 @@ public class ServerExplosion implements Explosion {
if (material != null && material.resistance() >= 0.0f && (this.level.sakuraConfig().cannons.explosion.allowNonTntBreakingDurableBlocks || this.source instanceof net.minecraft.world.entity.item.PrimedTnt)) {
return Optional.of(material.resistance());
}
+ // Sakura start - destroy water logged blocks
+ if (!fluidState.isEmpty() && !blockState.liquid() && this.level.sakuraConfig().cannons.explosion.destroyWaterloggedBlocks) {
+ return Optional.of(ZERO_RESISTANCE);
+ }
+ // Sakura end - destroy water logged blocks
}
return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 25 Nov 2023 21:14:45 +0000
Subject: [PATCH] Allow explosions to destroy lava
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index bdc3ff8cbe4e5eaa9d9a34f38fdd150f7368e33b..e857fd02a5e341a1a701da71874dbd850e3c5a5d 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -407,6 +407,11 @@ public class ServerExplosion implements Explosion {
return Optional.of(ZERO_RESISTANCE);
}
// Sakura end - destroy water logged blocks
+ // Sakura start - allow explosions to destroy lava
+ if (blockState.is(Blocks.LAVA) && this.level.sakuraConfig().cannons.explosion.explodeLava) {
+ return Optional.of(ZERO_RESISTANCE);
+ }
+ // Sakura end - allow explosions to destroy lava
}
return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState);
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 99fd67a78539133adf78d65e2c520ff3dd260301..37ea7f464d6ea691fb0ec08319d03f89bb41cfee 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -198,7 +198,7 @@ public abstract class BlockBehaviour implements FeatureElement {
});
}
- world.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
+ world.setBlock(pos, Blocks.AIR.defaultBlockState(), world.sakuraConfig().cannons.explosion.explodeLava && state.is(Blocks.LAVA) ? 2 : 3); // Sakura - allow explosions to destroy lava
block.wasExploded(world, pos, explosion);
}
}

View File

@@ -0,0 +1,66 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sun, 26 Nov 2023 17:57:50 +0000
Subject: [PATCH] Treat solid blocks as full when moving fast
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
index 079aa846fa9b5650675ae855ba0bb61ca42a6b52..7953ddb67530396c35b42496594a80e1b3f1b349 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
@@ -1908,6 +1908,7 @@ public final class CollisionUtil {
public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2;
public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3;
public static final int COLLISION_FLAG_ADD_TICKET = 1 << 4; // Sakura - load chunks on movement
+ public static final int COLLISION_FLAG_FULL_BLOCKS = 1 << 5; // Sakura - treat solid blocks as full when moving fast
public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb,
final List<VoxelShape> intoVoxel, final List<AABB> intoAABB,
@@ -1960,6 +1961,7 @@ public final class CollisionUtil {
final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0;
final boolean addTicket = (collisionFlags & COLLISION_FLAG_ADD_TICKET) != 0; // Sakura - load chunks on movement
+ final boolean fullBlocks = (collisionFlags & COLLISION_FLAG_FULL_BLOCKS) != 0; // Sakura - treat solid blocks as full when moving fast
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
@@ -1999,7 +2001,7 @@ public final class CollisionUtil {
continue;
}
- final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks();
+ final boolean hasSpecial = !fullBlocks && ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks(); // Sakura - treat solid blocks as full when moving fast
final int sectionAdjust = !hasSpecial ? 1 : 0;
final PalettedContainer<BlockState> blocks = section.states;
@@ -2038,6 +2040,11 @@ public final class CollisionUtil {
if (useEntityCollisionShape) {
mutablePos.set(blockX, blockY, blockZ);
blockCollision = collisionShape.getCollisionShape(blockData, world, mutablePos);
+ // Sakura start - treat solid blocks as full when moving fast
+ // todo: move this logic above emptyCollisionShape and consider all blocks as a solid except scaffolding and liquids
+ } else if (fullBlocks && blockData.moonrise$getConstantContextCollisionShape() != null) {
+ blockCollision = net.minecraft.world.phys.shapes.Shapes.block();
+ // Sakura end - treat solid blocks as full when moving fast
} else if (blockCollision == null) {
mutablePos.set(blockX, blockY, blockZ);
blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape);
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 6ab4c1db02846123e72a805a2e8070c398d726e3..21ef61cc82df99aae6be1e6c3f603058cbeb548f 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -583,6 +583,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_ADD_TICKET;
}
+ // Sakura start - treat solid blocks as full when moving fast
+ if (this.level().sakuraConfig().cannons.treatAllBlocksAsFullWhenMoving && (this.isPrimedTNT || this.isFallingBlock)) {
+ double horizontalMovementSqr = this.getDeltaMovement().horizontalDistanceSqr();
+ if (horizontalMovementSqr > Math.pow(this.level().sakuraConfig().cannons.treatAllBlocksAsFullWhenMovingFasterThan, 2.0)) {
+ flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_FULL_BLOCKS;
+ }
+ }
+ // Sakura end - treat solid blocks as full when moving fast
return flags;
}
// Sakura end - load chunks on movement

View File

@@ -0,0 +1,52 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 30 Nov 2023 15:54:49 +0000
Subject: [PATCH] Reduce entity tracker player updates
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index ed7ae4617c992b4e5f5ce14f998b9c763ee1b7e2..54bfe3dfda0e023cc3d25ddcf1fe366a6cf50c90 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -972,7 +972,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (tracker == null) {
continue;
}
- ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers);
+ // Sakura start - reduce entity tracker player updates
+ if (tracker.shouldUpdatePlayers()) {
+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers);
+ }
+ // Sakura end - reduce entity tracker player updates
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers()
|| ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
tracker.serverEntity.sendChanges();
@@ -1243,12 +1247,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return state != me.samsuik.sakura.player.visibility.VisibilityState.OFF;
}
// Sakura end - client visibility settings; entity visibility
+ // Sakura start - reduce entity tracker player updates
+ private final int playerUpdateInterval;
+ private Vec3 entityPosition;
+
+ public final boolean shouldUpdatePlayers() {
+ // We have to always update players otherwise they can turn invisible on teleports (why?)
+ if (this.entity instanceof net.minecraft.world.entity.player.Player || this.entity.tickCount % this.playerUpdateInterval == 0) {
+ return true;
+ }
+ Vec3 lastPosition = this.entityPosition;
+ this.entityPosition = this.entity.position();
+ return this.entity.position().distanceToSqr(lastPosition) >= (double) this.range / 2.0;
+ }
+ // Sakura start - reduce entity tracker player updates
public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
this.entity = entity;
this.range = i;
this.lastSectionPos = SectionPos.of((EntityAccess) entity);
+ this.playerUpdateInterval = Math.min(j, 20); // Sakura - reduce entity tracker player updates
+ this.entityPosition = entity.position(); // Sakura - reduce entity tracker player updates
}
public boolean equals(Object object) {

View File

@@ -0,0 +1,53 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 2 Dec 2023 15:14:15 +0000
Subject: [PATCH] Add option for legacy lava block formation
diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
index 9e0f4517069ee3fd16a60ccc214da68d518c7f85..0ea609556d906df3eadd3df83bd4a7f85857a14e 100644
--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
@@ -200,7 +200,15 @@ public class LiquidBlock extends Block implements BucketPickup {
if (fluidState.isSource()) {
block = Blocks.OBSIDIAN;
} else {
- final me.samsuik.sakura.physics.PhysicsVersion physics = world.localConfig().config(pos).physicsVersion;
+ // Sakura start - legacy lava block formation
+ final me.samsuik.sakura.physics.PhysicsVersion physics;
+
+ if (world.sakuraConfig().environment.blockGeneration.legacyBlockFormation) {
+ physics = me.samsuik.sakura.physics.PhysicsVersion.v1_12;
+ } else {
+ physics = world.localConfig().config(pos).physicsVersion;
+ }
+ // Sakura end - legacy lava block formation
// SANITY: In legacy a patch by paper removes the fluid level condition from vanilla.
if (physics.afterOrEqual(1_16_0) || physics.isLegacy()
diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
index 4132e26332e6e8e061f43867426291ec0aefe0a2..ce34bbe57c524964300b6086358fa456f9e6d3ca 100644
--- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
@@ -178,7 +178,7 @@ public abstract class LavaFluid extends FlowingFluid {
public boolean canBeReplacedWith(FluidState state, BlockGetter world, BlockPos pos, Fluid fluid, Direction direction) {
// Sakura start - physics version api
return state.getHeight(world, pos) >= 0.44444445F && fluid.is(FluidTags.WATER)
- && world instanceof Level level && level.localConfig().config(pos).physicsVersion.afterOrEqual(1_13_0);
+ && world instanceof Level level && !level.sakuraConfig().environment.blockGeneration.legacyBlockFormation && level.localConfig().config(pos).physicsVersion.afterOrEqual(1_13_0); // Sakura - legacy lava block formation
// Sakura end - physics version api
}
diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java
index 66b9a574eb57c6fb2964825ecca7110d079fb7cc..3fa9e6baab6b8a4f4b02ea5cbbb68d57a7f6888d 100644
--- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java
@@ -123,7 +123,7 @@ public abstract class WaterFluid extends FlowingFluid {
public boolean canBeReplacedWith(FluidState state, BlockGetter world, BlockPos pos, Fluid fluid, Direction direction) {
// Sakura start - physics version api
return direction == Direction.DOWN && !fluid.is(FluidTags.WATER)
- || fluid.is(FluidTags.LAVA) && world instanceof Level level && level.localConfig().config(pos).physicsVersion.before(1_13_0);
+ || fluid.is(FluidTags.LAVA) && world instanceof Level level && (level.sakuraConfig().environment.blockGeneration.legacyBlockFormation || level.localConfig().config(pos).physicsVersion.before(1_13_0)); // Sakura - legacy lava block formation
// Sakura end - physics version api
}

View File

@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 20 Feb 2024 19:16:16 +0000
Subject: [PATCH] Add entity travel distance limits
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 1973da88ac7c3a36cc2db48aeb7d4eab788be500..88432d15be0167450afe5e5c130b8be233c78437 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1329,6 +1329,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); // Paper - EAR 2
if (isActive) { // Paper - EAR 2
entity.tick();
+ // Sakura start - entity travel distance limits
+ if (entity.isPastTravelDistanceLimit()) {
+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN);
+ }
+ // Sakura end - entity travel distance limits
entity.postTick(); // CraftBukkit
} else { entity.inactiveTick(); } // Paper - EAR 2
gameprofilerfiller.pop();
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 1aa5803061861016a1bb790ac19b769170a76a79..a2b0fe7122c11da561ab6ceb91bc37385df16c0d 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -638,6 +638,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return this.physics;
}
// Sakura end - physics version api
+ // Sakura start - entity travel distance limits
+ private final double travelDistanceLimit;
+
+ public final boolean isPastTravelDistanceLimit() {
+ if (this.origin == null) {
+ return false;
+ }
+
+ double x = Math.pow(this.origin.getX() - this.position.x(), 2);
+ double z = Math.pow(this.origin.getZ() - this.position.z(), 2);
+ return Math.max(x, z) >= this.travelDistanceLimit;
+ }
+ // Sakura end - entity travel distance limits
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
@@ -691,6 +704,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.entityData = datawatcher_a.build();
this.setPos(0.0D, 0.0D, 0.0D);
this.eyeHeight = this.dimensions.eyeHeight();
+ this.travelDistanceLimit = Math.pow(this.level.sakuraConfig().entity.chunkTravelLimit.getOrDefault(type, Integer.MAX_VALUE) * 16.0, 2); // Sakura - entity travel distance limits
}
public boolean isColliding(BlockPos pos, BlockState state) {

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 17 Jun 2024 14:04:12 +0100
Subject: [PATCH] Protect scaffolding from creepers
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 0dae16d140666cae7633bbfef6d1c5b979d7dc9e..a9a63fa9d2c45298ebd4146e0dfeea54b6431797 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -412,6 +412,11 @@ public class ServerExplosion implements Explosion {
return Optional.of(ZERO_RESISTANCE);
}
// Sakura end - allow explosions to destroy lava
+ // Sakura start - protect scaffolding from creepers
+ if (this.level.sakuraConfig().cannons.explosion.protectScaffoldingFromCreepers && blockState.is(Blocks.SCAFFOLDING) && this.source instanceof net.minecraft.world.entity.monster.Creeper) {
+ return Optional.of(Blocks.BARRIER.getExplosionResistance());
+ }
+ // Sakura end - protect scaffolding from creepers
}
return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState);

View File

@@ -0,0 +1,89 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 9 Aug 2024 20:43:53 +0100
Subject: [PATCH] Configurable left shooting and adjusting limits
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index a2b0fe7122c11da561ab6ceb91bc37385df16c0d..95f4854ca21a6b34ad1ab95a1cb38f7d2dfd24c3 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -651,6 +651,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return Math.max(x, z) >= this.travelDistanceLimit;
}
// Sakura end - entity travel distance limits
+ // Sakura start - configurable left shooting and adjusting limits
+ public final void limitLeftShooting() {
+ Vec3 movement = this.getDeltaMovement();
+ int threshold = this.level.sakuraConfig().cannons.restrictions.leftShootingThreshold.or(-1);
+ if (threshold > 0 && (movement.x != 0.0 || movement.z != 0.0) && this.origin != null) {
+ double travelledX = Math.abs(this.getX() - this.origin.getX());
+ double travelledZ = Math.abs(this.getZ() - this.origin.getZ());
+ boolean xSmaller = travelledX < travelledZ; // intended
+
+ // Once entities have travelled past the threshold changing direction is restricted.
+ if (xSmaller && travelledX > threshold) {
+ this.setDeltaMovement(movement.multiply(1.0, 1.0, 0.0)); // limit z
+ } else if (!xSmaller && travelledZ > threshold) {
+ this.setDeltaMovement(movement.multiply(0.0, 1.0, 1.0)); // limit x
+ }
+ }
+ }
+
+ public final void limitAdjustMovement(AABB currBoundingBox, double dir, boolean xAdjust, List<VoxelShape> shapes) {
+ int adjustDistance = this.level.sakuraConfig().cannons.restrictions.maxAdjustDistance.or(-1);
+ if (adjustDistance > 0 && Math.abs(dir) > adjustDistance) {
+ double minX = Double.NEGATIVE_INFINITY;
+ double minZ = Double.NEGATIVE_INFINITY;
+ double maxX = Double.POSITIVE_INFINITY;
+ double maxZ = Double.POSITIVE_INFINITY;
+ if (xAdjust) { // limit x adjust
+ minX = Math.floor(currBoundingBox.minX) - adjustDistance;
+ maxX = Math.floor(currBoundingBox.maxX) + adjustDistance + 1;
+ } else { // limit z adjust
+ minZ = Math.floor(currBoundingBox.minZ) - adjustDistance;
+ maxZ = Math.floor(currBoundingBox.maxZ) + adjustDistance + 1;
+ }
+ VoxelShape safeSpace = Shapes.box(
+ minX, Double.NEGATIVE_INFINITY, minZ,
+ maxX, Double.POSITIVE_INFINITY, maxZ
+ );
+ shapes.add(Shapes.join(Shapes.INFINITY, safeSpace, BooleanOp.ONLY_FIRST));
+ }
+ }
+ // Sakura end - configurable left shooting and adjusting limits
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
@@ -1742,6 +1782,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
if (xSmaller && z != 0.0) {
+ this.limitAdjustMovement(currBoundingBox, z, false, voxelList); // Sakura - configurable left shooting and adjusting limits
z = this.scanZ(currBoundingBox, z, voxelList, bbList);
if (z != 0.0) {
currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetZ(currBoundingBox, z);
@@ -1749,6 +1790,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
if (x != 0.0) {
+ // Sakura start - configurable left shooting and adjusting limits
+ if (!xSmaller) {
+ this.limitAdjustMovement(currBoundingBox, x, true, voxelList);
+ }
+ // Sakura end - configurable left shooting and adjusting limits
x = this.scanX(currBoundingBox, x, voxelList, bbList);
if (x != 0.0) {
currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetX(currBoundingBox, x);
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 6a3d0d407dc25715c1408818cd7aba1d962ed382..1a90e7bb8209e20c288afd8c78e681c1fab317cb 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -270,6 +270,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
// Sakura end - physics version api
++this.time;
this.applyGravity();
+ this.limitLeftShooting(); // Sakura - configurable left shooting and adjusting limits
this.moveStripped(MoverType.SELF, this.getDeltaMovement()); // Sakura - optimise cannon entity movement
this.applyEffectsFromBlocks();
// Paper start - Configurable falling blocks height nerf

View File

@@ -0,0 +1,316 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 12 Aug 2024 15:35:57 +0100
Subject: [PATCH] Optimise hopper ticking
diff --git a/src/main/java/net/minecraft/world/CompoundContainer.java b/src/main/java/net/minecraft/world/CompoundContainer.java
index 241fec02e6869c638d3a160819b32173a081467b..dc15a12687ce9e8e354bea9825d9cd1882d00782 100644
--- a/src/main/java/net/minecraft/world/CompoundContainer.java
+++ b/src/main/java/net/minecraft/world/CompoundContainer.java
@@ -58,6 +58,15 @@ public class CompoundContainer implements Container {
return this.container1.getLocation(); // TODO: right?
}
// CraftBukkit end
+ // Sakura start - optimise hopper ticking
+ @Override
+ public final boolean addListener(net.minecraft.world.level.block.entity.BlockEntity.BlockEntityChangeListener listener) {
+ boolean result = false;
+ result |= this.container1.addListener(listener);
+ result |= this.container2.addListener(listener);
+ return result;
+ }
+ // Sakura end - optimise hopper ticking
public CompoundContainer(Container first, Container second) {
this.container1 = first;
diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java
index 5db5ba026462ca642dcee718af732f80fadabef5..51e26395b53628b34b1f7f68935a9ba44a1e3feb 100644
--- a/src/main/java/net/minecraft/world/Container.java
+++ b/src/main/java/net/minecraft/world/Container.java
@@ -17,6 +17,12 @@ public interface Container extends Clearable {
float DEFAULT_DISTANCE_BUFFER = 4.0F;
+ // Sakura start - optimise hopper ticking
+ default boolean addListener(BlockEntity.BlockEntityChangeListener container) {
+ return false;
+ }
+ // Sakura end - optimise hopper ticking
+
int getContainerSize();
boolean isEmpty();
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index f99497c7b10716c1804d29e1e51e608c3a542127..290f3fc3c05892c4de8015fe38cec0c5e5d9fb61 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1649,7 +1649,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
tilesThisCycle--;
toRemove.add(tickingblockentity); // Paper - Fix MC-117075; use removeAll
// Spigot end
- } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
+ } else if (flag && tickingblockentity.isBlockEntityActive() && this.shouldTickBlocksAt(tickingblockentity.getPos())) { // Sakura - optimise hopper ticking
tickingblockentity.tick();
// Paper start - rewrite chunk system
if ((++tickedEntities & 7) == 0) {
diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java
index 8ba23af2fa6c5174aa3ec34e78f9c21ce786c4fc..45070d4506b79890f3cd806789a2fa7ab9d37d27 100644
--- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java
@@ -140,6 +140,12 @@ public class HopperBlock extends BaseEntityBlock {
private void checkPoweredState(Level world, BlockPos pos, BlockState state) {
boolean bl = !world.hasNeighborSignal(pos);
if (bl != state.getValue(ENABLED)) {
+ // Sakura start - optimise hopper ticking
+ BlockEntity blockEntity = world.getBlockEntity(pos);
+ if (blockEntity instanceof HopperBlockEntity hbe && world.sakuraConfig().technical.optimiseIdleHopperTicking) {
+ hbe.setBlockEntityTicking(bl);
+ }
+ // Sakura end - optimise hopper ticking
world.setBlock(pos, state.setValue(ENABLED, Boolean.valueOf(bl)), 2);
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
index 1f664c10138a6e19bdc0051fa80575516d5602e7..1d3b7bf7b06b340f15671db4b910ca8d830f268d 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -48,6 +48,55 @@ public abstract class BlockEntity {
private BlockState blockState;
private DataComponentMap components;
+ // Sakura start - optimise hopper ticking
+ private final Set<BlockEntityChangeListener> listeners = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0);
+ private final java.util.List<BlockEntity> listeningBlocks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(0);
+ private boolean blockEntityTicking = true;
+ private int tickCount = 0;
+
+ public final int getIdleTickCount() {
+ return this.tickCount;
+ }
+
+ public final boolean isBlockEntityActive() {
+ this.tickCount++;
+ return this.blockEntityTicking;
+ }
+
+ public final void setBlockEntityTicking(boolean blockEntityTicking) {
+ this.tickCount = 0;
+ this.blockEntityTicking = blockEntityTicking;
+ }
+
+ public final boolean addListener(BlockEntityChangeListener listener) {
+ if (this.listeners.add(listener)) {
+ ((BlockEntity) listener).listeningBlocks.add(this);
+ }
+ return true;
+ }
+
+ public final void updateListeners(boolean onRemove) {
+ for (BlockEntityChangeListener listener : this.listeners) {
+ if (onRemove) {
+ listener.neighborRemoved();
+ } else {
+ listener.neighborChange();
+ }
+ }
+ if (onRemove) {
+ this.listeningBlocks.forEach(blockEntity -> blockEntity.listeners.clear());
+ this.listeningBlocks.clear();
+ this.listeners.clear();
+ }
+ }
+
+ public interface BlockEntityChangeListener {
+ void neighborChange();
+
+ void neighborRemoved();
+ }
+ // Sakura end - optimise hopper ticking
+
public BlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
this.components = DataComponentMap.EMPTY;
this.type = type;
@@ -217,12 +266,23 @@ public abstract class BlockEntity {
public void setChanged() {
if (this.level != null) {
if (ignoreTileUpdates) return; // Paper - Perf: Optimize Hoppers
- BlockEntity.setChanged(this.level, this.worldPosition, this.blockState);
+ BlockEntity.setChanged(this.level, this.worldPosition, this.blockState, this); // Sakura - optimise hopper ticking
}
}
protected static void setChanged(Level world, BlockPos pos, BlockState state) {
+ // Sakura start - optimise hopper ticking
+ net.minecraft.world.level.chunk.LevelChunk chunk = world.getChunkIfLoaded(pos);
+ BlockEntity blockEntity = chunk != null ? chunk.getBlockEntity(pos) : null;
+ setChanged(world, pos, state, blockEntity);
+ }
+
+ protected static void setChanged(Level world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity) {
+ if (blockEntity != null) {
+ blockEntity.updateListeners(false);
+ }
+ // Sakura end - optimise hopper ticking
world.blockEntityChanged(pos);
if (!state.isAir()) {
world.updateNeighbourForOutputSignal(pos, state.getBlock());
@@ -253,6 +313,7 @@ public abstract class BlockEntity {
public void setRemoved() {
this.remove = true;
+ this.updateListeners(true); // Sakura - optimise hopper ticking
}
public void clearRemoved() {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 5ebbdb94d9b91c442ff60eb6872f740ebd790fa0..ab82d62cc119cbeb981fcccded3dca8fc36eea66 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -42,7 +42,7 @@ import org.bukkit.event.inventory.InventoryPickupItemEvent;
import org.bukkit.inventory.Inventory;
// CraftBukkit end
-public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper {
+public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper, BlockEntity.BlockEntityChangeListener { // Sakura - optimise hopper ticking
public static final int MOVE_ITEM_SPEED = 8;
public static final int HOPPER_CONTAINER_SIZE = 5;
@@ -81,6 +81,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
this.maxStack = size;
}
// CraftBukkit end
+ // Sakura start - optimise hopper ticking
+ private static final int SOURCE_CONTAINER = 1 << 0;
+ private static final int ATTACHED_CONTAINER = 1 << 1;
+ private int connectedContainers = 0;
+
+ @Override
+ public final void neighborChange() {
+ this.startTicking();
+ }
+
+ @Override
+ public final void neighborRemoved() {
+ this.connectedContainers = 0;
+ this.startTicking();
+ }
+
+ private void startTicking() {
+ this.cooldownTime -= this.getIdleTickCount();
+ this.setBlockEntityTicking(true);
+ }
+
+ private void waitForChange(int fullState) {
+ if ((fullState == HOPPER_IS_FULL || (this.connectedContainers & SOURCE_CONTAINER) != 0) && (this.connectedContainers & ATTACHED_CONTAINER) != 0) {
+ this.addListener(this);
+ this.setBlockEntityTicking(false);
+ }
+ }
+
+ private static @Nullable Container sakura_getSourceContainer(Level level, Hopper hopper, BlockPos pos, BlockState state) {
+ Container container = getSourceContainer(level, hopper, pos, state);
+ if (hopper instanceof HopperBlockEntity hbe && HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0) {
+ hbe.listenForContainerChanges(container, SOURCE_CONTAINER);
+ }
+ return container;
+ }
+
+ private static @Nullable Container sakura_getAttachedContainer(Level level, BlockPos pos, HopperBlockEntity hbe) {
+ Container container = getAttachedContainer(level, pos, hbe);
+ if (HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0) {
+ hbe.listenForContainerChanges(container, ATTACHED_CONTAINER);
+ }
+ return container;
+ }
+
+ private void listenForContainerChanges(@Nullable Container container, int type) {
+ if (container != null && container.addListener(this)) {
+ this.connectedContainers |= type; // set
+ } else if ((this.connectedContainers & type) != 0) {
+ this.connectedContainers ^= type; // unset
+ }
+ }
+ // Sakura end - optimise hopper ticking
public HopperBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.HOPPER, pos, state);
@@ -214,6 +266,12 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
setChanged(world, pos, state);
return true;
}
+
+ // Sakura start - optimise hopper ticking
+ if (world.sakuraConfig().technical.optimiseIdleHopperTicking) {
+ blockEntity.waitForChange(fullState);
+ }
+ // Sakura end - optimise hopper ticking
}
return false;
@@ -433,7 +491,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
// Paper end - Perf: Optimize Hoppers
private static boolean ejectItems(Level world, BlockPos pos, HopperBlockEntity blockEntity) {
- Container iinventory = HopperBlockEntity.getAttachedContainer(world, pos, blockEntity);
+ Container iinventory = HopperBlockEntity.sakura_getAttachedContainer(world, pos, blockEntity); // Sakura
if (iinventory == null) {
return false;
@@ -548,7 +606,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public static boolean suckInItems(Level world, Hopper hopper) {
BlockPos blockposition = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ());
BlockState iblockdata = world.getBlockState(blockposition);
- Container iinventory = HopperBlockEntity.getSourceContainer(world, hopper, blockposition, iblockdata);
+ Container iinventory = HopperBlockEntity.sakura_getSourceContainer(world, hopper, blockposition, iblockdata); // Sakura - optimise hopper ticking
if (iinventory != null) {
Direction enumdirection = Direction.DOWN;
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TickingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TickingBlockEntity.java
index 28e3b73507b988f7234cbf29c4024c88180d0aef..a0d247aa883553708c4b92158232425593d50534 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TickingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TickingBlockEntity.java
@@ -10,4 +10,10 @@ public interface TickingBlockEntity {
BlockPos getPos();
String getType();
+
+ // Sakura start - optimise hopper ticking
+ default boolean isBlockEntityActive() {
+ return true;
+ }
+ // Sakura end - optimise hopper ticking
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 97937e3bd211997f0a0a3e9e671a1c59712d0003..ce92f8f2d2f96ec77ae7128b741689ec2b77aad9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -1040,6 +1040,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
return this.ticker.getType();
}
+ // Sakura start - optimise hopper ticking
+ @Override
+ public boolean isBlockEntityActive() {
+ return this.ticker.isBlockEntityActive();
+ }
+ // Sakura end - optimise hopper ticking
+
public String toString() {
return String.valueOf(this.ticker) + " <wrapped>";
}
@@ -1112,6 +1119,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
return BlockEntityType.getKey(this.blockEntity.getType()).toString();
}
+ // Sakura start - optimise hopper ticking
+ @Override
+ public boolean isBlockEntityActive() {
+ return this.blockEntity.isBlockEntityActive();
+ }
+ // Sakura end - optimise hopper ticking
+
public String toString() {
String s = this.getType();

View File

@@ -0,0 +1,107 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 13 Sep 2024 17:22:51 +0100
Subject: [PATCH] Tick entity schedulers only when necessary
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
index 58d9187adc188b693b6becc400f766e069bf1bf5..8b7860390717c3d2a3c0f3d2b081799bcd3d65c1 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
@@ -19,6 +19,22 @@ public final class ServerEntityLookup extends EntityLookup {
private final ServerLevel serverWorld;
public final ReferenceList<Entity> trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
+ // Sakura start - tick entity schedulers only when necessary
+ public final ReferenceList<org.bukkit.craftbukkit.entity.CraftEntity> scheduledEntities = new ReferenceList<>();
+
+ public void entityStartScheduled(final Entity entity) {
+ org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = entity.getBukkitEntityRaw();
+ if (bukkitEntity != null && bukkitEntity.taskScheduler.hasTask()) {
+ this.scheduledEntities.add(bukkitEntity);
+ }
+ }
+
+ public void entityEndScheduled(final Entity entity) {
+ if (entity.getBukkitEntityRaw() != null) {
+ this.scheduledEntities.remove(entity.getBukkitEntityRaw());
+ }
+ }
+ // Sakura end - tick entity schedulers only when necessary
public ServerEntityLookup(final ServerLevel world, final LevelCallback<Entity> worldCallback) {
super(world, worldCallback);
@@ -90,6 +106,7 @@ public final class ServerEntityLookup extends EntityLookup {
// Moonrise start - entity tracker
this.trackerEntities.add(entity);
// Moonrise end - entity tracker
+ this.entityStartScheduled(entity); // Sakura - tick entity schedulers only when necessary
}
@Override
@@ -97,6 +114,7 @@ public final class ServerEntityLookup extends EntityLookup {
// Moonrise start - entity tracker
this.trackerEntities.remove(entity);
// Moonrise end - entity tracker
+ this.entityEndScheduled(entity); // Sakura - tick entity schedulers only when necessary
}
@Override
diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java
index c03608fec96b51e1867f43d8f42e5aefb1520e46..32ac34e6ca4a7443e894369fee349911ebc1cf52 100644
--- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java
+++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java
@@ -50,6 +50,22 @@ public final class EntityScheduler {
this.entity = Validate.notNull(entity);
}
+ // Sakura start - tick entity schedulers only when necessary
+ public boolean hasTask() {
+ return !this.currentlyExecuting.isEmpty() || !this.oneTimeDelayed.isEmpty();
+ }
+
+ private void newScheduledTask() {
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
+ Entity handle = this.entity.getHandleRaw();
+ net.minecraft.server.level.ServerLevel level = (net.minecraft.server.level.ServerLevel) handle.level();
+ ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) level.moonrise$getEntityLookup();
+
+ entityLookup.entityStartScheduled(handle);
+ });
+ }
+ // Sakura end - tick entity schedulers only when necessary
+
/**
* Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback
* on all currently scheduled tasks.
@@ -128,6 +144,7 @@ public final class EntityScheduler {
return new ArrayList<>();
}).add(task);
}
+ this.newScheduledTask(); // Sakura - tick entity schedulers only when necessary
return true;
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 56357e253247d19c108a7f753058fa3eba0302ee..c2c0cd8ae1afd90d9b08adec3cb127b13ce60331 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1824,7 +1824,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper start - Folia scheduler API
((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) Bukkit.getGlobalRegionScheduler()).tick();
getAllLevels().forEach(level -> {
- for (final Entity entity : level.getEntities().getAll()) {
+ // Sakura start - tick entity schedulers only when necessary
+ final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) level.moonrise$getEntityLookup();
+ final Iterator<org.bukkit.craftbukkit.entity.CraftEntity> entityIterator = entityLookup.scheduledEntities.iterator();
+ while (entityIterator.hasNext()) {
+ final org.bukkit.craftbukkit.entity.CraftEntity scheduledEntity = entityIterator.next();
+ final Entity entity = scheduledEntity.getHandle();
+ if (!scheduledEntity.taskScheduler.hasTask()) {
+ entityIterator.remove();
+ continue;
+ }
+ // Sakura end - tick entity schedulers only when necessary
if (entity.isRemoved()) {
continue;
}

View File

@@ -0,0 +1,152 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 8 Nov 2024 19:35:49 +0000
Subject: [PATCH] Optimise check inside blocks and traverse blocks
diff --git a/src/main/java/me/samsuik/sakura/utils/BlockPosIterator.java b/src/main/java/me/samsuik/sakura/utils/BlockPosIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..e00c07c614e007c007076e3dbe8bd8ccf6c6572d
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/utils/BlockPosIterator.java
@@ -0,0 +1,60 @@
+package me.samsuik.sakura.utils;
+
+import com.google.common.collect.AbstractIterator;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.BlockPos.MutableBlockPos;
+import net.minecraft.util.Mth;
+import net.minecraft.world.phys.AABB;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
+public final class BlockPosIterator extends AbstractIterator<BlockPos> {
+ private final int startX;
+ private final int startY;
+ private final int startZ;
+ private final int endX;
+ private final int endY;
+ private final int endZ;
+ private @Nullable MutableBlockPos pos = null;
+
+ public static Iterable<BlockPos> iterable(AABB bb) {
+ return () -> new BlockPosIterator(bb);
+ }
+
+ public BlockPosIterator(AABB bb) {
+ this.startX = Mth.floor(bb.minX);
+ this.startY = Mth.floor(bb.minY);
+ this.startZ = Mth.floor(bb.minZ);
+ this.endX = Mth.floor(bb.maxX);
+ this.endY = Mth.floor(bb.maxY);
+ this.endZ = Mth.floor(bb.maxZ);
+ }
+
+ @Override
+ protected BlockPos computeNext() {
+ MutableBlockPos pos = this.pos;
+ if (pos == null) {
+ return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ);
+ } else {
+ int x = pos.getX();
+ int y = pos.getY();
+ int z = pos.getZ();
+
+ if (y < this.endY) {
+ y += 1;
+ } else if (x < this.endX) {
+ x += 1;
+ y = this.startY;
+ } else if (z < this.endZ) {
+ z += 1;
+ x = this.startX;
+ } else {
+ return this.endOfData();
+ }
+
+ pos.set(x, y, z);
+ return pos;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 95f4854ca21a6b34ad1ab95a1cb38f7d2dfd24c3..7d35952cdf48150f43b103b765358b7760411c9d 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -2061,6 +2061,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
final Iterator iterator1 = positions.iterator();
// Sakura end - physics version api
+ // Sakura start - optimise check inside blocks
+ int lastChunkX = Integer.MIN_VALUE;
+ int lastChunkZ = Integer.MIN_VALUE;
+ net.minecraft.world.level.chunk.ChunkAccess chunk = null;
+ // Sakura end - optimise check inside blocks
while (iterator1.hasNext()) {
BlockPos blockposition = (BlockPos) iterator1.next();
@@ -2069,7 +2074,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return;
}
- BlockState iblockdata = this.level().getBlockState(blockposition);
+ // Sakura start - optimise check inside blocks
+ int chunkX = blockposition.getX() >> 4;
+ int chunkZ = blockposition.getZ() >> 4;
+ if (chunk == null || chunkX != lastChunkX || chunkZ != lastChunkZ) {
+ chunk = this.level.getChunkIfLoadedImmediately(chunkX, chunkZ);
+ if (chunk == null) {
+ continue;
+ }
+ lastChunkX = chunkX;
+ lastChunkZ = chunkZ;
+ }
+ BlockState iblockdata = chunk.getBlockState(blockposition);
+ // Sakura end - optimise check inside blocks
if (!iblockdata.isAir() && longset.add(blockposition.asLong())) {
try {
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
index 62719778c62d6632a8eb6f2bc670956c15a247de..ecdb8769c1a388827bf5c4565c82f8e32672c5d7 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -215,11 +215,18 @@ public interface BlockGetter extends LevelHeightAccessor {
static Iterable<BlockPos> boxTraverseBlocks(Vec3 oldPos, Vec3 newPos, AABB boundingBox) {
Vec3 vec3d2 = newPos.subtract(oldPos);
- Iterable<BlockPos> iterable = BlockPos.betweenClosed(boundingBox);
-
+ // Sakura start - optimise check inside blocks
if (vec3d2.lengthSqr() < (double) Mth.square(0.99999F)) {
- return iterable;
+ return me.samsuik.sakura.utils.BlockPosIterator.iterable(boundingBox);
} else {
+ boolean xZero = vec3d2.x() == 0.0;
+ boolean yZero = vec3d2.y() == 0.0;
+ boolean zZero = vec3d2.z() == 0.0;
+ if (xZero && yZero || yZero && zZero || xZero && zZero) {
+ return traverseAreaFast(vec3d2, boundingBox);
+ }
+ Iterable<BlockPos> iterable = BlockPos.betweenClosed(boundingBox);
+ // Sakura end - optimise check inside blocks
Set<BlockPos> set = new ObjectLinkedOpenHashSet();
Vec3 vec3d3 = boundingBox.getMinPosition();
Vec3 vec3d4 = vec3d3.subtract(vec3d2);
@@ -237,6 +244,16 @@ public interface BlockGetter extends LevelHeightAccessor {
}
}
+ // Sakura start - optimise check inside blocks
+ private static Iterable<BlockPos> traverseAreaFast(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);
+ }
+ // Sakura end - optimise check inside blocks
+
private static void addCollisionsAlongTravel(Set<BlockPos> result, Vec3 oldPos, Vec3 newPos, AABB boundingBox) {
Vec3 vec3d2 = newPos.subtract(oldPos);
int i = Mth.floor(oldPos.x);

View File

View File

@@ -0,0 +1,251 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Tue, 23 May 2023 23:07:20 +0100
Subject: [PATCH] Sakura Utils
diff --git a/src/main/java/me/samsuik/sakura/utils/collections/FixedSizeCustomObjectTable.java b/src/main/java/me/samsuik/sakura/utils/collections/FixedSizeCustomObjectTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..e97f3cc00945f79026af894685d6104dfc920a35
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/utils/collections/FixedSizeCustomObjectTable.java
@@ -0,0 +1,54 @@
+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;
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java b/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java
new file mode 100644
index 0000000000000000000000000000000000000000..edbbf9dea7c6659c2053407dc55b5a2a1cfcb0dd
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java
@@ -0,0 +1,49 @@
+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);
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java b/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..d112b559eaff82c32e943edf34c616565cb1e189
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java
@@ -0,0 +1,32 @@
+package me.samsuik.sakura.utils.collections;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectCollection;
+import net.minecraft.server.level.ChunkMap;
+
+public final class TrackedEntityChunkMap extends Int2ObjectOpenHashMap<ChunkMap.TrackedEntity> {
+ private final ObjectArrayList<ChunkMap.TrackedEntity> entityList = new UnorderedIndexedList<>();
+
+ @Override
+ public ChunkMap.TrackedEntity put(int k, ChunkMap.TrackedEntity trackedEntity) {
+ ChunkMap.TrackedEntity tracked = super.put(k, trackedEntity);
+ // bad plugins may replace entries
+ if (tracked != null)
+ this.entityList.remove(trackedEntity);
+ this.entityList.add(trackedEntity);
+ return tracked;
+ }
+
+ @Override
+ public ChunkMap.TrackedEntity remove(int k) {
+ ChunkMap.TrackedEntity tracked = super.remove(k);
+ this.entityList.remove(tracked);
+ return tracked;
+ }
+
+ @Override
+ public ObjectCollection<ChunkMap.TrackedEntity> values() {
+ return this.entityList;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java b/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java
new file mode 100644
index 0000000000000000000000000000000000000000..131dc69cdd2c121975199324022f942194d345c8
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java
@@ -0,0 +1,61 @@
+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();
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/utils/objects/Expiry.java b/src/main/java/me/samsuik/sakura/utils/objects/Expiry.java
new file mode 100644
index 0000000000000000000000000000000000000000..97a54930416951645e35f7bd7ec11325d1ba4d53
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/utils/objects/Expiry.java
@@ -0,0 +1,19 @@
+package me.samsuik.sakura.utils.objects;
+
+public final class Expiry {
+ private int expireAt;
+ private final int inc;
+
+ public Expiry(int tick, int inc) {
+ this.expireAt = tick + inc;
+ this.inc = inc;
+ }
+
+ public void refresh(int tick) {
+ this.expireAt = tick + this.inc;
+ }
+
+ public boolean isExpired(int tick) {
+ return tick >= this.expireAt;
+ }
+}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,263 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Tue, 21 Nov 2023 23:24:24 +0000
Subject: [PATCH] Local Config and Value Storage API
diff --git a/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java b/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b77b942b1733380c566683ba0516a74ee5c6389
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java
@@ -0,0 +1,141 @@
+package me.samsuik.sakura.local.config;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import me.samsuik.sakura.local.LocalRegion;
+import me.samsuik.sakura.local.storage.LocalStorageHandler;
+import me.samsuik.sakura.local.storage.LocalValueStorage;
+import me.samsuik.sakura.utils.objects.Expiry;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import org.bukkit.Location;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public final class LocalConfigManager implements LocalStorageHandler {
+ private final Map<LocalRegion, LocalValueStorage> storageMap = new Object2ObjectOpenHashMap<>();
+ // tree is a tree. it may not be correct but it works.
+ private final Long2ObjectRBTreeMap<LocalRegion> regionTree = new Long2ObjectRBTreeMap<>();
+ private final Long2ObjectMap<LocalValueConfig> chunkConfigs = new Long2ObjectOpenHashMap<>();
+ private final Level level;
+
+ public LocalConfigManager(Level level) {
+ this.level = level;
+ }
+
+ @Override
+ public LocalRegion locate(Location location, int searchDistance) {
+ return this.locate(location.getBlockX(), location.getBlockZ(), searchDistance);
+ }
+
+ @Override
+ public synchronized LocalRegion locate(int x, int z, int searchDistance) {
+ long search = (long) searchDistance << 32;
+ long coordinate = ChunkPos.asLong(x, z);
+
+ // all regions below the coordinate (they're stored with the minX, minZ)
+ Long2ObjectSortedMap<LocalRegion> head = this.regionTree.headMap(coordinate);
+
+ if (head.isEmpty()) return null;
+
+ for (Long2ObjectMap.Entry<LocalRegion> entry : head.long2ObjectEntrySet()) {
+ // this has no respect for the x coordinate, but it works
+ if (coordinate - entry.getLongKey() > search) {
+ break;
+ }
+
+ // check if the region we found contains the position
+ if (entry.getValue().contains(x, z)) {
+ return entry.getValue();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public synchronized LocalValueStorage get(LocalRegion region) {
+ return this.storageMap.get(region);
+ }
+
+ @Override
+ public synchronized boolean has(LocalRegion region) {
+ return this.storageMap.containsKey(region);
+ }
+
+ @Override
+ public synchronized void put(LocalRegion region, LocalValueStorage storage) {
+ assert region != null : "region cannot be null";
+ assert storage != null : "storage cannot be null";
+
+ long base = ChunkPos.asLong(region.minX(), region.minZ());
+ this.ensureNotOverlapping(region);
+
+ this.storageMap.put(region, storage);
+ this.regionTree.put(base, region);
+ }
+
+ @Override
+ public synchronized void remove(LocalRegion region) {
+ assert region != null : "region cannot be null";
+
+ long base = ChunkPos.asLong(region.minX(), region.minZ());
+
+ this.storageMap.remove(region);
+ this.regionTree.remove(base);
+ }
+
+ @Override
+ public synchronized List<LocalRegion> regions() {
+ return new ArrayList<>(this.storageMap.keySet());
+ }
+
+ public synchronized LocalValueConfig config(BlockPos position) {
+ long chunkKey = ChunkPos.asLong(position.getX() >> 4, position.getZ() >> 4);
+
+ LocalValueConfig local = this.chunkConfigs.computeIfAbsent(chunkKey, key -> {
+ LocalValueConfig config = new LocalValueConfig(new Expiry(MinecraftServer.currentTick, 600));
+
+ // defaults from sakura config
+ config.loadDefaults(this.level);
+
+ // search the entire map if we have to for a region
+ LocalRegion region = this.locate(position.getX(), position.getZ(), Integer.MAX_VALUE);
+
+ if (region != null) {
+ // load local values
+ config.loadFromStorage(this.storageMap.get(region));
+ }
+
+ return config;
+ });
+
+ local.expiry().refresh(MinecraftServer.currentTick);
+ return local;
+ }
+
+ public synchronized void expire(int tick) {
+ if (tick % 200 != 0) return;
+
+ // remove expired
+ this.chunkConfigs.values().removeIf(obj -> obj.expiry().isExpired(tick));
+ }
+
+ private void ensureNotOverlapping(LocalRegion region) {
+ long top = ChunkPos.asLong(region.maxX(), region.maxZ());
+ Long2ObjectSortedMap<LocalRegion> head = this.regionTree.headMap(top);
+
+ for (LocalRegion present : head.values()) {
+ if (present != region && present.intersects(region)) {
+ throw new UnsupportedOperationException("overlapping regions");
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java b/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..53d2c0f3d3c2d857a44c7a993cbd91ea5ce4b099
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java
@@ -0,0 +1,57 @@
+package me.samsuik.sakura.local.config;
+
+import io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import me.samsuik.sakura.explosion.durable.DurableMaterial;
+import me.samsuik.sakura.local.LocalValueKeys;
+import me.samsuik.sakura.local.storage.LocalValueStorage;
+import me.samsuik.sakura.physics.PhysicsVersion;
+import me.samsuik.sakura.utils.objects.Expiry;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+
+import java.util.Map;
+
+public final class LocalValueConfig {
+ private final Expiry expiry;
+ public Map<Block, DurableMaterial> durableMaterials;
+ public RedstoneImplementation redstoneImplementation;
+ public PhysicsVersion physicsVersion;
+ public boolean consistentRadius;
+ public boolean redstoneCache;
+ public int lavaFlowSpeed = -1;
+
+ LocalValueConfig(Expiry expiry) {
+ this.expiry = expiry;
+ }
+
+ void loadDefaults(Level level) {
+ this.durableMaterials = new Reference2ObjectOpenHashMap<>(level.sakuraConfig().cannons.explosion.durableMaterials);
+ this.redstoneImplementation = level.paperConfig().misc.redstoneImplementation;
+ this.physicsVersion = level.sakuraConfig().cannons.mechanics.physicsVersion;
+ this.consistentRadius = level.sakuraConfig().cannons.explosion.consistentRadius;
+ this.redstoneCache = level.sakuraConfig().technical.redstone.redstoneCache;
+ }
+
+ void loadFromStorage(LocalValueStorage storage) {
+ storage.get(LocalValueKeys.DURABLE_MATERIALS).ifPresent(materials -> {
+ materials.forEach((materialType, materialProperties) -> {
+ Block nmsBlock = CraftMagicNumbers.getBlock(materialType);
+ DurableMaterial durableMaterial = new DurableMaterial(materialProperties.getKey(), materialProperties.getValue());
+ this.durableMaterials.put(nmsBlock, durableMaterial);
+ });
+ });
+ storage.get(LocalValueKeys.REDSTONE_IMPLEMENTATION).ifPresent(implementation -> {
+ this.redstoneImplementation = RedstoneImplementation.values()[implementation.ordinal()];
+ });
+ this.physicsVersion = storage.getOrDefault(LocalValueKeys.PHYSICS_VERSION, this.physicsVersion);
+ this.consistentRadius = storage.getOrDefault(LocalValueKeys.CONSISTENT_EXPLOSION_RADIUS, this.consistentRadius);
+ this.redstoneCache = storage.getOrDefault(LocalValueKeys.REDSTONE_CACHE, this.redstoneCache);
+ this.lavaFlowSpeed = storage.getOrDefault(LocalValueKeys.LAVA_FLOW_SPEED, this.lavaFlowSpeed);
+ }
+
+ Expiry expiry() {
+ return this.expiry;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index a39b00ebb37a0bf041bc7b9861fb7f9ebd962c40..1f9f023a43c16cc472deea5285b01c19136e019f 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1890,6 +1890,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
gameprofilerfiller.pop();
gameprofilerfiller.pop();
worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
+ worldserver.localConfig().expire(currentTick); // Sakura - add local config
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 34e7ae8b36e375280f9515c1580c55c0832fc194..13e8ac95daa115720a2b1f52ad33d124351cbd6d 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -179,6 +179,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return this.sakuraConfig;
}
// Sakura end - sakura configuration files
+ // Sakura start - add local config
+ private final me.samsuik.sakura.local.config.LocalConfigManager localConfig = new me.samsuik.sakura.local.config.LocalConfigManager(this);
+ public final me.samsuik.sakura.local.config.LocalConfigManager localConfig() {
+ return this.localConfig;
+ }
+ // Sakura end - add local config
public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
public static BlockPos lastPhysicsProblem; // Spigot
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 92d9f0ea8f7810ae20d3996f49aefa539b4bcb69..8fc4b65887351a4dc35eaa837dea4dc9513514a8 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -289,6 +289,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
// Paper end
+ // Sakura start - add local config
+ @Override
+ public final me.samsuik.sakura.local.storage.LocalStorageHandler getStorageHandler() {
+ return this.getHandle().localConfig();
+ }
+ // Sakura end - add local config
+
private static final Random rand = new Random();
public CraftWorld(ServerLevel world, ChunkGenerator gen, BiomeProvider biomeProvider, Environment env) {

View File

@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 11 Sep 2021 22:23:39 +0100
Subject: [PATCH] Optional Force Position Updates
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 5bd189fad703ac99d57258fa448dbcc103077297..eff8a8353b513da72e18f993fd41494291bdff3a 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -136,6 +136,14 @@ public class PrimedTnt extends Entity implements TraceableEntity {
}
}
+ // Sakura start - configure force position updates
+ if (this.level().sakuraConfig().cannons.tnt.forcePositionUpdates) {
+ this.forcePositionUpdate();
+ }
+ }
+
+ private void forcePositionUpdate() {
+ // Sakura end - configure force position updates
// Paper start - Option to prevent TNT from moving in water
if (!this.isRemoved() && this.wasTouchingWater && this.level().paperConfig().fixes.preventTntFromMovingInWater) {
/*

View File

@@ -0,0 +1,656 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sun, 19 Sep 2021 01:10:02 +0100
Subject: [PATCH] Track Tick Information
Keeps track of useful information every tick such as the tick rate,
entities, chunks and displays them with the tps command.
diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
index cbb2e57f9ab3b48a6e5f792711c4c6fd2d34d445..8e93fc5d7e1bc200f79b0e54edb62dc4d0bf5e74 100644
--- a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
+++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
@@ -4,6 +4,7 @@ import me.samsuik.sakura.command.subcommands.ConfigCommand;
import me.samsuik.sakura.command.subcommands.FPSCommand;
import me.samsuik.sakura.command.subcommands.VisualCommand;
import me.samsuik.sakura.player.visibility.VisibilityTypes;
+import me.samsuik.sakura.command.subcommands.TPSCommand;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command;
@@ -18,6 +19,7 @@ public final class SakuraCommands {
COMMANDS.put("fps", new FPSCommand("fps"));
COMMANDS.put("tntvisibility", new VisualCommand(VisibilityTypes.TNT, "tnttoggle"));
COMMANDS.put("sandvisibility", new VisualCommand(VisibilityTypes.SAND, "sandtoggle"));
+ COMMANDS.put("tps", new TPSCommand("tps"));
}
public static void registerCommands(MinecraftServer server) {
diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..db6d03c1ed1c1d0390826dd3f96e774e2bea8a1a
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java
@@ -0,0 +1,92 @@
+package me.samsuik.sakura.command.subcommands;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import me.samsuik.sakura.command.BaseSubCommand;
+import me.samsuik.sakura.tps.ServerTickInformation;
+import me.samsuik.sakura.tps.graph.BuiltComponentCanvas;
+import me.samsuik.sakura.tps.graph.DetailedTPSGraph;
+import me.samsuik.sakura.tps.graph.GraphComponents;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.Style;
+import net.kyori.adventure.text.format.TextDecoration;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.CommandSender;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public final class TPSCommand extends BaseSubCommand {
+ private static final int GRAPH_WIDTH = 71;
+ private static final int GRAPH_HEIGHT = 10;
+ private static final Style GRAY_WITH_STRIKETHROUGH = Style.style(NamedTextColor.GRAY, TextDecoration.STRIKETHROUGH);
+
+ public TPSCommand(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());
+ double scale = this.parseDouble(args, 0).orElse(-1.0);
+ if (scale < 0.0) {
+ 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();
+ canvas.appendLeft(Component.text(":", NamedTextColor.BLACK));
+ canvas.appendRight(Component.text(":", NamedTextColor.BLACK));
+ canvas.header(this.createHeaderComponent(tickInformation, identifier));
+ canvas.footer(Component.text("*", NamedTextColor.DARK_GRAY)
+ .append(Component.text(Strings.repeat(" ", GRAPH_WIDTH - 1), GRAY_WITH_STRIKETHROUGH))
+ .append(Component.text("*")));
+
+ for (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()
+ .mapToDouble(ServerTickInformation::tps)
+ .average()
+ .orElse(0.0);
+ return 20 / averageTps;
+ }
+
+ private Component createHeaderComponent(ServerTickInformation tickInformation, long identifier) {
+ int scrollAmount = GRAPH_WIDTH / 3 * 2;
+ double memoryUsage = memoryUsage();
+ TextComponent.Builder builder = Component.text();
+ builder.color(NamedTextColor.DARK_GRAY);
+ builder.append(Component.text("< ")
+ .clickEvent(ClickEvent.runCommand("/tps -1 " + (identifier + scrollAmount))));
+ builder.append(Component.text(Strings.repeat(" ", 19), GRAY_WITH_STRIKETHROUGH));
+ builder.append(Component.text(" ( "));
+ builder.append(Component.text("Now: ", NamedTextColor.WHITE)
+ .append(Component.text("%.1f".formatted(tickInformation.tps()), tickInformation.colour())));
+ builder.appendSpace();
+ builder.append(Component.text("Mem: ", NamedTextColor.WHITE)
+ .append(Component.text("%.1f".formatted(memoryUsage * 100), GraphComponents.colour(1 - (float) memoryUsage))));
+ builder.append(Component.text("% ) "));
+ builder.append(Component.text(Strings.repeat(" ", 18), GRAY_WITH_STRIKETHROUGH));
+ builder.append(Component.text(" >")
+ .clickEvent(ClickEvent.runCommand("/tps -1 " + (identifier - scrollAmount))));
+ return builder.build();
+ }
+
+ private static double memoryUsage() {
+ Runtime runtime = Runtime.getRuntime();
+ double free = runtime.freeMemory();
+ double max = runtime.maxMemory();
+ double alloc = runtime.totalMemory();
+ return (alloc - free) / max;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java b/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a65a3dac75834a2fe10d2c6d6ae594ceb208fef
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java
@@ -0,0 +1,37 @@
+package me.samsuik.sakura.tps;
+
+import me.samsuik.sakura.configuration.GlobalConfiguration;
+import me.samsuik.sakura.tps.graph.GraphComponents;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.format.TextColor;
+import net.minecraft.server.MinecraftServer;
+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 TextColor colour() {
+ float lag = (float) this.tps / this.targetTickRate;
+ return GraphComponents.colour(lag);
+ }
+
+ public Component hoverComponent(TextColor colour) {
+ TextComponent.Builder builder = Component.text();
+ builder.append(Component.text("TPS: ")
+ .append(Component.text("%.1f".formatted(this.tps), colour)));
+ builder.appendNewline();
+ builder.append(Component.text("MSPT: ")
+ .append(Component.text("%.1f".formatted(this.averageTick), colour))
+ .append(Component.text("/"))
+ .append(Component.text(this.longestTick, colour)));
+ if (GlobalConfiguration.get().messages.tpsShowEntityAndChunkCount) {
+ builder.appendNewline();
+ builder.append(Component.text("Entities: " + this.entities));
+ builder.appendNewline();
+ builder.append(Component.text("Chunks: " + this.chunks));
+ }
+ return builder.build();
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/TickInformationCollector.java b/src/main/java/me/samsuik/sakura/tps/TickInformationCollector.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f88e17db0cf8eca161d98cc8cdac8383903e694
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/TickInformationCollector.java
@@ -0,0 +1,71 @@
+package me.samsuik.sakura.tps;
+
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.Collection;
+import java.util.List;
+
+@NullMarked
+public final class TickInformationCollector {
+ private static final int TEN_MINUTES = 10 * 60;
+ private final ObjectArrayList<ServerTickInformation> collectedInformation = new ObjectArrayList<>();
+ private final LongArrayList tickSamples = new LongArrayList();
+ private long identifier = 0;
+
+ public ServerTickInformation latestTickInformation() {
+ return this.collectedInformation.getLast();
+ }
+
+ public void levelData(Collection<ServerLevel> levels, double tps) {
+ int chunks = 0;
+ int entities = 0;
+ for (ServerLevel level : levels) {
+ chunks += level.chunkSource.getFullChunksCount();
+ entities += level.entityTickList.entities.size();
+ }
+
+ double averageTick = this.tickSamples.longStream()
+ .average()
+ .orElse(0.0);
+ long longestTick = this.tickSamples.longStream()
+ .max()
+ .orElse(0);
+ float targetTickRate = MinecraftServer.getServer().tickRateManager().tickrate();
+
+ ServerTickInformation tickInformation = new ServerTickInformation(
+ this.identifier++, tps, averageTick, longestTick, targetTickRate, chunks, entities
+ );
+
+ this.collectedInformation.add(tickInformation);
+ this.tickSamples.clear();
+
+ if (this.collectedInformation.size() > TEN_MINUTES) {
+ this.collectedInformation.subList(0, 60).clear();
+ }
+ }
+
+ public void tickDuration(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()) {
+ 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);
+ }
+ return ImmutableList.copyOf(collected);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java b/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf3e953c99825274b800d19c74019c904620ec74
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java
@@ -0,0 +1,36 @@
+package me.samsuik.sakura.tps.graph;
+
+import com.google.common.collect.ImmutableList;
+import net.kyori.adventure.text.Component;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.List;
+
+@NullMarked
+public final class BuiltComponentCanvas {
+ private final List<Component> components;
+
+ BuiltComponentCanvas(List<Component> components) {
+ this.components = components;
+ }
+
+ public void appendLeft(Component component) {
+ this.components.replaceAll(component::append);
+ }
+
+ public void appendRight(Component component) {
+ this.components.replaceAll(row -> row.append(component));
+ }
+
+ public void header(Component component) {
+ this.components.addFirst(component);
+ }
+
+ public void footer(Component component) {
+ this.components.add(component);
+ }
+
+ public ImmutableList<Component> components() {
+ return ImmutableList.copyOf(this.components);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java b/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java
new file mode 100644
index 0000000000000000000000000000000000000000..42024655f1b1cad12ba1db66086bb978c5ee8f02
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java
@@ -0,0 +1,63 @@
+package me.samsuik.sakura.tps.graph;
+
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.JoinConfiguration;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.List;
+
+@NullMarked
+public final class ComponentCanvas {
+ private final int width;
+ private final int height;
+ private final Component[][] components;
+
+ public ComponentCanvas(int width, int height) {
+ this.width = width;
+ this.height = height;
+ // [x, y] is flipped as it makes converting the components into a list easier
+ this.components = new Component[height][width];
+ }
+
+ public void flip() {
+ for (int y = 0; y < this.height; ++y) {
+ if (y >= this.height / 2) {
+ Component[] row = this.components[y];
+ int relocatingRow = this.height - 1 - y;
+ this.components[y] = this.components[relocatingRow];
+ this.components[relocatingRow] = row;
+ }
+ }
+ }
+
+ public void fill(Component component) {
+ for (int x = 0; x < this.width; ++x) {
+ for (int y = 0; y < this.height; ++y) {
+ this.set(x, y, component);
+ }
+ }
+ }
+
+ public Component get(int x, int y) {
+ 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) {
+ this.components[y][x] = component;
+ }
+
+ public BuiltComponentCanvas build() {
+ return new BuiltComponentCanvas(this.joinComponents());
+ }
+
+ private List<Component> joinComponents() {
+ List<Component> componentList = new ObjectArrayList<>(this.height);
+ for (Component[] row : this.components) {
+ componentList.add(Component.join(JoinConfiguration.noSeparators(), row));
+ }
+ return componentList;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/graph/DetailedTPSGraph.java b/src/main/java/me/samsuik/sakura/tps/graph/DetailedTPSGraph.java
new file mode 100644
index 0000000000000000000000000000000000000000..bebc61d33a5cbb6a46c7f5ac0a9b453e0ab48655
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/graph/DetailedTPSGraph.java
@@ -0,0 +1,102 @@
+package me.samsuik.sakura.tps.graph;
+
+import me.samsuik.sakura.tps.ServerTickInformation;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.List;
+
+@NullMarked
+public final class DetailedTPSGraph extends TPSGraph {
+ public DetailedTPSGraph(int width, int height, double scale, List<ServerTickInformation> tickInformation) {
+ super(width, height, scale, tickInformation);
+ }
+
+ @Override
+ public BuiltComponentCanvas plot() {
+ ComponentCanvas canvas = new ComponentCanvas(this.width, this.height);
+ canvas.fill(GraphComponents.BACKGROUND);
+
+ this.basicOutline(canvas);
+ this.prettifyOutline(canvas);
+ this.addColourAndHoverInformation(canvas);
+
+ canvas.flip();
+ return canvas.build();
+ }
+
+ private void basicOutline(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);
+
+ if (maxRow - minRow >= 2) {
+ canvas.set(x, minRow, GraphComponents.TOP_DOTTED_LINE);
+ canvas.set(x, maxRow, GraphComponents.BOTTOM_DOTTED_LINE);
+
+ for (int y = minRow + 1; y < maxRow; ++y) {
+ canvas.set(x, y, GraphComponents.VERTICAL_LINE);
+ }
+ } else {
+ canvas.set(x, row, GraphComponents.HORIZONTAL_LINE);
+ }
+ }
+ }
+
+ private void prettifyOutline(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);
+
+ if (maxRow - minRow >= 2) {
+ this.prettifyVerticalOutline(canvas, x, row, nextRow, prevRow, minRow, maxRow);
+ } else {
+ this.prettifySlopes(canvas, x, row, nextRow, prevRow);
+ }
+ }
+ }
+
+ private void prettifyVerticalOutline(ComponentCanvas canvas, int x, int row, int nextRow, int prevRow, int minRow, int maxRow) {
+ if (minRow == nextRow) {
+ canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_LEFT);
+ } else if (prevRow <= minRow) {
+ canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_RIGHT);
+ }
+ if (prevRow == row + 1 && nextRow < row) {
+ canvas.set(x, maxRow, GraphComponents.CONE_TOP_RIGHT);
+ }
+ if (maxRow == row && Math.abs(nextRow - maxRow) > 1 && Math.abs(prevRow - maxRow) > 1 && prevRow < maxRow) {
+ canvas.set(x - 1, maxRow, GraphComponents.CONE_TOP_LEFT);
+ canvas.set(x, maxRow, GraphComponents.CONE_TOP_RIGHT);
+ }
+ if (minRow == row && Math.abs(nextRow - minRow) > 1 && Math.abs(prevRow - minRow) > 1 && prevRow > minRow) {
+ canvas.set(x - 1, minRow, GraphComponents.CONE_BOTTOM_LEFT);
+ canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_RIGHT);
+ }
+ }
+
+ private void prettifySlopes(ComponentCanvas canvas, int x, int row, int nextRow, int prevRow) {
+ int slopeDirection = nextRow - prevRow;
+ 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);
+ } else if (Math.abs(row - nextRow) == 1 || slopeDirection == 0) {
+ if (row < nextRow) {
+ canvas.set(x, row, GraphComponents.TOP_DOTTED_LINE);
+ } else if (row > nextRow) {
+ canvas.set(x, row, GraphComponents.BOTTOM_DOTTED_LINE);
+ }
+ } else if (Math.abs(row - prevRow) == 1) {
+ if (prevRow > row) {
+ canvas.set(x, row, GraphComponents.TOP_DOTTED_LINE);
+ } else if (prevRow < row) {
+ canvas.set(x, row, GraphComponents.BOTTOM_DOTTED_LINE);
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java b/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java
new file mode 100644
index 0000000000000000000000000000000000000000..95d3c8031e4708d2d0a0a12213060acd9ab65522
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java
@@ -0,0 +1,42 @@
+package me.samsuik.sakura.tps.graph;
+
+import net.kyori.adventure.text.Component;
+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 java.util.List;
+
+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));
+
+ public static final Component BACKGROUND = Component.text("::");
+ public static final Component HORIZONTAL_LINE = Component.text(" ", STRIKE_THROUGH_STYLE);
+ public static final Component VERTICAL_LINE = Component.text("||");
+ public static final Component TOP_DOTTED_LINE = Component.text("''");
+ public static final Component BOTTOM_DOTTED_LINE = Component.text("..");
+ public static final Component BL_TO_TR = Component.text(".", STRIKE_THROUGH_STYLE).append(Component.text("'", REMOVE_STRIKE_THROUGH_STYLE));
+ public static final Component TL_TO_BR = Component.text("'").append(Component.text(".", STRIKE_THROUGH_STYLE));
+ public static final Component CONE_TOP_LEFT = Component.text(".!");
+ public static final Component CONE_TOP_RIGHT = Component.text("!.");
+ public static final Component CONE_BOTTOM_LEFT = Component.text("'!");
+ public static final Component CONE_BOTTOM_RIGHT = Component.text("!'");
+
+ private static final List<TextColor> COLOURS = List.of(
+ NamedTextColor.GREEN, NamedTextColor.YELLOW, NamedTextColor.GOLD,
+ 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);
+ return TextColor.lerp(t, startColour, endColour);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java b/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java
new file mode 100644
index 0000000000000000000000000000000000000000..27b6b071ad38589d37e35ea7fdf1d45924079f49
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java
@@ -0,0 +1,60 @@
+package me.samsuik.sakura.tps.graph;
+
+import com.google.common.base.Preconditions;
+import me.samsuik.sakura.tps.ServerTickInformation;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
+import net.minecraft.util.Mth;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.List;
+
+@NullMarked
+public abstract class TPSGraph {
+ protected final List<ServerTickInformation> tickInformation;
+ protected final int width;
+ protected final int height;
+ protected final double scale;
+
+ public TPSGraph(int width, int height, double scale, List<ServerTickInformation> tickInformation) {
+ Preconditions.checkArgument(tickInformation.size() == width);
+ this.width = width;
+ this.height = height;
+ this.scale = scale;
+ this.tickInformation = tickInformation;
+ }
+
+ 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);
+ return this.rowFromTPS(tickInformation.tps());
+ }
+
+ protected final int rowFromTPS(double tps) {
+ int row = Mth.floor((tps / 3) * this.scale);
+ return Mth.clamp(row, 0, this.height - 1);
+ }
+
+ protected final void addColourAndHoverInformation(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);
+
+ for (int y = 0; y < this.height; ++y) {
+ Component component = canvas.get(x, y);
+ if (component == GraphComponents.BACKGROUND) {
+ component = component.color(NamedTextColor.BLACK);
+ } else {
+ component = component.color(colourFromTPS);
+ }
+ canvas.set(x, y, component.hoverEvent(hoverEvent));
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 1d0462b970304f543ff949f7aa32c67a1860ba4f..733541e91b0064d50de6c5c0985e9c472685c20c 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -427,6 +427,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
// Paper end - rewrite chunk system
+ // Sakura start - track tick information
+ private final me.samsuik.sakura.tps.TickInformationCollector tickInformationCollector = new me.samsuik.sakura.tps.TickInformationCollector();
+
+ public final me.samsuik.sakura.tps.ServerTickInformation latestTickInformation() {
+ return this.tickInformationCollector.latestTickInformation();
+ }
+
+ public final ImmutableList<me.samsuik.sakura.tps.ServerTickInformation> tickHistory(long from, long to) {
+ return this.tickInformationCollector.collect(from, to);
+ }
+ // Sakura end - track tick information
public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
super("Server");
@@ -1301,6 +1312,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
final long diff = currentTime - tickSection;
final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
+ this.tickInformationCollector.levelData(this.levels.values(), currentTps.doubleValue()); // Sakura - track tick information
tps1.add(currentTps, diff);
tps5.add(currentTps, diff);
tps15.add(currentTps, diff);
@@ -1343,6 +1355,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
throw new RuntimeException("Chunk system crash propagated to tick()", crash);
}
// Paper end - rewrite chunk system
+ this.tickInformationCollector.tickDuration((System.nanoTime() - currentTime) / 1_000_000L); // Sakura - track tick information
this.tickFrame.end();
gameprofilerfiller.popPush("nextTickWait");
this.mayHaveDelayedTasks = true;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 0c1587b8ccf05e7a0a5df5a64c483cee434e1c0f..e1f0da0b20ec310470160718338920bbd5b088de 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -201,7 +201,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private final MinecraftServer server;
public final PrimaryLevelData serverLevelData; // CraftBukkit - type
private int lastSpawnChunkRadius;
- final EntityTickList entityTickList = new EntityTickList();
+ public final EntityTickList entityTickList = new EntityTickList(); // Sakura - package-private -> public
// Paper - rewrite chunk system
private final GameEventDispatcher gameEventDispatcher;
public boolean noSave;
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
index d8b4196adf955f8d414688dc451caac2d9c609d9..0859aecb141261638b8547fb8854768fb6b6f5c7 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -9,7 +9,7 @@ import javax.annotation.Nullable;
import net.minecraft.world.entity.Entity;
public class EntityTickList {
- private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<net.minecraft.world.entity.Entity> entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system
+ public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<net.minecraft.world.entity.Entity> entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Sakura - track tick information; expose entity list // Paper - rewrite chunk system
private void ensureActiveIsNotIterated() {
// Paper - rewrite chunk system
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index 4dbb109d0526afee99b9190fc256585121aac9b5..019ae7104a644f23495e42510a80573a7ac06a37 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -279,7 +279,7 @@ public class SpigotConfig
private static void tpsCommand()
{
- SpigotConfig.commands.put( "tps", new TicksPerSecondCommand( "tps" ) );
+ // SpigotConfig.commands.put( "tps", new TicksPerSecondCommand( "tps" ) ); // Sakura - TPS Graph
}
public static int playerSample;

View File

@@ -0,0 +1,58 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Wed, 6 Oct 2021 17:25:27 +0100
Subject: [PATCH] Optimise New Liquid Level
diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
index f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1..4613162b6e716e33a838c59171c486b9c4d4b097 100644
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
@@ -181,7 +181,7 @@ public abstract class FlowingFluid extends Fluid {
FluidState fluid1 = iblockdata1.getFluidState();
if (this.canMaybePassThrough(world, fluidPos, blockState, Direction.DOWN, blockposition1, iblockdata1, fluid1)) {
- FluidState fluid2 = this.getNewLiquid(world, blockposition1, iblockdata1);
+ FluidState fluid2 = this.getLiquid(world, blockposition1, iblockdata1, fluidPos, blockState); // Sakura - optimise new liquid level
Fluid fluidtype = fluid2.getType();
if (fluid1.canBeReplacedWith(world, blockposition1, fluidtype, Direction.DOWN) && FlowingFluid.canHoldSpecificFluid(world, blockposition1, iblockdata1, fluidtype)) {
@@ -245,6 +245,23 @@ public abstract class FlowingFluid extends Fluid {
}
protected FluidState getNewLiquid(ServerLevel world, BlockPos pos, BlockState state) {
+ // Sakura start - optimise new liquid level
+ final BlockPos abovePos = pos.above();
+ final BlockState aboveState = world.getBlockState(abovePos);
+ return this.getLiquid(world, pos, state, abovePos, aboveState);
+ }
+
+ private FluidState getLiquid(final ServerLevel world, final BlockPos flowToPos, final BlockState flowToState, final BlockPos abovePos, final BlockState aboveState) {
+ final FluidState aboveFluid = aboveState.getFluidState();
+ if (!aboveFluid.isEmpty() && aboveFluid.getType().isSame(this) && FlowingFluid.canPassThroughWall(Direction.UP, world, flowToPos, flowToState, abovePos, aboveState)) {
+ return this.getFlowing(8, true);
+ } else {
+ return this.getLiquidFromSurroundings(world, flowToPos, flowToState);
+ }
+ }
+
+ private FluidState getLiquidFromSurroundings(final ServerLevel world, final BlockPos pos, final BlockState state) {
+ // Sakura start - optimise new liquid level
int i = 0;
int j = 0;
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
@@ -275,13 +292,7 @@ public abstract class FlowingFluid extends Fluid {
}
}
- BlockPos.MutableBlockPos blockposition_mutableblockposition2 = blockposition_mutableblockposition.setWithOffset(pos, Direction.UP);
- BlockState iblockdata3 = world.getBlockState(blockposition_mutableblockposition2);
- FluidState fluid2 = iblockdata3.getFluidState();
-
- if (!fluid2.isEmpty() && fluid2.getType().isSame(this) && FlowingFluid.canPassThroughWall(Direction.UP, world, pos, state, blockposition_mutableblockposition2, iblockdata3)) {
- return this.getFlowing(8, true);
- } else {
+ { // Sakura - optimise new liquid level
int k = i - this.getDropOff(world);
return k <= 0 ? Fluids.EMPTY.defaultFluidState() : this.getFlowing(k, false);

View File

@@ -0,0 +1,238 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cryptite <cryptite@gmail.com>
Date: Wed, 6 Oct 2021 11:03:01 -0500
Subject: [PATCH] (Slice) Packet obfuscation and reduction
Minecraft is overzealous about packet updates for Entities. In Loka's case, we want to reduce as many unnecessary
packet updates as possible. This patch is likely to be updated over and over in terms of reducing packet sends.
In summary, this patch creates the concept of a "foreignValue" of a packet's data. We treat packets in two ways:
1) The packet sent to the player itself (the normal way). This always has all of the values as usual.
2) The packet data as seen by any other (foreign) players.
This patch adds the ability to set a "foreignValue" for an entity value so as to obfuscate data received by other players.
The current packets modified/obfuscated are the following:
# This reduces the amount of health packet updates as well which is great for players in combat.
# Air level packets are sent PER-TICK, and as such a player with any change in air level will only spam themselves
# with packets instead of every single player within tracking distance
diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
index 0f99733660f91280e4c6262cf75b3c9cae86f65a..ba9f8fe6fafc54bbdfb104de28af4b392feb4483 100644
--- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
+++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
@@ -22,6 +22,7 @@ public class SynchedEntityData {
private final SyncedDataHolder entity;
private final SynchedEntityData.DataItem<?>[] itemsById;
private boolean isDirty;
+ private boolean isForeignDirty; // Slice
SynchedEntityData(SyncedDataHolder trackedEntity, SynchedEntityData.DataItem<?>[] entries) {
this.entity = trackedEntity;
@@ -63,6 +64,16 @@ public class SynchedEntityData {
}
public <T> void set(EntityDataAccessor<T> key, T value, boolean force) {
+ // Slice start
+ this.set(key, value, null, force);
+ }
+
+ public <T> void set(EntityDataAccessor<T> key, T value, T foreignValue) {
+ this.set(key, value, foreignValue, false);
+ }
+
+ public <T> void set(EntityDataAccessor<T> key, T value, T foreignValue, boolean force) {
+ // Slice end
SynchedEntityData.DataItem<T> datawatcher_item = this.getItem(key);
if (force || ObjectUtils.notEqual(value, datawatcher_item.getValue())) {
@@ -72,6 +83,12 @@ public class SynchedEntityData {
this.isDirty = true;
}
+ // Slice start
+ if (foreignValue != null && ObjectUtils.notEqual(foreignValue, datawatcher_item.getForeignValue())) {
+ datawatcher_item.setForeignValue(foreignValue);
+ this.isForeignDirty = true;
+ }
+ // Slice end
}
// CraftBukkit start - add method from above
@@ -81,6 +98,12 @@ public class SynchedEntityData {
}
// CraftBukkit end
+ // Slice start
+ public boolean isForeignDirty() {
+ return this.isForeignDirty;
+ }
+ // Slice end
+
public boolean isDirty() {
return this.isDirty;
}
@@ -108,6 +131,29 @@ public class SynchedEntityData {
}
}
+ // Slice start
+ @Nullable
+ public List<SynchedEntityData.DataValue<?>> packForeignDirty(List<DataValue<?>> unpackedData) {
+ List<SynchedEntityData.DataValue<?>> list = null;
+
+ for (DataValue<?> dataItem : unpackedData) {
+ DataItem<?> item = this.itemsById[dataItem.id()];
+ if (item.isDirty(true)) {
+ item.setForeignDirty(false);
+
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+
+ list.add(item.copy(true));
+ }
+ }
+
+ this.isForeignDirty = false;
+ return list;
+ }
+ // Slice end
+
@Nullable
public List<SynchedEntityData.DataValue<?>> getNonDefaultValues() {
List<SynchedEntityData.DataValue<?>> list = null;
@@ -171,11 +217,14 @@ public class SynchedEntityData {
T value;
private final T initialValue;
private boolean dirty;
+ @Nullable T foreignValue = null; // Slice
+ private boolean foreignDirty; // Slice
public DataItem(EntityDataAccessor<T> data, T value) {
this.accessor = data;
this.initialValue = value;
this.value = value;
+ this.foreignDirty = true; // Slice
}
public EntityDataAccessor<T> getAccessor() {
@@ -202,6 +251,35 @@ public class SynchedEntityData {
return this.initialValue.equals(this.value);
}
+ // Slice start
+ public void setForeignValue(T foreignValue) {
+ this.foreignValue = foreignValue;
+ this.foreignDirty = true;
+ }
+
+ public @Nullable T getForeignValue() {
+ return this.foreignValue;
+ }
+
+ public boolean isDirty(boolean foreign) {
+ if (foreign) {
+ //There must be a foreign value in order for this to be dirty, otherwise we consider this a normal
+ //value and check the normal dirty flag.
+ return this.foreignValue == null || this.foreignDirty;
+ }
+
+ return this.dirty;
+ }
+
+ public void setForeignDirty(boolean dirty) {
+ this.foreignDirty = dirty;
+ }
+
+ public SynchedEntityData.DataValue<T> copy(boolean foreign) {
+ return SynchedEntityData.DataValue.create(this.accessor, this.accessor.serializer().copy((foreign && this.foreignValue != null ? this.foreignValue : this.value)));
+ }
+ // Slice end
+
public SynchedEntityData.DataValue<T> value() {
return SynchedEntityData.DataValue.create(this.accessor, this.value);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 103e2c414780be66324bcb9cd4ea539bbdfe12ad..8004c920f9709723c9f4fe1a430247b5ee1520f1 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -156,7 +156,7 @@ public class ServerEntity {
}
}
- if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { // Paper - fix desync when a player is added to the tracker
+ if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isForeignDirty()) { // Slice // Paper - fix desync when a player is added to the tracker
byte b0 = Mth.packDegrees(this.entity.getYRot());
byte b1 = Mth.packDegrees(this.entity.getXRot());
boolean flag = Math.abs(b0 - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
@@ -449,7 +449,15 @@ public class ServerEntity {
if (list != null) {
this.trackedDataValues = datawatcher.getNonDefaultValues();
- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
+ // Slice start
+ if (!(this.entity instanceof ServerPlayer)) {
+ list = datawatcher.packForeignDirty(list);
+ }
+
+ if (list != null) {
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
+ }
+ // Slice end
}
if (this.entity instanceof LivingEntity) {
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 20980e5a180e0e7951d5c9a97b8b8f792c619b9a..384b64cfbc2fef49fa22baf1f640ea8f440093cd 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -3703,7 +3703,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID);
return;
}
- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount());
+ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount(), getMaxAirSupply()); // Slice
// CraftBukkit end
}
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 20fa9a70f2d51aaa7f9ea01150d65c1f73caa374..81d02da6afd4b77c0ca60e9c8c5100ce6988753c 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -124,7 +124,7 @@ public class FallingBlockEntity extends Entity {
}
public void setStartPos(BlockPos pos) {
- this.entityData.set(FallingBlockEntity.DATA_START_POS, pos);
+ this.entityData.set(FallingBlockEntity.DATA_START_POS, pos, BlockPos.ZERO); // Slice
}
public BlockPos getStartPos() {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 539219a4117c67278461ee483a457c005e6edcfc..46b729ecf0c58bdbe7a4717e73b098dcffd910f1 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -217,7 +217,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
}
public void setFuse(int fuse) {
- this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse);
+ this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse, (fuse / 10) * 10); // Slice
}
public int getFuse() {
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index 5b8b85a295a08ae495f729c595b3a78778965342..99133ad27aac31f68101aff4a4c4965c7ff1fd6b 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -679,7 +679,7 @@ public abstract class Player extends LivingEntity {
public void increaseScore(int score) {
int j = this.getScore();
- this.entityData.set(Player.DATA_SCORE_ID, j + score);
+ this.entityData.set(Player.DATA_SCORE_ID, j + score, 0); // Slice
}
public void startAutoSpinAttack(int riptideTicks, float riptideAttackDamage, ItemStack stack) {

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 3 Aug 2023 12:54:52 +0100
Subject: [PATCH] Use Optimised TrackedEntityMap
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 464fc90376075ecc46f7b7731ba5e2c705324e7d..dff0b8a1f2afcef21ca7e7aea3ee635826a2e9ef 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -187,7 +187,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.toDrop = new LongOpenHashSet();
this.tickingGenerated = new AtomicInteger();
this.playerMap = new PlayerMap();
- this.entityMap = new Int2ObjectOpenHashMap();
+ this.entityMap = new me.samsuik.sakura.utils.collections.TrackedEntityChunkMap(); // Sakura - optimised tracked entity map
this.chunkTypeCache = new Long2ByteOpenHashMap();
// Paper - rewrite chunk system
Path path = session.getDimensionPath(world.dimension());

View File

@@ -0,0 +1,81 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 14 Oct 2021 19:16:49 +0100
Subject: [PATCH] Copy EntityList methods to BasicEntityList
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
index d21ce54ebb5724c04eadf56a2cde701d5eeb5db2..1fad004f07270e8a27a85557de6fcf5e693751db 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
@@ -383,6 +383,13 @@ public final class ChunkEntitySlices {
private E[] storage;
private int size;
+ // Sakura start - use methods from EntityList
+ private it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap entityToIndex = null;
+ private void setupIndexMap() {
+ this.entityToIndex = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(2, 0.8f);
+ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
+ }
+ // Sakura end - use methods from EntityList
public BasicEntityList() {
this(0);
@@ -403,6 +410,7 @@ public final class ChunkEntitySlices {
private void resize() {
if (this.storage == EMPTY) {
this.storage = (E[])new Entity[DEFAULT_CAPACITY];
+ this.setupIndexMap(); // Sakura - use methods from EntityList
} else {
this.storage = Arrays.copyOf(this.storage, this.storage.length * 2);
}
@@ -416,6 +424,7 @@ public final class ChunkEntitySlices {
} else {
this.storage[idx] = entity;
}
+ this.entityToIndex.put(entity.getId(), idx); // Sakura - use methods from EntityList
}
public int indexOf(final E entity) {
@@ -431,24 +440,32 @@ public final class ChunkEntitySlices {
}
public boolean remove(final E entity) {
- final int idx = this.indexOf(entity);
- if (idx == -1) {
+ // Sakura start - use methods from EntityList
+ if (this.entityToIndex == null) {
return false;
}
- final int size = --this.size;
- final E[] storage = this.storage;
- if (idx != size) {
- System.arraycopy(storage, idx + 1, storage, idx, size - idx);
+ final int index = this.entityToIndex.remove(entity.getId());
+ if (index == Integer.MIN_VALUE) {
+ return false;
}
- storage[size] = null;
+ // move the entity at the end to this index
+ final int endIndex = --this.size;
+ final E end = this.storage[endIndex];
+ if (index != endIndex) {
+ // not empty after this call
+ this.entityToIndex.put(end.getId(), index); // update index
+ }
+ this.storage[index] = end;
+ this.storage[endIndex] = null;
+ // Sakura end - use methods from EntityList
return true;
}
public boolean has(final E entity) {
- return this.indexOf(entity) != -1;
+ return this.entityToIndex != null && this.entityToIndex.containsKey(entity.getId()); // Sakura - use methods from EntityList
}
}

View File

@@ -0,0 +1,42 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 3 Aug 2023 13:48:27 +0100
Subject: [PATCH] Add utility methods to EntitySlices
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
index 1fad004f07270e8a27a85557de6fcf5e693751db..39148fe540d616cdd4d63c4d1a02b422cab0f6eb 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
@@ -297,6 +297,12 @@ public final class ChunkEntitySlices {
return true;
}
+ // Sakura start - add utility methods to entity slices
+ public Entity[] getSectionEntities(int sectionY) {
+ return this.allEntities.getSectionEntities(sectionY);
+ }
+ // Sakura end - add utility methods to entity slices
+
public void getHardCollidingEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
this.hardCollidingEntities.getEntities(except, box, into, predicate);
}
@@ -512,6 +518,18 @@ public final class ChunkEntitySlices {
}
}
+ // Sakura start - add utility methods to entity slices
+ public Entity[] getSectionEntities(int sectionY) {
+ BasicEntityList<Entity> list = this.entitiesBySection[sectionY - this.slices.minSection];
+
+ if (list != null) {
+ return list.storage;
+ }
+
+ return new Entity[0];
+ }
+ // Sakura end - add utility methods to entity slices
+
public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
if (this.count == 0) {
return;

View File

@@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Fri, 13 Oct 2023 20:02:04 +0100
Subject: [PATCH] Entity pushed by fluid API
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 953aa1b3d292dd8e8ed29926c269e6d6e79a83c0..b1ae26d08f12aadea37e44221c44db6430a3a99c 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -622,6 +622,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
}
// Sakura end - merge cannon entities
+ public boolean pushedByFluid = true; // Sakura - entity pushed by fluid api
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
@@ -4480,7 +4481,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public boolean isPushedByFluid() {
- return true;
+ return this.pushedByFluid; // Sakura - entity pushed by fluid api
}
public static double getViewScale() {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index b25b10c24a379097233e61bcc10add841b6a7115..4f4c708361160461bb472dcc79751fa29c5274b9 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -204,6 +204,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return this.entity.isInWater();
}
+ // Sakura start - entity pushed by fluid api
+ @Override
+ public boolean isPushedByFluid() {
+ return this.getHandle().isPushedByFluid();
+ }
+
+ @Override
+ public void setPushedByFluid(boolean push) {
+ this.getHandle().pushedByFluid = push;
+ }
+ // Sakura end - entity pushed by fluid api
+
@Override
public World getWorld() {
return this.entity.level().getWorld();

View File

@@ -0,0 +1,88 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sun, 15 Oct 2023 22:48:35 +0100
Subject: [PATCH] Cannon Mechanics
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 32811e9d925911864e0c0fa5a2e25031098f423e..e6e38c5f2922989c8616d0cf0bd7bd32403ea863 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -72,6 +72,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
public boolean forceTickAfterTeleportToDuplicate;
protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
public boolean autoExpire = true; // Paper - Expand FallingBlock API
+ public boolean heightParity; // Sakura - configure cannon mechanics
// Sakura start - merge cannon entities
private final me.samsuik.sakura.entity.merge.MergeEntityData mergeData = new me.samsuik.sakura.entity.merge.MergeEntityData(this);
@@ -133,6 +134,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
this.isFallingBlock = true; // Sakura
this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
this.mergeData.setMergeLevel(world.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
+ this.heightParity = world.sakuraConfig().cannons.mechanics.fallingBlockParity; // Sakura - configure cannon mechanics
}
public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) {
@@ -199,6 +201,13 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
return !this.isRemoved();
}
+ // Sakura start - configure cannon mechanics
+ @Override
+ public final double getEyeY() {
+ return this.heightParity ? this.getY() : super.getEyeY();
+ }
+ // Sakura end - configure cannon mechanics
+
@Override
protected double getDefaultGravity() {
return 0.04D;
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 7bb55565208b99db095c5842a7e21fd21700173e..93cf290b25d569820dd1bf9b4b5fb255abdbd8cd 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -102,6 +102,12 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
this.yo = y;
this.zo = z;
this.owner = igniter;
+ // Sakura start - configure cannon mechanics
+ switch (world.sakuraConfig().cannons.mechanics.tntSpread) {
+ case NONE -> this.setDeltaMovement(0.0, 0.0, 0.0);
+ case Y -> this.setDeltaMovement(this.getDeltaMovement().multiply(0.0, 1.0, 0.0));
+ }
+ // Sakura end - configure cannon mechanics
}
@Override
@@ -286,7 +292,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
// Paper start - Option to prevent TNT from moving in water
@Override
public boolean isPushedByFluid() {
- return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
+ return !level().paperConfig().fixes.preventTntFromMovingInWater && level().sakuraConfig().cannons.mechanics.tntFlowsInWater && super.isPushedByFluid(); // Sakura - configure cannon mechanics
}
// Paper end - Option to prevent TNT from moving in water
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
index 55f67c2ca07eca0d3e1522eebbb4ce37704bdd04..2de0befc3948a23910cfc609fc25c57bbd0cb030 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
@@ -35,6 +35,17 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock {
this.getHandle().getMergeEntityData().setCount(stacked);
}
// Sakura end - merge cannon entities
+ // Sakura start - cannon mechanics
+ @Override
+ public void setHeightParity(boolean parity) {
+ this.getHandle().heightParity = parity;
+ }
+
+ @Override
+ public boolean getHeightParity() {
+ return this.getHandle().heightParity;
+ }
+ // Sakura end - cannon mechanics
@Override
public FallingBlockEntity getHandle() {

View File

@@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Mon, 16 Oct 2023 22:41:12 +0100
Subject: [PATCH] Cache MovingBlockEntity collision shape
diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
index e1c9a961064887070b29207efd7af47884f8dc29..f873061666cf7ba30b2b5dfe3b3a1ea85d2cdd4f 100644
--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
@@ -43,6 +43,11 @@ public class PistonMovingBlockEntity extends BlockEntity {
private float progressO;
private long lastTicked;
private int deathTicks;
+ // Sakura start
+ private VoxelShape collisionShape = Shapes.empty();
+ private Direction shapeDirection = null;
+ private float shapeProgress = Float.MIN_VALUE;
+ // Sakura end
public PistonMovingBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.PISTON, pos, state);
@@ -358,6 +363,18 @@ public class PistonMovingBlockEntity extends BlockEntity {
}
public VoxelShape getCollisionShape(BlockGetter world, BlockPos pos) {
+ // Sakura start
+ var direction = NOCLIP.get();
+ if (progress == shapeProgress && direction == shapeDirection) {
+ return collisionShape;
+ } else {
+ shapeProgress = progress;
+ shapeDirection = direction;
+ return collisionShape = createCollisionShape(world, pos);
+ }
+ }
+ private VoxelShape createCollisionShape(BlockGetter world, BlockPos pos) {
+ // Sakura end
VoxelShape voxelShape;
if (!this.extending && this.isSourcePiston && this.movedState.getBlock() instanceof PistonBaseBlock) {
voxelShape = this.movedState.setValue(PistonBaseBlock.EXTENDED, Boolean.valueOf(true)).getCollisionShape(world, pos);

View File

@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Mon, 16 Oct 2023 22:57:55 +0100
Subject: [PATCH] Optimise TNT fluid state
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index b1ae26d08f12aadea37e44221c44db6430a3a99c..a56517cb6628939e6eed4b26e5e7f8bf6fc1c7af 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -2256,7 +2256,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return this.isInWater() || flag;
}
- void updateInWaterStateAndDoWaterCurrentPushing() {
+ protected void updateInWaterStateAndDoWaterCurrentPushing() { // Sakura
Entity entity = this.getVehicle();
if (entity instanceof AbstractBoat abstractboat) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 712ac847cf5fbb01133da2630f942afc69c79672..00801a9396e3c128730fe75c287d7b29ad0a00df 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -126,6 +126,21 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
return !this.isRemoved();
}
+ // Sakura start - optimise tnt fluid state
+ @Override
+ protected boolean updateInWaterStateAndDoFluidPushing() {
+ if (this.isPushedByFluid()) {
+ return super.updateInWaterStateAndDoFluidPushing();
+ } else {
+ // super method also handles lava fluid pushing
+ // we only need to search for water to negate fall distance
+ this.fluidHeight.clear();
+ this.updateInWaterStateAndDoWaterCurrentPushing();
+ return this.isInWater();
+ }
+ }
+ // Sakura end - optimise tnt fluid state
+
@Override
protected double getDefaultGravity() {
return 0.04D;

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Wed, 15 Nov 2023 13:04:16 +0000
Subject: [PATCH] Despawn falling blocks inside moving pistons
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index e6e38c5f2922989c8616d0cf0bd7bd32403ea863..2f719d88d292fdf8e9fe51e3445b49e2ecc71e29 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -255,7 +255,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
}
}
- if (!this.onGround() && !flag1) {
+ if (!this.onGround() && !flag1 || this.level().sakuraConfig().cannons.sand.despawnInsideMovingPistons && autoExpire && this.time > 600) { // Sakura - allow falling blocks to despawn inside moving pistons
if ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API
if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
this.spawnAtLocation(worldserver, (ItemLike) block);

View File

@@ -0,0 +1,130 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Wed, 15 Nov 2023 20:52:56 +0000
Subject: [PATCH] Configure Entity Knockback
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 01179c4b01aa4898d08b392bd682587e3858b822..f1560f5139b3090057950b41c4374ded4c50b3e2 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -1572,7 +1572,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
// Paper end - Check distance in entity interactions
- this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
+ this.knockback((float) this.level().sakuraConfig().players.knockback.baseKnockback, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events // Sakura - configure entity knockback
if (!flag) {
this.indicateDamage(d0, d1);
}
@@ -1685,7 +1685,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
protected void blockedByShield(LivingEntity target) {
- target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events
+ target.knockback((float) this.level().sakuraConfig().players.knockback.shieldHitKnockback, target.getX() - this.getX(), target.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events // Sakura - configure entity knockback
}
private boolean checkTotemDeathProtection(DamageSource source) {
@@ -2035,7 +2035,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
public void knockback(double d0, double d1, double d2, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events
- d0 *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
+ d0 *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE) * this.level().sakuraConfig().players.knockback.knockbackResistanceModifier; // Sakura - configure entity knockback
if (true || d0 > 0.0D) { // CraftBukkit - Call event even when force is 0
//this.hasImpulse = true; // CraftBukkit - Move down
@@ -2046,9 +2046,21 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
Vec3 vec3d1 = (new Vec3(d1, 0.0D, d2)).normalize().scale(d0);
+ // Sakura start - configure entity knockback
+ double velocityX = vec3d.x / 2.0D - vec3d1.x;
+ double velocityY = vec3d.y / 2.0D + this.level().sakuraConfig().players.knockback.knockbackVertical.or(d0);
+ double velocityZ = vec3d.z / 2.0D - vec3d1.z;
+
+ // this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y
+ if (!this.level().sakuraConfig().players.knockback.verticalKnockbackRequireGround || this.onGround()) {
+ velocityY = Math.min(this.level().sakuraConfig().players.knockback.knockbackVerticalLimit, velocityY);
+ } else {
+ velocityY = vec3d.y;
+ }
+ // Sakura end - configure entity knockback
// Paper start - knockback events
- Vec3 finalVelocity = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z);
+ Vec3 finalVelocity = new Vec3(velocityX, velocityY, velocityZ); // Sakura - configure entity knockback
Vec3 diff = finalVelocity.subtract(vec3d);
io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, cause, d0, diff);
// Paper end - knockback events
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index 99133ad27aac31f68101aff4a4c4965c7ff1fd6b..d11f67157c1bf8c14776c56748de3588273c7d45 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -200,6 +200,7 @@ public abstract class Player extends LivingEntity {
private int currentImpulseContextResetGraceTime;
public boolean affectsSpawning = true; // Paper - Affects Spawning API
public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
+ private long lastSprintKnockback = -1; // Sakura - configure entity knockback
// CraftBukkit start
public boolean fauxSleeping;
@@ -1264,7 +1265,7 @@ public abstract class Player extends LivingEntity {
boolean flag = f2 > 0.9F;
boolean flag1;
- if (this.isSprinting() && flag) {
+ if (this.isSprinting() && (!this.level().sakuraConfig().players.knockback.sprinting.requireFullAttack || flag)) { // Sakura - configure entity knockback
sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
flag1 = true;
} else {
@@ -1307,7 +1308,21 @@ public abstract class Player extends LivingEntity {
float f5 = this.getKnockback(target, damagesource) + (flag1 ? 1.0F : 0.0F);
if (f5 > 0.0F) {
- if (target instanceof LivingEntity) {
+ // Sakura start - configure entity knockback; extra sprinting knockback
+ long millis = System.currentTimeMillis();
+ long sinceLastKnockback = millis - this.lastSprintKnockback;
+ if (flag1) { // attackHasExtraKnockback
+ double knockbackToApply = 0.0;
+ if (sinceLastKnockback >= this.level().sakuraConfig().players.knockback.sprinting.knockbackDelay.value().orElse(0)) {
+ knockbackToApply = this.level().sakuraConfig().players.knockback.sprinting.extraKnockback;
+ this.lastSprintKnockback = millis;
+ }
+ f5 = (f5 - 1.0f) + ((float) knockbackToApply * 2.0f);
+ }
+ if (f5 == 0.0f) {
+ // required
+ } else if (target instanceof LivingEntity) {
+ // Sakura end - configure entity knockback; extra sprinting knockback
LivingEntity entityliving1 = (LivingEntity) target;
entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - knockback events
@@ -1340,7 +1355,7 @@ public abstract class Player extends LivingEntity {
continue;
}
// CraftBukkit end
- entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK); // CraftBukkit // Paper - knockback events
+ entityliving2.knockback((float) this.level().sakuraConfig().players.knockback.sweepingEdgeKnockback, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK); // Sakura - configure entity knockback // CraftBukkit // Paper - knockback events
// entityliving2.hurt(damagesource, f7); // CraftBukkit - moved up
Level world = this.level();
diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
index 5e6ceb3c3728c0c08a516566c70a5c0d72d59196..1cc44f6626dfff7724e2ea1b41917ae4715b8d4d 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
@@ -304,6 +304,12 @@ public class FishingHook extends Projectile {
this.setHookedEntity(entityHitResult.getEntity());
}
+ // Sakura start - configure entity knockback
+ if (this.level().sakuraConfig().players.knockback.fishingHooksApplyKnockback) {
+ Entity entity = entityHitResult.getEntity();
+ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0f);
+ }
+ // Sakura end - configure entity knockback
}
@Override

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 16 Nov 2023 20:53:51 +0000
Subject: [PATCH] Falling Block Stacking Restrictions
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 2f719d88d292fdf8e9fe51e3445b49e2ecc71e29..890f1d6f8ad740afb0b30208f7cd42594e4c9d20 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -273,7 +273,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
boolean flag3 = FallingBlock.isFree(this.level().getBlockState(blockposition.below())) && (!flag || !flag1);
boolean flag4 = this.blockState.canSurvive(this.level(), blockposition) && !flag3;
- if (flag2 && flag4) {
+ if (flag2 && flag4 && level().sakuraConfig().cannons.sand.isFallingBlockInBounds(this)) { // Sakura
if (this.blockState.hasProperty(BlockStateProperties.WATERLOGGED) && this.level().getFluidState(blockposition).getType() == Fluids.WATER) {
this.blockState = (BlockState) this.blockState.setValue(BlockStateProperties.WATERLOGGED, true);
}

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hermelijn15 <j.s.dekker-1@student.utwente.nl>
Date: Fri, 17 Nov 2023 20:09:03 +0100
Subject: [PATCH] Added list of ItemEntity's that ignore explosions
diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
index 0f086af57a5ff08c264dcbf89a8c3931ec73a609..d9a6324eeb0cfaef3d0bbff884ddfadc6e4132f7 100644
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
@@ -410,6 +410,11 @@ public class ItemEntity extends Entity implements TraceableEntity {
@Override
public boolean ignoreExplosion(Explosion explosion) {
+ // Sakura start - add list of items that ignore explosions
+ if (this.level().sakuraConfig().entity.items.explosionResistantItems.contains(this.getItem().getItem())) {
+ return true;
+ }
+ // Sakura end - add list of items that ignore explosions
return explosion.shouldAffectBlocklikeEntities() ? super.ignoreExplosion(explosion) : true;
}

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 18 Nov 2023 15:12:14 +0000
Subject: [PATCH] Add option to disable entity ai
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 5a0b51342f4a646101f4588697bcae7d1ca8a010..700a690321fb08e0ce594505e6cc6f3b81d86995 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -910,7 +910,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
protected final void serverAiStep() {
++this.noActionTime;
// Paper start - Allow nerfed mobs to jump and float
- if (!this.aware) {
+ if (!this.aware || this.level().sakuraConfig().entity.disableMobAi) { // Sakura - add option to disable entity ai
if (goalFloat != null) {
if (goalFloat.canUse()) goalFloat.tick();
this.getJumpControl().tick();

View File

@@ -0,0 +1,35 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Mon, 20 Nov 2023 19:32:31 +0000
Subject: [PATCH] Consistent Explosion Radius
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index cc97e3a88ea99362ec043edf13c1f54c2c75daaa..1a912813ab7fa112379876ea1d4c903afe45c069 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -412,6 +412,7 @@ public class ServerExplosion implements Explosion {
return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState);
}
// Sakura end - explosion durable blocks
+ private final boolean consistentRadius; // Sakura - consistent explosion radius
public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
this.level = world;
@@ -423,6 +424,7 @@ public class ServerExplosion implements Explosion {
this.damageSource = damageSource == null ? world.damageSources().explosion(this) : damageSource;
this.damageCalculator = behavior == null ? this.makeDamageCalculator(entity) : behavior;
this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
+ this.consistentRadius = world.localConfig().config(BlockPos.containing(this.center)).consistentRadius; // Sakura - consistent explosion radius
}
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
@@ -533,7 +535,7 @@ public class ServerExplosion implements Explosion {
ray += 3;
- float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
+ float power = this.radius * (0.7F + (this.consistentRadius ? 0.7F : this.level.random.nextFloat()) * 0.6F); // Sakura - consistent explosion radius
do {
final int blockX = Mth.floor(currX);

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Tue, 21 Nov 2023 10:56:30 +0000
Subject: [PATCH] Remove spigot max tnt per tick
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index f2206e57c04d29ed470ae2083b25ab502144ccb1..3e4935f4a07fa14152d1f55f069c49fdd2f3bc02 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -148,7 +148,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
@Override
public void tick() {
- if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot
+ // Sakura - remove max tnt per tick
this.handlePortal();
this.applyGravity();
this.moveStripped(MoverType.SELF, this.getDeltaMovement()); // Sakura - optimise cannon entity movement

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Tue, 21 Nov 2023 11:21:37 +0000
Subject: [PATCH] Option to configure entity water sensitivity
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index f1560f5139b3090057950b41c4374ded4c50b3e2..e7603a67c985ff105a8a4e16179fe33fe26f5b22 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3730,7 +3730,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
// Paper end - Add EntityMoveEvent
world = this.level();
if (world instanceof ServerLevel worldserver) {
- if (this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
+ if (this.level().sakuraConfig().entity.waterSensitivity && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { // Sakura - configure entity water sensitivity
this.hurtServer(worldserver, this.damageSources().drown(), 1.0F);
}
}

View File

@@ -0,0 +1,58 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Wed, 29 Nov 2023 22:32:44 +0000
Subject: [PATCH] Add redstone implementation API
diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
index 5d847016f6ee2d6340d8b2234ed35c3b9228632b..5fae13db49b60ea32e046aff64059a08ce626e3f 100644
--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -300,7 +300,7 @@ public class RedStoneWireBlock extends Block {
* Note: Added 'source' argument so as to help determine direction of information flow
*/
private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) {
- if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) {
+ if (worldIn.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { // Sakura - redstone implementation api
// since 24w33a the source pos is no longer given, but instead an Orientation parameter
// when this is not null, it can be used to find the source pos, which the turbo uses
// to find the direction of information flow
@@ -373,7 +373,7 @@ public class RedStoneWireBlock extends Block {
protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
if (!oldState.is(state.getBlock()) && !world.isClientSide) {
// Paper start - optimize redstone - replace call to updatePowerStrength
- if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
+ if (world.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
world.getWireHandler().onWireAdded(pos, state); // Alternate Current
} else {
this.updateSurroundingRedstone(world, pos, state, null, true); // Vanilla/Eigencraft
@@ -398,7 +398,7 @@ public class RedStoneWireBlock extends Block {
}
// Paper start - optimize redstone - replace call to updatePowerStrength
- if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
+ if (world.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
world.getWireHandler().onWireRemoved(pos, state); // Alternate Current
} else {
this.updateSurroundingRedstone(world, pos, state, null, false); // Vanilla/Eigencraft
@@ -428,7 +428,7 @@ public class RedStoneWireBlock extends Block {
if (!world.isClientSide) {
// Paper start - optimize redstone (Alternate Current)
// Alternate Current handles breaking of redstone wires in the WireHandler.
- if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
+ if (world.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
world.getWireHandler().onWireUpdated(pos, state, wireOrientation);
} else
// Paper end - optimize redstone (Alternate Current)
diff --git a/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java b/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
index 8342dd636531729a187aff1bd69878d7aef9d3eb..2272b081152fc70f5034186b36172b4a19e2680b 100644
--- a/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
+++ b/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
@@ -18,6 +18,7 @@ public class ExperimentalRedstoneUtils {
orientation = orientation.withFront(up);
}
// Paper start - Optimize redstone (Alternate Current) - use default front instead of random
+ // Sakura - redstone implementation api; conflict on change
else if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
orientation = orientation.withFront(Direction.WEST);
}

View File

@@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Fri, 24 Nov 2023 18:52:51 +0000
Subject: [PATCH] Allow water in the nether
diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java
index 3bddfb6f7412ab86e0c090d0cbc6cf254b3f891c..4cb809976b42af933401e8fc34ee43e181761558 100644
--- a/src/main/java/net/minecraft/world/item/BucketItem.java
+++ b/src/main/java/net/minecraft/world/item/BucketItem.java
@@ -196,7 +196,7 @@ public class BucketItem extends Item implements DispensibleContainerItem {
// CraftBukkit end
if (!flag2) {
return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit
- } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
+ } else if (!world.sakuraConfig().environment.allowWaterInTheNether && world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { // Sakura
int i = blockposition.getX();
int j = blockposition.getY();
int k = blockposition.getZ();
diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java
index a94762e65853ccad38cf90b0049ca256106c0c9f..fac57b911966855c31dccc26cae7d7113da437dd 100644
--- a/src/main/java/net/minecraft/world/level/block/IceBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java
@@ -42,7 +42,7 @@ public class IceBlock extends HalfTransparentBlock {
public void afterDestroy(Level world, BlockPos pos, ItemStack tool) {
// Paper end - Improve Block#breakNaturally API
if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) {
- if (world.dimensionType().ultraWarm()) {
+ if (!world.sakuraConfig().environment.allowWaterInTheNether && world.dimensionType().ultraWarm()) { // Sakura
world.removeBlock(pos, false);
return;
}
@@ -70,7 +70,7 @@ public class IceBlock extends HalfTransparentBlock {
return;
}
// CraftBukkit end
- if (world.dimensionType().ultraWarm()) {
+ if (!world.sakuraConfig().environment.allowWaterInTheNether && world.dimensionType().ultraWarm()) { // Sakura
world.removeBlock(pos, false);
} else {
world.setBlockAndUpdate(pos, IceBlock.meltsInto());

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 25 Nov 2023 20:36:05 +0000
Subject: [PATCH] Configure concrete solidifying in water
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 7ec7cfde4ce47a7f4a64e83fa49ed7287684d6a0..a41fef64424ae5419713e54af41f3d9622b64e89 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -297,7 +297,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
// However, it makes sense for legacy versions pre-1.17 before the world height change.
BlockPos blockposition = this.physics.before(1_17_0) ? this.patchedBlockPosition() : this.blockPosition();
// Sakura end - physics version api
- boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock;
+ boolean flag = this.level().sakuraConfig().cannons.sand.concreteSolidifyInWater && this.blockState.getBlock() instanceof ConcretePowderBlock; // Sakura
boolean flag1 = flag && this.level().getFluidState(blockposition).is(FluidTags.WATER);
double d0 = this.getDeltaMovement().lengthSqr();

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 25 Nov 2023 20:59:07 +0000
Subject: [PATCH] Option for fast nether dimension lava
diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
index e6ed1e46a4880743b7eeb73857b4b501971d6e29..cc5ed4e2cc0ef8acf3768539103d2cdeab30362f 100644
--- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
@@ -184,7 +184,7 @@ public abstract class LavaFluid extends FlowingFluid {
@Override
public int getTickDelay(LevelReader world) {
- return world.dimensionType().ultraWarm() ? 10 : 30;
+ return world.dimensionType().ultraWarm() && !(world instanceof Level level && level.sakuraConfig().environment.disableFastNetherLava) ? 10 : 30; // Sakura - add option for fast nether lava
}
@Override

View File

@@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sun, 26 Nov 2023 17:41:00 +0000
Subject: [PATCH] Disable bubble columns affecting cannon entities
diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java
index 385da0585f409ee453f10d45f5837cdc09adc21b..da5b41ba5e5fea8e7bc0d40a880ce0f0778d5785 100644
--- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java
@@ -49,6 +49,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup {
@Override
protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!world.sakuraConfig().cannons.tntAndSandAffectedByBubbleColumns && (entity.isPrimedTNT || entity.isFallingBlock)) return; // Sakura - configure bubble columns affecting cannon entities
BlockState blockState = world.getBlockState(pos.above());
if (blockState.isAir()) {
entity.onAboveBubbleCol(state.getValue(DRAG_DOWN));

View File

@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 7 Dec 2023 16:30:24 +0000
Subject: [PATCH] Configure mob spawner defaults
diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java
index 7de66aa435dd36899b80f4ecc64480680e474d94..90c87fabab274f6202a92150c4f1d5bfe9f5dad8 100644
--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java
+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java
@@ -51,7 +51,16 @@ public abstract class BaseSpawner {
public int spawnRange = 4;
private int tickDelay = 0; // Paper - Configurable mob spawner tick rate
- public BaseSpawner() {}
+ // Sakura start - configure spawner defaults
+ public BaseSpawner() {
+ this.minSpawnDelay = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.minSpawnDelay;
+ this.maxSpawnDelay = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.maxSpawnDelay;
+ this.spawnCount = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.spawnCount;
+ this.maxNearbyEntities = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.maxNearbyEntities;
+ this.requiredPlayerRange = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.requiredPlayerRange;
+ this.spawnRange = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.spawnRange;
+ // Sakura end
+ }
public void setEntityId(EntityType<?> type, @Nullable Level world, RandomSource random, BlockPos pos) {
this.getOrCreateNextSpawnData(world, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());

View File

@@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Fri, 8 Dec 2023 18:12:20 +0000
Subject: [PATCH] Allow disabling random dispenser item selection
diff --git a/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
index c7f1937b0f171eee967388ab4699703dcdcfbd2b..77d2c11d5a01a1f209336c62ac5153b5efadbfd3 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
@@ -74,8 +74,15 @@ public class DispenserBlockEntity extends RandomizableContainerBlockEntity {
int j = 1;
for (int k = 0; k < this.items.size(); ++k) {
- if (!((ItemStack) this.items.get(k)).isEmpty() && random.nextInt(j++) == 0) {
+ // Sakura start - allow disabling dispenser random item selection
+ if (this.level.sakuraConfig().technical.dispenserRandomItemSelection || this instanceof DropperBlockEntity) {
+ if (!((ItemStack) this.items.get(k)).isEmpty() && random.nextInt(j++) == 0) {
+ i = k;
+ }
+ } else if (!this.items.get(k).isEmpty()) {
i = k;
+ break;
+ // Sakura end
}
}

View File

@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Fri, 8 Dec 2023 18:21:56 +0000
Subject: [PATCH] Add instant mob death animation
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index e7603a67c985ff105a8a4e16179fe33fe26f5b22..1bd9f31da1ec66259dc6391448e2b8ce69ddb817 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -1869,6 +1869,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
// Paper start
if (this.dead) { // Paper
+ // Sakura start
+ if (level().sakuraConfig().entity.instantDeathAnimation && !(this instanceof Player)) {
+ this.deathTime = 20;
+ return;
+ }
+ // Sakura end
this.level().broadcastEntityEvent(this, (byte) 3);
this.setPose(Pose.DYING);

View File

@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 9 Dec 2023 00:11:48 +0000
Subject: [PATCH] Configure fluids breaking redstone
diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
index 45127421ccbd4375c5408c27963ef2fa6e29de2a..b45ef9f57bf52d8b05fb0f4f9e97d1f9af0c1716 100644
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
@@ -514,6 +514,10 @@ public abstract class FlowingFluid extends Fluid {
if (block instanceof LiquidBlockContainer ifluidcontainer) {
return ifluidcontainer.canPlaceLiquid((Player) null, world, pos, state, fluid);
+ // Sakura start
+ } else if (world instanceof Level level && !level.sakuraConfig().technical.redstone.fluidsBreakRedstone && (state.isSignalSource() || state.getBlock() instanceof net.minecraft.world.level.block.CarpetBlock)) {
+ return false;
+ // Sakura end
} else {
return true;
}

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 9 Dec 2023 00:25:11 +0000
Subject: [PATCH] Option to disable explosions hurting players
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index d11f67157c1bf8c14776c56748de3588273c7d45..b439c196db1af082d5ebcd6df59c436396dc144e 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -928,6 +928,11 @@ public abstract class Player extends LivingEntity {
@Override
public boolean isInvulnerableTo(ServerLevel world, DamageSource source) {
+ // Sakura start - allow disabling explosions hurting players
+ if (!this.level().sakuraConfig().cannons.explosion.explosionsHurtPlayers && source.is(DamageTypeTags.IS_EXPLOSION)) {
+ return true;
+ }
+ // Sakura end - allow disabling explosions hurting players
return super.isInvulnerableTo(world, source) ? true : (source.is(DamageTypeTags.IS_DROWNING) ? !world.getGameRules().getBoolean(GameRules.RULE_DROWNING_DAMAGE) : (source.is(DamageTypeTags.IS_FALL) ? !world.getGameRules().getBoolean(GameRules.RULE_FALL_DAMAGE) : (source.is(DamageTypeTags.IS_FIRE) ? !world.getGameRules().getBoolean(GameRules.RULE_FIRE_DAMAGE) : (source.is(DamageTypeTags.IS_FREEZING) ? !world.getGameRules().getBoolean(GameRules.RULE_FREEZE_DAMAGE) : false))));
}

View File

@@ -0,0 +1,31 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 9 Dec 2023 16:10:29 +0000
Subject: [PATCH] Iron golems take fall damage
diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
index e07b79ef172095c1800c88342b3ac8dc7703aea2..35dab3806fab4d1fe0ba4246c2e8566d2952b8d6 100644
--- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
@@ -237,6 +237,20 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
}
+ // Sakura start
+ @Override
+ protected int calculateFallDamage(float fallDistance, float damageMultiplier) {
+ if (!this.level().sakuraConfig().entity.ironGolemsTakeFalldamage) {
+ return super.calculateFallDamage(fallDistance, damageMultiplier);
+ } else {
+ net.minecraft.world.effect.MobEffectInstance mobeffect = this.getEffect(net.minecraft.world.effect.MobEffects.JUMP);
+ float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1);
+
+ return net.minecraft.util.Mth.ceil((fallDistance - 3.0F - f2) * damageMultiplier);
+ }
+ }
+ // Sakura end
+
public int getAttackAnimationTick() {
return this.attackAnimationTick;
}

View File

@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sun, 24 Dec 2023 11:20:10 +0000
Subject: [PATCH] Add explosions dropping items config
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index e857fd02a5e341a1a701da71874dbd850e3c5a5d..0dae16d140666cae7633bbfef6d1c5b979d7dc9e 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -845,6 +845,12 @@ public class ServerExplosion implements Explosion {
});
}
+ // Sakura start - config for explosions dropping items
+ if (!this.level.sakuraConfig().cannons.explosion.explosionsDropItems) {
+ list1.clear();
+ }
+ // Sakura end - config for explosions dropping items
+
iterator = list1.iterator();
while (iterator.hasNext()) {

View File

@@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 22 Jan 2024 15:24:51 +0000
Subject: [PATCH] Avoid searching for lava if throttled water flow speed is
default
diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
index 0ea609556d906df3eadd3df83bd4a7f85857a14e..6cc129afdb19e121e1abe8dc07f5cc2216c7b084 100644
--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
@@ -148,7 +148,7 @@ public class LiquidBlock extends Block implements BucketPickup {
// Paper start - Configurable speed for water flowing over lava
public int getFlowSpeed(Level world, BlockPos blockposition) {
- if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) {
+ if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER) && this.fluid.getTickDelay(world) != world.paperConfig().environment.waterOverLavaFlowSpeed) { // Sakura
if (
isLava(world, blockposition.north(1)) ||
isLava(world, blockposition.south(1)) ||

View File

@@ -0,0 +1,30 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 22 Jan 2024 15:52:33 +0000
Subject: [PATCH] Calculate biome noise once per chunk section
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index e4ae25c83ab9dd1aaa530a5456275ef63cdb8511..ee296175dba6f05f88e8dd585e72b83bd31defcd 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -310,12 +310,18 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler, int x, int y, int z) {
PalettedContainer<Holder<Biome>> datapaletteblock = this.biomes.recreate();
+ Holder<Biome> biome = null; // Sakura
boolean flag = true;
for (int l = 0; l < 4; ++l) {
for (int i1 = 0; i1 < 4; ++i1) {
for (int j1 = 0; j1 < 4; ++j1) {
- datapaletteblock.getAndSetUnchecked(l, i1, j1, biomeSupplier.getNoiseBiome(x + l, y + i1, z + j1, sampler));
+ // Sakura start - calculate biome noise once per chunk section
+ if (biome == null || !me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.calculateBiomeNoiseOncePerChunkSection) {
+ biome = biomeSupplier.getNoiseBiome(x + l, y + i1, z + j1, sampler);
+ }
+ datapaletteblock.getAndSetUnchecked(l, i1, j1, biome);
+ // Sakura end
}
}
}

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sun, 28 Jan 2024 20:30:09 +0000
Subject: [PATCH] Fix doEntityDrops gamerule preventing falling blocks from
breaking
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index a41fef64424ae5419713e54af41f3d9622b64e89..2d9e42465d4a8adf2095d3d23b29df29af3df00d 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -376,6 +376,10 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
this.callOnBrokenAfterFall(block, blockposition);
this.spawnAtLocation(worldserver, (ItemLike) block);
+ // Sakura start - fix the do entity drops gamerule
+ } else {
+ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+ // Sakura end - fix the do entity drops gamerule
}
} else {
this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause

View File

@@ -0,0 +1,69 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 23 Feb 2024 16:18:51 +0000
Subject: [PATCH] Configure potion speed and breaking inside entities
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
index 6c2d4d6f3a36ab452dfd3c33f66e54f152906639..d0ebc66072b50e977a1cd2cca01bcfaf16495c7b 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
@@ -173,7 +173,7 @@ public abstract class Projectile extends Entity implements TraceableEntity {
super.tick();
}
- private boolean checkLeftOwner() {
+ protected boolean checkLeftOwner() { // Sakura - configure potion mechanics
Entity entity = this.getOwner();
if (entity != null) {
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java b/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java
index e09ffb062022263681148d93d7897feb4cc7e41b..1c6ad2e033b311c8c85c4cac37341f81a745c12c 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java
@@ -51,7 +51,14 @@ public final class ProjectileUtil {
vec3 = hitResult.getLocation();
}
- HitResult hitResult2 = getEntityHitResult(world, entity, pos, vec3, entity.getBoundingBox().expandTowards(velocity).inflate(1.0), predicate, margin);
+ // Sakura start - configure potion mechanics
+ final HitResult hitResult2;
+ if (world.sakuraConfig().entity.thrownPotion.allowBreakingInsideEntities && entity instanceof ThrownPotion) {
+ hitResult2 = getEntityHitResult(entity, pos, vec3, entity.getBoundingBox().expandTowards(velocity).inflate(1.0), predicate, margin);
+ } else {
+ hitResult2 = getEntityHitResult(world, entity, pos, vec3, entity.getBoundingBox().expandTowards(velocity).inflate(1.0), predicate, margin);
+ }
+ // Sakura end - configure potion mechanics
if (hitResult2 != null) {
hitResult = hitResult2;
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
index 9d79b193fe2a737a20d1709548b2cd6c454ff27b..df3f8d8fcdd23cc155d3bb156a8e6290497cb060 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
@@ -64,6 +64,25 @@ public class ThrownPotion extends ThrowableItemProjectile {
public ThrownPotion(Level world, double x, double y, double z, ItemStack stack) {
super(EntityType.POTION, x, y, z, world, stack);
}
+
+ // Sakura start - configure potion mechanics
+ @Override
+ public void shoot(double x, double y, double z, float speed, float divergence) {
+ super.shoot(x, y, z, speed, divergence);
+
+ net.minecraft.world.phys.Vec3 movement = this.getDeltaMovement();
+ double moveX = movement.x * this.level().sakuraConfig().entity.thrownPotion.horizontalSpeed;
+ double moveY = movement.y * this.level().sakuraConfig().entity.thrownPotion.verticalSpeed;
+ double moveZ = movement.z * this.level().sakuraConfig().entity.thrownPotion.horizontalSpeed;
+
+ this.setDeltaMovement(moveX, moveY, moveZ);
+ }
+
+ @Override
+ protected boolean checkLeftOwner() {
+ return super.checkLeftOwner() || this.level().sakuraConfig().entity.thrownPotion.allowBreakingInsideEntities && this.tickCount >= 5;
+ }
+ // Sakura end - configure potion mechanics
@Override
protected Item getDefaultItem() {

View File

@@ -0,0 +1,48 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 14 Mar 2024 18:13:24 +0000
Subject: [PATCH] Add outline colliison to enderpearls
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java
index c309198d092fdae6bdcc5d773b7b707bab2738bd..b0e15d97514da292a97d0bbfd0c522fa8b57ab0f 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java
@@ -44,12 +44,18 @@ public abstract class ThrowableProjectile extends Projectile {
return true;
}
+ // Sakura start - enderpearls use outline for collision
+ protected net.minecraft.world.level.ClipContext.Block getClipType() {
+ return net.minecraft.world.level.ClipContext.Block.COLLIDER;
+ }
+ // Sakura end - enderpearls use outline for collision
+
@Override
public void tick() {
this.handleFirstTickBubbleColumn();
this.applyGravity();
this.applyInertia();
- HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+ HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType()); // Sakura - enderpearls use outline for collision
Vec3 vec3d;
if (movingobjectposition.getType() != HitResult.Type.MISS) {
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
index bd2684528157f928460f2143dd71a48e11983123..59e55f1cd9a1bcfee657ec355007b1894301348c 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
@@ -210,6 +210,13 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
}
}
+ // Sakura start - enderpearls use outline for collision
+ @Override
+ protected net.minecraft.world.level.ClipContext.Block getClipType() {
+ return this.level().sakuraConfig().entity.enderPearl.useOutlineForCollision ? net.minecraft.world.level.ClipContext.Block.OUTLINE : super.getClipType();
+ }
+ // Sakura end - enderpearls use outline for collision
+
@Override
public void tick() {
int i;

View File

@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 14 Mar 2024 19:51:20 +0000
Subject: [PATCH] Disable player poses shrinking collision box
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index b439c196db1af082d5ebcd6df59c436396dc144e..f14759c18f760165dfad670049c880c01adb96d4 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -2249,7 +2249,13 @@ public abstract class Player extends LivingEntity {
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
- return (EntityDimensions) Player.POSES.getOrDefault(pose, Player.STANDING_DIMENSIONS);
+ // Sakura start - player poses shrink collision box
+ EntityDimensions dimensions = (EntityDimensions) Player.POSES.getOrDefault(pose, Player.STANDING_DIMENSIONS);
+ if (!level().sakuraConfig().players.posesShrinkCollisionBox && dimensions.height() == 0.6f) {
+ dimensions = Player.STANDING_DIMENSIONS;
+ }
+ return dimensions;
+ // Sakura end - player poses shrink collision box
}
@Override

View File

@@ -0,0 +1,46 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 28 Mar 2024 15:44:33 +0000
Subject: [PATCH] Mob spawner behaviour
diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java
index 90c87fabab274f6202a92150c4f1d5bfe9f5dad8..4bc3a51116f95e924ccc5187a1ad7674ac3a97fc 100644
--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java
+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java
@@ -68,7 +68,7 @@ public abstract class BaseSpawner {
}
public boolean isNearPlayer(Level world, BlockPos pos) {
- return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API
+ return !world.sakuraConfig().environment.mobSpawner.requireNearbyPlayer || world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Sakura - mob spawner behaviour // Paper - Affects Spawning API
}
public void clientTick(Level world, BlockPos pos) {
@@ -139,7 +139,7 @@ public abstract class BaseSpawner {
if (!mobspawnerdata_a.isValidPosition(blockposition1, world)) {
continue;
}
- } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, EntitySpawnReason.SPAWNER, blockposition1, world.getRandom())) {
+ } else if (world.sakuraConfig().environment.mobSpawner.checkSpawnConditions && !SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, EntitySpawnReason.SPAWNER, blockposition1, world.getRandom())) { // Sakura - mob spawner behaviour
continue;
}
// Paper start - PreCreatureSpawnEvent
@@ -167,7 +167,7 @@ public abstract class BaseSpawner {
return;
}
- int k = world.getEntities(EntityTypeTest.forExactClass(entity.getClass()), (new AABB((double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 1), (double) (pos.getZ() + 1))).inflate((double) this.spawnRange), EntitySelector.NO_SPECTATORS).size();
+ int k = world.sakuraConfig().environment.mobSpawner.ignoreEntityLimit ? 0 : world.getEntities(EntityTypeTest.forExactClass(entity.getClass()), (new AABB((double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 1), (double) (pos.getZ() + 1))).inflate((double) this.spawnRange), EntitySelector.NO_SPECTATORS).size(); // Sakura - mob spawner behaviour
if (k >= this.maxNearbyEntities) {
this.delay(world, pos);
@@ -179,7 +179,7 @@ public abstract class BaseSpawner {
if (entity instanceof Mob) {
Mob entityinsentient = (Mob) entity;
- if (mobspawnerdata.getCustomSpawnRules().isEmpty() && !entityinsentient.checkSpawnRules(world, EntitySpawnReason.SPAWNER) || !entityinsentient.checkSpawnObstruction(world)) {
+ if (world.sakuraConfig().environment.mobSpawner.checkSpawnConditions && (mobspawnerdata.getCustomSpawnRules().isEmpty() && !entityinsentient.checkSpawnRules(world, EntitySpawnReason.SPAWNER) || !entityinsentient.checkSpawnObstruction(world))) { // Sakura - mob spawner behaviour
continue;
}

View File

@@ -0,0 +1,75 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 16 May 2024 21:20:52 +0100
Subject: [PATCH] Use random chance for crop growth instead of age
diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
index c045b1cccf0047dbef8c04d5a28d31d53389054f..02da342fa002af134a75a9d2046e43808b76fbcc 100644
--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
@@ -53,6 +53,19 @@ public class CactusBlock extends Block {
@Override
protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ // Sakura start - use random chance for crop growth
+ if (world.sakuraConfig().environment.crops.useRandomChanceToGrow) {
+ int modifier = world.spigotConfig.cactusModifier;
+ if (random.nextFloat() >= modifier / (100.0f * 16)) {
+ return;
+ }
+ // set crop age to max so it grows right away
+ state = state.setValue(CactusBlock.AGE, AGE.max);
+ }
+ this.ageAndGrow(state, world, pos, random);
+ }
+ private void ageAndGrow(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ // Sakura end - use random chance for crop growth
BlockPos blockposition1 = pos.above();
if (world.isEmptyBlock(blockposition1)) {
@@ -69,7 +82,11 @@ public class CactusBlock extends Block {
if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution
CraftEventFactory.handleBlockGrowEvent(world, blockposition1, this.defaultBlockState()); // CraftBukkit
BlockState iblockdata1 = (BlockState) state.setValue(CactusBlock.AGE, 0);
-
+ // Sakura start - use random chance for crop growth
+ if (world.sakuraConfig().environment.crops.useRandomChanceToGrow) {
+ world.neighborShapeChanged(Direction.UP, blockposition1, pos, state, 4, 1);
+ }
+ // Sakura end - use random chance for crop growth
world.setBlock(pos, iblockdata1, 4);
world.neighborChanged(iblockdata1, blockposition1, this, (Orientation) null, false);
} else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
index 547ea09ed84595286c97c128b3b96f6d387ae25f..3db03582e710f87c176ec47d9cab901d27db2e5e 100644
--- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
@@ -52,6 +52,19 @@ public class SugarCaneBlock extends Block {
@Override
protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ // Sakura start - use random chance for crop growth
+ if (world.sakuraConfig().environment.crops.useRandomChanceToGrow) {
+ int modifier = world.spigotConfig.caneModifier;
+ if (random.nextFloat() >= modifier / (100.0f * 16)) {
+ return;
+ }
+ // set crop age to max so it grows right away
+ state = state.setValue(SugarCaneBlock.AGE, AGE.max);
+ }
+ this.ageAndGrow(state, world, pos, random);
+ }
+ private void ageAndGrow(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ // Sakura end - use random chance for crop growth
if (world.isEmptyBlock(pos.above())) {
int i;
@@ -66,6 +79,7 @@ public class SugarCaneBlock extends Block {
if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos.above(), this.defaultBlockState()); // CraftBukkit
world.setBlock(pos, (BlockState) state.setValue(SugarCaneBlock.AGE, 0), 4);
+ // Sakura - conflict on change
} else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
world.setBlock(pos, (BlockState) state.setValue(SugarCaneBlock.AGE, j + 1), 4);
}

View File

@@ -0,0 +1,83 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Wed, 22 May 2024 23:40:02 +0100
Subject: [PATCH] Protect block shapes against plugins
diff --git a/src/main/java/net/minecraft/world/level/block/CarpetBlock.java b/src/main/java/net/minecraft/world/level/block/CarpetBlock.java
index fe2d445a62fbd25d8dbbfad781bbf5a30e30175b..6b8e5ab4cf96865d608483e573d7bb809ac0b18a 100644
--- a/src/main/java/net/minecraft/world/level/block/CarpetBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CarpetBlock.java
@@ -15,6 +15,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
public class CarpetBlock extends Block {
public static final MapCodec<CarpetBlock> CODEC = simpleCodec(CarpetBlock::new);
protected static final VoxelShape SHAPE = Block.box(0.0, 0.0, 0.0, 16.0, 1.0, 16.0);
+ private static final VoxelShape SHAPE_COPY = SHAPE.copy(); // Sakura - protect block shapes against plugins
@Override
public MapCodec<? extends CarpetBlock> codec() {
@@ -27,7 +28,7 @@ public class CarpetBlock extends Block {
@Override
protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
- return SHAPE;
+ return SHAPE_COPY; // Sakura - protect block shapes against plugins
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/LadderBlock.java b/src/main/java/net/minecraft/world/level/block/LadderBlock.java
index f702c88e96f1f692074dc56f2212230d49db41fb..f022aa201e25da7c5a3c7195662c202cc1b57695 100644
--- a/src/main/java/net/minecraft/world/level/block/LadderBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/LadderBlock.java
@@ -29,6 +29,12 @@ public class LadderBlock extends Block implements SimpleWaterloggedBlock {
protected static final VoxelShape WEST_AABB = Block.box(13.0, 0.0, 0.0, 16.0, 16.0, 16.0);
protected static final VoxelShape SOUTH_AABB = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 3.0);
protected static final VoxelShape NORTH_AABB = Block.box(0.0, 0.0, 13.0, 16.0, 16.0, 16.0);
+ // Sakura start - protect block shapes against plugins
+ private static final VoxelShape EAST_AABB_COPY = EAST_AABB.copy();
+ private static final VoxelShape WEST_AABB_COPY = WEST_AABB.copy();
+ private static final VoxelShape SOUTH_AABB_COPY = SOUTH_AABB.copy();
+ private static final VoxelShape NORTH_AABB_COPY = NORTH_AABB.copy();
+ // Sakura end - protect block shapes against plugins
// Sakura start - physics version api
protected static final VoxelShape LEGACY_EAST_AABB = Block.box(0.0, 0.0, 0.0, 2.0, 16.0, 16.0);
protected static final VoxelShape LEGACY_WEST_AABB = Block.box(14.0, 0.0, 0.0, 16.0, 16.0, 16.0);
@@ -69,14 +75,16 @@ public class LadderBlock extends Block implements SimpleWaterloggedBlock {
// Sakura end - physics version api
switch ((Direction)state.getValue(FACING)) {
case NORTH:
- return NORTH_AABB;
+ // Sakura start - protect block shapes against plugins
+ return NORTH_AABB_COPY;
case SOUTH:
- return SOUTH_AABB;
+ return SOUTH_AABB_COPY;
case WEST:
- return WEST_AABB;
+ return WEST_AABB_COPY;
case EAST:
default:
- return EAST_AABB;
+ return EAST_AABB_COPY;
+ // Sakura end - protect block shapes against plugins
}
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
index 3f8e7e29c3e52211a29e6f0a32890f6b53bfd9a8..21210877f903323fbff961e9dd7f6eafb6f0a746 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
@@ -557,6 +557,13 @@ public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.coll
return this.isEmpty; // Paper - optimise collisions
}
+ // Sakura start - protect block shapes against plugins
+ public final VoxelShape copy() {
+ this.cachedToAABBs = null;
+ return this.move(Vec3.ZERO);
+ }
+ // Sakura end - protect block shapes against plugins
+
public VoxelShape move(Vec3 vec3d) {
return this.move(vec3d.x, vec3d.y, vec3d.z);
}

View File

@@ -0,0 +1,258 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 23 Feb 2024 01:48:08 +0000
Subject: [PATCH] Legacy player combat mechanics
diff --git a/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java b/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0aa768656081fac2c87ff573b61584dc4c1a9a3
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java
@@ -0,0 +1,64 @@
+package me.samsuik.sakura.player.combat;
+
+import net.minecraft.core.Holder;
+import net.minecraft.core.HolderLookup;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.ai.attributes.AttributeModifier;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.item.*;
+import net.minecraft.world.item.component.ItemAttributeModifiers;
+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 java.util.OptionalDouble;
+
+public final class CombatUtil {
+ public static double getLegacyAttackDifference(ItemStack itemstack) {
+ 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()) {
+ if (!entry.slot().test(EquipmentSlot.MAINHAND) || !entry.attribute().is(Attributes.ATTACK_DAMAGE))
+ continue;
+ if (entry.modifier().operation() != AttributeModifier.Operation.ADD_VALUE)
+ return 0;
+ baseAttack += entry.modifier().amount();
+ }
+
+ OptionalDouble legacyAttack = LegacyDamageMapping.itemAttackDamage(itemstack.getItem());
+ if (baseAttack != 0.0 && legacyAttack.isPresent()) {
+ return legacyAttack.getAsDouble() - baseAttack;
+ }
+ }
+ return 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();
+
+ if (entity.level() instanceof ServerLevel level) {
+ enchantment.value().modifyDamage(level, enchantmentLevel, itemstack, entity, damageSource, damage);
+ }
+ // legacy - modern
+ 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);
+ return enchantments.getOrThrow(enchantmentKey);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java b/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4ab8f172d713204bb9c1ebf575dcc28bd7dd73e
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java
@@ -0,0 +1,64 @@
+package me.samsuik.sakura.player.combat;
+
+import it.unimi.dsi.fastutil.objects.Reference2DoubleMap;
+import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.item.*;
+import net.minecraft.world.item.component.ItemAttributeModifiers;
+
+import java.util.OptionalDouble;
+
+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);
+ return result == Double.MIN_VALUE ? OptionalDouble.empty() : OptionalDouble.of(result);
+ }
+
+ private static double adjustDamageForItem(Item item, double attackDamage) {
+ return switch (item) {
+ case SwordItem i -> 1.0;
+ case PickaxeItem i -> 1.0;
+ case ShovelItem i -> -0.5;
+ case HoeItem i -> -attackDamage;
+ case null, default -> 0.0;
+ };
+ }
+
+ static {
+ LEGACY_ITEM_DAMAGE_MAP.defaultReturnValue(Double.MIN_VALUE);
+
+ // tool material is no longer exposed
+ LEGACY_ITEM_DAMAGE_MAP.put(Items.WOODEN_AXE, 3.0);
+ LEGACY_ITEM_DAMAGE_MAP.put(Items.GOLDEN_AXE, 3.0);
+ LEGACY_ITEM_DAMAGE_MAP.put(Items.STONE_AXE, 4.0);
+ LEGACY_ITEM_DAMAGE_MAP.put(Items.IRON_AXE, 5.0);
+ LEGACY_ITEM_DAMAGE_MAP.put(Items.DIAMOND_AXE, 6.0);
+ LEGACY_ITEM_DAMAGE_MAP.put(Items.NETHERITE_AXE, 7.0);
+
+ for (Item item : BuiltInRegistries.ITEM) {
+ ItemAttributeModifiers modifiers = item.components().get(DataComponents.ATTRIBUTE_MODIFIERS);
+
+ if (modifiers == null || LEGACY_ITEM_DAMAGE_MAP.containsKey(item)) {
+ continue;
+ }
+
+ assert item instanceof AxeItem : "missing axe mapping";
+
+ 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 = adjustDamageForItem(item, attackDamage);
+ LEGACY_ITEM_DAMAGE_MAP.put(item, attackDamage + adjustment);
+ }
+ }
+ }
+
+ private LegacyDamageMapping() {}
+}
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 1bd9f31da1ec66259dc6391448e2b8ce69ddb817..16e6bd46e54cba9e0ef39a488cefcc5e49476403 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -308,6 +308,43 @@ public abstract class LivingEntity extends Entity implements Attackable {
++this.noActionTime; // Above all the floats
}
// Spigot end
+ // Sakura start - legacy combat mechanics
+ private static final ResourceLocation LEGACY_COMBAT_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("sakura", "legacy_combat");
+ private static final AttributeModifier LEGACY_ATTACK_SPEED_MODIFIER = new AttributeModifier(LEGACY_COMBAT_MODIFIER_ID, 100.0, AttributeModifier.Operation.ADD_VALUE);
+
+ private void updateAttackSpeedModifier() {
+ AttributeInstance attackSpeed = this.getAttribute(Attributes.ATTACK_SPEED);
+ if (attackSpeed != null) {
+ attackSpeed.removeModifier(LEGACY_ATTACK_SPEED_MODIFIER);
+
+ if (this.level().sakuraConfig().players.combat.legacyCombatMechanics) {
+ attackSpeed.addTransientModifier(LEGACY_ATTACK_SPEED_MODIFIER);
+ }
+ }
+ }
+
+ protected final float getAttackDamageFromAttributes() {
+ AttributeInstance attackDamage = this.getAttribute(Attributes.ATTACK_DAMAGE);
+ AttributeModifier legacyModifier = null;
+
+ if (this.level().sakuraConfig().players.combat.legacyCombatMechanics) {
+ ItemStack heldItem = this.getLastHandItem(EquipmentSlot.MAINHAND);
+ double attackDifference = me.samsuik.sakura.player.combat.CombatUtil.getLegacyAttackDifference(heldItem);
+ legacyModifier = new AttributeModifier(LEGACY_COMBAT_MODIFIER_ID, attackDifference, AttributeModifier.Operation.ADD_VALUE);
+ }
+
+ final double damage;
+ if (attackDamage == null || legacyModifier == null) {
+ damage = this.getAttributeValue(Attributes.ATTACK_DAMAGE);
+ } else {
+ attackDamage.addTransientModifier(legacyModifier);
+ damage = this.getAttributeValue(Attributes.ATTACK_DAMAGE);
+ attackDamage.removeModifier(legacyModifier);
+ }
+
+ return (float) damage;
+ }
+ // Sakura end - legacy combat mechanics
protected LivingEntity(EntityType<? extends LivingEntity> type, Level world) {
super(type, world);
@@ -2302,7 +2339,16 @@ public abstract class LivingEntity extends Entity implements Attackable {
protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) {
if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) {
// this.hurtArmor(damagesource, f); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage
+ // Sakura start - legacy combat mechanics
+ if (!this.level().sakuraConfig().players.combat.legacyCombatMechanics) {
amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
+ } else {
+ // See: applyArmorModifier(DamageSource, float)
+ int i = 25 - this.getArmorValue();
+ float f1 = amount * (float) i;
+ amount = f1 / 25.0F;
+ }
+ // Sakura end - legacy combat mechanics
}
return amount;
@@ -3492,6 +3538,11 @@ public abstract class LivingEntity extends Entity implements Attackable {
EnchantmentHelper.runLocationChangedEffects(worldserver, itemstack, this, enumitemslot1);
}
+ // Sakura start - legacy combat mechanics
+ if (this instanceof ServerPlayer && enumitemslot1 == EquipmentSlot.MAINHAND) {
+ this.updateAttackSpeedModifier();
+ }
+ // Sakura end - legacy combat mechanics
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index f14759c18f760165dfad670049c880c01adb96d4..c6e728aec98c1abb55abc7bb31ae3604bd187374 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -1243,14 +1243,20 @@ public abstract class Player extends LivingEntity {
if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable.
{
// Paper end - PlayerAttackEntityEvent
- float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE);
+ float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : this.getAttackDamageFromAttributes(); // Sakura - legacy combat mechanics
ItemStack itemstack = this.getWeaponItem();
DamageSource damagesource = (DamageSource) Optional.ofNullable(itemstack.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this));
float f1 = this.getEnchantedDamage(target, f, damagesource) - f;
float f2 = this.getAttackStrengthScale(0.5F);
+ // Sakura start - legacy combat mechanics
+ if (!this.level().sakuraConfig().players.combat.legacyCombatMechanics) {
f *= 0.2F + f2 * f2 * 0.8F;
f1 *= f2;
+ } else if (f1 != 0.0) {
+ f1 += me.samsuik.sakura.player.combat.CombatUtil.calculateLegacySharpnessDamage(this, itemstack, damagesource);
+ }
+ // Sakura end - legacy combat mechanics
// this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt
if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && target instanceof Projectile) {
Projectile iprojectile = (Projectile) target;
@@ -1278,7 +1284,7 @@ public abstract class Player extends LivingEntity {
}
f += itemstack.getItem().getAttackDamageBonus(target, f, damagesource);
- boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && !this.isSprinting();
+ boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && (this.level().sakuraConfig().players.combat.legacyCombatMechanics || !this.isSprinting()); // Sakura - legacy combat mechanics
flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
if (flag2) {

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 23 Feb 2024 01:49:20 +0000
Subject: [PATCH] Allow disabling sweep attacks
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index c6e728aec98c1abb55abc7bb31ae3604bd187374..23c66dc1bba4f4242c848121f70879bb94f0fb4b 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -1351,7 +1351,7 @@ public abstract class Player extends LivingEntity {
LivingEntity entityliving2;
- if (flag3) {
+ if (flag3 && this.level().sakuraConfig().players.combat.allowSweepAttacks) { // Sakura - allow disabling sweep attacks
float f6 = 1.0F + (float) this.getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * f;
List<LivingEntity> list = this.level().getEntitiesOfClass(LivingEntity.class, target.getBoundingBox().inflate(1.0D, 0.25D, 1.0D));
Iterator iterator = list.iterator();

View File

@@ -0,0 +1,24 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 23 Feb 2024 02:07:03 +0000
Subject: [PATCH] Change shields to reduce damage
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 16e6bd46e54cba9e0ef39a488cefcc5e49476403..8dab67d53a058b62d73e009e378d6376dae70075 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -2444,7 +2444,13 @@ public abstract class LivingEntity extends Entity implements Attackable {
com.google.common.base.Function<Double, Double> blocking = new com.google.common.base.Function<Double, Double>() {
@Override
public Double apply(Double f) {
+ // Sakura start - shield damage reduction
+ if (!level().sakuraConfig().players.combat.shieldDamageReduction || damagesource.getDirectEntity() instanceof AbstractArrow) {
return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0);
+ } else {
+ return -(LivingEntity.this.isBlocking() ? f * 0.5 : 0.0);
+ }
+ // Sakura end - shield damage reduction
}
};
float blockingModifier = blocking.apply((double) f).floatValue();

View File

@@ -0,0 +1,88 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 23 Feb 2024 14:40:32 +0000
Subject: [PATCH] Old enchanted golden apples
diff --git a/src/main/java/me/samsuik/sakura/player/combat/CustomGoldenApple.java b/src/main/java/me/samsuik/sakura/player/combat/CustomGoldenApple.java
new file mode 100644
index 0000000000000000000000000000000000000000..18cb28a9bdfcb9a421bc001f057b4be54c1550be
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/combat/CustomGoldenApple.java
@@ -0,0 +1,64 @@
+package me.samsuik.sakura.player.combat;
+
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.effect.MobEffects;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.component.Consumable;
+import net.minecraft.world.item.component.Consumables;
+import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect;
+import net.minecraft.world.level.Level;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.List;
+import java.util.Optional;
+
+@NullMarked
+@SuppressWarnings("OptionalAssignedToNull")
+public final class CustomGoldenApple extends Item {
+ private static final Consumable LEGACY_ENCHANTED_GOLDEN_APPLE = Consumables.defaultFood()
+ .onConsume(
+ new ApplyStatusEffectsConsumeEffect(
+ List.of(
+ new MobEffectInstance(MobEffects.REGENERATION, 600, 4),
+ new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, 6000, 0),
+ new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 6000, 0),
+ new MobEffectInstance(MobEffects.ABSORPTION, 2400, 0)
+ )
+ )
+ )
+ .build();
+
+ public CustomGoldenApple(Properties settings) {
+ super(settings);
+ }
+
+ @Override
+ public InteractionResult use(Level level, Player player, InteractionHand hand) {
+ ItemStack stack = player.getItemInHand(hand);
+ if (this.itemHasConsumableComponent(stack, level)) {
+ return super.use(level, player, hand);
+ } else {
+ return LEGACY_ENCHANTED_GOLDEN_APPLE.startConsuming(player, stack, hand);
+ }
+ }
+
+ @Override
+ public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity entity) {
+ if (this.itemHasConsumableComponent(stack, level)) {
+ return super.finishUsingItem(stack, level, entity);
+ } else {
+ return LEGACY_ENCHANTED_GOLDEN_APPLE.onConsume(level, entity, stack);
+ }
+ }
+
+ private boolean itemHasConsumableComponent(ItemStack stack, Level level) {
+ Optional<?> consumable = stack.getComponentsPatch().get(DataComponents.CONSUMABLE);
+ return consumable != null || !level.sakuraConfig().players.combat.oldEnchantedGoldenApple;
+ }
+}
diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java
index 6d16b4433e79eca0ff8008941f0b9b807b1db9db..5aeb780535d6ab8010e544cade33fa2b20f9068c 100644
--- a/src/main/java/net/minecraft/world/item/Items.java
+++ b/src/main/java/net/minecraft/world/item/Items.java
@@ -1183,6 +1183,7 @@ public class Items {
public static final Item GOLDEN_APPLE = registerItem("golden_apple", new Item.Properties().food(Foods.GOLDEN_APPLE, Consumables.GOLDEN_APPLE));
public static final Item ENCHANTED_GOLDEN_APPLE = registerItem(
"enchanted_golden_apple",
+ me.samsuik.sakura.player.combat.CustomGoldenApple::new, // Sakura - old enchanted golden apples
new Item.Properties()
.rarity(Rarity.RARE)
.food(Foods.ENCHANTED_GOLDEN_APPLE, Consumables.ENCHANTED_GOLDEN_APPLE)

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 6 Jun 2024 18:18:28 +0100
Subject: [PATCH] Configure fast health regen
diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java
index 6a686be6a69ae890d519a54ca099d4ba14e5b9e1..bb8dcd7e307c033806de392fecf41a235c7d7765 100644
--- a/src/main/java/net/minecraft/world/food/FoodData.java
+++ b/src/main/java/net/minecraft/world/food/FoodData.java
@@ -74,7 +74,7 @@ public class FoodData {
boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION);
- if (flag && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) {
+ if (flag && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20 && player.level().sakuraConfig().players.combat.fastHealthRegen) { // Sakura - configure fast health regen
++this.tickTimer;
if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit
float f = Math.min(this.saturationLevel, 6.0F);

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 6 Jun 2024 20:34:29 +0100
Subject: [PATCH] Add option for fishing hooks pulling entities
diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
index c563f71fb79990771d032b8044b8f676d8d21058..651a1d6313df92258d9af00ceb57142ebc40fee5 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
@@ -621,7 +621,7 @@ public class FishingHook extends Projectile {
public void pullEntity(Entity entity) {
Entity entity1 = this.getOwner();
- if (entity1 != null) {
+ if (entity1 != null && (this.level().sakuraConfig().players.fishingHooksPullEntities || entity instanceof ItemEntity)) { // Sakura - Add option for fishing hooks pulling entities
Vec3 vec3d = (new Vec3(entity1.getX() - this.getX(), entity1.getY() - this.getY(), entity1.getZ() - this.getZ())).scale(0.1D);
entity.setDeltaMovement(entity.getDeltaMovement().add(vec3d));

View File

@@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 11 Jun 2024 13:37:51 +0100
Subject: [PATCH] Old combat sounds and particle effects
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index 23c66dc1bba4f4242c848121f70879bb94f0fb4b..e0cb3a9d0cd27a98e302816bc736db291a32cf4f 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -1461,7 +1461,7 @@ public abstract class Player extends LivingEntity {
float f8 = f4 - ((LivingEntity) target).getHealth();
this.awardStat(Stats.DAMAGE_DEALT, Math.round(f8 * 10.0F));
- if (this.level() instanceof ServerLevel && f8 > 2.0F) {
+ if (this.level() instanceof ServerLevel && f8 > 2.0F && !this.level().sakuraConfig().players.combat.oldSoundsAndParticleEffects) { // Sakura - old combat sounds and particles
int i = (int) ((double) f8 * 0.5D);
((ServerLevel) this.level()).sendParticles(ParticleTypes.DAMAGE_INDICATOR, target.getX(), target.getY(0.5D), target.getZ(), i, 0.1D, 0.0D, 0.1D, 0.2D);
@@ -1869,6 +1869,7 @@ public abstract class Player extends LivingEntity {
}
// Paper start - send while respecting visibility
private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) {
+ if (fromEntity.level().sakuraConfig().players.combat.oldSoundsAndParticleEffects) return; // Sakura - old combat sounds and particles
fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity itself
if (fromEntity instanceof ServerPlayer serverPlayer) {
serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong()));

View File

@@ -0,0 +1,57 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 18 Jun 2024 13:34:55 +0100
Subject: [PATCH] Entity tracking range modifier
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 274b3a5e1d4606ed47ba7e3e4ee369d607cea8a1..49f8afdaa91e89b899b9fcce1ef9a972ec141b8b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -1326,7 +1326,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
double vec3d_dz = player.getZ() - this.entity.getZ();
// Paper end - remove allocation of Vec3D here
int i = ChunkMap.this.getPlayerViewDistance(player);
- double d0 = (double) Math.min(this.getEffectiveRange(), i * 16);
+ // Sakura start - entity tracking range modifier
+ double visibleRange = (double) this.getEffectiveRange() * player.trackingRangeModifier;
+ double d0 = (double) Math.min(visibleRange, i * 16);
+ // Sakura end - entity tracking range modifier
double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
double d2 = d0 * d0;
// Paper start - Configurable entity tracking range by Y
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 2a9659158d39d4d5505328afd7a2d8dc9ecf456f..5f7945c852ef9eb0d1183cc0ce33db5a82edee56 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -358,6 +358,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
}
// Paper end - rewrite chunk system
public final me.samsuik.sakura.player.visibility.PlayerVisibilitySettings visibilitySettings = new me.samsuik.sakura.player.visibility.PlayerVisibilitySettings(); // Sakura - client visibility settings
+ public double trackingRangeModifier = 1.0; // Sakura - entity tracking range modifier
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 5455f78d4417f3f8b2e4820619ad24d91054d986..c01abfb8746c1280b9c48d22a5c7a33d5ba1cd61 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -3033,6 +3033,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return (this.getHandle().requestedViewDistance() == 0) ? Bukkit.getViewDistance() : this.getHandle().requestedViewDistance();
}
+ // Sakura start - entity tracking range modifier
+ @Override
+ public double getTrackingRangeModifier() {
+ return this.getHandle().trackingRangeModifier * 100.0;
+ }
+
+ @Override
+ public void setTrackingRangeModifier(double mod) {
+ this.getHandle().trackingRangeModifier = mod / 100.0;
+ }
+ // Sakura end - entity tracking range modifier
+
// Paper start
@Override
public java.util.Locale locale() {

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 29 Jul 2024 00:18:58 +0100
Subject: [PATCH] Set entity impulse on explosion
diff --git a/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java b/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java
index c916d3037bd5920ec06213a9162223a124428d6b..fd98f36ca2fd7e0b5961fd89aa976dbfc7df93b8 100644
--- a/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java
+++ b/src/main/java/me/samsuik/sakura/explosion/special/SpecialisedExplosion.java
@@ -204,6 +204,7 @@ public abstract class SpecialisedExplosion<T extends Entity> extends ServerExplo
moveZ += z;
}
+ entity.hasImpulse = true; // Sakura - set entity impulse on explosion
entity.setDeltaMovement(moveX, moveY, moveZ);
}
}
diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
index 8ddcbb5f5ea867d3bce207ba25f93a940f78a93b..06cbe3ac6adf0d91a185e365a056b29ee54380e6 100644
--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
@@ -748,7 +748,12 @@ public class ServerExplosion implements Explosion {
// Paper end - knockback events
}
// CraftBukkit end
- entity.push(vec3d);
+ // Sakura start - set entity impulse on explosion
+ // Wait for upstream to change the push method to be more sane.
+ // entity.push(vec3d);
+ entity.hasImpulse = true;
+ entity.setDeltaMovement(entity.getDeltaMovement().add(vec3d));
+ // Sakura end - set entity impulse on explosion
if (entity instanceof Player) {
Player entityhuman = (Player) entity;

View File

@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Wed, 2 Oct 2024 18:45:22 +0100
Subject: [PATCH] Add max armour durability damage
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 8dab67d53a058b62d73e009e378d6376dae70075..a32c4dac9c2a25f52ca39c8dcf7d72acc7ad7f8f 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -2546,6 +2546,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
// Apply damage to armor
if (!damagesource.is(DamageTypeTags.BYPASSES_ARMOR)) {
float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
+ // Sakura start - add max armour durability damage
+ int maxArmourDamage = this.level().sakuraConfig().players.combat.maxArmourDamage.or(-1);
+ if (maxArmourDamage >= 0) {
+ armorDamage = Math.min(armorDamage, maxArmourDamage);
+ }
+ // Sakura end - add max armour durability damage
this.hurtArmor(damagesource, armorDamage);
}

View File

@@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 14 Sep 2024 12:04:43 +0100
Subject: [PATCH] Modify bucket stack size
When a player interacts with a bucket this patch adds a data component to change the max stack size.
diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java
index 4cb809976b42af933401e8fc34ee43e181761558..ed417a2f5a2105a25d1c2bd57e37785595738144 100644
--- a/src/main/java/net/minecraft/world/item/BucketItem.java
+++ b/src/main/java/net/minecraft/world/item/BucketItem.java
@@ -49,6 +49,17 @@ public class BucketItem extends Item implements DispensibleContainerItem {
this.content = fluid;
}
+ // Sakura start - modify bucket stack size
+ @Override
+ public void verifyComponentsAfterLoad(ItemStack stack) {
+ me.samsuik.sakura.configuration.GlobalConfiguration config = me.samsuik.sakura.configuration.GlobalConfiguration.get();
+ int customItemSize = config == null || !config.players.bucketStackSize.isDefined() ? -1 : config.players.bucketStackSize.intValue();
+ if (customItemSize > 0 && customItemSize < 100) {
+ stack.set(net.minecraft.core.component.DataComponents.MAX_STACK_SIZE, customItemSize);
+ }
+ }
+ // Sakura end - modify bucket stack size
+
@Override
public InteractionResult use(Level world, Player user, InteractionHand hand) {
ItemStack itemstack = user.getItemInHand(hand);

View File

@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 11 Nov 2024 19:20:44 +0000
Subject: [PATCH] Configure TNT duplication
Adds a configuration option to enable TNT duplication.
diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
index 6146c786730b2cd5e5883acbe19d1eecff68e7e3..53065b89ffcbfbf00fd1f16800e4421e24f93d32 100644
--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
@@ -453,6 +453,11 @@ public class PistonBaseBlock extends DirectionalBlock {
for (j = list.size() - 1; j >= 0; --j) {
// Paper start - fix a variety of piston desync dupes
boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication;
+ // Sakura start - configure tnt duplication
+ if (world.sakuraConfig().technical.allowTNTDuplication && list1.get(j).is(Blocks.TNT)) {
+ allowDesync = true;
+ }
+ // Sakura end - configure tnt duplication
BlockPos oldPos = blockposition3 = (BlockPos) list.get(j);
iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null;
// Paper end - fix a variety of piston desync dupes

View File

@@ -0,0 +1,64 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Thu, 5 Dec 2024 22:21:39 +0000
Subject: [PATCH] Add lava flow speed api
diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
index 6cc129afdb19e121e1abe8dc07f5cc2216c7b084..b4a775c486d6de994591dd6b34055e3a80d9a4d3 100644
--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
@@ -158,7 +158,7 @@ public class LiquidBlock extends Block implements BucketPickup {
return world.paperConfig().environment.waterOverLavaFlowSpeed;
}
}
- return this.fluid.getTickDelay(world);
+ return this.fluid.getTickDelay(world, blockposition); // Sakura - lava flow speed api
}
private static boolean isLava(Level world, BlockPos blockPos) {
final FluidState fluidState = world.getFluidIfLoaded(blockPos);
diff --git a/src/main/java/net/minecraft/world/level/material/Fluid.java b/src/main/java/net/minecraft/world/level/material/Fluid.java
index 69586077f0d9b0eed1a7de7ef1d743048de0c9d5..86afcda1f14aec7ee9c459e935782037cb0c6634 100644
--- a/src/main/java/net/minecraft/world/level/material/Fluid.java
+++ b/src/main/java/net/minecraft/world/level/material/Fluid.java
@@ -69,6 +69,12 @@ public abstract class Fluid {
protected abstract Vec3 getFlow(BlockGetter world, BlockPos pos, FluidState state);
+ // Sakura start - lava flow speed api
+ public int getTickDelay(final Level world, final BlockPos pos) {
+ return this.getTickDelay(world);
+ }
+ // Sakura end - lava flow speed api
+
public abstract int getTickDelay(LevelReader world);
protected boolean isRandomlyTicking() {
diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
index 5fb8ad171e132174af12d63e45dd623ecac7480a..62d1487ee5820a38f033d60f197b476079ea3e51 100644
--- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
@@ -182,6 +182,14 @@ public abstract class LavaFluid extends FlowingFluid {
// Sakura end - physics version api
}
+ // Sakura start - lava flow speed api
+ @Override
+ public final int getTickDelay(Level world, BlockPos pos) {
+ final int flowSpeed = world.localConfig().config(pos).lavaFlowSpeed;
+ return flowSpeed >= 0 ? flowSpeed : this.getTickDelay(world);
+ }
+ // Sakura end - lava flow speed api
+
@Override
public int getTickDelay(LevelReader world) {
return world.dimensionType().ultraWarm() && !(world instanceof Level level && level.sakuraConfig().environment.disableFastNetherLava) ? 10 : 30; // Sakura - add option for fast nether lava
@@ -189,7 +197,7 @@ public abstract class LavaFluid extends FlowingFluid {
@Override
public int getSpreadDelay(Level world, BlockPos pos, FluidState oldState, FluidState newState) {
- int i = this.getTickDelay(world);
+ int i = this.getTickDelay(world, pos); // Sakura - lava flow speed api
if (!oldState.isEmpty() && !newState.isEmpty() && !(Boolean) oldState.getValue(LavaFluid.FALLING) && !(Boolean) newState.getValue(LavaFluid.FALLING) && newState.getHeight(world, pos) > oldState.getHeight(world, pos) && world.getRandom().nextInt(4) != 0) {
i *= 4;

View File

@@ -0,0 +1,65 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MiniDigger <admin@minidigger.me>
Date: Sat, 12 Jun 2021 16:40:34 +0200
Subject: [PATCH] Branding changes
From ForkPaper.
diff --git a/build.gradle.kts b/build.gradle.kts
index 2da91ed6363c0851e4c459188f5e8ef5475e0c97..478101cf841aebfce6d1e2422b96a080a3e56848 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -25,7 +25,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
// Paper end - configure mockito agent that is needed in newer java versions
dependencies {
- implementation(project(":paper-api"))
+ implementation(project(":sakura-api")) // Sakura
implementation("ca.spottedleaf:concurrentutil:0.0.2") // Paper - Add ConcurrentUtil dependency
// Paper start
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
@@ -100,14 +100,14 @@ tasks.jar {
val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
- "Implementation-Title" to "Paper",
+ "Implementation-Title" to "Sakura", // Sakura
"Implementation-Version" to implementationVersion,
"Implementation-Vendor" to date, // Paper
- "Specification-Title" to "Paper",
+ "Specification-Title" to "Sakura", // Sakura
"Specification-Version" to project.version,
"Specification-Vendor" to "Paper Team",
- "Brand-Id" to "papermc:paper",
- "Brand-Name" to "Paper",
+ "Brand-Id" to "samsuik:sakura", // Sakura
+ "Brand-Name" to "Sakura", // Sakura
"Build-Number" to (build ?: ""),
"Build-Time" to Instant.now().toString(),
"Git-Branch" to gitBranch, // Paper
diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java
index 790bad0494454ca12ee152e3de6da3da634d9b20..57f9545e7d94365a1db175ef463fd71dcf508bfd 100644
--- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java
+++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java
@@ -61,7 +61,7 @@ public record ServerBuildInfoImpl(
@Override
public boolean isBrandCompatible(final @NotNull Key brandId) {
- return brandId.equals(this.brandId);
+ return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID); // Sakura
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
index 774556a62eb240da42e84db4502e2ed43495be17..1941fd2dbdc7a10ddf17e2543a038dbd7fe8c88c 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
@@ -11,7 +11,7 @@ public final class Versioning {
public static String getBukkitVersion() {
String result = "Unknown-Version";
- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties");
+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/me.samsuik.sakura/sakura-api/pom.properties"); // Sakura
Properties properties = new Properties();
if (stream != null) {

View File

@@ -0,0 +1,427 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Thu, 16 Nov 2023 13:38:06 +0000
Subject: [PATCH] Cache Vanillia and Eigen Redstone
diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
index 9f17170179cc99d84ad25a1e838aff3d8cc66f93..a1fb1e286d66dde55682d899ded0e5d24715b642 100644
--- a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
+++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
@@ -662,6 +662,7 @@ public class RedstoneWireTurbo {
// restores old behavior, at the cost of bypassing the
// max-chained-neighbor-updates server property.
worldIn.getBlockState(upd.self).handleNeighborChanged(worldIn, upd.self, wire, upd.parent, false);
+ worldIn.redstoneTracker.trackUpdate(upd.self, upd.currentState, upd.parent); // Sakura
}
}
@@ -777,6 +778,17 @@ public class RedstoneWireTurbo {
// already on-going graph walk being performed by breadthFirstWalk.
return scheduleReentrantNeighborChanged(worldIn, pos, newState, source);
}
+
+ // Sakura start
+ int oldPower = state.getValue(RedStoneWireBlock.POWER);
+ int newPower = newState.getValue(RedStoneWireBlock.POWER);
+ if (worldIn.redstoneTracker.applyFromCache(pos, oldPower, newPower)) {
+ return newState;
+ } else {
+ worldIn.redstoneTracker.beginTracking(pos, oldPower, newPower);
+ }
+ // Sakura end
+
// If there are no on-going walks through redstone wire, then start a new walk.
// If the source of the block update to the redstone wire at 'pos' is known, we can use
@@ -806,6 +818,8 @@ public class RedstoneWireTurbo {
// updates in a breadth first order out from the initial update received for the block at 'pos'.
breadthFirstWalk(worldIn);
+ worldIn.redstoneTracker.endTracking(); // Sakura - we're done with the breadth walk
+
// With the whole search completed, clear the list of all known blocks.
// We do not want to keep around state information that may be changed by other code.
// In theory, we could cache the neighbor block positions, but that is a separate
@@ -913,6 +927,7 @@ public class RedstoneWireTurbo {
// bypass the new neighbor update stack.
if (worldIn.setBlock(upd.self, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS))
updateNeighborShapes(worldIn, upd.self, state);
+ worldIn.redstoneTracker.trackState(upd.self, state); // Sakura
}
}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneTracker.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneTracker.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e322ac7c8751ab4586fcce7974c26dacad18e4a
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneTracker.java
@@ -0,0 +1,285 @@
+package me.samsuik.sakura.redstone;
+
+import it.unimi.dsi.fastutil.HashCommon;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import me.samsuik.sakura.utils.objects.Expiry;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.redstone.InstantNeighborUpdater;
+import net.minecraft.world.level.redstone.NeighborUpdater;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+public final class RedstoneTracker {
+
+ private static final int DEPTH_LIMIT = 512;
+
+ // this is a fucking horrible hack, you could make a method in block
+ // then override it but that is far more work than I am willing right now.
+ private static final Predicate<Block> TECHNICAL_BLOCK = (block) -> {
+ return block instanceof BaseEntityBlock
+ || block instanceof DirectionalBlock
+ || block instanceof HorizontalDirectionalBlock
+ || block instanceof GameMasterBlock
+ || block instanceof BaseRailBlock // rails themselves don't require it but it's variants do
+ || block instanceof NoteBlock
+ || block instanceof DoorBlock
+ || block instanceof RedstoneLampBlock
+ || block instanceof RedstoneTorchBlock
+ || block instanceof RedStoneWireBlock; // required to update any nearby redstone that isn't the line currently being tested
+ };
+
+ private final Level level;
+ private final InstantNeighborUpdater updater;
+ private final Long2ObjectOpenHashMap<RedstoneCache> wireCaches = new Long2ObjectOpenHashMap<>();
+ // important locations, wires and adjacent
+ private final Long2ObjectMap<List<RedstoneCache>> adjacent = new Long2ObjectOpenHashMap<>();
+ // last block of physics
+ private final Long2ObjectMap<List<RedstoneCache>> updates = new Long2ObjectOpenHashMap<>();
+
+ private ConnectedWires tracking;
+ private int depth = 0;
+
+ public RedstoneTracker(Level level) {
+ this.level = level;
+ this.updater = new InstantNeighborUpdater(level);
+ }
+
+ private static long cacheKey(BlockPos position, int previous, int next) {
+ int powerState = 1 + previous + (next * 31); // previous and next only go up to 15
+ long powerHash = HashCommon.murmurHash3((long) powerState); // long for a better hash
+ return position.asLong() ^ powerHash;
+ }
+
+ private boolean stillValid(BlockPos position, BlockState previous, BlockState next) {
+ return previous.getBlock() == next.getBlock() || previous.isSignalSource() == next.isSignalSource()
+ && previous.isRedstoneConductor(this.level, position) == next.isRedstoneConductor(this.level, position);
+ }
+
+ public void updateNeighbors(BlockPos position, Block self) {
+ this.updater.updateNeighborsAtExceptFromFacing(position, self, null);
+ }
+
+ public boolean applyFromCache(BlockPos position, int previous, int next) {
+ if (this.level.localConfig().config(position).redstoneCache && this.depth == 0) {
+ long key = cacheKey(position, previous, next);
+ RedstoneCache cache = this.wireCaches.get(key);
+
+ if (cache != null && !cache.expiry().isExpired(MinecraftServer.currentTick)) {
+ cache.apply(this.level);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void invalidate(BlockPos position, BlockState previous, BlockState next) {
+ if (!this.stillValid(position, previous, next)) {
+ long packed = position.asLong();
+ this.invalidate(this.adjacent.get(packed));
+
+ if (TECHNICAL_BLOCK.test(next.getBlock())) {
+ this.invalidate(this.updates.get(packed));
+ }
+ }
+ }
+
+ private void invalidate(List<RedstoneCache> caches) {
+ if (caches != null) {
+ caches.forEach(cache -> cache.expiry().refresh(-200));
+ }
+ }
+
+ public void trackState(BlockPos position, BlockState state) {
+ if (this.tracking != null) {
+ this.tracking.wireUpdate(position, state);
+ }
+ }
+
+ public void trackUpdates(BlockPos position, Block block) {
+ if (this.tracking != null) {
+ this.tracking.updateNeighbors(position, block);
+ }
+ }
+
+ public void trackUpdate(BlockPos position, BlockState state, BlockPos parent) {
+ if (this.tracking != null) {
+ this.tracking.updates().put(position.asLong(), new UpdateState(state.getBlock(), parent));
+ }
+ }
+
+ public boolean isTracked(BlockPos position) {
+ List<RedstoneCache> wires = this.adjacent.get(position.asLong());
+ return wires != null && wires.stream().anyMatch(cache -> !cache.expiry().isExpired(MinecraftServer.currentTick));
+ }
+
+ public boolean isTracking() {
+ return this.tracking != null && this.depth != 0 && this.depth < DEPTH_LIMIT;
+ }
+
+ public void beginTracking(BlockPos position, int previous, int next) {
+ if (this.level.localConfig().config(position).redstoneCache && ++this.depth == 1) {
+ long key = cacheKey(position, previous, next);
+
+ if (this.wireCaches.containsKey(key)) {
+ return; // cache already exists
+ }
+
+ this.tracking = new ConnectedWires(
+ position, key,
+ new Object2ObjectOpenHashMap<>(),
+ new Long2ObjectLinkedOpenHashMap<>()
+ );
+ }
+ }
+
+ public void endTracking() {
+ if (this.depth > 0 && --this.depth == 0 && this.tracking != null) {
+ this.createRedstoneCache();
+ this.tracking = null;
+ }
+ }
+
+ private void createRedstoneCache() {
+ ConnectedWires connected = this.tracking;
+ List<RedstoneUpdate> updates = new ArrayList<>(connected.updates().size() / 2);
+
+ Long2ObjectMaps.fastForEach(connected.updates(), entry -> {
+ BlockPos position = BlockPos.of(entry.getLongKey());
+ UpdateState update = entry.getValue();
+
+ // do not update wires that are connected
+ if (connected.wires().containsKey(position)) return;
+
+ // filter out blocks that do not need updates
+ BlockState state = this.level.getBlockState(position);
+ if (TECHNICAL_BLOCK.test(state.getBlock())) {
+ updates.add(new RedstoneUpdate(position, state, update));
+ }
+ });
+
+ List<RedstoneWire> wires = new ArrayList<>(connected.wires().size());
+
+ connected.wires().forEach((pos, state) -> {
+ // is it even worth taking the hit here
+ boolean shapeUpdate = updates.stream()
+ .filter(update -> update.state().is(Blocks.OBSERVER))
+ .anyMatch(update -> pos.distManhattan(update.position()) == 1);
+
+ wires.add(new RedstoneWire(pos, state, shapeUpdate));
+ });
+
+ RedstoneCache redstoneCache = new RedstoneCache(
+ wires, updates, connected,
+ new Expiry(MinecraftServer.currentTick, 200)
+ );
+
+ // put the newly created redstone cache into use
+ this.wireCaches.put(connected.key(), redstoneCache);
+
+ // used for cache invalidation
+ connected.updates().keySet().forEach(update -> {
+ BlockPos position = BlockPos.of(update);
+
+ if (wires.stream().anyMatch(wire -> wire.position().distManhattan(position) == 1)) {
+ this.adjacent.computeIfAbsent(update, key -> new ArrayList<>(1))
+ .add(redstoneCache);
+ } else {
+ this.updates.computeIfAbsent(update, key -> new ArrayList<>(1))
+ .add(redstoneCache);
+ }
+ });
+
+ wires.forEach(wire -> {
+ this.adjacent.computeIfAbsent(wire.position().asLong(), key -> new ArrayList<>(1))
+ .add(redstoneCache);
+ });
+ }
+
+ public void expire(int tick) {
+ if (tick % 100 != 0) return;
+
+ this.wireCaches.values().removeIf(cache -> {
+ if (cache.expiry().isExpired(tick)) {
+ removeExpiredCache(cache);
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ private void removeExpiredCache(RedstoneCache cache) {
+ cache.connected().updates().keySet().forEach(update -> {
+ this.removeFromCaches(update, cache);
+ });
+
+ cache.connected().wires().keySet().forEach(wire -> {
+ this.removeFromCaches(wire.asLong(), cache);
+ });
+ }
+
+ private void removeFromCaches(long packed, RedstoneCache cache) {
+ this.removeCache(this.adjacent, packed, cache);
+ this.removeCache(this.updates, packed, cache);
+ }
+
+ private void removeCache(Long2ObjectMap<List<RedstoneCache>> locationMap, long packed, RedstoneCache cache) {
+ List<RedstoneCache> caches = locationMap.get(packed);
+
+ // in case something goes wrong
+ if (caches == null) return;
+
+ caches.remove(cache);
+
+ if (caches.isEmpty()) {
+ locationMap.remove(packed);
+ }
+ }
+
+ private record UpdateState(Block block, BlockPos position) {}
+ private record RedstoneWire(BlockPos position, BlockState state, boolean shapeUpdate) {}
+ private record RedstoneUpdate(BlockPos position, BlockState state, UpdateState parent) {}
+
+ private record RedstoneCache(List<RedstoneWire> wires, List<RedstoneUpdate> updates, ConnectedWires connected, Expiry expiry) {
+ public void apply(Level level) {
+ // Apply cached wire changes
+ wires.forEach(wire -> level.setBlock(wire.position(), wire.state(), wire.shapeUpdate() ? 2 : 18));
+
+ // Now update the neighbors. We have to do this after applying wire changes
+ // in case the block we're affecting here, checks the surrounding power level.
+ updates.forEach(update -> {
+ level.neighborChanged(update.position(), update.parent().block(), update.parent().position());
+ });
+
+ // Refresh so the cache won't expire
+ expiry.refresh(MinecraftServer.currentTick);
+ }
+ }
+
+ private record ConnectedWires(BlockPos source, long key, Object2ObjectMap<BlockPos, BlockState> wires, Long2ObjectMap<UpdateState> updates) {
+ public void updateNeighbors(BlockPos position, Block block) {
+ for (Direction direction : NeighborUpdater.UPDATE_ORDER) {
+ BlockPos neighbor = position.relative(direction);
+ this.updates.put(neighbor.asLong(), new UpdateState(block, position));
+ }
+ }
+
+ public void wireUpdate(BlockPos position, BlockState state) {
+ this.wires.put(position, state);
+ }
+ }
+
+}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 88aa465a4bc6ae79dbad8dc8ecc21345f70abf0b..d82d64c98fc7624dfcc86fcbf49cde108bb34ede 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -698,6 +698,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
public final me.samsuik.sakura.explosion.density.BlockDensityCache densityCache = new me.samsuik.sakura.explosion.density.BlockDensityCache(); // Sakura - explosion density cache
public final me.samsuik.sakura.explosion.durable.DurableBlockManager durabilityManager = new me.samsuik.sakura.explosion.durable.DurableBlockManager(); // Sakura - explosion durable blocks
+ public final me.samsuik.sakura.redstone.RedstoneTracker redstoneTracker = new me.samsuik.sakura.redstone.RedstoneTracker(this); // Sakura - cache vanilla and eigen redstone
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, Supplier<me.samsuik.sakura.configuration.WorldConfiguration> sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files// Paper - create paper world config & Anti-Xray
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
@@ -1077,6 +1078,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
} else {
BlockState iblockdata2 = this.getBlockState(pos);
+ this.redstoneTracker.invalidate(pos, iblockdata1, state); // Sakura
/*
if (iblockdata2 == iblockdata) {
if (iblockdata1 != iblockdata2) {
diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
index c131734cad123a35456d18f8a161f77a4ac9ac99..88ddd1747d9786210e8faf412b3b0363df4bab43 100644
--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -381,7 +381,15 @@ public class RedStoneWireBlock extends Block {
}
if (oldPower != i) {
// CraftBukkit end
+ // Sakura start
+ if (world.redstoneTracker.applyFromCache(pos, oldPower, i)) {
+ return;
+ }
+
+ world.redstoneTracker.beginTracking(pos, oldPower, i);
if (world.getBlockState(pos) == state) {
+ world.redstoneTracker.trackState(pos, state.setValue(RedStoneWireBlock.POWER, i));
+ // Sakura end
world.setBlock(pos, (BlockState) state.setValue(RedStoneWireBlock.POWER, i), 2);
}
@@ -402,8 +410,17 @@ public class RedStoneWireBlock extends Block {
while (iterator.hasNext()) {
BlockPos blockposition1 = (BlockPos) iterator.next();
- world.updateNeighborsAt(blockposition1, this);
+ // Sakura start
+ world.redstoneTracker.trackUpdates(blockposition1, this);
+ if (world.redstoneTracker.isTracking()) {
+ world.redstoneTracker.updateNeighbors(blockposition1, this);
+ } else {
+ world.updateNeighborsAt(blockposition1, this);
+ }
+ // Sakura end
}
+
+ world.redstoneTracker.endTracking(); // Sakura
}
}
@@ -559,7 +576,22 @@ public class RedStoneWireBlock extends Block {
if (this.shouldSignal && direction != Direction.DOWN) {
int i = (Integer) state.getValue(RedStoneWireBlock.POWER);
- return i == 0 ? 0 : (direction != Direction.UP && !((RedstoneSide) this.getConnectionState(world, state, pos).getValue((Property) RedStoneWireBlock.PROPERTY_BY_DIRECTION.get(direction.getOpposite()))).isConnected() ? 0 : i);
+ // Sakura start - use redstone cache to avoid recalculating wires
+ if (i == 0) {
+ return 0;
+ } else if (direction == Direction.UP) {
+ return i;
+ } else {
+ final BlockState wireState;
+ if (world instanceof Level level && level.redstoneTracker.isTracked(pos)) {
+ wireState = state; // cached wire
+ } else {
+ wireState = this.getConnectionState(world, state, pos);
+ }
+ return !((RedstoneSide) wireState.getValue((Property) RedStoneWireBlock.PROPERTY_BY_DIRECTION.get(direction.getOpposite()))).isConnected() ? 0 : i;
+ }
+ //return i == 0 ? 0 : (direction != Direction.UP && !((RedstoneSide) this.getConnectionState(world, state, pos).getValue((Property) RedStoneWireBlock.PROPERTY_BY_DIRECTION.get(direction.getOpposite()))).isConnected() ? 0 : i);
+ // Sakura end - use redstone cache to avoid recalculating wires
} else {
return 0;
}