diff --git a/.reuse/dep5 b/.reuse/dep5 index 451e288c7..640c0ff75 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -6,7 +6,7 @@ Upstream-Contact: Jonathan Coates Files: projects/common/src/main/resources/assets/computercraft/sounds.json projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg - projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/* + projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/* projects/common/src/testMod/resources/data/cctest/structures/* projects/*/src/generated/* projects/web/src/htmlTransform/export/index.json diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java index 6a170d1d6..ede3e7675 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java @@ -5,7 +5,7 @@ package dan200.computercraft.api.client.turtle; import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; /** * A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades. @@ -18,9 +18,9 @@ public interface RegisterTurtleUpgradeModeller { /** * Register a {@link TurtleUpgradeModeller}. * - * @param serialiser The turtle upgrade serialiser. - * @param modeller The upgrade modeller. - * @param The type of the turtle upgrade. + * @param type The turtle upgrade type. + * @param modeller The upgrade modeller. + * @param The type of the turtle upgrade. */ - void register(UpgradeSerialiser serialiser, TurtleUpgradeModeller modeller); + void register(UpgradeType type, TurtleUpgradeModeller modeller); } diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java index c168bd946..d18891dbe 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java @@ -4,8 +4,6 @@ package dan200.computercraft.api.pocket; -import dan200.computercraft.api.upgrades.UpgradeBase; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; @@ -15,25 +13,14 @@ import net.minecraft.world.item.ItemStack; * One does not have to use this, but it does provide a convenient template. */ public abstract class AbstractPocketUpgrade implements IPocketUpgrade { - private final ResourceLocation id; private final String adjective; private final ItemStack stack; - protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) { - this.id = id; + protected AbstractPocketUpgrade(String adjective, ItemStack stack) { this.adjective = adjective; this.stack = stack; } - protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) { - this(id, UpgradeBase.getDefaultAdjective(id), stack); - } - - @Override - public final ResourceLocation getUpgradeID() { - return id; - } - @Override public final String getUnlocalisedAdjective() { return adjective; diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java index 0f4d5be86..fe26f320d 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java @@ -6,7 +6,7 @@ package dan200.computercraft.api.pocket; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.upgrades.UpgradeBase; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.impl.ComputerCraftAPIService; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; @@ -18,7 +18,7 @@ import javax.annotation.Nullable; * A peripheral which can be equipped to the back side of a pocket computer. *

* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding - * {@link UpgradeSerialiser} instance, which are then registered in a registry. + * {@link UpgradeType} instance, which are then registered in a registry. *

* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and * the upgrade automatically registered. It is recommended this is done via {@linkplain PocketUpgradeDataProvider data @@ -26,15 +26,15 @@ import javax.annotation.Nullable; * *

Example

*
{@code
- * // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
- * static final DeferredRegister> SERIALISERS = DeferredRegister.create(IPocketUpgrade.serialiserRegistryKey(), "my_mod");
+ * // We use Forge's DeferredRegister to register our upgrade type. Fabric mods may register their type directly.
+ * static final DeferredRegister> POCKET_UPGRADES = DeferredRegister.create(IPocketUpgrade.typeRegistry(), "my_mod");
  *
- * // Register a new upgrade serialiser called "my_upgrade".
- * public static final RegistryObject> MY_UPGRADE =
- *     SERIALISERS.register("my_upgrade", () -> UpgradeSerialiser.simple(MyUpgrade::new));
+ * // Register a new upgrade upgrade type called "my_upgrade".
+ * public static final RegistryObject> MY_UPGRADE =
+ *     POCKET_UPGRADES.register("my_upgrade", () -> UpgradeType.simple(new MyUpgrade()));
  *
  * // Then in your constructor
- * SERIALISERS.register(bus);
+ * POCKET_UPGRADES.register(bus);
  * }
*

* We can then define a new upgrade using JSON by placing the following in @@ -49,14 +49,22 @@ import javax.annotation.Nullable; */ public interface IPocketUpgrade extends UpgradeBase { /** - * The registry key for upgrade serialisers. + * The registry key for pocket upgrade types. * * @return The registry key. */ - static ResourceKey>> serialiserRegistryKey() { + static ResourceKey>> typeRegistry() { return ComputerCraftAPIService.get().pocketUpgradeRegistryId(); } + /** + * Get the type of this upgrade. + * + * @return The type of this upgrade. + */ + @Override + UpgradeType getType(); + /** * Creates a peripheral for the pocket computer. *

diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/PocketUpgradeDataProvider.java b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/PocketUpgradeDataProvider.java index 80a2ebc21..e9685fb8e 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/PocketUpgradeDataProvider.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/PocketUpgradeDataProvider.java @@ -5,7 +5,9 @@ package dan200.computercraft.api.pocket; import dan200.computercraft.api.upgrades.UpgradeDataProvider; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.impl.ComputerCraftAPIService; +import dan200.computercraft.impl.RegistryHelper; import net.minecraft.data.DataGenerator; import net.minecraft.data.PackOutput; @@ -19,10 +21,10 @@ import java.util.function.Consumer; * generate them. * * @see IPocketUpgrade - * @see UpgradeSerialiser + * @see UpgradeType */ public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider { public PocketUpgradeDataProvider(PackOutput output) { - super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey()); + super(output, "Pocket Computer Upgrades", RegistryHelper.POCKET_UPGRADE, ComputerCraftAPIService.get().pocketUpgradeCodec()); } } diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/AbstractTurtleUpgrade.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/AbstractTurtleUpgrade.java index bede70fa3..8ee5a65dd 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/AbstractTurtleUpgrade.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/AbstractTurtleUpgrade.java @@ -4,8 +4,6 @@ package dan200.computercraft.api.turtle; -import dan200.computercraft.api.upgrades.UpgradeBase; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; @@ -15,34 +13,23 @@ import net.minecraft.world.item.ItemStack; * One does not have to use this, but it does provide a convenient template. */ public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade { - private final ResourceLocation id; private final TurtleUpgradeType type; private final String adjective; private final ItemStack stack; - protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) { - this.id = id; + protected AbstractTurtleUpgrade(TurtleUpgradeType type, String adjective, ItemStack stack) { this.type = type; this.adjective = adjective; this.stack = stack; } - protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) { - this(id, type, UpgradeBase.getDefaultAdjective(id), stack); - } - - @Override - public final ResourceLocation getUpgradeID() { - return id; - } - @Override public final String getUnlocalisedAdjective() { return adjective; } @Override - public final TurtleUpgradeType getType() { + public final TurtleUpgradeType getUpgradeType() { return type; } diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java index 9dbf8ab0c..08c54ca29 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java @@ -6,7 +6,7 @@ package dan200.computercraft.api.turtle; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.upgrades.UpgradeBase; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.impl.ComputerCraftAPIService; import net.minecraft.core.Direction; import net.minecraft.core.Registry; @@ -20,7 +20,7 @@ import javax.annotation.Nullable; * peripheral. *

* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding - * {@link UpgradeSerialiser} instance, which are then registered in a registry. + * {@link UpgradeType} instance, which are then registered in a registry. *

* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and * the upgrade automatically registered. It is recommended this is done via {@linkplain TurtleUpgradeDataProvider data @@ -28,15 +28,15 @@ import javax.annotation.Nullable; * *

Example

*
{@code
- * // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
- * static final DeferredRegister> SERIALISERS = DeferredRegister.create(ITurtleUpgrade.serialiserRegistryKey(), "my_mod");
+ * // We use Forge's DeferredRegister to register our upgrade type. Fabric mods may register their type directly.
+ * static final DeferredRegister> TURTLE_UPGRADES = DeferredRegister.create(ITurtleUpgrade.typeRegistry(), "my_mod");
  *
- * // Register a new upgrade serialiser called "my_upgrade".
- * public static final RegistryObject> MY_UPGRADE =
- *     SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
+ * // Register a new upgrade type called "my_upgrade".
+ * public static final RegistryObject> MY_UPGRADE =
+ *     TURTLE_UPGRADES.register("my_upgrade", () -> UpgradeType.simple(MyUpgrade::new));
  *
  * // Then in your constructor
- * SERIALISERS.register( bus );
+ * TURTLE_UPGRADES.register(bus);
  * }
*

* We can then define a new upgrade using JSON by placing the following in @@ -44,7 +44,7 @@ import javax.annotation.Nullable; * *

{@code
  * {
- *     "type": my_mod:my_upgrade",
+ *     "type": "my_mod:my_upgrade"
  * }
  * }
*

@@ -60,21 +60,29 @@ import javax.annotation.Nullable; */ public interface ITurtleUpgrade extends UpgradeBase { /** - * The registry key for upgrade serialisers. + * The registry key for turtle upgrade types. * * @return The registry key. */ - static ResourceKey>> serialiserRegistryKey() { + static ResourceKey>> typeRegistry() { return ComputerCraftAPIService.get().turtleUpgradeRegistryId(); } + /** + * Get the type of this upgrade. + * + * @return The type of this upgrade. + */ + @Override + UpgradeType getType(); + /** * Return whether this turtle adds a tool or a peripheral to the turtle. * * @return The type of upgrade this is. * @see TurtleUpgradeType for the differences between them. */ - TurtleUpgradeType getType(); + TurtleUpgradeType getUpgradeType(); /** * Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade. diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java index 2b11a1fde..e227a3fef 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java @@ -4,13 +4,13 @@ package dan200.computercraft.api.turtle; -import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftTags; +import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeDataProvider; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.impl.ComputerCraftAPIService; import dan200.computercraft.impl.RegistryHelper; +import dan200.computercraft.impl.upgrades.TurtleToolSpec; import net.minecraft.core.component.DataComponents; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.DataGenerator; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceLocation; @@ -21,6 +21,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import javax.annotation.Nullable; +import java.util.Optional; import java.util.function.Consumer; /** @@ -33,10 +34,8 @@ import java.util.function.Consumer; * @see ITurtleUpgrade */ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider { - private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool"); - public TurtleUpgradeDataProvider(PackOutput output) { - super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey()); + super(output, "Turtle Upgrades", RegistryHelper.TURTLE_UPGRADE, ComputerCraftAPIService.get().turtleUpgradeCodec()); } /** @@ -48,7 +47,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider serialiser; private final Item toolItem; - private @Nullable String adjective; + private String adjective; private @Nullable Item craftingItem; - private @Nullable Float damageMultiplier = null; + private float damageMultiplier = TurtleToolSpec.DEFAULT_DAMAGE_MULTIPLIER; private @Nullable TagKey breakable; private boolean allowEnchantments = false; private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER; - ToolBuilder(ResourceLocation id, UpgradeSerialiser serialiser, Item toolItem) { + ToolBuilder(ResourceLocation id, Item toolItem) { this.id = id; - this.serialiser = serialiser; + adjective = UpgradeBase.getDefaultAdjective(id); this.toolItem = toolItem; craftingItem = null; } @@ -150,20 +148,16 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider>> add) { - add.accept(new Upgrade<>(id, serialiser, s -> { - s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, toolItem).toString()); - if (adjective != null) s.addProperty("adjective", adjective); - if (craftingItem != null) { - s.addProperty("craftItem", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, craftingItem).toString()); - } - if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier); - if (breakable != null) s.addProperty("breakable", breakable.location().toString()); - if (allowEnchantments) s.addProperty("allowEnchantments", true); - if (consumeDurability != TurtleToolDurability.NEVER) { - s.addProperty("consumeDurability", consumeDurability.getSerializedName()); - } - })); + public void add(Consumer> add) { + upgrade(id, ComputerCraftAPIService.get().createTurtleTool(new TurtleToolSpec( + adjective, + Optional.ofNullable(craftingItem), + toolItem, + damageMultiplier, + allowEnchantments, + consumeDurability, + Optional.ofNullable(breakable) + ))).add(add); } } } diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeBase.java b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeBase.java index 2850fea15..654303d19 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeBase.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeBase.java @@ -19,15 +19,11 @@ import net.minecraft.world.item.ItemStack; */ public interface UpgradeBase { /** - * Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" - * or "my_mod:my_upgrade". - *

- * You should use a unique resource domain to ensure this upgrade is uniquely identified. - * The upgrade will fail registration if an already used ID is specified. + * Get the type of this upgrade. * - * @return The unique ID for this upgrade. + * @return The type of this upgrade. */ - ResourceLocation getUpgradeID(); + UpgradeType getType(); /** * Return an unlocalised string to describe this type of computer in item names. diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeData.java b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeData.java index e82c4643d..a2f622df4 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeData.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeData.java @@ -6,38 +6,53 @@ package dan200.computercraft.api.upgrades; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.world.item.ItemStack; /** * An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data. * - * @param upgrade The current upgrade. - * @param data The upgrade's data. - * @param The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}. + * @param holder The current upgrade holder. + * @param data The upgrade's data. + * @param The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}. */ -public record UpgradeData(T upgrade, DataComponentPatch data) { +public record UpgradeData(Holder.Reference holder, DataComponentPatch data) { /** * A utility method to construct a new {@link UpgradeData} instance. * - * @param upgrade An upgrade. - * @param data The upgrade's data. - * @param The type of upgrade. + * @param holder An upgrade. + * @param data The upgrade's data. + * @param The type of upgrade. * @return The new {@link UpgradeData} instance. */ - public static UpgradeData of(T upgrade, DataComponentPatch data) { - return new UpgradeData<>(upgrade, data); + public static UpgradeData of(Holder.Reference holder, DataComponentPatch data) { + return new UpgradeData<>(holder, data); } /** * Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade. * - * @param upgrade The upgrade instance. - * @param The type of upgrade. + * @param holder The upgrade instance. + * @param The type of upgrade. * @return The default upgrade data. */ - public static UpgradeData ofDefault(T upgrade) { - return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem())); + public static UpgradeData ofDefault(Holder.Reference holder) { + var upgrade = holder.value(); + return of(holder, upgrade.getUpgradeData(upgrade.getCraftingItem())); + } + + public UpgradeData { + if (!holder.isBound()) throw new IllegalArgumentException("Holder is not bound"); + } + + /** + * Get the current upgrade. + * + * @return The current upgrade. + */ + public T upgrade() { + return holder().value(); } /** @@ -49,6 +64,6 @@ public record UpgradeData(T upgrade, DataComponentPatch d * @return This upgrade's item. */ public ItemStack getUpgradeItem() { - return upgrade.getUpgradeItem(data).copy(); + return upgrade().getUpgradeItem(data).copy(); } } diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeDataProvider.java b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeDataProvider.java index 0d8804837..685d5e0e8 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeDataProvider.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeDataProvider.java @@ -5,27 +5,22 @@ package dan200.computercraft.api.upgrades; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; import dan200.computercraft.impl.PlatformHelper; -import dan200.computercraft.impl.RegistryHelper; -import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem; -import dan200.computercraft.impl.upgrades.SimpleSerialiser; import net.minecraft.Util; import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.CachedOutput; import net.minecraft.data.DataProvider; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import java.util.function.Function; /** * A data generator/provider for turtle and pocket computer upgrades. This should not be extended directly, instead see @@ -36,92 +31,66 @@ import java.util.function.Function; public abstract class UpgradeDataProvider implements DataProvider { private final PackOutput output; private final String name; - private final String folder; - private final Registry> registry; + private final ResourceKey> registryName; + private final Codec codec; - private @Nullable List upgrades; + private @Nullable Map, T> upgrades; @ApiStatus.Internal - protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey>> registry) { + protected UpgradeDataProvider(PackOutput output, String name, ResourceKey> registryName, Codec codec) { this.output = output; this.name = name; - this.folder = folder; - this.registry = RegistryHelper.getRegistry(registry); + this.registryName = registryName; + this.codec = codec; } /** - * Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) "simple" serialiser}. + * Add a new upgrade. * - * @param id The ID of the upgrade to create. - * @param serialiser The simple serialiser. + * @param id The ID of the upgrade to create. + * @param upgrade The upgrade to add. * @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer. */ - public final Upgrade> simple(ResourceLocation id, UpgradeSerialiser serialiser) { - if (!(serialiser instanceof SimpleSerialiser)) { - throw new IllegalStateException(serialiser + " must be a simple() seriaiser."); - } - - return new Upgrade<>(id, serialiser, s -> { + protected final Upgrade upgrade(ResourceLocation id, T upgrade) { + return new Upgrade<>(id, upgrade, j -> { }); } - /** - * Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) simple serialiser}. - * - * @param id The ID of the upgrade to create. - * @param serialiser The simple serialiser. - * @param item The crafting upgrade for this item. - * @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer. - */ - public final Upgrade> simpleWithCustomItem(ResourceLocation id, UpgradeSerialiser serialiser, Item item) { - if (!(serialiser instanceof SerialiserWithCraftingItem)) { - throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser."); - } - - return new Upgrade<>(id, serialiser, s -> - s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, item).toString()) - ); - } - /** * Add all turtle or pocket computer upgrades. *

* Example usage: *

{@code
-     * protected void addUpgrades(Consumer>> addUpgrade) {
-     *     simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
+     * protected void addUpgrades(Consumer> addUpgrade) {
+     *     upgrade(new ResourceLocation("mymod", "speaker"), new TurtleSpeaker(new ItemStack(Items.NOTE_BLOCK))).add(addUpgrade);
      * }
      * }
* * @param addUpgrade A callback used to register an upgrade. */ - protected abstract void addUpgrades(Consumer>> addUpgrade); + protected abstract void addUpgrades(Consumer> addUpgrade); @Override public CompletableFuture run(CachedOutput cache) { - var base = output.getOutputFolder().resolve("data"); + var base = output.createPathProvider(PackOutput.Target.DATA_PACK, registryName.location().getNamespace() + "/" + registryName.location().getPath()); + + Map, T> upgrades = new LinkedHashMap<>(); - Set seen = new HashSet<>(); - List upgrades = new ArrayList<>(); List> futures = new ArrayList<>(); addUpgrades(upgrade -> { - if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id()); + var id = ResourceKey.create(registryName, upgrade.id); + if (upgrades.containsKey(id)) throw new IllegalStateException("Duplicate upgrade " + upgrade.id); - var json = new JsonObject(); - json.addProperty("type", RegistryHelper.getKeyOrThrow(registry, upgrade.serialiser()).toString()); - upgrade.serialise().accept(json); + var json = (JsonObject) codec.encodeStart(JsonOps.INSTANCE, upgrade.upgrade).getOrThrow(); + upgrade.serialise.accept(json); - futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json"))); + futures.add(DataProvider.saveStable(cache, json, base.json(upgrade.id))); - try { - var result = upgrade.serialiser().fromJson(upgrade.id(), json); - upgrades.add(result); - } catch (IllegalArgumentException | JsonParseException e) { - LOGGER.error("Failed to parse {} {}", name, upgrade.id(), e); - } + upgrades.put(id, upgrade.upgrade); }); - this.upgrades = Collections.unmodifiableList(upgrades); + this.upgrades = Collections.unmodifiableMap(upgrades); + return Util.sequenceFailFast(futures); } @@ -130,34 +99,39 @@ public abstract class UpgradeDataProvider implements Data return name; } - public final UpgradeSerialiser existingSerialiser(ResourceLocation id) { - var result = registry.get(id); - if (result == null) throw new IllegalArgumentException("No such serialiser " + id); - return result; - } - - public List getGeneratedUpgrades() { - if (upgrades == null) throw new IllegalStateException("Upgrades have not been generated yet"); + /** + * Get all registered upgrades. + * + * @return The map of registered upgrades. + */ + public Map, T> getGeneratedUpgrades() { + var upgrades = this.upgrades; + if (upgrades == null) throw new IllegalStateException("Upgrades are not available yet"); return upgrades; } /** * A constructed upgrade instance, produced {@link #addUpgrades(Consumer)}. * - * @param id The ID for this upgrade. - * @param serialiser The serialiser which reads and writes this upgrade. - * @param serialise Augment the generated JSON with additional fields. - * @param The type of upgrade serialiser. + * @param The type of upgrade. */ - public record Upgrade>( - ResourceLocation id, R serialiser, Consumer serialise - ) { + public static final class Upgrade { + private final ResourceLocation id; + private final T upgrade; + private final Consumer serialise; + + private Upgrade(ResourceLocation id, T upgrade, Consumer serialise) { + this.id = id; + this.upgrade = upgrade; + this.serialise = serialise; + } + /** * Convenience method for registering an upgrade. * * @param add The callback given to {@link #addUpgrades(Consumer)} */ - public void add(Consumer> add) { + public void add(Consumer> add) { add.accept(this); } @@ -170,8 +144,8 @@ public abstract class UpgradeDataProvider implements Data * @param modId The id of the mod. * @return A new upgrade instance. */ - public Upgrade requireMod(String modId) { - return new Upgrade<>(id, serialiser, json -> { + public Upgrade requireMod(String modId) { + return new Upgrade<>(id, upgrade, json -> { PlatformHelper.get().addRequiredModCondition(json, modId); serialise.accept(json); }); diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeSerialiser.java b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeSerialiser.java deleted file mode 100644 index 0b5aef824..000000000 --- a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeSerialiser.java +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.api.upgrades; - -import com.google.gson.JsonObject; -import dan200.computercraft.api.pocket.IPocketUpgrade; -import dan200.computercraft.api.pocket.PocketUpgradeDataProvider; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider; -import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem; -import dan200.computercraft.impl.upgrades.SimpleSerialiser; -import net.minecraft.core.Registry; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer; - -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * A serialiser for {@link ITurtleUpgrade} or {@link IPocketUpgrade}s. - *

- * These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s. - *

- * This interface is very similar to {@link RecipeSerializer}; each serialiser should correspond to a specific upgrade - * class. Upgrades are then read from JSON files in datapacks, allowing multiple instances of the upgrade to be - * registered. - *

- * If your upgrade doesn't have any associated configurable parameters (like most upgrades), you can use - * {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser. - *

- * Upgrades may be data generated via a {@link UpgradeDataProvider} (see {@link TurtleUpgradeDataProvider} and - * {@link PocketUpgradeDataProvider}). - * - * @param The upgrade that this class can serialise and deserialise. - * @see ITurtleUpgrade - * @see IPocketUpgrade - */ -public interface UpgradeSerialiser { - /** - * Read this upgrade from a JSON file in a datapack. - * - * @param id The ID of this upgrade. - * @param object The JSON object to load this upgrade from. - * @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}. - * @see net.minecraft.util.GsonHelper For additional JSON helper methods. - */ - T fromJson(ResourceLocation id, JsonObject object); - - /** - * Read this upgrade from a network packet, sent from the server. - * - * @param id The ID of this upgrade. - * @param buffer The buffer object to read this upgrade from. - * @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}. - */ - T fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer); - - /** - * Write this upgrade to a network packet, to be sent to the client. - * - * @param buffer The buffer object to write this upgrade to - * @param upgrade The upgrade to write. - */ - void toNetwork(RegistryFriendlyByteBuf buffer, T upgrade); - - /** - * Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer}, - * but for upgrades. - *

- * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead. - * - * @param factory Generate a new upgrade with a specific ID. - * @param The type of the generated upgrade. - * @return The serialiser for this upgrade - */ - static UpgradeSerialiser simple(Function factory) { - return new SimpleSerialiser<>(factory); - } - - /** - * Create an upgrade serialiser for a simple upgrade whose crafting item can be specified. - * - * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's - * {@link UpgradeBase#getCraftingItem()} MUST equal the provided item. - * @param The type of the generated upgrade. - * @return The serialiser for this upgrade. - * @see #simple(Function) For upgrades whose crafting stack should not vary. - */ - static UpgradeSerialiser simpleWithCustomItem(BiFunction factory) { - return new SerialiserWithCraftingItem<>(factory); - } -} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeType.java b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeType.java new file mode 100644 index 000000000..561255690 --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/api/upgrades/UpgradeType.java @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.upgrades; + +import com.mojang.serialization.MapCodec; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.api.pocket.PocketUpgradeDataProvider; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider; +import dan200.computercraft.impl.upgrades.UpgradeTypeImpl; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; + +import java.util.function.Function; + +/** + * The type of a {@linkplain ITurtleUpgrade turtle} or {@linkplain IPocketUpgrade pocket} upgrade. + *

+ * Turtle and pocket computer upgrades are registered using Minecraft's dynamic registry system. As a result, they + * follow a similar design to other dynamic content, such as {@linkplain Recipe recipes} or {@link LootItemFunction + * loot functions}. + *

+ * First, one adds a new class implementing {@link ITurtleUpgrade} or {@link IPocketUpgrade}). This is responsible for + * handling all the logic of your upgrade. + *

+ * However, the upgrades are not registered directly. Instead, each upgrade class should have a corresponding + * {@link UpgradeType}, which is responsible for loading the upgrade from a datapack. The upgrade type should then be + * registered in its appropriate registry ({@link ITurtleUpgrade#typeRegistry()}, + * {@link IPocketUpgrade#typeRegistry()}). + *

+ * In order to register the actual upgrade, a JSON file referencing your upgrade type should be added to a datapack. It + * is recommended to do this via the data generators (see {@link TurtleUpgradeDataProvider} and + * {@link PocketUpgradeDataProvider}). + * + * @param The upgrade subclass that this upgrade type represents. + * @see ITurtleUpgrade + * @see IPocketUpgrade + */ +public interface UpgradeType { + /** + * The codec to read and write this upgrade from a datapack. + * + * @return The codec for this upgrade. + */ + MapCodec codec(); + + /** + * Create a new upgrade type. + * + * @param codec The codec + * @param The type of the generated upgrade. + * @return The newly created upgrade type. + */ + static UpgradeType create(MapCodec codec) { + return new UpgradeTypeImpl<>(codec); + } + + /** + * Create an upgrade type for an upgrade that takes no arguments. + *

+ * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(Function)} instead. + * + * @param instance Generate a new upgrade with a specific ID. + * @param The type of the generated upgrade. + * @return A new upgrade type. + */ + static UpgradeType simple(T instance) { + return create(MapCodec.unit(instance)); + } + + /** + * Create an upgrade type for a simple upgrade whose crafting item can be specified. + * + * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's + * {@link UpgradeBase#getCraftingItem()} MUST equal the provided item. + * @param The type of the generated upgrade. + * @return A new upgrade type. + * @see #simple(UpgradeBase) For upgrades whose crafting stack should not vary. + */ + static UpgradeType simpleWithCustomItem(Function factory) { + return create(BuiltInRegistries.ITEM.byNameCodec() + .xmap(x -> factory.apply(new ItemStack(x)), x -> x.getCraftingItem().getItem()) + .fieldOf("item")); + } +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/impl/ComputerCraftAPIService.java b/projects/common-api/src/main/java/dan200/computercraft/impl/ComputerCraftAPIService.java index 597bb51c7..a945108a6 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/impl/ComputerCraftAPIService.java +++ b/projects/common-api/src/main/java/dan200/computercraft/impl/ComputerCraftAPIService.java @@ -4,6 +4,7 @@ package dan200.computercraft.impl; +import com.mojang.serialization.Codec; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.detail.BlockReference; import dan200.computercraft.api.detail.DetailRegistry; @@ -19,7 +20,8 @@ import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.redstone.BundledRedstoneProvider; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleRefuelHandler; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.impl.upgrades.TurtleToolSpec; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Registry; @@ -68,9 +70,15 @@ public interface ComputerCraftAPIService { void registerRefuelHandler(TurtleRefuelHandler handler); - ResourceKey>> turtleUpgradeRegistryId(); + ResourceKey>> turtleUpgradeRegistryId(); - ResourceKey>> pocketUpgradeRegistryId(); + Codec turtleUpgradeCodec(); + + ResourceKey>> pocketUpgradeRegistryId(); + + ITurtleUpgrade createTurtleTool(TurtleToolSpec spec); + + Codec pocketUpgradeCodec(); DetailRegistry getItemStackDetailRegistry(); diff --git a/projects/common-api/src/main/java/dan200/computercraft/impl/RegistryHelper.java b/projects/common-api/src/main/java/dan200/computercraft/impl/RegistryHelper.java index 0c1c02ae7..8d64b1ca7 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/impl/RegistryHelper.java +++ b/projects/common-api/src/main/java/dan200/computercraft/impl/RegistryHelper.java @@ -4,6 +4,9 @@ package dan200.computercraft.impl; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.api.turtle.ITurtleUpgrade; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceKey; @@ -15,6 +18,9 @@ import org.jetbrains.annotations.ApiStatus; */ @ApiStatus.Internal public final class RegistryHelper { + public static final ResourceKey> TURTLE_UPGRADE = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade")); + public static final ResourceKey> POCKET_UPGRADE = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade")); + private RegistryHelper() { } diff --git a/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/SerialiserWithCraftingItem.java b/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/SerialiserWithCraftingItem.java deleted file mode 100644 index a78008936..000000000 --- a/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/SerialiserWithCraftingItem.java +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.impl.upgrades; - -import com.google.gson.JsonObject; -import dan200.computercraft.api.upgrades.UpgradeBase; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.ApiStatus; - -import java.util.function.BiFunction; - -/** - * Simple serialiser which returns a constant upgrade with a custom crafting item. - *

- * Do NOT directly reference this class. It exists for internal use by the API. - * - * @param The upgrade that this class can serialise and deserialise. - */ -@ApiStatus.Internal -public final class SerialiserWithCraftingItem implements UpgradeSerialiser { - private final BiFunction factory; - - public SerialiserWithCraftingItem(BiFunction factory) { - this.factory = factory; - } - - @Override - public T fromJson(ResourceLocation id, JsonObject object) { - var item = GsonHelper.getAsItem(object, "item"); - return factory.apply(id, new ItemStack(item)); - } - - @Override - public T fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) { - var item = ItemStack.STREAM_CODEC.decode(buffer); - return factory.apply(id, item); - } - - @Override - public void toNetwork(RegistryFriendlyByteBuf buffer, T upgrade) { - ItemStack.STREAM_CODEC.encode(buffer, upgrade.getCraftingItem()); - } -} diff --git a/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/SimpleSerialiser.java b/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/SimpleSerialiser.java deleted file mode 100644 index fb01bc435..000000000 --- a/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/SimpleSerialiser.java +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.impl.upgrades; - -import com.google.gson.JsonObject; -import dan200.computercraft.api.upgrades.UpgradeBase; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.ApiStatus; - -import java.util.function.Function; - -/** - * Simple serialiser which returns a constant upgrade. - *

- * Do NOT directly reference this class. It exists for internal use by the API. - * - * @param The upgrade that this class can serialise and deserialise. - */ -@ApiStatus.Internal -public final class SimpleSerialiser implements UpgradeSerialiser { - private final Function constructor; - - public SimpleSerialiser(Function constructor) { - this.constructor = constructor; - } - - @Override - public T fromJson(ResourceLocation id, JsonObject object) { - return constructor.apply(id); - } - - @Override - public T fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) { - return constructor.apply(id); - } - - @Override - public void toNetwork(RegistryFriendlyByteBuf buffer, T upgrade) { - } -} diff --git a/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/TurtleToolSpec.java b/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/TurtleToolSpec.java new file mode 100644 index 000000000..53532a629 --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/TurtleToolSpec.java @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.impl.upgrades; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dan200.computercraft.api.turtle.TurtleToolDurability; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Optional; + +/** + * The template for a turtle tool. + * + * @param adjective The adjective for this tool. + * @param craftItem The item used to craft this tool. + * @param toolItem The actual tool used. + * @param damageMultiplier The damage multiplier for this tool. + * @param allowEnchantments Whether to allow enchantments. + * @param consumeDurability When to consume durability. + * @param breakable The items breakable by this tool. + */ +public record TurtleToolSpec( + String adjective, + Optional craftItem, + Item toolItem, + float damageMultiplier, + boolean allowEnchantments, + TurtleToolDurability consumeDurability, + Optional> breakable +) { + public static final float DEFAULT_DAMAGE_MULTIPLIER = 3.0f; + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Codec.STRING.fieldOf("adjective").forGetter(TurtleToolSpec::adjective), + BuiltInRegistries.ITEM.byNameCodec().optionalFieldOf("craftingItem").forGetter(TurtleToolSpec::craftItem), + BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(TurtleToolSpec::toolItem), + Codec.FLOAT.optionalFieldOf("damageMultiplier", DEFAULT_DAMAGE_MULTIPLIER).forGetter(TurtleToolSpec::damageMultiplier), + Codec.BOOL.optionalFieldOf("allowEnchantments", false).forGetter(TurtleToolSpec::allowEnchantments), + TurtleToolDurability.CODEC.optionalFieldOf("consumeDurability", TurtleToolDurability.NEVER).forGetter(TurtleToolSpec::consumeDurability), + TagKey.codec(Registries.BLOCK).optionalFieldOf("breakable").forGetter(TurtleToolSpec::breakable) + ).apply(instance, TurtleToolSpec::new)); +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/UpgradeTypeImpl.java b/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/UpgradeTypeImpl.java new file mode 100644 index 000000000..4c3dcd91a --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/impl/upgrades/UpgradeTypeImpl.java @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.impl.upgrades; + +import com.mojang.serialization.MapCodec; +import dan200.computercraft.api.upgrades.UpgradeBase; +import dan200.computercraft.api.upgrades.UpgradeType; +import org.jetbrains.annotations.ApiStatus; + +/** + * Simple implementation of {@link UpgradeType}. + * + * @param codec The codec to read/write upgrades with. + * @param The upgrade subclass that this upgrade type represents. + */ +@ApiStatus.Internal +public record UpgradeTypeImpl(MapCodec codec) implements UpgradeType { +} diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java b/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java index ea1575ab1..bd45f9310 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientHooks.java @@ -128,8 +128,8 @@ public final class ClientHooks { } private static void addTurtleUpgrade(Consumer out, TurtleBlockEntity turtle, TurtleSide side) { - var upgrade = turtle.getUpgrade(side); - if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID())); + var upgrade = turtle.getAccess().getUpgradeWithData(side); + if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location())); } /** diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java index 09ba31986..d75ae2986 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java @@ -117,17 +117,17 @@ public final class ClientRegistry { } public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) { - register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided( + register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModeller.sided( new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"), new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right") )); - register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided( + register.register(ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(), TurtleUpgradeModeller.sided( new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"), new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right") )); - register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false)); - register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true)); - register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem()); + register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false)); + register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true)); + register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem()); } @SafeVarargs diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java index 988d5134d..7d62afc67 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java @@ -10,9 +10,8 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.impl.RegistryHelper; -import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.impl.UpgradeManager; import net.minecraft.client.Minecraft; import net.minecraft.core.component.DataComponentPatch; @@ -30,7 +29,7 @@ public final class TurtleUpgradeModellers { private static final TurtleUpgradeModeller NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) -> new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity()); - private static final Map, TurtleUpgradeModeller> turtleModels = new ConcurrentHashMap<>(); + private static final Map, TurtleUpgradeModeller> turtleModels = new ConcurrentHashMap<>(); private static volatile boolean fetchedModels; /** @@ -44,15 +43,15 @@ public final class TurtleUpgradeModellers { private TurtleUpgradeModellers() { } - public static void register(UpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { + public static void register(UpgradeType type, TurtleUpgradeModeller modeller) { if (fetchedModels) { throw new IllegalStateException(String.format( - "Turtle upgrade serialiser %s must be registered before models are baked.", - RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.serialiserRegistryKey()), serialiser) + "Turtle upgrade type %s must be registered before models are baked.", + RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.typeRegistry()), type) )); } - if (turtleModels.putIfAbsent(serialiser, modeller) != null) { + if (turtleModels.putIfAbsent(type, modeller) != null) { throw new IllegalStateException("Modeller already registered for serialiser"); } } @@ -69,11 +68,8 @@ public final class TurtleUpgradeModellers { return modeller.getModel(upgrade, null, side, data); } - private static TurtleUpgradeModeller getModeller(ITurtleUpgrade upgradeA) { - var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA); - if (wrapper == null) return NULL_TURTLE_MODELLER; - - var modeller = turtleModels.get(wrapper.serialiser()); + private static TurtleUpgradeModeller getModeller(ITurtleUpgrade upgrade) { + var modeller = turtleModels.get(upgrade.getType()); return modeller == null ? NULL_TURTLE_MODELLER : modeller; } diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrades/speaker.json b/projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrade/speaker.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrades/speaker.json rename to projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrade/speaker.json diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrades/wireless_modem_advanced.json b/projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrade/wireless_modem_advanced.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrades/wireless_modem_advanced.json rename to projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrade/wireless_modem_advanced.json diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrades/wireless_modem_normal.json b/projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrade/wireless_modem_normal.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrades/wireless_modem_normal.json rename to projects/common/src/generated/resources/data/computercraft/computercraft/pocket_upgrade/wireless_modem_normal.json diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrades/speaker.json b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrade/speaker.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrades/speaker.json rename to projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrade/speaker.json diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrades/wireless_modem_advanced.json b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrade/wireless_modem_advanced.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrades/wireless_modem_advanced.json rename to projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrade/wireless_modem_advanced.json diff --git a/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrades/wireless_modem_normal.json b/projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrade/wireless_modem_normal.json similarity index 100% rename from projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrades/wireless_modem_normal.json rename to projects/common/src/generated/resources/data/computercraft/computercraft/turtle_upgrade/wireless_modem_normal.json diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/crafting_table.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/crafting_table.json similarity index 100% rename from projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/crafting_table.json rename to projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/crafting_table.json diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_axe.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_axe.json new file mode 100644 index 000000000..c94316b76 --- /dev/null +++ b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_axe.json @@ -0,0 +1,6 @@ +{ + "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_axe.adjective", + "damageMultiplier": 6.0, + "item": "minecraft:diamond_axe" +} diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_hoe.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_hoe.json new file mode 100644 index 000000000..66e945689 --- /dev/null +++ b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_hoe.json @@ -0,0 +1,6 @@ +{ + "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_hoe.adjective", + "breakable": "computercraft:turtle_hoe_harvestable", + "item": "minecraft:diamond_hoe" +} diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_pickaxe.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_pickaxe.json new file mode 100644 index 000000000..6d5793315 --- /dev/null +++ b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_pickaxe.json @@ -0,0 +1,5 @@ +{ + "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_pickaxe.adjective", + "item": "minecraft:diamond_pickaxe" +} diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_shovel.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_shovel.json similarity index 68% rename from projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_shovel.json rename to projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_shovel.json index c3d57f601..0584f4e97 100644 --- a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_shovel.json +++ b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_shovel.json @@ -1,5 +1,6 @@ { "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_shovel.adjective", "breakable": "computercraft:turtle_shovel_harvestable", "item": "minecraft:diamond_shovel" } diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_sword.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_sword.json similarity index 72% rename from projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_sword.json rename to projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_sword.json index 2899cfd19..5cdae267d 100644 --- a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_sword.json +++ b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrade/diamond_sword.json @@ -1,5 +1,6 @@ { "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_sword.adjective", "breakable": "computercraft:turtle_sword_harvestable", "damageMultiplier": 9.0, "item": "minecraft:diamond_sword" diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_axe.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_axe.json deleted file mode 100644 index 5ecefb5e6..000000000 --- a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_axe.json +++ /dev/null @@ -1 +0,0 @@ -{"type": "computercraft:tool", "damageMultiplier": 6.0, "item": "minecraft:diamond_axe"} diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_hoe.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_hoe.json deleted file mode 100644 index 66a5cf9fa..000000000 --- a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_hoe.json +++ /dev/null @@ -1 +0,0 @@ -{"type": "computercraft:tool", "breakable": "computercraft:turtle_hoe_harvestable", "item": "minecraft:diamond_hoe"} diff --git a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_pickaxe.json b/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_pickaxe.json deleted file mode 100644 index 1bced4e59..000000000 --- a/projects/common/src/generated/resources/data/minecraft/computercraft/turtle_upgrades/diamond_pickaxe.json +++ /dev/null @@ -1 +0,0 @@ -{"type": "computercraft:tool", "item": "minecraft:diamond_pickaxe"} diff --git a/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java b/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java index 54f773df8..3c7008209 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java +++ b/projects/common/src/main/java/dan200/computercraft/data/DataProviders.java @@ -5,17 +5,20 @@ package dan200.computercraft.data; import com.mojang.serialization.Codec; +import dan200.computercraft.shared.ModRegistry; import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistrySetBuilder; import net.minecraft.data.DataProvider; import net.minecraft.data.PackOutput; +import net.minecraft.data.registries.RegistryPatchGenerator; import net.minecraft.data.tags.TagsProvider; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; -import java.util.concurrent.CompletableFuture; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -31,7 +34,14 @@ public final class DataProviders { public static void add(GeneratorSink generator) { var turtleUpgrades = generator.add(TurtleUpgradeProvider::new); var pocketUpgrades = generator.add(PocketUpgradeProvider::new); - generator.add((out, registries) -> new RecipeProvider(out, registries, turtleUpgrades, pocketUpgrades)); + + generator.add((out, registries) -> { + var builder = new RegistrySetBuilder(); + builder.add(ModRegistry.TURTLE_UPGRADE, bs -> turtleUpgrades.getGeneratedUpgrades().forEach(bs::register)); + builder.add(ModRegistry.POCKET_UPGRADE, bs -> pocketUpgrades.getGeneratedUpgrades().forEach(bs::register)); + + return new RecipeProvider(out, generator.createPatchedRegistries(registries, builder).thenApply(RegistrySetBuilder.PatchedRegistries::full)); + }); var blockTags = generator.blockTags(TagProvider::blockTags); generator.itemTags(TagProvider::itemTags, blockTags); @@ -62,5 +72,18 @@ public final class DataProviders { TagsProvider blockTags(Consumer> tags); TagsProvider itemTags(Consumer tags, TagsProvider blocks); + + /** + * Extend our registries with additional entries. + * + * @param registries The existing registries. + * @param patch The new registries to apply. + * @return The built registries. + */ + default CompletableFuture createPatchedRegistries( + CompletableFuture registries, RegistrySetBuilder patch + ) { + return RegistryPatchGenerator.createLookup(registries, patch); + } } } diff --git a/projects/common/src/main/java/dan200/computercraft/data/LanguageProvider.java b/projects/common/src/main/java/dan200/computercraft/data/LanguageProvider.java index 497198fe4..12780478d 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/LanguageProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/LanguageProvider.java @@ -287,8 +287,8 @@ public final class LanguageProvider implements DataProvider { BuiltInRegistries.ITEM.holders() .filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID)) .map(x -> x.value().getDescriptionId()), - turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective), - pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective), + turtleUpgrades.getGeneratedUpgrades().values().stream().map(UpgradeBase::getUnlocalisedAdjective), + pocketUpgrades.getGeneratedUpgrades().values().stream().map(UpgradeBase::getUnlocalisedAdjective), Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"), ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey), ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey), diff --git a/projects/common/src/main/java/dan200/computercraft/data/PocketUpgradeProvider.java b/projects/common/src/main/java/dan200/computercraft/data/PocketUpgradeProvider.java index 4c0f61cd0..74c7a080c 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/PocketUpgradeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/PocketUpgradeProvider.java @@ -7,14 +7,15 @@ package dan200.computercraft.data; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.pocket.PocketUpgradeDataProvider; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.shared.pocket.peripherals.PocketModem; +import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; import java.util.function.Consumer; import static dan200.computercraft.shared.ModRegistry.Items; -import static dan200.computercraft.shared.ModRegistry.PocketUpgradeSerialisers; class PocketUpgradeProvider extends PocketUpgradeDataProvider { PocketUpgradeProvider(PackOutput output) { @@ -22,10 +23,10 @@ class PocketUpgradeProvider extends PocketUpgradeDataProvider { } @Override - protected void addUpgrades(Consumer>> addUpgrade) { - addUpgrade.accept(simpleWithCustomItem(id("speaker"), PocketUpgradeSerialisers.SPEAKER.get(), Items.SPEAKER.get())); - simpleWithCustomItem(id("wireless_modem_normal"), PocketUpgradeSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade); - simpleWithCustomItem(id("wireless_modem_advanced"), PocketUpgradeSerialisers.WIRELESS_MODEM_ADVANCED.get(), Items.WIRELESS_MODEM_ADVANCED.get()).add(addUpgrade); + protected void addUpgrades(Consumer> addUpgrade) { + upgrade(id("speaker"), new PocketSpeaker(new ItemStack(Items.SPEAKER.get()))).add(addUpgrade); + upgrade(id("wireless_modem_normal"), new PocketModem(new ItemStack(Items.WIRELESS_MODEM_NORMAL.get()), false)).add(addUpgrade); + upgrade(id("wireless_modem_advanced"), new PocketModem(new ItemStack(Items.WIRELESS_MODEM_ADVANCED.get()), true)).add(addUpgrade); } private static ResourceLocation id(String id) { 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 152cdd97b..bc2f6f139 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/RecipeProvider.java @@ -8,8 +8,6 @@ import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.serialization.JsonOps; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.pocket.PocketUpgradeDataProvider; -import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider; import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.core.util.Colour; import dan200.computercraft.data.recipe.ShapedSpecBuilder; @@ -61,28 +59,43 @@ import net.minecraft.world.level.block.Blocks; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +import java.util.stream.Stream; import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER; import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM; final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { private final RecipeIngredients ingredients = PlatformHelper.get().getRecipeIngredients(); - private final TurtleUpgradeDataProvider turtleUpgrades; - private final PocketUpgradeDataProvider pocketUpgrades; - RecipeProvider(PackOutput output, CompletableFuture registries, TurtleUpgradeDataProvider turtleUpgrades, PocketUpgradeDataProvider pocketUpgrades) { + private final CompletableFuture registries; + + RecipeProvider(PackOutput output, CompletableFuture registries) { super(output, registries); - this.turtleUpgrades = turtleUpgrades; - this.pocketUpgrades = pocketUpgrades; + this.registries = registries; + } + + private HolderLookup.Provider registries() { + try { + return registries.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted"); + } catch (ExecutionException e) { + var cause = e.getCause(); + throw cause instanceof RuntimeException rt ? rt : new RuntimeException("Unexpected error", cause); + } } @Override public void buildRecipes(RecipeOutput add) { + var registries = registries(); + basicRecipes(add); diskColours(add); - pocketUpgrades(add); - turtleUpgrades(add); + pocketUpgrades(add, registries); + turtleUpgrades(add, registries); turtleOverlays(add); addSpecial(add, new PrintoutRecipe(CraftingBookCategory.MISC)); @@ -119,15 +132,17 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { /** * Register a crafting recipe for each turtle upgrade. * - * @param add The callback to add recipes. + * @param add The callback to add recipes. + * @param registries The currently available registries. */ - private void turtleUpgrades(RecipeOutput add) { + private void turtleUpgrades(RecipeOutput add, HolderLookup.Provider registries) { for (var turtleItem : turtleItems()) { var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem); - for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) { + registries.lookup(ModRegistry.TURTLE_UPGRADE).map(HolderLookup::listElements).orElse(Stream.empty()).forEach(upgradeHolder -> { + var upgrade = upgradeHolder.value(); ShapedSpecBuilder - .shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade))) + .shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder))) .group(name.toString()) .pattern("#T") .define('T', turtleItem) @@ -136,9 +151,9 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { .build(ImpostorShapedRecipe::new) .save( add, - name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath())) + name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath())) ); - } + }); } } @@ -149,15 +164,17 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { /** * Register a crafting recipe for each pocket upgrade. * - * @param add The callback to add recipes. + * @param add The callback to add recipes. + * @param registries The currently available registries. */ - private void pocketUpgrades(RecipeOutput add) { + private void pocketUpgrades(RecipeOutput add, HolderLookup.Provider registries) { for (var pocket : pocketComputerItems()) { var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_")); - for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) { + registries.lookup(ModRegistry.POCKET_UPGRADE).map(HolderLookup::listElements).orElse(Stream.empty()).forEach(upgradeHolder -> { + var upgrade = upgradeHolder.value(); ShapedSpecBuilder - .shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade))) + .shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder))) .group(name.toString()) .pattern("#") .pattern("P") @@ -167,9 +184,9 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider { .build(ImpostorShapedRecipe::new) .save( add, - name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath())) + name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath())) ); - } + }); } } diff --git a/projects/common/src/main/java/dan200/computercraft/data/TurtleUpgradeProvider.java b/projects/common/src/main/java/dan200/computercraft/data/TurtleUpgradeProvider.java index fac1cb72f..b740b85f4 100644 --- a/projects/common/src/main/java/dan200/computercraft/data/TurtleUpgradeProvider.java +++ b/projects/common/src/main/java/dan200/computercraft/data/TurtleUpgradeProvider.java @@ -8,32 +8,34 @@ import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftTags.Blocks; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.shared.ModRegistry; +import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable; +import dan200.computercraft.shared.turtle.upgrades.TurtleModem; +import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import java.util.function.Consumer; -import static dan200.computercraft.shared.ModRegistry.Items; -import static dan200.computercraft.shared.ModRegistry.TurtleSerialisers; - class TurtleUpgradeProvider extends TurtleUpgradeDataProvider { TurtleUpgradeProvider(PackOutput output) { super(output); } @Override - protected void addUpgrades(Consumer>> addUpgrade) { - simpleWithCustomItem(id("speaker"), TurtleSerialisers.SPEAKER.get(), Items.SPEAKER.get()).add(addUpgrade); - simpleWithCustomItem(vanilla("crafting_table"), TurtleSerialisers.WORKBENCH.get(), net.minecraft.world.item.Items.CRAFTING_TABLE).add(addUpgrade); - simpleWithCustomItem(id("wireless_modem_normal"), TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade); - simpleWithCustomItem(id("wireless_modem_advanced"), TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), Items.WIRELESS_MODEM_ADVANCED.get()).add(addUpgrade); + protected void addUpgrades(Consumer> addUpgrade) { + upgrade(id("speaker"), new TurtleSpeaker(new ItemStack(ModRegistry.Items.SPEAKER.get()))).add(addUpgrade); + upgrade(vanilla("crafting_table"), new TurtleCraftingTable(new ItemStack(Items.CRAFTING_TABLE))).add(addUpgrade); + upgrade(id("wireless_modem_normal"), new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()), false)).add(addUpgrade); + upgrade(id("wireless_modem_advanced"), new TurtleModem(new ItemStack(ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get()), true)).add(addUpgrade); - tool(vanilla("diamond_axe"), net.minecraft.world.item.Items.DIAMOND_AXE).damageMultiplier(6.0f).add(addUpgrade); - tool(vanilla("diamond_pickaxe"), net.minecraft.world.item.Items.DIAMOND_PICKAXE).add(addUpgrade); - tool(vanilla("diamond_hoe"), net.minecraft.world.item.Items.DIAMOND_HOE).breakable(Blocks.TURTLE_HOE_BREAKABLE).add(addUpgrade); - tool(vanilla("diamond_shovel"), net.minecraft.world.item.Items.DIAMOND_SHOVEL).breakable(Blocks.TURTLE_SHOVEL_BREAKABLE).add(addUpgrade); - tool(vanilla("diamond_sword"), net.minecraft.world.item.Items.DIAMOND_SWORD).breakable(Blocks.TURTLE_SWORD_BREAKABLE).damageMultiplier(9.0f).add(addUpgrade); + tool(vanilla("diamond_axe"), Items.DIAMOND_AXE).damageMultiplier(6.0f).add(addUpgrade); + tool(vanilla("diamond_pickaxe"), Items.DIAMOND_PICKAXE).add(addUpgrade); + tool(vanilla("diamond_hoe"), Items.DIAMOND_HOE).breakable(Blocks.TURTLE_HOE_BREAKABLE).add(addUpgrade); + tool(vanilla("diamond_shovel"), Items.DIAMOND_SHOVEL).breakable(Blocks.TURTLE_SHOVEL_BREAKABLE).add(addUpgrade); + tool(vanilla("diamond_sword"), Items.DIAMOND_SWORD).breakable(Blocks.TURTLE_SWORD_BREAKABLE).damageMultiplier(9.0f).add(addUpgrade); } private static ResourceLocation id(String id) { diff --git a/projects/common/src/main/java/dan200/computercraft/impl/AbstractComputerCraftAPI.java b/projects/common/src/main/java/dan200/computercraft/impl/AbstractComputerCraftAPI.java index 42e8a0f7b..4652c35e0 100644 --- a/projects/common/src/main/java/dan200/computercraft/impl/AbstractComputerCraftAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/impl/AbstractComputerCraftAPI.java @@ -4,6 +4,7 @@ package dan200.computercraft.impl; +import com.mojang.serialization.Codec; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.detail.BlockReference; import dan200.computercraft.api.detail.DetailRegistry; @@ -19,14 +20,16 @@ import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.redstone.BundledRedstoneProvider; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleRefuelHandler; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.core.filesystem.WritableFileMount; import dan200.computercraft.impl.detail.DetailRegistryImpl; import dan200.computercraft.impl.network.wired.WiredNodeImpl; +import dan200.computercraft.impl.upgrades.TurtleToolSpec; import dan200.computercraft.shared.computer.core.ResourceMount; import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.details.BlockDetails; import dan200.computercraft.shared.details.ItemDetails; +import dan200.computercraft.shared.turtle.upgrades.TurtleTool; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Registry; @@ -45,8 +48,8 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic private final DetailRegistry itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic); private final DetailRegistry blockDetails = new DetailRegistryImpl<>(BlockDetails::fillBasic); - protected static final ResourceKey>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser")); - protected static final ResourceKey>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser")); + protected static final ResourceKey>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_type")); + protected static final ResourceKey>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_type")); public static @Nullable InputStream getResourceFile(MinecraftServer server, String domain, String subPath) { var manager = server.getResourceManager(); @@ -117,15 +120,30 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic } @Override - public final ResourceKey>> turtleUpgradeRegistryId() { + public final ResourceKey>> turtleUpgradeRegistryId() { return turtleUpgradeRegistryId; } @Override - public final ResourceKey>> pocketUpgradeRegistryId() { + public Codec turtleUpgradeCodec() { + return TurtleUpgrades.instance().upgradeCodec(); + } + + @Override + public ITurtleUpgrade createTurtleTool(TurtleToolSpec spec) { + return new TurtleTool(spec); + } + + @Override + public final ResourceKey>> pocketUpgradeRegistryId() { return pocketUpgradeRegistryId; } + @Override + public Codec pocketUpgradeCodec() { + return PocketUpgrades.instance().upgradeCodec(); + } + @Override public final DetailRegistry getItemStackDetailRegistry() { return itemStackDetails; diff --git a/projects/common/src/main/java/dan200/computercraft/impl/PocketUpgrades.java b/projects/common/src/main/java/dan200/computercraft/impl/PocketUpgrades.java index 93030adc9..e77856a92 100644 --- a/projects/common/src/main/java/dan200/computercraft/impl/PocketUpgrades.java +++ b/projects/common/src/main/java/dan200/computercraft/impl/PocketUpgrades.java @@ -4,14 +4,12 @@ package dan200.computercraft.impl; -import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.pocket.IPocketUpgrade; - -import java.util.stream.Stream; +import dan200.computercraft.shared.ModRegistry; public final class PocketUpgrades { private static final UpgradeManager registry = new UpgradeManager<>( - "pocket computer upgrade", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey() + IPocketUpgrade.typeRegistry(), ModRegistry.POCKET_UPGRADE, IPocketUpgrade::getType ); private PocketUpgrades() { @@ -20,10 +18,4 @@ public final class PocketUpgrades { public static UpgradeManager instance() { return registry; } - - public static Stream getVanillaUpgrades() { - return instance().getUpgradeWrappers().values().stream() - .filter(x -> x.modId().equals(ComputerCraftAPI.MOD_ID)) - .map(UpgradeManager.UpgradeWrapper::upgrade); - } } diff --git a/projects/common/src/main/java/dan200/computercraft/impl/TurtleUpgrades.java b/projects/common/src/main/java/dan200/computercraft/impl/TurtleUpgrades.java index dafdc2089..523d9a602 100644 --- a/projects/common/src/main/java/dan200/computercraft/impl/TurtleUpgrades.java +++ b/projects/common/src/main/java/dan200/computercraft/impl/TurtleUpgrades.java @@ -4,14 +4,12 @@ package dan200.computercraft.impl; -import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.turtle.ITurtleUpgrade; - -import java.util.stream.Stream; +import dan200.computercraft.shared.ModRegistry; public final class TurtleUpgrades { private static final UpgradeManager registry = new UpgradeManager<>( - "turtle upgrade", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey() + ITurtleUpgrade.typeRegistry(), ModRegistry.TURTLE_UPGRADE, ITurtleUpgrade::getType ); private TurtleUpgrades() { @@ -20,10 +18,4 @@ public final class TurtleUpgrades { public static UpgradeManager instance() { return registry; } - - public static Stream getVanillaUpgrades() { - return instance().getUpgradeWrappers().values().stream() - .filter(x -> x.modId().equals(ComputerCraftAPI.MOD_ID)) - .map(UpgradeManager.UpgradeWrapper::upgrade); - } } diff --git a/projects/common/src/main/java/dan200/computercraft/impl/UpgradeManager.java b/projects/common/src/main/java/dan200/computercraft/impl/UpgradeManager.java index d60b44ce3..d11daab61 100644 --- a/projects/common/src/main/java/dan200/computercraft/impl/UpgradeManager.java +++ b/projects/common/src/main/java/dan200/computercraft/impl/UpgradeManager.java @@ -4,36 +4,26 @@ package dan200.computercraft.impl; -import com.google.gson.*; import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; import com.mojang.serialization.codecs.RecordCodecBuilder; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeData; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; -import dan200.computercraft.shared.platform.PlatformHelper; -import io.netty.buffer.ByteBuf; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.shared.util.SafeDispatchCodec; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.RegistryFixedCodec; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; -import net.minecraft.util.GsonHelper; -import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.item.ItemStack; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.function.Function; /** * Manages turtle and pocket computer upgrades. @@ -42,145 +32,81 @@ import java.util.stream.Collectors; * @see TurtleUpgrades * @see PocketUpgrades */ -public class UpgradeManager extends SimpleJsonResourceReloadListener { - private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class); - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); +public final class UpgradeManager { + private final ResourceKey> registry; + private final Codec upgradeCodec; + private final Codec> dataCodec; + private final StreamCodec> dataStreamCodec; - public record UpgradeWrapper( - ResourceLocation id, T upgrade, UpgradeSerialiser serialiser, String modId + UpgradeManager( + ResourceKey>> typeRegistry, + ResourceKey> registry, + Function> getType ) { - } - - private final String kind; - private final ResourceKey>> registry; - - private Map> current = Map.of(); - private Map> currentWrappers = Map.of(); - - private final Codec upgradeCodec = ResourceLocation.CODEC.flatXmap( - x -> { - var upgrade = get(x); - return upgrade == null ? DataResult.error(() -> "Unknown upgrade " + x) : DataResult.success(upgrade); - }, - x -> DataResult.success(x.getUpgradeID()) - ); - - private final Codec> fullCodec = RecordCodecBuilder.create(i -> i.group( - upgradeCodec.fieldOf("id").forGetter(UpgradeData::upgrade), - DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(UpgradeData::data) - ).apply(i, UpgradeData::new)); - - private final Codec> codec = Codec.withAlternative(fullCodec, upgradeCodec, UpgradeData::ofDefault); - - private final StreamCodec upgradeStreamCodec = ResourceLocation.STREAM_CODEC.map( - x -> { - var upgrade = get(x); - if (upgrade == null) throw new IllegalStateException("Unknown upgrade " + x); - return upgrade; - }, - UpgradeBase::getUpgradeID - ); - - private final StreamCodec> streamCodec = StreamCodec.composite( - upgradeStreamCodec, UpgradeData::upgrade, - DataComponentPatch.STREAM_CODEC, UpgradeData::data, - UpgradeData::new - ); - - public UpgradeManager(String kind, String path, ResourceKey>> registry) { - super(GSON, path); - this.kind = kind; this.registry = registry; + + upgradeCodec = SafeDispatchCodec.ofRegistry(typeRegistry, getType, UpgradeType::codec); + + var holderCodec = RegistryFixedCodec.create(registry).xmap(x -> (Holder.Reference) x, x -> x); + Codec> fullCodec = RecordCodecBuilder.create(i -> i.group( + holderCodec.fieldOf("id").forGetter(UpgradeData::holder), + DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(UpgradeData::data) + ).apply(i, UpgradeData::new)); + dataCodec = Codec.withAlternative(fullCodec, holderCodec, UpgradeData::ofDefault); + + dataStreamCodec = StreamCodec.composite( + ByteBufCodecs.holderRegistry(registry).map(x -> (Holder.Reference) x, x -> x), UpgradeData::holder, + DataComponentPatch.STREAM_CODEC, UpgradeData::data, + UpgradeData::new + ); + } + + /** + * The codec for an upgrade instance. + * + * @return The instance codec. + */ + public Codec upgradeCodec() { + return upgradeCodec; + } + + /** + * The codec for an upgrade and its associated data. + * + * @return The upgrade data codec. + */ + public Codec> upgradeDataCodec() { + return dataCodec; + } + + /** + * The stream codec for an upgrade and its associated data. + * + * @return The upgrade data codec. + */ + public StreamCodec> upgradeDataStreamCodec() { + return dataStreamCodec; + } + + public String getOwner(Holder.Reference upgrade) { + var ns = upgrade.key().location().getNamespace(); + return ns.equals("minecraft") ? ComputerCraftAPI.MOD_ID : ns; + + // TODO: Would be nice if we could use the registration info here. } @Nullable - public T get(ResourceLocation id) { - var wrapper = current.get(id); - return wrapper == null ? null : wrapper.upgrade(); - } - - @Nullable - public UpgradeWrapper getWrapper(T upgrade) { - return currentWrappers.get(upgrade); - } - - @Nullable - public String getOwner(T upgrade) { - var wrapper = currentWrappers.get(upgrade); - return wrapper != null ? wrapper.modId() : null; - } - - @Nullable - public UpgradeData get(ItemStack stack) { + public UpgradeData get(HolderLookup.Provider registries, ItemStack stack) { if (stack.isEmpty()) return null; - for (var wrapper : current.values()) { - var craftingStack = wrapper.upgrade().getCraftingItem(); - if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) { - return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack)); - } - } - - return null; - } - - public Collection getUpgrades() { - return currentWrappers.keySet(); - } - - public Map> getUpgradeWrappers() { - return current; - } - - public Codec> codec() { - return codec; - } - - public StreamCodec> streamCodec() { - return streamCodec; - } - - @Override - protected void apply(Map upgrades, ResourceManager manager, ProfilerFiller profiler) { - var registry = RegistryHelper.getRegistry(this.registry); - Map> newUpgrades = new HashMap<>(); - for (var element : upgrades.entrySet()) { - try { - loadUpgrade(registry, newUpgrades, element.getKey(), element.getValue()); - } catch (IllegalArgumentException | JsonParseException e) { - LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e); - } - } - - current = Collections.unmodifiableMap(newUpgrades); - currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x)); - LOG.info("Loaded {} {}s", current.size(), kind); - } - - private void loadUpgrade(Registry> registry, Map> current, ResourceLocation id, JsonElement json) { - var root = GsonHelper.convertToJsonObject(json, "top element"); - if (!PlatformHelper.get().shouldLoadResource(root)) return; - - var serialiserId = new ResourceLocation(GsonHelper.getAsString(root, "type")); - var serialiser = registry.get(serialiserId); - if (serialiser == null) throw new JsonSyntaxException("Unknown upgrade type '" + serialiserId + "'"); - - // TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible, - // but maybe not ideal for datapacks. - var modId = id.getNamespace(); - if (modId.equals("minecraft") || modId.isEmpty()) modId = ComputerCraftAPI.MOD_ID; - - var upgrade = serialiser.fromJson(id, root); - if (!upgrade.getUpgradeID().equals(id)) { - throw new IllegalArgumentException("Upgrade " + id + " from " + serialiser + " was incorrectly given id " + upgrade.getUpgradeID()); - } - - var result = new UpgradeWrapper(id, upgrade, serialiser, modId); - current.put(result.id(), result); - } - - public void loadFromNetwork(Map> newUpgrades) { - current = Collections.unmodifiableMap(newUpgrades); - currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x)); + return registries.lookupOrThrow(registry).listElements() + .filter(holder -> { + var upgrade = holder.value(); + var craftingStack = upgrade.getCraftingItem(); + return !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && upgrade.isItemSuitable(stack); + }) + .findAny() + .map(x -> UpgradeData.of(x, x.value().getUpgradeData(stack))) + .orElse(null); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java b/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java index 0a7d8b708..3b077f3b2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/CommonHooks.java @@ -6,8 +6,6 @@ package dan200.computercraft.shared; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.core.apis.http.NetworkUtils; -import dan200.computercraft.impl.PocketUpgrades; -import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.computer.core.ResourceMount; import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.metrics.ComputerMBean; @@ -121,8 +119,6 @@ public final class CommonHooks { public static void onDatapackReload(BiConsumer addReload) { addReload.accept("mounts", ResourceMount.RELOAD_LISTENER); - addReload.accept("turtle_upgrades", TurtleUpgrades.instance()); - addReload.accept("pocket_upgrades", PocketUpgrades.instance()); } public static boolean onEntitySpawn(Entity entity) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java index 538382a60..181891678 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -13,11 +13,12 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeData; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.core.util.Colour; import dan200.computercraft.impl.PocketUpgrades; -import dan200.computercraft.impl.TurtleUpgrades; +import dan200.computercraft.impl.RegistryHelper; import dan200.computercraft.shared.command.UserLevel; import dan200.computercraft.shared.command.arguments.ComputerArgumentType; import dan200.computercraft.shared.command.arguments.RepeatArgumentType; @@ -79,12 +80,18 @@ import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; -import dan200.computercraft.shared.turtle.upgrades.*; +import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable; +import dan200.computercraft.shared.turtle.upgrades.TurtleModem; +import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker; +import dan200.computercraft.shared.turtle.upgrades.TurtleTool; import dan200.computercraft.shared.util.DataComponentUtil; import dan200.computercraft.shared.util.NonNegativeId; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; import net.minecraft.core.cauldron.CauldronInteraction; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.Registries; @@ -92,6 +99,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.inventory.MenuType; @@ -122,6 +130,9 @@ public final class ModRegistry { private ModRegistry() { } + public static final ResourceKey> TURTLE_UPGRADE = RegistryHelper.TURTLE_UPGRADE; + public static final ResourceKey> POCKET_UPGRADE = RegistryHelper.POCKET_UPGRADE; + public static final class Blocks { static final RegistrationHelper REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK); @@ -289,7 +300,7 @@ public final class ModRegistry { * @see TurtleItem */ public static final RegistryEntry>> LEFT_TURTLE_UPGRADE = register("left_turtle_upgrade", b -> b - .persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec()) + .persistent(dan200.computercraft.impl.TurtleUpgrades.instance().upgradeDataCodec()).networkSynchronized(dan200.computercraft.impl.TurtleUpgrades.instance().upgradeDataStreamCodec()) ); /** @@ -298,7 +309,7 @@ public final class ModRegistry { * @see TurtleItem */ public static final RegistryEntry>> RIGHT_TURTLE_UPGRADE = register("right_turtle_upgrade", b -> b - .persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec()) + .persistent(dan200.computercraft.impl.TurtleUpgrades.instance().upgradeDataCodec()).networkSynchronized(dan200.computercraft.impl.TurtleUpgrades.instance().upgradeDataStreamCodec()) ); /** @@ -321,7 +332,7 @@ public final class ModRegistry { * @see PocketComputerItem */ public static final RegistryEntry>> POCKET_UPGRADE = register("pocket_upgrade", b -> b - .persistent(PocketUpgrades.instance().codec()).networkSynchronized(PocketUpgrades.instance().streamCodec()) + .persistent(PocketUpgrades.instance().upgradeDataCodec()).networkSynchronized(PocketUpgrades.instance().upgradeDataStreamCodec()) ); /** @@ -374,30 +385,30 @@ public final class ModRegistry { ); } - public static class TurtleSerialisers { - static final RegistrationHelper> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.serialiserRegistryKey()); + public static class TurtleUpgradeTypes { + static final RegistrationHelper> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.typeRegistry()); - public static final RegistryEntry> SPEAKER = - REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new)); - public static final RegistryEntry> WORKBENCH = - REGISTRY.register("workbench", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new)); - public static final RegistryEntry> WIRELESS_MODEM_NORMAL = - REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false))); - public static final RegistryEntry> WIRELESS_MODEM_ADVANCED = - REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true))); + public static final RegistryEntry> SPEAKER = + REGISTRY.register("speaker", () -> UpgradeType.simpleWithCustomItem(TurtleSpeaker::new)); + public static final RegistryEntry> WORKBENCH = + REGISTRY.register("workbench", () -> UpgradeType.simpleWithCustomItem(TurtleCraftingTable::new)); + public static final RegistryEntry> WIRELESS_MODEM_NORMAL = + REGISTRY.register("wireless_modem_normal", () -> UpgradeType.simpleWithCustomItem(item -> new TurtleModem(item, false))); + public static final RegistryEntry> WIRELESS_MODEM_ADVANCED = + REGISTRY.register("wireless_modem_advanced", () -> UpgradeType.simpleWithCustomItem(item -> new TurtleModem(item, true))); - public static final RegistryEntry> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE); + public static final RegistryEntry> TOOL = REGISTRY.register("tool", () -> UpgradeType.create(TurtleTool.CODEC)); } - public static class PocketUpgradeSerialisers { - static final RegistrationHelper> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.serialiserRegistryKey()); + public static class PocketUpgradeTypes { + static final RegistrationHelper> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.typeRegistry()); - public static final RegistryEntry> SPEAKER = - REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new)); - public static final RegistryEntry> WIRELESS_MODEM_NORMAL = - REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false))); - public static final RegistryEntry> WIRELESS_MODEM_ADVANCED = - REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true))); + public static final RegistryEntry> SPEAKER = + REGISTRY.register("speaker", () -> UpgradeType.simpleWithCustomItem(PocketSpeaker::new)); + public static final RegistryEntry> WIRELESS_MODEM_NORMAL = + REGISTRY.register("wireless_modem_normal", () -> UpgradeType.simpleWithCustomItem(item -> new PocketModem(item, false))); + public static final RegistryEntry> WIRELESS_MODEM_ADVANCED = + REGISTRY.register("wireless_modem_advanced", () -> UpgradeType.simpleWithCustomItem(item -> new PocketModem(item, true))); } public static class Menus { @@ -525,10 +536,10 @@ public final class ModRegistry { out.accept(new ItemStack(Items.COMPUTER_NORMAL.get())); out.accept(new ItemStack(Items.COMPUTER_ADVANCED.get())); if (context.hasPermissions()) out.accept(new ItemStack(Items.COMPUTER_COMMAND.get())); - addTurtle(out, Items.TURTLE_NORMAL.get()); - addTurtle(out, Items.TURTLE_ADVANCED.get()); - addPocket(out, Items.POCKET_COMPUTER_NORMAL.get()); - addPocket(out, Items.POCKET_COMPUTER_ADVANCED.get()); + addTurtle(out, Items.TURTLE_NORMAL.get(), context.holders()); + addTurtle(out, Items.TURTLE_ADVANCED.get(), context.holders()); + addPocket(out, Items.POCKET_COMPUTER_NORMAL.get(), context.holders()); + addPocket(out, Items.POCKET_COMPUTER_ADVANCED.get(), context.holders()); out.accept(Items.WIRELESS_MODEM_NORMAL.get()); out.accept(Items.WIRELESS_MODEM_ADVANCED.get()); @@ -562,8 +573,8 @@ public final class ModRegistry { BlockEntities.REGISTRY.register(); Items.REGISTRY.register(); DataComponents.REGISTRY.register(); - TurtleSerialisers.REGISTRY.register(); - PocketUpgradeSerialisers.REGISTRY.register(); + TurtleUpgradeTypes.REGISTRY.register(); + PocketUpgradeTypes.REGISTRY.register(); Menus.REGISTRY.register(); ArgumentTypes.REGISTRY.register(); LootItemConditionTypes.REGISTRY.register(); @@ -594,15 +605,23 @@ public final class ModRegistry { CauldronInteraction.WATER.map().put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION); } - private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) { + private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle, HolderLookup.Provider registries) { out.accept(new ItemStack(turtle)); - TurtleUpgrades.getVanillaUpgrades() + registries.lookupOrThrow(TURTLE_UPGRADE).listElements() + .filter(ModRegistry::isOurUpgrade) .map(x -> DataComponentUtil.createStack(turtle, DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(x))) .forEach(out::accept); } - private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) { + private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket, HolderLookup.Provider registries) { out.accept(new ItemStack(pocket)); - PocketUpgrades.getVanillaUpgrades().map(x -> DataComponentUtil.createStack(pocket, DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(x))).forEach(out::accept); + registries.lookupOrThrow(POCKET_UPGRADE).listElements() + .filter(ModRegistry::isOurUpgrade) + .map(x -> DataComponentUtil.createStack(pocket, DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(x))).forEach(out::accept); + } + + private static boolean isOurUpgrade(Holder.Reference upgrade) { + var namespace = upgrade.key().location().getNamespace(); + return namespace.equals("minecraft") || namespace.equals(ComputerCraftAPI.MOD_ID); } } 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 ae13e6436..869b2036a 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 @@ -6,18 +6,23 @@ package dan200.computercraft.shared.integration; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.upgrades.UpgradeData; -import dan200.computercraft.impl.PocketUpgrades; -import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.util.DataComponentUtil; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Stream; /** * Utilities for recipe mod plugins (such as JEI). @@ -48,24 +53,41 @@ public final class RecipeModHelpers { * Get additional ComputerCraft-related items which may not be visible in a creative tab. This includes upgraded * turtle and pocket computers for each upgrade. * + * @param registries The currently available registries. * @return The additional stacks to show. */ - public static List getExtraStacks() { + public static List getExtraStacks(HolderLookup.Provider registries) { List upgradeItems = new ArrayList<>(); for (var turtleSupplier : TURTLES) { var turtle = turtleSupplier.get(); - for (var upgrade : TurtleUpgrades.instance().getUpgrades()) { - upgradeItems.add(DataComponentUtil.createStack(turtle, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade))); - } + forEachRegistry(registries, ModRegistry.TURTLE_UPGRADE, upgrade -> + upgradeItems.add(DataComponentUtil.createStack(turtle, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade))) + ); } for (var pocketSupplier : POCKET_COMPUTERS) { var pocket = pocketSupplier.get(); - for (var upgrade : PocketUpgrades.instance().getUpgrades()) { - upgradeItems.add(DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade))); - } + forEachRegistry(registries, ModRegistry.POCKET_UPGRADE, upgrade -> + upgradeItems.add(DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade))) + ); } return upgradeItems; } + + /** + * A temporary function to denote places where we need a {@link HolderLookup.Provider} within our recipe mods, but + * don't have access to one. + * + * @return The empty recipe mod access. + * @deprecated We should get the registry access from a more sensible place. + */ + @Deprecated + public static HolderLookup.Provider getEmptyRegistryAccess() { + return RegistryAccess.EMPTY; + } + + static void forEachRegistry(HolderLookup.Provider registries, ResourceKey> registry, Consumer> consumer) { + registries.lookup(registry).map(HolderLookup::listElements).orElse(Stream.empty()).forEach(consumer); + } } 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 e4904516c..6995716df 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 @@ -9,12 +9,12 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeData; -import dan200.computercraft.impl.PocketUpgrades; -import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.util.DataComponentUtil; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -27,8 +27,7 @@ import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; -import static dan200.computercraft.shared.integration.RecipeModHelpers.POCKET_COMPUTERS; -import static dan200.computercraft.shared.integration.RecipeModHelpers.TURTLES; +import static dan200.computercraft.shared.integration.RecipeModHelpers.*; /** * Provides dynamic recipe and usage information for upgraded turtle and pocket computers. This is intended to be @@ -39,14 +38,16 @@ import static dan200.computercraft.shared.integration.RecipeModHelpers.TURTLES; */ public class UpgradeRecipeGenerator { private final Function wrap; + private final HolderLookup.Provider registries; private final Map> upgradeItemLookup = new HashMap<>(); private final List pocketUpgrades = new ArrayList<>(); private final List turtleUpgrades = new ArrayList<>(); private boolean initialised = false; - public UpgradeRecipeGenerator(Function wrap) { + public UpgradeRecipeGenerator(Function wrap, HolderLookup.Provider registries) { this.wrap = wrap; + this.registries = registries; } /** @@ -56,23 +57,25 @@ public class UpgradeRecipeGenerator { if (initialised) return; initialised = true; - for (var upgrade : TurtleUpgrades.instance().getUpgrades()) { + forEachRegistry(registries, ModRegistry.TURTLE_UPGRADE, holder -> { + var upgrade = holder.value(); var stack = upgrade.getCraftingItem(); if (stack.isEmpty()) return; - var info = new UpgradeInfo(stack, upgrade); + var info = new UpgradeInfo(stack, upgrade, holder, null); upgradeItemLookup.computeIfAbsent(stack.getItem(), k -> new ArrayList<>(1)).add(info); turtleUpgrades.add(info); - } + }); - for (var upgrade : PocketUpgrades.instance().getUpgrades()) { + forEachRegistry(registries, ModRegistry.POCKET_UPGRADE, holder -> { + var upgrade = holder.value(); var stack = upgrade.getCraftingItem(); if (stack.isEmpty()) return; - var info = new UpgradeInfo(stack, upgrade); + var info = new UpgradeInfo(stack, upgrade, null, holder); upgradeItemLookup.computeIfAbsent(stack.getItem(), k -> new ArrayList<>(1)).add(info); pocketUpgrades.add(info); - } + }); } /** @@ -248,23 +251,17 @@ public class UpgradeRecipeGenerator { private class UpgradeInfo { final ItemStack stack; final Ingredient ingredient; - final @Nullable ITurtleUpgrade turtle; - final @Nullable IPocketUpgrade pocket; + final @Nullable Holder.Reference turtle; + final @Nullable Holder.Reference pocket; final UpgradeBase upgrade; private @Nullable ArrayList recipes; - UpgradeInfo(ItemStack stack, ITurtleUpgrade turtle) { + UpgradeInfo(ItemStack stack, UpgradeBase upgrade, @Nullable Holder.Reference turtle, @Nullable Holder.Reference pocket) { this.stack = stack; ingredient = Ingredient.of(stack); - upgrade = this.turtle = turtle; - pocket = null; - } - - UpgradeInfo(ItemStack stack, IPocketUpgrade pocket) { - this.stack = stack; - ingredient = Ingredient.of(stack); - turtle = null; - upgrade = this.pocket = pocket; + this.turtle = turtle; + this.pocket = pocket; + this.upgrade = upgrade; } List getRecipes() { 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 825e80bd9..730778631 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 @@ -52,7 +52,7 @@ public class JEIComputerCraft implements IModPlugin { var registry = runtime.getRecipeManager(); // Register all turtles/pocket computers (not just vanilla upgrades) as upgrades on JEI. - var upgradeItems = RecipeModHelpers.getExtraStacks(); + var upgradeItems = RecipeModHelpers.getExtraStacks(RecipeModHelpers.getEmptyRegistryAccess()); if (!upgradeItems.isEmpty()) { runtime.getIngredientManager().addIngredientsAtRuntime(VanillaTypes.ITEM_STACK, upgradeItems); } @@ -73,11 +73,11 @@ public class JEIComputerCraft implements IModPlugin { var name = new StringBuilder("turtle:"); // Add left and right upgrades to the identifier - var left = TurtleItem.getUpgrade(stack, TurtleSide.LEFT); - var right = TurtleItem.getUpgrade(stack, TurtleSide.RIGHT); - if (left != null) name.append(left.getUpgradeID()); + var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT); + var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT); + if (left != null) name.append(left.holder().key().location()); if (left != null && right != null) name.append('|'); - if (right != null) name.append(right.getUpgradeID()); + if (right != null) name.append(right.holder().key().location()); return name.toString(); }; @@ -89,8 +89,8 @@ public class JEIComputerCraft implements IModPlugin { var name = new StringBuilder("pocket:"); // Add the upgrade to the identifier - var upgrade = PocketComputerItem.getUpgrade(stack); - if (upgrade != null) name.append(upgrade.getUpgradeID()); + var upgrade = PocketComputerItem.getUpgradeWithData(stack); + if (upgrade != null) name.append(upgrade.holder().key().location()); return name.toString(); }; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/RecipeResolver.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/RecipeResolver.java index d07c57266..f7addc311 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/RecipeResolver.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/jei/RecipeResolver.java @@ -5,6 +5,7 @@ package dan200.computercraft.shared.integration.jei; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.integration.RecipeModHelpers; import dan200.computercraft.shared.integration.UpgradeRecipeGenerator; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.turtle.items.TurtleItem; @@ -22,7 +23,7 @@ import java.util.List; class RecipeResolver implements IRecipeManagerPlugin { private static final ResourceLocation RECIPE_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "upgrade"); - private final UpgradeRecipeGenerator> resolver = new UpgradeRecipeGenerator<>(x -> new RecipeHolder<>(RECIPE_ID, x)); + private final UpgradeRecipeGenerator> resolver = new UpgradeRecipeGenerator<>(x -> new RecipeHolder<>(RECIPE_ID, x), RecipeModHelpers.getEmptyRegistryAccess()); @Override public List> getRecipeTypes(IFocus focus) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/network/NetworkMessages.java b/projects/common/src/main/java/dan200/computercraft/shared/network/NetworkMessages.java index 04d6b0cd8..216a39ca4 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/network/NetworkMessages.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/network/NetworkMessages.java @@ -42,7 +42,6 @@ public final class NetworkMessages { public static final CustomPacketPayload.Type SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage.STREAM_CODEC); public static final CustomPacketPayload.Type SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage.STREAM_CODEC); public static final CustomPacketPayload.Type UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage.STREAM_CODEC); - public static final CustomPacketPayload.Type UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage.STREAM_CODEC); private NetworkMessages() { } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/network/client/UpgradesLoadedMessage.java b/projects/common/src/main/java/dan200/computercraft/shared/network/client/UpgradesLoadedMessage.java deleted file mode 100644 index b836f6549..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/network/client/UpgradesLoadedMessage.java +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.network.client; - -import dan200.computercraft.api.pocket.IPocketUpgrade; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.upgrades.UpgradeBase; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; -import dan200.computercraft.impl.PocketUpgrades; -import dan200.computercraft.impl.RegistryHelper; -import dan200.computercraft.impl.TurtleUpgrades; -import dan200.computercraft.impl.UpgradeManager; -import dan200.computercraft.shared.network.NetworkMessage; -import dan200.computercraft.shared.network.NetworkMessages; -import net.minecraft.core.Registry; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; - -import java.util.HashMap; -import java.util.Map; - -/** - * Syncs turtle and pocket upgrades to the client. - */ -public final class UpgradesLoadedMessage implements NetworkMessage { - public static final StreamCodec STREAM_CODEC = StreamCodec.ofMember(UpgradesLoadedMessage::write, UpgradesLoadedMessage::new); - - private final Map> turtleUpgrades; - private final Map> pocketUpgrades; - - public UpgradesLoadedMessage() { - turtleUpgrades = TurtleUpgrades.instance().getUpgradeWrappers(); - pocketUpgrades = PocketUpgrades.instance().getUpgradeWrappers(); - } - - private UpgradesLoadedMessage(RegistryFriendlyByteBuf buf) { - turtleUpgrades = fromBytes(buf, ITurtleUpgrade.serialiserRegistryKey()); - pocketUpgrades = fromBytes(buf, IPocketUpgrade.serialiserRegistryKey()); - } - - private Map> fromBytes( - RegistryFriendlyByteBuf buf, ResourceKey>> registryKey - ) { - var registry = RegistryHelper.getRegistry(registryKey); - - var size = buf.readVarInt(); - Map> upgrades = new HashMap<>(size); - for (var i = 0; i < size; i++) { - var id = buf.readResourceLocation(); - - var serialiserId = buf.readResourceLocation(); - var serialiser = registry.get(serialiserId); - if (serialiser == null) throw new IllegalStateException("Unknown serialiser " + serialiserId); - - var upgrade = serialiser.fromNetwork(id, buf); - var modId = buf.readUtf(); - - upgrades.put(id, new UpgradeManager.UpgradeWrapper(id, upgrade, serialiser, modId)); - } - - return upgrades; - } - - private void write(RegistryFriendlyByteBuf buf) { - toBytes(buf, ITurtleUpgrade.serialiserRegistryKey(), turtleUpgrades); - toBytes(buf, IPocketUpgrade.serialiserRegistryKey(), pocketUpgrades); - } - - private void toBytes( - RegistryFriendlyByteBuf buf, ResourceKey>> registryKey, Map> upgrades - ) { - var registry = RegistryHelper.getRegistry(registryKey); - - buf.writeVarInt(upgrades.size()); - for (var entry : upgrades.entrySet()) { - buf.writeResourceLocation(entry.getKey()); - - var serialiser = entry.getValue().serialiser(); - @SuppressWarnings("unchecked") - var unwrappedSerialiser = (UpgradeSerialiser) serialiser; - - buf.writeResourceLocation(RegistryHelper.getKeyOrThrow(registry, serialiser)); - unwrappedSerialiser.toNetwork(buf, entry.getValue().upgrade()); - - buf.writeUtf(entry.getValue().modId()); - } - } - - @Override - public void handle(ClientNetworkContext context) { - TurtleUpgrades.instance().loadFromNetwork(turtleUpgrades); - PocketUpgrades.instance().loadFromNetwork(pocketUpgrades); - } - - @Override - public CustomPacketPayload.Type type() { - return NetworkMessages.UPGRADES_LOADED; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java index a0749450d..81c3d1f7e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java @@ -107,11 +107,11 @@ public class PocketAPI implements ILuaAPI { } } - private static @Nullable UpgradeData findUpgrade(NonNullList inv, int start, @Nullable UpgradeData previous) { + private @Nullable UpgradeData findUpgrade(NonNullList inv, int start, @Nullable UpgradeData previous) { for (var i = 0; i < inv.size(); i++) { var invStack = inv.get((i + start) % inv.size()); if (!invStack.isEmpty()) { - var newUpgrade = PocketUpgrades.instance().get(invStack); + var newUpgrade = PocketUpgrades.instance().get(computer.getLevel().registryAccess(), invStack); if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) { // Consume an item from this stack and exit the loop diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java index d9ad11cae..e6bf06627 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -100,10 +100,10 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces @Override public void setUpgradeData(DataComponentPatch data) { - var upgrade = PocketComputerItem.getUpgrade(stack); + var upgrade = PocketComputerItem.getUpgradeWithData(stack); if (upgrade == null) return; - PocketComputerItem.setUpgrade(stack, new UpgradeData<>(upgrade, data)); + PocketComputerItem.setUpgrade(stack, new UpgradeData<>(upgrade.holder(), data)); setItemChanged(); } @@ -118,7 +118,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces } public @Nullable UpgradeData getUpgrade() { - return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeData()); + return PocketComputerItem.getUpgradeWithData(stack); } /** 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 9ecb666d8..9512ae4d9 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 @@ -150,15 +150,9 @@ public class PocketComputerItem extends Item implements IMedia { @Nullable @ForgeOverride public String getCreatorModId(ItemStack stack) { - var upgrade = getUpgrade(stack); - if (upgrade != null) { - // If we're a non-vanilla, non-CC upgrade then return whichever mod this upgrade - // belongs to. - var mod = PocketUpgrades.instance().getOwner(upgrade); - if (mod != null && !mod.equals(ComputerCraftAPI.MOD_ID)) return mod; - } + var upgrade = getUpgradeWithData(stack); + return upgrade != null ? PocketUpgrades.instance().getOwner(upgrade.holder()) : ComputerCraftAPI.MOD_ID; - return ComputerCraftAPI.MOD_ID; } public PocketServerComputer createServerComputer(ServerLevel level, Entity entity, @Nullable Container inventory, ItemStack stack) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java index 273c5e8a4..b23faa456 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java @@ -7,8 +7,9 @@ package dan200.computercraft.shared.pocket.peripherals; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.pocket.AbstractPocketUpgrade; import dan200.computercraft.api.pocket.IPocketAccess; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import javax.annotation.Nullable; @@ -16,8 +17,8 @@ import javax.annotation.Nullable; public class PocketModem extends AbstractPocketUpgrade { private final boolean advanced; - public PocketModem(ResourceLocation id, ItemStack stack, boolean advanced) { - super(id, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, stack); + public PocketModem(ItemStack stack, boolean advanced) { + super(advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, stack); this.advanced = advanced; } @@ -36,4 +37,11 @@ public class PocketModem extends AbstractPocketUpgrade { var state = modem.getModemState(); if (state.pollChanged()) access.setLight(state.isOpen() ? 0xBA0000 : -1); } + + @Override + public UpgradeType getType() { + return advanced + ? ModRegistry.PocketUpgradeTypes.WIRELESS_MODEM_ADVANCED.get() + : ModRegistry.PocketUpgradeTypes.WIRELESS_MODEM_NORMAL.get(); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java index c9508cbc7..74824fc25 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java @@ -7,15 +7,16 @@ package dan200.computercraft.shared.pocket.peripherals; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.pocket.AbstractPocketUpgrade; import dan200.computercraft.api.pocket.IPocketAccess; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import javax.annotation.Nullable; public class PocketSpeaker extends AbstractPocketUpgrade { - public PocketSpeaker(ResourceLocation id, ItemStack item) { - super(id, UpgradeSpeakerPeripheral.ADJECTIVE, item); + public PocketSpeaker(ItemStack item) { + super(UpgradeSpeakerPeripheral.ADJECTIVE, item); } @Nullable @@ -29,4 +30,9 @@ public class PocketSpeaker extends AbstractPocketUpgrade { if (!(peripheral instanceof PocketSpeakerPeripheral)) return; ((PocketSpeakerPeripheral) peripheral).update(); } + + @Override + public UpgradeType getType() { + return ModRegistry.PocketUpgradeTypes.SPEAKER.get(); + } } 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 5fa758269..30ff8cc05 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 @@ -59,7 +59,7 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe { if (computer.isEmpty()) return ItemStack.EMPTY; var itemComputer = (PocketComputerItem) computer.getItem(); - if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY; + if (PocketComputerItem.getUpgradeWithData(computer) != null) return ItemStack.EMPTY; // Check for upgrades around the item UpgradeData upgrade = null; @@ -69,7 +69,7 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe { if (x == computerX && y == computerY) continue; if (x == computerX && y == computerY - 1) { - upgrade = PocketUpgrades.instance().get(item); + upgrade = PocketUpgrades.instance().get(registryAccess, item); if (upgrade == null) return ItemStack.EMPTY; } else if (!item.isEmpty()) { return ItemStack.EMPTY; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java b/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java index 003c3b255..08f603733 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/recipe/function/RecipeFunction.java @@ -7,9 +7,9 @@ package dan200.computercraft.shared.recipe.function; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.impl.RegistryHelper; import dan200.computercraft.shared.recipe.TransformShapedRecipe; import dan200.computercraft.shared.recipe.TransformShapelessRecipe; +import dan200.computercraft.shared.util.SafeDispatchCodec; import net.minecraft.core.Registry; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; @@ -44,7 +44,7 @@ public interface RecipeFunction { /** * The codec to read and write {@link RecipeFunction}s with. */ - Codec CODEC = Codec.lazyInitialized(() -> RegistryHelper.getRegistry(REGISTRY).byNameCodec().dispatch(RecipeFunction::getType, Type::codec)); + Codec CODEC = SafeDispatchCodec.ofRegistry(REGISTRY, RecipeFunction::getType, Type::codec); /** * A codec for a list of functions. 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 1c6d0b28f..142bdd4d2 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 @@ -180,7 +180,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba } private static @Nullable UpgradeData withPersistedData(@Nullable UpgradeData upgrade) { - return upgrade == null ? null : UpgradeData.of(upgrade.upgrade(), upgrade.upgrade().getPersistedData(upgrade.data())); + return upgrade == null ? null : UpgradeData.of(upgrade.holder(), upgrade.upgrade().getPersistedData(upgrade.data())); } @Override @@ -313,7 +313,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba default: return false; } - return upgrade != null && upgrade.getType().isPeripheral(); + return upgrade != null && upgrade.getUpgradeType().isPeripheral(); } public void transferStateFrom(TurtleBlockEntity copy) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index 652583f47..c4799d3fa 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -26,6 +26,7 @@ import dan200.computercraft.shared.util.Holiday; import dan200.computercraft.shared.util.NBTUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.particles.ParticleTypes; @@ -119,7 +120,7 @@ public class TurtleBrain implements TurtleAccessInternal { // Advance upgrades for (var side : TurtleSide.values()) { var upgrade = upgrades[side.ordinal()].upgrade; - if (upgrade != null) upgrade.update(this, side); + if (upgrade != null) upgrade.value().update(this, side); } } @@ -136,8 +137,8 @@ public class TurtleBrain implements TurtleAccessInternal { overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null; // Read upgrades - setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().codec(), registries, nbt, NBT_LEFT_UPGRADE)); - setUpgradeDirect(TurtleSide.RIGHT, NBTUtil.decodeFrom(TurtleUpgrades.instance().codec(), registries, nbt, NBT_RIGHT_UPGRADE)); + setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE)); + setUpgradeDirect(TurtleSide.RIGHT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_RIGHT_UPGRADE)); } private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) { @@ -146,8 +147,8 @@ public class TurtleBrain implements TurtleAccessInternal { if (overlay != null) nbt.putString(NBT_OVERLAY, overlay.toString()); // Write upgrades - NBTUtil.encodeTo(TurtleUpgrades.instance().codec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT)); - NBTUtil.encodeTo(TurtleUpgrades.instance().codec(), registries, nbt, NBT_RIGHT_UPGRADE, getUpgradeWithData(TurtleSide.RIGHT)); + NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT)); + NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_RIGHT_UPGRADE, getUpgradeWithData(TurtleSide.RIGHT)); } public void readFromNBT(CompoundTag nbt, HolderLookup.Provider registries) { @@ -459,7 +460,8 @@ public class TurtleBrain implements TurtleAccessInternal { @Override public @Nullable ITurtleUpgrade getUpgrade(TurtleSide side) { - return upgrades[side.ordinal()].upgrade; + var upgrade = upgrades[side.ordinal()].upgrade; + return upgrade == null ? null : upgrade.value(); } @Override @@ -561,7 +563,7 @@ public class TurtleBrain implements TurtleAccessInternal { for (var side : TurtleSide.values()) { var upgrade = getUpgrade(side); IPeripheral peripheral = null; - if (upgrade != null && upgrade.getType().isPeripheral()) { + if (upgrade != null && upgrade.getUpgradeType().isPeripheral()) { peripheral = upgrade.createPeripheral(this, side); } @@ -747,7 +749,7 @@ public class TurtleBrain implements TurtleAccessInternal { } private static final class UpgradeInstance { - private @Nullable ITurtleUpgrade upgrade; + private @Nullable Holder.Reference upgrade; private DataComponentPatch data = DataComponentPatch.EMPTY; private @Nullable IPeripheral peripheral; @@ -759,7 +761,7 @@ public class TurtleBrain implements TurtleAccessInternal { data = DataComponentPatch.EMPTY; cachedUpgradeData = null; } else { - this.upgrade = upgrade.upgrade(); + this.upgrade = upgrade.holder(); this.data = upgrade.data(); this.cachedUpgradeData = upgrade; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java index b4b7e910d..fdfce6a6a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java @@ -25,7 +25,7 @@ public class TurtleEquipCommand implements TurtleCommand { UpgradeData newUpgrade; var selectedStack = turtle.getInventory().getItem(turtle.getSelectedSlot()); if (!selectedStack.isEmpty()) { - newUpgrade = TurtleUpgrades.instance().get(selectedStack); + newUpgrade = TurtleUpgrades.instance().get(turtle.getLevel().registryAccess(), selectedStack); if (newUpgrade == null) return TurtleCommandResult.failure("Not a valid upgrade"); } else { newUpgrade = null; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java index 5bc19294d..5889056f2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java @@ -27,7 +27,7 @@ public class TurtleToolCommand implements TurtleCommand { if (this.side != null && this.side != side) continue; var upgrade = turtle.getUpgrade(side); - if (upgrade == null || !upgrade.getType().isTool()) continue; + if (upgrade == null || !upgrade.getUpgradeType().isTool()) continue; var result = upgrade.useTool(turtle, side, verb, direction.toWorldDir(turtle)); if (result.isSuccess()) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java index 61d2e67d8..d4709295b 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java @@ -62,8 +62,9 @@ public final class TurtleMenu extends AbstractComputerMenu { } // Turtle upgrades - addSlot(new UpgradeSlot(turtleUpgrades, TurtleSide.LEFT, 0, UPGRADE_START_X, PLAYER_START_Y + 1)); - addSlot(new UpgradeSlot(turtleUpgrades, TurtleSide.RIGHT, 1, UPGRADE_START_X, PLAYER_START_Y + 1 + 18)); + var registries = playerInventory.player.level().registryAccess(); + addSlot(new UpgradeSlot(turtleUpgrades, registries, TurtleSide.LEFT, 0, UPGRADE_START_X, PLAYER_START_Y + 1)); + addSlot(new UpgradeSlot(turtleUpgrades, registries, TurtleSide.RIGHT, 1, UPGRADE_START_X, PLAYER_START_Y + 1 + 18)); } public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java index 6f98972c9..a8dc08997 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java @@ -66,7 +66,7 @@ class UpgradeContainer implements Container { @Override public void setItem(int slot, ItemStack itemStack) { - var upgrade = TurtleUpgrades.instance().get(itemStack); + var upgrade = TurtleUpgrades.instance().get(turtle.getLevel().registryAccess(), itemStack); turtle.setUpgrade(getSide(slot), upgrade); setUpgradeStack(slot, upgrade); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeSlot.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeSlot.java index 7947785e1..fb9e42810 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeSlot.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeSlot.java @@ -8,6 +8,7 @@ import com.mojang.datafixers.util.Pair; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.impl.TurtleUpgrades; +import net.minecraft.core.HolderLookup; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.Container; import net.minecraft.world.inventory.InventoryMenu; @@ -25,16 +26,18 @@ public class UpgradeSlot extends Slot { public static final ResourceLocation LEFT_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/turtle_upgrade_left"); public static final ResourceLocation RIGHT_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/turtle_upgrade_right"); + private final HolderLookup.Provider registries; private final TurtleSide side; - public UpgradeSlot(Container container, TurtleSide side, int slot, int xPos, int yPos) { + public UpgradeSlot(Container container, HolderLookup.Provider registries, TurtleSide side, int slot, int xPos, int yPos) { super(container, slot, xPos, yPos); + this.registries = registries; this.side = side; } @Override public boolean mayPlace(ItemStack stack) { - return TurtleUpgrades.instance().get(stack) != null; + return TurtleUpgrades.instance().get(registries, stack) != null; } @Override 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 f3c8cd925..7e24218c7 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 @@ -57,16 +57,16 @@ public class TurtleItem extends AbstractComputerItem { // Determine our "creator mod" from the upgrades. We attempt to find the first non-vanilla/non-CC // upgrade (starting from the left). - var left = getUpgrade(stack, TurtleSide.LEFT); + var left = getUpgradeWithData(stack, TurtleSide.LEFT); if (left != null) { - var mod = TurtleUpgrades.instance().getOwner(left); - if (mod != null && !mod.equals(ComputerCraftAPI.MOD_ID)) return mod; + var mod = TurtleUpgrades.instance().getOwner(left.holder()); + if (!mod.equals(ComputerCraftAPI.MOD_ID)) return mod; } - var right = getUpgrade(stack, TurtleSide.RIGHT); + var right = getUpgradeWithData(stack, TurtleSide.RIGHT); if (right != null) { - var mod = TurtleUpgrades.instance().getOwner(right); - if (mod != null && !mod.equals(ComputerCraftAPI.MOD_ID)) return mod; + var mod = TurtleUpgrades.instance().getOwner(right.holder()); + if (!mod.equals(ComputerCraftAPI.MOD_ID)) return mod; } return ComputerCraftAPI.MOD_ID; 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 ab703cd80..61095eb5e 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 @@ -113,7 +113,7 @@ public final class TurtleUpgradeRecipe extends CustomRecipe { var items = new ItemStack[]{ rightItem, leftItem }; for (var i = 0; i < 2; i++) { if (!items[i].isEmpty()) { - var itemUpgrade = TurtleUpgrades.instance().get(items[i]); + var itemUpgrade = TurtleUpgrades.instance().get(registryAccess, items[i]); if (itemUpgrade == null || upgrades[i] != null) return ItemStack.EMPTY; upgrades[i] = itemUpgrade; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java index 83ce27f32..7060853d7 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java @@ -9,17 +9,23 @@ import dan200.computercraft.api.turtle.AbstractTurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleUpgradeType; -import net.minecraft.resources.ResourceLocation; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.shared.ModRegistry; import net.minecraft.world.item.ItemStack; public class TurtleCraftingTable extends AbstractTurtleUpgrade { - public TurtleCraftingTable(ResourceLocation id, ItemStack stack) { - super(id, TurtleUpgradeType.PERIPHERAL, "upgrade.minecraft.crafting_table.adjective", stack); + public TurtleCraftingTable(ItemStack stack) { + super(TurtleUpgradeType.PERIPHERAL, "upgrade.minecraft.crafting_table.adjective", stack); } @Override public IPeripheral createPeripheral(ITurtleAccess turtle, TurtleSide side) { return new CraftingTablePeripheral(turtle); } + + @Override + public UpgradeType getType() { + return ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java index eb00a11ba..547118406 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java @@ -6,12 +6,12 @@ package dan200.computercraft.shared.turtle.upgrades; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.turtle.*; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral; import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponentPatch; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; @@ -50,8 +50,8 @@ public class TurtleModem extends AbstractTurtleUpgrade { private final boolean advanced; - public TurtleModem(ResourceLocation id, ItemStack stack, boolean advanced) { - super(id, TurtleUpgradeType.PERIPHERAL, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, stack); + public TurtleModem(ItemStack stack, boolean advanced) { + super(TurtleUpgradeType.PERIPHERAL, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, stack); this.advanced = advanced; } @@ -83,4 +83,11 @@ public class TurtleModem extends AbstractTurtleUpgrade { public DataComponentPatch getPersistedData(DataComponentPatch upgradeData) { return DataComponentPatch.EMPTY; } + + @Override + public UpgradeType getType() { + return advanced + ? ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM_ADVANCED.get() + : ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM_NORMAL.get(); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java index a0766bd85..8c08e8763 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java @@ -9,9 +9,10 @@ import dan200.computercraft.api.turtle.AbstractTurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleUpgradeType; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; @@ -36,8 +37,8 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade { } } - public TurtleSpeaker(ResourceLocation id, ItemStack item) { - super(id, TurtleUpgradeType.PERIPHERAL, UpgradeSpeakerPeripheral.ADJECTIVE, item); + public TurtleSpeaker(ItemStack item) { + super(TurtleUpgradeType.PERIPHERAL, UpgradeSpeakerPeripheral.ADJECTIVE, item); } @Override @@ -50,4 +51,9 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade { var peripheral = turtle.getPeripheral(turtleSide); if (peripheral instanceof Peripheral speaker) speaker.update(); } + + @Override + public UpgradeType getType() { + return ModRegistry.TurtleUpgradeTypes.SPEAKER.get(); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 42034b06d..6e49b5292 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -4,8 +4,12 @@ package dan200.computercraft.shared.turtle.upgrades; +import com.mojang.serialization.MapCodec; import dan200.computercraft.api.ComputerCraftTags; import dan200.computercraft.api.turtle.*; +import dan200.computercraft.api.upgrades.UpgradeType; +import dan200.computercraft.impl.upgrades.TurtleToolSpec; +import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.turtle.TurtleUtil; import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; @@ -16,7 +20,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponents; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.TagKey; @@ -27,7 +30,6 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.BlockGetter; @@ -41,25 +43,26 @@ import javax.annotation.Nullable; import java.util.function.Function; public class TurtleTool extends AbstractTurtleUpgrade { + public static final MapCodec CODEC = TurtleToolSpec.CODEC.xmap(TurtleTool::new, x -> x.spec); + private static final TurtleCommandResult UNBREAKABLE = TurtleCommandResult.failure("Cannot break unbreakable block"); private static final TurtleCommandResult INEFFECTIVE = TurtleCommandResult.failure("Cannot break block with this tool"); + final TurtleToolSpec spec; final ItemStack item; final float damageMulitiplier; final boolean allowEnchantments; final TurtleToolDurability consumeDurability; final @Nullable TagKey breakable; - public TurtleTool( - ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, - boolean allowEnchantments, TurtleToolDurability consumeDurability, @Nullable TagKey breakable - ) { - super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem)); - item = toolItem; - this.damageMulitiplier = damageMulitiplier; - this.allowEnchantments = allowEnchantments; - this.consumeDurability = consumeDurability; - this.breakable = breakable; + public TurtleTool(TurtleToolSpec spec) { + super(TurtleUpgradeType.TOOL, spec.adjective(), new ItemStack(spec.craftItem().orElse(spec.toolItem()))); + this.spec = spec; + item = new ItemStack(spec.toolItem()); + this.damageMulitiplier = spec.damageMultiplier(); + this.allowEnchantments = spec.allowEnchantments(); + this.consumeDurability = spec.consumeDurability(); + this.breakable = spec.breakable().orElse(null); } @Override @@ -328,4 +331,9 @@ public class TurtleTool extends AbstractTurtleUpgrade { // Allow breaking any "instabreak" block. || state.getDestroySpeed(reader, pos) == 0; } + + @Override + public UpgradeType getType() { + return ModRegistry.TurtleUpgradeTypes.TOOL.get(); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java deleted file mode 100644 index e8e965d07..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.turtle.upgrades; - -import com.google.gson.JsonObject; -import dan200.computercraft.api.turtle.TurtleToolDurability; -import dan200.computercraft.api.upgrades.UpgradeBase; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; - -public final class TurtleToolSerialiser implements UpgradeSerialiser { - public static final TurtleToolSerialiser INSTANCE = new TurtleToolSerialiser(); - - private TurtleToolSerialiser() { - } - - @Override - public TurtleTool fromJson(ResourceLocation id, JsonObject object) { - var adjective = GsonHelper.getAsString(object, "adjective", UpgradeBase.getDefaultAdjective(id)); - var toolItem = GsonHelper.getAsItem(object, "item"); - var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem).value(); - var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f); - var allowEnchantments = GsonHelper.getAsBoolean(object, "allowEnchantments", false); - var consumeDurability = TurtleToolDurability.CODEC.byName(GsonHelper.getAsString(object, "consumeDurability", null), TurtleToolDurability.NEVER); - - TagKey breakable = null; - if (object.has("breakable")) { - var tag = new ResourceLocation(GsonHelper.getAsString(object, "breakable")); - breakable = TagKey.create(Registries.BLOCK, tag); - } - - return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowEnchantments, consumeDurability, breakable); - } - - @Override - public TurtleTool fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) { - var adjective = buffer.readUtf(); - var craftingItem = ByteBufCodecs.registry(Registries.ITEM).decode(buffer); - var toolItem = ItemStack.STREAM_CODEC.decode(buffer); - // damageMultiplier and breakable aren't used by the client, but we need to construct the upgrade exactly - // as otherwise syncing on an SP world will overwrite the (shared) upgrade registry with an invalid upgrade! - var damageMultiplier = buffer.readFloat(); - var allowsEnchantments = buffer.readBoolean(); - var consumesDurability = buffer.readEnum(TurtleToolDurability.class); - - var breakable = buffer.readNullable(b -> TagKey.create(Registries.BLOCK, b.readResourceLocation())); - return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, allowsEnchantments, consumesDurability, breakable); - } - - @Override - public void toNetwork(RegistryFriendlyByteBuf buffer, TurtleTool upgrade) { - buffer.writeUtf(upgrade.getUnlocalisedAdjective()); - ByteBufCodecs.registry(Registries.ITEM).encode(buffer, upgrade.getCraftingItem().getItem()); - ItemStack.STREAM_CODEC.encode(buffer, upgrade.item); - buffer.writeFloat(upgrade.damageMulitiplier); - buffer.writeBoolean(upgrade.allowEnchantments); - buffer.writeEnum(upgrade.consumeDurability); - buffer.writeNullable(upgrade.breakable, (b, x) -> b.writeResourceLocation(x.location())); - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java index 3f673c041..cc49eb75d 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentizationFixers.java @@ -187,10 +187,10 @@ public class ComponentizationFixers { /** * Add our custom data components to the datafixer system. * - * @param type The existing component type definition. + * @param type The existing component type definition. * @param schema The current schema. * @return The new component type definition. - * @see UpgradeManager#codec() + * @see UpgradeManager#upgradeDataCodec() * @see ModRegistry.DataComponents#POCKET_UPGRADE * @see ModRegistry.DataComponents#LEFT_TURTLE_UPGRADE * @see ModRegistry.DataComponents#RIGHT_TURTLE_UPGRADE diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/SafeDispatchCodec.java b/projects/common/src/main/java/dan200/computercraft/shared/util/SafeDispatchCodec.java new file mode 100644 index 000000000..929474655 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/SafeDispatchCodec.java @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.util; + +import com.mojang.serialization.*; +import com.mojang.serialization.codecs.KeyDispatchCodec; +import dan200.computercraft.impl.RegistryHelper; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; + +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * An implementation of {@link Codec#dispatch(Function, Function)}/{@link KeyDispatchCodec} that checks that the + * result of {@code instance.getType()} matches the supplied type. + * + * @param The type of keys. + * @param The type of values. + */ +public final class SafeDispatchCodec extends MapCodec { + private static final String TYPE_KEY = "type"; + private static final String COMPRESSED_VALUE_KEY = "value"; + + private final Codec typeCodec; + private final Function type; + private final Function> instanceCodec; + + private SafeDispatchCodec(Codec typeCodec, Function type, Function> instanceCodec) { + this.typeCodec = typeCodec; + this.type = type; + this.instanceCodec = instanceCodec; + } + + /** + * Create a new safe dispatch codec. + *

+ * This decodes the {@code "type"} field of a map (using {@code typeCodec}), and then uses {@code instanceCodec} to + * find the codec that should be used for that type. + * + * @param typeCodec The codec to decode the type. + * @param type The function to get the type of an instance. + * @param instanceCodec The codec to decode an instance. + * @param The type of keys. + * @param The type of values. + * @return The dispatch codec. + */ + public static Codec codec(Codec typeCodec, Function type, Function> instanceCodec) { + return new SafeDispatchCodec<>(typeCodec, type, instanceCodec).codec(); + } + + /** + * Create a new safe dispatch codec. + *

+ * This decodes the {@code "type"} field of a map (using {@code typeCodec}), and then uses {@code instanceCodec} to + * find the codec that should be used for that type. + * + * @param typeRegistry The built-in registry of types. + * @param type The function to get the type of an instance. + * @param instanceCodec The codec to decode an instance. + * @param The type of keys. + * @param The type of values. + * @return The dispatch codec. + */ + public static Codec ofRegistry(ResourceKey> typeRegistry, Function type, Function> instanceCodec) { + return codec(Codec.lazyInitialized(() -> RegistryHelper.getRegistry(typeRegistry).byNameCodec()), type, instanceCodec); + } + + @Override + public DataResult decode(final DynamicOps ops, final MapLike input) { + var elementName = input.get(TYPE_KEY); + if (elementName == null) { + return DataResult.error(() -> "Input does not contain a key [" + TYPE_KEY + "]: " + input); + } + + return typeCodec.decode(ops, elementName).flatMap(typeResult -> { + var type = typeResult.getFirst(); + var decoder = instanceCodec.apply(type); + + DataResult result; + if (ops.compressMaps()) { + var value = input.get(ops.createString(COMPRESSED_VALUE_KEY)); + if (value == null) return DataResult.error(() -> "Input does not have a \"value\" entry: " + input); + result = decoder.decoder().parse(ops, value); + } else { + result = decoder.decode(ops, input); + } + + return result.flatMap(x -> { + var actualType = this.type.apply(x); + if (actualType != type) { + return DataResult.error(() -> x + " has the incorrect type. Expected " + type + ", but was " + actualType + "."); + } + return DataResult.success(x); + }); + }); + } + + @Override + public RecordBuilder encode(final V input, final DynamicOps ops, final RecordBuilder builder) { + @SuppressWarnings("unchecked") var encoder = (MapCodec) instanceCodec.apply(type.apply(input)); + + if (ops.compressMaps()) { + return builder + .add(TYPE_KEY, typeCodec.encodeStart(ops, type.apply(input))) + .add(COMPRESSED_VALUE_KEY, encoder.encoder().encodeStart(ops, input)); + } + + return encoder.encode(input, ops, builder).add(TYPE_KEY, typeCodec.encodeStart(ops, type.apply(input))); + } + + @Override + public Stream keys(final DynamicOps ops) { + return Stream.of(TYPE_KEY, COMPRESSED_VALUE_KEY).map(ops::createString); + } +} diff --git a/projects/common/src/test/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiserTest.java b/projects/common/src/test/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiserTest.java deleted file mode 100644 index 43d8bb1f4..000000000 --- a/projects/common/src/test/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiserTest.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.turtle.upgrades; - -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.turtle.TurtleToolDurability; -import dan200.computercraft.test.core.StructuralEquality; -import dan200.computercraft.test.shared.MinecraftArbitraries; -import dan200.computercraft.test.shared.MinecraftEqualities; -import dan200.computercraft.test.shared.NetworkSupport; -import dan200.computercraft.test.shared.WithMinecraft; -import net.jqwik.api.*; -import net.minecraft.core.registries.Registries; - -import static org.hamcrest.MatcherAssert.assertThat; - -@WithMinecraft -class TurtleToolSerialiserTest { - static { - WithMinecraft.Setup.bootstrap(); // @Property doesn't run test lifecycle methods. - } - - /** - * Sends turtle tools on a roundtrip, ensuring that their contents are reassembled on the other end. - * - * @param tool The message to send. - */ - @Property - public void testRoundTrip(@ForAll("tool") TurtleTool tool) { - var converted = NetworkSupport.roundTripSerialiser( - tool.getUpgradeID(), tool, TurtleToolSerialiser.INSTANCE::toNetwork, TurtleToolSerialiser.INSTANCE::fromNetwork - ); - - if (!equality.equals(tool, converted)) { - System.out.println("Break"); - } - assertThat("Messages are equal", converted, equality.asMatcher(TurtleTool.class, tool)); - } - - @Provide - Arbitrary tool() { - return Combinators.combine( - MinecraftArbitraries.resourceLocation(), - Arbitraries.strings().ofMaxLength(100), - MinecraftArbitraries.item(), - MinecraftArbitraries.nonEmptyItemStack(), - Arbitraries.floats(), - Arbitraries.of(true, false), - Arbitraries.of(TurtleToolDurability.values()), - MinecraftArbitraries.tagKey(Registries.BLOCK) - ).as(TurtleTool::new); - } - - private static final StructuralEquality equality = StructuralEquality.all( - StructuralEquality.at("id", ITurtleUpgrade::getUpgradeID), - StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingItem, MinecraftEqualities.itemStack), - StructuralEquality.at("tool", x -> x.item, MinecraftEqualities.itemStack), - StructuralEquality.at("damageMulitiplier", x -> x.damageMulitiplier), - StructuralEquality.at("allowEnchantments", x -> x.allowEnchantments), - StructuralEquality.at("consumeDurability", x -> x.consumeDurability), - StructuralEquality.at("breakable", x -> x.breakable) - ); -} diff --git a/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/NetworkSupport.java b/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/NetworkSupport.java deleted file mode 100644 index d187879d0..000000000 --- a/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/NetworkSupport.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.test.shared; - -import io.netty.buffer.Unpooled; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.crafting.RecipeSerializer; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Support methods for working with Minecraft's networking code. - */ -public final class NetworkSupport { - private NetworkSupport() { - } - - /** - * Attempt to serialise and then deserialise a value. - * - * @param value The value to serialise. - * @param write Serialise this value to a buffer. - * @param read Deserialise this value from a buffer. - * @param The type of the value to round trip. - * @return The converted value, for checking equivalency. - */ - public static T roundTrip(T value, BiConsumer write, Function read) { - var buffer = new RegistryFriendlyByteBuf(Unpooled.directBuffer(), RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY)); - write.accept(value, buffer); - - var converted = read.apply(buffer); - assertEquals(buffer.readableBytes(), 0, "Whole packet was read"); - return converted; - } - - /** - * Attempt to serialise and then deserialise a value from a {@link RecipeSerializer}-like interface. - * - * @param id The id of this value. - * @param value The value to serialise. - * @param write Serialise this value to a buffer. - * @param read Deserialise this value from a buffer. - * @param The type of the value to round trip. - * @return The converted value, for checking equivalency. - */ - public static T roundTripSerialiser(ResourceLocation id, T value, BiConsumer write, BiFunction read) { - return roundTrip(value, (x, b) -> write.accept(b, x), b -> read.apply(id, b)); - } -} diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index becb5fc8c..df558ff7b 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -13,7 +13,6 @@ import dan200.computercraft.api.upgrades.UpgradeData import dan200.computercraft.core.apis.PeripheralAPI import dan200.computercraft.gametest.api.* import dan200.computercraft.gametest.core.TestHooks -import dan200.computercraft.impl.TurtleUpgrades import dan200.computercraft.mixin.gametest.GameTestHelperAccessor import dan200.computercraft.mixin.gametest.GameTestInfoAccessor import dan200.computercraft.shared.ModRegistry @@ -234,7 +233,7 @@ class Turtle_Test { val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access val upgrade = turtle.getUpgrade(TurtleSide.LEFT) assertEquals( - TurtleUpgrades.instance() + helper.level.registryAccess().registryOrThrow(ModRegistry.TURTLE_UPGRADE) .get(ResourceLocation("cctest", "wooden_pickaxe")), upgrade, "Upgrade is a wooden pickaxe", @@ -262,7 +261,10 @@ class Turtle_Test { helper.assertUpgradeItem( ItemStack(Items.WOODEN_PICKAXE), - UpgradeData.ofDefault(TurtleUpgrades.instance().get(ResourceLocation("cctest", "wooden_pickaxe"))), + UpgradeData.ofDefault( + helper.level.registryAccess().registryOrThrow(ModRegistry.TURTLE_UPGRADE) + .getHolder(ResourceLocation("cctest", "wooden_pickaxe")).orElseThrow(), + ), ) } } @@ -281,7 +283,8 @@ class Turtle_Test { val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access val upgrade = turtle.getUpgrade(TurtleSide.LEFT) assertEquals( - TurtleUpgrades.instance().get(ResourceLocation("cctest", "netherite_pickaxe")), + helper.level.registryAccess().registryOrThrow(ModRegistry.TURTLE_UPGRADE) + .get(ResourceLocation("cctest", "netherite_pickaxe")), upgrade, "Upgrade is a netherite pickaxe", ) @@ -299,8 +302,8 @@ class Turtle_Test { fail("Invalid upgrade item\n Expected => ${expected.componentsPatch}\n Actual => ${upgrade.upgradeItem.componentsPatch}") } - if (!ItemStack.matches(ItemStack(expected.item), upgrade.upgrade.craftingItem)) { - fail("Original upgrade item has changed (is now ${upgrade.upgrade.craftingItem})") + if (!ItemStack.matches(ItemStack(expected.item), upgrade.upgrade().craftingItem)) { + fail("Original upgrade item has changed (is now ${upgrade.upgrade().craftingItem})") } } diff --git a/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/netherite_pickaxe.json b/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/netherite_pickaxe.json similarity index 70% rename from projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/netherite_pickaxe.json rename to projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/netherite_pickaxe.json index 42aa7fa85..c385abddd 100644 --- a/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/netherite_pickaxe.json +++ b/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/netherite_pickaxe.json @@ -1,5 +1,6 @@ { "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_pickaxe.adjective", "item": "minecraft:netherite_pickaxe", "allowEnchantments": true, "consumeDurability": "when_enchanted" diff --git a/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/wooden_pickaxe.json b/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/wooden_pickaxe.json similarity index 63% rename from projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/wooden_pickaxe.json rename to projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/wooden_pickaxe.json index 0516dd1bb..5da827ea0 100644 --- a/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/wooden_pickaxe.json +++ b/projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/wooden_pickaxe.json @@ -1,5 +1,6 @@ { "type": "computercraft:tool", + "adjective": "upgrade.minecraft.diamond_pickaxe.adjective", "item": "minecraft:wooden_pickaxe", "consumeDurability": "always" } diff --git a/projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json b/projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json index 0e238db06..9abac80c4 100644 --- a/projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json +++ b/projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json @@ -9,8 +9,8 @@ "name": "computercraft:treasure_disk", "functions": [ { - "function": "minecraft:set_nbt", - "tag": "{\"Title\": \"Demo disk\", \"SubPath\": \"demo\", \"Colour\": 15905331}" + "function": "minecraft:set_components", + "components": {"computercraft:treasure_disk": {"name": "Demo disk","title": "demo"}} } ] } diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java index b38b532f1..acf9f8037 100644 --- a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java +++ b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java @@ -6,7 +6,7 @@ package dan200.computercraft.api.client; import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.impl.client.FabricComputerCraftAPIClientService; /** @@ -27,12 +27,12 @@ public final class FabricComputerCraftAPIClient { * This method may be used as a {@link dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller}, for * convenient use in multi-loader code. * - * @param serialiser The turtle upgrade serialiser. - * @param modeller The upgrade modeller. - * @param The type of the turtle upgrade. + * @param type The turtle upgrade type. + * @param modeller The upgrade modeller. + * @param The type of the turtle upgrade. */ - public static void registerTurtleUpgradeModeller(UpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { - getInstance().registerTurtleUpgradeModeller(serialiser, modeller); + public static void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModeller modeller) { + getInstance().registerTurtleUpgradeModeller(type, modeller); } private static FabricComputerCraftAPIClientService getInstance() { diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java b/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java index 280c30762..94ae828cc 100644 --- a/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java +++ b/projects/fabric-api/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientService.java @@ -6,7 +6,7 @@ package dan200.computercraft.impl.client; import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.impl.Services; import org.jetbrains.annotations.ApiStatus; @@ -24,7 +24,7 @@ public interface FabricComputerCraftAPIClientService { return instance == null ? Services.raise(FabricComputerCraftAPIClientService.class, Instance.ERROR) : instance; } - void registerTurtleUpgradeModeller(UpgradeSerialiser serialiser, TurtleUpgradeModeller modeller); + void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModeller modeller); final class Instance { static final @Nullable FabricComputerCraftAPIClientService INSTANCE; diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/REIComputerCraft.java b/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/REIComputerCraft.java index fb8b22a9a..7a4be36eb 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/REIComputerCraft.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/REIComputerCraft.java @@ -12,7 +12,9 @@ import dan200.computercraft.shared.turtle.items.TurtleItem; import dev.architectury.event.EventResult; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; +import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.common.entry.comparison.ItemComparatorRegistry; +import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.plugin.common.BuiltinPlugin; /** @@ -26,20 +28,27 @@ public class REIComputerCraft implements REIClientPlugin { registry.register((context, stack) -> { long hash = 1; - var left = TurtleItem.getUpgrade(stack, TurtleSide.LEFT); - var right = TurtleItem.getUpgrade(stack, TurtleSide.RIGHT); - if (left != null) hash = hash * 31 + left.getUpgradeID().hashCode(); - if (right != null) hash = hash * 31 + right.getUpgradeID().hashCode(); + var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT); + var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT); + if (left != null) hash = hash * 31 + left.holder().key().location().hashCode(); + if (right != null) hash = hash * 31 + right.holder().key().location().hashCode(); return hash; }, ModRegistry.Items.TURTLE_NORMAL.get(), ModRegistry.Items.TURTLE_ADVANCED.get()); registry.register((context, stack) -> { - var upgrade = PocketComputerItem.getUpgrade(stack); - return upgrade == null ? 1 : upgrade.getUpgradeID().hashCode(); + var upgrade = PocketComputerItem.getUpgradeWithData(stack); + return upgrade == null ? 1 : upgrade.holder().key().location().hashCode(); }, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()); } + @Override + public void registerEntries(EntryRegistry registry) { + for (var stack : RecipeModHelpers.getExtraStacks(RecipeModHelpers.getEmptyRegistryAccess())) { + registry.addEntry(EntryStacks.of(stack)); + } + } + @Override public void registerDisplays(DisplayRegistry registry) { registry.registerDisplayGenerator(BuiltinPlugin.CRAFTING, new UpgradeDisplayGenerator()); diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/UpgradeDisplayGenerator.java b/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/UpgradeDisplayGenerator.java index 5727e1c19..ebfdb2224 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/UpgradeDisplayGenerator.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/integration/rei/UpgradeDisplayGenerator.java @@ -4,6 +4,7 @@ package dan200.computercraft.client.integration.rei; +import dan200.computercraft.shared.integration.RecipeModHelpers; import dan200.computercraft.shared.integration.UpgradeRecipeGenerator; import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator; import me.shedaniel.rei.api.common.display.basic.BasicDisplay; @@ -23,7 +24,7 @@ import java.util.Optional; * Provides custom recipe and usage hints for pocket/turtle upgrades. */ class UpgradeDisplayGenerator implements DynamicDisplayGenerator> { - private final UpgradeRecipeGenerator> resolver = new UpgradeRecipeGenerator<>(GeneratedShapedDisplay::new); + private final UpgradeRecipeGenerator> resolver = new UpgradeRecipeGenerator<>(GeneratedShapedDisplay::new, RecipeModHelpers.getEmptyRegistryAccess()); @Override public Optional>> getRecipeFor(EntryStack entry) { diff --git a/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java b/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java index 83202e7f7..500b88772 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java +++ b/projects/fabric/src/client/java/dan200/computercraft/impl/client/FabricComputerCraftAPIClientImpl.java @@ -7,13 +7,13 @@ package dan200.computercraft.impl.client; import com.google.auto.service.AutoService; import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.turtle.ITurtleUpgrade; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.client.turtle.TurtleUpgradeModellers; @AutoService(FabricComputerCraftAPIClientService.class) public final class FabricComputerCraftAPIClientImpl implements FabricComputerCraftAPIClientService { @Override - public void registerTurtleUpgradeModeller(UpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { - TurtleUpgradeModellers.register(serialiser, modeller); + public void registerTurtleUpgradeModeller(UpgradeType type, TurtleUpgradeModeller modeller) { + TurtleUpgradeModellers.register(type, modeller); } } diff --git a/projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java b/projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java index ba0229964..6748fe23e 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java +++ b/projects/fabric/src/main/java/dan200/computercraft/data/FabricDataGenerators.java @@ -5,26 +5,32 @@ package dan200.computercraft.data; import com.mojang.serialization.Codec; +import com.mojang.serialization.Lifecycle; import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider; -import net.minecraft.core.HolderLookup; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; +import net.minecraft.core.*; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.DataProvider; import net.minecraft.data.PackOutput; import net.minecraft.data.tags.TagsProvider; +import net.minecraft.resources.RegistryDataLoader; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.stream.Stream; public class FabricDataGenerators implements DataGeneratorEntrypoint { @Override @@ -103,5 +109,63 @@ public class FabricDataGenerators implements DataGeneratorEntrypoint { } }); } + + @Override + public CompletableFuture createPatchedRegistries(CompletableFuture registries, RegistrySetBuilder patch) { + return registries.thenApply(oldRegistries -> { + var factory = new Cloner.Factory(); + DynamicRegistries.getDynamicRegistries().forEach(registryData -> registryData.runWithArguments(factory::addCodec)); + return patch.buildPatch(RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY), new DynamicRegistryLookup(oldRegistries), factory); + }); + } + + /** + * A {@link HolderLookup.Provider} implementation that adds any Fabric dynamic registry, if missing. + * + * @param parent The parent registry. + */ + private record DynamicRegistryLookup(HolderLookup.Provider parent) implements HolderLookup.Provider { + @Override + public Stream>> listRegistries() { + return Stream.concat( + parent.listRegistries(), + DynamicRegistries.getDynamicRegistries().stream().map(RegistryDataLoader.RegistryData::key) + ).distinct(); + } + + @Override + public Optional> lookup(ResourceKey> registryKey) { + return parent.lookup(registryKey).or(() -> Optional.of(new EmptyRegistry<>(registryKey))); + } + } + + private record EmptyRegistry( + ResourceKey> key + ) implements HolderLookup.RegistryLookup { + @Override + public Lifecycle registryLifecycle() { + return Lifecycle.stable(); + } + + @Override + public Stream> listElements() { + return Stream.empty(); + } + + @Override + public Stream> listTags() { + return Stream.empty(); + } + + @Override + public Optional> get(ResourceKey resourceKey) { + return Optional.empty(); + } + + @Override + public Optional> get(TagKey tagKey) { + return Optional.empty(); + } + } } } diff --git a/projects/fabric/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java b/projects/fabric/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java index 14073805b..07f0b872a 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java +++ b/projects/fabric/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java @@ -22,7 +22,7 @@ public final class ComputerCraftAPIImpl extends AbstractComputerCraftAPI impleme static { // This We create the registries here (rather than in the mod initialiser) to guarantee that they're available - // when people come to register upgrade serialisers. + // when people come to register upgrade types. // This is a little nasty (side effects in static constructors and all that!), but seems to be the easiest way. FabricRegistryBuilder.createSimple(turtleUpgradeRegistryId).buildAndRegister(); FabricRegistryBuilder.createSimple(pocketUpgradeRegistryId).buildAndRegister(); diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java index 526d68bac..425349db2 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -9,13 +9,13 @@ import dan200.computercraft.api.detail.FabricDetailRegistries; import dan200.computercraft.api.network.wired.WiredElementLookup; import dan200.computercraft.api.peripheral.PeripheralLookup; import dan200.computercraft.impl.Peripherals; +import dan200.computercraft.impl.PocketUpgrades; +import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.ConfigSpec; import dan200.computercraft.shared.details.FluidDetails; import dan200.computercraft.shared.network.NetworkMessages; -import dan200.computercraft.shared.network.client.UpgradesLoadedMessage; -import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral; import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; import dan200.computercraft.shared.peripheral.modem.wired.CableBlockEntity; @@ -29,6 +29,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; @@ -65,6 +66,9 @@ public class ComputerCraft { FabricRegistryBuilder.createSimple(RecipeFunction.REGISTRY).attribute(RegistryAttribute.SYNCED).buildAndRegister(); + DynamicRegistries.registerSynced(ModRegistry.TURTLE_UPGRADE, TurtleUpgrades.instance().upgradeCodec()); + DynamicRegistries.registerSynced(ModRegistry.POCKET_UPGRADE, PocketUpgrades.instance().upgradeCodec()); + ModRegistry.register(); ModRegistry.registerMainThread(); @@ -106,7 +110,6 @@ public class ComputerCraft { ((FabricConfigFile) ConfigSpec.serverSpec).unload(); }); ServerLifecycleEvents.SERVER_STARTED.register(CommonHooks::onServerStarted); - ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.register((player, joined) -> ServerNetworking.sendToPlayer(new UpgradesLoadedMessage(), player)); ServerTickEvents.START_SERVER_TICK.register(CommonHooks::onServerTickStart); ServerTickEvents.START_SERVER_TICK.register(s -> CommonHooks.onServerTickEnd()); diff --git a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java index 553a4f577..48ea95534 100644 --- a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java +++ b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java @@ -6,7 +6,7 @@ package dan200.computercraft.api.client.turtle; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleUpgradeType; -import dan200.computercraft.api.upgrades.UpgradeSerialiser; +import dan200.computercraft.api.upgrades.UpgradeType; import net.neoforged.bus.api.Event; import net.neoforged.fml.event.IModBusEvent; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; @@ -33,7 +33,7 @@ public class RegisterTurtleModellersEvent extends Event implements IModBusEvent, * {@inheritDoc} */ @Override - public void register(UpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { - dispatch.register(serialiser, modeller); + public void register(UpgradeType type, TurtleUpgradeModeller modeller) { + dispatch.register(type, modeller); } } diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java index f2006cc75..1d2de4570 100644 --- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java @@ -12,7 +12,9 @@ import dan200.computercraft.api.network.wired.WiredElementCapability; import dan200.computercraft.api.peripheral.PeripheralCapability; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.impl.PocketUpgrades; import dan200.computercraft.impl.Services; +import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.CommonHooks; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.config.Config; @@ -48,6 +50,7 @@ import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import net.neoforged.neoforge.registries.DataPackRegistryEvent; import net.neoforged.neoforge.registries.NewRegistryEvent; import net.neoforged.neoforge.registries.RegistryBuilder; @@ -80,11 +83,17 @@ public final class ComputerCraft { @SubscribeEvent public static void registerRegistries(NewRegistryEvent event) { - event.create(new RegistryBuilder<>(ITurtleUpgrade.serialiserRegistryKey())); - event.create(new RegistryBuilder<>(IPocketUpgrade.serialiserRegistryKey())); + event.create(new RegistryBuilder<>(ITurtleUpgrade.typeRegistry())); + event.create(new RegistryBuilder<>(IPocketUpgrade.typeRegistry())); event.create(new RegistryBuilder<>(RecipeFunction.REGISTRY).sync(true)); } + @SubscribeEvent + public static void registerDynamicRegistries(DataPackRegistryEvent.NewRegistry event) { + event.dataPackRegistry(ModRegistry.TURTLE_UPGRADE, TurtleUpgrades.instance().upgradeCodec(), TurtleUpgrades.instance().upgradeCodec()); + event.dataPackRegistry(ModRegistry.POCKET_UPGRADE, PocketUpgrades.instance().upgradeCodec(), PocketUpgrades.instance().upgradeCodec()); + } + @SubscribeEvent public static void init(FMLCommonSetupEvent event) { event.enqueueWork(ModRegistry::registerMainThread); diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java b/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java index 7aea705a8..f659695dd 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java @@ -6,8 +6,6 @@ package dan200.computercraft.shared; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.shared.command.CommandComputerCraft; -import dan200.computercraft.shared.network.client.UpgradesLoadedMessage; -import dan200.computercraft.shared.network.server.ServerNetworking; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; @@ -15,7 +13,10 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.neoforge.event.*; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.LootTableLoadEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.TickEvent; import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent; import net.neoforged.neoforge.event.entity.living.LivingDropsEvent; import net.neoforged.neoforge.event.level.ChunkEvent; @@ -80,16 +81,6 @@ public class ForgeCommonHooks { CommonHooks.onDatapackReload((id, listener) -> event.addListener(listener)); } - @SubscribeEvent - public static void onDatapackSync(OnDatapackSyncEvent event) { - var packet = new UpgradesLoadedMessage(); - if (event.getPlayer() == null) { - ServerNetworking.sendToAllPlayers(packet, event.getPlayerList().getServer()); - } else { - ServerNetworking.sendToPlayer(packet, event.getPlayer()); - } - } - @SubscribeEvent public static void lootLoad(LootTableLoadEvent event) { var pool = CommonHooks.getExtraLootPool(ResourceKey.create(Registries.LOOT_TABLE, event.getName()));