diff --git a/build.gradle b/build.gradle index 7fb0a08..cfa41a2 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,16 @@ import com.gtnewhorizons.retrofuturagradle.mcp.InjectTagsTask import org.jetbrains.changelog.Changelog import org.jetbrains.gradle.ext.Gradle +import com.gtnewhorizons.retrofuturagradle.MinecraftExtension +import com.gtnewhorizons.retrofuturagradle.mcp.MCPTasks +import com.gtnewhorizons.retrofuturagradle.minecraft.MinecraftTasks +import com.gtnewhorizons.retrofuturagradle.minecraft.RunMinecraftTask +import com.gtnewhorizons.retrofuturagradle.util.Distribution +import de.undercouch.gradle.tasks.download.DownloadExtension +import org.apache.commons.io.FileUtils + +import javax.inject.Inject + plugins { id 'java' id 'java-library' @@ -11,6 +21,7 @@ plugins { id 'com.matthewprenger.cursegradle' version '1.4.0' apply false id 'com.modrinth.minotaur' version '2.+' apply false id 'org.jetbrains.changelog' version '2.5.0' + id 'de.undercouch.download' version '5.6.0' apply false } apply from: 'gradle/scripts/helpers.gradle' @@ -63,6 +74,22 @@ java { configurations { embed + create("runtimeOnlyNonPublishable") { + description = "Runtime only dependencies that are not published alongside the jar" + canBeConsumed = false + canBeResolved = false + } + create("devOnlyNonPublishable") { + description = "Runtime and compiletime dependencies that are not published alongside the jar (compileOnly + runtimeOnlyNonPublishable)" + canBeConsumed = false + canBeResolved = false + } + + compileOnly.extendsFrom(devOnlyNonPublishable) + runtimeOnlyNonPublishable.extendsFrom(devOnlyNonPublishable) + runtimeClasspath.extendsFrom(runtimeOnlyNonPublishable) + testRuntimeClasspath.extendsFrom(runtimeOnlyNonPublishable) + implementation.extendsFrom(embed) } @@ -230,9 +257,29 @@ idea { "1. Run Client"(Gradle) { taskNames = ["runClient"] } + if (propertyBool('enable_java17_run_tasks')) { + '1a. Run Hotswap Client (Java 17)'(Gradle) { + taskNames = ['runClient17'] + envs=[HOTSWAP:"true"] + } + '1b. Run Hotswap Client (Java 21)'(Gradle) { + taskNames = ['runClient21'] + envs=[HOTSWAP:"true"] + } + } "2. Run Server"(Gradle) { taskNames = ["runServer"] } + if (propertyBool('enable_java17_run_tasks')) { + '2a. Run Hotswap Server (Java 17)'(Gradle) { + taskNames = ['runServer17'] + envs=[HOTSWAP:"true"] + } + '2b. Run Hotswap Server (Java 21)'(Gradle) { + taskNames = ['runServer21'] + envs=[HOTSWAP:"true"] + } + } "3. Run Obfuscated Client"(Gradle) { taskNames = ["runObfClient"] } @@ -354,3 +401,215 @@ idea.project.settings { apply from: 'gradle/scripts/publishing.gradle' apply from: 'gradle/scripts/extra.gradle' + + +if (propertyBool('enable_java17_run_tasks')) { + + apply plugin: 'de.undercouch.download' + + ext.java17Toolchain = (JavaToolchainSpec spec) -> { + spec.languageVersion.set(JavaLanguageVersion.of(17)) + spec.vendor.set(JvmVendorSpec.matching("jetbrains")) + } + ext.java21Toolchain = (JavaToolchainSpec spec) -> { + spec.languageVersion.set(JavaLanguageVersion.of(21)) + spec.vendor.set(JvmVendorSpec.matching("jetbrains")) + } + + ext.java17DependenciesCfg = configurations.create("java17Dependencies") { + extendsFrom(configurations.getByName("runtimeClasspath")) // Ensure consistent transitive dependency resolution + canBeConsumed = false + } + ext.java17PatchDependenciesCfg = configurations.create("java17PatchDependencies") { + canBeConsumed = false + } + + dependencies { + if (propertyString('mod_id') != 'lwjgl3ify') { + java17Dependencies("io.github.twilightflower:lwjgl3ify:1.0.1") + java17PatchDependencies("io.github.twilightflower:lwjgl3ify:1.0.1:forgePatches") { + transitive = false + } + } + } + + ext.java17JvmArgs = [ + "-Dfile.encoding=UTF-8", + "-Djava.system.class.loader=com.gtnewhorizons.retrofuturabootstrap.RfbSystemClassLoader", + "-Djava.security.manager=allow", + "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED", + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/java.nio=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens", "java.base/java.text=ALL-UNNAMED", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.reflect=ALL-UNNAMED", + "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED,java.naming", + "--add-opens", "java.desktop/sun.awt=ALL-UNNAMED", + "--add-opens", "java.desktop/sun.awt.image=ALL-UNNAMED", + "--add-opens", "java.desktop/com.sun.imageio.plugins.png=ALL-UNNAMED", + "--add-opens", "jdk.dynalink/jdk.dynalink.beans=ALL-UNNAMED", + "--add-opens", "java.sql.rowset/javax.sql.rowset.serial=ALL-UNNAMED" + ] + + ext.hotswapJvmArgs = [ + // DCEVM advanced hot reload + "-XX:+AllowEnhancedClassRedefinition", + "-XX:HotswapAgent=fatjar" + ] + + ext.setupHotswapAgent17 = tasks.register("setupHotswapAgent17", SetupHotswapAgentTask, t -> { + t.setTargetForToolchain(java17Toolchain) + }) + + ext.setupHotswapAgent21 = tasks.register("setupHotswapAgent21", SetupHotswapAgentTask, t -> { + t.setTargetForToolchain(java21Toolchain) + }) + + def runClient17Task = tasks.register("runClient17", RunHotswappableMinecraftTask, Distribution.CLIENT, "runClient") + runClient17Task.configure { + dependsOn(setupHotswapAgent17) + setup(project) + javaLauncher = project.javaToolchains.launcherFor(project.java17Toolchain) + } + + def runServer17Task = tasks.register("runServer17", RunHotswappableMinecraftTask, Distribution.DEDICATED_SERVER, "runServer") + runServer17Task.configure { + dependsOn(setupHotswapAgent17) + setup(project) + javaLauncher = project.javaToolchains.launcherFor(project.java17Toolchain) + } + + def runClient21Task = tasks.register("runClient21", RunHotswappableMinecraftTask, Distribution.CLIENT, "runClient") + runClient21Task.configure { + dependsOn(setupHotswapAgent21) + setup(project) + javaLauncher = project.javaToolchains.launcherFor(project.java21Toolchain) + } + + def runServer21Task = tasks.register("runServer21", RunHotswappableMinecraftTask, Distribution.DEDICATED_SERVER, "runServer") + runServer21Task.configure { + dependsOn(setupHotswapAgent21) + setup(project) + javaLauncher = project.javaToolchains.launcherFor(project.java21Toolchain) + } +} + +abstract class RunHotswappableMinecraftTask extends RunMinecraftTask { + + // IntelliJ doesn't seem to allow pre-set commandline arguments, so we also support an env variable + private boolean enableHotswap = Boolean.valueOf(System.getenv("HOTSWAP")) + + public final Distribution side + public final String superTask + + @Input + boolean getEnableHotswap() { + return enableHotswap + } + + @Option(option = "hotswap", description = "Enables HotSwapAgent for enhanced class reloading under a debugger") + boolean setEnableHotswap(boolean enable) { + enableHotswap = enable + } + + @Inject + RunHotswappableMinecraftTask(Distribution side, String superTask, org.gradle.api.invocation.Gradle gradle) { + super(side, gradle) + + this.side = side + this.superTask = superTask + setGroup("Modded Minecraft") + setDescription("Runs the modded " + side.name().toLowerCase(Locale.ROOT) + " using modern Java and lwjgl3ify") + this.getLwjglVersion().set(3) + } + + void setup(Project project) { + final MinecraftExtension minecraft = project.getExtensions().getByType(MinecraftExtension.class) + final MCPTasks mcpTasks = project.getExtensions().getByType(MCPTasks.class) + final MinecraftTasks mcTasks = project.getExtensions().getByType(MinecraftTasks.class) + + this.getExtraJvmArgs().addAll((List) project.property("java17JvmArgs")) + if (getEnableHotswap()) { + this.getExtraJvmArgs().addAll((List) project.property("hotswapJvmArgs")) + } + + this.classpath(project.property("java17PatchDependenciesCfg")) + this.classpath(mcpTasks.getTaskPackageMcLauncher()) + this.classpath(mcpTasks.getTaskPackagePatchedMc()) + this.classpath(mcpTasks.getPatchedConfiguration()) + this.classpath(project.getTasks().named("jar")) + this.classpath(project.property("java17DependenciesCfg")) + + super.setup(project) + + dependsOn( + mcpTasks.getLauncherSources().getClassesTaskName(), + mcTasks.getTaskDownloadVanillaAssets(), + mcpTasks.getTaskPackagePatchedMc(), + "jar" + ) + + getMainClass().set((side == Distribution.CLIENT) ? "GradleStart" : "GradleStartServer") + getUsername().set(minecraft.getUsername()) + getUserUUID().set(minecraft.getUserUUID()) + if (side == Distribution.DEDICATED_SERVER) { + getExtraArgs().add("nogui") + } + + systemProperty("gradlestart.bouncerClient", "com.gtnewhorizons.retrofuturabootstrap.Main") + systemProperty("gradlestart.bouncerServer", "com.gtnewhorizons.retrofuturabootstrap.Main") + + if (project.use_mixins.toBoolean()) { + this.extraJvmArgs.addAll(project.provider(() -> { + def mixinCfg = project.configurations.detachedConfiguration(project.dependencies.create(project.mixinProviderSpec)) + mixinCfg.canBeConsumed = false + mixinCfg.canBeResolved = true + mixinCfg.transitive = false + enableHotswap ? ["-javaagent:" + mixinCfg.singleFile.absolutePath] : [] + })) + } + } +} + +abstract class SetupHotswapAgentTask extends DefaultTask { + + @OutputFile + abstract RegularFileProperty getTargetFile() + + void setTargetForToolchain(Action spec) { + getTargetFile().set(project.javaToolchains.launcherFor(spec).map { + it.metadata.installationPath.file("lib/hotswap/hotswap-agent.jar") + }) + } + + @Inject + SetupHotswapAgentTask() { + setGroup("GT Buildscript") + setDescription("Installs a recent version of HotSwapAgent into the Java runtime directory") + onlyIf("Run only if not already installed", t -> !((SetupHotswapAgentTask) t).getTargetFile().getAsFile().get().exists()) + } + + @TaskAction + void installHSA() { + final String url = 'https://github.com/HotswapProjects/HotswapAgent/releases/download/RELEASE-2.0.3/hotswap-agent-2.0.3.jar' + final File target = getTargetFile().getAsFile().get() + final File parent = target.getParentFile() + FileUtils.forceMkdir(parent) + final DownloadExtension download = getProject().getExtensions().findByType(DownloadExtension.class) + download.run(ds -> { + try { + ds.src(url) + } catch (MalformedURLException e) { + throw new RuntimeException(e) + } + ds.dest(target) + ds.overwrite(false) + ds.tempAndMove(true) + }) + } +} + diff --git a/gradle.properties b/gradle.properties index c06d269..47f4254 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,6 +5,7 @@ org.gradle.caching = true # Source Options use_modern_java_syntax = false +enable_java17_run_tasks = true # Compilation Options generate_sources_jar = true diff --git a/gradle/scripts/dependencies.gradle b/gradle/scripts/dependencies.gradle index a054957..41da57b 100644 --- a/gradle/scripts/dependencies.gradle +++ b/gradle/scripts/dependencies.gradle @@ -33,9 +33,26 @@ repositories { metadataSources { artifact() } } + maven { + name = 'BlameJared Maven' + url = 'https://maven.blamejared.com' + } + maven { + name = 'GTCEu Maven' + url = 'https://maven.gtceu.com' + } + mavenLocal() // Must be last for caching to work } - +/** + * NOT DEPENDENCIES: + * - runtimeOnly: not required at all for the mod, added for developer convenience to run targets + * OPTIONAL DEPENDENCIES: + * - compileOnly: will not be included in developer run targets + * - devOnlyNonPublishable: will be included in developer run targets + * REQUIRED DEPENDENCIES: + * - implementation: mods or other jars that are required to be installed alongside this mod + */ dependencies { // Include StripLatestForgeRequirements by default for the dev env runtimeOnly 'com.cleanroommc:strip-latest-forge-requirements:1.0' @@ -45,28 +62,28 @@ dependencies { // ---- Required dependencies ---- // AE2 Extended Life (AE2-UEL) - implementation rfg.deobf("curse.maven:ae2-extended-life-570458:5378163") + implementation rfg.deobf("curse.maven:ae2-extended-life-570458:6302098") // Baubles (required by AE2) implementation rfg.deobf("curse.maven:baubles-227083:2518667") // ---- Optional: JEI ---- - implementation rfg.deobf("curse.maven:jei-238222:4538010") + devOnlyNonPublishable 'mezz.jei:jei_1.12.2:4.16.1.302' // ---- Optional: AE2WUT (Wireless Universal Terminal) ---- - implementation rfg.deobf("curse.maven:ae2uel-wireless-universal-terminal-1196988:7366371") + devOnlyNonPublishable rfg.deobf("curse.maven:ae2uel-wireless-universal-terminal-1196988:7366371") // ---- Optional: Thaumic Energistics ---- - implementation rfg.deobf("curse.maven:thaumcraft-223628:2629023") - implementation rfg.deobf("curse.maven:thaumic-energistics-223666:2915506") + devOnlyNonPublishable rfg.deobf("curse.maven:thaumcraft-223628:2629023") + devOnlyNonPublishable rfg.deobf("curse.maven:thaumic-energistics-223666:2915506") // ---- Optional: CrazyAE ---- - implementation rfg.deobf("curse.maven:crazyae-1111042:6840104") + devOnlyNonPublishable rfg.deobf("curse.maven:crazyae-1111042:6840104") // ---- Optional: ECO AE Extension ---- - implementation "curse.maven:modularmachinery-community-edition-817377:7372951" - implementation "curse.maven:geckolib-388172:4020277" - implementation ':Nova-Engineering-ECO-AE-Extension-1.2.0@jar' + compileOnly "curse.maven:modularmachinery-community-edition-817377:7372951" + compileOnly "curse.maven:geckolib-388172:4020277" + compileOnly ':Nova-Engineering-ECO-AE-Extension-1.2.0@jar' // ---- Test dependencies ---- // JUnit 4 via the Vintage engine on JUnit Platform diff --git a/settings.gradle b/settings.gradle index e05ec3b..57960bb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,15 @@ pluginManagement { } } +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath group: 'commons-io', name: 'commons-io', version: '2.21.0' + } +} + plugins { // Automatic toolchain provisioning id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' @@ -22,3 +31,5 @@ plugins { // Different repo name from mod name rootProject.name = 'cell-terminal' + + diff --git a/src/main/java/com/cellterminal/container/ContainerCellTerminalBase.java b/src/main/java/com/cellterminal/container/ContainerCellTerminalBase.java index 51e5c24..b567956 100644 --- a/src/main/java/com/cellterminal/container/ContainerCellTerminalBase.java +++ b/src/main/java/com/cellterminal/container/ContainerCellTerminalBase.java @@ -7,7 +7,13 @@ import java.util.List; import java.util.Map; + +import appeng.container.slot.SlotRestrictedInput; +import appeng.helpers.WirelessTerminalGuiObject; +import appeng.items.contents.NetworkToolViewer; +import appeng.items.tools.ToolNetworkTool; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.entity.player.EntityPlayerMP; @@ -90,8 +96,76 @@ public abstract class ContainerCellTerminalBase extends AEBaseContainer { protected long currentNetworkId = 0; protected IGrid currentNetworkGrid = null; // Cached grid for current network (main or subnet) + // AE2's code mostly refers to the network tool as the "toolbox" so we use that name + // to distinguish it from our network tools + private int toolboxSlot; + private NetworkToolViewer toolboxInventory; + public ContainerCellTerminalBase(InventoryPlayer ip, IPart part) { super(ip, null, part); + + this.setupToolbox(); + } + + public ContainerCellTerminalBase( + InventoryPlayer ip, + WirelessTerminalGuiObject guiObject + ) { + + super(ip, guiObject); + + if (Platform.isServer()) { + IGridNode node = guiObject.getActionableNode(); + if (node != null && node.isActive()) { + this.grid = node.getGrid(); + } + } + + this.setupToolbox(); + } + + public void setupToolbox() { + + final IInventory pi = this.getPlayerInv(); + ItemStack toolbox = null; + for (int x = 0; x < pi.getSizeInventory(); x++) { + final ItemStack pii = pi.getStackInSlot(x); + if (!pii.isEmpty() && pii.getItem() instanceof ToolNetworkTool) { + this.lockPlayerInventorySlot(x); + this.toolboxSlot = x; + toolbox = pii; + break; + } + } + + if (toolbox != null) { + IGridNode node = this.getActionHost().getActionableNode(); + this.toolboxInventory = new NetworkToolViewer( + toolbox, + node != null ? node.getMachine() : null + ); + + + for (int v = 0; v < 3; v++) { + for (int u = 0; u < 3; u++) { + this.addSlotToContainer( + new SlotRestrictedInput( + SlotRestrictedInput.PlacableItemType.UPGRADES, + this.toolboxInventory.getInternalInventory(), + u + v * 3, + 8 + 9 * 18 + 14 + 18 + 1 + u * 18, + v * 18, + this.getInventoryPlayer() + ).setPlayerSide() + ); + } + } + } + } + + public boolean hasToolbox() { + + return this.toolboxInventory != null; } @Override @@ -124,6 +198,32 @@ public void detectAndSendChanges() { this.pendingData = new NBTTagCompound(); } + + this.checkToolbox(); + } + + public void checkToolbox() { + + if (hasToolbox()) { + final ItemStack currentItem = this.getPlayerInv().getStackInSlot(this.toolboxSlot); + + if (currentItem != this.toolboxInventory.getItemStack()) { + if (!currentItem.isEmpty()) { + if (ItemStack.areItemsEqual(this.toolboxInventory.getItemStack(), currentItem)) { + this + .getPlayerInv() + .setInventorySlotContents( + this.toolboxSlot, + this.toolboxInventory.getItemStack() + ); + } else { + this.setValidContainer(false); + } + } else { + this.setValidContainer(false); + } + } + } } /** diff --git a/src/main/java/com/cellterminal/container/ContainerWUTCellTerminal.java b/src/main/java/com/cellterminal/container/ContainerWUTCellTerminal.java deleted file mode 100644 index 4f975a4..0000000 --- a/src/main/java/com/cellterminal/container/ContainerWUTCellTerminal.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.cellterminal.container; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.InventoryPlayer; -import net.minecraft.item.ItemStack; -import net.minecraftforge.fml.common.Optional; - -import appeng.api.AEApi; -import appeng.api.features.ILocatable; -import appeng.api.features.IWirelessTermHandler; -import appeng.api.networking.IGridNode; -import appeng.api.networking.security.IActionHost; -import appeng.core.AEConfig; -import appeng.core.localization.PlayerMessages; -import appeng.helpers.WirelessTerminalGuiObject; - -import appeng.util.Platform; - -import baubles.api.BaublesApi; - -import com.cellterminal.CellTerminal; -import com.circulation.ae2wut.item.ItemWirelessUniversalTerminal; - - -/** - * Container for Cell Terminal when opened via AE2WUT's Wireless Universal Terminal. - * Uses WirelessTerminalGuiObject instead of direct slot access. - */ -@Optional.Interface(iface = "appeng.helpers.WirelessTerminalGuiObject", modid = "ae2wut") -public class ContainerWUTCellTerminal extends ContainerCellTerminalBase { - - private final WirelessTerminalGuiObject wirelessTerminalGuiObject; - private final int slot; - private final boolean isBauble; - - private int ticks = 0; - - public ContainerWUTCellTerminal(InventoryPlayer ip, WirelessTerminalGuiObject wth) { - super(ip, null); - - this.wirelessTerminalGuiObject = wth; - this.slot = wth.getInventorySlot(); - this.isBauble = wth.isBaubleSlot(); - - // Get grid by looking up the encryption key (same pattern as ContainerWirelessCellTerminal) - // Only do this server-side where the grid/locatable registry is available - ItemStack terminalStack = getTerminalStack(); - - if (Platform.isServer() && !terminalStack.isEmpty()) { - IWirelessTermHandler handler = AEApi.instance().registries().wireless().getWirelessTerminalHandler(terminalStack); - if (handler != null) { - String encKey = handler.getEncryptionKey(terminalStack); - - try { - long parsedKey = Long.parseLong(encKey); - ILocatable obj = AEApi.instance().registries().locatable().getLocatableBy(parsedKey); - - if (obj instanceof IActionHost) { - IGridNode n = ((IActionHost) obj).getActionableNode(); - if (n != null) this.grid = n.getGrid(); - } - } catch (NumberFormatException e) { - CellTerminal.LOGGER.error("ContainerWUTCellTerminal - invalid encKey: {}", encKey); - } - } - } - - // Lock the slot if it's in the main inventory - if (!isBauble && slot >= 0 && slot < ip.mainInventory.size()) this.lockPlayerInventorySlot(slot); - - this.bindPlayerInventory(ip, 0, 0); - } - - /** - * Get the WirelessTerminalGuiObject for this container. - * Used by the GUI to access WUT functionality. - */ - public WirelessTerminalGuiObject getWirelessTerminalGuiObject() { - return this.wirelessTerminalGuiObject; - } - - private ItemStack getTerminalStack() { - if (isBauble) return getBaubleStack(getPlayerInv().player, slot); - - return getPlayerInv().getStackInSlot(slot); - } - - @Optional.Method(modid = "baubles") - private ItemStack getBaubleStack(EntityPlayer player, int baubleSlot) { - return BaublesApi.getBaublesHandler(player).getStackInSlot(baubleSlot); - } - - @Override - protected boolean canSendUpdates() { - ItemStack currentStack = getTerminalStack(); - - if (currentStack.isEmpty() || !(currentStack.getItem() instanceof ItemWirelessUniversalTerminal)) { - this.setValidContainer(false); - - return false; - } - - // Drain power periodically (every 20 ticks = 1 second) - this.ticks++; - if (this.ticks >= 20) { - IWirelessTermHandler handler = AEApi.instance().registries().wireless().getWirelessTerminalHandler(currentStack); - if (handler == null) { - this.setValidContainer(false); - - return false; - } - - double powerDrain = AEConfig.instance().wireless_getDrainRate(0); - boolean powerUsed = handler.usePower(getPlayerInv().player, powerDrain, currentStack); - - if (!powerUsed) { - if (this.isValidContainer()) { - getPlayerInv().player.sendMessage(PlayerMessages.DeviceNotPowered.get()); - } - this.setValidContainer(false); - - return false; - } - - this.ticks = 0; - } - - // Check range (simplified - just check grid is still valid) - if (this.grid == null) { - if (this.isValidContainer()) { - getPlayerInv().player.sendMessage(PlayerMessages.StationCanNotBeLocated.get()); - } - this.setValidContainer(false); - - return false; - } - - return true; - } - - @Override - public IActionHost getActionHost() { - return this.wirelessTerminalGuiObject; - } -} diff --git a/src/main/java/com/cellterminal/container/ContainerWirelessCellTerminal.java b/src/main/java/com/cellterminal/container/ContainerWirelessCellTerminal.java index 1be06af..a13b1ca 100644 --- a/src/main/java/com/cellterminal/container/ContainerWirelessCellTerminal.java +++ b/src/main/java/com/cellterminal/container/ContainerWirelessCellTerminal.java @@ -1,70 +1,46 @@ package com.cellterminal.container; +import appeng.api.config.Actionable; +import appeng.api.config.PowerMultiplier; +import com.cellterminal.integration.AE2WUTIntegration; +import com.cellterminal.items.ItemWirelessCellTerminal; +import com.cellterminal.util.AE2OldVersionSupport; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.item.ItemStack; import net.minecraftforge.fml.common.Optional; import appeng.api.AEApi; -import appeng.api.config.Actionable; -import appeng.api.features.ILocatable; import appeng.api.features.IWirelessTermHandler; -import appeng.api.networking.IGridNode; -import appeng.api.networking.security.IActionHost; import appeng.core.AEConfig; import appeng.core.localization.PlayerMessages; -import appeng.util.Platform; +import appeng.helpers.WirelessTerminalGuiObject; import baubles.api.BaublesApi; -import com.cellterminal.items.ItemWirelessCellTerminal; - /** * Container for the Wireless Cell Terminal GUI. * Similar to ContainerCellTerminal but works with wireless connection. */ +@Optional.Interface(iface = "appeng.helpers.WirelessTerminalGuiObject", modid = "ae2wut") public class ContainerWirelessCellTerminal extends ContainerCellTerminalBase { + private final WirelessTerminalGuiObject wirelessTerminalGuiObject; private final int slot; private final boolean isBauble; - private final ItemStack terminalStack; private int ticks = 0; - public ContainerWirelessCellTerminal(InventoryPlayer ip, int slot, boolean isBauble) { - super(ip, null); - this.slot = slot; - this.isBauble = isBauble; - - if (isBauble) { - this.terminalStack = getBaubleStack(ip.player, slot); - } else { - this.terminalStack = ip.getStackInSlot(slot); - this.lockPlayerInventorySlot(slot); - } - - if (Platform.isServer() && !terminalStack.isEmpty()) { - IWirelessTermHandler handler = AEApi.instance().registries().wireless().getWirelessTerminalHandler(terminalStack); - - if (handler != null) { - String encKey = handler.getEncryptionKey(terminalStack); + public ContainerWirelessCellTerminal(InventoryPlayer ip, WirelessTerminalGuiObject wth) { + super(ip, wth); - try { - long parsedKey = Long.parseLong(encKey); - ILocatable obj = AEApi.instance().registries().locatable().getLocatableBy(parsedKey); + this.wirelessTerminalGuiObject = wth; + this.slot = wth.getInventorySlot(); + this.isBauble = AE2OldVersionSupport.wirelessTerminalGuiObjectIsBauble(wth, ip); - if (obj instanceof IActionHost) { - IGridNode n = ((IActionHost) obj).getActionableNode(); - if (n != null) { - this.grid = n.getGrid(); - } - } - } catch (NumberFormatException e) { - // Invalid key - } - } - } + // Lock the slot if it's in the main inventory + if (!isBauble && slot >= 0 && slot < ip.mainInventory.size()) this.lockPlayerInventorySlot(slot); this.bindPlayerInventory(ip, 0, 0); } @@ -73,14 +49,27 @@ public ContainerWirelessCellTerminal(InventoryPlayer ip, int slot, boolean isBau private ItemStack getBaubleStack(EntityPlayer player, int baubleSlot) { return BaublesApi.getBaublesHandler(player).getStackInSlot(baubleSlot); } + /** + * Get the WirelessTerminalGuiObject for this container. + * Used by the GUI to access WUT functionality. + */ + public WirelessTerminalGuiObject getWirelessTerminalGuiObject() { + return this.wirelessTerminalGuiObject; + } + + private ItemStack getTerminalStack() { + if (isBauble) return getBaubleStack(getPlayerInv().player, slot); + + return getPlayerInv().getStackInSlot(slot); + } + + @Override protected boolean canSendUpdates() { - // Check terminal is still valid - ItemStack currentStack = isBauble ? getBaubleStack(getPlayerInv().player, slot) - : getPlayerInv().getStackInSlot(slot); + ItemStack currentStack = getTerminalStack(); - if (currentStack.isEmpty() || !(currentStack.getItem() instanceof ItemWirelessCellTerminal)) { + if (currentStack.isEmpty() || !(currentStack.getItem() instanceof ItemWirelessCellTerminal || AE2WUTIntegration.isWirelessUniversalTerminal(currentStack))) { this.setValidContainer(false); return false; diff --git a/src/main/java/com/cellterminal/gui/GuiCellTerminalBase.java b/src/main/java/com/cellterminal/gui/GuiCellTerminalBase.java index eac9547..ddddc7c 100644 --- a/src/main/java/com/cellterminal/gui/GuiCellTerminalBase.java +++ b/src/main/java/com/cellterminal/gui/GuiCellTerminalBase.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; +import com.cellterminal.container.ContainerCellTerminalBase; import org.lwjgl.input.Keyboard; import net.minecraft.client.Minecraft; @@ -78,7 +79,6 @@ import com.cellterminal.network.PacketExtractUpgrade; import com.cellterminal.network.PacketHighlightBlock; import com.cellterminal.network.PacketSubnetAction; -import com.cellterminal.network.PacketSubnetListRequest; import com.cellterminal.network.PacketSwitchNetwork; import com.cellterminal.network.PacketPartitionAction; import com.cellterminal.network.PacketRenameAction; @@ -89,7 +89,6 @@ import com.cellterminal.network.PacketUpgradeStorageBus; import com.cellterminal.gui.rename.InlineRenameEditor; import com.cellterminal.gui.rename.Renameable; -import com.cellterminal.gui.rename.RenameTargetType; /** @@ -963,6 +962,12 @@ public List getJEIExclusionArea() { )); } + if (hasToolbox()) { + areas.add(new Rectangle( + this.guiLeft + this.xSize + 1, this.guiTop + this.ySize - 90, 68, 68 + )); + } + return areas; } @@ -1045,6 +1050,14 @@ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { this.drawTexturedModalRect(offsetX, bottomY, 0, 158, this.xSize, 98); drawTabs(offsetX, offsetY, mouseX, mouseY); + this.bindTexture("guis/bus.png"); + if (this.hasToolbox()) { + this.drawTexturedModalRect(offsetX + this.xSize + 1, offsetY + this.ySize - 90, 178, 184 - 90, 68, 68); + } + } + + public boolean hasToolbox() { + return ((ContainerCellTerminalBase) this.inventorySlots).hasToolbox(); } protected void drawTabs(int offsetX, int offsetY, int mouseX, int mouseY) { diff --git a/src/main/java/com/cellterminal/gui/GuiHandler.java b/src/main/java/com/cellterminal/gui/GuiHandler.java index 6c76457..642eb1f 100644 --- a/src/main/java/com/cellterminal/gui/GuiHandler.java +++ b/src/main/java/com/cellterminal/gui/GuiHandler.java @@ -2,9 +2,14 @@ import javax.annotation.Nullable; +import appeng.api.AEApi; +import appeng.api.features.IWirelessTermHandler; +import appeng.helpers.WirelessTerminalGuiObject; +import baubles.api.BaublesApi; +import com.cellterminal.container.ContainerWirelessCellTerminal; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.common.network.IGuiHandler; @@ -12,12 +17,10 @@ import appeng.api.parts.IPart; import appeng.api.parts.IPartHost; import appeng.api.util.AEPartLocation; -import appeng.container.AEBaseContainer; import appeng.container.ContainerOpenContext; import com.cellterminal.CellTerminal; import com.cellterminal.container.ContainerCellTerminal; -import com.cellterminal.container.ContainerWirelessCellTerminal; import com.cellterminal.part.PartCellTerminal; @@ -62,7 +65,23 @@ public Object getServerGuiElement(int id, EntityPlayer player, World world, int } } else if (guiType == GUI_WIRELESS_CELL_TERMINAL) { boolean isBauble = y == 1; - return new ContainerWirelessCellTerminal(player.inventory, x, isBauble); + ItemStack terminal = ItemStack.EMPTY; + if (!isBauble) { + if (x >= 0 && x < player.inventory.mainInventory.size()) { + terminal = player.inventory.getStackInSlot(x); + } + } else { + terminal = BaublesApi.getBaublesHandler(player).getStackInSlot(x); + } + IWirelessTermHandler handler = AEApi + .instance() + .registries() + .wireless() + .getWirelessTerminalHandler(terminal); + return new ContainerWirelessCellTerminal( + player.inventory, + new WirelessTerminalGuiObject(handler, terminal, player, world, x, y, z) + ); } return null; @@ -84,7 +103,23 @@ public Object getClientGuiElement(int id, EntityPlayer player, World world, int } } else if (guiType == GUI_WIRELESS_CELL_TERMINAL) { boolean isBauble = y == 1; - return new GuiWirelessCellTerminal(player.inventory, x, isBauble); + ItemStack terminal = ItemStack.EMPTY; + if (!isBauble) { + if (x >= 0 && x < player.inventory.mainInventory.size()) { + terminal = player.inventory.getStackInSlot(x); + } + } else { + terminal = BaublesApi.getBaublesHandler(player).getStackInSlot(x); + } + IWirelessTermHandler handler = AEApi + .instance() + .registries() + .wireless() + .getWirelessTerminalHandler(terminal); + return new GuiWirelessCellTerminal( + player.inventory, + new WirelessTerminalGuiObject(handler, terminal, player, world, x, y, z) + ); } return null; diff --git a/src/main/java/com/cellterminal/gui/GuiWUTCellTerminal.java b/src/main/java/com/cellterminal/gui/GuiWUTCellTerminal.java deleted file mode 100644 index ba74ae0..0000000 --- a/src/main/java/com/cellterminal/gui/GuiWUTCellTerminal.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.cellterminal.gui; - -import java.awt.Rectangle; -import java.io.IOException; -import java.util.List; - -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.resources.I18n; -import net.minecraft.entity.player.InventoryPlayer; - -import appeng.helpers.WirelessTerminalGuiObject; - -import com.cellterminal.config.CellTerminalClientConfig; -import com.cellterminal.container.ContainerWUTCellTerminal; -import com.cellterminal.integration.AE2WUTIntegration; -import com.cellterminal.integration.WUTModeSwitcher; - - -/** - * GUI for Cell Terminal when opened via AE2WUT's Wireless Universal Terminal. - * Same functionality as GuiWirelessCellTerminal but works with WUT's system. - */ -public class GuiWUTCellTerminal extends GuiCellTerminalBase { - - private final WirelessTerminalGuiObject wirelessTerminalGuiObject; - private WUTModeSwitcher modeSwitcher; - - public GuiWUTCellTerminal(InventoryPlayer playerInventory, WirelessTerminalGuiObject wth) { - super(new ContainerWUTCellTerminal(playerInventory, wth)); - this.wirelessTerminalGuiObject = wth; - } - - @Override - protected String getGuiTitle() { - return I18n.format("gui.cellterminal.wireless_cell_terminal.title"); - } - - @Override - public void initGui() { - // Clear old mode switcher buttons before super.initGui() clears buttonList - if (modeSwitcher != null) { - modeSwitcher = null; - } - - super.initGui(); - - // Initialize WUT mode switcher if available - if (AE2WUTIntegration.isModLoaded()) { - this.modeSwitcher = new WUTModeSwitcher(wirelessTerminalGuiObject); - boolean isSmallStyle = CellTerminalClientConfig.getInstance().getTerminalStyle() == CellTerminalClientConfig.TerminalStyle.SMALL; - this.nextButtonId = modeSwitcher.initButtons(this.buttonList, this.nextButtonId, this.guiLeft, this.guiTop, this.ySize, this.height, isSmallStyle); - } - } - - @Override - protected void actionPerformed(GuiButton button) throws IOException { - // Check if WUT mode switcher handles this button - if (modeSwitcher != null && modeSwitcher.handleButtonClick(button)) return; - - super.actionPerformed(button); - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - super.drawScreen(mouseX, mouseY, partialTicks); - - // Draw WUT mode switcher tooltips - if (modeSwitcher != null) modeSwitcher.drawTooltips(mouseX, mouseY, this); - } - - /** - * Get the WirelessTerminalGuiObject for WUT integration. - */ - public WirelessTerminalGuiObject getWirelessTerminalGuiObject() { - return this.wirelessTerminalGuiObject; - } - - /** - * Get the container as the WUT-specific type. - */ - public ContainerWUTCellTerminal getWUTContainer() { - return (ContainerWUTCellTerminal) this.inventorySlots; - } - - @Override - public List getJEIExclusionArea() { - List areas = super.getJEIExclusionArea(); - - // Add WUT mode switcher exclusion area if present - if (modeSwitcher != null) { - Rectangle switcherArea = modeSwitcher.getExclusionArea(); - if (switcherArea.width > 0) areas.add(switcherArea); - } - - return areas; - } -} diff --git a/src/main/java/com/cellterminal/gui/GuiWirelessCellTerminal.java b/src/main/java/com/cellterminal/gui/GuiWirelessCellTerminal.java index 0e04e19..a4e6fb4 100644 --- a/src/main/java/com/cellterminal/gui/GuiWirelessCellTerminal.java +++ b/src/main/java/com/cellterminal/gui/GuiWirelessCellTerminal.java @@ -1,9 +1,19 @@ package com.cellterminal.gui; +import java.awt.Rectangle; +import java.io.IOException; +import java.util.List; + +import com.cellterminal.container.ContainerWirelessCellTerminal; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.InventoryPlayer; -import com.cellterminal.container.ContainerWirelessCellTerminal; +import appeng.helpers.WirelessTerminalGuiObject; + +import com.cellterminal.config.CellTerminalClientConfig; +import com.cellterminal.integration.AE2WUTIntegration; +import com.cellterminal.integration.WUTModeSwitcher; /** @@ -12,12 +22,76 @@ */ public class GuiWirelessCellTerminal extends GuiCellTerminalBase { - public GuiWirelessCellTerminal(InventoryPlayer playerInventory, int slot, boolean isBauble) { - super(new ContainerWirelessCellTerminal(playerInventory, slot, isBauble)); + private final WirelessTerminalGuiObject wirelessTerminalGuiObject; + private WUTModeSwitcher modeSwitcher; + + public GuiWirelessCellTerminal(InventoryPlayer playerInventory, WirelessTerminalGuiObject wth) { + super(new ContainerWirelessCellTerminal(playerInventory, wth)); + this.wirelessTerminalGuiObject = wth; } @Override protected String getGuiTitle() { return I18n.format("gui.cellterminal.wireless_cell_terminal.title"); } + + @Override + public void initGui() { + // Clear old mode switcher buttons before super.initGui() clears buttonList + if (modeSwitcher != null) { + modeSwitcher = null; + } + + super.initGui(); + + // Initialize WUT mode switcher if available + if (AE2WUTIntegration.isModLoaded()) { + this.modeSwitcher = new WUTModeSwitcher(wirelessTerminalGuiObject); + boolean isSmallStyle = CellTerminalClientConfig.getInstance().getTerminalStyle() == CellTerminalClientConfig.TerminalStyle.SMALL; + this.nextButtonId = modeSwitcher.initButtons(this.buttonList, this.nextButtonId, this.guiLeft, this.guiTop, this.ySize, this.height, isSmallStyle); + } + } + + @Override + protected void actionPerformed(GuiButton button) throws IOException { + // Check if WUT mode switcher handles this button + if (modeSwitcher != null && modeSwitcher.handleButtonClick(button)) return; + + super.actionPerformed(button); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + + // Draw WUT mode switcher tooltips + if (modeSwitcher != null) modeSwitcher.drawTooltips(mouseX, mouseY, this); + } + + /** + * Get the WirelessTerminalGuiObject for WUT integration. + */ + public WirelessTerminalGuiObject getWirelessTerminalGuiObject() { + return this.wirelessTerminalGuiObject; + } + + /** + * Get the container as the WUT-specific type. + */ + public ContainerWirelessCellTerminal getWUTContainer() { + return (ContainerWirelessCellTerminal) this.inventorySlots; + } + + @Override + public List getJEIExclusionArea() { + List areas = super.getJEIExclusionArea(); + + // Add WUT mode switcher exclusion area if present + if (modeSwitcher != null) { + Rectangle switcherArea = modeSwitcher.getExclusionArea(); + if (switcherArea.width > 0) areas.add(switcherArea); + } + + return areas; + } } diff --git a/src/main/java/com/cellterminal/integration/AE2WUTIntegration.java b/src/main/java/com/cellterminal/integration/AE2WUTIntegration.java index 3cdeb02..0271c89 100644 --- a/src/main/java/com/cellterminal/integration/AE2WUTIntegration.java +++ b/src/main/java/com/cellterminal/integration/AE2WUTIntegration.java @@ -1,5 +1,6 @@ package com.cellterminal.integration; +import com.cellterminal.container.ContainerWirelessCellTerminal; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraftforge.common.util.Constants; @@ -10,15 +11,11 @@ import appeng.api.AEApi; import appeng.api.features.IWirelessTermHandler; -import appeng.client.gui.AEBaseGui; -import appeng.container.AEBaseContainer; import appeng.helpers.WirelessTerminalGuiObject; -import com.cellterminal.CellTerminal; import com.cellterminal.ItemRegistry; import com.cellterminal.config.CellTerminalServerConfig; -import com.cellterminal.container.ContainerWUTCellTerminal; -import com.cellterminal.gui.GuiWUTCellTerminal; +import com.cellterminal.gui.GuiWirelessCellTerminal; import com.circulation.ae2wut.AE2UELWirelessUniversalTerminal; import com.circulation.ae2wut.client.model.ItemWUTBakedModel; import com.circulation.ae2wut.item.ItemWirelessUniversalTerminal; @@ -85,7 +82,7 @@ private static void registerContainerInternal() { WirelessTerminalGuiObject wth = getWirelessTerminalGuiObject(item, player, slot, isBauble); if (wth == null) return null; - return new ContainerWUTCellTerminal(player.inventory, wth); + return new ContainerWirelessCellTerminal(player.inventory, wth); } ); } @@ -110,7 +107,7 @@ private static void registerGuiInternal() { WirelessTerminalGuiObject wth = getWirelessTerminalGuiObject(item, player, slot, isBauble); if (wth == null) return null; - return new GuiWUTCellTerminal(player.inventory, wth); + return new GuiWirelessCellTerminal(player.inventory, wth); } ); } diff --git a/src/main/java/com/cellterminal/util/AE2OldVersionSupport.java b/src/main/java/com/cellterminal/util/AE2OldVersionSupport.java new file mode 100644 index 0000000..700d567 --- /dev/null +++ b/src/main/java/com/cellterminal/util/AE2OldVersionSupport.java @@ -0,0 +1,24 @@ +package com.cellterminal.util; + +import appeng.helpers.WirelessTerminalGuiObject; +import baubles.api.BaublesApi; +import com.cellterminal.ItemRegistry; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.Optional; + +public class AE2OldVersionSupport { + public static boolean wirelessTerminalGuiObjectIsBauble(WirelessTerminalGuiObject wth, InventoryPlayer ip) { + try { + return wth.isBaubleSlot(); + } catch (NoSuchMethodError e) { + return ip.getStackInSlot(wth.getInventorySlot()).getItem() != ItemRegistry.WIRELESS_CELL_TERMINAL && getBaubleStack(ip.player, wth.getInventorySlot()).getItem() == ItemRegistry.WIRELESS_CELL_TERMINAL; + } + } + + @Optional.Method(modid = "baubles") + private static ItemStack getBaubleStack(EntityPlayer player, int baubleSlot) { + return BaublesApi.getBaublesHandler(player).getStackInSlot(baubleSlot); + } +}