Files
Purpur/patches/server/0001-Airplane-Server-Changes.patch
William Blake Galbreath 78b0bcc5ef Updated Upstream (Paper & Airplane)
Upstream has released updates that appear to apply and compile correctly

Paper Changes:
2ec6c99152 ci: Update gh actions with caching (#6819) [ci skip]

Airplane Changes:
7a52870a45 Upstream
0afb2b901a Fix entity hard collision issue. (#69)
2021-10-23 16:58:47 -05:00

4385 lines
208 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Initial Source <auto@mated.null>
Date: Fri, 16 Jul 2021 17:11:36 -0500
Subject: [PATCH] Airplane Server Changes
Copyright (C) 2020 Technove LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/build.gradle.kts b/build.gradle.kts
index 13f89bdaf1ada33060b1ee4d6f2860ec194b68a4..0bd09bd56968660437a94ec078a13ee78339fa0e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -25,8 +25,8 @@ repositories {
}
dependencies {
- implementation(project(":Paper-API"))
- implementation(project(":Paper-MojangAPI"))
+ implementation(project(":Airplane-API")) // Airplane // Paper
+ implementation("io.papermc.paper:paper-mojangapi:1.17.1-R0.1-SNAPSHOT") // Airplane
// Paper start
implementation("org.jline:jline-terminal-jansi:3.12.1")
implementation("net.minecrell:terminalconsoleappender:1.2.0")
@@ -61,6 +61,13 @@ dependencies {
implementation("org.quiltmc:tiny-mappings-parser:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation
implementation("com.velocitypowered:velocity-native:1.1.0-SNAPSHOT") // Paper
+ implementation("com.github.technove:AIR:fe3dbb4420") // Airplane - config
+ implementation("org.yaml:snakeyaml:1.28")
+ implementation ("me.carleslc.Simple-YAML:Simple-Yaml:1.7.2") { // Airplane - more config
+ exclude(group="org.yaml", module="snakeyaml") // exclude snakeyaml dependency because its old (1.26)
+ } // Airplane - more config
+ implementation("com.github.technove:Flare:2c4a2114a0") // Airplane - flare
+
testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
testImplementation("junit:junit:4.13.1")
testImplementation("org.hamcrest:hamcrest-library:1.3")
@@ -79,6 +86,7 @@ tasks.jar {
"Main-Class" to "org.bukkit.craftbukkit.Main",
"Implementation-Title" to "CraftBukkit",
"Implementation-Version" to "git-Paper-$implementationVersion",
+ "Implementation-Version" to "git-Airplane-$implementationVersion", // Airplane
"Implementation-Vendor" to date, // Paper
"Specification-Title" to "Bukkit",
"Specification-Version" to project.version,
@@ -136,6 +144,22 @@ relocation {
}
}
+val generateReobfMappings = rootProject.tasks.named<io.papermc.paperweight.tasks.GenerateReobfMappings>("generateReobfMappings")
+
+val patchReobfMappings by tasks.registering<io.papermc.paperweight.tasks.PatchMappings> {
+ inputMappings.set(generateReobfMappings.flatMap { it.reobfMappings })
+ patch.set(rootProject.layout.cache.resolve("paperweight/upstreams/paper/build-data/reobf-mappings-patch.tiny"))
+
+ fromNamespace.set(io.papermc.paperweight.util.constants.DEOBF_NAMESPACE)
+ toNamespace.set(io.papermc.paperweight.util.constants.SPIGOT_NAMESPACE)
+
+ outputMappings.set(layout.cache.resolve("paperweight/mappings/reobf-patched.tiny"))
+}
+
+tasks.reobfJar {
+ mappingsFile.set(patchReobfMappings.flatMap { it.outputMappings })
+}
+
val generatePom = tasks.named<GenerateMavenPom>("generatePomFileForMavenPublication")
tasks.shadowJar {
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
index e29b0a90019b12bd6586ad0f7b5314f307e527ba..6e8001c7ff6497c9e5c274a5fe85cc204f3ba4c5 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -228,7 +228,8 @@ public class TimingsExport extends Thread {
parent.put("config", createObject(
pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Airplane
+ pair("airplane", mapAsJSON(gg.airplane.AirplaneConfig.getConfigCopy(), null)) // Airplane
));
new TimingsExport(listeners, parent, history).start();
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
index 218f5bafeed8551b55b91c7fccaf6935c8b631ca..f5d01bce4d5547b4aeca96b7962b2090f47ea541 100644
--- a/src/main/java/com/destroystokyo/paper/Metrics.java
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
@@ -593,7 +593,7 @@ public class Metrics {
boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
// Only start Metrics, if it's enabled in the config
if (config.getBoolean("enabled", true)) {
- Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger());
+ Metrics metrics = new Metrics("Airplane", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page // Airplane
metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
String minecraftVersion = Bukkit.getVersion();
@@ -603,7 +603,7 @@ public class Metrics {
metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"));
+ metrics.addCustomChart(new Metrics.SimplePie("airplane_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page // Airplane
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0277627e97b51e20470ccf578cee48470e06a34b..6a30338648214c98b05feb5a628dbdc6b2683ff9 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -202,16 +202,26 @@ public class PaperConfig {
public static String timingsServerName;
private static void timings() {
boolean timings = getBoolean("timings.enabled", true);
+ // Airplane start
+ boolean reallyEnableTimings = getBoolean("timings.really-enabled", false);
+ if (timings && !reallyEnableTimings) {
+ Bukkit.getLogger().log(Level.WARNING, "[Airplane] To improve performance, timings have been disabled by default");
+ Bukkit.getLogger().log(Level.WARNING, "[Airplane] You can still use timings by using /timings on, but they will not start on server startup unless you set timings.really-enabled to true in paper.yml");
+ Bukkit.getLogger().log(Level.WARNING, "[Airplane] If you would like to disable this message, either set timings.really-enabled to true or timings.enabled to false.");
+ }
+ timings = reallyEnableTimings;
+ // Airplane end
boolean verboseTimings = getBoolean("timings.verbose", true);
TimingsManager.url = getString("timings.url", "https://timings.aikar.co/");
if (!TimingsManager.url.endsWith("/")) {
TimingsManager.url += "/";
}
TimingsManager.privacy = getBoolean("timings.server-name-privacy", false);
- TimingsManager.hiddenConfigs = getList("timings.hidden-config-entries", Lists.newArrayList("database", "settings.bungeecord-addresses", "settings.velocity-support.secret"));
+ TimingsManager.hiddenConfigs = getList("timings.hidden-config-entries", Lists.newArrayList("database", "settings.bungeecord-addresses", "settings.velocity-support.secret", "web-services.token")); // Airplane
if (!TimingsManager.hiddenConfigs.contains("settings.velocity-support.secret")) {
TimingsManager.hiddenConfigs.add("settings.velocity-support.secret");
}
+ if (!TimingsManager.hiddenConfigs.contains("web-services.token")) TimingsManager.hiddenConfigs.add("web-services.token"); // Airplane
int timingHistoryInterval = getInt("timings.history-interval", 300);
int timingHistoryLength = getInt("timings.history-length", 3600);
timingsServerName = getString("timings.server-name", "Unknown Server");
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index c2d8294ffeff017a6ec9e7725b50eaef8eb75dfd..6b7006829dde81840bd305ee775d26c9f1ef24f7 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -268,6 +268,9 @@ public class PaperWorldConfig {
public int softDespawnDistance;
public int hardDespawnDistance;
+ // Airplane start - separate squared values
+ public int softDespawnDistanceSq;
+ public int hardDespawnDistanceSq;
private void despawnDistances() {
softDespawnDistance = getInt("despawn-ranges.soft", 32); // 32^2 = 1024, Minecraft Default
hardDespawnDistance = getInt("despawn-ranges.hard", 128); // 128^2 = 16384, Minecraft Default
@@ -278,8 +281,9 @@ public class PaperWorldConfig {
log("Living Entity Despawn Ranges: Soft: " + softDespawnDistance + " Hard: " + hardDespawnDistance);
- softDespawnDistance = softDespawnDistance*softDespawnDistance;
- hardDespawnDistance = hardDespawnDistance*hardDespawnDistance;
+ softDespawnDistanceSq = softDespawnDistance*softDespawnDistance;
+ hardDespawnDistanceSq = hardDespawnDistance*hardDespawnDistance;
+ // Airplane end
}
public boolean keepSpawnInMemory;
diff --git a/src/main/java/gg/airplane/AirplaneCommand.java b/src/main/java/gg/airplane/AirplaneCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..89c89e633f14b5820147e734b1b7ad8cadfdce80
--- /dev/null
+++ b/src/main/java/gg/airplane/AirplaneCommand.java
@@ -0,0 +1,65 @@
+package gg.airplane;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.md_5.bungee.api.ChatColor;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class AirplaneCommand extends Command {
+
+ public AirplaneCommand() {
+ super("airplane");
+ this.description = "Airplane related commands";
+ this.usageMessage = "/airplane [reload | version]";
+ this.setPermission("bukkit.command.airplane");
+ }
+
+ @Override
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
+ if (args.length == 1) {
+ return Stream.of("reload", "version")
+ .filter(arg -> arg.startsWith(args[0].toLowerCase()))
+ .collect(Collectors.toList());
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
+ if (!testPermission(sender)) return true;
+ String prefix = ChatColor.of("#6a7eda") + "" + ChatColor.BOLD + "Airplane ✈ " + ChatColor.of("#e8ebf9");
+
+ if (args.length != 1) {
+ sender.sendMessage(prefix + "Usage: " + usageMessage);
+ args = new String[]{"version"};
+ }
+
+ if (args[0].equalsIgnoreCase("reload")) {
+ MinecraftServer console = MinecraftServer.getServer();
+ try {
+ AirplaneConfig.load();
+ } catch (IOException e) {
+ sender.sendMessage(Component.text("Failed to reload.", NamedTextColor.RED));
+ e.printStackTrace();
+ return true;
+ }
+ console.server.reloadCount++;
+
+ Command.broadcastCommandMessage(sender, prefix + "Airplane configuration has been reloaded.");
+ } else if (args[0].equalsIgnoreCase("version")) {
+ Command.broadcastCommandMessage(sender, prefix + "This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")");
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/gg/airplane/AirplaneConfig.java b/src/main/java/gg/airplane/AirplaneConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b654e6adfe331a5fa68c1aea7d6d6cea1f3567d
--- /dev/null
+++ b/src/main/java/gg/airplane/AirplaneConfig.java
@@ -0,0 +1,257 @@
+package gg.airplane;
+
+import co.technove.air.AIR;
+import co.technove.air.ValueType;
+import net.minecraft.core.Registry;
+import gg.airplane.flare.FlareCommand;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.entity.EntityType;
+import org.apache.logging.log4j.Level;
+import org.bukkit.configuration.Configuration;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.MemoryConfiguration;
+import org.bukkit.configuration.MemorySection;
+import org.jetbrains.annotations.Nullable;
+import org.simpleyaml.configuration.comments.CommentType;
+import org.simpleyaml.configuration.file.YamlFile;
+import org.simpleyaml.exceptions.InvalidConfigurationException;
+import org.bukkit.command.SimpleCommandMap;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.net.URI;
+import java.util.Collections;
+
+public class AirplaneConfig {
+
+ private static @Nullable AIR oldConfig;
+ private static final YamlFile config = new YamlFile();
+ private static int updates = 0;
+
+ private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) {
+ ConfigurationSection newSection = new MemoryConfiguration();
+ for (String key : section.getKeys(false)) {
+ if (section.isConfigurationSection(key)) {
+ newSection.set(key, convertToBukkit(section.getConfigurationSection(key)));
+ } else {
+ newSection.set(key, section.get(key));
+ }
+ }
+ return newSection;
+ }
+
+ public static ConfigurationSection getConfigCopy() {
+ return convertToBukkit(config);
+ }
+
+ public static int getUpdates() {
+ return updates;
+ }
+
+ public static void load() throws IOException {
+ File oldConfigFile = new File("airplane.air");
+ File configFile = new File("airplane.yml");
+ if (oldConfigFile.exists() && !configFile.exists()) {
+ try (FileInputStream inputStream = new FileInputStream(oldConfigFile)) {
+ oldConfig = new AIR(inputStream);
+ }
+ }
+
+ if (configFile.exists()) {
+ try {
+ config.load(configFile);
+ } catch (InvalidConfigurationException e) {
+ throw new IOException(e);
+ }
+ }
+
+ getString("info.version", "1.0");
+ setComment("info",
+ "Airplane Configuration",
+ "Read https://blog.airplane.gg/ to find out more about Airplane",
+ "Join our Discord to receive support & optimization help: https://discord.gg/3gtc45q");
+
+ for (Method method : AirplaneConfig.class.getDeclaredMethods()) {
+ if (Modifier.isStatic(method.getModifiers()) && Modifier.isPrivate(method.getModifiers()) && method.getParameterCount() == 0 &&
+ method.getReturnType() == Void.TYPE && !method.getName().startsWith("lambda")) {
+ method.setAccessible(true);
+ try {
+ method.invoke(null);
+ } catch (Throwable t) {
+ MinecraftServer.LOGGER.log(Level.WARN, "Failed to load configuration option from " + method.getName(), t);
+ }
+ }
+ }
+
+ updates++;
+
+ config.save(configFile);
+ oldConfig = null;
+
+ if (oldConfigFile.exists()) {
+ oldConfigFile.renameTo(new File("airplane.air.old"));
+ }
+ }
+
+ private static void setComment(String key, String... comment) {
+ if (config.contains(key)) {
+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK);
+ }
+ }
+
+ private static void ensureDefault(String key, Object defaultValue, String... comment) {
+ if (!config.contains(key)) {
+ config.set(key, defaultValue);
+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK);
+ }
+ }
+
+ private static boolean getBoolean(String key, boolean defaultValue, String... comment) {
+ return getBoolean(key, null, defaultValue, comment);
+ }
+
+ private static boolean getBoolean(String key, @Nullable String oldKey, boolean defaultValue, String... comment) {
+ if (oldConfig != null) {
+ defaultValue = oldConfig.getBoolean(oldKey == null ? key : oldKey, defaultValue);
+ }
+ ensureDefault(key, defaultValue, comment);
+ return config.getBoolean(key, defaultValue);
+ }
+
+ private static int getInt(String key, int defaultValue, String... comment) {
+ return getInt(key, null, defaultValue, comment);
+ }
+
+ private static int getInt(String key, @Nullable String oldKey, int defaultValue, String... comment) {
+ if (oldConfig != null) {
+ defaultValue = oldConfig.getInt(oldKey == null ? key : oldKey, defaultValue);
+ }
+ ensureDefault(key, defaultValue, comment);
+ return config.getInt(key, defaultValue);
+ }
+
+ private static double getDouble(String key, double defaultValue, String... comment) {
+ return getDouble(key, null, defaultValue, comment);
+ }
+
+ private static double getDouble(String key, @Nullable String oldKey, double defaultValue, String... comment) {
+ if (oldConfig != null) {
+ defaultValue = oldConfig.getDouble(oldKey == null ? key : oldKey, defaultValue);
+ }
+ ensureDefault(key, defaultValue, comment);
+ return config.getDouble(key, defaultValue);
+ }
+
+ private static String getString(String key, String defaultValue, String... comment) {
+ return getOldString(key, null, defaultValue, comment);
+ }
+
+ private static String getOldString(String key, @Nullable String oldKey, String defaultValue, String... comment) {
+ if (oldConfig != null) {
+ defaultValue = oldConfig.getString(oldKey == null ? key : oldKey, defaultValue);
+ }
+ ensureDefault(key, defaultValue, comment);
+ return config.getString(key, defaultValue);
+ }
+
+ private static List<String> getStringList(String key, List<String> defaultValue, String... comment) {
+ return getStringList(key, null, defaultValue, comment);
+ }
+
+ private static List<String> getStringList(String key, @Nullable String oldKey, List<String> defaultValue, String... comment) {
+ if (oldConfig != null) {
+ try {
+ defaultValue = oldConfig.getList(oldKey != null ? oldKey : key, ValueType.STRING, defaultValue);
+ } catch (IOException e) {
+ }
+ }
+ ensureDefault(key, defaultValue, comment);
+ return config.getStringList(key);
+ }
+
+ public static int maxProjectileLoadsPerTick;
+ public static int maxProjectileLoadsPerProjectile;
+
+
+ private static void projectileLoading() {
+ maxProjectileLoadsPerTick = getInt("projectile.max-loads-per-tick", 10, "Controls how many chunks are allowed", "to be sync loaded by projectiles in a tick.");
+ maxProjectileLoadsPerProjectile = getInt("projectile.max-loads-per-projectile", 10, "Controls how many chunks a projectile", "can load in its lifetime before it gets", "automatically removed.");
+
+ setComment("projectile", "Optimizes projectile settings");
+ }
+
+
+ public static boolean dearEnabled;
+ public static int startDistance;
+ public static int startDistanceSquared;
+ public static int maximumActivationPrio;
+ public static int activationDistanceMod;
+
+ private static void dynamicActivationOfBrains() throws IOException {
+ dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", true);
+ startDistance = getInt("dab.start-distance", "activation-range.start-distance", 12,
+ "This value determines how far away an entity has to be",
+ "from the player to start being effected by DEAR.");
+ startDistanceSquared = startDistance * startDistance;
+ maximumActivationPrio = getInt("dab.max-tick-freq", "activation-range.max-tick-freq", 20,
+ "This value defines how often in ticks, the furthest entity",
+ "will get their pathfinders and behaviors ticked. 20 = 1s");
+ activationDistanceMod = getInt("dab.activation-dist-mod", "activation-range.activation-dist-mod", 8,
+ "This value defines how much distance modifies an entity's",
+ "tick frequency. freq = (distanceToPlayer^2) / (2^value)",
+ "If you want further away entities to tick less often, use 7.",
+ "If you want further away entities to tick more often, try 9.");
+
+ for (EntityType<?> entityType : Registry.ENTITY_TYPE) {
+ entityType.dabEnabled = true; // reset all, before setting the ones to true
+ }
+ getStringList("dab.blacklisted-entities", "activation-range.blacklisted-entities", Collections.emptyList(), "A list of entities to ignore for activation")
+ .forEach(name -> EntityType.byString(name).ifPresentOrElse(entityType -> {
+ entityType.dabEnabled = false;
+ }, () -> MinecraftServer.LOGGER.log(Level.WARN, "Unknown entity \"" + name + "\"")));
+
+ setComment("dab", "Optimizes entity brains when", "they're far away from the player");
+ }
+
+
+ public static URI profileWebUrl;
+
+ private static void profilerOptions() {
+ profileWebUrl = URI.create(getString("flare.url", "https://flare.airplane.gg", "Sets the server to use for profiles."));
+
+ setComment("flare", "Configures Flare, the built-in profiler");
+ }
+
+
+ public static String accessToken;
+
+ private static void airplaneWebServices() {
+ accessToken = getString("web-services.token", "");
+ // todo lookup token (off-thread) and let users know if their token is valid
+ if (accessToken.length() > 0) {
+ gg.airplane.flare.FlareSetup.init(); // Airplane
+ SimpleCommandMap commandMap = MinecraftServer.getServer().server.getCommandMap();
+ if (commandMap.getCommand("flare") == null) {
+ commandMap.register("flare", "Airplane", new FlareCommand());
+ }
+ }
+
+ setComment("web-services", "Options for connecting to Airplane's online utilities");
+
+ }
+
+
+ public static boolean disableMethodProfiler;
+
+ private static void miscSettings() {
+ disableMethodProfiler = config.getBoolean("misc.disable-method-profiler", true);
+
+ config.setComment("misc", "Settings for things that don't belong elsewhere");
+ }
+
+
+}
diff --git a/src/main/java/gg/airplane/AirplaneLogger.java b/src/main/java/gg/airplane/AirplaneLogger.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a9d71739019d12772bec6076b195552ff6299f9
--- /dev/null
+++ b/src/main/java/gg/airplane/AirplaneLogger.java
@@ -0,0 +1,17 @@
+package gg.airplane;
+
+import org.bukkit.Bukkit;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AirplaneLogger extends Logger {
+ public static final AirplaneLogger LOGGER = new AirplaneLogger();
+
+ private AirplaneLogger() {
+ super("Airplane", null);
+
+ setParent(Bukkit.getLogger());
+ setLevel(Level.ALL);
+ }
+}
diff --git a/src/main/java/gg/airplane/AirplaneVersionFetcher.java b/src/main/java/gg/airplane/AirplaneVersionFetcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..e79ec7919e6cf160fec1cb981d85f0f33c316b41
--- /dev/null
+++ b/src/main/java/gg/airplane/AirplaneVersionFetcher.java
@@ -0,0 +1,137 @@
+package gg.airplane;
+
+import com.destroystokyo.paper.VersionHistoryManager;
+import com.destroystokyo.paper.util.VersionFetcher;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.JoinConfiguration;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.bukkit.craftbukkit.CraftServer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+public class AirplaneVersionFetcher implements VersionFetcher {
+
+ private static final Logger LOGGER = Logger.getLogger("AirplaneVersionFetcher");
+ private static final HttpClient client = HttpClient.newHttpClient();
+
+ private static final URI JENKINS_URI = URI.create("https://ci.tivy.ca/job/Airplane-1.17/lastSuccessfulBuild/buildNumber");
+ private static final String GITHUB_FORMAT = "https://api.github.com/repos/TECHNOVE/Airplane/compare/ver/1.17...%s";
+
+ private static final HttpResponse.BodyHandler<JsonObject> JSON_OBJECT_BODY_HANDLER = responseInfo -> HttpResponse.BodySubscribers.mapping(
+ HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8),
+ string -> new Gson().fromJson(string, JsonObject.class)
+ );
+
+ @Override
+ public long getCacheTime() {
+ return TimeUnit.MINUTES.toMillis(30);
+ }
+
+ @Override
+ public @NotNull Component getVersionMessage(final @NotNull String serverVersion) {
+ final String[] parts = CraftServer.class.getPackage().getImplementationVersion().split("-");
+ @NotNull Component component;
+
+ if (parts.length != 3) {
+ component = text("Unknown server version.", RED);
+ } else {
+ final String versionString = parts[2];
+
+ try {
+ component = this.fetchJenkinsVersion(Integer.parseInt(versionString));
+ } catch (NumberFormatException e) {
+ component = this.fetchGithubVersion(versionString.substring(1, versionString.length() - 1));
+ }
+ }
+
+ final @Nullable Component history = this.getHistory();
+ return history != null ? Component.join(JoinConfiguration.noSeparators(), component, Component.newline(), this.getHistory()) : component;
+ }
+
+ private @NotNull Component fetchJenkinsVersion(final int versionNumber) {
+ final HttpRequest request = HttpRequest.newBuilder(JENKINS_URI).build();
+ try {
+ final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() != 200) {
+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED);
+ }
+
+ int latestVersionNumber;
+ try {
+ latestVersionNumber = Integer.parseInt(response.body());
+ } catch (NumberFormatException e) {
+ LOGGER.log(Level.WARNING, "Received invalid response from Jenkins \"" + response.body() + "\".");
+ return text("Received invalid response from server.", RED);
+ }
+
+ final int versionDiff = latestVersionNumber - versionNumber;
+ return this.getResponseMessage(versionDiff);
+ } catch (IOException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Failed to look up version from Jenkins", e);
+ return text("Failed to retrieve version from server.", RED);
+ }
+ }
+
+ // Based off code contributed by Techcable <Techcable@outlook.com> in Paper/GH-65
+ private @NotNull Component fetchGithubVersion(final @NotNull String hash) {
+ final URI uri = URI.create(String.format(GITHUB_FORMAT, hash));
+ final HttpRequest request = HttpRequest.newBuilder(uri).build();
+ try {
+ final HttpResponse<JsonObject> response = client.send(request, JSON_OBJECT_BODY_HANDLER);
+ if (response.statusCode() != 200) {
+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED);
+ }
+
+ final JsonObject obj = response.body();
+ final int versionDiff = obj.get("behind_by").getAsInt();
+
+ return this.getResponseMessage(versionDiff);
+ } catch (IOException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Failed to look up version from GitHub", e);
+ return text("Failed to retrieve version from server.", RED);
+ }
+ }
+
+ private @NotNull Component getResponseMessage(final int versionDiff) {
+ return switch (Math.max(-1, Math.min(1, versionDiff))) {
+ case -1 ->
+ text("You are running an unsupported version of Airplane.", RED);
+ case 0 ->
+ text("You are on the latest version!", GREEN);
+ default ->
+ text("You are running " + versionDiff + " version" + (versionDiff == 1 ? "" : "s") + " beyond. " +
+ "Please update your server when possible to maintain stability, security, and receive the latest optimizations.", RED);
+ };
+ }
+
+ private @Nullable Component getHistory() {
+ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
+ if (data == null) {
+ return null;
+ }
+
+ final String oldVersion = data.getOldVersion();
+ if (oldVersion == null) {
+ return null;
+ }
+
+ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
+ }
+}
diff --git a/src/main/java/gg/airplane/commands/AirplaneCommands.java b/src/main/java/gg/airplane/commands/AirplaneCommands.java
new file mode 100644
index 0000000000000000000000000000000000000000..807cf274619b8f7be839e249cb62b9817876ca04
--- /dev/null
+++ b/src/main/java/gg/airplane/commands/AirplaneCommands.java
@@ -0,0 +1,10 @@
+package gg.airplane.commands;
+
+import gg.airplane.AirplaneCommand;
+import net.minecraft.server.MinecraftServer;
+
+public class AirplaneCommands {
+ public static void init() {
+ MinecraftServer.getServer().server.getCommandMap().register("airplane", "Airplane", new AirplaneCommand());
+ }
+}
diff --git a/src/main/java/gg/airplane/compat/ServerConfigurations.java b/src/main/java/gg/airplane/compat/ServerConfigurations.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9a71ff3edd7e7b6cda680e5a156373b5aa813c2
--- /dev/null
+++ b/src/main/java/gg/airplane/compat/ServerConfigurations.java
@@ -0,0 +1,78 @@
+package gg.airplane.compat;
+
+import co.aikar.timings.TimingsManager;
+import com.google.common.io.Files;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+public class ServerConfigurations {
+
+ public static final String[] configurationFiles = new String[]{
+ "server.properties",
+ "bukkit.yml",
+ "spigot.yml",
+ "paper.yml",
+ "airplane.yml"
+ };
+
+ public static Map<String, String> getCleanCopies() throws IOException {
+ Map<String, String> files = new HashMap<>(configurationFiles.length);
+ for (String file : configurationFiles) {
+ files.put(file, getCleanCopy(file));
+ }
+ return files;
+ }
+
+ public static String getCleanCopy(String configName) throws IOException {
+ File file = new File(configName);
+ List<String> hiddenConfigs = TimingsManager.hiddenConfigs;
+
+ switch (Files.getFileExtension(configName)) {
+ case "properties": {
+ Properties properties = new Properties();
+ try (FileInputStream inputStream = new FileInputStream(file)) {
+ properties.load(inputStream);
+ }
+ for (String hiddenConfig : hiddenConfigs) {
+ properties.remove(hiddenConfig);
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ properties.store(outputStream, "");
+ return Arrays.stream(outputStream.toString()
+ .split("\n"))
+ .filter(line -> !line.startsWith("#"))
+ .collect(Collectors.joining("\n"));
+ }
+ case "yml": {
+ YamlConfiguration configuration = new YamlConfiguration();
+ try {
+ configuration.load(file);
+ } catch (InvalidConfigurationException e) {
+ throw new IOException(e);
+ }
+ configuration.options().header(null);
+ for (String key : configuration.getKeys(true)) {
+ if (hiddenConfigs.contains(key)) {
+ configuration.set(key, null);
+ }
+ }
+ return configuration.saveToString();
+ }
+ default:
+ throw new IllegalArgumentException("Bad file type " + configName);
+ }
+ }
+
+}
diff --git a/src/main/java/gg/airplane/entity/CollisionCache.java b/src/main/java/gg/airplane/entity/CollisionCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..3822fc6b78419e681ff838bc2050c4f8fb3e90eb
--- /dev/null
+++ b/src/main/java/gg/airplane/entity/CollisionCache.java
@@ -0,0 +1,244 @@
+package gg.airplane.entity;
+
+import io.papermc.paper.util.CollisionUtil;
+import io.papermc.paper.util.WorldUtil;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.SectionPos;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.CollisionGetter;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import org.bukkit.craftbukkit.util.UnsafeList;
+import org.jetbrains.annotations.NotNull;
+import org.simpleyaml.utils.Validate;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiPredicate;
+
+public class CollisionCache {
+
+ private static record BlockEntry(int x, int y, int z, BlockState state, VoxelShape shape){}
+
+ @NotNull
+ private final Entity entity;
+ private final UnsafeList<BlockEntry> blocks = new UnsafeList<>();
+ private Set<CollisionCacheList> chunkToList = new HashSet<>();
+
+ private boolean dirty = true;
+
+ private int previousMinBlockX;
+ private int previousMaxBlockX;
+ private int previousMinBlockY;
+ private int previousMaxBlockY;
+ private int previousMinBlockZ;
+ private int previousMaxBlockZ;
+
+ public CollisionCache(@NotNull Entity entity) {
+ this.entity = entity;
+ }
+
+ public int getId() {
+ return this.entity.getId();
+ }
+
+ public void dirtySection(@NotNull SectionPos sectionPos) {
+ this.dirty = true;
+ }
+
+ public void onRemove() {
+ this.blocks.setSize(0);
+
+ for (CollisionCacheList collisionCaches : this.chunkToList) {
+ collisionCaches.remove(this);
+ }
+ this.chunkToList.clear();
+ this.dirty = false;
+ }
+
+ public boolean getCollisions(final CollisionGetter view, AABB aabb, List<AABB> into, boolean collidesWithUnloaded, boolean checkOnly, BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ int minBlockX = Mth.floor(aabb.minX - CollisionUtil.COLLISION_EPSILON) - 1;
+ int maxBlockX = Mth.floor(aabb.maxX + CollisionUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockY = Mth.floor(aabb.minY - CollisionUtil.COLLISION_EPSILON) - 1;
+ int maxBlockY = Mth.floor(aabb.maxY + CollisionUtil.COLLISION_EPSILON) + 1;
+
+ int minBlockZ = Mth.floor(aabb.minZ - CollisionUtil.COLLISION_EPSILON) - 1;
+ int maxBlockZ = Mth.floor(aabb.maxZ + CollisionUtil.COLLISION_EPSILON) + 1;
+
+ // if nothing changed and the location didn't move out of our area, use previous set
+ if (!this.dirty && minBlockX >= this.previousMinBlockX && maxBlockX <= this.previousMaxBlockX &&
+ minBlockY >= this.previousMinBlockY && maxBlockY <= this.previousMaxBlockY &&
+ minBlockZ >= this.previousMinBlockZ && maxBlockZ <= this.previousMaxBlockZ) {
+ if (checkOnly) {
+ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
+ for (int i = 0, length = this.blocks.size(); i < length; i++) {
+ BlockEntry entry = this.blocks.unsafeGet(i);
+ if (entry.shape.intersects(aabb) && predicate.test(entry.state, pos.set(entry.x, entry.y, entry.z))) {
+ return true;
+ }
+ }
+ } else {
+ for (int i = 0, length = this.blocks.size(); i < length; i++) {
+ ret |= CollisionUtil.addBoxesToIfIntersects(this.blocks.unsafeGet(i).shape, aabb, into);
+ }
+ }
+
+ return ret;
+ } else if (checkOnly) {
+ // tl;dr this is only used by inWall right now, and we don't want to generate a cache for inWall because it'll always be smaller than a move cache anyways
+ return CollisionUtil.getCollisionsForBlocksOrWorldBorder(view, this.entity, aabb, into, false, collidesWithUnloaded, false, checkOnly, predicate);
+ }
+
+ Validate.isTrue(predicate == null, "predicate cannot be used without checkOnly");
+
+ this.previousMinBlockX = minBlockX;
+ this.previousMaxBlockX = maxBlockX;
+
+ this.previousMinBlockY = minBlockY;
+ this.previousMaxBlockY = maxBlockY;
+
+ this.previousMinBlockZ = minBlockZ;
+ this.previousMaxBlockZ = maxBlockZ;
+
+ // remove old shapes, since we missed cache
+ this.blocks.setSize(0);
+ this.dirty = false;
+
+ final int minSection = WorldUtil.getMinSection(this.entity.level);
+ final int maxSection = WorldUtil.getMaxSection(this.entity.level);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ if (!this.chunkToList.isEmpty()) {
+ for (CollisionCacheList collisionCaches : this.chunkToList) {
+ collisionCaches.remove(this);
+ }
+ this.chunkToList.clear();
+ }
+ return ret;
+ }
+
+ int minYIterate = Math.max(minBlock, minBlockY);
+ int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ int minChunkX = minBlockX >> 4;
+ int maxChunkX = maxBlockX >> 4;
+
+ int minChunkZ = minBlockZ >> 4;
+ int maxChunkZ = maxBlockZ >> 4;
+
+ ServerChunkCache chunkProvider = (ServerChunkCache) this.entity.level.getChunkSource();
+
+ Set<CollisionCacheList> cacheLists = new HashSet<>(this.blocks.size());
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ int chunkXGlobalPos = currChunkX << 4;
+ int chunkZGlobalPos = currChunkZ << 4;
+
+ LevelChunk chunk = chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ into.add(CollisionUtil.getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ continue;
+ }
+
+ LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+
+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
+ int sectionIndex = SectionPos.blockToSectionCoord(currY) - minSection;
+
+ CollisionCacheList cacheList = chunk.collisionCaches[sectionIndex];
+ if (cacheLists.add(cacheList)) {
+ cacheList.add(this);
+ }
+
+ LevelChunkSection section = sections[sectionIndex];
+ if (section == null || section.isEmpty()) {
+ // empty
+ // skip to next section
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
+ continue;
+ }
+
+ net.minecraft.world.level.chunk.PalettedContainer<BlockState> blocks = section.states;
+
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8);
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+ if (blockData.isAir()) {
+ continue;
+ }
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new CollisionUtil.LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(this.entity.level, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double) blockX, (double) blockY, (double) blockZ);
+
+ this.blocks.add(new BlockEntry(blockX, blockY, blockZ, blockData, voxelshape3));
+
+ ret |= CollisionUtil.addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (CollisionCacheList cache : this.chunkToList) {
+ if (!cacheLists.contains(cache)) {
+ cache.remove(this);
+ }
+ }
+ this.chunkToList = cacheLists;
+
+ return ret;
+ }
+
+}
diff --git a/src/main/java/gg/airplane/entity/CollisionCacheList.java b/src/main/java/gg/airplane/entity/CollisionCacheList.java
new file mode 100644
index 0000000000000000000000000000000000000000..017da9e1461250a0fd8baacdcca203d6949244fc
--- /dev/null
+++ b/src/main/java/gg/airplane/entity/CollisionCacheList.java
@@ -0,0 +1,128 @@
+package gg.airplane.entity;
+
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * @see com.destroystokyo.paper.util.maplist.EntityList
+ */
+public class CollisionCacheList implements Iterable<CollisionCache> {
+
+ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
+
+ {
+ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
+ }
+
+ protected static final CollisionCache[] EMPTY_LIST = new CollisionCache[0];
+
+ protected CollisionCache[] entities = EMPTY_LIST;
+ protected int count;
+
+ public int size() {
+ return this.count;
+ }
+
+ public boolean contains(final CollisionCache entity) {
+ return this.entityToIndex.containsKey(entity.getId());
+ }
+
+ public boolean remove(final CollisionCache entity) {
+ final int index = this.entityToIndex.remove(entity.getId());
+ if (index == Integer.MIN_VALUE) {
+ return false;
+ }
+
+ // move the entity at the end to this index
+ final int endIndex = --this.count;
+ final CollisionCache end = this.entities[endIndex];
+ if (index != endIndex) {
+ // not empty after this call
+ this.entityToIndex.put(end.getId(), index); // update index
+ }
+ this.entities[index] = end;
+ this.entities[endIndex] = null;
+
+ return true;
+ }
+
+ public boolean add(final CollisionCache entity) {
+ final int count = this.count;
+ final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count);
+
+ if (currIndex != Integer.MIN_VALUE) {
+ return false; // already in this list
+ }
+
+ CollisionCache[] list = this.entities;
+
+ if (list.length == count) {
+ // resize required
+ list = this.entities = Arrays.copyOf(list, (int) Math.max(4L, count * 2L)); // overflow results in negative
+ }
+
+ list[count] = entity;
+ this.count = count + 1;
+
+ return true;
+ }
+
+ public CollisionCache getChecked(final int index) {
+ if (index < 0 || index >= this.count) {
+ throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count);
+ }
+ return this.entities[index];
+ }
+
+ public CollisionCache getUnchecked(final int index) {
+ return this.entities[index];
+ }
+
+ public CollisionCache[] getRawData() {
+ return this.entities;
+ }
+
+ public void clear() {
+ this.entityToIndex.clear();
+ Arrays.fill(this.entities, 0, this.count, null);
+ this.count = 0;
+ }
+
+ @Override
+ public Iterator<CollisionCache> iterator() {
+ return new Iterator<CollisionCache>() {
+
+ CollisionCache lastRet;
+ int current;
+
+ @Override
+ public boolean hasNext() {
+ return this.current < CollisionCacheList.this.count;
+ }
+
+ @Override
+ public CollisionCache next() {
+ if (this.current >= CollisionCacheList.this.count) {
+ throw new NoSuchElementException();
+ }
+ return this.lastRet = CollisionCacheList.this.entities[this.current++];
+ }
+
+ @Override
+ public void remove() {
+ final CollisionCache lastRet = this.lastRet;
+
+ if (lastRet == null) {
+ throw new IllegalStateException();
+ }
+ this.lastRet = null;
+
+ CollisionCacheList.this.remove(lastRet);
+ --this.current;
+ }
+ };
+ }
+}
diff --git a/src/main/java/gg/airplane/flare/CustomCategories.java b/src/main/java/gg/airplane/flare/CustomCategories.java
new file mode 100644
index 0000000000000000000000000000000000000000..031700b291ce71eac7de2ff3423a9bbfd8de4ac6
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/CustomCategories.java
@@ -0,0 +1,8 @@
+package gg.airplane.flare;
+
+import co.technove.flare.live.category.GraphCategory;
+
+public class CustomCategories {
+ public static final GraphCategory MC_PERF = new GraphCategory("MC Performance");
+ public static final GraphCategory ENTITIES_AND_CHUNKS = new GraphCategory("Entities & Chunks");
+}
diff --git a/src/main/java/gg/airplane/flare/FlareCommand.java b/src/main/java/gg/airplane/flare/FlareCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..12acd136a3f98a79c28e4986be3e92c5cc42658e
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/FlareCommand.java
@@ -0,0 +1,136 @@
+package gg.airplane.flare;
+
+import co.technove.flare.exceptions.UserReportableException;
+import co.technove.flare.internal.profiling.ProfileType;
+import gg.airplane.AirplaneConfig;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import net.minecraft.server.MinecraftServer;
+import org.apache.logging.log4j.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
+import org.bukkit.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FlareCommand extends Command {
+
+ private static final String BASE_URL = "https://blog.airplane.gg/flare-tutorial/#setting-the-access-token";
+ private static final TextColor HEX = TextColor.fromHexString("#e3eaea");
+ private static final Component PREFIX = Component.text()
+ .append(Component.text("Flare ✈")
+ .color(TextColor.fromHexString("#6a7eda"))
+ .decoration(TextDecoration.BOLD, true)
+ .append(Component.text(" ", HEX)
+ .decoration(TextDecoration.BOLD, false)))
+ .asComponent();
+
+ public FlareCommand() {
+ super("flare", "Profile your server with Flare", "/flare", Collections.singletonList("profile"));
+ this.setPermission("airplane.flare");
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String @NotNull [] args) {
+ if (!testPermission(sender)) return true;
+ if (AirplaneConfig.accessToken.length() == 0) {
+ Component clickable = Component.text(BASE_URL, HEX, TextDecoration.UNDERLINED).clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, BASE_URL));
+
+ sender.sendMessage(PREFIX.append(Component.text("Flare currently requires an access token to use. To learn more, visit ").color(HEX).append(clickable)));
+ return true;
+ }
+
+ if (!FlareSetup.isSupported()) {
+ sender.sendMessage(PREFIX.append(
+ Component.text("Profiling is not supported in this environment, check your startup logs for the error.", NamedTextColor.RED)));
+ return true;
+ }
+ if (ProfilingManager.isProfiling()) {
+ if (args.length == 1 && args[0].equalsIgnoreCase("status")) {
+ sender.sendMessage(PREFIX.append(Component.text("Current profile has been ran for " + ProfilingManager.getTimeRan().toString(), HEX)));
+ return true;
+ }
+ if (ProfilingManager.stop()) {
+ if (!(sender instanceof ConsoleCommandSender)) {
+ sender.sendMessage(PREFIX.append(Component.text("Profiling has been stopped.", HEX)));
+ }
+ } else {
+ sender.sendMessage(PREFIX.append(Component.text("Profiling has already been stopped.", HEX)));
+ }
+ } else {
+ ProfileType profileType = ProfileType.ITIMER;
+ if (args.length > 0) {
+ try {
+ profileType = ProfileType.valueOf(args[0].toUpperCase());
+ } catch (Exception e) {
+ sender.sendMessage(PREFIX.append(Component
+ .text("Invalid profile type ", HEX)
+ .append(Component.text(args[0], HEX, TextDecoration.BOLD)
+ .append(Component.text("!", HEX)))
+ ));
+ }
+ }
+ ProfileType finalProfileType = profileType;
+ Bukkit.getScheduler().runTaskAsynchronously(new MinecraftInternalPlugin(), () -> {
+ try {
+ if (ProfilingManager.start(finalProfileType)) {
+ if (!(sender instanceof ConsoleCommandSender)) {
+ sender.sendMessage(PREFIX.append(Component
+ .text("Flare has been started: " + ProfilingManager.getProfilingUri(), HEX)
+ .clickEvent(ClickEvent.openUrl(ProfilingManager.getProfilingUri()))
+ ));
+ sender.sendMessage(PREFIX.append(Component.text(" Run /" + commandLabel + " to stop the Flare.", HEX)));
+ }
+ } else {
+ sender.sendMessage(PREFIX.append(Component
+ .text("Flare has already been started: " + ProfilingManager.getProfilingUri(), HEX)
+ .clickEvent(ClickEvent.openUrl(ProfilingManager.getProfilingUri()))
+ ));
+ }
+ } catch (UserReportableException e) {
+ sender.sendMessage(Component.text("Flare failed to start: " + e.getUserError(), NamedTextColor.RED));
+ if (e.getCause() != null) {
+ MinecraftServer.LOGGER.log(Level.WARN, "Flare failed to start", e);
+ }
+ }
+ });
+ }
+ return true;
+ }
+
+ @Override
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args) throws IllegalArgumentException {
+ List<String> list = new ArrayList<>();
+ if (ProfilingManager.isProfiling()) {
+ if (args.length == 1) {
+ String lastWord = args[0];
+ if (StringUtil.startsWithIgnoreCase("status", lastWord)) {
+ list.add("status");
+ }
+ if (StringUtil.startsWithIgnoreCase("stop", lastWord)) {
+ list.add("stop");
+ }
+ }
+ } else {
+ if (args.length <= 1) {
+ String lastWord = args.length == 0 ? "" : args[0];
+ for (ProfileType value : ProfileType.values()) {
+ if (StringUtil.startsWithIgnoreCase(value.getInternalName(), lastWord)) {
+ list.add(value.name().toLowerCase());
+ }
+ }
+ }
+ }
+ return list;
+ }
+}
diff --git a/src/main/java/gg/airplane/flare/FlareSetup.java b/src/main/java/gg/airplane/flare/FlareSetup.java
new file mode 100644
index 0000000000000000000000000000000000000000..932b976a234f2b1d2975e72a34aa53838e9e5170
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/FlareSetup.java
@@ -0,0 +1,33 @@
+package gg.airplane.flare;
+
+import co.technove.flare.FlareInitializer;
+import co.technove.flare.internal.profiling.InitializationException;
+import net.minecraft.server.MinecraftServer;
+import org.apache.logging.log4j.Level;
+
+public class FlareSetup {
+
+ private static boolean initialized = false;
+ private static boolean supported = false;
+
+ public static void init() {
+ if (initialized) {
+ return;
+ }
+
+ initialized = true;
+ try {
+ for (String warning : FlareInitializer.initialize()) {
+ MinecraftServer.LOGGER.log(Level.WARN, "Flare warning: " + warning);
+ }
+ supported = true;
+ } catch (InitializationException e) {
+ MinecraftServer.LOGGER.log(Level.WARN, "Failed to enable Flare:", e);
+ }
+ }
+
+ public static boolean isSupported() {
+ return supported;
+ }
+
+}
diff --git a/src/main/java/gg/airplane/flare/PluginLookup.java b/src/main/java/gg/airplane/flare/PluginLookup.java
new file mode 100644
index 0000000000000000000000000000000000000000..22368976212ac6638a31e81d22c322a53c94d276
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/PluginLookup.java
@@ -0,0 +1,44 @@
+package gg.airplane.flare;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.PluginClassLoader;
+
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+public class PluginLookup {
+ private static final Cache<String, String> pluginNameCache = CacheBuilder.newBuilder()
+ .expireAfterAccess(1, TimeUnit.MINUTES)
+ .maximumSize(1024)
+ .build();
+
+ public static Optional<String> getPluginForClass(String name) {
+ if (name.startsWith("net.minecraft") || name.startsWith("java.") || name.startsWith("com.mojang") ||
+ name.startsWith("com.google") || name.startsWith("it.unimi") || name.startsWith("sun")) {
+ return Optional.empty();
+ }
+
+ String existing = pluginNameCache.getIfPresent(name);
+ if (existing != null) {
+ return Optional.ofNullable(existing.isEmpty() ? null : existing);
+ }
+
+ String newValue = "";
+
+ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
+ ClassLoader classLoader = plugin.getClass().getClassLoader();
+ if (classLoader instanceof PluginClassLoader) {
+ if (((PluginClassLoader) classLoader)._airplane_hasClass(name)) {
+ newValue = plugin.getName();
+ break;
+ }
+ }
+ }
+
+ pluginNameCache.put(name, newValue);
+ return Optional.ofNullable(newValue.isEmpty() ? null : newValue);
+ }
+}
diff --git a/src/main/java/gg/airplane/flare/ProfilingManager.java b/src/main/java/gg/airplane/flare/ProfilingManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a429348bb4129b7810d5aa048cbc74d126624d8
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/ProfilingManager.java
@@ -0,0 +1,151 @@
+package gg.airplane.flare;
+
+import co.technove.flare.Flare;
+import co.technove.flare.FlareAuth;
+import co.technove.flare.FlareBuilder;
+import co.technove.flare.exceptions.UserReportableException;
+import co.technove.flare.internal.profiling.ProfileType;
+import gg.airplane.AirplaneConfig;
+import gg.airplane.AirplaneLogger;
+import gg.airplane.compat.ServerConfigurations;
+import gg.airplane.flare.collectors.GCEventCollector;
+import gg.airplane.flare.collectors.StatCollector;
+import gg.airplane.flare.collectors.TPSCollector;
+import gg.airplane.flare.collectors.WorldCountCollector;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
+import org.bukkit.scheduler.BukkitTask;
+import oshi.SystemInfo;
+import oshi.hardware.CentralProcessor;
+import oshi.hardware.GlobalMemory;
+import oshi.hardware.HardwareAbstractionLayer;
+import oshi.hardware.VirtualMemory;
+import oshi.software.os.OperatingSystem;
+
+import java.io.IOException;
+import java.net.URI;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.logging.Level;
+
+public class ProfilingManager {
+
+ private static Flare currentFlare;
+ private static BukkitTask currentTask = null;
+
+ public static synchronized boolean isProfiling() {
+ return currentFlare != null && currentFlare.isRunning();
+ }
+
+ public static synchronized String getProfilingUri() {
+ return Objects.requireNonNull(currentFlare).getURI().map(URI::toString).orElse("Flare is not running");
+ }
+
+ public static Duration getTimeRan() {
+ Flare flare = currentFlare; // copy reference so no need to sync
+ if (flare == null) {
+ return Duration.ofMillis(0);
+ }
+ return flare.getCurrentDuration();
+ }
+
+ public static synchronized boolean start(ProfileType profileType) throws UserReportableException {
+ if (currentFlare != null && !currentFlare.isRunning()) {
+ currentFlare = null; // errored out
+ }
+ if (isProfiling()) {
+ return false;
+ }
+ if (Bukkit.isPrimaryThread()) {
+ throw new UserReportableException("Profiles should be started off-thread");
+ }
+
+ try {
+ OperatingSystem os = new SystemInfo().getOperatingSystem();
+
+ SystemInfo systemInfo = new SystemInfo();
+ HardwareAbstractionLayer hardware = systemInfo.getHardware();
+
+ CentralProcessor processor = hardware.getProcessor();
+ CentralProcessor.ProcessorIdentifier processorIdentifier = processor.getProcessorIdentifier();
+
+ GlobalMemory memory = hardware.getMemory();
+ VirtualMemory virtualMemory = memory.getVirtualMemory();
+
+ FlareBuilder builder = new FlareBuilder()
+ .withProfileType(profileType)
+ .withMemoryProfiling(true)
+ .withAuth(FlareAuth.fromTokenAndUrl(AirplaneConfig.accessToken, AirplaneConfig.profileWebUrl))
+
+ .withFiles(ServerConfigurations.getCleanCopies())
+ .withVersion("Primary Version", Bukkit.getVersion())
+ .withVersion("Bukkit Version", Bukkit.getBukkitVersion())
+ .withVersion("Minecraft Version", Bukkit.getMinecraftVersion())
+
+ .withGraphCategories(CustomCategories.ENTITIES_AND_CHUNKS, CustomCategories.MC_PERF)
+ .withCollectors(new TPSCollector(), new WorldCountCollector(), new GCEventCollector(), new StatCollector())
+ .withClassIdentifier(PluginLookup::getPluginForClass)
+
+ .withHardware(new FlareBuilder.HardwareBuilder()
+ .setCoreCount(processor.getPhysicalProcessorCount())
+ .setThreadCount(processor.getLogicalProcessorCount())
+ .setCpuModel(processorIdentifier.getName())
+ .setCpuFrequency(processor.getMaxFreq())
+
+ .setTotalMemory(memory.getTotal())
+ .setTotalSwap(virtualMemory.getSwapTotal())
+ .setTotalVirtual(virtualMemory.getVirtualMax())
+ )
+
+ .withOperatingSystem(new FlareBuilder.OperatingSystemBuilder()
+ .setManufacturer(os.getManufacturer())
+ .setFamily(os.getFamily())
+ .setVersion(os.getVersionInfo().toString())
+ .setBitness(os.getBitness())
+ );
+
+ currentFlare = builder.build();
+ } catch (IOException e) {
+ AirplaneLogger.LOGGER.log(Level.WARNING, "Failed to read configuration files:", e);
+ throw new UserReportableException("Failed to load configuration files, check logs for further details.");
+ }
+
+ try {
+ currentFlare.start();
+ } catch (IllegalStateException e) {
+ AirplaneLogger.LOGGER.log(Level.WARNING, "Error starting Flare:", e);
+ throw new UserReportableException("Failed to start Flare, check logs for further details.");
+ }
+
+ currentTask = Bukkit.getScheduler().runTaskLater(new MinecraftInternalPlugin(), ProfilingManager::stop, 20 * 60 * 15);
+ AirplaneLogger.LOGGER.log(Level.INFO, "Flare has been started: " + getProfilingUri());
+ return true;
+ }
+
+ public static synchronized boolean stop() {
+ if (!isProfiling()) {
+ return false;
+ }
+ if (!currentFlare.isRunning()) {
+ currentFlare = null;
+ return true;
+ }
+ AirplaneLogger.LOGGER.log(Level.INFO, "Flare has been stopped: " + getProfilingUri());
+ try {
+ currentFlare.stop();
+ } catch (IllegalStateException e) {
+ AirplaneLogger.LOGGER.log(Level.WARNING, "Error occurred stopping Flare", e);
+ }
+ currentFlare = null;
+
+ try {
+ currentTask.cancel();
+ } catch (Throwable t) {
+ AirplaneLogger.LOGGER.log(Level.WARNING, "Error occurred stopping Flare", t);
+ }
+
+ currentTask = null;
+ return true;
+ }
+
+}
diff --git a/src/main/java/gg/airplane/flare/collectors/GCEventCollector.java b/src/main/java/gg/airplane/flare/collectors/GCEventCollector.java
new file mode 100644
index 0000000000000000000000000000000000000000..d355b6cf3132b69ab24e351e49bf4c34e2ab3752
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/collectors/GCEventCollector.java
@@ -0,0 +1,66 @@
+package gg.airplane.flare.collectors;
+
+import co.technove.flare.Flare;
+import co.technove.flare.internal.FlareInternal;
+import co.technove.flare.live.CollectorData;
+import co.technove.flare.live.EventCollector;
+import co.technove.flare.live.LiveEvent;
+import co.technove.flare.live.category.GraphCategory;
+import co.technove.flare.live.formatter.DataFormatter;
+import com.google.common.collect.ImmutableMap;
+import com.sun.management.GarbageCollectionNotificationInfo;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.Notification;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationListener;
+import javax.management.openmbean.CompositeData;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+
+public class GCEventCollector extends EventCollector implements NotificationListener {
+
+ private static final CollectorData MINOR_GC = new CollectorData("builtin:gc:minor", "Minor GC", "A small pause in the program to allow Garbage Collection to run.", DataFormatter.MILLISECONDS, GraphCategory.SYSTEM);
+ private static final CollectorData MAJOR_GC = new CollectorData("builtin:gc:major", "Major GC", "A large pause in the program to allow Garbage Collection to run.", DataFormatter.MILLISECONDS, GraphCategory.SYSTEM);
+ private static final CollectorData UNKNOWN_GC = new CollectorData("builtin:gc:generic", "Major GC", "A run of the Garbage Collection.", DataFormatter.MILLISECONDS, GraphCategory.SYSTEM);
+
+ public GCEventCollector() {
+ super(MINOR_GC, MAJOR_GC, UNKNOWN_GC);
+ }
+
+ private static CollectorData fromString(String string) {
+ if (string.endsWith("minor GC")) {
+ return MINOR_GC;
+ } else if (string.endsWith("major GC")) {
+ return MAJOR_GC;
+ }
+ return UNKNOWN_GC;
+ }
+
+ @Override
+ public void start(Flare flare) {
+ for (GarbageCollectorMXBean garbageCollectorBean : ManagementFactory.getGarbageCollectorMXBeans()) {
+ NotificationEmitter notificationEmitter = (NotificationEmitter) garbageCollectorBean;
+ notificationEmitter.addNotificationListener(this, null, null);
+ }
+ }
+
+ @Override
+ public void stop(Flare flare) {
+ for (GarbageCollectorMXBean garbageCollectorBean : ManagementFactory.getGarbageCollectorMXBeans()) {
+ NotificationEmitter notificationEmitter = (NotificationEmitter) garbageCollectorBean;
+ try {
+ notificationEmitter.removeNotificationListener(this);
+ } catch (ListenerNotFoundException e) {
+ }
+ }
+ }
+
+ @Override
+ public void handleNotification(Notification notification, Object o) {
+ if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
+ GarbageCollectionNotificationInfo gcInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
+ reportEvent(new LiveEvent(fromString(gcInfo.getGcAction()), System.currentTimeMillis(), (int) gcInfo.getGcInfo().getDuration(), ImmutableMap.of()));
+ }
+ }
+}
diff --git a/src/main/java/gg/airplane/flare/collectors/StatCollector.java b/src/main/java/gg/airplane/flare/collectors/StatCollector.java
new file mode 100644
index 0000000000000000000000000000000000000000..5444a587a28664ebd8aaac98ee51616c078e54dd
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/collectors/StatCollector.java
@@ -0,0 +1,41 @@
+package gg.airplane.flare.collectors;
+
+import co.technove.flare.live.CollectorData;
+import co.technove.flare.live.LiveCollector;
+import co.technove.flare.live.category.GraphCategory;
+import co.technove.flare.live.formatter.DataFormatter;
+import com.sun.management.OperatingSystemMXBean;
+import oshi.SystemInfo;
+import oshi.hardware.CentralProcessor;
+
+import java.lang.management.ManagementFactory;
+import java.time.Duration;
+
+public class StatCollector extends LiveCollector {
+
+ private static final CollectorData CPU = new CollectorData("builtin:stat:cpu", "CPU Load", "The total amount of CPU usage across all cores.", DataFormatter.PERCENT, GraphCategory.SYSTEM);
+ private static final CollectorData CPU_PROCESS = new CollectorData("builtin:stat:cpu_process", "Process CPU", "The amount of CPU being used by this process.", DataFormatter.PERCENT, GraphCategory.SYSTEM);
+ private static final CollectorData MEMORY = new CollectorData("builtin:stat:memory_used", "Memory", "The amount of memory being used currently.", DataFormatter.BYTES, GraphCategory.SYSTEM);
+ private static final CollectorData MEMORY_TOTAL = new CollectorData("builtin:stat:memory_total", "Memory Total", "The total amount of memory allocated.", DataFormatter.BYTES, GraphCategory.SYSTEM);
+
+ private final OperatingSystemMXBean bean;
+ private final CentralProcessor processor;
+
+ public StatCollector() {
+ super(CPU, CPU_PROCESS, MEMORY, MEMORY_TOTAL);
+ this.interval = Duration.ofSeconds(5);
+
+ this.bean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
+ this.processor = new SystemInfo().getHardware().getProcessor();
+ }
+
+ @Override
+ public void run() {
+ Runtime runtime = Runtime.getRuntime();
+
+ this.report(CPU, this.processor.getSystemLoadAverage(1)[0] / 100); // percentage
+ this.report(CPU_PROCESS, this.bean.getProcessCpuLoad());
+ this.report(MEMORY, runtime.totalMemory() - runtime.freeMemory());
+ this.report(MEMORY_TOTAL, runtime.totalMemory());
+ }
+}
diff --git a/src/main/java/gg/airplane/flare/collectors/TPSCollector.java b/src/main/java/gg/airplane/flare/collectors/TPSCollector.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7552c92552efc2193fc015b56089d39ce2f6cf0
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/collectors/TPSCollector.java
@@ -0,0 +1,31 @@
+package gg.airplane.flare.collectors;
+
+import co.technove.flare.live.CollectorData;
+import co.technove.flare.live.LiveCollector;
+import co.technove.flare.live.formatter.SuffixFormatter;
+import gg.airplane.flare.CustomCategories;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
+
+import java.time.Duration;
+import java.util.Arrays;
+
+public class TPSCollector extends LiveCollector {
+ private static final CollectorData TPS = new CollectorData("airplane:tps", "TPS", "Ticks per second, or how fast the server updates. For a smooth server this should be a constant 20TPS.", SuffixFormatter.of("TPS"), CustomCategories.MC_PERF);
+ private static final CollectorData MSPT = new CollectorData("airplane:mspt", "MSPT", "Milliseconds per tick, which can show how well your server is performing. This value should always be under 50mspt.", SuffixFormatter.of("mspt"), CustomCategories.MC_PERF);
+
+ public TPSCollector() {
+ super(TPS, MSPT);
+
+ this.interval = Duration.ofSeconds(5);
+ }
+
+ @Override
+ public void run() {
+ long[] times = MinecraftServer.getServer().tickTimes5s.getTimes();
+ double mspt = ((double) Arrays.stream(times).sum() / (double) times.length) * 1.0E-6D;
+
+ this.report(TPS, Math.min(20D, Math.round(Bukkit.getServer().getTPS()[0] * 100d) / 100d));
+ this.report(MSPT, (double) Math.round(mspt * 100d) / 100d);
+ }
+}
diff --git a/src/main/java/gg/airplane/flare/collectors/WorldCountCollector.java b/src/main/java/gg/airplane/flare/collectors/WorldCountCollector.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b1ca40d99b04db9dca522aa42eb68c761c73037
--- /dev/null
+++ b/src/main/java/gg/airplane/flare/collectors/WorldCountCollector.java
@@ -0,0 +1,45 @@
+package gg.airplane.flare.collectors;
+
+import co.technove.flare.live.CollectorData;
+import co.technove.flare.live.LiveCollector;
+import co.technove.flare.live.formatter.SuffixFormatter;
+import gg.airplane.flare.CustomCategories;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.craftbukkit.CraftWorld;
+
+import java.time.Duration;
+
+public class WorldCountCollector extends LiveCollector {
+
+ private static final CollectorData PLAYER_COUNT = new CollectorData("airplane:world:playercount", "Player Count", "The number of players currently on the server.", new SuffixFormatter(" Player", " Players"), CustomCategories.ENTITIES_AND_CHUNKS);
+ private static final CollectorData ENTITY_COUNT = new CollectorData("airplane:world:entitycount", "Entity Count", "The number of entities in all worlds", new SuffixFormatter(" Entity", " Entities"), CustomCategories.ENTITIES_AND_CHUNKS);
+ private static final CollectorData CHUNK_COUNT = new CollectorData("airplane:world:chunkcount", "Chunk Count", "The number of chunks currently loaded.", new SuffixFormatter(" Chunk", " Chunks"), CustomCategories.ENTITIES_AND_CHUNKS);
+ private static final CollectorData TILE_ENTITY_COUNT = new CollectorData("airplane:world:blockentitycount", "Block Entity Count", "The number of block entities currently loaded.", new SuffixFormatter(" Block Entity", " Block Entities"), CustomCategories.ENTITIES_AND_CHUNKS);
+
+ public WorldCountCollector() {
+ super(PLAYER_COUNT, ENTITY_COUNT, CHUNK_COUNT, TILE_ENTITY_COUNT);
+
+ this.interval = Duration.ofSeconds(5);
+ }
+
+ @Override
+ public void run() {
+ int entities = 0;
+ int chunkCount = 0;
+ int tileEntityCount = 0;
+
+ if (!Bukkit.isStopping()) {
+ for (World world : Bukkit.getWorlds()) {
+ entities += ((CraftWorld) world).getHandle().entityManager.getEntityGetter().getCount();
+ chunkCount += world.getChunkCount();
+ tileEntityCount += world.getTileEntityCount();
+ }
+ }
+
+ this.report(PLAYER_COUNT, Bukkit.getOnlinePlayers().size());
+ this.report(ENTITY_COUNT, entities);
+ this.report(CHUNK_COUNT, chunkCount);
+ this.report(TILE_ENTITY_COUNT, tileEntityCount);
+ }
+}
diff --git a/src/main/java/gg/airplane/structs/FluidDirectionCache.java b/src/main/java/gg/airplane/structs/FluidDirectionCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa8467b9dda1f7707e41f50ac7b3e9d7343723ec
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/FluidDirectionCache.java
@@ -0,0 +1,136 @@
+package gg.airplane.structs;
+
+import it.unimi.dsi.fastutil.HashCommon;
+
+/**
+ * This is a replacement for the cache used in FluidTypeFlowing.
+ * The requirements for the previous cache were:
+ * - Store 200 entries
+ * - Look for the flag in the cache
+ * - If it exists, move to front of cache
+ * - If it doesn't exist, remove last entry in cache and insert in front
+ *
+ * This class accomplishes something similar, however has a few different
+ * requirements put into place to make this more optimize:
+ *
+ * - maxDistance is the most amount of entries to be checked, instead
+ * of having to check the entire list.
+ * - In combination with that, entries are all tracked by age and how
+ * frequently they're used. This enables us to remove old entries,
+ * without constantly shifting any around.
+ *
+ * Usage of the previous map would have to reset the head every single usage,
+ * shifting the entire map. Here, nothing happens except an increment when
+ * the cache is hit, and when it needs to replace an old element only a single
+ * element is modified.
+ */
+public class FluidDirectionCache<T> {
+
+ private static class FluidDirectionEntry<T> {
+ private final T data;
+ private final boolean flag;
+ private int uses = 0;
+ private int age = 0;
+
+ private FluidDirectionEntry(T data, boolean flag) {
+ this.data = data;
+ this.flag = flag;
+ }
+
+ public int getValue() {
+ return this.uses - (this.age >> 1); // age isn't as important as uses
+ }
+
+ public void incrementUses() {
+ this.uses = this.uses + 1 & Integer.MAX_VALUE;
+ }
+
+ public void incrementAge() {
+ this.age = this.age + 1 & Integer.MAX_VALUE;
+ }
+ }
+
+ private final FluidDirectionEntry[] entries;
+ private final int mask;
+ private final int maxDistance; // the most amount of entries to check for a value
+
+ public FluidDirectionCache(int size) {
+ int arraySize = HashCommon.nextPowerOfTwo(size);
+ this.entries = new FluidDirectionEntry[arraySize];
+ this.mask = arraySize - 1;
+ this.maxDistance = Math.min(arraySize, 4);
+ }
+
+ public Boolean getValue(T data) {
+ FluidDirectionEntry curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) {
+ return null;
+ } else if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return curr.flag;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return curr.flag;
+ } else if (++checked >= this.maxDistance) {
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ public void putValue(T data, boolean flag) {
+ FluidDirectionEntry<T> curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) {
+ this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add
+ return;
+ } else if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return;
+ } else if (++checked >= this.maxDistance) {
+ this.forceAdd(data, flag);
+ return;
+ }
+ }
+
+ this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add
+ }
+
+ private void forceAdd(T data, boolean flag) {
+ int expectedPos = HashCommon.mix(data.hashCode()) & this.mask;
+
+ int toRemovePos = expectedPos;
+ FluidDirectionEntry entryToRemove = this.entries[toRemovePos];
+
+ for (int i = expectedPos + 1; i < expectedPos + this.maxDistance; i++) {
+ int pos = i & this.mask;
+ FluidDirectionEntry entry = this.entries[pos];
+ if (entry.getValue() < entryToRemove.getValue()) {
+ toRemovePos = pos;
+ entryToRemove = entry;
+ }
+
+ entry.incrementAge(); // use this as a mechanism to age the other entries
+ }
+
+ // remove the least used/oldest entry
+ this.entries[toRemovePos] = new FluidDirectionEntry(data, flag);
+ }
+}
diff --git a/src/main/java/gg/airplane/structs/ItemListWithBitset.java b/src/main/java/gg/airplane/structs/ItemListWithBitset.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b7a4ee47f4445d7f2ac91d3a73ae113edbdddb2
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/ItemListWithBitset.java
@@ -0,0 +1,114 @@
+package gg.airplane.structs;
+
+import net.minecraft.core.NonNullList;
+import net.minecraft.world.item.ItemStack;
+import org.apache.commons.lang.Validate;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ItemListWithBitset extends AbstractList<ItemStack> {
+ public static ItemListWithBitset fromList(List<ItemStack> list) {
+ if (list instanceof ItemListWithBitset ours) {
+ return ours;
+ }
+ return new ItemListWithBitset(list);
+ }
+
+ private static ItemStack[] createArray(int size) {
+ ItemStack[] array = new ItemStack[size];
+ Arrays.fill(array, ItemStack.EMPTY);
+ return array;
+ }
+
+ private final ItemStack[] items;
+
+ private long bitSet = 0;
+ private final long allBits;
+
+ private static class OurNonNullList extends NonNullList<ItemStack> {
+ protected OurNonNullList(List<ItemStack> delegate) {
+ super(delegate, ItemStack.EMPTY);
+ }
+ }
+
+ public final NonNullList<ItemStack> nonNullList = new OurNonNullList(this);
+
+ private ItemListWithBitset(List<ItemStack> list) {
+ this(list.size());
+
+ for (int i = 0; i < list.size(); i++) {
+ this.set(i, list.get(i));
+ }
+ }
+
+ public ItemListWithBitset(int size) {
+ Validate.isTrue(size < Long.BYTES * 8, "size is too large");
+
+ this.items = createArray(size);
+ this.allBits = ((1L << size) - 1);
+ }
+
+ public boolean isCompletelyEmpty() {
+ return this.bitSet == 0;
+ }
+
+ public boolean hasFullStacks() {
+ return (this.bitSet & this.allBits) == allBits;
+ }
+
+ @Override
+ public ItemStack set(int index, @NotNull ItemStack itemStack) {
+ ItemStack existing = this.items[index];
+
+ this.items[index] = itemStack;
+
+ if (itemStack == ItemStack.EMPTY) {
+ this.bitSet &= ~(1L << index);
+ } else {
+ this.bitSet |= 1L << index;
+ }
+
+ return existing;
+ }
+
+ @NotNull
+ @Override
+ public ItemStack get(int var0) {
+ return this.items[var0];
+ }
+
+ @Override
+ public int size() {
+ return this.items.length;
+ }
+
+ @Override
+ public void clear() {
+ Arrays.fill(this.items, ItemStack.EMPTY);
+ }
+
+ // these are unsupported for block inventories which have a static size
+ @Override
+ public void add(int var0, ItemStack var1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ItemStack remove(int var0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return "ItemListWithBitset{" +
+ "items=" + Arrays.toString(items) +
+ ", bitSet=" + Long.toString(bitSet, 2) +
+ ", allBits=" + Long.toString(allBits, 2) +
+ ", size=" + this.items.length +
+ '}';
+ }
+}
diff --git a/src/main/java/gg/airplane/structs/Long2FloatAgingCache.java b/src/main/java/gg/airplane/structs/Long2FloatAgingCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7f297ebb569f7c1f205e967ca485be70013a714
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/Long2FloatAgingCache.java
@@ -0,0 +1,119 @@
+package gg.airplane.structs;
+
+import it.unimi.dsi.fastutil.HashCommon;
+
+/**
+ * A replacement for the cache used in Biome.
+ */
+public class Long2FloatAgingCache {
+
+ private static class AgingEntry {
+ private long data;
+ private float value;
+ private int uses = 0;
+ private int age = 0;
+
+ private AgingEntry(long data, float value) {
+ this.data = data;
+ this.value = value;
+ }
+
+ public void replace(long data, float flag) {
+ this.data = data;
+ this.value = flag;
+ }
+
+ public int getValue() {
+ return this.uses - (this.age >> 1); // age isn't as important as uses
+ }
+
+ public void incrementUses() {
+ this.uses = this.uses + 1 & Integer.MAX_VALUE;
+ }
+
+ public void incrementAge() {
+ this.age = this.age + 1 & Integer.MAX_VALUE;
+ }
+ }
+
+ private final AgingEntry[] entries;
+ private final int mask;
+ private final int maxDistance; // the most amount of entries to check for a value
+
+ public Long2FloatAgingCache(int size) {
+ int arraySize = HashCommon.nextPowerOfTwo(size);
+ this.entries = new AgingEntry[arraySize];
+ this.mask = arraySize - 1;
+ this.maxDistance = Math.min(arraySize, 4);
+ }
+
+ public float getValue(long data) {
+ AgingEntry curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(HashCommon.long2int(data)) & this.mask]) == null) {
+ return Float.NaN;
+ } else if (data == curr.data) {
+ curr.incrementUses();
+ return curr.value;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data == curr.data) {
+ curr.incrementUses();
+ return curr.value;
+ } else if (++checked >= this.maxDistance) {
+ break;
+ }
+ }
+
+ return Float.NaN;
+ }
+
+ public void putValue(long data, float value) {
+ AgingEntry curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(HashCommon.long2int(data)) & this.mask]) == null) {
+ this.entries[pos] = new AgingEntry(data, value); // add
+ return;
+ } else if (data == curr.data) {
+ curr.incrementUses();
+ return;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data == curr.data) {
+ curr.incrementUses();
+ return;
+ } else if (++checked >= this.maxDistance) {
+ this.forceAdd(data, value);
+ return;
+ }
+ }
+
+ this.entries[pos] = new AgingEntry(data, value); // add
+ }
+
+ private void forceAdd(long data, float value) {
+ int expectedPos = HashCommon.mix(HashCommon.long2int(data)) & this.mask;
+ AgingEntry entryToRemove = this.entries[expectedPos];
+
+ for (int i = expectedPos + 1; i < expectedPos + this.maxDistance; i++) {
+ int pos = i & this.mask;
+ AgingEntry entry = this.entries[pos];
+ if (entry.getValue() < entryToRemove.getValue()) {
+ entryToRemove = entry;
+ }
+
+ entry.incrementAge(); // use this as a mechanism to age the other entries
+ }
+
+ // remove the least used/oldest entry
+ entryToRemove.replace(data, value);
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java
index 98ca1199a823cdf55b913396ce0a24554e85f116..b16e65fa8be40f6c938c8c183c9bca7c13acc9e2 100644
--- a/src/main/java/io/papermc/paper/util/CollisionUtil.java
+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java
@@ -547,6 +547,18 @@ public final class CollisionUtil {
return ret;
}
+ public static boolean getEntityCollisionsWithCache(final net.minecraft.world.level.Level getter, Entity entity, AABB aabb, List<AABB> into,
+ final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ if (checkOnly) {
+ return entity.collisionCache.getCollisions(getter, aabb, into, collidesWithUnloaded, checkOnly, predicate) ||
+ getEntityHardCollisions(getter, entity, aabb, into, checkOnly, null);
+ } else {
+ return entity.collisionCache.getCollisions(getter, aabb, into, collidesWithUnloaded, checkOnly, predicate) |
+ getEntityHardCollisions(getter, entity, aabb, into, checkOnly, null);
+ }
+ }
+
public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb,
final List<AABB> into, final boolean checkOnly, final Predicate<Entity> predicate) {
if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) {
diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
index 505546d32eea4682452dbac02311433157f6a30e..5c7b9ad379f3c272e15648dd16f4df9245d927da 100644
--- a/src/main/java/net/minecraft/Util.java
+++ b/src/main/java/net/minecraft/Util.java
@@ -344,6 +344,10 @@ public class Util {
}
public static <V> CompletableFuture<List<V>> sequence(List<? extends CompletableFuture<? extends V>> futures) {
+ // Airplane start - faster sequencing without all of.. _that_
+ return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
+ .thenApply(unused -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
+ /*
return futures.stream().reduce(CompletableFuture.completedFuture(Lists.newArrayList()), (completableFuture, completableFuture2) -> {
return completableFuture2.thenCombine(completableFuture, (object, list) -> {
List<V> list2 = Lists.newArrayListWithCapacity(list.size() + 1);
@@ -359,6 +363,8 @@ public class Util {
return list3;
});
});
+ */
+ // Airplane end
}
public static <V> CompletableFuture<List<V>> sequenceFailFast(List<? extends CompletableFuture<? extends V>> futures) {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 7636b0ae9031e06fd8b4a0c359a45a17e6e075de..49821f441886b4de2cac5b1b13981e2161992f98 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1726,7 +1726,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@DontObfuscate
public String getServerModName() {
- return "Paper"; // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
+ return "Airplane"; // Airplane // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
}
public SystemReport fillSystemReport(SystemReport details) {
@@ -2302,6 +2302,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public ProfilerFiller getProfiler() {
+ if (gg.airplane.AirplaneConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE;
return this.profiler;
}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index b6ee0e709b0f0529b99567bc9b8fb6bfd99bcd8e..e2901132b78126c0a4eb04363dfe6a0dccd1313f 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -234,6 +234,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // load mappings for stacktrace deobf and etc.
io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
// Paper end
+ gg.airplane.AirplaneConfig.load(); // Airplane - config
+ gg.airplane.commands.AirplaneCommands.init(); // Airplane - command
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 65090afbb510231c05db3132ba416502d188f1c0..4249f24f4c43c55eb13ca85be6e0b8871a1cace9 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -2413,8 +2413,28 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
return ChunkMap.this.level.getServer().getScaledTrackingDistance(initialDistance);
}
+ private static int getHighestRange(Entity parent, int highest) {
+ List<Entity> passengers = parent.getPassengers();
+
+ for (int i = 0, size = passengers.size(); i < size; i++) {
+ Entity entity = passengers.get(i);
+ int range = entity.getType().clientTrackingRange() * 16;
+ range = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, range); // Paper
+
+ if (range > highest) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic // Tuinity - not anymore!
+ highest = range;
+ }
+
+ highest = getHighestRange(entity, highest);
+ }
+
+ return highest;
+ }
+
private int getEffectiveRange() {
int i = this.range;
+ // Airplane start - remove iterators and streams
+ /*
Iterator iterator = this.entity.getIndirectPassengers().iterator();
while (iterator.hasNext()) {
@@ -2426,6 +2446,9 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
i = j;
}
}
+ */
+ i = getHighestRange(this.entity, i);
+ // Airplane end
return this.scaledRange(i);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 7470f3ba66c2e894b5a5b0ba392ecabf8b04aff9..35f27e9a7c82eaec5b4a1a71696dac8485b2cd6d 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -983,6 +983,7 @@ public class ServerChunkCache extends ChunkSource {
}
// Paper end - optimize isOutisdeRange
this.level.getProfiler().push("pollingChunks");
+ this.level.resetIceAndSnowTick(); // Airplane - reset ice & snow tick random
int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
boolean flag2 = level.ticksPerAnimalSpawns != 0L && worlddata.getGameTime() % level.ticksPerAnimalSpawns == 0L; // CraftBukkit
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index b7c9294fdd3d799d410afba4a1118aa371c98533..c71bc00973899feec0ec5530bf3d237928810cf4 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -174,6 +174,7 @@ public class ServerEntity {
boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround() && !(com.destroystokyo.paper.PaperConfig.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync
+ if (flag2 || flag3 || this.entity instanceof AbstractArrow) { // Airplane
if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
if (flag2) {
packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.isOnGround());
@@ -183,6 +184,7 @@ public class ServerEntity {
} else {
packet1 = new ClientboundMoveEntityPacket.PosRot(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), (byte) i, (byte) j, this.entity.isOnGround());
}
+ } // Airplane
} else {
this.wasOnGround = this.entity.isOnGround();
this.teleportDelay = 0;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index a62abcb2e05198001516cf2402dc7ead09761905..ce146c771b8e38eb24e9372058f238920ec88882 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -772,7 +772,20 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
gameprofilerfiller.push("tick");
- this.guardEntityTick(this::tickNonPassenger, entity);
+ // Airplane start - copied from this.guardEntityTick
+ try {
+ this.tickNonPassenger(entity); // Airplane - changed
+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
+ // Paper start - Prevent tile entity and entity crashes
+ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
+ MinecraftServer.LOGGER.error(msg, throwable);
+ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable)));
+ entity.discard();
+ // Paper end
+ }
+ // Airplane end
gameprofilerfiller.pop();
}
}
@@ -830,9 +843,11 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
// Paper start - optimise random block ticking
private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
- private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom();
+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(); public java.util.Random getThreadUnsafeRandom() { return this.randomTickRandom; } // Airplane - getter
// Paper end
+ private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // Airplane
+
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
ChunkPos chunkcoordintpair = chunk.getPos();
boolean flag = this.isRaining();
@@ -843,7 +858,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
gameprofilerfiller.push("thunder");
final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
- if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder
+ if (!this.paperConfig.disableThunder && flag && this.isThundering() && chunk.shouldDoLightning(this.random)) { // Paper - Disable thunder // Airplane - replace random with shouldDoLightning
blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
if (this.isRainingAt(blockposition)) {
DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
@@ -867,7 +882,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
gameprofilerfiller.popPush("iceandsnow");
- if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking
+ if (!this.paperConfig.disableIceAndSnow && (this.currentIceAndSnowTick++ & 15) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking // Airplane - optimize further random ticking
// Paper start - optimise chunk ticking
this.getRandomBlockPosition(j, 0, k, 15, blockposition);
int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1;
diff --git a/src/main/java/net/minecraft/world/CompoundContainer.java b/src/main/java/net/minecraft/world/CompoundContainer.java
index 087ae3a6b49872a3580eb1a572bdbc493711a77a..5ef8657197beea06c1dcad6a32968c56a823b182 100644
--- a/src/main/java/net/minecraft/world/CompoundContainer.java
+++ b/src/main/java/net/minecraft/world/CompoundContainer.java
@@ -1,5 +1,6 @@
package net.minecraft.world;
+import net.minecraft.core.Direction; // Airplane
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@@ -72,6 +73,23 @@ public class CompoundContainer implements Container {
this.container2 = second;
}
+ // Airplane start
+ @Override
+ public boolean hasEmptySlot(Direction enumdirection) {
+ return this.container1.hasEmptySlot(null) || this.container2.hasEmptySlot(null);
+ }
+
+ @Override
+ public boolean isCompletelyFull(Direction enumdirection) {
+ return this.container1.isCompletelyFull(null) && this.container2.isCompletelyFull(null);
+ }
+
+ @Override
+ public boolean isCompletelyEmpty(Direction enumdirection) {
+ return this.container1.isCompletelyEmpty(null) && this.container2.isCompletelyEmpty(null);
+ }
+ // Airplane end
+
@Override
public int getContainerSize() {
return this.container1.getContainerSize() + this.container2.getContainerSize();
diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java
index 7437f01ca8f416e2c9150250e324af4725a4efb6..bdcd0e38a3ba904811112f41d8bfbfc0902ef190 100644
--- a/src/main/java/net/minecraft/world/Container.java
+++ b/src/main/java/net/minecraft/world/Container.java
@@ -1,6 +1,8 @@
package net.minecraft.world;
import java.util.Set;
+
+import net.minecraft.core.Direction; // Airplane
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@@ -9,6 +11,63 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity;
// CraftBukkit end
public interface Container extends Clearable {
+ // Airplane start - allow the inventory to override and optimize these frequent calls
+ default boolean hasEmptySlot(@org.jetbrains.annotations.Nullable Direction enumdirection) { // there is a slot with 0 items in it
+ if (this instanceof WorldlyContainer worldlyContainer) {
+ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
+ if (this.getItem(i).isEmpty()) {
+ return true;
+ }
+ }
+ } else {
+ int size = this.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ if (this.getItem(i).isEmpty()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ default boolean isCompletelyFull(@org.jetbrains.annotations.Nullable Direction enumdirection) { // every stack is maxed
+ if (this instanceof WorldlyContainer worldlyContainer) {
+ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
+ ItemStack itemStack = this.getItem(i);
+ if (itemStack.getCount() < itemStack.getMaxStackSize()) {
+ return false;
+ }
+ }
+ } else {
+ int size = this.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ ItemStack itemStack = this.getItem(i);
+ if (itemStack.getCount() < itemStack.getMaxStackSize()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ default boolean isCompletelyEmpty(@org.jetbrains.annotations.Nullable Direction enumdirection) {
+ if (this instanceof WorldlyContainer worldlyContainer) {
+ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
+ if (!this.getItem(i).isEmpty()) {
+ return false;
+ }
+ }
+ } else {
+ int size = this.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ if (!this.getItem(i).isEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ // Airplane end
int LARGE_MAX_STACK_SIZE = 64;
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index e17bda0d13bae337cfad5ae31b118aa7a85499fc..d35cdcf2ee7568ad5caf52588db54e9c6f607d4b 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -338,6 +338,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
protected int numCollisions = 0; // Paper
public void inactiveTick() { }
// Spigot end
+ // Airplane start
+ public int activatedPriority = gg.airplane.AirplaneConfig.maximumActivationPrio; // golf score
+ public final BlockPos.MutableBlockPos cachedBlockPos = new BlockPos.MutableBlockPos(); // used where needed
+ public final gg.airplane.entity.CollisionCache collisionCache = new gg.airplane.entity.CollisionCache(this);
+ // Airplane end
public float getBukkitYaw() {
return this.yRot;
@@ -362,17 +367,36 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
this.isLegacyTrackingEntity = isLegacyTrackingEntity;
}
+ private org.spigotmc.TrackingRange.TrackingRangeType getFurthestEntity(Entity entity, net.minecraft.server.level.ChunkMap chunkMap, org.spigotmc.TrackingRange.TrackingRangeType type, int range) {
+ List<Entity> passengers = entity.getPassengers();
+ for (int i = 0, size = passengers.size(); i < size; i++) {
+ Entity passenger = passengers.get(i);
+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
+ if (passengerRange > range) {
+ type = passengerType;
+ range = passengerRange;
+ }
+
+ type = this.getFurthestEntity(passenger, chunkMap, type, range);
+ }
+
+ return type;
+ }
+
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInTrackRange() {
// determine highest range of passengers
if (this.passengers.isEmpty()) {
return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()]
.getObjectsInRange(MCUtil.getCoordinateKey(this));
}
- Iterable<Entity> passengers = this.getIndirectPassengers();
+ //Iterable<Entity> passengers = this.getIndirectPassengers(); // Airplane
net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap;
org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType;
int range = chunkMap.getEntityTrackerRange(type.ordinal());
+ // Airplane start - use getFurthestEntity to skip getIndirectPassengers
+ /*
for (Entity passenger : passengers) {
org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
@@ -381,6 +405,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
range = passengerRange;
}
}
+ */
+ type = this.getFurthestEntity(this, chunkMap, type, range);
+ // Airplane end
return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this));
}
@@ -1264,8 +1291,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
}
}
- io.papermc.paper.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, true,
- false, false, null, null);
+ // Airplane start - use collision cache
+ io.papermc.paper.util.CollisionUtil.getEntityCollisionsWithCache(world, this, collisionBox, potentialCollisions, false, true,
+ false, false, null);
+ // Airplane end
if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) {
io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions);
@@ -2440,10 +2469,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f);
// Paper start
- return io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this.level, this, axisalignedbb, null,
- false, false, false, true, (iblockdata, blockposition) -> {
- return iblockdata.isSuffocating(this.level, blockposition);
- });
+ return io.papermc.paper.util.CollisionUtil.getEntityCollisionsWithCache(this.level, this, axisalignedbb, null, // Airplane - use cache
+ false, false, false, true, this.level.isAlmostSuffocating); // Airplane - don't allocate lambda here
// Paper end
}
}
@@ -3830,16 +3857,18 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
}
public boolean updateFluidHeightAndDoFluidPushing(Tag<Fluid> tag, double d0) {
- if (this.touchingUnloadedChunk()) {
+ if (false && this.touchingUnloadedChunk()) { // Airplane - cost of a lookup here is the same cost as below, so skip
return false;
} else {
AABB axisalignedbb = this.getBoundingBox().deflate(0.001D);
- int i = Mth.floor(axisalignedbb.minX);
- int j = Mth.ceil(axisalignedbb.maxX);
- int k = Mth.floor(axisalignedbb.minY);
- int l = Mth.ceil(axisalignedbb.maxY);
- int i1 = Mth.floor(axisalignedbb.minZ);
- int j1 = Mth.ceil(axisalignedbb.maxZ);
+ // Airplane start - rename
+ int minBlockX = Mth.floor(axisalignedbb.minX);
+ int maxBlockX = Mth.ceil(axisalignedbb.maxX);
+ int minBlockY = Mth.floor(axisalignedbb.minY);
+ int maxBlockY = Mth.ceil(axisalignedbb.maxY);
+ int minBlockZ = Mth.floor(axisalignedbb.minZ);
+ int maxBlockZ = Mth.ceil(axisalignedbb.maxZ);
+ // Airplane end
double d1 = 0.0D;
boolean flag = this.isPushedByFluid();
boolean flag1 = false;
@@ -3847,14 +3876,62 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
int k1 = 0;
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- for (int l1 = i; l1 < j; ++l1) {
- for (int i2 = k; i2 < l; ++i2) {
- for (int j2 = i1; j2 < j1; ++j2) {
- blockposition_mutableblockposition.set(l1, i2, j2);
- FluidState fluid = this.level.getFluidState(blockposition_mutableblockposition);
+ // Airplane start - based off CollisionUtil.getCollisionsForBlocksOrWorldBorder
+ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this.level);
+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxSection(this.level);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return false;
+ }
+
+ int minYIterate = Math.max(minBlock, minBlockY);
+ int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ int minChunkX = minBlockX >> 4;
+ int maxChunkX = maxBlockX >> 4;
+
+ int minChunkZ = minBlockZ >> 4;
+ int maxChunkZ = maxBlockZ >> 4;
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 16; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 16; // coordinate in chunk
+
+ net.minecraft.world.level.chunk.ChunkAccess chunk = this.level.getChunkIfLoadedImmediately(currChunkX, currChunkZ);
+ if (chunk == null) {
+ return false; // if we're touching an unloaded chunk then it's false
+ }
+
+ net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
+
+ for (int currY = minYIterate; currY < maxYIterate; ++currY) {
+ net.minecraft.world.level.chunk.LevelChunkSection section = sections[(currY >> 4) - minSection];
+
+ if (section == null || section.isEmpty() || section.fluidStateCount == 0) { // if no fluids, nothing in this section
+ // empty
+ // skip to next section
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
+ continue;
+ }
+
+ net.minecraft.world.level.chunk.PalettedContainer<BlockState> blocks = section.states;
+
+ for (int currZ = minZ; currZ < maxZ; ++currZ) {
+ for (int currX = minX; currX < maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8);
+ FluidState fluid = blocks.get(localBlockIndex).getFluidState();
if (fluid.is(tag)) {
- double d2 = (double) ((float) i2 + fluid.getHeight(this.level, blockposition_mutableblockposition));
+ blockposition_mutableblockposition.set((currChunkX << 4) + currX, currY, (currChunkZ << 4) + currZ);
+ double d2 = (double) ((float) currY + fluid.getHeight(this.level, blockposition_mutableblockposition));
if (d2 >= axisalignedbb.minY) {
flag1 = true;
@@ -3871,9 +3948,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
}
}
}
+ }
+ }
}
}
}
+ // Airplane end
if (vec3d.length() > 0.0D) {
if (k1 > 0) {
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
index 1c446dba5de89698397041ee38a2e1a00bec8a56..03371c99c34ba4b2ffde3f6da36f171b582e3c3f 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -294,6 +294,7 @@ public class EntityType<T extends Entity> implements EntityTypeTest<Entity, T> {
return Registry.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(id));
}
+ public boolean dabEnabled = false; // Airplane
// Paper start - add id
public final String id;
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index cbdff14b26f67b5040c13659f9d64d9ec4c7eaed..71fefe3956c1e4458dd4b441f90902a3ed0cdf24 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -142,7 +142,6 @@ import org.bukkit.event.entity.EntityTeleportEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
// CraftBukkit end
-import co.aikar.timings.MinecraftTimings; // Paper
public abstract class LivingEntity extends Entity {
@@ -1828,6 +1827,20 @@ public abstract class LivingEntity extends Entity {
return this.lastClimbablePos;
}
+
+ // Airplane start
+ private boolean cachedOnClimable = false;
+ private BlockPos lastClimbingPosition = null;
+
+ public boolean onClimableCached() {
+ if (!this.blockPosition().equals(this.lastClimbingPosition)) {
+ this.cachedOnClimable = this.onClimbable();
+ this.lastClimbingPosition = this.blockPosition();
+ }
+ return this.cachedOnClimable;
+ }
+ // Airplane end
+
public boolean onClimbable() {
if (this.isSpectator()) {
return false;
@@ -3444,7 +3457,10 @@ public abstract class LivingEntity extends Entity {
Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ());
// Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists
- return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr
+ // Airplane start
+ //return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr
+ return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.rayTraceDirect(vec3d, vec3d1, net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.BlockHitResult.Type.MISS;
+ // Airplane end
}
}
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 8a864238e154e2131834d013652746b7e7a78c97..6051fd771dd989f2903b854b6564252a847a74e3 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -207,10 +207,10 @@ public abstract class Mob extends LivingEntity {
@Override
public void inactiveTick() {
super.inactiveTick();
- if (this.goalSelector.inactiveTick()) {
+ if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Airplane - pass activated priroity
this.goalSelector.tick();
}
- if (this.targetSelector.inactiveTick()) {
+ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Airplane - pass activated priority
this.targetSelector.tick();
}
}
@@ -801,16 +801,16 @@ public abstract class Mob extends LivingEntity {
int i = this.getType().getCategory().getDespawnDistance();
int j = i * i;
- if (d0 > (double) level.paperConfig.hardDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances
+ if (d0 > (double) level.paperConfig.hardDespawnDistanceSq) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances // Airplane
this.discard();
}
int k = this.getType().getCategory().getNoDespawnDistance();
int l = k * k;
- if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > level.paperConfig.softDespawnDistance) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances
+ if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > level.paperConfig.softDespawnDistanceSq) { // CraftBukkit - remove isTypeNotPersistent() check // Paper - custom despawn distances // Airplane
this.discard();
- } else if (d0 < level.paperConfig.softDespawnDistance) { // Paper - custom despawn distances
+ } else if (d0 < level.paperConfig.softDespawnDistanceSq) { // Paper - custom despawn distances // Airplane
this.noActionTime = 0;
}
}
@@ -838,9 +838,11 @@ public abstract class Mob extends LivingEntity {
this.sensing.tick();
this.level.getProfiler().pop();
this.level.getProfiler().push("targetSelector");
+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Airplane - use this to alternate ticking
this.targetSelector.tick();
this.level.getProfiler().pop();
this.level.getProfiler().push("goalSelector");
+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Airplane - use this to alternate ticking
this.goalSelector.tick();
this.level.getProfiler().pop();
this.level.getProfiler().push("navigation");
diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java
index 75022e5888de2730bb29d1e7535ddc63e2c5f713..a7c71fc4e49079d11b9a0537a4f6d20bbbba815b 100644
--- a/src/main/java/net/minecraft/world/entity/ai/Brain.java
+++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java
@@ -76,15 +76,15 @@ public class Brain<E extends LivingEntity> {
@Override
public <T> DataResult<Brain<E>> decode(DynamicOps<T> dynamicOps, MapLike<T> mapLike) {
- MutableObject<DataResult<Builder<Brain.MemoryValue<?>>>> mutableObject = new MutableObject<>(DataResult.success(ImmutableList.builder()));
+ MutableObject<DataResult<Builder<Brain.MemoryValue<?>>>> mutableObject2 = new MutableObject<>(DataResult.success(ImmutableList.builder())); // Airplane - compile error due to decompiler
mapLike.entries().forEach((pair) -> {
DataResult<MemoryModuleType<?>> dataResult = Registry.MEMORY_MODULE_TYPE.parse(dynamicOps, pair.getFirst());
DataResult<? extends Brain.MemoryValue<?>> dataResult2 = dataResult.flatMap((memoryModuleType) -> {
return this.captureRead(memoryModuleType, dynamicOps, (T)pair.getSecond());
});
- mutableObject.setValue(mutableObject.getValue().apply2(Builder::add, dataResult2));
+ mutableObject2.setValue(mutableObject2.getValue().apply2(Builder::add, dataResult2)); // Airplane - compile error due to decompiler
});
- ImmutableList<Brain.MemoryValue<?>> immutableList = mutableObject.getValue().resultOrPartial(Brain.LOGGER::error).map(Builder::build).orElseGet(ImmutableList::of);
+ ImmutableList<Brain.MemoryValue<?>> immutableList = mutableObject2.getValue().resultOrPartial(Brain.LOGGER::error).map(Builder::build).orElseGet(ImmutableList::of); // Airplane - compile error due to decompiler
return DataResult.success(new Brain<>(memoryModules, sensors, immutableList, mutableObject::getValue));
}
@@ -174,7 +174,7 @@ public class Brain<E extends LivingEntity> {
}
public <U> Optional<U> getMemory(MemoryModuleType<U> type) {
- return this.memories.get(type).map(ExpirableValue::getValue);
+ return (Optional<U>) this.memories.get(type).map(ExpirableValue::getValue); // Airplane - compile fix
}
public <U> long getTimeUntilExpiry(MemoryModuleType<U> type) {
@@ -465,7 +465,7 @@ public class Brain<E extends LivingEntity> {
private final Optional<? extends ExpirableValue<U>> value;
static <U> Brain.MemoryValue<U> createUnchecked(MemoryModuleType<U> type, Optional<? extends ExpirableValue<?>> data) {
- return new Brain.MemoryValue<>(type, data);
+ return new Brain.MemoryValue<>(type, (Optional) data); // Airplane - compile fix
}
MemoryValue(MemoryModuleType<U> type, Optional<? extends ExpirableValue<U>> data) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
index 9cbfda029782385d1a7987f5be46d450bd8a758e..d3e97858dacc850012e5585ac44a1aeafc82b8f4 100644
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -22,9 +22,11 @@ public class AttributeMap {
private final Map<Attribute, AttributeInstance> attributes = Maps.newHashMap();
private final Set<AttributeInstance> dirtyAttributes = Sets.newHashSet();
private final AttributeSupplier supplier;
+ private final java.util.function.Function<Attribute, AttributeInstance> createInstance; // Airplane
public AttributeMap(AttributeSupplier defaultAttributes) {
this.supplier = defaultAttributes;
+ this.createInstance = attribute -> this.supplier.createInstance(this::onAttributeModified, attribute); // Airplane
}
private void onAttributeModified(AttributeInstance instance) {
@@ -44,11 +46,10 @@ public class AttributeMap {
}).collect(Collectors.toList());
}
+
@Nullable
public AttributeInstance getInstance(Attribute attribute) {
- return this.attributes.computeIfAbsent(attribute, (attributex) -> {
- return this.supplier.createInstance(this::onAttributeModified, attributex);
- });
+ return this.attributes.computeIfAbsent(attribute, this.createInstance); // Airplane - cache lambda, as for some reason java allocates it anyways
}
public boolean hasAttribute(Attribute attribute) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index afbb2acd27416c801af3d718850b82a170734cd3..0b206a3f964f5143e0720890d78d682b8b558c15 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -68,6 +68,7 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
@Override
protected void start(ServerLevel world, PathfinderMob entity, long time) {
this.nextScheduledStart = time + 20L + (long)world.getRandom().nextInt(20);
+ if (entity.getNavigation().isStuck()) this.nextScheduledStart += 200L; // Airplane - wait an additional 10s to check again if they're stuck
PoiManager poiManager = world.getPoiManager();
this.batchCache.long2ObjectEntrySet().removeIf((entry) -> {
return !entry.getValue().isStillValid(time);
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java
index 42d466f7f162943886078eba3db18f2dfc2d7bee..32da2d3f87758bee359522769ebbee73f4f3256a 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java
@@ -37,7 +37,11 @@ public class VillagerPanicTrigger extends Behavior<Villager> {
@Override
protected void tick(ServerLevel serverLevel, Villager villager, long l) {
- if (l % 100L == 0L) {
+ // Airplane start
+ if (villager.nextGolemPanic < 0) villager.nextGolemPanic = l + 100;
+ if (--villager.nextGolemPanic < l) {
+ villager.nextGolemPanic = -1;
+ // Airplane end
villager.spawnGolemIfNeeded(serverLevel, l, 3);
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
index a96831d5df2b88203aec8fe2a5909708764b38ee..441e2edd8357c4f11093b4dee2192780ac3f3579 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
@@ -49,9 +49,12 @@ public class GoalSelector {
}
// Paper start
- public boolean inactiveTick() {
+ public boolean inactiveTick(int tickRate, boolean inactive) { // Airplane start
+ if (inactive && !gg.airplane.AirplaneConfig.dearEnabled) tickRate = 4; // reset to Paper's
+ tickRate = Math.min(tickRate, this.newGoalRate);
this.curRate++;
- return this.curRate % this.newGoalRate == 0;
+ return this.curRate % tickRate == 0;
+ // Airplane end
}
public boolean hasTasks() {
for (WrappedGoal task : this.availableGoals) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index c28ade67f6a59146064a57bf016a646197f47ac4..419e6275a400f587f57e81684520072a93654aae 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
@@ -114,6 +114,7 @@ public abstract class MoveToBlockGoal extends Goal {
for(int m = 0; m <= l; m = m > 0 ? -m : 1 - m) {
for(int n = m < l && m > -l ? l : 0; n <= l; n = n > 0 ? -n : 1 - n) {
mutableBlockPos.setWithOffset(blockPos, m, k - 1, n);
+ if (!this.mob.level.hasChunkAt(mutableBlockPos)) continue; // Airplane - if this block isn't loaded, continue
if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level, mutableBlockPos)) {
this.blockPos = mutableBlockPos;
setTargetPosition(mutableBlockPos.immutable()); // Paper
diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
index 3ee691d4caccbc1b3e0f52decb41d436ac0d08ec..8a0aea6b28295e03aaac1768336b1bc36d9ad9e9 100644
--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -74,9 +74,18 @@ public class TargetingConditions {
}
if (this.range > 0.0D) {
- double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
- double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0D); // Paper
+ // Airplane start - check range before getting visibility
+ // d = invisibility percent, e = follow range adjusted for invisibility, f = distance
double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ());
+ double followRangeRaw = this.useFollowRange ? this.getFollowRange(baseEntity) : this.range;
+
+ if (f > followRangeRaw * followRangeRaw) { // the actual follow range will always be this value or smaller, so if the distance is larger then it never will return true after getting invis
+ return false;
+ }
+
+ double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
+ double e = Math.max((followRangeRaw) * d, 2.0D); // Paper
+ // Airplane end
if (f > e * e) {
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
index 9ed2f8f2843c634fd14dda4459f85b7140dd447a..cda441f5cf64f061dc145fbeba175874c31cebe4 100644
--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
@@ -254,13 +254,22 @@ public class Bat extends AmbientCreature {
}
}
+ // Airplane start - only check for spooky season once an hour
+ private static boolean isSpookySeason = false;
+ private static final int ONE_HOUR = 20 * 60 * 60;
+ private static int lastSpookyCheck = -ONE_HOUR;
private static boolean isHalloween() {
+ if (net.minecraft.server.MinecraftServer.currentTick - lastSpookyCheck > ONE_HOUR) {
LocalDate localdate = LocalDate.now();
int i = localdate.get(ChronoField.DAY_OF_MONTH);
int j = localdate.get(ChronoField.MONTH_OF_YEAR);
- return j == 10 && i >= 20 || j == 11 && i <= 3;
+ isSpookySeason = j == 10 && i >= 20 || j == 11 && i <= 3;
+ lastSpookyCheck = net.minecraft.server.MinecraftServer.currentTick;
+ }
+ return isSpookySeason;
}
+ // Airplane end
@Override
protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
index 2b8725087fd3bfeca7162bda2783fdacd13a8390..16adf07774d568852b289ae8507779dd78110fc3 100644
--- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
@@ -275,9 +275,11 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable {
return true;
}
+ private int behaviorTick = 0; // Airplane
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("axolotlBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Airplane
this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error
this.level.getProfiler().pop();
this.level.getProfiler().push("axolotlActivityUpdate");
diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
index 32d0387b6c66462ca965add78a562dec3c4b95a9..82a363c5141cd00eec9b7e03e7ac6522b9b3c527 100644
--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
@@ -144,9 +144,11 @@ public class Goat extends Animal {
return (Brain<Goat>) super.getBrain(); // CraftBukkit - decompile error
}
+ private int behaviorTick = 0; // Airplane
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("goatBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Airplane
this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error
this.level.getProfiler().pop();
this.level.getProfiler().push("goatActivityUpdate");
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 063f3e4c67e6716c9a03dbe4b72eafd32e4f0d53..dae6f7a05426ea31d13c82458b33e20abc2571b6 100644
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
@@ -243,10 +243,16 @@ public class ItemEntity extends Entity {
if (entityitem.isMergable()) {
// Paper Start - Fix items merging through walls
if (this.level.paperConfig.fixItemsMergingThroughWalls) {
+ // Airplane start - skip the allocations
+ /*
net.minecraft.world.level.ClipContext rayTrace = new net.minecraft.world.level.ClipContext(this.position(), entityitem.position(),
net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, this);
net.minecraft.world.phys.BlockHitResult rayTraceResult = level.clip(rayTrace);
if (rayTraceResult.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) continue;
+ */
+ if (level.rayTraceDirect(this.position(), entityitem.position(), net.minecraft.world.phys.shapes.CollisionContext.of(this)) ==
+ net.minecraft.world.phys.HitResult.Type.BLOCK) continue;
+ // Airplane end
}
// Paper End
this.tryToMerge(entityitem);
diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
index b27e2e1e2270a7a2f3e36b90a19237e39e4175b8..e5068464d66641b4ae96aecee8d1216ae2dc37c1 100644
--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
@@ -312,11 +312,17 @@ public class EnderMan extends Monster implements NeutralMob {
private boolean teleport(double x, double y, double z) {
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(x, y, z);
- while (blockposition_mutableblockposition.getY() > this.level.getMinBuildHeight() && !this.level.getBlockState(blockposition_mutableblockposition).getMaterial().blocksMotion()) {
+ // Airplane start - single chunk lookup
+ net.minecraft.world.level.chunk.LevelChunk chunk = this.level.getChunkIfLoaded(blockposition_mutableblockposition);
+ if (chunk == null) {
+ return false;
+ }
+ // Airplane end
+ while (blockposition_mutableblockposition.getY() > this.level.getMinBuildHeight() && !chunk.getBlockState(blockposition_mutableblockposition).getMaterial().blocksMotion()) { // Airplane
blockposition_mutableblockposition.move(Direction.DOWN);
}
- BlockState iblockdata = this.level.getBlockState(blockposition_mutableblockposition);
+ BlockState iblockdata = chunk.getBlockState(blockposition_mutableblockposition); // Airplane
boolean flag = iblockdata.getMaterial().blocksMotion();
boolean flag1 = iblockdata.getFluidState().is((Tag) FluidTags.WATER);
diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
index c5b9c0c650df5f4b7e3d2a431dc900e210104dea..4c111bc335ab8cafe6674b77dcd7a22cb1c58ae9 100644
--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
@@ -123,12 +123,14 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
@Override
public Brain<Hoglin> getBrain() {
- return super.getBrain();
+ return (Brain<Hoglin>) super.getBrain(); // Airplane - decompile fix
}
+ private int behaviorTick; // Airplane
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("hoglinBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Airplane
this.getBrain().tick((ServerLevel)this.level, this);
this.level.getProfiler().pop();
HoglinAi.updateActivity(this);
diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
index 37712fe8585ede00569026bba5377ab61ad08ff5..758558c1e312edc730d1f5d7ffdbc5a41356a6e5 100644
--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
@@ -289,9 +289,11 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
return !this.cannotHunt;
}
+ private int behaviorTick; // Airplane
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("piglinBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Airplane
this.getBrain().tick((ServerLevel) this.level, (Piglin) this); // CraftBukkit - decompile error
this.level.getProfiler().pop();
PiglinAi.updateActivity(this);
diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
index b746453774054dd2db023b7eea265a395cfc7cc9..4904971710acbb2855ed9ed96fead4aed189c494 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
@@ -142,6 +142,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
return villageplacetype == PoiType.MEETING;
});
+ public long nextGolemPanic = -1; // Airplane
+
public Villager(EntityType<? extends Villager> entityType, Level world) {
this(entityType, world, VillagerType.PLAINS);
}
@@ -245,11 +247,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
// Spigot End
+ private int behaviorTick = 0; // Airplane
@Override
protected void customServerAiStep() { mobTick(false); }
protected void mobTick(boolean inactive) {
this.level.getProfiler().push("villagerBrain");
- if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // CraftBukkit - decompile error // Paper
+ // Airplane start
+ if (!inactive) {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Airplane
+ this.getBrain().tick((ServerLevel) this.level, this);
+ }
+ // Airplane end
this.level.getProfiler().pop();
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java
index c7e16e96633e17b951f0681599c5b3efc3ce1e6c..8d329bca0818033df41fbd781028919c73e052a6 100644
--- a/src/main/java/net/minecraft/world/entity/player/Inventory.java
+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java
@@ -688,6 +688,8 @@ public class Inventory implements Container, Nameable {
}
public boolean contains(ItemStack stack) {
+ // Airplane start - don't allocate iterators
+ /*
Iterator iterator = this.compartments.iterator();
while (iterator.hasNext()) {
@@ -702,6 +704,18 @@ public class Inventory implements Container, Nameable {
}
}
}
+ */
+ for (int i = 0; i < this.compartments.size(); i++) {
+ List<ItemStack> list = this.compartments.get(i);
+ for (int j = 0; j < list.size(); j++) {
+ ItemStack itemstack1 = list.get(j);
+
+ if (!itemstack1.isEmpty() && itemstack1.sameItem(stack)) {
+ return true;
+ }
+ }
+ }
+ // Airplane end
return false;
}
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 6339203bda5e569d5df241dd589eb36e7233704b..461173191361fdb0c2c950eacf03ba43693e1908 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
@@ -42,6 +42,36 @@ public abstract class Projectile extends Entity {
super(type, world);
}
+ // Airplane start
+ private static int loadedThisTick = 0;
+ private static int loadedTick;
+
+ private int loadedLifetime = 0;
+ @Override
+ public void setPos(double x, double y, double z) {
+ int currentTick = net.minecraft.server.MinecraftServer.currentTick;
+ if (loadedTick != currentTick) {
+ loadedTick = currentTick;
+ loadedThisTick = 0;
+ }
+ int previousX = Mth.floor(this.getX()) >> 4, previousZ = Mth.floor(this.getZ()) >> 4;
+ int newX = Mth.floor(x) >> 4, newZ = Mth.floor(z) >> 4;
+ if (previousX != newX || previousZ != newZ) {
+ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level.getChunkSource()).getChunkAtIfLoadedMainThread(newX, newZ) != null;
+ if (!isLoaded) {
+ if (Projectile.loadedThisTick > gg.airplane.AirplaneConfig.maxProjectileLoadsPerTick) {
+ if (++this.loadedLifetime > gg.airplane.AirplaneConfig.maxProjectileLoadsPerProjectile) {
+ this.discard();
+ }
+ return;
+ }
+ Projectile.loadedThisTick++;
+ }
+ }
+ super.setPos(x, y, z);
+ }
+ // Airplane start
+
public void setOwner(@Nullable Entity entity) {
if (entity != null) {
this.ownerUUID = entity.getUUID();
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
index f57864ce919ef4721cfb5913c636fe8903ce4cc1..610d756b4a264deb58ea8144c951f652697805ee 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
@@ -40,7 +40,10 @@ import org.bukkit.inventory.InventoryHolder;
public abstract class AbstractMinecartContainer extends AbstractMinecart implements Container, MenuProvider {
+ // Airplane start
private NonNullList<ItemStack> itemStacks;
+ private gg.airplane.structs.ItemListWithBitset itemStacksOptimized;
+ // Airplane end
@Nullable
public ResourceLocation lootTable;
public long lootTableSeed;
@@ -89,12 +92,18 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
protected AbstractMinecartContainer(EntityType<?> type, Level world) {
super(type, world);
- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
+ // Airplane start
+ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513
+ this.itemStacks = this.itemStacksOptimized.nonNullList;
+ // Airplane end
}
protected AbstractMinecartContainer(EntityType<?> type, double x, double y, double z, Level world) {
super(type, world, x, y, z);
- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
+ // Airplane start
+ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513
+ this.itemStacks = this.itemStacksOptimized.nonNullList;
+ // Airplane end
}
@Override
@@ -217,7 +226,10 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
protected void readAdditionalSaveData(CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
this.lootableData.loadNbt(nbt); // Paper
- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
+ // Airplane start
+ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
+ this.itemStacks = this.itemStacksOptimized.nonNullList;
+ // Airplane end
if (nbt.contains("LootTable", 8)) {
this.lootTable = new ResourceLocation(nbt.getString("LootTable"));
this.lootTableSeed = nbt.getLong("LootTableSeed");
diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
index 6b960f0a31175bcfd8d477ee5b3c4d783303cdd5..3a81d3a58b937c9800cd0be738439cff0ba28ce9 100644
--- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
+++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
@@ -25,8 +25,13 @@ public class ShapelessRecipe implements CraftingRecipe {
final String group;
final ItemStack result;
final NonNullList<Ingredient> ingredients;
+ private final boolean isBukkit; // Airplane
+ // Airplane start
public ShapelessRecipe(ResourceLocation id, String group, ItemStack output, NonNullList<Ingredient> input) {
+ this(id, group, output, input, false);
+ }
+ public ShapelessRecipe(ResourceLocation id, String group, ItemStack output, NonNullList<Ingredient> input, boolean isBukkit) { this.isBukkit = isBukkit; // Airplane end
this.id = id;
this.group = group;
this.result = output;
@@ -73,6 +78,28 @@ public class ShapelessRecipe implements CraftingRecipe {
}
public boolean matches(CraftingContainer inventory, Level world) {
+ // Airplane start
+ if (!this.isBukkit) {
+ java.util.List<Ingredient> ingredients = com.google.common.collect.Lists.newArrayList(this.ingredients.toArray(new Ingredient[0]));
+
+ inventory: for (int index = 0; index < inventory.getContainerSize(); index++) {
+ ItemStack itemStack = inventory.getItem(index);
+
+ if (!itemStack.isEmpty()) {
+ for (int i = 0; i < ingredients.size(); i++) {
+ if (ingredients.get(i).test(itemStack)) {
+ ingredients.remove(i);
+ continue inventory;
+ }
+ }
+ return false;
+ }
+ }
+
+ return ingredients.isEmpty();
+ }
+ // Airplane end
+
StackedContents autorecipestackmanager = new StackedContents();
int i = 0;
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
index 6200a8ab4f7b2c40e7139cfb90a62f42c5828de2..f6a8e10347b9a374e2da9d28734b72443555459d 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -73,6 +73,16 @@ public interface BlockGetter extends LevelHeightAccessor {
});
}
+ // Airplane start - broken down variant of below rayTraceBlock, used by World#rayTraceDirect
+ default net.minecraft.world.phys.BlockHitResult.Type rayTraceBlockDirect(Vec3 vec3d, Vec3 vec3d1, BlockPos blockposition, BlockState iblockdata, net.minecraft.world.phys.shapes.CollisionContext voxelshapecoll) {
+ if (iblockdata.isAir()) return null; // Tuinity - optimise air cases
+ VoxelShape voxelshape = ClipContext.Block.COLLIDER.get(iblockdata, this, blockposition, voxelshapecoll);
+ net.minecraft.world.phys.BlockHitResult movingobjectpositionblock = this.clipWithInteractionOverride(vec3d, vec3d1, blockposition, voxelshape, iblockdata);
+
+ return movingobjectpositionblock == null ? null : movingobjectpositionblock.getType();
+ }
+ // Airplane end
+
// CraftBukkit start - moved block handling into separate method for use by Block#rayTrace
default BlockHitResult rayTraceBlock(ClipContext raytrace1, BlockPos blockposition) {
// Paper start - Prevent raytrace from loading chunks
diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java
index e7ca5d6fb8922e7e8065864f736b06056be080a0..833ad6fbedfc275b3fde640b0e873f23e61acc3b 100644
--- a/src/main/java/net/minecraft/world/level/GameRules.java
+++ b/src/main/java/net/minecraft/world/level/GameRules.java
@@ -90,6 +90,7 @@ public class GameRules {
public static final GameRules.Key<GameRules.BooleanValue> RULE_UNIVERSAL_ANGER = GameRules.register("universalAnger", GameRules.Category.MOBS, GameRules.BooleanValue.create(false));
public static final GameRules.Key<GameRules.IntegerValue> RULE_PLAYERS_SLEEPING_PERCENTAGE = GameRules.register("playersSleepingPercentage", GameRules.Category.PLAYER, GameRules.IntegerValue.create(100));
private final Map<GameRules.Key<?>, GameRules.Value<?>> rules;
+ private final GameRules.Value<?>[] gameruleArray;
private static <T extends GameRules.Value<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
GameRules.Key<T> gamerules_gamerulekey = new GameRules.Key<>(name, category);
@@ -108,17 +109,33 @@ public class GameRules {
}
public GameRules() {
- this.rules = (Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> {
+ // Airplane start - use this to ensure gameruleArray is initialized
+ this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> {
return ((GameRules.Type) entry.getValue()).createRule();
- }));
+ })));
+ // Airplane end
}
private GameRules(Map<GameRules.Key<?>, GameRules.Value<?>> rules) {
this.rules = rules;
+
+ // Airplane start
+ int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1;
+ GameRules.Value<?>[] values = new GameRules.Value[arraySize];
+
+ for (Entry<GameRules.Key<?>, GameRules.Value<?>> entry : rules.entrySet()) {
+ values[entry.getKey().gameRuleIndex] = entry.getValue();
+ }
+
+ this.gameruleArray = values;
+ // Airplane end
}
public <T extends GameRules.Value<T>> T getRule(GameRules.Key<T> key) {
- return (T) this.rules.get(key); // CraftBukkit - decompile error
+ // Airplane start
+ return key == null ? null : (T) this.gameruleArray[key.gameRuleIndex];
+ //return (T) this.rules.get(key); // CraftBukkit - decompile error
+ // Airplane end
}
public CompoundTag createTag() {
@@ -177,6 +194,10 @@ public class GameRules {
}
public static final class Key<T extends GameRules.Value<T>> {
+ // Airplane start
+ private static int lastGameRuleIndex = 0;
+ public final int gameRuleIndex = lastGameRuleIndex++;
+ // Airplane end
final String id;
private final GameRules.Category category;
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index f936e9f9a9fa655fa997d6862b5ed54c04169d35..24d772c18d5a448154909e4a51964ba29485a5c2 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -176,6 +176,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here
+ public final java.util.function.BiPredicate<BlockState, BlockPos> isAlmostSuffocating = (iblockdata, blockposition) -> iblockdata.isSuffocating(this, blockposition); // Airplane - move here, no allocs
+
// Paper start - fix and optimise world upgrading
// copied from below
public static ResourceKey<DimensionType> getDimensionKey(DimensionType manager) {
@@ -311,6 +313,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
// Paper end - optimise checkDespawn
+ // Airplane start - ensure these get inlined
+ private final int minBuildHeight, minSection, height, maxBuildHeight, maxSection;
+ @Override public final int getMaxBuildHeight() { return this.maxBuildHeight; }
+ @Override public final int getMinSection() { return this.minSection; }
+ @Override public final int getMaxSection() { return this.maxSection; }
+ @Override public final int getMinBuildHeight() { return this.minBuildHeight; }
+ @Override public final int getHeight() { return this.height; }
+ // Airplane end
+
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper
@@ -329,6 +340,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.profiler = supplier;
this.levelData = worlddatamutable;
this.dimensionType = dimensionmanager;
+ // Airplane start
+ this.minBuildHeight = dimensionmanager.minY();
+ this.minSection = SectionPos.blockToSectionCoord(this.minBuildHeight);
+ this.height = dimensionmanager.height();
+ this.maxBuildHeight = this.minBuildHeight + this.height;
+ this.maxSection = SectionPos.blockToSectionCoord(this.maxBuildHeight - 1) + 1;
+ // Airplane end
this.dimension = resourcekey;
this.isClientSide = flag;
if (dimensionmanager.coordinateScale() != 1.0D) {
@@ -452,6 +470,91 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
return null;
}
+ // Airplane start - broken down method of raytracing for EntityLiving#hasLineOfSight, replaces IBlockAccess#rayTrace(RayTrace)
+ public net.minecraft.world.phys.BlockHitResult.Type rayTraceDirect(net.minecraft.world.phys.Vec3 vec3d, net.minecraft.world.phys.Vec3 vec3d1, net.minecraft.world.phys.shapes.CollisionContext voxelshapecoll) {
+ // most of this code comes from IBlockAccess#a(RayTrace, BiFunction, Function), but removes the needless functions
+ if (vec3d.equals(vec3d1)) {
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+ }
+
+ double endX = Mth.lerp(-1.0E-7D, vec3d1.x, vec3d.x);
+ double endY = Mth.lerp(-1.0E-7D, vec3d1.y, vec3d.y);
+ double endZ = Mth.lerp(-1.0E-7D, vec3d1.z, vec3d.z);
+
+ double startX = Mth.lerp(-1.0E-7D, vec3d.x, vec3d1.x);
+ double startY = Mth.lerp(-1.0E-7D, vec3d.y, vec3d1.y);
+ double startZ = Mth.lerp(-1.0E-7D, vec3d.z, vec3d1.z);
+
+ int currentX = Mth.floor(startX);
+ int currentY = Mth.floor(startY);
+ int currentZ = Mth.floor(startZ);
+
+ BlockPos.MutableBlockPos currentBlock = new BlockPos.MutableBlockPos(currentX, currentY, currentZ);
+
+ LevelChunk chunk = this.getChunkIfLoaded(currentBlock);
+ if (chunk == null) {
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+ }
+
+ net.minecraft.world.phys.BlockHitResult.Type initialCheck = this.rayTraceBlockDirect(vec3d, vec3d1, currentBlock, chunk.getBlockState(currentBlock), voxelshapecoll);
+
+ if (initialCheck != null) {
+ return initialCheck;
+ }
+
+ double diffX = endX - startX;
+ double diffY = endY - startY;
+ double diffZ = endZ - startZ;
+
+ int xDirection = Mth.sign(diffX);
+ int yDirection = Mth.sign(diffY);
+ int zDirection = Mth.sign(diffZ);
+
+ double normalizedX = xDirection == 0 ? Double.MAX_VALUE : (double) xDirection / diffX;
+ double normalizedY = yDirection == 0 ? Double.MAX_VALUE : (double) yDirection / diffY;
+ double normalizedZ = zDirection == 0 ? Double.MAX_VALUE : (double) zDirection / diffZ;
+
+ double normalizedXDirection = normalizedX * (xDirection > 0 ? 1.0D - Mth.frac(startX) : Mth.frac(startX));
+ double normalizedYDirection = normalizedY * (yDirection > 0 ? 1.0D - Mth.frac(startY) : Mth.frac(startY));
+ double normalizedZDirection = normalizedZ * (zDirection > 0 ? 1.0D - Mth.frac(startZ) : Mth.frac(startZ));
+
+ net.minecraft.world.phys.BlockHitResult.Type result;
+
+ do {
+ if (normalizedXDirection > 1.0D && normalizedYDirection > 1.0D && normalizedZDirection > 1.0D) {
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+ }
+
+ if (normalizedXDirection < normalizedYDirection) {
+ if (normalizedXDirection < normalizedZDirection) {
+ currentX += xDirection;
+ normalizedXDirection += normalizedX;
+ } else {
+ currentZ += zDirection;
+ normalizedZDirection += normalizedZ;
+ }
+ } else if (normalizedYDirection < normalizedZDirection) {
+ currentY += yDirection;
+ normalizedYDirection += normalizedY;
+ } else {
+ currentZ += zDirection;
+ normalizedZDirection += normalizedZ;
+ }
+
+ currentBlock.set(currentX, currentY, currentZ);
+ if (chunk.getPos().x != currentBlock.getX() >> 4 || chunk.getPos().z != currentBlock.getZ() >> 4) {
+ chunk = this.getChunkIfLoaded(currentBlock);
+ if (chunk == null) {
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
+ }
+ }
+ result = this.rayTraceBlockDirect(vec3d, vec3d1, currentBlock, chunk.getBlockState(currentBlock), voxelshapecoll);
+ } while (result == null);
+
+ return result;
+ }
+ // Airplane end
+
public boolean isInWorldBounds(BlockPos pos) {
return pos.isValidLocation(this); // Paper - use better/optimized check
}
@@ -987,13 +1090,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
try {
tickConsumer.accept(entity);
MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
- } catch (Throwable throwable) {
+ } catch (Throwable throwable) { // Airplane - diff on change ServerLevel.tick
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
MinecraftServer.LOGGER.error(msg, throwable);
getCraftServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable)));
- entity.discard();
+ entity.discard(); // Airplane - diff on change ServerLevel.tick
// Paper end
}
}
@@ -1447,6 +1550,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
public ProfilerFiller getProfiler() {
+ if (gg.airplane.AirplaneConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Airplane
return (ProfilerFiller) this.profiler.get();
}
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index bacd75f67f783f49208a74501cc7e6e7485010a4..dc613142603fca4cf5c8e47c084e1439d0450424 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -418,12 +418,12 @@ public final class NaturalSpawner {
return spawnGroup == MobCategory.MONSTER && world.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS) && structureAccessor.getStructureAt(pos, false, StructureFeature.NETHER_BRIDGE).isValid() ? StructureFeature.NETHER_BRIDGE.getSpecialEnemies() : chunkGenerator.getMobsAt(biome != null ? biome : world.getBiome(pos), structureAccessor, spawnGroup, pos);
}
- private static BlockPos getRandomPosWithin(Level world, LevelChunk chunk) {
+ private static BlockPos getRandomPosWithin(ServerLevel world, LevelChunk chunk) { // Airplane - accept serverlevel
ChunkPos chunkcoordintpair = chunk.getPos();
- int i = chunkcoordintpair.getMinBlockX() + world.random.nextInt(16);
- int j = chunkcoordintpair.getMinBlockZ() + world.random.nextInt(16);
+ int i = chunkcoordintpair.getMinBlockX() + world.getThreadUnsafeRandom().nextInt(16); // Airplane - use thread unsafe random
+ int j = chunkcoordintpair.getMinBlockZ() + world.getThreadUnsafeRandom().nextInt(16); // Airplane
int k = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i, j) + 1;
- int l = Mth.randomBetweenInclusive(world.random, world.getMinBuildHeight(), k);
+ int l = Mth.randomBetweenInclusive(world.getThreadUnsafeRandom(), world.getMinBuildHeight(), k); // Airplane
return new BlockPos(i, l, j);
}
diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java
index 2b814006fa30dd233dcb345d1d20ce3bf6469053..b5c6b0bc307aef2835761cfa50413cebfd624795 100644
--- a/src/main/java/net/minecraft/world/level/biome/Biome.java
+++ b/src/main/java/net/minecraft/world/level/biome/Biome.java
@@ -104,8 +104,10 @@ public final class Biome {
private final float scale;
private final Biome.BiomeCategory biomeCategory;
private final BiomeSpecialEffects specialEffects;
- private final ThreadLocal<Long2FloatLinkedOpenHashMap> temperatureCache = ThreadLocal.withInitial(() -> {
+ // Airplane start - use our cache
+ private final ThreadLocal<gg.airplane.structs.Long2FloatAgingCache> temperatureCache = ThreadLocal.withInitial(() -> {
return Util.make(() -> {
+ /*
Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = new Long2FloatLinkedOpenHashMap(1024, 0.25F) {
@Override
protected void rehash(int i) {
@@ -113,6 +115,10 @@ public final class Biome {
};
long2FloatLinkedOpenHashMap.defaultReturnValue(Float.NaN);
return long2FloatLinkedOpenHashMap;
+
+ */
+ return new gg.airplane.structs.Long2FloatAgingCache(TEMPERATURE_CACHE_SIZE);
+ // Airplane end
});
});
@@ -154,17 +160,15 @@ public final class Biome {
public final float getTemperature(BlockPos blockPos) {
long l = blockPos.asLong();
- Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = this.temperatureCache.get();
- float f = long2FloatLinkedOpenHashMap.get(l);
+ // Airplane start
+ gg.airplane.structs.Long2FloatAgingCache cache = this.temperatureCache.get();
+ float f = cache.getValue(l);
if (!Float.isNaN(f)) {
return f;
} else {
float g = this.getHeightAdjustedTemperature(blockPos);
- if (long2FloatLinkedOpenHashMap.size() == 1024) {
- long2FloatLinkedOpenHashMap.removeFirstFloat();
- }
-
- long2FloatLinkedOpenHashMap.put(l, g);
+ cache.putValue(l, g);
+ // Airplane end
return g;
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
index 52de9852f87d346714a950b60a0004d386ac10f0..86bbd9fcee5982cf901ef0480052025ccf57ccb4 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
@@ -32,7 +32,10 @@ import org.bukkit.entity.HumanEntity;
public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity {
private static final int EVENT_SET_OPEN_COUNT = 1;
+ // Airplane start
private NonNullList<ItemStack> items;
+ private gg.airplane.structs.ItemListWithBitset optimizedItems;
+ // Airplane end
public final ContainerOpenersCounter openersCounter;
private final ChestLidController chestLidController;
@@ -66,9 +69,13 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
}
// CraftBukkit end
+ private final boolean isNative = getClass().equals(ChestBlockEntity.class); // Airplane
protected ChestBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
- this.items = NonNullList.withSize(27, ItemStack.EMPTY);
+ // Airplane start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(27);
+ this.items = this.optimizedItems.nonNullList;
+ // Airplane end
this.openersCounter = new ContainerOpenersCounter() {
@Override
protected void onOpen(Level world, BlockPos pos, BlockState state) {
@@ -99,6 +106,23 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
this.chestLidController = new ChestLidController();
}
+ // Airplane start
+ @Override
+ public boolean hasEmptySlot(Direction enumdirection) {
+ return isNative ? !this.optimizedItems.hasFullStacks() : super.hasEmptySlot(enumdirection);
+ }
+
+ @Override
+ public boolean isCompletelyFull(Direction enumdirection) {
+ return isNative ? this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection) : super.isCompletelyFull(enumdirection);
+ }
+
+ @Override
+ public boolean isCompletelyEmpty(Direction enumdirection) {
+ return isNative && this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection);
+ }
+ // Airplane end
+
public ChestBlockEntity(BlockPos pos, BlockState state) {
this(BlockEntityType.CHEST, pos, state);
}
@@ -116,7 +140,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
- this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
+ // Airplane start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
+ this.items = this.optimizedItems.nonNullList;
+ // Airplane end
if (!this.tryLoadLootTable(nbt)) {
ContainerHelper.loadAllItems(nbt, this.items);
}
@@ -189,7 +216,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
@Override
protected void setItems(NonNullList<ItemStack> list) {
- this.items = list;
+ // Airplane start
+ this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list);
+ this.items = this.optimizedItems.nonNullList;
+ // Airplane end
}
@Override
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 4950d49ed414e1c82c125b9347113f595f1078b6..f9b0956ea73cee2f97c22c5ed2fa42546278a650 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
@@ -44,7 +44,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public static final int MOVE_ITEM_SPEED = 8;
public static final int HOPPER_CONTAINER_SIZE = 5;
+ // Airplane start
private NonNullList<ItemStack> items;
+ private gg.airplane.structs.ItemListWithBitset optimizedItems; // Airplane
+ // Airplane end
private int cooldownTime;
private long tickedGameTime;
@@ -80,14 +83,37 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public HopperBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.HOPPER, pos, state);
- this.items = NonNullList.withSize(5, ItemStack.EMPTY);
+ // Airplane start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(5);
+ this.items = this.optimizedItems.nonNullList;
+ // Airplane end
this.cooldownTime = -1;
}
+ // Airplane start
+ @Override
+ public boolean hasEmptySlot(Direction enumdirection) {
+ return !this.optimizedItems.hasFullStacks();
+ }
+
+ @Override
+ public boolean isCompletelyFull(Direction enumdirection) {
+ return this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection);
+ }
+
+ @Override
+ public boolean isCompletelyEmpty(Direction enumdirection) {
+ return this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection);
+ }
+ // Airplane end
+
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
- this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
+ // Airplane start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
+ this.items = this.optimizedItems.nonNullList;
+ // Airplane end
if (!this.tryLoadLootTable(nbt)) {
ContainerHelper.loadAllItems(nbt, this.items);
}
@@ -160,7 +186,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
flag = HopperBlockEntity.a(world, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit
}
- if (!blockEntity.inventoryFull()) {
+ if (!blockEntity.optimizedItems.hasFullStacks() || !blockEntity.inventoryFull()) { // Airplane - use bitset first
flag |= booleansupplier.getAsBoolean();
}
@@ -199,7 +225,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
skipPushModeEventFire = skipHopperEvents;
boolean foundItem = false;
for (int i = 0; i < hopper.getContainerSize(); ++i) {
- ItemStack item = hopper.getItem(i);
+ ItemStack item = hopper.getItem(i); // Airplane
if (!item.isEmpty()) {
foundItem = true;
ItemStack origItemStack = item;
@@ -403,12 +429,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
private static boolean isFullContainer(Container inventory, Direction direction) {
- return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams
+ // Airplane start - use bitsets
+ //return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams
+ return inventory.isCompletelyFull(direction);
+ // Airplane end
}
private static boolean isEmptyContainer(Container inv, Direction facing) {
// Paper start
- return allMatch(inv, facing, IS_EMPTY_TEST);
+ // Airplane start - use bitsets
+ //return allMatch(inv, facing, IS_EMPTY_TEST);
+ return inv.isCompletelyEmpty(facing);
+ // Airplane end
}
private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
if (iinventory instanceof WorldlyContainer) {
@@ -585,7 +617,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
if (HopperBlockEntity.canPlaceItemInContainer(to, stack, slot, enumdirection)) {
boolean flag = false;
- boolean flag1 = to.isEmpty();
+ boolean flag1 = to.isCompletelyEmpty(enumdirection); // Airplane
if (itemstack1.isEmpty()) {
// Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
@@ -733,7 +765,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@Override
protected void setItems(NonNullList<ItemStack> list) {
- this.items = list;
+ // Airplane start
+ this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list);
+ this.items = this.optimizedItems.nonNullList;
+ // Airplane end
}
public static void entityInside(Level world, BlockPos pos, BlockState state, Entity entity, HopperBlockEntity blockEntity) {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
index ed3518fe7c841d9e1a9c97626acaa3d765a6d76f..ac564148956beb984650341c5c0994573f4f7225 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
@@ -96,13 +96,8 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
public boolean isEmpty() {
this.unpackLootTable((Player)null);
// Paper start
- for (ItemStack itemStack : this.getItems()) {
- if (!itemStack.isEmpty()) {
- return false;
- }
- }
+ return this.isCompletelyEmpty(null); // Airplane - use super
// Paper end
- return true;
}
@Override
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 86686c24b0b7de4b4bfadbc77419a8872a8e86ee..db7904b1bb402a36684b97c443336630762aeaf9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -172,6 +172,20 @@ public class LevelChunk implements ChunkAccess {
}
// Paper end - rewrite light engine
+ // Airplane start - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively
+ private int lightningTick;
+ // shouldDoLightning compiles down to 29 bytes, which with the default of 35 byte inlining should guarantee an inline
+ public final boolean shouldDoLightning(java.util.Random random) {
+ if (this.lightningTick-- <= 0) {
+ this.lightningTick = random.nextInt(100000) << 1;
+ return true;
+ }
+ return false;
+ }
+ // Airplane end
+
+ public final gg.airplane.entity.CollisionCacheList[] collisionCaches; // Airplane
+
public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes) {
this(world, pos, biomes, UpgradeData.EMPTY, EmptyTickList.empty(), EmptyTickList.empty(), 0L, (LevelChunkSection[]) null, (Consumer) null);
}
@@ -209,6 +223,12 @@ public class LevelChunk implements ChunkAccess {
this.inhabitedTime = inhabitedTime;
this.postLoad = loadToWorldConsumer;
this.sections = new LevelChunkSection[world.getSectionsCount()];
+ // Airplane start
+ this.collisionCaches = new gg.airplane.entity.CollisionCacheList[world.getSectionsCount()];
+ for (int i = 0; i < this.collisionCaches.length; i++) {
+ this.collisionCaches[i] = new gg.airplane.entity.CollisionCacheList();
+ }
+ // Airplane end
if (sections != null) {
if (this.sections.length == sections.length) {
System.arraycopy(sections, 0, this.sections, 0, this.sections.length);
@@ -220,6 +240,7 @@ public class LevelChunk implements ChunkAccess {
this.postProcessing = new ShortList[world.getSectionsCount()];
// CraftBukkit start
this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+ this.lightningTick = this.level.random.nextInt(100000) << 1; // Airplane - initialize lightning tick
}
public org.bukkit.Chunk bukkitChunk;
@@ -655,6 +676,17 @@ public class LevelChunk implements ChunkAccess {
int i1 = blockposition.getZ() & 15;
BlockState iblockdata1 = chunksection.setBlockState(k, l, i1, iblockdata);
+ // Airplane start - notify dirty
+ SectionPos pos = SectionPos.of(this.chunkPos, j);
+ gg.airplane.entity.CollisionCache[] caches = this.collisionCaches[j].getRawData();
+ for (int index = 0; index < caches.length; index++) {
+ gg.airplane.entity.CollisionCache cache = caches[index];
+ if (cache != null) {
+ cache.dirtySection(pos);
+ }
+ }
+ // Airplane end
+
if (iblockdata1 == iblockdata) {
return null;
} else {
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 72e3264dc74822f746fb84fec0be400047d2d9f5..831e2dbe530daf63ac9e681a92af2740fa18ac8c 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -18,6 +18,9 @@ public class LevelChunkSection {
short nonEmptyBlockCount; // Paper - package-private
private short tickingBlockCount;
private short tickingFluidCount;
+ // Airplane start
+ public short fluidStateCount;
+ // Airplane end
public final PalettedContainer<BlockState> states; // Paper - package-private // Paper - public
public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
@@ -88,6 +91,7 @@ public class LevelChunkSection {
if (!fluidState.isEmpty()) {
--this.tickingFluidCount;
+ --this.fluidStateCount; // Airplane
}
if (!state.isAir()) {
@@ -102,6 +106,7 @@ public class LevelChunkSection {
if (!fluidState2.isEmpty()) {
++this.tickingFluidCount;
+ ++this.fluidStateCount; // Airplane
}
return blockState;
@@ -155,6 +160,7 @@ public class LevelChunkSection {
if (fluidState.isRandomlyTicking()) {
this.tickingFluidCount = (short)(this.tickingFluidCount + 1); // Paper
}
+ this.fluidStateCount++; // Airplane
}
});
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
index c9e942669458668a184aaec3bc0a5509dd6ab5f0..178e56ffc87ea2beb4d84d1f278f4acf90102379 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -263,13 +263,17 @@ public class PalettedContainer<T> implements PaletteResize<T> {
}
+ // Airplane start - allow reusing int array
public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey) { // Paper - synchronize
+ this.write(nbt, paletteKey, dataKey, new int[4096]);
+ }
+ public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey, int[] is) { // Paper - synchronize // Airplane end
try {
this.acquire();
HashMapPalette<T> hashMapPalette = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);
T object = this.defaultValue;
int i = hashMapPalette.idFor(this.defaultValue);
- int[] is = new int[4096];
+ //int[] is = new int[4096]; // Airplane
for(int j = 0; j < 4096; ++j) {
T object2 = this.get(j);
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 7921ee2786d0d6a60d43786b20efc03a0f9178e3..9ea4229f58679c6c833762fc6a50471445ff0b9d 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -500,6 +500,7 @@ public class ChunkSerializer {
return new AsyncSaveData(blockLight, skyLight, blockTickListSerialized, fluidTickListSerialized, blockEntitiesSerialized, world.getGameTime());
}
+ private static final ThreadLocal<int[]> paletteArray = ThreadLocal.withInitial(() -> new int[4096]); // Airplane
public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
return saveChunk(world, chunk, null);
}
@@ -533,6 +534,7 @@ public class ChunkSerializer {
ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
boolean flag = chunk.isLightCorrect();
+ int[] is = paletteArray.get(); // Airplane - use cached
for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
int finalI = i; // CraftBukkit - decompile errors
LevelChunkSection chunksection = (LevelChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> {
@@ -547,7 +549,7 @@ public class ChunkSerializer {
nbttagcompound2.putByte("Y", (byte) (i & 255));
if (chunksection != LevelChunk.EMPTY_SECTION) {
- chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates");
+ chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates", is); // Airplane - reuse array
}
// Paper start - replace light engine
diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java
index da1ad0b2679e392ed81b50c15f012c63cb5c939e..81e83022421e2c311c32f6e6007cfc0c82efb822 100644
--- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java
@@ -6,6 +6,8 @@ import javax.annotation.Nullable;
import net.minecraft.world.phys.AABB;
public interface LevelEntityGetter<T extends EntityAccess> {
+ int getCount(); // Airplane
+
@Nullable
T get(int id);
diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
index 3b13f6ea36a3bfecabe09221eb5c48dddab119db..c02b9104c0cc1a7319cca29d5e32a5c2a33bff18 100644
--- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
+++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
@@ -14,6 +14,8 @@ public class LevelEntityGetterAdapter<T extends EntityAccess> implements LevelEn
this.sectionStorage = cache;
}
+ @Override public int getCount() { return this.visibleEntities.count(); } // Airplane
+
@Nullable
@Override
public T get(int id) {
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
index 9681e397588a8abc4150b991e546fa79b5635c87..646385d0bef31f43a7273fa78ec696dfc761093c 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -606,6 +606,12 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
PersistentEntitySectionManager.this.knownUuids.remove(this.entity.getUUID());
this.entity.setLevelCallback(PersistentEntitySectionManager.Callback.NULL);
PersistentEntitySectionManager.this.removeSectionIfEmpty(this.currentSectionKey, this.currentSection);
+
+ // Airplane start
+ if (this.entity instanceof Entity realEntity) {
+ realEntity.collisionCache.onRemove();
+ }
+ // Airplane end
}
}
}
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 7fda7da544b2d0bbd3803d88ee34c92350a8b8ef..adf91f3006a2d224c957f08520f93f761c3ba832 100644
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
@@ -45,6 +45,8 @@ public abstract class FlowingFluid extends Fluid {
public static final BooleanProperty FALLING = BlockStateProperties.FALLING;
public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL_FLOWING;
private static final int CACHE_SIZE = 200;
+ // Airplane start - use our own cache
+ /*
private static final ThreadLocal<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> {
Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2bytelinkedopenhashmap = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(200) {
protected void rehash(int i) {}
@@ -53,6 +55,14 @@ public abstract class FlowingFluid extends Fluid {
object2bytelinkedopenhashmap.defaultReturnValue((byte) 127);
return object2bytelinkedopenhashmap;
});
+ */
+
+ private static final ThreadLocal<gg.airplane.structs.FluidDirectionCache<Block.BlockStatePairKey>> localFluidDirectionCache = ThreadLocal.withInitial(() -> {
+ // Airplane todo - mess with this number for performance
+ // with 2048 it seems very infrequent on a small world that it has to remove old entries
+ return new gg.airplane.structs.FluidDirectionCache<>(2048);
+ });
+ // Airplane end
private final Map<FluidState, VoxelShape> shapes = Maps.newIdentityHashMap();
public FlowingFluid() {}
@@ -240,6 +250,8 @@ public abstract class FlowingFluid extends Fluid {
}
private boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) {
+ // Airplane start - modify to use our cache
+ /*
Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap;
if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
@@ -247,9 +259,16 @@ public abstract class FlowingFluid extends Fluid {
} else {
object2bytelinkedopenhashmap = null;
}
+ */
+ gg.airplane.structs.FluidDirectionCache<Block.BlockStatePairKey> cache = null;
+
+ if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
+ cache = localFluidDirectionCache.get();
+ }
Block.BlockStatePairKey block_a;
+ /*
if (object2bytelinkedopenhashmap != null) {
block_a = new Block.BlockStatePairKey(state, fromState, face);
byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(block_a);
@@ -260,11 +279,22 @@ public abstract class FlowingFluid extends Fluid {
} else {
block_a = null;
}
+ */
+ if (cache != null) {
+ block_a = new Block.BlockStatePairKey(state, fromState, face);
+ Boolean flag = cache.getValue(block_a);
+ if (flag != null) {
+ return flag;
+ }
+ } else {
+ block_a = null;
+ }
VoxelShape voxelshape = state.getCollisionShape(world, pos);
VoxelShape voxelshape1 = fromState.getCollisionShape(world, fromPos);
boolean flag = !Shapes.mergedFaceOccludes(voxelshape, voxelshape1, face);
+ /*
if (object2bytelinkedopenhashmap != null) {
if (object2bytelinkedopenhashmap.size() == 200) {
object2bytelinkedopenhashmap.removeLastByte();
@@ -272,6 +302,11 @@ public abstract class FlowingFluid extends Fluid {
object2bytelinkedopenhashmap.putAndMoveToFirst(block_a, (byte) (flag ? 1 : 0));
}
+ */
+ if (cache != null) {
+ cache.putValue(block_a, flag);
+ }
+ // Airplane end
return flag;
}
diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java b/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java
index 05b64f2730bfe836bd1d72dcfccd9f536908a099..39e941a6a315e2a9fc0f47eb39ef9d2b58069f90 100644
--- a/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java
+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java
@@ -41,8 +41,10 @@ public class LootContext {
this.level = world;
this.lootTables = tableGetter;
this.conditions = conditionGetter;
- this.params = ImmutableMap.copyOf(parameters);
- this.dynamicDrops = ImmutableMap.copyOf(drops);
+ // Airplane start - use unmodifiable maps instead of immutable ones to skip the copy
+ this.params = java.util.Collections.unmodifiableMap(parameters);
+ this.dynamicDrops = java.util.Collections.unmodifiableMap(drops);
+ // Airplane end
}
public boolean hasParam(LootContextParam<?> parameter) {
diff --git a/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java b/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java
index fcb7bd9f3b6b6ada0f2e5692bce32ab76b8798a7..61c2096f2c034dbc3ad33b193b058c7d0d05e909 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java
@@ -22,55 +22,82 @@ public class EntityCollisionContext implements CollisionContext {
return defaultValue;
}
};
- private final boolean descending;
- private final double entityBottom;
- private final ItemStack heldItem;
- private final ItemStack footItem;
- private final Predicate<Fluid> canStandOnFluid;
- private final Optional<Entity> entity;
+ // Airplane start - remove these and pray no plugin uses them
+ //private final boolean descending;
+ //private final double entityBottom;
+ //private final ItemStack heldItem;
+ //private final ItemStack footItem;
+ //private final Predicate<Fluid> canStandOnFluid;
+ // Airplane end
+ private final @org.jetbrains.annotations.Nullable Entity entity; // Airplane
protected EntityCollisionContext(boolean descending, double minY, ItemStack boots, ItemStack heldItem, Predicate<Fluid> walkOnFluidPredicate, Optional<Entity> entity) {
- this.descending = descending;
- this.entityBottom = minY;
- this.footItem = boots;
- this.heldItem = heldItem;
- this.canStandOnFluid = walkOnFluidPredicate;
- this.entity = entity;
+ // Airplane start
+ //this.descending = descending;
+ //this.entityBottom = minY;
+ //this.footItem = boots;
+ //this.heldItem = heldItem;
+ ///this.canStandOnFluid = walkOnFluidPredicate;
+ this.entity = entity.orElse(null);
+ // Airplane end
}
@Deprecated
protected EntityCollisionContext(Entity entity) {
+ // Airplane start - remove unneeded things
+ /*
this(entity.isDescending(), entity.getY(), entity instanceof LivingEntity ? ((LivingEntity)entity).getItemBySlot(EquipmentSlot.FEET) : ItemStack.EMPTY, entity instanceof LivingEntity ? ((LivingEntity)entity).getMainHandItem() : ItemStack.EMPTY, entity instanceof LivingEntity ? ((LivingEntity)entity)::canStandOnFluid : (fluid) -> {
return false;
}, Optional.of(entity));
+ */
+ this.entity = entity;
+ // Airplane end
}
@Override
public boolean hasItemOnFeet(Item item) {
- return this.footItem.is(item);
+ // Airplane start
+ Entity entity = this.entity;
+ if (entity instanceof LivingEntity livingEntity) {
+ return livingEntity.getItemBySlot(EquipmentSlot.FEET).is(item);
+ }
+ return ItemStack.EMPTY.is(item);
+ // Airplane end
}
@Override
public boolean isHoldingItem(Item item) {
- return this.heldItem.is(item);
+ // Airplane start
+ Entity entity = this.entity;
+ if (entity instanceof LivingEntity livingEntity) {
+ return livingEntity.getMainHandItem().is(item);
+ }
+ return ItemStack.EMPTY.is(item);
+ // Airplane end
}
@Override
public boolean canStandOnFluid(FluidState state, FlowingFluid fluid) {
- return this.canStandOnFluid.test(fluid) && !state.getType().isSame(fluid);
+ // Airplane start
+ Entity entity = this.entity;
+ if (entity instanceof LivingEntity livingEntity) {
+ return livingEntity.canStandOnFluid(fluid) && !state.getType().isSame(fluid);
+ }
+ return false;
+ // Airplane end
}
@Override
public boolean isDescending() {
- return this.descending;
+ return this.entity != null && this.entity.isDescending(); // Airplane
}
@Override
public boolean isAbove(VoxelShape shape, BlockPos pos, boolean defaultValue) {
- return this.entityBottom > (double)pos.getY() + shape.max(Direction.Axis.Y) - (double)1.0E-5F;
+ return (this.entity == null ? -Double.MAX_VALUE : entity.getY()) > (double)pos.getY() + shape.max(Direction.Axis.Y) - (double)1.0E-5F; // Airplane
}
public Optional<Entity> getEntity() {
- return this.entity;
+ return Optional.ofNullable(this.entity); // Airplane
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index cfbde4c1220b0d6081a3e4ad2375e0d7bfb2ef40..ae2c4388eddef529fba5d5eae354ee15c6e36c9d 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -251,7 +251,7 @@ import javax.annotation.Nullable; // Paper
import javax.annotation.Nonnull; // Paper
public final class CraftServer implements Server {
- private final String serverName = "Paper"; // Paper
+ private final String serverName = "Airplane"; // Paper // Airplane
private final String serverVersion;
private final String bukkitVersion = Versioning.getBukkitVersion();
private final Logger logger = Logger.getLogger("Minecraft");
@@ -1035,6 +1035,11 @@ public final class CraftServer implements Server {
plugin.getDescription().getName(),
"This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies."
));
+ getLogger().log(Level.SEVERE, String.format("%s Stacktrace", worker.getThread().getName()));
+ StackTraceElement[] stackTrace = worker.getThread().getStackTrace();
+ for (StackTraceElement element : stackTrace) {
+ getLogger().log(Level.SEVERE, " " + element.toString());
+ }
}
}
// Paper end
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
index 0b3b46348ac9195bff1492ffc11fcbff7d3f5c6f..4010052c53f3a2831b4d5aa1c041d85897856acb 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
@@ -43,6 +43,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe
data.set(i, toNMS(ingred.get(i), true));
}
- MinecraftServer.getServer().getRecipeManager().addRecipe(new net.minecraft.world.item.crafting.ShapelessRecipe(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftItemStack.asNMSCopy(this.getResult()), data));
+ MinecraftServer.getServer().getRecipeManager().addRecipe(new net.minecraft.world.item.crafting.ShapelessRecipe(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftItemStack.asNMSCopy(this.getResult()), data, true));
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
index 909b2c98e7a9117d2f737245e4661792ffafb744..9da898c6f44832b4421b8c2745e3121bd13a71ab 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
@@ -22,7 +22,8 @@ public class MinecraftInternalPlugin extends PluginBase {
private boolean enabled = true;
private final String pluginName;
- private PluginDescriptionFile pdf;
+ private org.bukkit.plugin.PluginLogger logger;
+ private PluginDescriptionFile pdf; // Airplane
public MinecraftInternalPlugin() {
this.pluginName = "Minecraft";
@@ -75,7 +76,12 @@ public class MinecraftInternalPlugin extends PluginBase {
@Override
public PluginLogger getLogger() {
- throw new UnsupportedOperationException("Not supported.");
+ // Airplane start
+ if (this.logger == null) {
+ this.logger = new org.bukkit.plugin.PluginLogger(this); // Airplane
+ }
+ return this.logger;
+ // Airplane end
}
@Override
@@ -85,7 +91,7 @@ public class MinecraftInternalPlugin extends PluginBase {
@Override
public Server getServer() {
- throw new UnsupportedOperationException("Not supported.");
+ return org.bukkit.Bukkit.getServer(); // Airplane - impl
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index dcaa189c17dd928d7a19e820ec2ff521e7243b7a..25bb190bf7e08539a3590ee35a01f1d816e48d83 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -394,7 +394,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
@Override
public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
- return new com.destroystokyo.paper.PaperVersionFetcher();
+ return new gg.airplane.AirplaneVersionFetcher(); // Airplane
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
index d752720f2f234b9dbd2117333fee1bfad663ec02..9868b3a9a35cea9689c76ea9b62f2732ab61c94c 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -11,6 +11,7 @@ public class ServerShutdownThread extends Thread {
@Override
public void run() {
+ try { gg.airplane.flare.ProfilingManager.stop(); } catch (Throwable t) {} // Airplane - shut down Flare if it's running
try {
// Paper start - try to shutdown on main
server.safeShutdown(false, false);
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
index 774556a62eb240da42e84db4502e2ed43495be17..1788d79ea489e446d3d9f541693d4ba3dfc26015 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/gg.airplane/airplane-api/pom.properties"); // Tuinity // Airplane
Properties properties = new Properties();
if (stream != null) {
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 9c456cce42ef9d1654df9047d6fc1e0da13dc1c9..3cc2cf10efbe05cf91876f760c786ab4d2217858 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -37,6 +37,10 @@ import co.aikar.timings.MinecraftTimings;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
+// Airplane start
+import net.minecraft.world.phys.Vec3;
+import java.util.List;
+// Airplane end
public class ActivationRange
{
@@ -210,6 +214,21 @@ public class ActivationRange
for (int i = 0; i < entities.size(); i++) {
Entity entity = entities.get(i);
ActivationRange.activateEntity(entity);
+
+ // Airplane start
+ if (gg.airplane.AirplaneConfig.dearEnabled && entity.getType().dabEnabled) {
+ Vec3 playerVec = player.position();
+ Vec3 entityVec = entity.position();
+ double diffX = playerVec.x - entityVec.x, diffY = playerVec.y - entityVec.y, diffZ = playerVec.z - entityVec.z;
+ int squaredDistance = (int) (diffX * diffX + diffY * diffY + diffZ * diffZ);
+ entity.activatedPriority = squaredDistance > gg.airplane.AirplaneConfig.startDistanceSquared ?
+ Math.max(1, Math.min(squaredDistance >> gg.airplane.AirplaneConfig.activationDistanceMod, gg.airplane.AirplaneConfig.maximumActivationPrio)) :
+ 1;
+ } else {
+ entity.activatedPriority = 1;
+ }
+ // Airplane end
+
}
// Paper end
}
@@ -226,12 +245,12 @@ public class ActivationRange
if ( MinecraftServer.currentTick > entity.activatedTick )
{
if ( entity.defaultActivationState )
- {
+ { // Airplane - diff on change
entity.activatedTick = MinecraftServer.currentTick;
return;
}
if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) )
- {
+ { // Airplane - diff on change
entity.activatedTick = MinecraftServer.currentTick;
}
}
@@ -279,7 +298,7 @@ public class ActivationRange
if ( entity instanceof LivingEntity )
{
LivingEntity living = (LivingEntity) entity;
- if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper
+ if ( living.onClimableCached() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper // Airplane - use cached
{
return 1; // Paper
}