1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 22:17:39 +00:00

Compare commits

..

12 Commits

Author SHA1 Message Date
Jonathan Coates
2c740bb904 Bump CC:T to 1.111.1 2024-07-04 19:08:06 +01:00
Jonathan Coates
0e4710a956 Update NF and NG
- Rename ToolActions to ItemAbilities. Closes #1881.
 - Remove our source set helper, as NG has built-in support for this
   now.
 - Remove our code to generate new JavaExec tasks from runs, as NG now
   generates JavaExec tasks normally.
2024-06-29 12:58:03 +01:00
Jonathan Coates
aca1d43550 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-06-29 10:50:44 +01:00
Jonathan Coates
f10e401aea Load turtle overlays from a registry
- Add a new computercraft:turtle_overlay dynamic registry, which stores
   turtle overlays. Turtle overlays are just a model id and an
   (optional) boolean flag, which specifies whether this overlay is
   compatible with the elf/christmas model.

 - Change the computercraft:overlay component to accept a
   Holder<TurtleOverlay> (instead of just a model ID). This accepts both
   an overlay ID or an inline overlay object (e.g. you can do
   cc:turtle_normal[computercraft:overlay={model:"foo"}].

 - Update turtle model and BE rendering code to render both the overlay
   and elf (if compatible). Fixes #1663.

 - Ideally we'd automatically load all models listed in the overlay
   registry. However, resource loading happens separately to datapacks,
   so we can't link the two.

   Instead, we add a new assets/computercraft/extra_models.json file
   that lists any additional models that should be loaded and baked.

   This file includes all built-in overlay models, but external resource
   packs and/or mods can easily extend it.
2024-06-27 20:57:43 +01:00
Jonathan Coates
7744d2663b Fix heights of turtle flags
They were 0.5 squares too high, so the textures were a little stretched.
2024-06-27 20:41:42 +01:00
Jonathan Coates
4566cb8273 Add path-based error constructor to FileSystemException
This doesn't change any functionality, but means we only construct
"/{path}: {message}" strings in one location.
2024-06-26 21:12:44 +01:00
Jonathan Coates
052e7a7ae5 Make FileSystem.toLocal private
Use a custom to-local function in the various ArchiveMounts, which don't
faff around with sanitising paths.
2024-06-26 21:12:05 +01:00
Jonathan Coates
0895200681 Small bits of cleanup
Build system:
 - Switch to our new maven server. This has a cleaner separation between
   published packages and mirrored packages, to avoid leaking those into
   other people's builds.
 - Update Gradle and Loom versions.

Code:
 - Link to definitions instead in the breaking changes page.
 - Fix several unused variable warnings.

Other:
 - Remove unsupported Minecraft versions from the issue template.
2024-06-26 18:07:57 +01:00
Jonathan Coates
1a1623075f Fix turtle labels not rendering
The X scale factor should now be flipped. I'm not quite sure what in MC
has meant this should be changed, possibly the cameraOrientation matrix?

Fixes #1872
2024-06-26 18:06:48 +01:00
Jonathan Coates
54a95e07a4 Fix monitors not updating on NeoForge 2024-06-23 09:21:15 +01:00
Jonathan Coates
09d0f563b7 Add 1.21 to version list 2024-06-23 09:14:32 +01:00
Jonathan Coates
e188f1d3fa Link to os.time in os.setAlarm docs 2024-06-23 08:43:42 +01:00
62 changed files with 605 additions and 276 deletions

View File

@@ -8,10 +8,8 @@ body:
label: Minecraft Version
description: What version of Minecraft are you using?
options:
- 1.16.x
- 1.18.x
- 1.19.x
- 1.20.x
- 1.20.1
- 1.21.x
validations:
required: true
- type: input

View File

@@ -39,7 +39,7 @@ on is present.
```groovy
repositories {
maven {
url "https://squiddev.cc/maven/"
url "https://maven.squiddev.cc"
content {
includeGroup("cc.tweaked")
}

View File

@@ -32,7 +32,7 @@ repositories {
}
}
maven("https://squiddev.cc/maven") {
maven("https://maven.squiddev.cc") {
name = "SquidDev"
content {
includeGroup("cc.tweaked.vanilla-extract")

View File

@@ -38,7 +38,7 @@ java {
repositories {
mavenCentral()
val mainMaven = maven("https://squiddev.cc/maven") {
val mainMaven = maven("https://maven.squiddev.cc/mirror") {
name = "SquidDev"
}
@@ -133,8 +133,8 @@ tasks.processResources {
tasks.withType(AbstractArchiveTask::class.java).configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
dirMode = Integer.valueOf("755", 8)
fileMode = Integer.valueOf("664", 8)
filePermissions {}
dirPermissions {}
}
tasks.jar {

View File

@@ -38,7 +38,7 @@ publishing {
}
repositories {
maven("https://squiddev.cc/maven") {
maven("https://maven.squiddev.cc") {
name = "SquidDev"
credentials(PasswordCredentials::class)

View File

@@ -9,6 +9,7 @@ import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
import org.gradle.kotlin.dsl.getByName
import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions
@@ -46,6 +47,21 @@ fun JavaExec.copyToFull(spec: JavaExec) {
copyToExec(spec)
}
/**
* Base this [JavaExec] task on an existing task.
*/
fun JavaExec.copyFromTask(task: JavaExec) {
for (dep in task.dependsOn) dependsOn(dep)
task.copyToFull(this)
}
/**
* Base this [JavaExec] task on an existing task.
*/
fun JavaExec.copyFromTask(task: String) {
copyFromTask(project.tasks.getByName<JavaExec>(task))
}
/**
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
*/

View File

@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import net.neoforged.gradle.common.runs.run.RunImpl
import net.neoforged.gradle.common.runs.tasks.RunExec
import net.neoforged.gradle.dsl.common.extensions.RunnableSourceSet
import net.neoforged.gradle.dsl.common.runs.run.Run
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
import java.nio.file.Files
/**
* Set [JavaExec] task to run a given [RunConfig].
*
* See also [RunExec].
*/
fun JavaExec.setRunConfig(config: Run) {
mainClass.set(config.mainClass)
workingDir = config.workingDirectory.get().asFile
argumentProviders.add { config.programArguments.get() }
jvmArgumentProviders.add { config.jvmArguments.get() }
environment(config.environmentVariables.get())
systemProperties(config.systemProperties.get())
config.modSources.all().get().values().forEach { classpath(it.runtimeClasspath) }
classpath(config.classpath)
classpath(config.dependencies.get().runtimeConfiguration)
(config as RunImpl).taskDependencies.forEach { dependsOn(it) }
javaLauncher.set(
project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
}
/**
* Add a new [Run.modSource] with a specific mod id.
*/
fun Run.modSourceAs(sourceSet: SourceSet, mod: String) {
// NeoGradle requires a RunnableSourceSet to be present, so we inject it into other project's source sets.
val runnable = sourceSet.extensions.findByType<RunnableSourceSet>() ?: run {
val extension = sourceSet.extensions.create<RunnableSourceSet>(RunnableSourceSet.NAME, project)
extension.modIdentifier = mod
extension.modIdentifier.finalizeValueOnRead()
extension
}
if (runnable.modIdentifier.get() != mod) throw IllegalArgumentException("Multiple mod identifiers")
modSource(sourceSet)
}

View File

@@ -25,13 +25,13 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
with other functions, and `setfenv` will have no effect.
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. [`getfenv`]/[`setfenv`]
now only work on Lua functions with an `_ENV` upvalue. [`getfenv`] will return the global environment when called
with other functions, and [`setfenv`] will have no effect.
- [`load`]/[`loadstring`] defaults to using the global environment (`_G`) rather than the current coroutine's
environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
- `math.random` now uses Lua 5.4's random number generator.
- Support for dumping functions ([`string.dump`]) and loading binary chunks has been removed.
- [`math.random`] now uses Lua 5.4's random number generator.
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.
@@ -44,7 +44,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
`keys.enter` constant was queued when the key was pressed)
- Minecraft 1.13 removed the concept of item damage and block metadata (see ["The Flattening"][flattening]). As a
result `turtle.inspect` no longer provides block metadata, and `turtle.getItemDetail` no longer provides damage.
result [`turtle.inspect`] no longer provides block metadata, and [`turtle.getItemDetail`] no longer provides damage.
- Block states (`turtle.inspect().state`) should provide all the same information as block metadata, but in a much
more understandable format.
@@ -70,7 +70,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- Unlabelled computers and turtles now keep their ID when broken, meaning that unlabelled computers/items do not stack.
## ComputerCraft 1.80pr1 {#cc-1.80}
- Programs run via `shell.run` are now started in their own isolated environment. This means globals set by programs
- Programs run via [`shell.run`] are now started in their own isolated environment. This means globals set by programs
will not be accessible outside of this program.
- Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,

View File

@@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false
# Mod properties
isUnstable=true
modVersion=1.111.0
modVersion=1.111.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21

View File

@@ -9,7 +9,7 @@
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
fabric-api = "0.100.3+1.21"
fabric-loader = "0.15.11"
neoForge = "21.0.21-beta"
neoForge = "21.0.42-beta"
neoForgeSpi = "8.0.1"
mixin = "0.8.5"
parchment = "2024.06.16"
@@ -60,18 +60,18 @@ checkstyle = "10.14.1"
curseForgeGradle = "1.1.18"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.6.7"
fabric-loom = "1.7.1"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-73-g43ee16c"
lwjgl = "3.3.3"
minotaur = "2.8.7"
neoGradle = "7.0.145"
neoGradle = "7.0.152"
nullAway = "0.10.25"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.4"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.1.3"
versionCatalogUpdate = "0.8.1"

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

2
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.

View File

@@ -25,6 +25,7 @@ import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
@@ -53,6 +54,7 @@ import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -143,15 +145,14 @@ public final class ClientRegistry {
register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
}
private static final String[] EXTRA_MODELS = new String[]{
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_rainbow_overlay",
"block/turtle_trans_overlay",
private static final ResourceLocation[] EXTRA_MODELS = {
TurtleOverlay.ELF_MODEL,
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
};
public static void registerExtraModels(Consumer<ResourceLocation> register) {
for (var model : EXTRA_MODELS) register.accept(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, model));
public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) {
for (var model : EXTRA_MODELS) register.accept(model);
extraModels.forEach(register);
TurtleUpgradeModellers.getDependencies().forEach(register);
}

View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
/**
* A list of extra models to load on the client.
* <p>
* This is largely intended for use with {@linkplain TurtleOverlay turtle overlays}. As overlays are stored in a dynamic
* registry, they are not available when resources are loaded, and so we need a way to request the overlays' models be
* loaded.
*
* @param models The models to load.
*/
public record ExtraModels(List<ResourceLocation> models) {
private static final Logger LOG = LoggerFactory.getLogger(ExtraModels.class);
private static final Gson GSON = new Gson();
/**
* The path where the extra models are listed.
*/
public static final ResourceLocation PATH = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "extra_models.json");
/**
* The coded used to store the extra model file.
*/
public static final Codec<ExtraModels> CODEC = ResourceLocation.CODEC.listOf().xmap(ExtraModels::new, ExtraModels::models);
/**
* Get the list of all extra models to load.
*
* @param resources The current resource manager.
* @return A set of all resources to load.
*/
public static Collection<ResourceLocation> loadAll(ResourceManager resources) {
Set<ResourceLocation> out = new HashSet<>();
for (var path : resources.getResourceStack(PATH)) {
ExtraModels models;
try (var stream = path.openAsReader()) {
models = ExtraModels.CODEC.parse(JsonOps.INSTANCE, GSON.fromJson(stream, JsonElement.class)).getOrThrow(JsonParseException::new);
} catch (IOException | RuntimeException e) {
LOG.error("Failed to load extra models from {}", path.sourcePackId());
continue;
}
out.addAll(models.models());
}
return Collections.unmodifiableCollection(out);
}
}

View File

@@ -12,13 +12,14 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@@ -52,7 +53,7 @@ public final class TurtleModelParts<T> {
boolean colour,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
@Nullable ResourceLocation overlay,
@Nullable TurtleOverlay overlay,
boolean christmas,
boolean flip
) {
@@ -113,10 +114,10 @@ public final class TurtleModelParts<T> {
var parts = new ArrayList<BakedModel>(4);
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation));
var overlayModelLocation = TurtleBlockEntityRenderer.getTurtleOverlayModel(combo.overlay(), combo.christmas());
if (overlayModelLocation != null) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
}
if (combo.overlay() != null) addPart(parts, modelManager, transformation, combo.overlay().model());
var showChristmas = TurtleOverlay.showElfOverlay(combo.overlay(), combo.christmas());
if (showChristmas) addPart(parts, modelManager, transformation, TurtleOverlay.ELF_MODEL);
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
@@ -124,6 +125,10 @@ public final class TurtleModelParts<T> {
return parts;
}
private void addPart(List<BakedModel> parts, ModelManager modelManager, Transformation transformation, ResourceLocation model) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, model), transformation));
}
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) return;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
@@ -21,14 +22,14 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.CommonColors;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private final BlockEntityRenderDispatcher renderer;
private final Font font;
@@ -38,12 +39,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
font = context.getFont();
}
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
if (overlay != null) return overlay;
if (christmas) return ELF_OVERLAY_MODEL;
return null;
}
@Override
public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight) {
transform.pushPose();
@@ -62,13 +57,13 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
transform.pushPose();
transform.translate(0.5, 1.2, 0.5);
transform.mulPose(mc.getEntityRenderDispatcher().cameraOrientation());
transform.scale(-0.025f, -0.025f, 0.025f);
transform.scale(0.025f, -0.025f, 0.025f);
var matrix = transform.last().pose();
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
var width = -font.width(label) / 2.0f;
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
font.drawInBatch(label, width, (float) 0, CommonColors.WHITE, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
transform.popPose();
}
@@ -98,10 +93,11 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
}
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (overlayModel != null) {
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
}
if (overlay != null) renderModel(transform, buffers, lightmapCoord, overlayLight, overlay.model(), null);
// And the Christmas overlay.
var showChristmas = TurtleOverlay.showElfOverlay(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null);
// Render the upgrades
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);

View File

@@ -6,15 +6,18 @@ package dan200.computercraft.data.client;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
/**
@@ -26,7 +29,7 @@ public final class ClientDataProviders {
private ClientDataProviders() {
}
public static void add(DataProviders.GeneratorSink generator) {
public static void add(DataProviders.GeneratorSink generator, CompletableFuture<HolderLookup.Provider> registries) {
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of(
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
@@ -44,5 +47,12 @@ public final class ClientDataProviders {
GuiSprites.COMPUTER_COLOUR.textures()
).flatMap(x -> x).<SpriteSource>map(x -> new SingleFile(x, Optional.empty())).toList());
});
generator.add(pack -> new ExtraModelsProvider(pack, registries) {
@Override
public Stream<ResourceLocation> getModels(HolderLookup.Provider registries) {
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
}
});
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.client;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.client.model.ExtraModels;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
/**
* A data provider to generate {@link ExtraModels}.
*/
abstract class ExtraModelsProvider implements DataProvider {
private final Path path;
private final CompletableFuture<HolderLookup.Provider> registries;
ExtraModelsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
path = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(ExtraModels.PATH.getNamespace()).resolve(ExtraModels.PATH.getPath());
this.registries = registries;
}
/**
* Return a stream of models to load.
*
* @param registries The current registries.
* @return The collection of extra models to load.
*/
public abstract Stream<ResourceLocation> getModels(HolderLookup.Provider registries);
@Override
public final CompletableFuture<?> run(CachedOutput output) {
return registries.thenCompose(registries -> {
var models = new ExtraModels(getModels(registries).sorted().toList());
var json = ExtraModels.CODEC.encodeStart(JsonOps.INSTANCE, models).getOrThrow(IllegalStateException::new);
return DataProvider.saveStable(output, json, path);
});
}
@Override
public final String getName() {
return "Extra Models";
}
}

View File

@@ -0,0 +1 @@
["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"]

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_trans_overlay"},
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/rainbow_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/turtle_trans_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/rainbow_flag"]}
}

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"},
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/trans_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/trans_flag"]}
}

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_trans_overlay"},
"conditions": {"recipe": "computercraft:turtle_normal_overlays/rainbow_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/turtle_trans_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/rainbow_flag"]}
}

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_rainbow_overlay"},
"conditions": {"recipe": "computercraft:turtle_normal_overlays/trans_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/turtle_rainbow_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/trans_flag"]}
}

View File

@@ -0,0 +1 @@
{"model": "computercraft:block/turtle_rainbow_overlay", "show_elf_overlay": true}

View File

@@ -0,0 +1 @@
{"model": "computercraft:block/turtle_trans_overlay", "show_elf_overlay": true}

View File

@@ -20,7 +20,7 @@
{"item": "computercraft:turtle_advanced"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"},
"components": {"computercraft:overlay": "computercraft:rainbow_flag"},
"count": 1,
"id": "computercraft:turtle_advanced"
}

View File

@@ -17,7 +17,7 @@
{"item": "computercraft:turtle_advanced"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"},
"components": {"computercraft:overlay": "computercraft:trans_flag"},
"count": 1,
"id": "computercraft:turtle_advanced"
}

View File

@@ -20,7 +20,7 @@
{"item": "computercraft:turtle_normal"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"},
"components": {"computercraft:overlay": "computercraft:rainbow_flag"},
"count": 1,
"id": "computercraft:turtle_normal"
}

View File

@@ -17,7 +17,7 @@
{"item": "computercraft:turtle_normal"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"},
"components": {"computercraft:overlay": "computercraft:trans_flag"},
"count": 1,
"id": "computercraft:turtle_normal"
}

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistrySetBuilder;
@@ -38,6 +39,7 @@ public final class DataProviders {
Util.make(new RegistrySetBuilder(), builder -> {
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades);
builder.add(TurtleOverlay.REGISTRY, TurtleOverlays::register);
}));
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
@@ -57,7 +59,8 @@ public final class DataProviders {
// and invoke that.
try {
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
.getMethod("add", GeneratorSink.class).invoke(null, generator);
.getMethod("add", GeneratorSink.class, CompletableFuture.class)
.invoke(null, generator, fullRegistries);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}

View File

@@ -28,6 +28,7 @@ import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
import dan200.computercraft.shared.recipe.function.CopyComponents;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.ColourUtils;
@@ -45,6 +46,7 @@ import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
@@ -96,7 +98,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
diskColours(add);
pocketUpgrades(add, registries);
turtleUpgrades(add, registries);
turtleOverlays(add);
turtleOverlays(add, registries);
addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC));
@@ -189,8 +191,8 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
}
private void turtleOverlays(RecipeOutput add) {
turtleOverlay(add, "turtle_trans_overlay", x -> x
private void turtleOverlays(RecipeOutput add, HolderLookup.Provider registries) {
turtleOverlay(add, registries, TurtleOverlays.TRANS_FLAG, x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
.requires(ColourUtils.getDyeTag(DyeColor.PINK))
@@ -198,7 +200,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.requires(Items.STICK)
);
turtleOverlay(add, "turtle_rainbow_overlay", x -> x
turtleOverlay(add, registries, TurtleOverlays.RAINBOW_FLAG, x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.RED))
.requires(ColourUtils.getDyeTag(DyeColor.ORANGE))
@@ -210,14 +212,14 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
);
}
private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) {
var overlayId = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/" + overlay);
private void turtleOverlay(RecipeOutput add, HolderLookup.Provider registries, ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) {
var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay);
for (var turtleItem : turtleItems()) {
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var builder = ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), overlayId))
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
.group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(turtleItem));
build.accept(builder);
@@ -226,7 +228,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.build(s -> new TransformShapelessRecipe(s, List.of(
CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build()
)))
.save(add, name.withSuffix("_overlays/" + overlay));
.save(add, name.withSuffix("_overlays/" + overlay.location().getPath()));
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
/**
* Built-in turtle overlays.
*/
final class TurtleOverlays {
public static final ResourceKey<TurtleOverlay> RAINBOW_FLAG = create("rainbow_flag");
public static final ResourceKey<TurtleOverlay> TRANS_FLAG = create("trans_flag");
private static ResourceKey<TurtleOverlay> create(String name) {
return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
private TurtleOverlays() {
}
public static void register(BootstrapContext<TurtleOverlay> registry) {
registry.register(RAINBOW_FLAG, new TurtleOverlay(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"),
true
));
registry.register(TRANS_FLAG, new TurtleOverlay(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"),
true
));
}
}

View File

@@ -72,6 +72,7 @@ import dan200.computercraft.shared.recipe.*;
import dan200.computercraft.shared.recipe.function.CopyComponents;
import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
@@ -95,7 +96,6 @@ 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.ResourceLocation;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.BlockItem;
@@ -320,8 +320,8 @@ public final class ModRegistry {
/**
* The overlay on a turtle.
*/
public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b
.persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC)
public static final RegistryEntry<DataComponentType<Holder<TurtleOverlay>>> OVERLAY = register("overlay", b -> b
.persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC)
);
/**

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.shared.computer.core;
import com.google.common.annotations.VisibleForTesting;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.core.filesystem.ArchiveMount;
import dan200.computercraft.core.filesystem.FileSystem;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
@@ -65,9 +64,10 @@ public final class ResourceMount extends ArchiveMount<ResourceMount.FileEntry> {
existingNamespace = file.getNamespace();
if (!file.getNamespace().equals(namespace)) continue;
if (!FileSystem.contains(subPath, file.getPath())) continue; // Some packs seem to include the parent?
var localPath = FileSystem.toLocal(file.getPath(), subPath);
var localPath = getLocalPath(file.getPath(), subPath);
if (localPath == null) continue;
try {
getOrCreateChild(newRoot, localPath, this::createEntry);
} catch (ResourceLocationException e) {

View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.turtle;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.RegistryFileCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
/**
* A cosmetic overlay on a turtle.
*
* @param model The path to the overlay's model.
* @param showElfOverlay Whether this overlay is compatible with the {@linkplain #ELF_MODEL Christmas elf model}.
* @see ModRegistry.DataComponents#OVERLAY
*/
public record TurtleOverlay(ResourceLocation model, boolean showElfOverlay) {
/**
* The registry turtle overlays are stored in.
*/
public static final ResourceKey<Registry<TurtleOverlay>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_overlay"));
/**
* The codec used to read/write turtle overlay definitions from datapacks.
*/
public static final Codec<TurtleOverlay> DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("model").forGetter(TurtleOverlay::model),
Codec.BOOL.optionalFieldOf("show_elf_overlay", false).forGetter(TurtleOverlay::showElfOverlay)
).apply(instance, TurtleOverlay::new));
/**
* The codec used for {@link TurtleOverlay} instances.
*
* @see ModRegistry.DataComponents#OVERLAY
*/
public static final Codec<Holder<TurtleOverlay>> CODEC = RegistryFileCodec.create(REGISTRY, DIRECT_CODEC);
/**
* The stream codec used for {@link TurtleOverlay} instances.
*/
public static final StreamCodec<RegistryFriendlyByteBuf, Holder<TurtleOverlay>> STREAM_CODEC = ByteBufCodecs.holder(REGISTRY, StreamCodec.composite(
ResourceLocation.STREAM_CODEC, TurtleOverlay::model,
ByteBufCodecs.BOOL, TurtleOverlay::showElfOverlay,
TurtleOverlay::new
));
/**
* An additional overlay that is rendered on all turtles at {@linkplain Holiday#CHRISTMAS Christmas}.
*
* @see #showElfOverlay()
* @see #showElfOverlay(TurtleOverlay, boolean)
*/
public static final ResourceLocation ELF_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
/**
* Determine whether we should show the {@linkplain #ELF_MODEL elf overlay}.
*
* @param overlay The current {@link TurtleOverlay}.
* @param christmas Whether it is Christmas.
* @return Whether we should show the elf overlay.
*/
public static boolean showElfOverlay(@Nullable TurtleOverlay overlay, boolean christmas) {
return christmas && (overlay == null || overlay.showElfOverlay());
}
}

View File

@@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
@@ -30,7 +31,6 @@ import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
@@ -222,8 +222,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
return brain.getColour();
}
public @Nullable ResourceLocation getOverlay() {
return brain.getOverlay();
public @Nullable TurtleOverlay getOverlay() {
var overlay = brain.getOverlay();
return overlay == null ? null : overlay.value();
}
public ITurtleAccess getAccess() {

View File

@@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.InventoryDelegate;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.Holiday;
@@ -32,7 +33,6 @@ import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.Container;
@@ -74,7 +74,7 @@ public class TurtleBrain implements TurtleAccessInternal {
private int selectedSlot = 0;
private int fuelLevel = 0;
private int colourHex = -1;
private @Nullable ResourceLocation overlay = null;
private @Nullable Holder<TurtleOverlay> overlay = null;
private TurtleAnimation animation = TurtleAnimation.NONE;
private int animationProgress = 0;
@@ -134,7 +134,7 @@ public class TurtleBrain implements TurtleAccessInternal {
// Read fields
colourHex = nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : -1;
fuelLevel = nbt.contains(NBT_FUEL) ? nbt.getInt(NBT_FUEL) : 0;
overlay = nbt.contains(NBT_OVERLAY) ? ResourceLocation.parse(nbt.getString(NBT_OVERLAY)) : null;
overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY) : null;
// Read upgrades
setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE));
@@ -144,7 +144,7 @@ public class TurtleBrain implements TurtleAccessInternal {
private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) {
nbt.putInt(NBT_FUEL, fuelLevel);
if (colourHex != -1) nbt.putInt(NBT_COLOUR, colourHex);
if (overlay != null) nbt.putString(NBT_OVERLAY, overlay.toString());
if (overlay != null) NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay);
// Write upgrades
NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT));
@@ -418,11 +418,11 @@ public class TurtleBrain implements TurtleAccessInternal {
BlockEntityHelpers.updateBlock(owner);
}
public @Nullable ResourceLocation getOverlay() {
public @Nullable Holder<TurtleOverlay> getOverlay() {
return overlay;
}
public void setOverlay(@Nullable ResourceLocation overlay) {
public void setOverlay(@Nullable Holder<TurtleOverlay> overlay) {
if (!Objects.equals(this.overlay, overlay)) {
this.overlay = overlay;
BlockEntityHelpers.updateBlock(owner);

View File

@@ -12,11 +12,11 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.LayeredCauldronBlock;
@@ -74,8 +74,9 @@ public class TurtleItem extends AbstractComputerItem {
return stack.get(side == TurtleSide.LEFT ? ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get() : ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get());
}
public static @Nullable ResourceLocation getOverlay(ItemStack stack) {
return stack.get(ModRegistry.DataComponents.OVERLAY.get());
public static @Nullable TurtleOverlay getOverlay(ItemStack stack) {
var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get());
return overlay == null ? null : overlay.value();
}
public static int getFuelLevel(ItemStack stack) {

View File

@@ -82,7 +82,8 @@ public class ComponentizationFixers {
if (item.is(TURTLES)) {
item.moveTagToComponent("Fuel", "computercraft:fuel");
item.moveTagToComponent("Overlay", "computercraft:overlay");
item.removeTag("Overlay").result().ifPresent(x -> item.setComponent("computercraft:overlay", fixOverlay(x)));
moveUpgradeToComponent(item, ops, "LeftUpgrade", "LeftUpgradeNbt", "computercraft:left_turtle_upgrade");
moveUpgradeToComponent(item, ops, "RightUpgrade", "RightUpgradeNbt", "computercraft:right_turtle_upgrade");
@@ -152,6 +153,7 @@ public class ComponentizationFixers {
return typed -> typed.updateTyped(input, output, typed2 -> typed2.update(DSL.remainderFinder(), x -> {
x = moveUpgradeData(x, "LeftUpgrade", "LeftUpgradeNbt");
x = moveUpgradeData(x, "RightUpgrade", "RightUpgradeNbt");
x = x.update("Overlay", ComponentizationFixers::fixOverlay);
return x;
}));
}
@@ -164,6 +166,20 @@ public class ComponentizationFixers {
return ops.set(key, createUpgradeData(ops, upgradeId, ops.get(dataKey))).remove(dataKey);
}
private static Dynamic<?> fixOverlay(Dynamic<?> overlay) {
// Rewrite known overlays to their new ids.
var overlayId = overlay.asString(null);
if (overlayId == null) return overlay;
return switch (overlayId) {
// Map known overlays to their new id.
case "computercraft:block/turtle_trans_overlay" -> overlay.createString("computercraft:trans_flag");
case "computercraft:block/turtle_rainbow_overlay" -> overlay.createString("computercraft:rainbow_flag");
// And unknown overlays to a direct entry.
default -> overlay.emptyMap().set("model", overlay);
};
}
/**
* Create a new upgrade data.
*

View File

@@ -1,38 +1,37 @@
{
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_rainbow_overlay",
"texture": "computercraft:block/turtle_rainbow_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13, 10.5],
"to": [2, 16.5, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 6], "texture": "#texture"},
"down": {"uv": [8, 0, 9, 6], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
],
"display": {}
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_rainbow_overlay",
"texture": "computercraft:block/turtle_rainbow_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13.5, 10.5],
"to": [2, 16.5, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 6], "texture": "#texture"},
"down": {"uv": [8, 0, 9, 6], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13.5, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
]
}

View File

@@ -1,38 +1,37 @@
{
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_trans_overlay",
"texture": "computercraft:block/turtle_trans_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13.5, 10.5],
"to": [2, 16.5, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 5], "texture": "#texture"},
"down": {"uv": [15, 0, 16, 5], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13.5, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
],
"display": {}
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_trans_overlay",
"texture": "computercraft:block/turtle_trans_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13.5, 10.5],
"to": [2, 16, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 5], "texture": "#texture"},
"down": {"uv": [15, 0, 16, 5], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13.5, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
]
}

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.gametest
import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.api.detail.VanillaDetailRegistries
import dan200.computercraft.api.lua.ObjectArguments
@@ -21,7 +22,9 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlock
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState
import dan200.computercraft.shared.turtle.TurtleOverlay
import dan200.computercraft.shared.turtle.apis.TurtleAPI
import dan200.computercraft.shared.turtle.items.TurtleItem
import dan200.computercraft.shared.util.WaterloggableHelpers
import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.LuaTaskContext
@@ -654,6 +657,27 @@ class Turtle_Test {
}
}
/**
* Loads a structure created on an older version of the game, and checks that data fixers have been applied.
*/
@GameTest
fun Data_fixers(helper: GameTestHelper) = helper.sequence {
thenExecute {
val overlay = helper.level.registryAccess().registryOrThrow(TurtleOverlay.REGISTRY)
.get(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag"))!!
val upgrade = helper.level.registryAccess().registryOrThrow(ITurtleUpgrade.REGISTRY)
.get(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!!
val turtleBe = helper.getBlockEntity(BlockPos(1, 2, 1), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
assertEquals(overlay, turtleBe.overlay)
assertEquals(upgrade, turtleBe.getUpgrade(TurtleSide.LEFT))
val turtleItem = turtleBe.getItem(0)
assertEquals(overlay, TurtleItem.getOverlay(turtleItem))
assertEquals(upgrade, TurtleItem.getUpgrade(turtleItem, TurtleSide.LEFT))
}
}
/**
* `turtle.suck` only pulls for the current side.
*/

View File

@@ -0,0 +1,39 @@
{
DataVersion: 3465,
size: [3, 3, 3],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 123, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {ComputerId: 123, LeftUpgrade: "minecraft:diamond_pickaxe", Overlay: "computercraft:block/turtle_trans_overlay"}}], LeftUpgrade: "minecraft:diamond_pickaxe", LeftUpgradeNbt: {}, On: 0b, Overlay: "computercraft:block/turtle_trans_overlay", Owner: {LowerId: -4770154215762454977L, Name: "Player436", UpperId: 114963728012426084L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}"
]
}

View File

@@ -175,9 +175,9 @@ public class OSAPI implements ILuaAPI {
}
/**
* Sets an alarm that will fire at the specified in-game time. When it
* fires, * an {@code alarm} event will be added to the event queue with the
* ID * returned from this function as the first parameter.
* Sets an alarm that will fire at the specified {@linkplain #time(IArguments) in-game time}.
* When it fires, an {@code alarm} event will be added to the event queue with the
* ID returned from this function as the first parameter.
*
* @param time The time at which to fire the alarm, in the range [0.0, 24.0).
* @return The ID of the new alarm. This can be used to filter the

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.core.computer.computerthread;
import com.google.common.annotations.VisibleForTesting;
import com.google.errorprone.annotations.Keep;
import dan200.computercraft.core.ComputerContext;
import dan200.computercraft.core.Logging;
import dan200.computercraft.core.computer.TimeoutState;
@@ -774,6 +775,7 @@ public final class ComputerThread implements ComputerScheduler {
/**
* The current state of this worker.
*/
@Keep
private volatile ExecutorState $state = ExecutorState.IDLE;
/**
@@ -784,6 +786,7 @@ public final class ComputerThread implements ComputerScheduler {
* {@linkplain #afterWork()} finishes executing, we set this back to null and compute the difference between the
* two, updating the {@link Metrics#JAVA_ALLOCATION} metric.
*/
@Keep
private volatile @Nullable ThreadAllocation $threadAllocation = null;
/**

View File

@@ -8,6 +8,7 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.util.concurrent.TimeUnit;
@@ -82,6 +83,24 @@ public abstract class ArchiveMount<T extends ArchiveMount.FileEntry<T>> extends
*/
protected abstract byte[] getFileContents(String path, T file) throws IOException;
/**
* Convert an absolute path to one relative to {@code root}. If this path is not a child of {@code root}, return
* {@code null}.
*
* @param path The full path.
* @param root The root directory to be relative to.
* @return The relativised path, or {@code null}.
*/
protected static @Nullable String getLocalPath(String path, String root) {
// Some packs seem to include files not under the root, so drop them immediately.
if (!path.startsWith(root)) return null;
if (path.length() == root.length()) return "";
if (path.charAt(root.length()) != '/') return null;
return path.substring(root.length() + 1);
}
protected static class FileEntry<T extends FileEntry<T>> extends AbstractInMemoryMount.FileEntry<T> {
long size = -1;
}

View File

@@ -224,11 +224,11 @@ public class FileSystem {
public synchronized void copy(String sourcePath, String destPath) throws FileSystemException {
sourcePath = sanitizePath(sourcePath);
destPath = sanitizePath(destPath);
if (isReadOnly(destPath)) throw new FileSystemException("/" + destPath + ": " + ACCESS_DENIED);
if (!exists(sourcePath)) throw new FileSystemException("/" + sourcePath + ": " + NO_SUCH_FILE);
if (exists(destPath)) throw new FileSystemException("/" + destPath + ": " + FILE_EXISTS);
if (isReadOnly(destPath)) throw new FileSystemException(destPath, ACCESS_DENIED);
if (!exists(sourcePath)) throw new FileSystemException(sourcePath, NO_SUCH_FILE);
if (exists(destPath)) throw new FileSystemException(destPath, FILE_EXISTS);
if (contains(sourcePath, destPath)) {
throw new FileSystemException("/" + sourcePath + ": Can't copy a directory inside itself");
throw new FileSystemException(sourcePath, "Can't copy a directory inside itself");
}
copyRecursive(sourcePath, getMount(sourcePath), destPath, getMount(destPath), 0);
}
@@ -341,7 +341,7 @@ public class FileSystem {
}
}
if (match == null) {
throw new FileSystemException("/" + path + ": Invalid Path");
throw new FileSystemException(path, "Invalid Path");
}
return match;
}
@@ -404,7 +404,7 @@ public class FileSystem {
return String.join("/", outputParts);
}
public static boolean contains(String pathA, String pathB) {
private static boolean contains(String pathA, String pathB) {
pathA = sanitizePath(pathA).toLowerCase(Locale.ROOT);
pathB = sanitizePath(pathB).toLowerCase(Locale.ROOT);
@@ -421,7 +421,7 @@ public class FileSystem {
}
}
public static String toLocal(String path, String location) {
static String toLocal(String path, String location) {
path = sanitizePath(path);
location = sanitizePath(location);

View File

@@ -13,8 +13,12 @@ public class FileSystemException extends Exception {
@Serial
private static final long serialVersionUID = -2500631644868104029L;
FileSystemException(String s) {
super(s);
FileSystemException(String message) {
super(message);
}
FileSystemException(String path, String message) {
this("/" + path + ": " + message);
}
public static FileSystemException of(IOException e) {

View File

@@ -49,10 +49,9 @@ public final class JarMount extends ArchiveMount<JarMount.FileEntry> implements
while (zipEntries.hasMoreElements()) {
var entry = zipEntries.nextElement();
var entryPath = entry.getName();
if (!entryPath.startsWith(subPath)) continue;
var localPath = getLocalPath(entry.getName(), subPath);
if (localPath == null) continue;
var localPath = FileSystem.toLocal(entryPath, subPath);
getOrCreateChild(root, localPath, x -> new FileEntry()).setup(entry);
}
}

View File

@@ -129,7 +129,7 @@ class MountWrapper {
}
public void makeDirectory(String path) throws FileSystemException {
if (writableMount == null) throw exceptionOf(path, ACCESS_DENIED);
if (writableMount == null) throw new FileSystemException(path, ACCESS_DENIED);
path = toLocal(path);
try {
@@ -140,7 +140,7 @@ class MountWrapper {
}
public void delete(String path) throws FileSystemException {
if (writableMount == null) throw exceptionOf(path, ACCESS_DENIED);
if (writableMount == null) throw new FileSystemException(path, ACCESS_DENIED);
path = toLocal(path);
try {
@@ -151,7 +151,7 @@ class MountWrapper {
}
public void rename(String source, String dest) throws FileSystemException {
if (writableMount == null) throw exceptionOf(source, ACCESS_DENIED);
if (writableMount == null) throw new FileSystemException(source, ACCESS_DENIED);
source = toLocal(source);
dest = toLocal(dest);
@@ -168,7 +168,7 @@ class MountWrapper {
}
public SeekableByteChannel openForWrite(String path, Set<OpenOption> options) throws FileSystemException {
if (writableMount == null) throw exceptionOf(path, ACCESS_DENIED);
if (writableMount == null) throw new FileSystemException(path, ACCESS_DENIED);
path = toLocal(path);
try {
@@ -206,10 +206,6 @@ class MountWrapper {
private FileSystemException localExceptionOf(String path, String message) {
if (!location.isEmpty()) path = path.isEmpty() ? location : location + "/" + path;
return exceptionOf(path, message);
}
private static FileSystemException exceptionOf(String path, String message) {
return new FileSystemException("/" + path + ": " + message);
return new FileSystemException(path, message);
}
}

View File

@@ -1,3 +1,13 @@
# New features in CC: Tweaked 1.111.1
* Add support for data-driven turtle upgrades.
Several bug fixes:
* Fix monitors not rendering on NeoForge.
* Fix turtle labels not rendering.
* Fix compatibility with newer versions of NeoForge.
* Fix heights of turtle flags.
# New features in CC: Tweaked 1.111.0
* Update several translations (Ale32bit).

View File

@@ -1,13 +1,11 @@
New features in CC: Tweaked 1.111.0
New features in CC: Tweaked 1.111.1
* Update several translations (Ale32bit).
* Split up turtle textures into individual textures.
* Add `r+`/`w+` support to the `io` library.
* Warn when capabilities are not registered and Optifine is installed.
* Add support for data-driven turtle upgrades.
Several bug fixes:
* Allow planks to be used for building in "adventure" (dan200).
* Fix `disk.getAudioTitle()` returning untranslated strings for some modded discs.
* Fix crash when right clicking turtles in spectator.
* Fix monitors not rendering on NeoForge.
* Fix turtle labels not rendering.
* Fix compatibility with newer versions of NeoForge.
* Fix heights of turtle flags.
Type "help changelog" to see the full version history.

View File

@@ -53,7 +53,7 @@ public class ComputerCraftClient {
ClientRegistry.registerMainThread(ItemProperties::register);
PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> {
ClientRegistry.registerExtraModels(context::addModels);
ClientRegistry.registerExtraModels(context::addModels, state.getExtraModels());
context.resolveModel().register(ctx -> state.loadModel(ctx.id()));
context.modifyModelAfterBake().register((model, ctx) -> model == null ? null : state.wrapModel(ctx, model));
});

View File

@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -46,13 +47,15 @@ public final class CustomModelLoader {
private final Map<ResourceLocation, UnbakedModel> models = new HashMap<>();
private final Map<ResourceLocation, String> emissiveModels = new HashMap<>();
private final Collection<ResourceLocation> extraModels;
private CustomModelLoader() {
private CustomModelLoader(Collection<ResourceLocation> extraModels) {
this.extraModels = extraModels;
}
public static CompletableFuture<CustomModelLoader> prepare(ResourceManager resources, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
var loader = new CustomModelLoader();
var loader = new CustomModelLoader(ExtraModels.loadAll(resources));
for (var resource : resources.listResources("models", x -> x.getNamespace().equals(ComputerCraftAPI.MOD_ID) && x.getPath().endsWith(".json")).entrySet()) {
loader.loadModel(resource.getKey(), resource.getValue());
}
@@ -85,6 +88,10 @@ public final class CustomModelLoader {
}
}
public Collection<ResourceLocation> getExtraModels() {
return extraModels;
}
/**
* Load a custom model. This searches for CC models with a custom {@code loader} field.
*

View File

@@ -25,6 +25,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEnt
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
@@ -70,6 +71,7 @@ public class ComputerCraft {
DynamicRegistries.registerSynced(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec());
DynamicRegistries.registerSynced(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec());
DynamicRegistries.registerSynced(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC);
ModRegistry.register();
ModRegistry.registerMainThread();

View File

@@ -40,7 +40,7 @@ runs {
systemProperty("forge.logging.console.level", "debug")
cct.sourceDirectories.get().forEach {
if (it.classes) modSourceAs(it.sourceSet, "computercraft")
if (it.classes) modSources.add("computercraft", it.sourceSet)
}
dependencies {
@@ -74,7 +74,7 @@ runs {
modSource(sourceSets.testMod.get())
modSource(sourceSets.testFixtures.get())
modSourceAs(project(":core").sourceSets.testFixtures.get(), "cctest")
modSources.add("cctest", project(":core").sourceSets.testFixtures.get())
jvmArgument("-ea")
@@ -199,7 +199,7 @@ val runGametest by tasks.registering(JavaExec::class) {
dependsOn("cleanRunGametest")
usesService(MinecraftRunnerService.get(gradle))
setRunConfig(runs["gameTestServer"])
copyFromTask("runGameTestServer")
systemProperty("forge.logging.console.level", "info")
systemProperty("cctest.gametest-report", layout.buildDirectory.dir("test-results/$name.xml").getAbsolutePath())

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.client;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
import dan200.computercraft.client.model.ExtraModels;
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import net.minecraft.client.Minecraft;
@@ -57,7 +58,8 @@ public final class ForgeClientRegistry {
@SubscribeEvent
public static void registerModels(ModelEvent.RegisterAdditional event) {
gatherModellers();
ClientRegistry.registerExtraModels(x -> event.register(ModelResourceLocation.standalone(x)));
var extraModels = ExtraModels.loadAll(Minecraft.getInstance().getResourceManager());
ClientRegistry.registerExtraModels(x -> event.register(ModelResourceLocation.standalone(x)), extraModels);
}
@SubscribeEvent

View File

@@ -33,6 +33,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEnt
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.platform.ForgeConfigFile;
import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.server.level.ServerPlayer;
@@ -92,6 +93,7 @@ public final class ComputerCraft {
public static void registerDynamicRegistries(DataPackRegistryEvent.NewRegistry event) {
event.dataPackRegistry(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec(), TurtleUpgrades.instance().upgradeCodec());
event.dataPackRegistry(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec(), PocketUpgrades.instance().upgradeCodec());
event.dataPackRegistry(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC, TurtleOverlay.DIRECT_CODEC);
}
@SubscribeEvent

View File

@@ -38,7 +38,7 @@ public class ForgeCommonHooks {
@SubscribeEvent
public static void onServerTick(ServerTickEvent.Post event) {
CommonHooks.onServerTickStart(event.getServer());
CommonHooks.onServerTickEnd();
}
@SubscribeEvent

View File

@@ -54,8 +54,8 @@ import net.neoforged.neoforge.capabilities.BlockCapability;
import net.neoforged.neoforge.capabilities.BlockCapabilityCache;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.common.CommonHooks;
import net.neoforged.neoforge.common.ItemAbilities;
import net.neoforged.neoforge.common.Tags;
import net.neoforged.neoforge.common.ToolActions;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.event.EventHooks;
import net.neoforged.neoforge.items.wrapper.InvWrapper;
@@ -211,7 +211,7 @@ public class PlatformHelperImpl implements PlatformHelper {
@Override
public boolean hasToolUsage(ItemStack stack) {
return stack.canPerformAction(ToolActions.SHOVEL_FLATTEN) || stack.canPerformAction(ToolActions.HOE_TILL);
return stack.canPerformAction(ItemAbilities.SHOVEL_FLATTEN) || stack.canPerformAction(ItemAbilities.HOE_TILL);
}
@Override

View File

@@ -360,13 +360,13 @@ public class Main {
// And our VBA
var termVertexArray = gl.createVertexArray("Terminal VAO");
glEnableVertexArrayAttrib(termVertexArray, 0);
glVertexArrayAttribFormat(termVertexArray, 0, 2, GL_FLOAT, false, 0); // Position
glEnableVertexArrayAttrib(termVertexArray, 1);
glVertexArrayAttribFormat(termVertexArray, 1, 2, GL_FLOAT, false, 8); // UV
glEnableVertexArrayAttrib(termVertexArray, ATTRIBUTE_POSITION);
glVertexArrayAttribFormat(termVertexArray, ATTRIBUTE_POSITION, 2, GL_FLOAT, false, 0); // Position
glEnableVertexArrayAttrib(termVertexArray, ATTRIBUTE_UV);
glVertexArrayAttribFormat(termVertexArray, ATTRIBUTE_UV, 2, GL_FLOAT, false, 8); // UV
// FIXME: Can we merge this into one call?
glVertexArrayVertexBuffer(termVertexArray, 0, termVertices, 0, 16);
glVertexArrayVertexBuffer(termVertexArray, 1, termVertices, 0, 16);
glVertexArrayVertexBuffer(termVertexArray, ATTRIBUTE_POSITION, termVertices, 0, 16);
glVertexArrayVertexBuffer(termVertexArray, ATTRIBUTE_UV, termVertices, 0, 16);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

View File

@@ -27,7 +27,7 @@ pluginManagement {
}
}
maven("https://squiddev.cc/maven") {
maven("https://maven.squiddev.cc") {
name = "SquidDev"
content {
includeGroup("cc.tweaked.vanilla-extract")