diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53226e20..140aa052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,23 +20,12 @@ jobs: - uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' - - # We can't use 'maven' prebuilt cache setup because it requires that the project have a pom file. - # BuildTools installs to Maven local if available, so it's easier to just rely on that. - - name: Cache Spigot dependency - uses: actions/cache@v5 - with: - path: | - ~/.m2/repository/org/spigotmc/ - key: ${{ runner.os }}-buildtools-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-buildtools- + java-version: '25' - uses: gradle/actions/setup-gradle@v5 - name: Build with Gradle - run: ./gradlew clean build + run: ./gradlew build # Upload artifacts - name: Upload Distributable Jar diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index aef4e143..08bf5248 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,6 +9,5 @@ repositories { dependencies { val libs = project.extensions.getByType(VersionCatalogsExtension::class.java).named("libs") - implementation(libs.findLibrary("specialsource").orElseThrow()) implementation(libs.findLibrary("errorprone-gradle").orElseThrow()) } diff --git a/buildSrc/src/main/kotlin/com/github/jikoo/openinv/BuildToolsValueSource.kt b/buildSrc/src/main/kotlin/com/github/jikoo/openinv/BuildToolsValueSource.kt index 2bedf878..6ddd7113 100644 --- a/buildSrc/src/main/kotlin/com/github/jikoo/openinv/BuildToolsValueSource.kt +++ b/buildSrc/src/main/kotlin/com/github/jikoo/openinv/BuildToolsValueSource.kt @@ -16,7 +16,7 @@ abstract class BuildToolsValueSource : ValueSource + val installDir: DirectoryProperty val workingDir: DirectoryProperty val spigotVersion: Property @@ -31,11 +31,11 @@ abstract class BuildToolsValueSource : ValueSource { - - companion object { - const val ARTIFACT_CONFIG = "reobf" - } - - override fun apply(target: Project) { - // Re-use extension from Spigot dependency declaration if available to reduce configuration requirements. - val spigotExt = target.dependencies.extensions.findByType(SpigotDependencyExtension::class.java) - ?: target.dependencies.extensions.create( - "spigot", - SpigotDependencyExtension::class.java, - target.objects - ) - - val mvnLocal = target.repositories.mavenLocal() - - val reobfTask = target.tasks.register("reobfTask") { - dependsOn(target.tasks.named("shadowJar")) - // ShadowJar extends Jar, so this should be a safe way to get the result without having - // to jump through hoops and shift around shadow declarations in the rest of the project. - inputFile.convention(target.tasks.named("shadowJar").get().archiveFile) - spigotVersion.convention(spigotExt.version) - getMavenLocal().set(Paths.get(mvnLocal.url).toFile()) - } - - // Set up configuration for producing reobf jar. - target.configurations.consumable(ARTIFACT_CONFIG) { - attributes { - attribute(Category.CATEGORY_ATTRIBUTE, target.objects.named(Category.LIBRARY)) - attribute(Usage.USAGE_ATTRIBUTE, target.objects.named(Usage.JAVA_RUNTIME)) - attribute(Bundling.BUNDLING_ATTRIBUTE, target.objects.named(Bundling.EXTERNAL)) - attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, target.objects.named(LibraryElements.JAR)) - } - } - - // Add artifact from reobf task. - target.artifacts { - add(ARTIFACT_CONFIG, reobfTask) - } - } - -} diff --git a/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotReobfTask.kt b/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotReobfTask.kt deleted file mode 100644 index e9e12ede..00000000 --- a/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotReobfTask.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.jikoo.openinv - -import net.md_5.specialsource.Jar -import net.md_5.specialsource.JarMapping -import net.md_5.specialsource.JarRemapper -import net.md_5.specialsource.RemapperProcessor -import net.md_5.specialsource.provider.JarProvider -import net.md_5.specialsource.provider.JointProvider -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.TaskAction -import java.io.File - -abstract class SpigotReobfTask : org.gradle.api.tasks.bundling.Jar() { - - @get:Input - val spigotVersion: Property = objectFactory.property(String::class.java) - - @get:InputFile - val inputFile: RegularFileProperty = objectFactory.fileProperty() - - @get:Input - val intermediaryClassifier: Property = objectFactory.property(String::class.java).convention("mojang-mapped") - - private val mavenLocal: Property = objectFactory.property(File::class.java) - - init { - archiveClassifier.convention(SpigotReobf.ARTIFACT_CONFIG) - } - - @TaskAction - override fun copy() { - val spigotVer = spigotVersion.get() - val inFile = inputFile.get().asFile - val obfPath = inFile.resolveSibling(inFile.name.replace(".jar", "-${intermediaryClassifier.get()}.jar")) - - // https://www.spigotmc.org/threads/510208/#post-4184317 - val repo = mavenLocal.get() - val spigotDir = repo.resolve("org/spigotmc/spigot/$spigotVer/") - val mappingDir = repo.resolve("org/spigotmc/minecraft-server/$spigotVer/") - - // Remap original Mojang-mapped jar to obfuscated intermediary - val mojangServer = spigotDir.resolve("spigot-$spigotVer-remapped-mojang.jar") - val mojangMappings = mappingDir.resolve("minecraft-server-$spigotVer-maps-mojang.txt") - remapPartial(mojangServer, mojangMappings, inFile, obfPath, true) - - // Remap obfuscated intermediary jar to Spigot and replace original - val obfServer = spigotDir.resolve("spigot-$spigotVer-remapped-obf.jar") - val spigotMappings = mappingDir.resolve("minecraft-server-$spigotVer-maps-spigot.csrg") - remapPartial(obfServer, spigotMappings, obfPath, archiveFile.get().asFile, false) - } - - private fun remapPartial(server: File, mapping: File, input: File, output: File, reverse: Boolean) { - val jarMapping = JarMapping() - jarMapping.loadMappings(mapping.path, reverse, false, null, null) - - val inheritance = JointProvider() - jarMapping.setFallbackInheritanceProvider(inheritance) - - // Equivalent of --live with server jar on classpath. - val serverJar = Jar.init(server) - inheritance.add(JarProvider(serverJar)) - - val inputJar = Jar.init(input) - inheritance.add(JarProvider(inputJar)) - - // Remap reflective access. - val preprocessor = RemapperProcessor(null, jarMapping, null) - - val remapper = JarRemapper(preprocessor, jarMapping, null) - remapper.remapJar(inputJar, output, emptySet()) - - serverJar.close() - inputJar.close() - } - - @Internal - internal fun getMavenLocal(): Property { - return mavenLocal - } - -} diff --git a/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotSetup.kt b/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotSetup.kt index 8cdfae20..1f41c3c8 100644 --- a/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotSetup.kt +++ b/buildSrc/src/main/kotlin/com/github/jikoo/openinv/SpigotSetup.kt @@ -4,8 +4,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginExtension import org.gradle.jvm.toolchain.JavaToolchainService -import org.gradle.kotlin.dsl.create -import java.nio.file.Paths +import java.io.File import javax.inject.Inject abstract class SpigotSetup : Plugin { @@ -24,17 +23,15 @@ abstract class SpigotSetup : Plugin { target.objects ) - val mvnLocal = target.repositories.mavenLocal() - target.afterEvaluate { // Get Java requirements, defaulting to version used for compilation. spigotExt.java.convention(target.extensions.getByType(JavaPluginExtension::class.java).toolchain) val launcher = javaToolchainService.launcherFor(spigotExt.java.get()).get() // Install Spigot with BuildTools. - target.providers.of(BuildToolsValueSource::class.java) { + val spigot: File = target.providers.of(BuildToolsValueSource::class.java) { parameters { - mavenLocal.set(Paths.get(mvnLocal.url).toFile()) + installDir.set(target.gradle.gradleUserHomeDir.resolve("caches/spigot")) workingDir.set(target.layout.buildDirectory.dir("tmp/buildtools")) spigotVersion.set(spigotExt.version) spigotRevision.set(spigotExt.revision) @@ -45,9 +42,7 @@ abstract class SpigotSetup : Plugin { }.get() // Add Spigot dependency. - val dependency = target.dependencies.create( - "org.spigotmc:spigot:${spigotExt.version.get()}:${spigotExt.classifier.getOrElse("")}" - ) + val dependency = target.dependencies.create(files(spigot)) target.dependencies.add("compileOnly", dependency) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dcd2cb7..572b1fc6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] spigotapi = "1.21.5-R0.1-SNAPSHOT" -specialsource = "1.11.6" planarwrappers = "3.3.0" annotations = "26.1.0" paperweight = "2.0.0-beta.17" @@ -13,7 +12,6 @@ sqlite-jdbc = "3.49.1.0" [libraries] spigotapi = { module = "org.spigotmc:spigot-api", version.ref = "spigotapi" } -specialsource = { module = "net.md-5:SpecialSource", version.ref = "specialsource" } planarwrappers = { module = "com.github.jikoo:planarwrappers", version.ref = "planarwrappers" } annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } folia-scheduler-wrapper = { module = "com.github.NahuLD.folia-scheduler-wrapper:folia-scheduler-wrapper", version.ref = "folia-scheduler-wrapper" } diff --git a/internal/common/build.gradle.kts b/internal/common/build.gradle.kts index 71ca46d2..e94b4ea3 100644 --- a/internal/common/build.gradle.kts +++ b/internal/common/build.gradle.kts @@ -1,5 +1,3 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { `openinv-base` alias(libs.plugins.paperweight) @@ -35,26 +33,3 @@ dependencies { paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") } - -val spigot = tasks.register("spigotRelocations") { - dependsOn(tasks.jar) - from(sourceSets.main.get().output) - relocate("com.lishid.openinv.internal.common", "com.lishid.openinv.internal.reobf") - relocate("org.bukkit.craftbukkit", "org.bukkit.craftbukkit.${rootProject.extra["craftbukkitPackage"]}") - archiveClassifier = "spigot" -} - -configurations { - consumable("spigotRelocated") { - attributes { - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) - attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) - attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR)) - } - } -} - -artifacts { - add("spigotRelocated", spigot) -} diff --git a/internal/spigot/build.gradle.kts b/internal/spigot/build.gradle.kts index 526d5998..ee8f8d6c 100644 --- a/internal/spigot/build.gradle.kts +++ b/internal/spigot/build.gradle.kts @@ -1,5 +1,4 @@ import com.github.jikoo.openinv.SpigotDependencyExtension -import com.github.jikoo.openinv.SpigotReobf import com.github.jikoo.openinv.SpigotSetup plugins { @@ -7,12 +6,11 @@ plugins { alias(libs.plugins.shadow) } -apply() -apply() +java { + toolchain.languageVersion = JavaLanguageVersion.of(25) +} -val spigotVer = "1.21.11-R0.2-SNAPSHOT" -// Used by common adapter to relocate Craftbukkit classes to a versioned package. -rootProject.extra["craftbukkitPackage"] = "v1_21_R7" +apply() configurations.all { resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") { @@ -30,15 +28,12 @@ configurations.all { dependencies { compileOnly(libs.spigotapi) - extensions.getByType(SpigotDependencyExtension::class.java).version = spigotVer + extensions.getByType(SpigotDependencyExtension::class.java).version = "26.1-R0.1-SNAPSHOT" + compileOnly("com.mojang:logging:1.6.11") + compileOnly("com.mojang:brigadier:1.3.10") + compileOnly("com.mojang:datafixerupper:9.0.19") + compileOnly("com.mojang:authlib:7.0.62") compileOnly(project(":openinvapi")) compileOnly(project(":openinvcommon")) - - // Reduce duplicate code by lightly remapping common adapter. - implementation(project(":openinvadaptercommon", configuration = "spigotRelocated")) -} - -tasks.shadowJar { - relocate("com.lishid.openinv.internal.common", "com.lishid.openinv.internal.reobf") } diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/InternalAccessor.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/InternalAccessor.java new file mode 100644 index 00000000..7ac4bd1f --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/InternalAccessor.java @@ -0,0 +1,80 @@ +package com.github.jikoo.openinv.internal.spigot26_1; + +import com.github.jikoo.openinv.internal.spigot26_1.container.AnySilentContainer; +import com.github.jikoo.openinv.internal.spigot26_1.container.OpenEnderChest; +import com.github.jikoo.openinv.internal.spigot26_1.container.OpenInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.PlaceholderLoader; +import com.github.jikoo.openinv.internal.spigot26_1.player.PlayerManager; +import com.lishid.openinv.internal.Accessor; +import com.lishid.openinv.internal.IAnySilentContainer; +import com.lishid.openinv.internal.ISpecialEnderChest; +import com.lishid.openinv.internal.ISpecialInventory; +import com.lishid.openinv.internal.ISpecialPlayerInventory; +import com.lishid.openinv.util.lang.LanguageManager; +import net.minecraft.world.Container; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class InternalAccessor implements Accessor { + + protected final @NotNull Logger logger; + private final @NotNull PlayerManager manager; + private final @NotNull AnySilentContainer anySilentContainer; + + public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { + this.logger = logger; + manager = new PlayerManager(logger); + anySilentContainer = new AnySilentContainer(logger, lang); + } + + @Override + public @NotNull PlayerManager getPlayerManager() { + return manager; + } + + @Override + public @NotNull IAnySilentContainer getAnySilentContainer() { + return anySilentContainer; + } + + @Override + public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) { + return new OpenInventory(player); + } + + @Override + public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) { + return new OpenEnderChest(player); + } + + @Override + public @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class clazz) { + if (!(bukkitInventory instanceof CraftInventory craftInventory)) { + return null; + } + Container container = craftInventory.getInventory(); + if (clazz.isInstance(container)) { + return clazz.cast(container); + } + return null; + } + + @Override + public void reload(@NotNull ConfigurationSection config) { + ConfigurationSection placeholders = config.getConfigurationSection("placeholders"); + try { + // Reset placeholders to defaults and try to load configuration. + new PlaceholderLoader().load(placeholders); + } catch (Exception e) { + logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e); + } + } + +} diff --git a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/AnySilentContainer.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/AnySilentContainer.java similarity index 96% rename from internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/AnySilentContainer.java rename to internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/AnySilentContainer.java index 87ad310a..99865095 100644 --- a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/AnySilentContainer.java +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/AnySilentContainer.java @@ -1,8 +1,8 @@ -package com.lishid.openinv.internal.reobf.container; +package com.github.jikoo.openinv.internal.spigot26_1.container; import com.lishid.openinv.internal.AnySilentContainerBase; -import com.lishid.openinv.internal.reobf.container.menu.OpenChestMenu; -import com.lishid.openinv.internal.reobf.player.PlayerManager; +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenChestMenu; +import com.github.jikoo.openinv.internal.spigot26_1.player.PlayerManager; import com.lishid.openinv.util.ReflectionHelper; import com.lishid.openinv.util.lang.LanguageManager; import net.minecraft.core.BlockPos; @@ -205,7 +205,7 @@ protected org.bukkit.block.BlockState getState(@NotNull org.bukkit.block.Block b } @Override - protected InventoryHolder getHolder(@NotNull Inventory inventory) { + protected @Nullable InventoryHolder getHolder(@NotNull Inventory inventory) { return inventory.getHolder(); } diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/BaseOpenInventory.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/BaseOpenInventory.java new file mode 100644 index 00000000..f513bdb5 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/BaseOpenInventory.java @@ -0,0 +1,335 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container; + +import com.github.jikoo.openinv.internal.spigot26_1.container.bukkit.OpenPlayerInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenChestMenu; +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenInventoryMenu; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.Content; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentCrafting; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentCraftingResult; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentCursor; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentDrop; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentEquipment; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentList; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentOffHand; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentViewOnly; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.SlotViewOnly; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; +import com.github.jikoo.openinv.internal.spigot26_1.player.PlayerManager; +import com.lishid.openinv.internal.ISpecialPlayerInventory; +import com.lishid.openinv.internal.InternalOwned; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.core.NonNullList; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public abstract class BaseOpenInventory implements Container, InternalOwned, ISpecialPlayerInventory { + + protected final List slots; + private final int size; + protected ServerPlayer owner; + private int maxStackSize = 99; + protected CraftInventory bukkitEntity; + public List transaction = new ArrayList<>(); + + public BaseOpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) { + owner = PlayerManager.getHandle(bukkitPlayer); + + // Get total size, rounding up to nearest 9 for client compatibility. + int rawSize = owner.getInventory().getContainerSize() + owner.inventoryMenu.getCraftSlots().getContainerSize() + 1; + size = ((int) Math.ceil(rawSize / 9.0)) * 9; + + slots = NonNullList.withSize(size, new ContentViewOnly(owner)); + setupSlots(); + } + + protected void setupSlots() { + // Top of inventory: Regular contents. + int nextIndex = addMainInventory(); + + // If inventory is expected size, we can arrange slots to be pretty. + Inventory ownerInv = owner.getInventory(); + if (ownerInv.getNonEquipmentItems().size() == 36 + && owner.inventoryMenu.getCraftSlots().getContainerSize() == 4 + && (Inventory.EQUIPMENT_SLOT_MAPPING.size() == 5 || Inventory.EQUIPMENT_SLOT_MAPPING.size() == 7)) { + // Armor slots: Bottom left. + addArmor(36); + // Off-hand: Below chestplate. + addOffHand(46); + // Drop slot: Bottom right. + slots.set(53, new ContentDrop(owner)); + // Cursor slot: Above drop. + slots.set(44, new ContentCursor(owner)); + + // Crafting is displayed in the bottom right corner. + // As we're using the pretty view, this is a 3x2. + addCrafting(41, true); + return; + } + + // Otherwise we'll just add elements linearly. + nextIndex = addArmor(nextIndex); + nextIndex = addOffHand(nextIndex); + nextIndex = addCrafting(nextIndex, false); + slots.set(nextIndex, new ContentCursor(owner)); + // Drop slot last. + slots.set(slots.size() - 1, new ContentDrop(owner)); + } + + private int addMainInventory() { + int listSize = owner.getInventory().getNonEquipmentItems().size(); + // Hotbar slots are 0-8. We want those to appear on the bottom of the inventory like a normal player inventory, + // so everything else needs to move up a row. + int hotbarDiff = listSize - 9; + for (int localIndex = 0; localIndex < listSize; ++localIndex) { + InventoryType.SlotType type; + int invIndex; + if (localIndex < hotbarDiff) { + invIndex = localIndex + 9; + type = InventoryType.SlotType.CONTAINER; + } else { + type = InventoryType.SlotType.QUICKBAR; + invIndex = localIndex - hotbarDiff; + } + + slots.set( + localIndex, + new ContentList(owner, invIndex, type) { + @Override + public void setHolder(@NotNull ServerPlayer holder) { + items = holder.getInventory().getNonEquipmentItems(); + } + } + ); + } + return listSize; + } + + private int addArmor(int startIndex) { + // Armor slots go bottom to top; boots are first and helmet is last. + // Since we have to display horizontally due to space restrictions, + // making the left side the "top" is more user-friendly. + EquipmentSlot[] sorted = Inventory.EQUIPMENT_SLOT_MAPPING.int2ObjectEntrySet() + .stream() + .sorted(Comparator.comparingInt(Int2ObjectMap.Entry::getIntKey)) + .map(Map.Entry::getValue) + .toArray(EquipmentSlot[]::new); + int localIndex = 0; + for (int i = sorted.length - 1; i >= 0; --i) { + // Skip off-hand, handled separately. Also skip non-player slots. + if (sorted[i].getType() != EquipmentSlot.Type.HUMANOID_ARMOR) { + continue; + } + + slots.set(startIndex + localIndex, new ContentEquipment(owner, sorted[i])); + ++localIndex; + } + + return startIndex + localIndex; + } + + private int addOffHand(int startIndex) { + // No off-hand? + if (!Inventory.EQUIPMENT_SLOT_MAPPING.containsValue(EquipmentSlot.OFFHAND)) { + return startIndex; + } + + slots.set(startIndex, new ContentOffHand(owner)); + return startIndex + 1; + } + + private int addCrafting(int startIndex, boolean pretty) { + int listSize = owner.inventoryMenu.getCraftSlots().getContents().size(); + pretty &= listSize == 4; + + for (int localIndex = 0; localIndex < listSize; ++localIndex) { + // Pretty display is a 2x2 rather than linear. + // If index is in top row, grid is not 2x2, or pretty is disabled, just use current index. + // Otherwise, subtract 2 and add 9 to start in the same position on the next row. + int modIndex = startIndex + (localIndex < 2 || !pretty ? localIndex : localIndex + 7); + + slots.set(modIndex, new ContentCrafting(owner, localIndex)); + } + + if (pretty) { + slots.set(startIndex + 2, new ContentViewOnly(owner) { + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotViewOnly(container, slot, x, y) { + @Override + public ItemStack getOrDefault() { + return Placeholders.craftingOutput; + } + }; + } + } + ); + slots.set(startIndex + 11, getCraftingResult(owner)); + } + + return startIndex + listSize; + } + + protected Content getCraftingResult(@NotNull ServerPlayer serverPlayer) { + return new ContentCraftingResult(serverPlayer); + } + + public Slot getMenuSlot(int index, int x, int y) { + return slots.get(index).asSlot(this, index, x, y); + } + + public InventoryType.SlotType getSlotType(int index) { + return slots.get(index).getSlotType(); + } + + public abstract Component getTitle(ServerPlayer player, @Nullable OpenChestMenu menu); + + @Override + public ServerPlayer getOwnerHandle() { + return owner; + } + + @Override + public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() { + if (bukkitEntity == null) { + bukkitEntity = new OpenPlayerInventory(this); + } + return bukkitEntity; + } + + @Override + public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) { + ServerPlayer newOwner = PlayerManager.getHandle(player); + // Only transfer regular inventory - crafting and cursor slots are transient. + newOwner.getInventory().replaceWith(owner.getInventory()); + owner = newOwner; + // Update slots to point to new inventory. + slots.forEach(slot -> slot.setHolder(newOwner)); + } + + @Override + public boolean isInUse() { + return !transaction.isEmpty(); + } + + @Override + public @NotNull org.bukkit.entity.Player getPlayer() { + return getOwner(); + } + + @Override + public int getContainerSize() { + return size; + } + + @Override + public boolean isEmpty() { + return slots.stream().map(Content::get).allMatch(ItemStack::isEmpty); + } + + @Override + public @NotNull ItemStack getItem(int index) { + return slots.get(index).get(); + } + + @Override + public @NotNull ItemStack removeItem(int index, int amount) { + return slots.get(index).removePartial(amount); + } + + @Override + public @NotNull ItemStack removeItemNoUpdate(int index) { + return slots.get(index).remove(); + } + + @Override + public void setItem(int index, @NotNull ItemStack itemStack) { + slots.get(index).set(itemStack); + } + + @Override + public int getMaxStackSize() { + return maxStackSize; + } + + @Override + public void setMaxStackSize(int maxStackSize) { + this.maxStackSize = maxStackSize; + } + + @Override + public void setChanged() { + } + + @Override + public boolean stillValid(@NotNull Player player) { + return true; + } + + @Override + public @NotNull List getContents() { + NonNullList contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY); + for (int i = 0; i < getContainerSize(); ++i) { + contents.set(i, getItem(i)); + } + return contents; + } + + @Override + public void onOpen(@NotNull CraftHumanEntity viewer) { + transaction.add(viewer); + } + + @Override + public void onClose(@NotNull CraftHumanEntity viewer) { + transaction.remove(viewer); + } + + @Override + public @NotNull List getViewers() { + return transaction; + } + + @Override + public @NotNull org.bukkit.entity.Player getOwner() { + return owner.getBukkitEntity(); + } + + @Override + public @NotNull Location getLocation() { + return owner.getBukkitEntity().getLocation(); + } + + @Override + public void clearContent() { + owner.getInventory().clearContent(); + owner.inventoryMenu.getCraftSlots().clearContent(); + owner.inventoryMenu.slotsChanged(owner.inventoryMenu.getCraftSlots()); + owner.containerMenu.setCarried(ItemStack.EMPTY); + } + + public @Nullable OpenChestMenu createMenu(Player player, int i, boolean viewOnly) { + if (player instanceof ServerPlayer serverPlayer) { + return new OpenInventoryMenu(this, serverPlayer, i, viewOnly); + } + return null; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/OpenEnderChest.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/OpenEnderChest.java new file mode 100644 index 00000000..3d91dd46 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/OpenEnderChest.java @@ -0,0 +1,201 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container; + +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenChestMenu; +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenEnderChestMenu; +import com.github.jikoo.openinv.internal.spigot26_1.player.PlayerManager; +import com.lishid.openinv.internal.ISpecialEnderChest; +import com.lishid.openinv.internal.InternalOwned; +import net.minecraft.core.NonNullList; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.player.StackedItemContents; +import net.minecraft.world.inventory.StackedContentsCompatible; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned, + ISpecialEnderChest { + + private CraftInventory inventory; + private @NotNull ServerPlayer owner; + private NonNullList items; + private int maxStack = 64; + private final List transaction = new ArrayList<>(); + + public OpenEnderChest(@NotNull org.bukkit.entity.Player player) { + this.owner = PlayerManager.getHandle(player); + this.items = owner.getEnderChestInventory().items; + } + + @Override + public @NotNull ServerPlayer getOwnerHandle() { + return owner; + } + + @Override + public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() { + if (inventory == null) { + inventory = new CraftInventory(this) { + @Override + public @NotNull InventoryType getType() { + return InventoryType.ENDER_CHEST; + } + }; + } + return inventory; + } + + @Override + public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) { + owner = PlayerManager.getHandle(player); + NonNullList activeItems = owner.getEnderChestInventory().items; + + // Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally. + int max = Math.min(items.size(), activeItems.size()); + for (int index = 0; index < max; ++index) { + activeItems.set(index, items.get(index)); + } + + items = activeItems; + } + + @Override + public @NotNull org.bukkit.entity.Player getPlayer() { + return owner.getBukkitEntity(); + } + + @Override + public int getContainerSize() { + return items.size(); + } + + @Override + public boolean isEmpty() { + return items.stream().allMatch(ItemStack::isEmpty); + } + + @Override + public @NotNull ItemStack getItem(int index) { + return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY; + } + + @Override + public @NotNull ItemStack removeItem(int index, int amount) { + ItemStack itemstack = ContainerHelper.removeItem(items, index, amount); + + if (!itemstack.isEmpty()) { + setChanged(); + } + + return itemstack; + } + + @Override + public @NotNull ItemStack removeItemNoUpdate(int index) { + return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY; + } + + @Override + public void setItem(int index, @NotNull ItemStack itemStack) { + if (index >= 0 && index < items.size()) { + items.set(index, itemStack); + } + } + + @Override + public int getMaxStackSize() { + return maxStack; + } + + @Override + public void setChanged() { + this.owner.getEnderChestInventory().setChanged(); + } + + @Override + public boolean stillValid(@NotNull Player player) { + return true; + } + + @Override + public @NotNull List getContents() { + return items; + } + + @Override + public void onOpen(@NotNull CraftHumanEntity craftHumanEntity) { + transaction.add(craftHumanEntity); + } + + @Override + public void onClose(@NotNull CraftHumanEntity craftHumanEntity) { + transaction.remove(craftHumanEntity); + } + + @Override + public @NotNull List getViewers() { + return transaction; + } + + @Override + public org.bukkit.entity.@NotNull Player getOwner() { + return getPlayer(); + } + + @Override + public void setMaxStackSize(int size) { + maxStack = size; + } + + @Override + public @Nullable Location getLocation() { + return null; + } + + @Override + public void clearContent() { + items.clear(); + setChanged(); + } + + @Override + public void fillStackedContents(@NotNull StackedItemContents stackedContents) { + for (ItemStack itemstack : items) { + stackedContents.accountStack(itemstack); + } + } + + public Component getTitle(@Nullable OpenChestMenu menu) { + MutableComponent component; + if (menu != null && menu.isViewOnly()) { + component = Component.translatableWithFallback("openinv.container.enderchest.viewonly", "[RO] "); + } else { + component = Component.translatableWithFallback("openinv.container.enderchest.editable", ""); + } + return component + .append(Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName())) + .append(Component.translatable("container.enderchest")) + .append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName())); + } + + public @Nullable OpenChestMenu createMenu(Player player, int i, boolean viewOnly) { + if (player instanceof ServerPlayer serverPlayer) { + return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly); + } + return null; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/OpenInventory.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/OpenInventory.java new file mode 100644 index 00000000..3ff8dec9 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/OpenInventory.java @@ -0,0 +1,49 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container; + +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenChestMenu; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FontDescription; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class OpenInventory extends BaseOpenInventory { + + public OpenInventory(@NotNull Player bukkitPlayer) { + super(bukkitPlayer); + } + + @Override + public @NotNull Component getTitle(@Nullable ServerPlayer viewer, @Nullable OpenChestMenu menu) { + MutableComponent component = Component.empty(); + // Prefix for use with custom bitmap image fonts. + if (owner.equals(viewer)) { + component.append( + Component.translatableWithFallback("openinv.container.inventory.self", "") + .withStyle(style -> style + .withFont(new FontDescription.Resource(Identifier.parse("openinv:font/inventory"))) + .withColor(ChatFormatting.WHITE))); + } else { + component.append( + Component.translatableWithFallback("openinv.container.inventory.other", "") + .withStyle(style -> style + .withFont(new FontDescription.Resource(Identifier.parse("openinv:font/inventory"))) + .withColor(ChatFormatting.WHITE))); + } + if (menu != null && menu.isViewOnly()) { + component.append(Component.translatableWithFallback("openinv.container.inventory.viewonly", "[RO] ")); + } else { + component.append(Component.translatableWithFallback("openinv.container.inventory.editable", "")); + } + // Normal title: "Inventory - OwnerName" + component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName())) + .append(Component.translatable("container.inventory")) + .append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName())); + return component; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenDummyInventory.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenDummyInventory.java new file mode 100644 index 00000000..4f19a2b3 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenDummyInventory.java @@ -0,0 +1,168 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.bukkit; + +import com.lishid.openinv.internal.ViewOnly; +import net.minecraft.world.Container; +import org.bukkit.Material; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.ListIterator; + +/** + * A locked down "empty" inventory that rejects plugin interaction. + */ +public class OpenDummyInventory extends CraftInventory implements ViewOnly { + + private final InventoryType type; + + public OpenDummyInventory(Container inventory, InventoryType type) { + super(inventory); + this.type = type; + } + + @Override + public @NotNull InventoryType getType() { + return type; + } + + @Override + public @Nullable ItemStack getItem(int index) { + return null; + } + + @Override + public void setItem(int index, @Nullable ItemStack item) { + + } + + @Override + public @NotNull HashMap addItem(@NotNull ItemStack... items) throws IllegalArgumentException { + return arrayToHashMap(items); + } + + @Override + public @NotNull HashMap removeItem(@NotNull ItemStack... items) throws IllegalArgumentException { + return arrayToHashMap(items); + } + + private static @NotNull HashMap arrayToHashMap(@NotNull ItemStack[] items) { + HashMap ignored = new HashMap<>(); + for (int index = 0; index < items.length; ++index) { + ignored.put(index, items[index]); + } + return ignored; + } + + @Override + public ItemStack @NotNull [] getContents() { + return new ItemStack[getSize()]; + } + + @Override + public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException { + + } + + @Override + public @NotNull ItemStack @NotNull [] getStorageContents() { + return new ItemStack[getSize()]; + } + + @Override + public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException { + + } + + @Override + public boolean contains(@NotNull Material material) throws IllegalArgumentException { + return false; + } + + @Override + public boolean contains(@Nullable ItemStack item) { + return false; + } + + @Override + public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException { + return false; + } + + @Override + public boolean contains(@Nullable ItemStack item, int amount) { + return false; + } + + @Override + public boolean containsAtLeast(@Nullable ItemStack item, int amount) { + return false; + } + + @Override + public @NotNull HashMap all( + @NotNull Material material + ) throws IllegalArgumentException { + return new HashMap<>(); + } + + @Override + public @NotNull HashMap all(@Nullable ItemStack item) { + return new HashMap<>(); + } + + @Override + public int first(@NotNull Material material) throws IllegalArgumentException { + return -1; + } + + @Override + public int first(@NotNull ItemStack item) { + return -1; + } + + @Override + public int firstEmpty() { + return -1; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void remove(@NotNull Material material) throws IllegalArgumentException { + + } + + @Override + public void remove(@NotNull ItemStack item) { + + } + + @Override + public void clear(int index) { + + } + + @Override + public void clear() { + + } + + @Override + public @NotNull ListIterator iterator() { + return Collections.emptyListIterator(); + } + + @Override + public @NotNull ListIterator iterator(int index) { + return Collections.emptyListIterator(); + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenDummyPlayerInventory.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenDummyPlayerInventory.java new file mode 100644 index 00000000..94651862 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenDummyPlayerInventory.java @@ -0,0 +1,137 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.bukkit; + +import net.minecraft.world.Container; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class OpenDummyPlayerInventory extends OpenDummyInventory implements PlayerInventory { + + public OpenDummyPlayerInventory(Container inventory) { + super(inventory, InventoryType.PLAYER); + } + + @Override + public HumanEntity getHolder() { + return (HumanEntity) super.getHolder(); + } + + @Override + public @NotNull ItemStack @NotNull [] getArmorContents() { + return new ItemStack[4]; + } + + @Override + public @NotNull ItemStack @NotNull [] getExtraContents() { + return new ItemStack[4]; + } + + @Override + public @Nullable ItemStack getHelmet() { + return null; + } + + @Override + public @Nullable ItemStack getChestplate() { + return null; + } + + @Override + public @Nullable ItemStack getLeggings() { + return null; + } + + @Override + public @Nullable ItemStack getBoots() { + return null; + } + + @Override + public void setItem(@NotNull EquipmentSlot slot, @Nullable ItemStack item) { + + } + + @Override + public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) { + return new ItemStack(Material.AIR); + } + + @Override + public void setArmorContents(ItemStack @NotNull [] items) { + + } + + @Override + public void setExtraContents(ItemStack @NotNull [] items) { + + } + + @Override + public void setHelmet(@Nullable ItemStack helmet) { + + } + + @Override + public void setChestplate(@Nullable ItemStack chestplate) { + + } + + @Override + public void setLeggings(@Nullable ItemStack leggings) { + + } + + @Override + public void setBoots(@Nullable ItemStack boots) { + + } + + @Override + public @NotNull ItemStack getItemInMainHand() { + return new ItemStack(Material.AIR); + } + + @Override + public void setItemInMainHand(@Nullable ItemStack item) { + + } + + @Override + public @NotNull ItemStack getItemInOffHand() { + return new ItemStack(Material.AIR); + } + + @Override + public void setItemInOffHand(@Nullable ItemStack item) { + + } + + @SuppressWarnings("InlineMeSuggester") + @Deprecated + @Override + public @NotNull ItemStack getItemInHand() { + return new ItemStack(Material.AIR); + } + + @Deprecated + @Override + public void setItemInHand(@Nullable ItemStack stack) { + + } + + @Override + public int getHeldItemSlot() { + return 0; + } + + @Override + public void setHeldItemSlot(int slot) { + + } + +} diff --git a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/bukkit/OpenPlayerInventory.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenPlayerInventory.java similarity index 95% rename from internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/bukkit/OpenPlayerInventory.java rename to internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenPlayerInventory.java index 77aefe24..512f5258 100644 --- a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/bukkit/OpenPlayerInventory.java +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenPlayerInventory.java @@ -1,11 +1,11 @@ -package com.lishid.openinv.internal.reobf.container.bukkit; +package com.github.jikoo.openinv.internal.spigot26_1.container.bukkit; import com.google.common.base.Preconditions; -import com.lishid.openinv.internal.reobf.container.BaseOpenInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.BaseOpenInventory; import net.minecraft.core.NonNullList; import net.minecraft.world.entity.player.Inventory; -import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenPlayerInventorySelf.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenPlayerInventorySelf.java new file mode 100644 index 00000000..7d57a8a6 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/bukkit/OpenPlayerInventorySelf.java @@ -0,0 +1,26 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.bukkit; + +import com.github.jikoo.openinv.internal.spigot26_1.container.BaseOpenInventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class OpenPlayerInventorySelf extends OpenPlayerInventory { + + private final int offset; + + public OpenPlayerInventorySelf(@NotNull BaseOpenInventory inventory, int offset) { + super(inventory); + this.offset = offset; + } + + @Override + public ItemStack getItem(int index) { + return super.getItem(offset + index); + } + + @Override + public void setItem(int index, ItemStack item) { + super.setItem(offset + index, item); + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenChestMenu.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenChestMenu.java new file mode 100644 index 00000000..92fe5046 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenChestMenu.java @@ -0,0 +1,288 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.menu; + +import com.github.jikoo.openinv.internal.spigot26_1.container.bukkit.OpenDummyInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.SlotViewOnly; +import com.lishid.openinv.internal.ISpecialInventory; +import com.lishid.openinv.internal.InternalOwned; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An extension of {@link AbstractContainerMenu} storing and managing data common to all special inventories. + */ +public abstract class OpenChestMenu> + extends AbstractContainerMenu { + + protected static final int BOTTOM_INVENTORY_SIZE = 36; + + protected final T container; + protected final ServerPlayer viewer; + protected final boolean viewOnly; + protected final boolean ownContainer; + protected final int topSize; + private CraftInventoryView, Inventory> bukkitEntity; + + protected OpenChestMenu( + @NotNull MenuType type, + int containerCounter, + @NotNull T container, + @NotNull ServerPlayer viewer, + boolean viewOnly + ) { + super(type, containerCounter); + this.container = container; + this.viewer = viewer; + this.viewOnly = viewOnly; + ownContainer = container.getOwnerHandle().equals(viewer); + topSize = getTopSize(viewer); + + preSlotSetup(); + + int upperRows = topSize / 9; + // View's upper inventory - our container + for (int row = 0; row < upperRows; ++row) { + for (int col = 0; col < 9; ++col) { + // x and y for client purposes, but hey, we're thorough here. + // Adapted from net.minecraft.world.inventory.ChestMenu + int x = 8 + col * 18; + int y = 18 + row * 18; + int index = row * 9 + col; + + // Guard against weird inventory sizes. + if (index >= container.getContainerSize()) { + addSlot(new SlotViewOnly(container, index, x, y)); + continue; + } + + Slot slot = getUpperSlot(index, x, y); + + addSlot(slot); + } + } + + // View's lower inventory - viewer inventory + int playerInvPad = (upperRows - 4) * 18; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 9; ++col) { + int x = 8 + col * 18; + int y = playerInvPad + row * 18 + 103; + addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y)); + } + } + // Hotbar + for (int col = 0; col < 9; ++col) { + int x = 8 + col * 18; + int y = playerInvPad + 161; + addSlot(new Slot(viewer.getInventory(), col, x, y)); + } + } + + public static @NotNull MenuType getChestMenuType(int inventorySize) { + inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9; + return switch (inventorySize) { + case 9 -> MenuType.GENERIC_9x1; + case 18 -> MenuType.GENERIC_9x2; + case 27 -> MenuType.GENERIC_9x3; + case 36 -> MenuType.GENERIC_9x4; + case 45 -> MenuType.GENERIC_9x5; + case 54 -> MenuType.GENERIC_9x6; + default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize); + }; + } + + + protected void preSlotSetup() { + } + + protected @NotNull Slot getUpperSlot(int index, int x, int y) { + Slot slot = new Slot(container, index, x, y); + if (viewOnly) { + return SlotViewOnly.wrap(slot); + } + return slot; + } + + public boolean isViewOnly() { + return viewOnly; + } + + @Override + public final @NotNull CraftInventoryView, Inventory> getBukkitView() { + if (bukkitEntity == null) { + bukkitEntity = createBukkitEntity(); + } + + return bukkitEntity; + } + + protected @NotNull CraftInventoryView, Inventory> createBukkitEntity() { + Inventory top; + if (viewOnly) { + top = new OpenDummyInventory(container, container.getBukkitType()); + } else { + top = container.getBukkitInventory(); + } + return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) { + @Override + public @Nullable Inventory getInventory(int rawSlot) { + if (viewOnly) { + return null; + } + return super.getInventory(rawSlot); + } + + @Override + public int convertSlot(int rawSlot) { + if (viewOnly) { + return InventoryView.OUTSIDE; + } + return super.convertSlot(rawSlot); + } + + @Override + public @NotNull InventoryType.SlotType getSlotType(int slot) { + if (viewOnly) { + return InventoryType.SlotType.OUTSIDE; + } + return super.getSlotType(slot); + } + }; + } + + private int getTopSize(ServerPlayer viewer) { + MenuType menuType = getType(); + if (menuType == MenuType.GENERIC_9x1) { + return 9; + } else if (menuType == MenuType.GENERIC_9x2) { + return 18; + } else if (menuType == MenuType.GENERIC_9x3) { + return 27; + } else if (menuType == MenuType.GENERIC_9x4) { + return 36; + } else if (menuType == MenuType.GENERIC_9x5) { + return 45; + } else if (menuType == MenuType.GENERIC_9x6) { + return 54; + } + // This is a bit gross, but allows us a safe fallthrough. + return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE; + } + + /** + * Reimplementation of {@link AbstractContainerMenu#moveItemStackTo(ItemStack, int, int, boolean)} that ignores fake + * slots and respects {@link Slot#hasItem()}. + * + * @param itemStack the stack to quick-move + * @param rangeLow the start of the range of slots that can be moved to, inclusive + * @param rangeHigh the end of the range of slots that can be moved to, exclusive + * @param topDown whether to start at the top of the range or bottom + * @return whether the stack was modified as a result of being quick-moved + */ + @Override + protected boolean moveItemStackTo(ItemStack itemStack, int rangeLow, int rangeHigh, boolean topDown) { + boolean modified = false; + boolean stackable = itemStack.isStackable(); + Slot firstEmpty = null; + + for (int index = topDown ? rangeHigh - 1 : rangeLow; + !itemStack.isEmpty() && (topDown ? index >= rangeLow : index < rangeHigh); + index += topDown ? -1 : 1 + ) { + Slot slot = slots.get(index); + // If the slot cannot be added to, check the next slot. + if (slot.isFake() || !slot.mayPlace(itemStack)) { + continue; + } + + if (slot.hasItem()) { + // If the item isn't stackable, check the next slot. + if (!stackable) { + continue; + } + // Otherwise, add as many as we can from our stack to the slot. + modified |= addToExistingStack(itemStack, slot); + } else { + // If this is the first empty slot, keep track of it for later use. + if (firstEmpty == null) { + firstEmpty = slot; + } + // If the item isn't stackable, we've located the slot we're adding it to, so we're done. + if (!stackable) { + break; + } + } + } + + // If the item hasn't been fully added yet, add as many as we can to the first open slot. + if (!itemStack.isEmpty() && firstEmpty != null) { + firstEmpty.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), firstEmpty.getMaxStackSize(itemStack)))); + firstEmpty.setChanged(); + modified = true; + } + + return modified; + } + + private static boolean addToExistingStack(ItemStack itemStack, Slot slot) { + ItemStack existing = slot.getItem(); + + // If the items aren't the same, we can't add our item. + if (!ItemStack.isSameItemSameComponents(itemStack, existing)) { + return false; + } + + int max = slot.getMaxStackSize(existing); + int existingCount = existing.getCount(); + + // If the stack is already full, we can't add more. + if (existingCount >= max) { + return false; + } + + int total = existingCount + itemStack.getCount(); + + // If the existing item can accept the entirety of our item, we're done! + if (total <= max) { + itemStack.setCount(0); + existing.setCount(total); + slot.setChanged(); + return true; + } + + // Otherwise, add as many as we can. + itemStack.shrink(max - existingCount); + existing.setCount(max); + slot.setChanged(); + return true; + } + + @Override + public void clicked(int i, int j, @NotNull ContainerInput clickType, @NotNull Player player) { + if (viewOnly) { + if (clickType == ContainerInput.QUICK_CRAFT) { + sendAllDataToRemote(); + } + return; + } + super.clicked(i, j, clickType, player); + } + + @Override + public boolean stillValid(@NotNull Player player) { + return true; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenEnderChestMenu.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenEnderChestMenu.java new file mode 100644 index 00000000..6669982d --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenEnderChestMenu.java @@ -0,0 +1,54 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.menu; + +import com.github.jikoo.openinv.internal.spigot26_1.container.OpenEnderChest; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class OpenEnderChestMenu extends OpenSyncMenu { + + public OpenEnderChestMenu( + @NotNull OpenEnderChest enderChest, + @NotNull ServerPlayer viewer, + int containerId, + boolean viewOnly + ) { + super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly); + } + + @Override + public @NotNull ItemStack quickMoveStack(@NotNull Player player, int index) { + if (viewOnly) { + return ItemStack.EMPTY; + } + + // See ChestMenu + Slot slot = this.slots.get(index); + + if (slot.isFake() || !slot.hasItem()) { + return ItemStack.EMPTY; + } + + ItemStack itemStack = slot.getItem(); + ItemStack original = itemStack.copy(); + + if (index < topSize) { + if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) { + return ItemStack.EMPTY; + } + } else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) { + return ItemStack.EMPTY; + } + + if (itemStack.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + + return original; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenInventoryMenu.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenInventoryMenu.java new file mode 100644 index 00000000..b8e69195 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenInventoryMenu.java @@ -0,0 +1,265 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.menu; + +import com.google.common.base.Preconditions; +import com.lishid.openinv.util.Permissions; +import com.github.jikoo.openinv.internal.spigot26_1.container.BaseOpenInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.bukkit.OpenDummyPlayerInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.bukkit.OpenPlayerInventorySelf; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentDrop; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.ContentEquipment; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.SlotViewOnly; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class OpenInventoryMenu extends OpenSyncMenu { + + private int offset; + + public OpenInventoryMenu(BaseOpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) { + super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly); + } + + private static MenuType getMenuType(BaseOpenInventory inventory, ServerPlayer viewer) { + int size = inventory.getContainerSize(); + // Disallow duplicate access to own main inventory contents. + if (inventory.getOwnerHandle().equals(viewer)) { + size -= viewer.getInventory().getNonEquipmentItems().size(); + size = ((int) Math.ceil(size / 9.0)) * 9; + } + + return getChestMenuType(size); + } + + @Override + protected void preSlotSetup() { + offset = ownContainer ? viewer.getInventory().getNonEquipmentItems().size() : 0; + } + + @Override + protected @NotNull Slot getUpperSlot(int index, int x, int y) { + index += offset; + Slot slot = container.getMenuSlot(index, x, y); + + // If the slot cannot be interacted with there's nothing to configure. + if (slot.getClass().equals(SlotViewOnly.class)) { + return slot; + } + + // Remove drop slot if viewer is not allowed to use it. + if (slot instanceof ContentDrop.SlotDrop + && (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) { + return new SlotViewOnly(container, index, x, y); + } + + if (slot instanceof ContentEquipment.SlotEquipment equipment) { + if (viewOnly) { + return SlotViewOnly.wrap(slot); + } + + Permissions perm = switch (equipment.getEquipmentSlot()) { + case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY; + case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY; + case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY; + case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY; + // Off-hand can hold anything, not just equipment. + default -> null; + }; + + // If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot. + if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) { + equipment.onlyEquipmentFor(container.getOwnerHandle()); + } + + // Equipment slots are a core part of the inventory, so they will always be shown. + return slot; + } + + // When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above). + if (ownContainer && !(slot instanceof ContentDrop.SlotDrop)) { + return new SlotViewOnly(container, index, x, y); + } + + if (viewOnly) { + return SlotViewOnly.wrap(slot); + } + + return slot; + } + + @Override + protected @NotNull CraftInventoryView, Inventory> createBukkitEntity() { + Inventory bukkitInventory; + if (viewOnly) { + bukkitInventory = new OpenDummyPlayerInventory(container); + } else if (ownContainer) { + bukkitInventory = new OpenPlayerInventorySelf(container, offset); + } else { + bukkitInventory = container.getBukkitInventory(); + } + + return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) { + @Override + public org.bukkit.inventory.ItemStack getItem(int index) { + if (viewOnly || index < 0) { + return null; + } + + Slot slot = slots.get(index); + return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY); + } + + @Override + public boolean isInTop(int rawSlot) { + return rawSlot < topSize; + } + + @Override + public @Nullable Inventory getInventory(int rawSlot) { + if (viewOnly) { + return null; + } + if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) { + return null; + } + Preconditions.checkArgument( + rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE, + "Slot %s outside of inventory", + rawSlot + ); + if (rawSlot > topSize) { + return getBottomInventory(); + } + Slot slot = slots.get(rawSlot); + if (slot.isFake()) { + return null; + } + return getTopInventory(); + } + + @Override + public int convertSlot(int rawSlot) { + if (viewOnly) { + return InventoryView.OUTSIDE; + } + if (rawSlot < 0) { + return rawSlot; + } + if (rawSlot < topSize) { + Slot slot = slots.get(rawSlot); + if (slot.isFake()) { + return InventoryView.OUTSIDE; + } + return rawSlot; + } + + int slot = rawSlot - topSize; + + if (slot >= 27) { + slot -= 27; + } else { + slot += 9; + } + + return slot; + } + + @Override + public @NotNull InventoryType.SlotType getSlotType(int slot) { + if (viewOnly || slot < 0) { + return InventoryType.SlotType.OUTSIDE; + } + if (slot >= topSize) { + slot -= topSize; + if (slot >= 27) { + return InventoryType.SlotType.QUICKBAR; + } + return InventoryType.SlotType.CONTAINER; + } + return OpenInventoryMenu.this.container.getSlotType(offset + slot); + } + + @Override + public int countSlots() { + return topSize + BOTTOM_INVENTORY_SIZE; + } + }; + } + + @Override + public @NotNull ItemStack quickMoveStack(@NotNull Player player, int index) { + if (viewOnly) { + return ItemStack.EMPTY; + } + + // See ChestMenu and InventoryMenu + Slot slot = this.slots.get(index); + + if (!slot.hasItem() || slot.isFake()) { + return ItemStack.EMPTY; + } + + ItemStack itemStack = slot.getItem(); + ItemStack originalStack = itemStack.copy(); + + if (index < topSize) { + // If we're moving top to bottom, do a normal transfer. + if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) { + return ItemStack.EMPTY; + } + } else { + EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack); + boolean movedGear = switch (equipmentSlot) { + // If this is gear, try to move it to the correct slot first. + case OFFHAND, FEET, LEGS, CHEST, HEAD -> { + // Locate the correct slot in the contents following the main inventory. + for (int extra = container.getOwnerHandle().getInventory().getNonEquipmentItems().size() - offset; extra < topSize; ++extra) { + Slot extraSlot = getSlot(extra); + if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot + && equipSlot.getEquipmentSlot() == equipmentSlot) { + // If we've found a matching slot, try to move to it. + // If this succeeds, even partially, we will not attempt to move to other slots. + // Otherwise, armor is already occupied, so we'll fall through to main inventory. + yield this.moveItemStackTo(itemStack, extra, extra + 1, false); + } + } + yield false; + } + // Non-gear gets no special treatment. + default -> false; + }; + + // If main inventory is not available, there's nowhere else to move. + if (offset != 0) { + if (!movedGear) { + return ItemStack.EMPTY; + } + } else { + // If we didn't move to a gear slot, try to move to a main inventory slot. + if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().getNonEquipmentItems().size(), true)) { + return ItemStack.EMPTY; + } + } + } + + if (itemStack.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + + return originalStack; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenSyncMenu.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenSyncMenu.java new file mode 100644 index 00000000..43186c1e --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/menu/OpenSyncMenu.java @@ -0,0 +1,237 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.menu; + +import com.google.common.base.Suppliers; +import com.lishid.openinv.internal.ISpecialInventory; +import com.lishid.openinv.internal.InternalOwned; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.SlotPlaceholder; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.network.HashedStack; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.ContainerListener; +import net.minecraft.world.inventory.ContainerSynchronizer; +import net.minecraft.world.inventory.DataSlot; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.RemoteSlot; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * An extension of {@link OpenChestMenu} that supports {@link SlotPlaceholder placeholders}. + */ +@SuppressWarnings("HidingField") // Revisit when removing 1.21.4 support +public abstract class OpenSyncMenu> + extends OpenChestMenu { + + // Syncher fields + protected @Nullable ContainerSynchronizer synchronizer; + protected final List dataSlots = new ArrayList<>(); + protected final IntList remoteDataSlots = new IntArrayList(); + protected final List containerListeners = new ArrayList<>(); + private RemoteSlot remoteCarried = RemoteSlot.PLACEHOLDER; + protected boolean suppressRemoteUpdates; + + protected OpenSyncMenu( + @NotNull MenuType type, + int containerCounter, + @NotNull T container, + @NotNull ServerPlayer viewer, + boolean viewOnly + ) { + super(type, containerCounter, container, viewer, viewOnly); + } + + // Overrides from here on are purely to modify the sync process to send placeholder items. + @Override + protected @NotNull Slot addSlot(@NotNull Slot slot) { + slot.index = this.slots.size(); + this.slots.add(slot); + this.lastSlots.add(ItemStack.EMPTY); + this.remoteSlots.add(this.synchronizer != null ? this.synchronizer.createSlot() : RemoteSlot.PLACEHOLDER); + return slot; + } + + @Override + protected @NotNull DataSlot addDataSlot(@NotNull DataSlot dataSlot) { + this.dataSlots.add(dataSlot); + this.remoteDataSlots.add(0); + return dataSlot; + } + + @Override + protected void addDataSlots(ContainerData containerData) { + for (int i = 0; i < containerData.getCount(); i++) { + this.addDataSlot(DataSlot.forContainer(containerData, i)); + } + } + + @Override + public void addSlotListener(@NotNull ContainerListener containerListener) { + if (!this.containerListeners.contains(containerListener)) { + this.containerListeners.add(containerListener); + this.broadcastChanges(); + } + } + + @Override + public void setSynchronizer(@NotNull ContainerSynchronizer containerSynchronizer) { + this.synchronizer = containerSynchronizer; + this.remoteCarried = synchronizer.createSlot(); + this.remoteSlots.replaceAll(slot -> synchronizer.createSlot()); + this.sendAllDataToRemote(); + } + + @Override + public void sendAllDataToRemote() { + List contentsCopy = new ArrayList<>(); + for (int index = 0; index < slots.size(); ++index) { + Slot slot = slots.get(index); + ItemStack itemStack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem(); + contentsCopy.add(itemStack); + this.remoteSlots.get(index).force(itemStack); + } + + remoteCarried.force(getCarried()); + + for (int index = 0; index < this.dataSlots.size(); ++index) { + this.remoteDataSlots.set(index, this.dataSlots.get(index).get()); + } + + if (this.synchronizer != null) { + this.synchronizer.sendInitialData(this, contentsCopy, this.getCarried().copy(), this.remoteDataSlots.toIntArray()); + } + } + + @Override + public void broadcastCarriedItem() { + ItemStack carried = this.getCarried(); + this.remoteCarried.force(carried); + if (this.synchronizer != null) { + this.synchronizer.sendCarriedChange(this, carried.copy()); + } + } + + @Override + public void removeSlotListener(@NotNull ContainerListener containerListener) { + this.containerListeners.remove(containerListener); + } + + @Override + public void broadcastChanges() { + for (int index = 0; index < this.slots.size(); ++index) { + Slot slot = this.slots.get(index); + ItemStack itemstack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem(); + Supplier supplier = Suppliers.memoize(itemstack::copy); + this.triggerSlotListeners(index, itemstack, supplier); + this.synchronizeSlotToRemote(index, itemstack, supplier); + } + + this.synchronizeCarriedToRemote(); + + for (int index = 0; index < this.dataSlots.size(); ++index) { + DataSlot dataSlot = this.dataSlots.get(index); + int j = dataSlot.get(); + if (dataSlot.checkAndClearUpdateFlag()) { + this.updateDataSlotListeners(index, j); + } + + this.synchronizeDataSlotToRemote(index, j); + } + } + + @Override + public void broadcastFullState() { + for (int index = 0; index < this.slots.size(); ++index) { + ItemStack itemstack = this.slots.get(index).getItem(); + this.triggerSlotListeners(index, itemstack, itemstack::copy); + } + + for (int index = 0; index < this.dataSlots.size(); ++index) { + DataSlot containerproperty = this.dataSlots.get(index); + if (containerproperty.checkAndClearUpdateFlag()) { + this.updateDataSlotListeners(index, containerproperty.get()); + } + } + + this.sendAllDataToRemote(); + } + + private void updateDataSlotListeners(int i, int j) { + for (ContainerListener containerListener : this.containerListeners) { + containerListener.dataChanged(this, i, j); + } + } + + private void triggerSlotListeners(int index, @NotNull ItemStack itemStack, @NotNull Supplier supplier) { + ItemStack itemStack1 = this.lastSlots.get(index); + if (!ItemStack.matches(itemStack1, itemStack)) { + ItemStack itemStack2 = supplier.get(); + this.lastSlots.set(index, itemStack2); + + for (ContainerListener containerListener : this.containerListeners) { + containerListener.slotChanged(this, index, itemStack2); + } + } + } + + private void synchronizeSlotToRemote(int i, @NotNull ItemStack itemStack, @NotNull Supplier supplier) { + if (!this.suppressRemoteUpdates) { + RemoteSlot slot = this.remoteSlots.get(i); + if (!slot.matches(itemStack)) { + slot.force(itemStack); + if (this.synchronizer != null) { + this.synchronizer.sendSlotChange(this, i, supplier.get()); + } + } + } + } + + private void synchronizeDataSlotToRemote(int index, int value) { + if (!this.suppressRemoteUpdates) { + int existing = this.remoteDataSlots.getInt(index); + if (existing != value) { + this.remoteDataSlots.set(index, value); + if (this.synchronizer != null) { + this.synchronizer.sendDataChange(this, index, value); + } + } + } + } + + private void synchronizeCarriedToRemote() { + if (!this.suppressRemoteUpdates) { + ItemStack carried = this.getCarried(); + if (!this.remoteCarried.matches(carried)) { + this.remoteCarried.force(carried); + if (this.synchronizer != null) { + this.synchronizer.sendCarriedChange(this, carried.copy()); + } + } + } + } + + @Override + public void setRemoteCarried(@NotNull HashedStack stack) { + this.remoteCarried.receive(stack); + } + + @Override + public void suppressRemoteUpdates() { + this.suppressRemoteUpdates = true; + } + + @Override + public void resumeRemoteUpdates() { + this.suppressRemoteUpdates = false; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/Content.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/Content.java new file mode 100644 index 00000000..6be9ca5d --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/Content.java @@ -0,0 +1,69 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * An interface defining behaviors for entries in a {@link Container}. Used to reduce duplicate content reordering. + */ +public interface Content { + + /** + * Update internal holder. + * + * @param holder the new holder + */ + void setHolder(@NotNull ServerPlayer holder); + + /** + * Get the current item. + * + * @return the current item + */ + ItemStack get(); + + /** + * Remove the current item. + * + * @return the current item + */ + ItemStack remove(); + + /** + * Remove some of the current item. + * + * @return the current item + */ + ItemStack removePartial(int amount); + + /** + * Set the current item. If slot is currently not usable, will drop item instead. + * + * @param itemStack the item to set + */ + void set(ItemStack itemStack); + + /** + * Get a {@link Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will + * impose any specific restrictions to insertion or removal. + * + * @param container the backing container + * @param slot the slot of the backing container represented + * @param x clientside x dimension from top left of inventory, not used + * @param y clientside y dimension from top left of inventory, not used + * @return a menu slot + */ + Slot asSlot(Container container, int slot, int x, int y); + + /** + * Get a loose Bukkit translation of what this slot stores. For example, any slot that drops items at the owner rather + * than insert them will report itself as being {@link org.bukkit.event.inventory.InventoryType.SlotType#OUTSIDE}. + * + * @return the closes Bukkit slot type + */ + org.bukkit.event.inventory.InventoryType.SlotType getSlotType(); + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCrafting.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCrafting.java new file mode 100644 index 00000000..a2c76f27 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCrafting.java @@ -0,0 +1,132 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; +import com.github.jikoo.openinv.internal.spigot26_1.player.OpenPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * A slot in a survival crafting inventory. Unavailable when not online in a survival mode. + */ +public class ContentCrafting implements Content { + + private final int index; + private ServerPlayer holder; + private List items; + + public ContentCrafting(@NotNull ServerPlayer holder, int index) { + setHolder(holder); + this.index = index; + } + + private boolean isAvailable() { + return isAvailable(holder); + } + + public static boolean isAvailable(@NotNull ServerPlayer holder) { + // Player must be online and not in creative - since the creative client is (semi-)authoritative, + // it ignores changes without extra help, and will delete the item as a result. + // Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory. + return OpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival(); + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + // Note: CraftingContainer#getItems is immutable! Be careful with updates. + this.items = holder.inventoryMenu.getCraftSlots().getContents(); + } + + @Override + public ItemStack get() { + return isAvailable() ? items.get(index) : ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + if (!this.isAvailable()) { + return ItemStack.EMPTY; + } + ItemStack removed = items.remove(index); + if (removed.isEmpty()) { + return ItemStack.EMPTY; + } + holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots()); + return removed; + } + + @Override + public ItemStack removePartial(int amount) { + if (!this.isAvailable()) { + return ItemStack.EMPTY; + } + ItemStack removed = ContainerHelper.removeItem(items, index, amount); + if (removed.isEmpty()) { + return ItemStack.EMPTY; + } + holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots()); + return removed; + } + + @Override + public void set(ItemStack itemStack) { + if (isAvailable()) { + items.set(index, itemStack); + holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots()); + } else { + holder.drop(itemStack, false); + } + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotCrafting(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + return isAvailable() ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE; + } + + public class SlotCrafting extends SlotPlaceholder { + + private SlotCrafting(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + return isAvailable() ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder); + } + + @Override + public boolean mayPickup(@NotNull Player player) { + return isAvailable(); + } + + @Override + public boolean mayPlace(@NotNull ItemStack itemStack) { + return isAvailable(); + } + + @Override + public boolean hasItem() { + return isAvailable() && super.hasItem(); + } + + @Override + public boolean isFake() { + return !isAvailable(); + } + + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCraftingResult.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCraftingResult.java new file mode 100644 index 00000000..886a8d1d --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCraftingResult.java @@ -0,0 +1,48 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A slot allowing viewing of the crafting result. + * + *

Unmodifiable because I said so. Use your own crafting grid.

+ */ +public class ContentCraftingResult extends ContentViewOnly { + + public ContentCraftingResult(@NotNull ServerPlayer holder) { + super(holder); + } + + @Override + public ItemStack get() { + InventoryMenu inventoryMenu = holder.inventoryMenu; + return inventoryMenu.getResultSlot().getItem(); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotViewOnly(container, slot, x, y) { + @Override + public ItemStack getOrDefault() { + if (!ContentCrafting.isAvailable(holder)) { + return Placeholders.survivalOnly(holder); + } + InventoryMenu inventoryMenu = holder.inventoryMenu; + return inventoryMenu.getResultSlot().getItem(); + } + }; + } + + @Override + public InventoryType.SlotType getSlotType() { + return InventoryType.SlotType.RESULT; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCursor.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCursor.java new file mode 100644 index 00000000..0a35c588 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentCursor.java @@ -0,0 +1,118 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; +import com.github.jikoo.openinv.internal.spigot26_1.player.OpenPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A slot wrapping the active menu's cursor. Unavailable when not online in a survival mode. + */ +public class ContentCursor implements Content { + + private @NotNull ServerPlayer holder; + + public ContentCursor(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public ItemStack get() { + return isAvailable() ? holder.containerMenu.getCarried() : ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + ItemStack carried = holder.containerMenu.getCarried(); + holder.containerMenu.setCarried(ItemStack.EMPTY); + return carried; + } + + @Override + public ItemStack removePartial(int amount) { + ItemStack carried = holder.containerMenu.getCarried(); + if (!carried.isEmpty() && carried.getCount() >= amount) { + ItemStack value = carried.split(amount); + if (carried.isEmpty()) { + holder.containerMenu.setCarried(ItemStack.EMPTY); + } + return value; + } + return ItemStack.EMPTY; + } + + @Override + public void set(ItemStack itemStack) { + if (isAvailable()) { + holder.containerMenu.setCarried(itemStack); + } else { + holder.drop(itemStack, false); + } + } + + private boolean isAvailable() { + // Player must be online and not in creative - since the creative client is (semi-)authoritative, + // it ignores changes without extra help, and will delete the item as a result. + // Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory. + return OpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival(); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotCursor(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + // As close as possible to "not real" + return InventoryType.SlotType.OUTSIDE; + } + + public class SlotCursor extends SlotPlaceholder { + + private SlotCursor(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + if (!isAvailable()) { + return Placeholders.survivalOnly(holder); + } + ItemStack carried = holder.containerMenu.getCarried(); + return carried.isEmpty() ? Placeholders.cursor : carried; + } + + @Override + public boolean mayPickup(@NotNull Player player) { + return isAvailable(); + } + + @Override + public boolean mayPlace(@NotNull ItemStack itemStack) { + return isAvailable(); + } + + @Override + public boolean hasItem() { + return isAvailable() && super.hasItem(); + } + + @Override + public boolean isFake() { + return true; + } + + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentDrop.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentDrop.java new file mode 100644 index 00000000..06c102bd --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentDrop.java @@ -0,0 +1,89 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; +import com.github.jikoo.openinv.internal.spigot26_1.player.OpenPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A fake slot used to drop items. Unavailable offline. + */ +public class ContentDrop implements Content { + + private ServerPlayer holder; + + public ContentDrop(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public ItemStack get() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack removePartial(int amount) { + return ItemStack.EMPTY; + } + + @Override + public void set(ItemStack itemStack) { + holder.drop(itemStack, true); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotDrop(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + // Behaves like dropping an item outside the screen, just by the target player. + return InventoryType.SlotType.OUTSIDE; + } + + public class SlotDrop extends SlotPlaceholder { + + private SlotDrop(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + return OpenPlayer.isConnected(holder.connection) + ? Placeholders.drop + : Placeholders.blockedOffline; + } + + @Override + public boolean mayPlace(@NotNull ItemStack itemStack) { + return OpenPlayer.isConnected(holder.connection); + } + + @Override + public boolean hasItem() { + return false; + } + + @Override + public boolean isFake() { + return true; + } + + } + +} diff --git a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/slot/ContentEquipment.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentEquipment.java similarity index 91% rename from internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/slot/ContentEquipment.java rename to internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentEquipment.java index bfec218e..f55c9ce7 100644 --- a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/slot/ContentEquipment.java +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentEquipment.java @@ -1,13 +1,13 @@ -package com.lishid.openinv.internal.reobf.container.slot; +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; -import com.lishid.openinv.internal.reobf.container.slot.placeholder.Placeholders; +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.Container; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; -import org.bukkit.craftbukkit.v1_21_R7.CraftEquipmentSlot; -import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.CraftEquipmentSlot; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.PlayerInventory; import org.jetbrains.annotations.NotNull; diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentList.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentList.java new file mode 100644 index 00000000..97e1422a --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentList.java @@ -0,0 +1,58 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; + +import java.util.List; + +/** + * A normal slot backed by an item list. + */ +public abstract class ContentList implements Content { + + private final int index; + private final InventoryType.SlotType slotType; + protected List items; + + public ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) { + this.index = index; + this.slotType = slotType; + setHolder(holder); + } + + @Override + public ItemStack get() { + return items.get(index); + } + + @Override + public ItemStack remove() { + ItemStack removed = items.remove(index); + return removed == null || removed.isEmpty() ? ItemStack.EMPTY : removed; + } + + @Override + public ItemStack removePartial(int amount) { + return ContainerHelper.removeItem(items, index, amount); + } + + @Override + public void set(ItemStack itemStack) { + items.set(index, itemStack); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new Slot(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + return slotType; + } + +} diff --git a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/slot/ContentOffHand.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentOffHand.java similarity index 91% rename from internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/slot/ContentOffHand.java rename to internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentOffHand.java index 5052987a..9aebadd3 100644 --- a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/container/slot/ContentOffHand.java +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentOffHand.java @@ -1,6 +1,6 @@ -package com.lishid.openinv.internal.reobf.container.slot; +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; -import com.lishid.openinv.internal.reobf.player.OpenPlayer; +import com.github.jikoo.openinv.internal.spigot26_1.player.OpenPlayer; import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.Container; diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentViewOnly.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentViewOnly.java new file mode 100644 index 00000000..bbce19d5 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/ContentViewOnly.java @@ -0,0 +1,56 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * A view-only slot that can't be interacted with. + */ +public class ContentViewOnly implements Content { + + protected @NotNull ServerPlayer holder; + + public ContentViewOnly(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public void setHolder(@NotNull ServerPlayer holder) { + this.holder = holder; + } + + @Override + public ItemStack get() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack remove() { + return ItemStack.EMPTY; + } + + @Override + public ItemStack removePartial(int amount) { + return ItemStack.EMPTY; + } + + @Override + public void set(ItemStack itemStack) { + this.holder.drop(itemStack, false); + } + + @Override + public Slot asSlot(Container container, int slot, int x, int y) { + return new SlotViewOnly(container, slot, x, y); + } + + @Override + public InventoryType.SlotType getSlotType() { + return InventoryType.SlotType.OUTSIDE; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/SlotPlaceholder.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/SlotPlaceholder.java new file mode 100644 index 00000000..d1e793c7 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/SlotPlaceholder.java @@ -0,0 +1,20 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import net.minecraft.world.Container; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +/** + * An implementation of a slot as used by a menu that may have fake placeholder items. + * + *

Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories.

+ */ +public abstract class SlotPlaceholder extends Slot { + + public SlotPlaceholder(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + public abstract ItemStack getOrDefault(); + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/SlotViewOnly.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/SlotViewOnly.java new file mode 100644 index 00000000..6c24d6ce --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/SlotViewOnly.java @@ -0,0 +1,151 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot; + +import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.Placeholders; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content. + */ +public class SlotViewOnly extends SlotPlaceholder { + + public static @NotNull SlotViewOnly wrap(@NotNull Slot wrapped) { + SlotViewOnly wrapper; + if (wrapped instanceof SlotPlaceholder placeholder) { + wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) { + @Override + public ItemStack getOrDefault() { + return placeholder.getOrDefault(); + } + }; + } else { + wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) { + @Override + public ItemStack getOrDefault() { + return wrapped.getItem(); + } + }; + } + wrapper.index = wrapped.index; + return wrapper; + } + + public SlotViewOnly(Container container, int index, int x, int y) { + super(container, index, x, y); + } + + @Override + public ItemStack getOrDefault() { + return Placeholders.notSlot; + } + + @Override + public void onQuickCraft(@NotNull ItemStack itemStack1, @NotNull ItemStack itemStack2) { + } + + @Override + public void onTake(@NotNull Player player, @NotNull ItemStack itemStack) { + } + + @Override + public boolean mayPlace(@NotNull ItemStack itemStack) { + return false; + } + + @Override + public @NotNull ItemStack getItem() { + return ItemStack.EMPTY; + } + + @Override + public boolean hasItem() { + return false; + } + + @Override + public void setByPlayer(@NotNull ItemStack newStack) { + } + + @Override + public void setByPlayer(@NotNull ItemStack newStack, @NotNull ItemStack oldStack) { + } + + @Override + public void set(@NotNull ItemStack itemStack) { + } + + @Override + public void setChanged() { + } + + @Override + public int getMaxStackSize() { + return 0; + } + + @Override + public int getMaxStackSize(@NotNull ItemStack itemStack) { + return 0; + } + + @Override + public @NotNull ItemStack remove(int amount) { + return ItemStack.EMPTY; + } + + @Override + public boolean mayPickup(@NotNull Player player) { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public @NotNull Optional tryRemove(int var0, int var1, @NotNull Player player) { + return Optional.empty(); + } + + @Override + public @NotNull ItemStack safeTake(int var0, int var1, @NotNull Player player) { + return ItemStack.EMPTY; + } + + @Override + public @NotNull ItemStack safeInsert(@NotNull ItemStack itemStack) { + return itemStack; + } + + @Override + public @NotNull ItemStack safeInsert(@NotNull ItemStack itemStack, int amount) { + return itemStack; + } + + @Override + public boolean allowModification(@NotNull Player player) { + return false; + } + + @Override + public int getContainerSlot() { + return this.slot; + } + + @Override + public boolean isHighlightable() { + return false; + } + + @Override + public boolean isFake() { + return true; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/PlaceholderLoader.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/PlaceholderLoader.java new file mode 100644 index 00000000..c1925d78 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/PlaceholderLoader.java @@ -0,0 +1,40 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomModelData; +import net.minecraft.world.item.component.DyedItemColor; +import net.minecraft.world.item.component.TooltipDisplay; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedHashSet; +import java.util.List; + +public class PlaceholderLoader extends PlaceholderLoaderBase { + + private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(List.of(), List.of(), List.of("openinv:custom"), List.of()); + private static final TooltipDisplay HIDE_TOOLTIP = new TooltipDisplay(true, new LinkedHashSet<>()); + + @Override + protected @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception { + return TagParser.parseCompoundFully(itemText); + } + + @Override + protected void addModelData(@NotNull ItemStack itemStack) { + itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA); + } + + @Override + protected void hideTooltip(@NotNull ItemStack itemStack) { + itemStack.set(DataComponents.TOOLTIP_DISPLAY, HIDE_TOOLTIP); + } + + @Override + protected DyedItemColor getDye(int rgb) { + return new DyedItemColor(rgb); + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/PlaceholderLoaderBase.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/PlaceholderLoaderBase.java new file mode 100644 index 00000000..b16ec996 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/PlaceholderLoaderBase.java @@ -0,0 +1,180 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder; + +import com.mojang.serialization.DataResult; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.DyedItemColor; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.entity.BannerPattern; +import net.minecraft.world.level.block.entity.BannerPatternLayers; +import net.minecraft.world.level.block.entity.BannerPatterns; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.craftbukkit.CraftRegistry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class PlaceholderLoaderBase { + + public void load(@Nullable ConfigurationSection section) throws Exception { + Placeholders.craftingOutput = parse(section, "crafting-output", defaultCraftingOutput()); + Placeholders.cursor = parse(section, "cursor", defaultCursor()); + Placeholders.drop = parse(section, "drop", defaultDrop()); + Placeholders.emptyHelmet = parse(section, "empty-helmet", getEmptyArmor(Items.LEATHER_HELMET)); + Placeholders.emptyChestplate = parse(section, "empty-chestplate", getEmptyArmor(Items.LEATHER_CHESTPLATE)); + Placeholders.emptyLeggings = parse(section, "empty-leggings", getEmptyArmor(Items.LEATHER_LEGGINGS)); + Placeholders.emptyBoots = parse(section, "empty-boots", getEmptyArmor(Items.LEATHER_BOOTS)); + Placeholders.emptyOffHand = parse(section, "empty-off-hand", defaultShield()); + Placeholders.notSlot = parse(section, "not-a-slot", defaultNotSlot()); + Placeholders.blockedOffline = parse(section, "blocked.offline", defaultBlockedOffline()); + + for (GameType type : GameType.values()) { + // Barrier: "Not available - Creative" etc. + ItemStack typeItem = new ItemStack(Items.BARRIER); + typeItem.set( + DataComponents.ITEM_NAME, + Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName()) + ); + Placeholders.BLOCKED_GAME_TYPE.put(type, typeItem); + } + + Placeholders.BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", Placeholders.BLOCKED_GAME_TYPE.get(GameType.CREATIVE))); + Placeholders.BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", Placeholders.BLOCKED_GAME_TYPE.get(GameType.SPECTATOR))); + } + + protected @NotNull ItemStack parse( + @Nullable ConfigurationSection section, + @NotNull String path, + @NotNull ItemStack defaultStack + ) throws Exception { + if (section == null) { + return defaultStack; + } + + String itemText = section.getString(path); + + if (itemText == null) { + return defaultStack; + } + + CompoundTag compoundTag = parseTag(itemText); + DataResult parsed = ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), compoundTag); + ItemStack itemStack; + try { + itemStack = parsed.getOrThrow(); + } catch (Exception e) { + itemStack = null; + } + return itemStack == null ? defaultStack : itemStack; + } + + protected abstract @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception; + + protected abstract void addModelData(@NotNull ItemStack itemStack); + + protected abstract void hideTooltip(@NotNull ItemStack itemStack); + + protected abstract DyedItemColor getDye(int rgb); + + protected @NotNull ItemStack defaultCraftingOutput() { + // Crafting table: "Crafting" + ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE); + itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting")); + addModelData(itemStack); + return itemStack; + } + + protected @NotNull ItemStack defaultCursor() { + // Cursor-like banner with no tooltip + ItemStack itemStack = new ItemStack(Items.WHITE_BANNER); + RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry(); + Registry bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN); + BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT).value(); + BannerPattern downRight = bannerPatterns.getOrThrow(BannerPatterns.STRIPE_DOWNRIGHT).value(); + BannerPattern border = bannerPatterns.getOrThrow(BannerPatterns.BORDER).value(); + itemStack.set(DataComponents.BANNER_PATTERNS, + new BannerPatternLayers(List.of( + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY) + )) + ); + addModelData(itemStack); + hideTooltip(itemStack); + return itemStack; + } + + protected @NotNull ItemStack defaultDrop() { + // Dropper: "Drop Selected Item" + ItemStack itemStack = new ItemStack(Items.DROPPER); + // Note: translatable component, not keybind component! We want the text identifying the keybind, not the key. + itemStack.set(DataComponents.ITEM_NAME, Component.translatable("key.drop")); + addModelData(itemStack); + return itemStack; + } + + protected @NotNull ItemStack getEmptyArmor(@NotNull ItemLike item) { + // Inventory-background-grey-ish leather armor with no tooltip + ItemStack itemStack = new ItemStack(item); + DyedItemColor color = getDye(0xC8C8C8); + itemStack.set(DataComponents.DYED_COLOR, color); + hideTooltip(itemStack); + addModelData(itemStack); + return itemStack; + } + + protected @NotNull ItemStack defaultShield() { + // Shield with "missing texture" pattern, magenta and black squares. + ItemStack itemStack = new ItemStack(Items.SHIELD); + itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA); + RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry(); + Registry bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN); + BannerPattern halfLeft = bannerPatterns.getOrThrow(BannerPatterns.HALF_VERTICAL).value(); + BannerPattern topLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_LEFT).value(); + BannerPattern topRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_RIGHT).value(); + BannerPattern bottomLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_LEFT).value(); + BannerPattern bottomRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_RIGHT).value(); + itemStack.set(DataComponents.BANNER_PATTERNS, + new BannerPatternLayers(List.of( + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfLeft), DyeColor.BLACK), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK), + new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK) + )) + ); + hideTooltip(itemStack); + addModelData(itemStack); + return itemStack; + } + + protected @NotNull ItemStack defaultNotSlot() { + // White pane with no tooltip + ItemStack itemStack = new ItemStack(Items.WHITE_STAINED_GLASS_PANE); + hideTooltip(itemStack); + addModelData(itemStack); + return itemStack; + } + + protected @NotNull ItemStack defaultBlockedOffline() { + // Barrier: "Not available - Offline" + ItemStack itemStack = new ItemStack(Items.BARRIER); + itemStack.set(DataComponents.ITEM_NAME, + Component.translatable("options.narrator.notavailable") + .append(Component.literal(" - ")) + .append(Component.translatable("gui.socialInteractions.status_offline")) + ); + return itemStack; + } + +} diff --git a/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/Placeholders.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/Placeholders.java new file mode 100644 index 00000000..30308d92 --- /dev/null +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/container/slot/placeholder/Placeholders.java @@ -0,0 +1,37 @@ +package com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder; + +import com.github.jikoo.openinv.internal.spigot26_1.player.OpenPlayer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameType; +import org.jetbrains.annotations.NotNull; + +import java.util.EnumMap; + +public final class Placeholders { + + static final @NotNull EnumMap BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class); + public static @NotNull ItemStack craftingOutput = ItemStack.EMPTY; + public static @NotNull ItemStack cursor = ItemStack.EMPTY; + public static @NotNull ItemStack drop = ItemStack.EMPTY; + public static @NotNull ItemStack emptyHelmet = ItemStack.EMPTY; + public static @NotNull ItemStack emptyChestplate = ItemStack.EMPTY; + public static @NotNull ItemStack emptyLeggings = ItemStack.EMPTY; + public static @NotNull ItemStack emptyBoots = ItemStack.EMPTY; + public static @NotNull ItemStack emptyOffHand = ItemStack.EMPTY; + public static @NotNull ItemStack notSlot = ItemStack.EMPTY; + public static @NotNull ItemStack blockedOffline = ItemStack.EMPTY; + + public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) { + if (!OpenPlayer.isConnected(serverPlayer.connection)) { + return blockedOffline; + } + + return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY); + } + + private Placeholders() { + throw new IllegalStateException("Cannot create instance of utility class."); + } + +} diff --git a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/player/OpenPlayer.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/player/OpenPlayer.java similarity index 97% rename from internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/player/OpenPlayer.java rename to internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/player/OpenPlayer.java index 33c630ac..cae26f4a 100644 --- a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/player/OpenPlayer.java +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/player/OpenPlayer.java @@ -1,4 +1,4 @@ -package com.lishid.openinv.internal.reobf.player; +package com.github.jikoo.openinv.internal.spigot26_1.player; import com.lishid.openinv.event.OpenEvents; import com.mojang.logging.LogUtils; @@ -13,8 +13,8 @@ import net.minecraft.world.level.storage.PlayerDataStorage; import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.level.storage.ValueOutput; -import org.bukkit.craftbukkit.v1_21_R7.CraftServer; -import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/player/PlayerManager.java b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/player/PlayerManager.java similarity index 94% rename from internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/player/PlayerManager.java rename to internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/player/PlayerManager.java index d619f847..916ec14a 100644 --- a/internal/spigot/src/main/java/com/lishid/openinv/internal/reobf/player/PlayerManager.java +++ b/internal/spigot/src/main/java/com/github/jikoo/openinv/internal/spigot26_1/player/PlayerManager.java @@ -1,9 +1,9 @@ -package com.lishid.openinv.internal.reobf.player; +package com.github.jikoo.openinv.internal.spigot26_1.player; +import com.github.jikoo.openinv.internal.spigot26_1.container.OpenEnderChest; +import com.github.jikoo.openinv.internal.spigot26_1.container.OpenInventory; +import com.github.jikoo.openinv.internal.spigot26_1.container.menu.OpenChestMenu; import com.lishid.openinv.internal.ISpecialInventory; -import com.lishid.openinv.internal.reobf.container.OpenEnderChest; -import com.lishid.openinv.internal.reobf.container.OpenInventory; -import com.lishid.openinv.internal.reobf.container.menu.OpenChestMenu; import com.lishid.openinv.util.JulLoggerAdapter; import com.mojang.authlib.GameProfile; import net.minecraft.nbt.CompoundTag; @@ -24,9 +24,9 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; -import org.bukkit.craftbukkit.v1_21_R7.CraftServer; -import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_21_R7.event.CraftEventFactory; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.event.CraftEventFactory; import org.bukkit.entity.Player; import org.bukkit.inventory.InventoryView; import org.jetbrains.annotations.NotNull; diff --git a/jitpack.yml b/jitpack.yml index 4130c60e..db0815ca 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,6 +1,6 @@ before_install: - sdk update - - sdk install java 21-tem - - sdk use java 21-tem + - sdk install java 25-tem + - sdk use java 25-tem install: - ./gradlew -Djitpack=true :openinvapi:publishJitpackPublicationToMavenLocal diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 12c3faac..128f5abd 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -1,5 +1,3 @@ -import com.github.jikoo.openinv.SpigotReobf - plugins { `openinv-base` alias(libs.plugins.shadow) @@ -19,7 +17,9 @@ dependencies { implementation(project(":openinvadapterpaper1_21_4")) implementation(project(":openinvadapterpaper1_21_3")) implementation(project(":openinvadapterpaper1_21_1")) - implementation(project(":openinvadapterspigot", configuration = SpigotReobf.ARTIFACT_CONFIG)) + implementation(project(":openinvadapterspigot")) { + isTransitive = false + } implementation(libs.planarwrappers) implementation(libs.folia.scheduler.wrapper) compileOnly(libs.sqlite.jdbc) diff --git a/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java b/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java index f5d77139..b0828f3e 100644 --- a/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java +++ b/plugin/src/main/java/com/lishid/openinv/util/InternalAccessor.java @@ -64,6 +64,15 @@ public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { } private @Nullable Accessor getAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { + // TODO reorganize internals, version handling + if (!PAPER) { + if (BukkitVersions.MINECRAFT.equals(Version.of(26, 1))) { + // Load Spigot accessor. + return new com.github.jikoo.openinv.internal.spigot26_1.InternalAccessor(logger, lang); + } + return null; + } + Version maxSupported = Version.of(1, 21, 11); Version minSupported = Version.of(1, 21, 1); @@ -72,17 +81,6 @@ public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) { return null; } - // Load Spigot accessor. - if (!PAPER) { - if (BukkitVersions.MINECRAFT.equals(maxSupported)) { - // Current Spigot, remapped internals are available. - return new com.lishid.openinv.internal.reobf.InternalAccessor(logger, lang); - } else { - // Older Spigot; unsupported. - return null; - } - } - // Paper or a Paper fork, can use Mojang-mapped internals. if (BukkitVersions.MINECRAFT.equals(maxSupported)) { // 1.21.11 return new com.lishid.openinv.internal.common.InternalAccessor(logger, lang); @@ -212,6 +210,9 @@ private String getSpigotReleaseLink() { if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 10))) { return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.15"; } + if (BukkitVersions.MINECRAFT.lessThan(Version.of(26, 1))) { + return "https://github.com/Jikoo/OpenInv/releases/tag/5.3.0"; + } return "https://github.com/Jikoo/OpenInv/releases"; }