1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-21 03:39:44 +00:00

Rewrite upgrades to use dynamic registries

Ever since 1.17, turtle and pocket upgrades have been loaded from
datpacks, rather than being hard-coded in Java. However, upgrades have
always been stored in our own registry-like structure, rather than using
vanilla's registries.

This has become a bit of a problem with the introduction of components.
The upgrade components now hold the upgrade object (rather than just its
id), which means we need the upgrades to be available much earlier (e.g.
when reading recipes).

The easiest fix here is to store upgrades in proper registries instead.
This means that upgrades can no longer be reloaded (it requires a world
restart), but otherwise is much nicer:

 - UpgradeData now stores a Holder<T> rather than a T.

 - UpgradeSerialiser has been renamed to UpgradeType. This now just
   provides a Codec<T>, rather than JSON and network reading/writing
   functions.

 - Upgrade classes no longer implement getUpgradeID(), but instead have
   a getType() function, which returns the associated UpgradeType.

 - Upgrades are now stored in turtle_upgrade (or pocket_upgrade) rather
   than turtle_upgrades (or pocket_upgrades). This will break existing
   datapacks, sorry!
This commit is contained in:
Jonathan Coates 2024-04-28 19:46:18 +01:00
parent cd9840d1c1
commit bf203bb1f3
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
94 changed files with 1031 additions and 1115 deletions

View File

@ -6,7 +6,7 @@ Upstream-Contact: Jonathan Coates <git@squiddev.cc>
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

View File

@ -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 type The turtle upgrade type.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
<T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
<T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller);
}

View File

@ -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;

View File

@ -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.
* <p>
* 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.
* <p>
* 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;
*
* <h2>Example</h2>
* <pre>{@code
* // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
* static final DeferredRegister<UpgradeSerialiser<? extends IPocketUpgrade>> 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<UpgradeType<? extends IPocketUpgrade>> POCKET_UPGRADES = DeferredRegister.create(IPocketUpgrade.typeRegistry(), "my_mod");
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register("my_upgrade", () -> UpgradeSerialiser.simple(MyUpgrade::new));
* // Register a new upgrade upgrade type called "my_upgrade".
* public static final RegistryObject<UpgradeType<MyUpgrade>> MY_UPGRADE =
* POCKET_UPGRADES.register("my_upgrade", () -> UpgradeType.simple(new MyUpgrade()));
*
* // Then in your constructor
* SERIALISERS.register(bus);
* POCKET_UPGRADES.register(bus);
* }</pre>
* <p>
* 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<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> serialiserRegistryKey() {
static ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> typeRegistry() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Get the type of this upgrade.
*
* @return The type of this upgrade.
*/
@Override
UpgradeType<? extends IPocketUpgrade> getType();
/**
* Creates a peripheral for the pocket computer.
* <p>

View File

@ -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<IPocketUpgrade> {
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());
}
}

View File

@ -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;
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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;
*
* <h2>Example</h2>
* <pre>{@code
* // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
* static final DeferredRegister<UpgradeSerialiser<? extends ITurtleUpgrade>> 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<UpgradeType<? extends ITurtleUpgrade>> TURTLE_UPGRADES = DeferredRegister.create(ITurtleUpgrade.typeRegistry(), "my_mod");
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
* // Register a new upgrade type called "my_upgrade".
* public static final RegistryObject<UpgradeType<MyUpgrade>> MY_UPGRADE =
* TURTLE_UPGRADES.register("my_upgrade", () -> UpgradeType.simple(MyUpgrade::new));
*
* // Then in your constructor
* SERIALISERS.register( bus );
* TURTLE_UPGRADES.register(bus);
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
@ -44,7 +44,7 @@ import javax.annotation.Nullable;
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* "type": "my_mod:my_upgrade"
* }
* }</pre>
* <p>
@ -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<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> serialiserRegistryKey() {
static ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> typeRegistry() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Get the type of this upgrade.
*
* @return The type of this upgrade.
*/
@Override
UpgradeType<? extends ITurtleUpgrade> 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.

View File

@ -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<ITurtleUpgrade> {
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<ITur
* @return A tool builder,
*/
public final ToolBuilder tool(ResourceLocation id, Item item) {
return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
return new ToolBuilder(id, item);
}
/**
@ -56,20 +55,19 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*
* @see #tool(ResourceLocation, Item)
*/
public static class ToolBuilder {
public final class ToolBuilder {
private final ResourceLocation id;
private final UpgradeSerialiser<? extends ITurtleUpgrade> 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<Block> breakable;
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
ToolBuilder(ResourceLocation id, UpgradeSerialiser<? extends ITurtleUpgrade> 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<ITur
*
* @param add The callback given to {@link #addUpgrades(Consumer)}.
*/
public void add(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> 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<Upgrade<ITurtleUpgrade>> add) {
upgrade(id, ComputerCraftAPIService.get().createTurtleTool(new TurtleToolSpec(
adjective,
Optional.ofNullable(craftingItem),
toolItem,
damageMultiplier,
allowEnchantments,
consumeDurability,
Optional.ofNullable(breakable)
))).add(add);
}
}
}

View File

@ -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".
* <p>
* 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.

View File

@ -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 holder The current upgrade holder.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(T upgrade, DataComponentPatch data) {
public record UpgradeData<T extends UpgradeBase>(Holder.Reference<T> holder, DataComponentPatch data) {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
* @param upgrade An upgrade.
* @param holder An upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance.
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, DataComponentPatch data) {
return new UpgradeData<>(upgrade, data);
public static <T extends UpgradeBase> UpgradeData<T> of(Holder.Reference<T> 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 holder The upgrade instance.
* @param <T> The type of upgrade.
* @return The default upgrade data.
*/
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(Holder.Reference<T> 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 extends UpgradeBase>(T upgrade, DataComponentPatch d
* @return This upgrade's item.
*/
public ItemStack getUpgradeItem() {
return upgrade.getUpgradeItem(data).copy();
return upgrade().getUpgradeItem(data).copy();
}
}

View File

@ -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<T extends UpgradeBase> implements DataProvider {
private final PackOutput output;
private final String name;
private final String folder;
private final Registry<UpgradeSerialiser<? extends T>> registry;
private final ResourceKey<Registry<T>> registryName;
private final Codec<T> codec;
private @Nullable List<T> upgrades;
private @Nullable Map<ResourceKey<T>, T> upgrades;
@ApiStatus.Internal
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
protected UpgradeDataProvider(PackOutput output, String name, ResourceKey<Registry<T>> registryName, Codec<T> 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 upgrade The upgrade to add.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<UpgradeSerialiser<? extends T>> simple(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser) {
if (!(serialiser instanceof SimpleSerialiser)) {
throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
}
return new Upgrade<>(id, serialiser, s -> {
protected final Upgrade<T> 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<UpgradeSerialiser<? extends T>> simpleWithCustomItem(ResourceLocation id, UpgradeSerialiser<? extends T> 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.
* <p>
* <strong>Example usage:</strong>
* <pre>{@code
* protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
* simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
* protected void addUpgrades(Consumer<Upgrade<ITurtleUpgrade>> addUpgrade) {
* upgrade(new ResourceLocation("mymod", "speaker"), new TurtleSpeaker(new ItemStack(Items.NOTE_BLOCK))).add(addUpgrade);
* }
* }</pre>
*
* @param addUpgrade A callback used to register an upgrade.
*/
protected abstract void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends T>>> addUpgrade);
protected abstract void addUpgrades(Consumer<Upgrade<T>> 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<ResourceKey<T>, T> upgrades = new LinkedHashMap<>();
Set<ResourceLocation> seen = new HashSet<>();
List<T> upgrades = new ArrayList<>();
List<CompletableFuture<?>> 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<T extends UpgradeBase> implements Data
return name;
}
public final UpgradeSerialiser<? extends T> existingSerialiser(ResourceLocation id) {
var result = registry.get(id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + id);
return result;
}
public List<T> 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<ResourceKey<T>, 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 <R> The type of upgrade serialiser.
* @param <T> The type of upgrade.
*/
public record Upgrade<R extends UpgradeSerialiser<?>>(
ResourceLocation id, R serialiser, Consumer<JsonObject> serialise
) {
public static final class Upgrade<T extends UpgradeBase> {
private final ResourceLocation id;
private final T upgrade;
private final Consumer<JsonObject> serialise;
private Upgrade(ResourceLocation id, T upgrade, Consumer<JsonObject> 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<Upgrade<R>> add) {
public void add(Consumer<Upgrade<T>> add) {
add.accept(this);
}
@ -170,8 +144,8 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase> implements Data
* @param modId The id of the mod.
* @return A new upgrade instance.
*/
public Upgrade<R> requireMod(String modId) {
return new Upgrade<>(id, serialiser, json -> {
public Upgrade<T> requireMod(String modId) {
return new Upgrade<>(id, upgrade, json -> {
PlatformHelper.get().addRequiredModCondition(json, modId);
serialise.accept(json);
});

View File

@ -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.
* <p>
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* 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.
* <p>
* 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.
* <p>
* Upgrades may be data generated via a {@link UpgradeDataProvider} (see {@link TurtleUpgradeDataProvider} and
* {@link PocketUpgradeDataProvider}).
*
* @param <T> The upgrade that this class can serialise and deserialise.
* @see ITurtleUpgrade
* @see IPocketUpgrade
*/
public interface UpgradeSerialiser<T extends UpgradeBase> {
/**
* 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.
* <p>
* 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 <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends UpgradeBase> UpgradeSerialiser<T> simple(Function<ResourceLocation, T> 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()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends UpgradeBase> UpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
return new SerialiserWithCraftingItem<>(factory);
}
}

View File

@ -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.
* <p>
* 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}.
* <p>
* First, one adds a new class implementing {@link ITurtleUpgrade} or {@link IPocketUpgrade}). This is responsible for
* handling all the logic of your upgrade.
* <p>
* 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()}).
* <p>
* 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 <T> The upgrade subclass that this upgrade type represents.
* @see ITurtleUpgrade
* @see IPocketUpgrade
*/
public interface UpgradeType<T extends UpgradeBase> {
/**
* The codec to read and write this upgrade from a datapack.
*
* @return The codec for this upgrade.
*/
MapCodec<T> codec();
/**
* Create a new upgrade type.
*
* @param codec The codec
* @param <T> The type of the generated upgrade.
* @return The newly created upgrade type.
*/
static <T extends UpgradeBase> UpgradeType<T> create(MapCodec<T> codec) {
return new UpgradeTypeImpl<>(codec);
}
/**
* Create an upgrade type for an upgrade that takes no arguments.
* <p>
* 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 <T> The type of the generated upgrade.
* @return A new upgrade type.
*/
static <T extends UpgradeBase> UpgradeType<T> 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()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return A new upgrade type.
* @see #simple(UpgradeBase) For upgrades whose crafting stack should not vary.
*/
static <T extends UpgradeBase> UpgradeType<T> simpleWithCustomItem(Function<ItemStack, T> factory) {
return create(BuiltInRegistries.ITEM.byNameCodec()
.xmap(x -> factory.apply(new ItemStack(x)), x -> x.getCraftingItem().getItem())
.fieldOf("item"));
}
}

View File

@ -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<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
Codec<ITurtleUpgrade> turtleUpgradeCodec();
ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
ITurtleUpgrade createTurtleTool(TurtleToolSpec spec);
Codec<IPocketUpgrade> pocketUpgradeCodec();
DetailRegistry<ItemStack> getItemStackDetailRegistry();

View File

@ -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<Registry<ITurtleUpgrade>> TURTLE_UPGRADE = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade"));
public static final ResourceKey<Registry<IPocketUpgrade>> POCKET_UPGRADE = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade"));
private RegistryHelper() {
}

View File

@ -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.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public final class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final BiFunction<ResourceLocation, ItemStack, T> factory;
public SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> 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());
}
}

View File

@ -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.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public final class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final Function<ResourceLocation, T> constructor;
public SimpleSerialiser(Function<ResourceLocation, T> 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) {
}
}

View File

@ -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<Item> craftItem,
Item toolItem,
float damageMultiplier,
boolean allowEnchantments,
TurtleToolDurability consumeDurability,
Optional<TagKey<Block>> breakable
) {
public static final float DEFAULT_DAMAGE_MULTIPLIER = 3.0f;
public static final MapCodec<TurtleToolSpec> 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));
}

View File

@ -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 <T> The upgrade subclass that this upgrade type represents.
*/
@ApiStatus.Internal
public record UpgradeTypeImpl<T extends UpgradeBase>(MapCodec<T> codec) implements UpgradeType<T> {
}

View File

@ -128,8 +128,8 @@ public final class ClientHooks {
}
private static void addTurtleUpgrade(Consumer<String> 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()));
}
/**

View File

@ -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

View File

@ -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<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) ->
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
private static final Map<UpgradeSerialiser<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
private static final Map<UpgradeType<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
private static volatile boolean fetchedModels;
/**
@ -44,15 +43,15 @@ public final class TurtleUpgradeModellers {
private TurtleUpgradeModellers() {
}
public static <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
public static <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModeller<T> 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;
}

View File

@ -0,0 +1,6 @@
{
"type": "computercraft:tool",
"adjective": "upgrade.minecraft.diamond_axe.adjective",
"damageMultiplier": 6.0,
"item": "minecraft:diamond_axe"
}

View File

@ -0,0 +1,6 @@
{
"type": "computercraft:tool",
"adjective": "upgrade.minecraft.diamond_hoe.adjective",
"breakable": "computercraft:turtle_hoe_harvestable",
"item": "minecraft:diamond_hoe"
}

View File

@ -0,0 +1,5 @@
{
"type": "computercraft:tool",
"adjective": "upgrade.minecraft.diamond_pickaxe.adjective",
"item": "minecraft:diamond_pickaxe"
}

View File

@ -1,5 +1,6 @@
{
"type": "computercraft:tool",
"adjective": "upgrade.minecraft.diamond_shovel.adjective",
"breakable": "computercraft:turtle_shovel_harvestable",
"item": "minecraft:diamond_shovel"
}

View File

@ -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"

View File

@ -1 +0,0 @@
{"type": "computercraft:tool", "damageMultiplier": 6.0, "item": "minecraft:diamond_axe"}

View File

@ -1 +0,0 @@
{"type": "computercraft:tool", "breakable": "computercraft:turtle_hoe_harvestable", "item": "minecraft:diamond_hoe"}

View File

@ -1 +0,0 @@
{"type": "computercraft:tool", "item": "minecraft:diamond_pickaxe"}

View File

@ -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<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
TagsProvider<Item> itemTags(Consumer<TagProvider.ItemTagConsumer> tags, TagsProvider<Block> 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<RegistrySetBuilder.PatchedRegistries> createPatchedRegistries(
CompletableFuture<HolderLookup.Provider> registries, RegistrySetBuilder patch
) {
return RegistryPatchGenerator.createLookup(registries, patch);
}
}
}

View File

@ -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),

View File

@ -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<Upgrade<UpgradeSerialiser<? extends IPocketUpgrade>>> 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<Upgrade<IPocketUpgrade>> 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) {

View File

@ -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<HolderLookup.Provider> registries, TurtleUpgradeDataProvider turtleUpgrades, PocketUpgradeDataProvider pocketUpgrades) {
private final CompletableFuture<HolderLookup.Provider> registries;
RecipeProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> 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));
@ -120,14 +133,16 @@ 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 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()))
);
}
});
}
}
@ -150,14 +165,16 @@ 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 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()))
);
}
});
}
}

View File

@ -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<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> 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<Upgrade<ITurtleUpgrade>> 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) {

View File

@ -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<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic);
private final DetailRegistry<BlockReference> blockDetails = new DetailRegistryImpl<>(BlockDetails::fillBasic);
protected static final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
protected static final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
protected static final ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_type"));
protected static final ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> 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<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId() {
public final ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId() {
return turtleUpgradeRegistryId;
}
@Override
public final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId() {
public Codec<ITurtleUpgrade> turtleUpgradeCodec() {
return TurtleUpgrades.instance().upgradeCodec();
}
@Override
public ITurtleUpgrade createTurtleTool(TurtleToolSpec spec) {
return new TurtleTool(spec);
}
@Override
public final ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId() {
return pocketUpgradeRegistryId;
}
@Override
public Codec<IPocketUpgrade> pocketUpgradeCodec() {
return PocketUpgrades.instance().upgradeCodec();
}
@Override
public final DetailRegistry<ItemStack> getItemStackDetailRegistry() {
return itemStackDetails;

View File

@ -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<IPocketUpgrade> 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<IPocketUpgrade> instance() {
return registry;
}
public static Stream<IPocketUpgrade> getVanillaUpgrades() {
return instance().getUpgradeWrappers().values().stream()
.filter(x -> x.modId().equals(ComputerCraftAPI.MOD_ID))
.map(UpgradeManager.UpgradeWrapper::upgrade);
}
}

View File

@ -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<ITurtleUpgrade> 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<ITurtleUpgrade> instance() {
return registry;
}
public static Stream<ITurtleUpgrade> getVanillaUpgrades() {
return instance().getUpgradeWrappers().values().stream()
.filter(x -> x.modId().equals(ComputerCraftAPI.MOD_ID))
.map(UpgradeManager.UpgradeWrapper::upgrade);
}
}

View File

@ -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<T extends UpgradeBase> 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<T extends UpgradeBase> {
private final ResourceKey<Registry<T>> registry;
private final Codec<T> upgradeCodec;
private final Codec<UpgradeData<T>> dataCodec;
private final StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> dataStreamCodec;
public record UpgradeWrapper<T extends UpgradeBase>(
ResourceLocation id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId
UpgradeManager(
ResourceKey<Registry<UpgradeType<? extends T>>> typeRegistry,
ResourceKey<Registry<T>> registry,
Function<T, UpgradeType<? extends T>> getType
) {
}
this.registry = registry;
private final String kind;
private final ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry;
upgradeCodec = SafeDispatchCodec.ofRegistry(typeRegistry, getType, UpgradeType::codec);
private Map<ResourceLocation, UpgradeWrapper<T>> current = Map.of();
private Map<T, UpgradeWrapper<T>> currentWrappers = Map.of();
private final Codec<T> 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<UpgradeData<T>> fullCodec = RecordCodecBuilder.create(i -> i.group(
upgradeCodec.fieldOf("id").forGetter(UpgradeData::upgrade),
var holderCodec = RegistryFixedCodec.create(registry).xmap(x -> (Holder.Reference<T>) x, x -> x);
Codec<UpgradeData<T>> 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);
private final Codec<UpgradeData<T>> codec = Codec.withAlternative(fullCodec, upgradeCodec, UpgradeData::ofDefault);
private final StreamCodec<ByteBuf, T> 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<RegistryFriendlyByteBuf, UpgradeData<T>> streamCodec = StreamCodec.composite(
upgradeStreamCodec, UpgradeData::upgrade,
dataStreamCodec = StreamCodec.composite(
ByteBufCodecs.holderRegistry(registry).map(x -> (Holder.Reference<T>) x, x -> x), UpgradeData::holder,
DataComponentPatch.STREAM_CODEC, UpgradeData::data,
UpgradeData::new
);
}
public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
super(GSON, path);
this.kind = kind;
this.registry = registry;
/**
* The codec for an upgrade instance.
*
* @return The instance codec.
*/
public Codec<T> upgradeCodec() {
return upgradeCodec;
}
/**
* The codec for an upgrade and its associated data.
*
* @return The upgrade data codec.
*/
public Codec<UpgradeData<T>> upgradeDataCodec() {
return dataCodec;
}
/**
* The stream codec for an upgrade and its associated data.
*
* @return The upgrade data codec.
*/
public StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> upgradeDataStreamCodec() {
return dataStreamCodec;
}
public String getOwner(Holder.Reference<T> 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<T> 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<T> get(ItemStack stack) {
public UpgradeData<T> 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<T> getUpgrades() {
return currentWrappers.keySet();
}
public Map<ResourceLocation, UpgradeWrapper<T>> getUpgradeWrappers() {
return current;
}
public Codec<UpgradeData<T>> codec() {
return codec;
}
public StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> streamCodec() {
return streamCodec;
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) {
var registry = RegistryHelper.getRegistry(this.registry);
Map<ResourceLocation, UpgradeWrapper<T>> 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<UpgradeSerialiser<? extends T>> registry, Map<ResourceLocation, UpgradeWrapper<T>> 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<T>(id, upgrade, serialiser, modId);
current.put(result.id(), result);
}
public void loadFromNetwork(Map<ResourceLocation, UpgradeWrapper<T>> 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);
}
}

View File

@ -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<String, PreparableReloadListener> 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) {

View File

@ -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<Registry<ITurtleUpgrade>> TURTLE_UPGRADE = RegistryHelper.TURTLE_UPGRADE;
public static final ResourceKey<Registry<IPocketUpgrade>> POCKET_UPGRADE = RegistryHelper.POCKET_UPGRADE;
public static final class Blocks {
static final RegistrationHelper<Block> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK);
@ -289,7 +300,7 @@ public final class ModRegistry {
* @see TurtleItem
*/
public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> 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<DataComponentType<UpgradeData<ITurtleUpgrade>>> 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<DataComponentType<UpgradeData<IPocketUpgrade>>> 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<UpgradeSerialiser<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.serialiserRegistryKey());
public static class TurtleUpgradeTypes {
static final RegistrationHelper<UpgradeType<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.typeRegistry());
public static final RegistryEntry<UpgradeSerialiser<TurtleSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
public static final RegistryEntry<UpgradeSerialiser<TurtleCraftingTable>> WORKBENCH =
REGISTRY.register("workbench", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new));
public static final RegistryEntry<UpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false)));
public static final RegistryEntry<UpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true)));
public static final RegistryEntry<UpgradeType<TurtleSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> UpgradeType.simpleWithCustomItem(TurtleSpeaker::new));
public static final RegistryEntry<UpgradeType<TurtleCraftingTable>> WORKBENCH =
REGISTRY.register("workbench", () -> UpgradeType.simpleWithCustomItem(TurtleCraftingTable::new));
public static final RegistryEntry<UpgradeType<TurtleModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> UpgradeType.simpleWithCustomItem(item -> new TurtleModem(item, false)));
public static final RegistryEntry<UpgradeType<TurtleModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> UpgradeType.simpleWithCustomItem(item -> new TurtleModem(item, true)));
public static final RegistryEntry<UpgradeSerialiser<TurtleTool>> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE);
public static final RegistryEntry<UpgradeType<TurtleTool>> TOOL = REGISTRY.register("tool", () -> UpgradeType.create(TurtleTool.CODEC));
}
public static class PocketUpgradeSerialisers {
static final RegistrationHelper<UpgradeSerialiser<? extends IPocketUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.serialiserRegistryKey());
public static class PocketUpgradeTypes {
static final RegistrationHelper<UpgradeType<? extends IPocketUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.typeRegistry());
public static final RegistryEntry<UpgradeSerialiser<PocketSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
public static final RegistryEntry<UpgradeSerialiser<PocketModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false)));
public static final RegistryEntry<UpgradeSerialiser<PocketModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true)));
public static final RegistryEntry<UpgradeType<PocketSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> UpgradeType.simpleWithCustomItem(PocketSpeaker::new));
public static final RegistryEntry<UpgradeType<PocketModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> UpgradeType.simpleWithCustomItem(item -> new PocketModem(item, false)));
public static final RegistryEntry<UpgradeType<PocketModem>> 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<? extends UpgradeBase> upgrade) {
var namespace = upgrade.key().location().getNamespace();
return namespace.equals("minecraft") || namespace.equals(ComputerCraftAPI.MOD_ID);
}
}

View File

@ -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<ItemStack> getExtraStacks() {
public static List<ItemStack> getExtraStacks(HolderLookup.Provider registries) {
List<ItemStack> 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 <T> void forEachRegistry(HolderLookup.Provider registries, ResourceKey<Registry<T>> registry, Consumer<Holder.Reference<T>> consumer) {
registries.lookup(registry).map(HolderLookup::listElements).orElse(Stream.empty()).forEach(consumer);
}
}

View File

@ -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<T> {
private final Function<ShapedRecipe, T> wrap;
private final HolderLookup.Provider registries;
private final Map<Item, List<UpgradeInfo>> upgradeItemLookup = new HashMap<>();
private final List<UpgradeInfo> pocketUpgrades = new ArrayList<>();
private final List<UpgradeInfo> turtleUpgrades = new ArrayList<>();
private boolean initialised = false;
public UpgradeRecipeGenerator(Function<ShapedRecipe, T> wrap) {
public UpgradeRecipeGenerator(Function<ShapedRecipe, T> wrap, HolderLookup.Provider registries) {
this.wrap = wrap;
this.registries = registries;
}
/**
@ -56,23 +57,25 @@ public class UpgradeRecipeGenerator<T> {
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<T> {
private class UpgradeInfo {
final ItemStack stack;
final Ingredient ingredient;
final @Nullable ITurtleUpgrade turtle;
final @Nullable IPocketUpgrade pocket;
final @Nullable Holder.Reference<ITurtleUpgrade> turtle;
final @Nullable Holder.Reference<IPocketUpgrade> pocket;
final UpgradeBase upgrade;
private @Nullable ArrayList<T> recipes;
UpgradeInfo(ItemStack stack, ITurtleUpgrade turtle) {
UpgradeInfo(ItemStack stack, UpgradeBase upgrade, @Nullable Holder.Reference<ITurtleUpgrade> turtle, @Nullable Holder.Reference<IPocketUpgrade> 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<T> getRecipes() {

View File

@ -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();
};

View File

@ -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<RecipeHolder<CraftingRecipe>> resolver = new UpgradeRecipeGenerator<>(x -> new RecipeHolder<>(RECIPE_ID, x));
private final UpgradeRecipeGenerator<RecipeHolder<CraftingRecipe>> resolver = new UpgradeRecipeGenerator<>(x -> new RecipeHolder<>(RECIPE_ID, x), RecipeModHelpers.getEmptyRegistryAccess());
@Override
public <V> List<RecipeType<?>> getRecipeTypes(IFocus<V> focus) {

View File

@ -42,7 +42,6 @@ public final class NetworkMessages {
public static final CustomPacketPayload.Type<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<UploadResultMessage> UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage.STREAM_CODEC);
private NetworkMessages() {
}

View File

@ -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<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, UpgradesLoadedMessage> STREAM_CODEC = StreamCodec.ofMember(UpgradesLoadedMessage::write, UpgradesLoadedMessage::new);
private final Map<ResourceLocation, UpgradeManager.UpgradeWrapper<ITurtleUpgrade>> turtleUpgrades;
private final Map<ResourceLocation, UpgradeManager.UpgradeWrapper<IPocketUpgrade>> 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 <T extends UpgradeBase> Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> fromBytes(
RegistryFriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey
) {
var registry = RegistryHelper.getRegistry(registryKey);
var size = buf.readVarInt();
Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> 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<T>(id, upgrade, serialiser, modId));
}
return upgrades;
}
private void write(RegistryFriendlyByteBuf buf) {
toBytes(buf, ITurtleUpgrade.serialiserRegistryKey(), turtleUpgrades);
toBytes(buf, IPocketUpgrade.serialiserRegistryKey(), pocketUpgrades);
}
private <T extends UpgradeBase> void toBytes(
RegistryFriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey, Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> 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<T>) 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<UpgradesLoadedMessage> type() {
return NetworkMessages.UPGRADES_LOADED;
}
}

View File

@ -107,11 +107,11 @@ public class PocketAPI implements ILuaAPI {
}
}
private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) {
private @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> 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

View File

@ -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<IPocketUpgrade> getUpgrade() {
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeData());
return PocketComputerItem.getUpgradeWithData(stack);
}
/**

View File

@ -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) {

View File

@ -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<PocketModem> getType() {
return advanced
? ModRegistry.PocketUpgradeTypes.WIRELESS_MODEM_ADVANCED.get()
: ModRegistry.PocketUpgradeTypes.WIRELESS_MODEM_NORMAL.get();
}
}

View File

@ -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<PocketSpeaker> getType() {
return ModRegistry.PocketUpgradeTypes.SPEAKER.get();
}
}

View File

@ -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<IPocketUpgrade> 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;

View File

@ -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<RecipeFunction> CODEC = Codec.lazyInitialized(() -> RegistryHelper.getRegistry(REGISTRY).byNameCodec().dispatch(RecipeFunction::getType, Type::codec));
Codec<RecipeFunction> CODEC = SafeDispatchCodec.ofRegistry(REGISTRY, RecipeFunction::getType, Type::codec);
/**
* A codec for a list of functions.

View File

@ -180,7 +180,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
}
private static @Nullable UpgradeData<ITurtleUpgrade> withPersistedData(@Nullable UpgradeData<ITurtleUpgrade> 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) {

View File

@ -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<ITurtleUpgrade> 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;
}

View File

@ -25,7 +25,7 @@ public class TurtleEquipCommand implements TurtleCommand {
UpgradeData<ITurtleUpgrade> 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;

View File

@ -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()) {

View File

@ -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) {

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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<TurtleCraftingTable> getType() {
return ModRegistry.TurtleUpgradeTypes.WORKBENCH.get();
}
}

View File

@ -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<TurtleModem> getType() {
return advanced
? ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM_ADVANCED.get()
: ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM_NORMAL.get();
}
}

View File

@ -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<TurtleSpeaker> getType() {
return ModRegistry.TurtleUpgradeTypes.SPEAKER.get();
}
}

View File

@ -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<TurtleTool> 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<Block> breakable;
public TurtleTool(
ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier,
boolean allowEnchantments, TurtleToolDurability consumeDurability, @Nullable TagKey<Block> 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<TurtleTool> getType() {
return ModRegistry.TurtleUpgradeTypes.TOOL.get();
}
}

View File

@ -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<TurtleTool> {
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<Block> 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()));
}
}

View File

@ -190,7 +190,7 @@ public class ComponentizationFixers {
* @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

View File

@ -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 <K> The type of keys.
* @param <V> The type of values.
*/
public final class SafeDispatchCodec<K, V> extends MapCodec<V> {
private static final String TYPE_KEY = "type";
private static final String COMPRESSED_VALUE_KEY = "value";
private final Codec<K> typeCodec;
private final Function<V, K> type;
private final Function<K, MapCodec<? extends V>> instanceCodec;
private SafeDispatchCodec(Codec<K> typeCodec, Function<V, K> type, Function<K, MapCodec<? extends V>> instanceCodec) {
this.typeCodec = typeCodec;
this.type = type;
this.instanceCodec = instanceCodec;
}
/**
* Create a new safe dispatch codec.
* <p>
* 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 <K> The type of keys.
* @param <V> The type of values.
* @return The dispatch codec.
*/
public static <K, V> Codec<V> codec(Codec<K> typeCodec, Function<V, K> type, Function<K, MapCodec<? extends V>> instanceCodec) {
return new SafeDispatchCodec<>(typeCodec, type, instanceCodec).codec();
}
/**
* Create a new safe dispatch codec.
* <p>
* 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 <K> The type of keys.
* @param <V> The type of values.
* @return The dispatch codec.
*/
public static <K, V> Codec<V> ofRegistry(ResourceKey<Registry<K>> typeRegistry, Function<V, K> type, Function<K, MapCodec<? extends V>> instanceCodec) {
return codec(Codec.lazyInitialized(() -> RegistryHelper.getRegistry(typeRegistry).byNameCodec()), type, instanceCodec);
}
@Override
public <T> DataResult<V> decode(final DynamicOps<T> ops, final MapLike<T> 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<? extends V> 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 <T> RecordBuilder<T> encode(final V input, final DynamicOps<T> ops, final RecordBuilder<T> builder) {
@SuppressWarnings("unchecked") var encoder = (MapCodec<V>) 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 <T> Stream<T> keys(final DynamicOps<T> ops) {
return Stream.of(TYPE_KEY, COMPRESSED_VALUE_KEY).map(ops::createString);
}
}

View File

@ -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<TurtleTool> 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<TurtleTool> 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)
);
}

View File

@ -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 <T> The type of the value to round trip.
* @return The converted value, for checking equivalency.
*/
public static <T> T roundTrip(T value, BiConsumer<T, RegistryFriendlyByteBuf> write, Function<RegistryFriendlyByteBuf, T> 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 <T> The type of the value to round trip.
* @return The converted value, for checking equivalency.
*/
public static <T> T roundTripSerialiser(ResourceLocation id, T value, BiConsumer<RegistryFriendlyByteBuf, T> write, BiFunction<ResourceLocation, RegistryFriendlyByteBuf, T> read) {
return roundTrip(value, (x, b) -> write.accept(b, x), b -> read.apply(id, b));
}
}

View File

@ -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})")
}
}

View File

@ -1,5 +1,6 @@
{
"type": "computercraft:tool",
"adjective": "upgrade.minecraft.diamond_pickaxe.adjective",
"item": "minecraft:netherite_pickaxe",
"allowEnchantments": true,
"consumeDurability": "when_enchanted"

View File

@ -1,5 +1,6 @@
{
"type": "computercraft:tool",
"adjective": "upgrade.minecraft.diamond_pickaxe.adjective",
"item": "minecraft:wooden_pickaxe",
"consumeDurability": "always"
}

View File

@ -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"}}
}
]
}

View File

@ -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 type The turtle upgrade type.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller) {
getInstance().registerTurtleUpgradeModeller(type, modeller);
}
private static FabricComputerCraftAPIClientService getInstance() {

View File

@ -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;
}
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller);
final class Instance {
static final @Nullable FabricComputerCraftAPIClientService INSTANCE;

View File

@ -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());

View File

@ -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<DefaultCraftingDisplay<?>> {
private final UpgradeRecipeGenerator<DefaultCraftingDisplay<?>> resolver = new UpgradeRecipeGenerator<>(GeneratedShapedDisplay::new);
private final UpgradeRecipeGenerator<DefaultCraftingDisplay<?>> resolver = new UpgradeRecipeGenerator<>(GeneratedShapedDisplay::new, RecipeModHelpers.getEmptyRegistryAccess());
@Override
public Optional<List<DefaultCraftingDisplay<?>>> getRecipeFor(EntryStack<?> entry) {

View File

@ -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 <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
TurtleUpgradeModellers.register(serialiser, modeller);
public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller) {
TurtleUpgradeModellers.register(type, modeller);
}
}

View File

@ -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<RegistrySetBuilder.PatchedRegistries> createPatchedRegistries(CompletableFuture<HolderLookup.Provider> 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<ResourceKey<? extends Registry<?>>> listRegistries() {
return Stream.concat(
parent.listRegistries(),
DynamicRegistries.getDynamicRegistries().stream().map(RegistryDataLoader.RegistryData::key)
).distinct();
}
@Override
public <T> Optional<HolderLookup.RegistryLookup<T>> lookup(ResourceKey<? extends Registry<? extends T>> registryKey) {
return parent.lookup(registryKey).or(() -> Optional.of(new EmptyRegistry<>(registryKey)));
}
}
private record EmptyRegistry<T>(
ResourceKey<? extends Registry<? extends T>> key
) implements HolderLookup.RegistryLookup<T> {
@Override
public Lifecycle registryLifecycle() {
return Lifecycle.stable();
}
@Override
public Stream<Holder.Reference<T>> listElements() {
return Stream.empty();
}
@Override
public Stream<HolderSet.Named<T>> listTags() {
return Stream.empty();
}
@Override
public Optional<Holder.Reference<T>> get(ResourceKey<T> resourceKey) {
return Optional.empty();
}
@Override
public Optional<HolderSet.Named<T>> get(TagKey<T> tagKey) {
return Optional.empty();
}
}
}
}

View File

@ -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();

View File

@ -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());

View File

@ -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 <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
dispatch.register(serialiser, modeller);
public <T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller) {
dispatch.register(type, modeller);
}
}

View File

@ -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);

View File

@ -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()));