diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07956e0cb..fa3ffc7c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ slf4j = "1.7.36" # Minecraft mods iris = "1.5.2+1.19.4" -jei = "11.3.0.262" +jei = "13.1.0.11" modmenu = "6.1.0-rc.1" oculus = "1.2.5" rei = "10.0.578" @@ -83,6 +83,8 @@ kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" } +netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" } +netty-proxy = { module = "io.netty:netty-handler-proxy", version.ref = "netty" } nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" } nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" } slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } @@ -91,9 +93,9 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" } fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } iris = { module = "maven.modrinth:iris", version.ref = "iris" } -jei-api = { module = "mezz.jei:jei-1.19.2-common-api", version.ref = "jei" } -jei-fabric = { module = "mezz.jei:jei-1.19.2-fabric", version.ref = "jei" } -jei-forge = { module = "mezz.jei:jei-1.19.2-forge", version.ref = "jei" } +jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" } +jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" } +jei-forge = { module = "mezz.jei:jei-1.19.4-forge", version.ref = "jei" } mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" } modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" } oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" } @@ -148,10 +150,10 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"] # Minecraft externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"] externalMods-forge-compile = ["oculus", "jei-api"] -externalMods-forge-runtime = [] +externalMods-forge-runtime = ["jei-forge"] externalMods-fabric = ["nightConfig-core", "nightConfig-toml"] externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"] -externalMods-fabric-runtime = ["modmenu"] +externalMods-fabric-runtime = ["jei-fabric", "modmenu"] # Testing test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"] diff --git a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java index dd4da45cf..46854e04d 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java @@ -15,8 +15,8 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.platform.RecipeIngredients; import dan200.computercraft.shared.platform.RegistryWrappers; -import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.pocket.items.PocketComputerItem; +import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.util.ColourUtils; import net.minecraft.advancements.critereon.InventoryChangeTrigger; import net.minecraft.advancements.critereon.ItemPredicate; @@ -38,6 +38,7 @@ import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.block.Blocks; +import java.util.List; import java.util.Locale; import java.util.function.Consumer; @@ -93,20 +94,23 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { } } + private static List turtleItems() { + return List.of(ModRegistry.Items.TURTLE_NORMAL.get(), ModRegistry.Items.TURTLE_ADVANCED.get()); + } + /** * Register a crafting recipe for each turtle upgrade. * * @param add The callback to add recipes. */ private void turtleUpgrades(Consumer add) { - for (var family : ComputerFamily.values()) { - var base = TurtleItemFactory.create(-1, null, -1, family, null, null, 0, null); - if (base.isEmpty()) continue; + for (var turtleItem : turtleItems()) { + var base = turtleItem.create(-1, null, -1, null, null, 0, null); - var nameId = family.name().toLowerCase(Locale.ROOT); + var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT); for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) { - var result = TurtleItemFactory.create(-1, null, -1, family, null, upgrade, -1, null); + var result = turtleItem.create(-1, null, -1, null, upgrade, -1, null); ShapedRecipeBuilder .shaped(RecipeCategory.REDSTONE, result.getItem()) .group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId)) @@ -125,20 +129,24 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { } } + private static List pocketComputerItems() { + return List.of(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()); + } + /** * Register a crafting recipe for each pocket upgrade. * * @param add The callback to add recipes. */ private void pocketUpgrades(Consumer add) { - for (var family : ComputerFamily.values()) { - var base = PocketComputerItemFactory.create(-1, null, -1, family, null); + for (var pocket : pocketComputerItems()) { + var base = pocket.create(-1, null, -1, null); if (base.isEmpty()) continue; - var nameId = family.name().toLowerCase(Locale.ROOT); + var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT); for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) { - var result = PocketComputerItemFactory.create(-1, null, -1, family, upgrade); + var result = pocket.create(-1, null, -1, upgrade); ShapedRecipeBuilder .shaped(RecipeCategory.REDSTONE, result.getItem()) .group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId)) @@ -180,11 +188,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { } private void turtleOverlay(Consumer add, String overlay, Consumer build) { - for (var family : ComputerFamily.values()) { - var base = TurtleItemFactory.create(-1, null, -1, family, null, null, 0, null); - if (base.isEmpty()) continue; + for (var turtleItem : turtleItems()) { + var base = turtleItem.create(-1, null, -1, null, null, 0, null); - var nameId = family.name().toLowerCase(Locale.ROOT); + var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT); var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId); var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem()) diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java index 6d1544dec..443383e51 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java @@ -5,7 +5,6 @@ package dan200.computercraft.shared.computer.inventory; import dan200.computercraft.core.terminal.Terminal; -import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.menu.ComputerMenu; @@ -13,6 +12,7 @@ import dan200.computercraft.shared.computer.menu.ServerInputHandler; import dan200.computercraft.shared.computer.menu.ServerInputState; import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; import dan200.computercraft.shared.computer.terminal.TerminalState; +import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.container.SingleContainerData; import dan200.computercraft.shared.network.container.ComputerContainerData; import net.minecraft.world.entity.player.Player; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/config/AddressRuleConfig.java b/projects/common/src/main/java/dan200/computercraft/shared/config/AddressRuleConfig.java index 5c6cd20a2..8974c9185 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/config/AddressRuleConfig.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/config/AddressRuleConfig.java @@ -45,6 +45,9 @@ class AddressRuleConfig { config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet."); config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE); + + config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured."); + config.set("use_proxy", false); } return config; @@ -58,6 +61,7 @@ class AddressRuleConfig { && check(builder, "max_upload", Number.class) && check(builder, "max_download", Number.class) && check(builder, "websocket_message", Number.class) + && check(builder, "use_proxy", Boolean.class) && AddressRule.parse(hostObj, port, PartialOptions.DEFAULT) != null; } @@ -71,12 +75,14 @@ class AddressRuleConfig { var maxUpload = unboxOptLong(get(builder, "max_upload", Number.class).map(Number::longValue)); var maxDownload = unboxOptLong(get(builder, "max_download", Number.class).map(Number::longValue)); var websocketMessage = unboxOptInt(get(builder, "websocket_message", Number.class).map(Number::intValue)); + var useProxy = get(builder, "use_proxy", Boolean.class); var options = new PartialOptions( action, maxUpload, maxDownload, - websocketMessage + websocketMessage, + useProxy ); return AddressRule.parse(hostObj, port, options); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java index 346fb5e49..afb239e9a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigFile.java @@ -8,6 +8,7 @@ import com.google.common.base.Splitter; import javax.annotation.Nullable; import javax.annotation.OverridingMethodsMustInvokeSuper; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; @@ -141,6 +142,18 @@ public interface ConfigFile { * @param onChange The function to run on change. * @return The built config file. */ - public abstract ConfigFile build(Runnable onChange); + public abstract ConfigFile build(ConfigListener onChange); + } + + @FunctionalInterface + interface ConfigListener { + /** + * The function called then a config file is changed. + * + * @param path The path to the config file. This will be {@code null} when the config file does not exist on + * disk, such as when synced from a server to the client. + * @see Builder#build(ConfigListener) + */ + void onConfigChanged(@Nullable Path path); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java index 9f751ce30..fdd603ade 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/config/ConfigSpec.java @@ -5,10 +5,12 @@ package dan200.computercraft.shared.config; import com.electronwill.nightconfig.core.UnmodifiableConfig; +import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.core.CoreConfig; import dan200.computercraft.core.Logging; import dan200.computercraft.core.apis.http.NetworkUtils; import dan200.computercraft.core.apis.http.options.Action; +import dan200.computercraft.core.apis.http.options.ProxyType; import dan200.computercraft.core.computer.mainthread.MainThreadConfig; import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer; import dan200.computercraft.shared.platform.PlatformHelper; @@ -16,6 +18,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.filter.MarkerFilter; +import javax.annotation.Nullable; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -49,6 +53,10 @@ public final class ConfigSpec { public static final ConfigFile.Value httpDownloadBandwidth; public static final ConfigFile.Value httpUploadBandwidth; + public static final ConfigFile.Value httpProxyType; + public static final ConfigFile.Value httpProxyHost; + public static final ConfigFile.Value httpProxyPort; + public static final ConfigFile.Value commandBlockEnabled; public static final ConfigFile.Value modemRange; public static final ConfigFile.Value modemHighAltitudeRange; @@ -222,6 +230,30 @@ public final class ConfigSpec { builder.pop(); + builder + .comment(""" + Tunnels HTTP and websocket requests through a proxy server. Only affects HTTP + rules with "use_proxy" set to true (off by default). + If authentication is required for the proxy, create a "computercraft-proxy.pw" + file in the same directory as "computercraft-server.toml", containing the + username and password separated by a colon, e.g. "myuser:mypassword". For + SOCKS4 proxies only the username is required.""") + .push("proxy"); + + httpProxyType = builder + .comment("The type of proxy to use.") + .defineEnum("type", CoreConfig.httpProxyType); + + httpProxyHost = builder + .comment("The hostname or IP address of the proxy server.") + .define("host", CoreConfig.httpProxyHost); + + httpProxyPort = builder + .comment("The port of the proxy server.") + .defineInRange("port", CoreConfig.httpProxyPort, 1, 65536); + + builder.pop(); + builder.pop(); } @@ -339,7 +371,7 @@ public final class ConfigSpec { clientSpec = clientBuilder.build(ConfigSpec::syncClient); } - public static void syncServer() { + public static void syncServer(@Nullable Path path) { // General Config.computerSpaceLimit = computerSpaceLimit.get(); Config.floppySpaceLimit = floppySpaceLimit.get(); @@ -370,6 +402,13 @@ public final class ConfigSpec { CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get(); CoreConfig.httpDownloadBandwidth = httpDownloadBandwidth.get(); CoreConfig.httpUploadBandwidth = httpUploadBandwidth.get(); + + CoreConfig.httpProxyType = httpProxyType.get(); + CoreConfig.httpProxyHost = httpProxyHost.get(); + CoreConfig.httpProxyPort = httpProxyPort.get(); + + if (path != null) ProxyPasswordConfig.init(path.resolveSibling(ComputerCraftAPI.MOD_ID + "-proxy.pw")); + NetworkUtils.reloadConfig(); // Peripheral @@ -396,7 +435,7 @@ public final class ConfigSpec { Config.monitorHeight = monitorHeight.get(); } - public static void syncClient() { + public static void syncClient(@Nullable Path path) { Config.monitorRenderer = monitorRenderer.get(); Config.monitorDistance = monitorDistance.get(); Config.uploadNagDelay = uploadNagDelay.get(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/config/ProxyPasswordConfig.java b/projects/common/src/main/java/dan200/computercraft/shared/config/ProxyPasswordConfig.java new file mode 100644 index 000000000..3c45f64f4 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/config/ProxyPasswordConfig.java @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.config; + +import dan200.computercraft.core.CoreConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +record ProxyPasswordConfig(String username, String password) { + private static final Logger LOG = LoggerFactory.getLogger(ProxyPasswordConfig.class); + + @Nullable + private static ProxyPasswordConfig loadFromFile(@Nullable Path path) { + if (path == null || !path.toFile().exists()) return null; + + try (var br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + var line = br.readLine(); + if (line == null) return null; + + var parts = line.trim().split(":", 2); + if (parts.length == 0) return null; + + return new ProxyPasswordConfig(parts[0], parts.length == 2 ? parts[1] : ""); + } catch (IOException e) { + LOG.error("Failed to load proxy password from {}.", path, e); + return null; + } + } + + static void init(@Nullable Path path) { + var config = loadFromFile(path); + if (config == null) { + CoreConfig.httpProxyUsername = ""; + CoreConfig.httpProxyPassword = ""; + } else { + CoreConfig.httpProxyUsername = config.username; + CoreConfig.httpProxyPassword = config.password; + } + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/RecipeModHelpers.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/RecipeModHelpers.java index 69e443890..1e93c30a9 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/integration/RecipeModHelpers.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/RecipeModHelpers.java @@ -7,21 +7,25 @@ package dan200.computercraft.shared.integration; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.impl.PocketUpgrades; import dan200.computercraft.impl.TurtleUpgrades; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.pocket.items.PocketComputerItem; +import dan200.computercraft.shared.turtle.items.TurtleItem; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; /** * Utilities for recipe mod plugins (such as JEI). */ public final class RecipeModHelpers { static final List MAIN_FAMILIES = Arrays.asList(ComputerFamily.NORMAL, ComputerFamily.ADVANCED); + static final List> TURTLES = List.of(ModRegistry.Items.TURTLE_NORMAL, ModRegistry.Items.TURTLE_ADVANCED); + static final List> POCKET_COMPUTERS = List.of(ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED); private RecipeModHelpers() { } @@ -49,13 +53,17 @@ public final class RecipeModHelpers { */ public static List getExtraStacks() { List upgradeItems = new ArrayList<>(); - for (var family : MAIN_FAMILIES) { + for (var turtleSupplier : TURTLES) { + var turtle = turtleSupplier.get(); for (var upgrade : TurtleUpgrades.instance().getUpgrades()) { - upgradeItems.add(TurtleItemFactory.create(-1, null, -1, family, null, upgrade, 0, null)); + upgradeItems.add(turtle.create(-1, null, -1, null, upgrade, 0, null)); } + } + for (var pocketSupplier : POCKET_COMPUTERS) { + var pocket = pocketSupplier.get(); for (var upgrade : PocketUpgrades.instance().getUpgrades()) { - upgradeItems.add(PocketComputerItemFactory.create(-1, null, -1, family, upgrade)); + upgradeItems.add(pocket.create(-1, null, -1, upgrade)); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/UpgradeRecipeGenerator.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/UpgradeRecipeGenerator.java index 36e86ed51..0dbe35b5d 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/integration/UpgradeRecipeGenerator.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/UpgradeRecipeGenerator.java @@ -12,9 +12,7 @@ import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.impl.PocketUpgrades; import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.pocket.items.PocketComputerItem; -import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; import dan200.computercraft.shared.turtle.items.TurtleItem; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; import net.minecraft.core.NonNullList; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; @@ -28,7 +26,8 @@ import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; -import static dan200.computercraft.shared.integration.RecipeModHelpers.MAIN_FAMILIES; +import static dan200.computercraft.shared.integration.RecipeModHelpers.POCKET_COMPUTERS; +import static dan200.computercraft.shared.integration.RecipeModHelpers.TURTLES; /** * Provides dynamic recipe and usage information for upgraded turtle and pocket computers. This is intended to be @@ -218,17 +217,16 @@ public class UpgradeRecipeGenerator { private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable ITurtleUpgrade right) { var item = (TurtleItem) stack.getItem(); - return TurtleItemFactory.create( - item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), item.getFamily(), + return item.create( + item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), left, right, item.getFuelLevel(stack), item.getOverlay(stack) ); } private static ItemStack pocketWith(ItemStack stack, @Nullable IPocketUpgrade back) { var item = (PocketComputerItem) stack.getItem(); - return PocketComputerItemFactory.create( - item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), item.getFamily(), - back + return item.create( + item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back ); } @@ -267,20 +265,25 @@ public class UpgradeRecipeGenerator { if (recipes != null) return recipes; recipes = this.recipes = new ArrayList<>(4); - for (var family : MAIN_FAMILIES) { - if (turtle != null) { + + if (turtle != null) { + for (var turtleSupplier : TURTLES) { + var turtleItem = turtleSupplier.get(); recipes.add(turtle( ingredient, // Right upgrade, recipe on left - Ingredient.of(TurtleItemFactory.create(-1, null, -1, family, null, null, 0, null)), - TurtleItemFactory.create(-1, null, -1, family, null, turtle, 0, null) + Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)), + turtleItem.create(-1, null, -1, null, turtle, 0, null) )); } + } - if (pocket != null) { + if (pocket != null) { + for (var pocketSupplier : POCKET_COMPUTERS) { + var pocketItem = pocketSupplier.get(); recipes.add(pocket( ingredient, - Ingredient.of(PocketComputerItemFactory.create(-1, null, -1, family, null)), - PocketComputerItemFactory.create(-1, null, -1, family, pocket) + Ingredient.of(pocketItem.create(-1, null, -1, null)), + pocketItem.create(-1, null, -1, pocket) )); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java index c2c7f5825..b6b014697 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java @@ -10,7 +10,7 @@ import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.integration.RecipeModHelpers; import dan200.computercraft.shared.media.items.DiskItem; import dan200.computercraft.shared.pocket.items.PocketComputerItem; -import dan200.computercraft.shared.turtle.items.ITurtleItem; +import dan200.computercraft.shared.turtle.items.TurtleItem; import mezz.jei.api.IModPlugin; import mezz.jei.api.JeiPlugin; import mezz.jei.api.constants.RecipeTypes; @@ -71,7 +71,7 @@ public class JEIComputerCraft implements IModPlugin { */ private static final IIngredientSubtypeInterpreter turtleSubtype = (stack, ctx) -> { var item = stack.getItem(); - if (!(item instanceof ITurtleItem turtle)) return IIngredientSubtypeInterpreter.NONE; + if (!(item instanceof TurtleItem turtle)) return IIngredientSubtypeInterpreter.NONE; var name = new StringBuilder("turtle:"); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java index 7d3abc57b..a3648ba0c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java @@ -12,6 +12,7 @@ import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.impl.PocketUpgrades; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerContext; @@ -45,7 +46,6 @@ import java.util.List; public class PocketComputerItem extends Item implements IComputerItem, IMedia, IColouredItem { private static final String NBT_UPGRADE = "Upgrade"; private static final String NBT_UPGRADE_INFO = "UpgradeInfo"; - public static final String NBT_LIGHT = "Light"; public static final String NBT_ON = "On"; private static final String NBT_INSTANCE = "InstanceId"; @@ -58,6 +58,14 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I this.family = family; } + public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable IPocketUpgrade upgrade) { + return switch (family) { + case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade); + case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade); + default -> ItemStack.EMPTY; + }; + } + public ItemStack create(int id, @Nullable String label, int colour, @Nullable IPocketUpgrade upgrade) { var result = new ItemStack(this); if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id); @@ -234,7 +242,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I @Override public ItemStack withFamily(ItemStack stack, ComputerFamily family) { - return PocketComputerItemFactory.create( + return create( getComputerID(stack), getLabel(stack), getColour(stack), family, getUpgrade(stack) ); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItemFactory.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItemFactory.java deleted file mode 100644 index 010ef708b..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItemFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.shared.pocket.items; - -import dan200.computercraft.api.pocket.IPocketUpgrade; -import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.computer.core.ComputerFamily; -import net.minecraft.world.item.ItemStack; - -import javax.annotation.Nullable; - -public final class PocketComputerItemFactory { - private PocketComputerItemFactory() { - } - - public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable IPocketUpgrade upgrade) { - return switch (family) { - case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade); - case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade); - default -> ItemStack.EMPTY; - }; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java index 9ffa630b6..6c74ea7b6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java @@ -7,9 +7,7 @@ package dan200.computercraft.shared.pocket.recipes; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.impl.PocketUpgrades; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.pocket.items.PocketComputerItem; -import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.CraftingContainer; @@ -31,7 +29,7 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe { @Override public ItemStack getResultItem(RegistryAccess registryAccess) { - return PocketComputerItemFactory.create(-1, null, -1, ComputerFamily.NORMAL, null); + return ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(-1, null, -1, null); } @Override @@ -86,7 +84,7 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe { var computerID = itemComputer.getComputerID(computer); var label = itemComputer.getLabel(computer); var colour = itemComputer.getColour(computer); - return PocketComputerItemFactory.create(computerID, label, colour, family, upgrade); + return itemComputer.create(computerID, label, colour, upgrade); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/ITurtleBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/ITurtleBlockEntity.java deleted file mode 100644 index 2ee127bba..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/ITurtleBlockEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.shared.turtle.blocks; - -import dan200.computercraft.api.turtle.ITurtleAccess; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.shared.computer.blocks.IComputerBlockEntity; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.phys.Vec3; - -import javax.annotation.Nullable; - -public interface ITurtleBlockEntity extends IComputerBlockEntity { - int getColour(); - - @Nullable - ResourceLocation getOverlay(); - - @Nullable - ITurtleUpgrade getUpgrade(TurtleSide side); - - ITurtleAccess getAccess(); - - Vec3 getRenderOffset(float f); - - float getRenderYaw(float f); - - float getToolRenderAngle(TurtleSide side, float f); -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java index 59c3f0c99..7e058698e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java @@ -11,8 +11,7 @@ import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.platform.RegistryEntry; import dan200.computercraft.shared.turtle.core.TurtleBrain; -import dan200.computercraft.shared.turtle.items.ITurtleItem; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.util.BlockEntityHelpers; import dan200.computercraft.shared.util.WaterloggableHelpers; import net.minecraft.core.BlockPos; @@ -126,7 +125,7 @@ public class TurtleBlock extends AbstractComputerBlock implem if (!world.isClientSide && tile instanceof TurtleBlockEntity turtle) { if (entity instanceof Player player) turtle.setOwningPlayer(player.getGameProfile()); - if (stack.getItem() instanceof ITurtleItem item) { + if (stack.getItem() instanceof TurtleItem item) { // Set Upgrades for (var side : TurtleSide.values()) { turtle.getAccess().setUpgrade(side, item.getUpgrade(stack, side)); @@ -157,7 +156,14 @@ public class TurtleBlock extends AbstractComputerBlock implem @Override protected ItemStack getItem(AbstractComputerBlockEntity tile) { - return tile instanceof TurtleBlockEntity turtle ? TurtleItemFactory.create(turtle) : ItemStack.EMPTY; + if (!(tile instanceof TurtleBlockEntity turtle)) return ItemStack.EMPTY; + + var access = turtle.getAccess(); + return TurtleItem.create( + turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(), + access.getUpgrade(TurtleSide.LEFT), access.getUpgrade(TurtleSide.RIGHT), + access.getFuelLevel(), turtle.getOverlay() + ); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java index 9929d4447..e5c9e2f00 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java @@ -39,7 +39,7 @@ import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.Collections; -public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer, ITurtleBlockEntity { +public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer { public static final int INVENTORY_SIZE = 16; public static final int INVENTORY_WIDTH = 4; public static final int INVENTORY_HEIGHT = 4; @@ -189,39 +189,30 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba onTileEntityChange(); } - // ITurtleTile - - @Override public @Nullable ITurtleUpgrade getUpgrade(TurtleSide side) { return brain.getUpgrade(side); } - @Override public int getColour() { return brain.getColour(); } - @Override public @Nullable ResourceLocation getOverlay() { return brain.getOverlay(); } - @Override public ITurtleAccess getAccess() { return brain; } - @Override public Vec3 getRenderOffset(float f) { return brain.getRenderOffset(f); } - @Override public float getRenderYaw(float f) { return brain.getVisualYaw(f); } - @Override public float getToolRenderAngle(TurtleSide side, float f) { return brain.getToolRenderAngle(side, f); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/ITurtleItem.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/ITurtleItem.java deleted file mode 100644 index 5dfb4751a..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/ITurtleItem.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.shared.turtle.items; - -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.shared.common.IColouredItem; -import dan200.computercraft.shared.computer.items.IComputerItem; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; - -import javax.annotation.Nullable; - -public interface ITurtleItem extends IComputerItem, IColouredItem { - @Nullable - ITurtleUpgrade getUpgrade(ItemStack stack, TurtleSide side); - - int getFuelLevel(ItemStack stack); - - @Nullable - ResourceLocation getOverlay(ItemStack stack); -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java index 59620a755..91bc45717 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItem.java @@ -9,6 +9,7 @@ import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.impl.TurtleUpgrades; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.items.AbstractComputerItem; @@ -24,11 +25,25 @@ import javax.annotation.Nullable; import static dan200.computercraft.shared.turtle.core.TurtleBrain.*; -public class TurtleItem extends AbstractComputerItem implements ITurtleItem { +public class TurtleItem extends AbstractComputerItem implements IColouredItem { public TurtleItem(TurtleBlock block, Properties settings) { super(block, settings); } + public static ItemStack create( + int id, @Nullable String label, int colour, ComputerFamily family, + @Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade, + int fuelLevel, @Nullable ResourceLocation overlay + ) { + return switch (family) { + case NORMAL -> + ModRegistry.Items.TURTLE_NORMAL.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay); + case ADVANCED -> + ModRegistry.Items.TURTLE_ADVANCED.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay); + default -> ItemStack.EMPTY; + }; + } + public ItemStack create( int id, @Nullable String label, int colour, @Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade, @@ -99,7 +114,7 @@ public class TurtleItem extends AbstractComputerItem implements ITurtleItem { @Override public ItemStack withFamily(ItemStack stack, ComputerFamily family) { - return TurtleItemFactory.create( + return create( getComputerID(stack), getLabel(stack), getColour(stack), family, getUpgrade(stack, TurtleSide.LEFT), getUpgrade(stack, TurtleSide.RIGHT), @@ -107,7 +122,6 @@ public class TurtleItem extends AbstractComputerItem implements ITurtleItem { ); } - @Override public @Nullable ITurtleUpgrade getUpgrade(ItemStack stack, TurtleSide side) { var tag = stack.getTag(); if (tag == null) return null; @@ -116,13 +130,11 @@ public class TurtleItem extends AbstractComputerItem implements ITurtleItem { return tag.contains(key) ? TurtleUpgrades.instance().get(tag.getString(key)) : null; } - @Override public @Nullable ResourceLocation getOverlay(ItemStack stack) { var tag = stack.getTag(); return tag != null && tag.contains(NBT_OVERLAY) ? new ResourceLocation(tag.getString(NBT_OVERLAY)) : null; } - @Override public int getFuelLevel(ItemStack stack) { var tag = stack.getTag(); return tag != null && tag.contains(NBT_FUEL) ? tag.getInt(NBT_FUEL) : 0; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItemFactory.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItemFactory.java deleted file mode 100644 index 81953a899..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/items/TurtleItemFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. -// -// SPDX-License-Identifier: LicenseRef-CCPL - -package dan200.computercraft.shared.turtle.items; - -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.turtle.blocks.ITurtleBlockEntity; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; - -import javax.annotation.Nullable; - -public final class TurtleItemFactory { - private TurtleItemFactory() { - } - - public static ItemStack create(ITurtleBlockEntity turtle) { - var access = turtle.getAccess(); - - return create( - turtle.getComputerID(), turtle.getLabel(), turtle.getColour(), turtle.getFamily(), - access.getUpgrade(TurtleSide.LEFT), access.getUpgrade(TurtleSide.RIGHT), - access.getFuelLevel(), turtle.getOverlay() - ); - } - - public static ItemStack create( - int id, @Nullable String label, int colour, ComputerFamily family, - @Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade, - int fuelLevel, @Nullable ResourceLocation overlay - ) { - return switch (family) { - case NORMAL -> - ModRegistry.Items.TURTLE_NORMAL.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay); - case ADVANCED -> - ModRegistry.Items.TURTLE_ADVANCED.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay); - default -> ItemStack.EMPTY; - }; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java index 4e6cf5c0b..4076a3662 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleOverlayRecipe.java @@ -9,9 +9,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.turtle.items.ITurtleItem; import dan200.computercraft.shared.turtle.items.TurtleItem; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.network.FriendlyByteBuf; @@ -22,7 +20,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.*; /** - * A {@link ShapelessRecipe} which sets the {@linkplain ITurtleItem#getOverlay(ItemStack)} turtle's overlay} instead. + * A {@link ShapelessRecipe} which sets the {@linkplain TurtleItem#getOverlay(ItemStack)} turtle's overlay} instead. */ public class TurtleOverlayRecipe extends ShapelessRecipe { private final ResourceLocation overlay; @@ -35,12 +33,11 @@ public class TurtleOverlayRecipe extends ShapelessRecipe { } private static ItemStack make(ItemStack stack, ResourceLocation overlay) { - var turtle = (ITurtleItem) stack.getItem(); - return TurtleItemFactory.create( + var turtle = (TurtleItem) stack.getItem(); + return turtle.create( turtle.getComputerID(stack), turtle.getLabel(stack), turtle.getColour(stack), - turtle.getFamily(), turtle.getUpgrade(stack, TurtleSide.LEFT), turtle.getUpgrade(stack, TurtleSide.RIGHT), turtle.getFuelLevel(stack), diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java index 2451c8324..2b786a5db 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java @@ -8,7 +8,7 @@ import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.items.IComputerItem; import dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.turtle.items.TurtleItem; import net.minecraft.core.NonNullList; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; @@ -31,7 +31,7 @@ public final class TurtleRecipe extends ComputerFamilyRecipe { var computerID = item.getComputerID(stack); var label = item.getLabel(stack); - return TurtleItemFactory.create(computerID, label, -1, getFamily(), null, null, 0, null); + return TurtleItem.create(computerID, label, -1, getFamily(), null, null, 0, null); } public static class Serializer extends ComputerFamilyRecipe.Serializer { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java index 690116d56..4dcd9e5a2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java @@ -8,9 +8,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.turtle.items.ITurtleItem; -import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.turtle.items.TurtleItem; import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.CraftingContainer; @@ -32,7 +30,7 @@ public final class TurtleUpgradeRecipe extends CustomRecipe { @Override public ItemStack getResultItem(RegistryAccess registryAccess) { - return TurtleItemFactory.create(-1, null, -1, ComputerFamily.NORMAL, null, null, 0, null); + return ModRegistry.Items.TURTLE_NORMAL.get().create(-1, null, -1, null, null, 0, null); } @Override @@ -58,7 +56,7 @@ public final class TurtleUpgradeRecipe extends CustomRecipe { return ItemStack.EMPTY; } - if (item.getItem() instanceof ITurtleItem) { + if (item.getItem() instanceof TurtleItem) { // Item is a turtle if (turtle.isEmpty()) { turtle = item; @@ -105,8 +103,7 @@ public final class TurtleUpgradeRecipe extends CustomRecipe { // At this point we have a turtle + 1 or 2 items // Get the turtle we already have - var itemTurtle = (ITurtleItem) turtle.getItem(); - var family = itemTurtle.getFamily(); + var itemTurtle = (TurtleItem) turtle.getItem(); var upgrades = new ITurtleUpgrade[]{ itemTurtle.getUpgrade(turtle, TurtleSide.LEFT), itemTurtle.getUpgrade(turtle, TurtleSide.RIGHT), @@ -128,7 +125,7 @@ public final class TurtleUpgradeRecipe extends CustomRecipe { var fuelLevel = itemTurtle.getFuelLevel(turtle); var colour = itemTurtle.getColour(turtle); var overlay = itemTurtle.getOverlay(turtle); - return TurtleItemFactory.create(computerID, label, colour, family, upgrades[0], upgrades[1], fuelLevel, overlay); + return itemTurtle.create(computerID, label, colour, upgrades[0], upgrades[1], fuelLevel, overlay); } @Override diff --git a/projects/core/build.gradle.kts b/projects/core/build.gradle.kts index ba5e8ef9b..fc84c1771 100644 --- a/projects/core/build.gradle.kts +++ b/projects/core/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { implementation(libs.guava) implementation(libs.jzlib) implementation(libs.netty.http) + implementation(libs.netty.socks) + implementation(libs.netty.proxy) implementation(libs.slf4j) implementation(libs.asm) diff --git a/projects/core/src/main/java/dan200/computercraft/core/CoreConfig.java b/projects/core/src/main/java/dan200/computercraft/core/CoreConfig.java index 01dee60b2..46cff11c7 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/CoreConfig.java +++ b/projects/core/src/main/java/dan200/computercraft/core/CoreConfig.java @@ -6,6 +6,7 @@ package dan200.computercraft.core; import dan200.computercraft.core.apis.http.options.Action; import dan200.computercraft.core.apis.http.options.AddressRule; +import dan200.computercraft.core.apis.http.options.ProxyType; import java.util.List; import java.util.OptionalInt; @@ -34,4 +35,9 @@ public final class CoreConfig { public static int httpMaxWebsockets = 4; public static int httpDownloadBandwidth = 32 * 1024 * 1024; public static int httpUploadBandwidth = 32 * 1024 * 1024; + public static ProxyType httpProxyType = ProxyType.HTTP; + public static String httpProxyHost = ""; + public static int httpProxyPort = 8080; + public static String httpProxyUsername = ""; + public static String httpProxyPassword = ""; } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java index 3d0aa167b..517e67790 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -4,6 +4,7 @@ package dan200.computercraft.core.apis.http; +import com.google.common.base.Strings; import dan200.computercraft.core.CoreConfig; import dan200.computercraft.core.apis.http.options.Action; import dan200.computercraft.core.apis.http.options.AddressRule; @@ -17,11 +18,16 @@ import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; +import io.netty.handler.proxy.HttpProxyHandler; +import io.netty.handler.proxy.Socks4ProxyHandler; +import io.netty.handler.proxy.Socks5ProxyHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.traffic.AbstractTrafficShapingHandler; import io.netty.handler.traffic.GlobalTrafficShapingHandler; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +38,7 @@ import java.net.InetSocketAddress; import java.net.URI; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Just a shared object for executing simple HTTP related tasks. @@ -57,8 +64,7 @@ public final class NetworkUtils { private static @Nullable SslContext sslContext; private static boolean triedSslContext = false; - @Nullable - private static SslContext makeSslContext() { + private static @Nullable SslContext makeSslContext() { if (triedSslContext) return sslContext; synchronized (sslLock) { if (triedSslContext) return sslContext; @@ -133,6 +139,66 @@ public final class NetworkUtils { return options; } + /** + * Creates a proxy handler for a specific domain. Returns null if a proxy is not required for this HTTP rule, or + * throws if it is required but is not configured correctly. + *

+ * Note, this may require a DNS lookup, and so should not be executed on the main CC thread. + * + * @param options The options for the host to be proxied. + * @param timeout The timeout for this connection. Currently only used for establishing the SSL initialisation. + * @return A consumer that takes a {@link SocketChannel} and injects the proxy handler.. + * @throws HTTPRequestException If a proxy is required but not configured correctly. + */ + public static @Nullable Consumer getProxyHandler(Options options, int timeout) throws HTTPRequestException { + if (!options.useProxy) return null; + + var type = CoreConfig.httpProxyType; + var host = CoreConfig.httpProxyHost; + var port = CoreConfig.httpProxyPort; + var username = CoreConfig.httpProxyUsername; + var password = CoreConfig.httpProxyPassword; + + if (Strings.isNullOrEmpty(host)) { + throw new HTTPRequestException("Proxy host not configured"); + } + + var proxyAddress = new InetSocketAddress(host, port); + if (proxyAddress.isUnresolved()) throw new HTTPRequestException("Unknown proxy host"); + + return switch (type) { + case HTTP -> ch -> ch.pipeline().addLast(new HttpProxyHandler(proxyAddress, username, password)); + case HTTPS -> { + var sslContext = getSslContext(); + yield ch -> { + var p = ch.pipeline(); + // If we're using an HTTPS proxy, we need to add an SSL handler for the proxy too. + p.addLast(makeSslHandler(ch, sslContext, timeout, host, port)); + p.addLast(new HttpProxyHandler(proxyAddress, username, password)); + }; + } + case SOCKS4 -> ch -> ch.pipeline().addLast(new Socks4ProxyHandler(proxyAddress, username)); + case SOCKS5 -> ch -> ch.pipeline().addLast(new Socks5ProxyHandler(proxyAddress, username, password)); + }; + } + + /** + * Make an SSL handler for the remote host. + * + * @param ch The channel the handler will be added to. + * @param sslContext The SSL context, if present. + * @param timeout The timeout on this channel. + * @param peerHost The host to connect to. + * @param peerPort The port to connect to. + * @return The SSL handler. + * @see io.netty.handler.ssl.SslHandler + */ + private static SslHandler makeSslHandler(SocketChannel ch, @NotNull SslContext sslContext, int timeout, String peerHost, int peerPort) { + var handler = sslContext.newHandler(ch.alloc(), peerHost, peerPort); + if (timeout > 0) handler.setHandshakeTimeoutMillis(timeout); + return handler; + } + /** * Set up some basic properties of the channel. This adds a timeout, the traffic shaping handler, and the SSL * handler. @@ -141,19 +207,20 @@ public final class NetworkUtils { * @param uri The URI to connect to. * @param socketAddress The address of the socket to connect to. * @param sslContext The SSL context, if present. + * @param proxy The proxy handler, if present. * @param timeout The timeout on this channel. * @see io.netty.channel.ChannelInitializer */ - public static void initChannel(SocketChannel ch, URI uri, InetSocketAddress socketAddress, @Nullable SslContext sslContext, int timeout) { + public static void initChannel(SocketChannel ch, URI uri, InetSocketAddress socketAddress, @Nullable SslContext sslContext, @Nullable Consumer proxy, int timeout) { if (timeout > 0) ch.config().setConnectTimeoutMillis(timeout); var p = ch.pipeline(); p.addLast(SHAPING_HANDLER); + if (proxy != null) proxy.accept(ch); + if (sslContext != null) { - var handler = sslContext.newHandler(ch.alloc(), uri.getHost(), socketAddress.getPort()); - if (timeout > 0) handler.setHandshakeTimeoutMillis(timeout); - p.addLast(handler); + p.addLast(makeSslHandler(ch, sslContext, timeout, uri.getHost(), socketAddress.getPort())); } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Action.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Action.java index 7b8483121..ffdcfb3b1 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Action.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Action.java @@ -4,6 +4,7 @@ package dan200.computercraft.core.apis.http.options; +import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; @@ -12,7 +13,7 @@ public enum Action { DENY; private final PartialOptions partial = new PartialOptions( - this, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty() + this, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), Optional.empty() ); public PartialOptions toPartial() { diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Options.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Options.java index 6205e1ded..ddc3cab0a 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Options.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/Options.java @@ -13,11 +13,13 @@ public final class Options { public final long maxUpload; public final long maxDownload; public final int websocketMessage; + public final boolean useProxy; - Options(Action action, long maxUpload, long maxDownload, int websocketMessage) { + Options(Action action, long maxUpload, long maxDownload, int websocketMessage, boolean useProxy) { this.action = action; this.maxUpload = maxUpload; this.maxDownload = maxDownload; this.websocketMessage = websocketMessage; + this.useProxy = useProxy; } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java index 9717f936b..1d4d6824f 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java @@ -7,28 +7,31 @@ package dan200.computercraft.core.apis.http.options; import com.google.errorprone.annotations.Immutable; import javax.annotation.Nullable; +import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; @Immutable public final class PartialOptions { public static final PartialOptions DEFAULT = new PartialOptions( - null, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty() + null, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), Optional.empty() ); private final @Nullable Action action; private final OptionalLong maxUpload; private final OptionalLong maxDownload; private final OptionalInt websocketMessage; + private final Optional useProxy; @SuppressWarnings("Immutable") // Lazily initialised, so this mutation is invisible in the public API private @Nullable Options options; - public PartialOptions(@Nullable Action action, OptionalLong maxUpload, OptionalLong maxDownload, OptionalInt websocketMessage) { + public PartialOptions(@Nullable Action action, OptionalLong maxUpload, OptionalLong maxDownload, OptionalInt websocketMessage, Optional useProxy) { this.action = action; this.maxUpload = maxUpload; this.maxDownload = maxDownload; this.websocketMessage = websocketMessage; + this.useProxy = useProxy; } Options toOptions() { @@ -38,7 +41,8 @@ public final class PartialOptions { action == null ? Action.DENY : action, maxUpload.orElse(AddressRule.MAX_UPLOAD), maxDownload.orElse(AddressRule.MAX_DOWNLOAD), - websocketMessage.orElse(AddressRule.WEBSOCKET_MESSAGE) + websocketMessage.orElse(AddressRule.WEBSOCKET_MESSAGE), + useProxy.orElse(false) ); } @@ -56,7 +60,8 @@ public final class PartialOptions { action == null && other.action != null ? other.action : action, maxUpload.isPresent() ? maxUpload : other.maxUpload, maxDownload.isPresent() ? maxDownload : other.maxDownload, - websocketMessage.isPresent() ? websocketMessage : other.websocketMessage + websocketMessage.isPresent() ? websocketMessage : other.websocketMessage, + useProxy.isPresent() ? useProxy : other.useProxy ); } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/ProxyType.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/ProxyType.java new file mode 100644 index 000000000..cbbfa35a3 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/ProxyType.java @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.apis.http.options; + +/** + * The type of proxy to use for HTTP requests. + * + * @see dan200.computercraft.core.apis.http.NetworkUtils#getProxyHandler(Options, int) + * @see dan200.computercraft.core.CoreConfig#httpProxyType + */ +public enum ProxyType { + HTTP, + HTTPS, + SOCKS4, + SOCKS5 +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java index d7d40c1c0..33fdab50e 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java @@ -124,6 +124,7 @@ public class HttpRequest extends Resource { var socketAddress = NetworkUtils.getAddress(uri, ssl); var options = NetworkUtils.getOptions(uri.getHost(), socketAddress); var sslContext = ssl ? NetworkUtils.getSslContext() : null; + var proxy = NetworkUtils.getProxyHandler(options, timeout); // getAddress may have a slight delay, so let's perform another cancellation check. if (isClosed()) return; @@ -145,7 +146,7 @@ public class HttpRequest extends Resource { .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { - NetworkUtils.initChannel(ch, uri, socketAddress, sslContext, timeout); + NetworkUtils.initChannel(ch, uri, socketAddress, sslContext, proxy, timeout); var p = ch.pipeline(); if (timeout > 0) p.addLast(new ReadTimeoutHandler(timeout, TimeUnit.MILLISECONDS)); diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java index 282b04c79..3dfb3fd8e 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java @@ -117,6 +117,7 @@ public class Websocket extends Resource { var socketAddress = NetworkUtils.getAddress(uri, ssl); var options = NetworkUtils.getOptions(uri.getHost(), socketAddress); var sslContext = ssl ? NetworkUtils.getSslContext() : null; + var proxy = NetworkUtils.getProxyHandler(options, timeout); // getAddress may have a slight delay, so let's perform another cancellation check. if (isClosed()) return; @@ -127,7 +128,7 @@ public class Websocket extends Resource { .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { - NetworkUtils.initChannel(ch, uri, socketAddress, sslContext, timeout); + NetworkUtils.initChannel(ch, uri, socketAddress, sslContext, proxy, timeout); var subprotocol = headers.get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); var handshaker = new NoOriginWebSocketHandshaker( diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/colors.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/colors.lua index 249c69f6f..5bdbf9fd2 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/colors.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/colors.lua @@ -273,7 +273,7 @@ end --- Combine a three-colour RGB value into one hexadecimal representation. -- -- @tparam number r The red channel, should be between 0 and 1. --- @tparam number g The red channel, should be between 0 and 1. +-- @tparam number g The green channel, should be between 0 and 1. -- @tparam number b The blue channel, should be between 0 and 1. -- @treturn number The combined hexadecimal colour. -- @usage @@ -296,7 +296,7 @@ end -- -- @tparam number rgb The combined hexadecimal colour. -- @treturn number The red channel, will be between 0 and 1. --- @treturn number The red channel, will be between 0 and 1. +-- @treturn number The green channel, will be between 0 and 1. -- @treturn number The blue channel, will be between 0 and 1. -- @usage -- ```lua diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index f9e8fb6b0..576bac769 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -54,6 +54,8 @@ dependencies { include(libs.cobalt) include(libs.jzlib) include(libs.netty.http) + include(libs.netty.socks) + include(libs.netty.proxy) include(libs.nightConfig.core) include(libs.nightConfig.toml) @@ -65,6 +67,8 @@ dependencies { // in our POM, and this is the easiest way. runtimeOnly(libs.cobalt) runtimeOnly(libs.netty.http) + runtimeOnly(libs.netty.socks) + runtimeOnly(libs.netty.proxy) annotationProcessorEverywhere(libs.autoService) diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java index 1115f9b95..1294e3428 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricConfigFile.java @@ -34,11 +34,11 @@ public class FabricConfigFile implements ConfigFile { private final ConfigSpec spec; private final Trie entries; - private final Runnable onChange; + private final ConfigListener onChange; private @Nullable CommentedFileConfig config; - public FabricConfigFile(ConfigSpec spec, Trie entries, Runnable onChange) { + public FabricConfigFile(ConfigSpec spec, Trie entries, ConfigListener onChange) { this.spec = spec; this.entries = entries; this.onChange = onChange; @@ -95,7 +95,7 @@ public class FabricConfigFile implements ConfigFile { LOG.warn("Incorrect key {} was corrected from {} to {}", String.join(".", entryPath), oldValue, newValue); }); - onChange.run(); + onChange.onConfigChanged(config.getNioPath()); return corrected > 0; } @@ -204,7 +204,7 @@ public class FabricConfigFile implements ConfigFile { } @Override - public ConfigFile build(Runnable onChange) { + public ConfigFile build(ConfigListener onChange) { return new FabricConfigFile(spec, entries, onChange); } } diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index bb32e08fe..589e44f0d 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -147,6 +147,14 @@ dependencies { jarJar.ranged(this, "[${libs.versions.netty.get()},)") isTransitive = false } + minecraftEmbed(libs.netty.socks) { + jarJar.ranged(this, "[${libs.versions.netty.get()},)") + isTransitive = false + } + minecraftEmbed(libs.netty.proxy) { + jarJar.ranged(this, "[${libs.versions.netty.get()},)") + isTransitive = false + } testFixturesApi(libs.bundles.test) testFixturesApi(libs.bundles.kotlin) diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java index fded03ddb..b47f39772 100644 --- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java @@ -4,6 +4,7 @@ package dan200.computercraft; +import com.electronwill.nightconfig.core.file.FileConfig; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ForgeComputerCraftAPI; import dan200.computercraft.api.detail.ForgeDetailRegistries; @@ -94,10 +95,12 @@ public final class ComputerCraft { private static void syncConfig(ModConfig config) { if (!config.getModId().equals(ComputerCraftAPI.MOD_ID)) return; + var path = config.getConfigData() instanceof FileConfig fileConfig ? fileConfig.getNioPath() : null; + if (config.getType() == ModConfig.Type.SERVER && ((ForgeConfigFile) ConfigSpec.serverSpec).spec().isLoaded()) { - ConfigSpec.syncServer(); + ConfigSpec.syncServer(path); } else if (config.getType() == ModConfig.Type.CLIENT) { - ConfigSpec.syncClient(); + ConfigSpec.syncClient(path); } } } diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java index eba7500ce..2f44b2e73 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/ForgeConfigFile.java @@ -117,7 +117,7 @@ public final class ForgeConfigFile implements ConfigFile { } @Override - public ConfigFile build(Runnable onChange) { + public ConfigFile build(ConfigListener onChange) { var spec = builder.build(); entries.stream().forEach(x -> { if (x instanceof ValueImpl value) value.owner = spec;