Merge branch 'mc-1.19.x' of github.com:cc-tweaked/CC-Tweaked into mc-1.19.x

This commit is contained in:
SirEdvin 2023-06-18 13:02:04 +03:00
commit 593a1e2e8e
76 changed files with 892 additions and 272 deletions

View File

@ -11,6 +11,7 @@ body:
- 1.16.x
- 1.18.x
- 1.19.x
- 1.20.x
validations:
required: true
- type: input

View File

@ -104,6 +104,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
tasks.processResources {
exclude("**/*.license")
exclude(".cache")
}
tasks.withType(AbstractArchiveTask::class.java).configureEach {

View File

@ -33,7 +33,6 @@ val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
enabled = apiToken != ""
val mainFile = upload("282001", modPublishing.output.get().archiveFile)
dependsOn(modPublishing.output) // See https://github.com/Darkhax/CurseForgeGradle/pull/7.
mainFile.changelog =
"Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
mainFile.changelogType = "markdown"

View File

@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: MPL-2.0
import org.gradle.kotlin.dsl.`maven-publish`
plugins {
`java-library`
`maven-publish`

View File

@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.104.0
modVersion=1.105.0
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.19.4

View File

@ -50,7 +50,7 @@ junit = "5.9.2"
# Build tools
cctJavadoc = "1.7.0"
checkstyle = "10.3.4"
curseForgeGradle = "1.0.11"
curseForgeGradle = "1.0.14"
errorProne-core = "2.18.0"
errorProne-plugin = "3.0.1"
fabric-loom = "1.1.10"

View File

@ -75,7 +75,9 @@ public interface ITurtleAccess {
* @param f The subframe fraction.
* @return A vector containing the floating point co-ordinates at which the turtle resides.
* @see #getVisualYaw(float)
* @deprecated Will be removed in 1.20.
*/
@Deprecated(forRemoval = true)
Vec3 getVisualPosition(float f);
/**
@ -84,7 +86,9 @@ public interface ITurtleAccess {
* @param f The subframe fraction.
* @return The yaw the turtle is facing.
* @see #getVisualPosition(float)
* @deprecated Will be removed in 1.20.
*/
@Deprecated(forRemoval = true)
float getVisualYaw(float f);
/**

View File

@ -24,6 +24,7 @@
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -104,7 +105,7 @@ public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser,
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
@Override
public final CompletableFuture<?> run(CachedOutput cache) {
public CompletableFuture<?> run(CachedOutput cache) {
var base = output.getOutputFolder().resolve("data");
Set<ResourceLocation> seen = new HashSet<>();
@ -127,7 +128,7 @@ public final CompletableFuture<?> run(CachedOutput cache) {
}
});
this.upgrades = upgrades;
this.upgrades = Collections.unmodifiableList(upgrades);
return Util.sequenceFailFast(futures);
}

View File

@ -23,7 +23,7 @@
* A {@link TableFormatter} subclass which writes directly to {@linkplain ChatComponent the chat GUI}.
* <p>
* Each message written gets a special {@link GuiMessageTag}, so we can remove the previous table of the same
* {@link TableBuilder#getId() id}.
* {@linkplain TableBuilder#getId() id}.
*/
public class ClientTableFormatter implements TableFormatter {
public static final ClientTableFormatter INSTANCE = new ClientTableFormatter();

View File

@ -26,9 +26,11 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
private static final int TEX_WIDTH = 254;
private static final int TEX_WIDTH = 278;
private static final int TEX_HEIGHT = 217;
private static final int FULL_TEX_SIZE = 512;
public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
super(container, player, title, BORDER);
@ -45,16 +47,16 @@ protected TerminalWidget createTerminal() {
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
var advanced = family == ComputerFamily.ADVANCED;
RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL);
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT);
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
// Render selected slot
var slot = getMenu().getSelectedSlot();
if (slot >= 0) {
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
var slotX = slot % 4;
var slotY = slot / 4;
blit(transform,
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
0, 217, 24, 24
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
);
}

View File

@ -15,6 +15,7 @@
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarratedElementType;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component;
import org.lwjgl.glfw.GLFW;
@ -81,6 +82,11 @@ public boolean charTyped(char ch, int modifiers) {
@Override
public boolean keyPressed(int key, int scancode, int modifiers) {
if (key == GLFW.GLFW_KEY_ESCAPE) return false;
if (Screen.isPaste(key)) {
paste();
return true;
}
if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
switch (key) {
case GLFW.GLFW_KEY_T -> {
@ -92,32 +98,6 @@ public boolean keyPressed(int key, int scancode, int modifiers) {
case GLFW.GLFW_KEY_R -> {
if (rebootTimer < 0) rebootTimer = 0;
}
case GLFW.GLFW_KEY_V -> {
// Ctrl+V for paste
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
if (clipboard != null) {
// Clip to the first occurrence of \r or \n
var newLineIndex1 = clipboard.indexOf("\r");
var newLineIndex2 = clipboard.indexOf("\n");
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
} else if (newLineIndex1 >= 0) {
clipboard = clipboard.substring(0, newLineIndex1);
} else if (newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, newLineIndex2);
}
// Filter the string
clipboard = SharedConstants.filterText(clipboard);
if (!clipboard.isEmpty()) {
// Clip to 512 characters and queue the event
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
computer.queueEvent("paste", new Object[]{ clipboard });
}
return true;
}
}
}
}
@ -131,6 +111,29 @@ public boolean keyPressed(int key, int scancode, int modifiers) {
return true;
}
private void paste() {
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
// Clip to the first occurrence of \r or \n
var newLineIndex1 = clipboard.indexOf('\r');
var newLineIndex2 = clipboard.indexOf('\n');
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
} else if (newLineIndex1 >= 0) {
clipboard = clipboard.substring(0, newLineIndex1);
} else if (newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, newLineIndex2);
}
// Filter the string
clipboard = SharedConstants.filterText(clipboard);
if (!clipboard.isEmpty()) {
// Clip to 512 characters and queue the event
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
computer.queueEvent("paste", new Object[]{ clipboard });
}
}
@Override
public boolean keyReleased(int key, int scancode, int modifiers) {
// Queue the "key_up" event and remove from the down set

View File

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.client;
import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import java.util.List;
import java.util.Optional;
/**
* A version of {@link DataProviders} which relies on client-side classes.
* <p>
* This is called from {@link DataProviders#add(DataProviders.GeneratorSink)}.
*/
public final class ClientDataProviders {
private ClientDataProviders() {
}
public static void add(DataProviders.GeneratorSink generator) {
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(new ResourceLocation("blocks"), List.of(
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
));
});
}
}

View File

@ -37,6 +37,14 @@
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
class BlockModelProvider {
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
private static final ModelTemplate COMPUTER_ON = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer_on")),
Optional.empty(),
TextureSlot.FRONT, TextureSlot.SIDE, TextureSlot.TOP, CURSOR
);
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
Optional.empty(),
@ -142,11 +150,18 @@ private static void registerPrinter(BlockModelGenerators generators) {
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
.with(createHorizontalFacingDispatch())
.with(createModelDispatch(ComputerBlock.STATE, state -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block).put(TextureSlot.FRONT, getBlockTexture(block, "_front" + state.getTexture())),
generators.modelOutput
)))
.with(createModelDispatch(ComputerBlock.STATE, state -> switch (state) {
case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block),
generators.modelOutput
);
case ON, BLINKING -> COMPUTER_ON.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block).put(CURSOR, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer" + state.getTexture())),
generators.modelOutput
);
}))
);
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
}

View File

@ -4,16 +4,20 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.ItemModelGenerators;
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.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@ -37,11 +41,22 @@ public static void add(GeneratorSink generator) {
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
// Unfortunately we rely on some client-side classes in this code. We just load in the client side data provider
// and invoke that.
try {
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
.getMethod("add", GeneratorSink.class).invoke(null, generator);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
interface GeneratorSink {
public interface GeneratorSink {
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
void lootTable(List<SubProviderEntry> tables);
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);

View File

@ -230,6 +230,11 @@ private void addTranslations() {
addConfigEntry(ConfigSpec.httpDownloadBandwidth, "Global download limit");
addConfigEntry(ConfigSpec.httpUploadBandwidth, "Global upload limit");
addConfigGroup(ConfigSpec.serverSpec, "http.proxy", "Proxy");
addConfigEntry(ConfigSpec.httpProxyHost, "Host name");
addConfigEntry(ConfigSpec.httpProxyPort, "Port");
addConfigEntry(ConfigSpec.httpProxyType, "Proxy type");
addConfigGroup(ConfigSpec.serverSpec, "peripheral", "Peripherals");
addConfigEntry(ConfigSpec.commandBlockEnabled, "Enable command block peripheral");
addConfigEntry(ConfigSpec.modemRange, "Modem range (default)");

View File

@ -213,8 +213,10 @@ public AbstractContainerMenu createMenu(int id, Inventory player, Player entity)
getMetricsInstance(context.getSource()).start();
var stopCommand = "/computercraft track stop";
Object[] args = new Object[]{ link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action")) };
context.getSource().sendSuccess(Component.translatable("commands.computercraft.track.start.stop", args), false);
context.getSource().sendSuccess(Component.translatable(
"commands.computercraft.track.start.stop",
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
), false);
return 1;
}))

View File

@ -7,7 +7,6 @@
import net.minecraft.core.NonNullList;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
/**
@ -16,24 +15,6 @@
public interface BasicContainer extends Container {
NonNullList<ItemStack> getContents();
@Override
default int getMaxStackSize() {
return 64;
}
@Override
default void startOpen(Player player) {
}
@Override
default void stopOpen(Player player) {
}
@Override
default boolean canPlaceItem(int slot, ItemStack stack) {
return true;
}
@Override
default int getContainerSize() {
return getContents().size();

View File

@ -5,14 +5,11 @@
package dan200.computercraft.shared.details;
import com.google.gson.JsonParseException;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
@ -45,7 +42,9 @@ public static void fill(Map<? super String, Object> data, ItemStack stack) {
}
data.put("tags", DetailHelpers.getTags(stack.getTags()));
data.put("itemGroups", getItemGroups(stack));
// Include deprecated itemGroups field
data.put("itemGroups", List.of());
var tag = stack.getTag();
if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) {
@ -84,27 +83,6 @@ private static Component parseTextComponent(Tag x) {
}
}
/**
* Retrieve all item groups an item stack pertains to.
*
* @param stack Stack to analyse
* @return A filled list that contains pairs of item group IDs and their display names.
*/
private static List<Map<String, Object>> getItemGroups(ItemStack stack) {
return CreativeModeTabs.allTabs().stream()
.filter(x -> x.shouldDisplay() && x.getType() == CreativeModeTab.Type.CATEGORY && x.contains(stack))
.map(group -> {
Map<String, Object> groupData = new HashMap<>(2);
var id = PlatformHelper.get().getCreativeTabId(group);
if (id != null) groupData.put("id", id.toString());
groupData.put("displayName", group.getDisplayName().getString());
return groupData;
})
.toList();
}
/**
* Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility.
*

View File

@ -190,7 +190,7 @@ public String getType() {
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
* These are:
* <p>
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, @code "flute"},
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, {@code "flute"},
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
*

View File

@ -33,7 +33,6 @@
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -273,15 +272,6 @@ static PlatformHelper get() {
*/
int getBurnTime(ItemStack stack);
/**
* Get a unique identifier for this creative tab.
*
* @param tab The tab to get
* @return The unique identifier, or {@code null} if not available.
*/
@Nullable
ResourceLocation getCreativeTabId(CreativeModeTab tab);
/**
* Get the "container" item to be returned after crafting. For instance, crafting with a lava bucket should return
* an empty bucket.

View File

@ -168,7 +168,7 @@ private static boolean deployOnBlock(
if (Math.abs(hitY - 0.5f) < 0.01f) hitY = 0.45f;
// Check if there's something suitable to place onto
var hit = new BlockHitResult(new Vec3(hitX, hitY, hitZ), side, position, false);
var hit = new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false);
var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit);
if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, adjacent, outErrorMessage)) {
return false;

View File

@ -4,6 +4,7 @@
package dan200.computercraft.shared.turtle.inventory;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
@ -29,12 +30,13 @@ public final class TurtleMenu extends AbstractComputerMenu {
public static final int PLAYER_START_Y = 134;
public static final int TURTLE_START_X = SIDEBAR_WIDTH + 175;
public static final int PLAYER_START_X = SIDEBAR_WIDTH + BORDER;
public static final int UPGRADE_START_X = SIDEBAR_WIDTH + 254;
private final ContainerData data;
private TurtleMenu(
int id, Predicate<Player> canUse, ComputerFamily family, @Nullable ServerComputer computer, @Nullable ComputerContainerData menuData,
Inventory playerInventory, Container inventory, ContainerData data
Inventory playerInventory, Container inventory, Container turtleUpgrades, ContainerData data
) {
super(ModRegistry.Menus.TURTLE.get(), id, canUse, family, computer, menuData);
this.data = data;
@ -58,19 +60,24 @@ private TurtleMenu(
for (var x = 0; x < 9; x++) {
addSlot(new Slot(playerInventory, x, PLAYER_START_X + x * 18, PLAYER_START_Y + 3 * 18 + 5));
}
// 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));
}
public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) {
return new TurtleMenu(
// Laziness in turtle.getOwner() is important here!
id, p -> turtle.getOwner().stillValid(p), turtle.getFamily(), turtle.getOwner().createServerComputer(), null,
player, turtle.getInventory(), (SingleContainerData) turtle::getSelectedSlot
player, turtle.getInventory(), new UpgradeContainer(turtle), (SingleContainerData) turtle::getSelectedSlot
);
}
public static TurtleMenu ofMenuData(int id, Inventory player, ComputerContainerData data) {
return new TurtleMenu(
id, x -> true, data.family(), null, data, player, new SimpleContainer(TurtleBlockEntity.INVENTORY_SIZE), new SimpleContainerData(1)
id, x -> true, data.family(), null, data,
player, new SimpleContainer(TurtleBlockEntity.INVENTORY_SIZE), new SimpleContainer(2), new SimpleContainerData(1)
);
}

View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.turtle.inventory;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.TurtleUpgrades;
import net.minecraft.core.NonNullList;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import java.util.Arrays;
import java.util.List;
/**
* A fake {@link Container} which exposes the {@linkplain ITurtleAccess#getUpgrade(TurtleSide) upgrades} a turtle has.
*
* @see TurtleMenu
* @see UpgradeSlot
*/
class UpgradeContainer implements Container {
private static final int SIZE = 2;
private final ITurtleAccess turtle;
private final List<ITurtleUpgrade> lastUpgrade = Arrays.asList(null, null);
private final NonNullList<ItemStack> lastStack = NonNullList.withSize(2, ItemStack.EMPTY);
UpgradeContainer(ITurtleAccess turtle) {
this.turtle = turtle;
}
private TurtleSide getSide(int slot) {
return switch (slot) {
case 0 -> TurtleSide.LEFT;
case 1 -> TurtleSide.RIGHT;
default -> throw new IllegalArgumentException("Invalid slot " + slot);
};
}
@Override
public ItemStack getItem(int slot) {
var upgrade = turtle.getUpgrade(getSide(slot));
// We don't want to return getCraftingItem directly here, as consumers may mutate the stack (they shouldn't!,
// but if they do it's a pain to track down). To avoid recreating the stack each tick, we maintain a simple
// cache.
if (upgrade == lastUpgrade.get(slot)) return lastStack.get(slot);
var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getCraftingItem().copy();
lastUpgrade.set(slot, upgrade);
lastStack.set(slot, stack);
return stack;
}
@Override
public void setItem(int slot, ItemStack itemStack) {
turtle.setUpgrade(getSide(slot), TurtleUpgrades.instance().get(itemStack));
}
@Override
public int getContainerSize() {
return SIZE;
}
@Override
public int getMaxStackSize() {
return 1;
}
@Override
public boolean isEmpty() {
for (var i = 0; i < SIZE; i++) {
if (!getItem(i).isEmpty()) return false;
}
return true;
}
@Override
public ItemStack removeItem(int slot, int count) {
return count <= 0 ? ItemStack.EMPTY : removeItemNoUpdate(slot);
}
@Override
public ItemStack removeItemNoUpdate(int slot) {
var current = getItem(slot);
setItem(slot, ItemStack.EMPTY);
return current;
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public void clearContent() {
for (var i = 0; i < SIZE; i++) setItem(i, ItemStack.EMPTY);
}
}

View File

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.turtle.inventory;
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.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
/**
* A slot in the turtle UI which holds the turtle's current upgrade.
*
* @see TurtleMenu
*/
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 TurtleSide side;
public UpgradeSlot(Container container, TurtleSide side, int slot, int xPos, int yPos) {
super(container, slot, xPos, yPos);
this.side = side;
}
@Override
public boolean mayPlace(ItemStack stack) {
return TurtleUpgrades.instance().get(stack) != null;
}
@Override
public int getMaxStackSize() {
return 1;
}
@Nullable
@Override
public Pair<ResourceLocation, ResourceLocation> getNoItemIcon() {
return Pair.of(InventoryMenu.BLOCK_ATLAS, side == TurtleSide.LEFT ? LEFT_UPGRADE : RIGHT_UPGRADE);
}
}

View File

@ -115,5 +115,67 @@
"upgrade.minecraft.diamond_pickaxe.adjective": "Добывающая",
"upgrade.minecraft.diamond_shovel.adjective": "Копающая",
"upgrade.minecraft.diamond_sword.adjective": "Боевая",
"gui.computercraft.pocket_computer_overlay": "Карманный компьютер открыт. Чтобы закрыть, нажми ESC."
"gui.computercraft.pocket_computer_overlay": "Карманный компьютер открыт. Чтобы закрыть, нажми ESC.",
"gui.computercraft.config.command_require_creative.tooltip": "Требовать творческий режим и права оператора для взаимодействия с\nкомандными компьютерами. Это поведение по умолчанию для Командных блоков ванильной игры.",
"gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.",
"gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.\nОграничение: > 1",
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "Идеальный максимум времени, которое отведено компьютеру на выполнение задач, в миллисекундах.\nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.\nОграничение: > 1",
"gui.computercraft.config.execution.max_main_global_time.tooltip": "Максимум времени, которое может быть потрачено на выполнение задач за один тик, в \nмиллисекундах. \nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.\nОграничение: > 1",
"gui.computercraft.config.http.bandwidth.global_download": "Глобальный лимит на скачивание",
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "Количество байтов, которое можно скачать за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)\nОграничение: > 1",
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Количество байтов, которое можно загрузить за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)\nОграничение: > 1",
"tracking_field.computercraft.http_requests.name": "HTTP запросы",
"tracking_field.computercraft.turtle_ops.name": "Операции Черепашек",
"gui.computercraft.config.http.enabled.tooltip": "Включить API \"http\" на Компьютерах. Это также отключает программы \"pastebin\" и \"wget\", \nкоторые нужны многим пользователям. Рекомендуется оставить это включенным и использовать \nконфиг \"rules\" для более тонкой настройки.",
"gui.computercraft.config.http.max_websockets.tooltip": "Количество одновременно открытых веб-сокетов, которые может иметь компьютер. Установите на 0 для неограниченных веб-сокетов.\nОграничение: > 1",
"gui.computercraft.config.term_sizes": "Размер терминала",
"gui.computercraft.config.term_sizes.computer.height": "Высота терминала",
"gui.computercraft.config.term_sizes.monitor.height": "Максимальная высота монитора",
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Ограничение: 1 ~ 32",
"gui.computercraft.config.turtle.advanced_fuel_limit": "Лимит топлива Продвинутых Черепашек",
"gui.computercraft.config.turtle.need_fuel.tooltip": "Устанавливает, нуждаются ли Черепашки в топливе для передвижения.",
"gui.computercraft.terminal": "Компьютерный терминал",
"tracking_field.computercraft.computer_tasks.name": "Задачи",
"tracking_field.computercraft.server_tasks.name": "Серверные задачи",
"gui.computercraft.upload.no_response": "Перенос файлов",
"tracking_field.computercraft.avg": "%s (среднее)",
"gui.computercraft.config.command_require_creative": "Для использования командных компьютеров нужен творческий режим",
"gui.computercraft.config.computer_space_limit": "Лимит места на компьютерах (в байтах)",
"gui.computercraft.config.computer_space_limit.tooltip": "Лимит места на дисках компьютеров и черепашек, в байтах.",
"gui.computercraft.config.default_computer_settings": "Настройки Компьютера по умолчанию",
"gui.computercraft.config.disable_lua51_features": "Отключить функции Lua 5.1",
"gui.computercraft.config.disable_lua51_features.tooltip": "Поставьте, чтобы отключить функции из Lua 5.1, которые будут убраны в будущих\nобновлениях. Полезно для того, чтобы улучшить совместимость вперед ваших программ.",
"gui.computercraft.config.execution": "Выполнение",
"gui.computercraft.config.execution.computer_threads": "Потоки компьютера",
"gui.computercraft.config.execution.max_main_global_time": "Глобальный лимит времени на тик сервера",
"gui.computercraft.config.execution.tooltip": "Контролирует поведение выполнения задач компьютеров. Эта настройка преднезначается для \nтонкой настройки серверов, и в основном не должна быть изменена.",
"gui.computercraft.config.floppy_space_limit": "Лимит места на дискетах (байты)",
"gui.computercraft.config.floppy_space_limit.tooltip": "Лимит места для хранения информации на дискетах, в байтах.",
"gui.computercraft.config.http": "HTTP",
"gui.computercraft.config.http.bandwidth": "Пропускная способность",
"gui.computercraft.config.http.bandwidth.global_upload": "Глобальный лимит загрузки",
"gui.computercraft.config.http.bandwidth.tooltip": "Ограничивает пропускную способность, используемую компьютерами.",
"gui.computercraft.config.http.enabled": "Включить HTTP API",
"gui.computercraft.config.http.max_requests": "Максимум одновременных запросов",
"gui.computercraft.config.http.max_requests.tooltip": "Количество http-запросов, которые компьютер может сделать одновременно. Дополнительные запросы \nбудут поставлены в очередь, и отправлены когда существующие запросы будут выполнены. Установите на 0 для \nнеограниченных запросов.\nОграничение: > 0",
"gui.computercraft.config.http.max_websockets": "Максимум одновременных веб-сокетов",
"gui.computercraft.config.term_sizes.computer": "Компьютер",
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.term_sizes.computer.tooltip": "Размер терминала на компьютерах.",
"gui.computercraft.config.term_sizes.computer.width": "Ширина терминала",
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.term_sizes.monitor": "Монитор",
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Ограничение: 1 ~ 32",
"gui.computercraft.config.term_sizes.monitor.tooltip": "Максимальный размер мониторов (в блоках).",
"gui.computercraft.config.term_sizes.monitor.width": "Максимальная ширина мониторов",
"gui.computercraft.config.term_sizes.pocket_computer.height": "Высота терминала",
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.term_sizes.pocket_computer.width": "Ширина терминала",
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.turtle": "Черепашки",
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "Лимит топлива для Продвинутых Черепашек.\nОграничение: > 0",
"gui.computercraft.config.turtle.need_fuel": "Включить механику топлива",
"gui.computercraft.config.turtle.normal_fuel_limit": "Лимит топлива Черепашек",
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Лимит топлива для Черепашек.\nОграничение: > 0",
"gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками."
}

View File

@ -0,0 +1,39 @@
{
"parent": "minecraft:block/block",
"render_type": "cutout",
"textures": {
"particle": "#front"
},
"display": {
"firstperson_righthand": {
"rotation": [ 0, 135, 0 ],
"translation": [ 0, 0, 0 ],
"scale": [ 0.40, 0.40, 0.40 ]
}
},
"elements": [
{
"from": [ 0, 0, 0 ],
"to": [ 16, 16, 16 ],
"faces": {
"down": { "texture": "#top", "cullface": "down" },
"up": { "texture": "#top", "cullface": "up" },
"north": { "texture": "#front", "cullface": "north" },
"south": { "texture": "#side", "cullface": "south" },
"west": { "texture": "#side", "cullface": "west" },
"east": { "texture": "#side", "cullface": "east" }
}
},
{
"from": [ 0, 0, 0 ],
"to": [ 16, 16, 16 ],
"faces": {
"north": {
"texture": "#cursor",
"cullface": "north",
"forge_data": {"block_light": 15, "sky_light": 15}
}
}
}
]
}

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

View File

@ -1,6 +0,0 @@
{
"animation": {
"frametime": 8,
"frames": [ 0, 1 ]
}
}

View File

@ -1,6 +0,0 @@
{
"animation": {
"frametime": 8,
"frames": [ 0, 1 ]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1004 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0

View File

@ -36,7 +36,6 @@
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
@ -165,13 +164,6 @@ public boolean onNotifyNeighbour(Level level, BlockPos pos, BlockState block, Di
throw new UnsupportedOperationException("Cannot interact with the world inside tests");
}
@Nullable
@Override
public ResourceLocation getCreativeTabId(CreativeModeTab tab) {
return null;
}
@Override
public RecipeIngredients getRecipeIngredients() {
throw new UnsupportedOperationException("Cannot query recipes inside tests");

View File

@ -443,28 +443,6 @@ public final Object getFreeSpace(String path) throws LuaException {
}
}
/**
* Searches for files matching a string with wildcards.
* <p>
* This string is formatted like a normal path string, but can include any
* number of wildcards ({@code *}) to look for files matching anything.
* For example, <code>rom/&#42;/command*</code> will look for any path starting with
* {@code command} inside any subdirectory of {@code /rom}.
*
* @param path The wildcard-qualified path to search for.
* @return A list of paths that match the search string.
* @throws LuaException If the path doesn't exist.
* @cc.since 1.6
*/
@LuaFunction
public final String[] find(String path) throws LuaException {
try (var ignored = environment.time(Metrics.FS_OPS)) {
return getFileSystem().find(path);
} catch (FileSystemException e) {
throw new LuaException(e.getMessage());
}
}
/**
* Returns the capacity of the drive the path is located on.
*

View File

@ -166,47 +166,6 @@ public synchronized String[] list(String path) throws FileSystemException {
return array;
}
private void findIn(String dir, List<String> matches, Pattern wildPattern) throws FileSystemException {
var list = list(dir);
for (var entry : list) {
var entryPath = dir.isEmpty() ? entry : dir + "/" + entry;
if (wildPattern.matcher(entryPath).matches()) {
matches.add(entryPath);
}
if (isDir(entryPath)) {
findIn(entryPath, matches, wildPattern);
}
}
}
public synchronized String[] find(String wildPath) throws FileSystemException {
// Match all the files on the system
wildPath = sanitizePath(wildPath, true);
// If we don't have a wildcard at all just check the file exists
var starIndex = wildPath.indexOf('*');
if (starIndex == -1) {
return exists(wildPath) ? new String[]{ wildPath } : new String[0];
}
// Find the all non-wildcarded directories. For instance foo/bar/baz* -> foo/bar
var prevDir = wildPath.substring(0, starIndex).lastIndexOf('/');
var startDir = prevDir == -1 ? "" : wildPath.substring(0, prevDir);
// If this isn't a directory then just abort
if (!isDir(startDir)) return new String[0];
// Scan as normal, starting from this directory
var wildPattern = Pattern.compile("^\\Q" + wildPath.replaceAll("\\*", "\\\\E[^\\\\/]*\\\\Q") + "\\E$");
List<String> matches = new ArrayList<>();
findIn(startDir, matches, wildPattern);
// Return matches
var array = new String[matches.size()];
matches.toArray(array);
return array;
}
public synchronized boolean exists(String path) throws FileSystemException {
path = sanitizePath(path);
var mount = getMount(path);
@ -400,21 +359,20 @@ private static String sanitizePath(String path) {
private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$");
// IMPORTANT: Both arrays are sorted by ASCII value.
private static final char[] specialChars = new char[]{ '"', '*', ':', '<', '>', '?', '|' };
private static final char[] specialCharsAllowWildcards = new char[]{ '"', ':', '<', '>', '|' };
public static String sanitizePath(String path, boolean allowWildcards) {
// Allow windowsy slashes
path = path.replace('\\', '/');
// Clean the path or illegal characters.
final var specialChars = new char[]{
'"', ':', '<', '>', '?', '|', // Sorted by ascii value (important)
};
var cleanName = new StringBuilder();
var allowedChars = allowWildcards ? specialCharsAllowWildcards : specialChars;
for (var i = 0; i < path.length(); i++) {
var c = path.charAt(i);
if (c >= 32 && Arrays.binarySearch(specialChars, c) < 0 && (allowWildcards || c != '*')) {
cleanName.append(c);
}
if (c >= 32 && Arrays.binarySearch(allowedChars, c) < 0) cleanName.append(c);
}
path = cleanName.toString();

View File

@ -133,6 +133,93 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
return {}
end
local function find_aux(path, parts, i, out)
local part = parts[i]
if not part then
-- If we're at the end of the pattern, ensure our path exists and append it.
if fs.exists(path) then out[#out + 1] = path end
elseif part.exact then
-- If we're an exact match, just recurse into this directory.
return find_aux(fs.combine(path, part.contents), parts, i + 1, out)
else
-- Otherwise we're a pattern. Check we're a directory, then recurse into each
-- matching file.
if not fs.isDir(path) then return end
local files = fs.list(path)
for j = 1, #files do
local file = files[j]
if file:find(part.contents) then find_aux(fs.combine(path, file), parts, i + 1, out) end
end
end
end
local find_escape = {
-- Escape standard Lua pattern characters
["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%",
["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-",
-- Aside from our wildcards.
["*"] = ".*",
["?"] = ".",
}
--[[- Searches for files matching a string with wildcards.
This string looks like a normal path string, but can include wildcards, which
can match multiple paths:
- "?" matches any single character in a file name.
- "*" matches any number of characters.
For example, `rom/*/command*` will look for any path starting with `command`
inside any subdirectory of `/rom`.
Note that these wildcards match a single segment of the path. For instance
`rom/*.lua` will include `rom/startup.lua` but _not_ include `rom/programs/list.lua`.
@tparam string path The wildcard-qualified path to search for.
@treturn { string... } A list of paths that match the search string.
@throws If the supplied path was invalid.
@since 1.6
@changed 1.106.0 Added support for the `?` wildcard.
@usage List all Markdown files in the help folder
fs.find("rom/help/*.md")
]]
function fs.find(pattern)
expect(1, pattern, "string")
pattern = fs.combine(pattern) -- Normalise the path, removing ".."s.
-- If the pattern is trying to search outside the computer root, just abort.
-- This will fail later on anyway.
if pattern == ".." or pattern:sub(1, 3) == "../" then
error("/" .. pattern .. ": Invalid Path", 2)
end
-- If we've no wildcards, just check the file exists.
if not pattern:find("[*?]") then
if fs.exists(pattern) then return { pattern } else return {} end
end
local parts = {}
for part in pattern:gmatch("[^/]+") do
if part:find("[*?]") then
parts[#parts + 1] = {
exact = false,
contents = "^" .. part:gsub(".", find_escape) .. "$",
}
else
parts[#parts + 1] = { exact = true, contents = part }
end
end
local out = {}
find_aux("", parts, 1, out)
return out
end
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and

View File

@ -1,3 +1,27 @@
# New features in CC: Tweaked 1.105.0
* Optimise JSON string parsing.
* Add `colors.fromBlit` (Erb3).
* Upload file size limit is now configurable (khankul).
* Wired cables no longer have a distance limit.
* Java methods now coerce values to strings consistently with Lua.
* Add custom timeout support to the HTTP API.
* Support custom proxies for HTTP requests (Lemmmy).
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).
Several bug fixes:
* Allow GPS hosts to only be 1 block apart.
* Fix "Turn On"/"Turn Off" buttons being inverted in the computer GUI (Erb3).
* Fix arrow keys not working in the printout UI.
* Several documentation fixes (zyxkad, Lupus590, Commandcracker).
* Fix monitor renderer debug text always being visible on Forge.
* Fix crash when another mod changes the LoggerContext.
* Fix the `monitor_renderer` option not being present in Fabric config files.
* Pasting on MacOS/OSX now uses Cmd+V rather than Ctrl+V.
* Fix turtles placing blocks upside down when at y<0.
# New features in CC: Tweaked 1.104.0
* Update to Minecraft 1.19.4.

View File

@ -1,31 +1,25 @@
New features in CC: Tweaked 1.104.0
New features in CC: Tweaked 1.105.0
* Update to Minecraft 1.19.4.
* Turtles can now right click items "into" certain blocks (cauldrons and hives by default, configurable with the `computercraft:turtle_can_use` block tag).
* Update Cobalt to 0.7:
* `table` methods and `ipairs` now use metamethods.
* Type errors now use the `__name` metatag.
* Coroutines no longer run on multiple threads.
* Timeout errors should be thrown more reliably.
* `speaker` program now reports an error on common unsupported audio formats.
* `multishell` now hides the implementation details of its terminal redirect from programs.
* Use VBO monitor renderer by default.
* Improve syntax errors when missing commas in tables, and on trailing commas in parameter lists.
* Turtles can now hold flags.
* Update several translations (Alessandro, chesiren, Erlend, RomanPlayer22).
* Optimise JSON string parsing.
* Add `colors.fromBlit` (Erb3).
* Upload file size limit is now configurable (khankul).
* Wired cables no longer have a distance limit.
* Java methods now coerce values to strings consistently with Lua.
* Add custom timeout support to the HTTP API.
* Support custom proxies for HTTP requests (Lemmmy).
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).
Several bug fixes:
* `settings.load` now ignores malformed values created by editing the `.settings` file by hand.
* Fix introduction dates on `os.cancelAlarm` and `os.cancelTimer` (MCJack123).
* Fix the REPL syntax reporting crashing on valid parses.
* Make writes to the ID file atomic.
* Obey stack limits when transferring items with Fabric's APIs.
* Ignore metatables in `textutils.serialize`.
* Correctly recurse into NBT lists when computing the NBT hash (Lemmmy).
* Fix advanced pocket computers rendering as greyscale.
* Fix stack overflow when using `shell` as a hashbang program.
* Fix websocket messages being empty when using a non-default compression settings.
* Fix `gps.locate` returning `nan` when receiving a duplicate location (Wojbie).
* Remove several thread safety issues inside Java-side argument parsing code.
* Allow GPS hosts to only be 1 block apart.
* Fix "Turn On"/"Turn Off" buttons being inverted in the computer GUI (Erb3).
* Fix arrow keys not working in the printout UI.
* Several documentation fixes (zyxkad, Lupus590, Commandcracker).
* Fix monitor renderer debug text always being visible on Forge.
* Fix crash when another mod changes the LoggerContext.
* Fix the `monitor_renderer` option not being present in Fabric config files.
* Pasting on MacOS/OSX now uses Cmd+V rather than Ctrl+V.
* Fix turtles placing blocks upside down when at y<0.
Type "help changelog" to see the full version history.

View File

@ -2,7 +2,7 @@
--
-- SPDX-License-Identifier: MPL-2.0
--- Read and draw nbt ("Nitrogen Fingers Text") images.
--- Read and draw nft ("Nitrogen Fingers Text") images.
--
-- nft ("Nitrogen Fingers Text") is a file format for drawing basic images.
-- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured

View File

@ -87,6 +87,53 @@ describe("The fs library", function()
end)
end)
describe("fs.find", function()
it("fails on invalid paths", function()
expect.error(fs.find, ".."):eq("/..: Invalid Path")
expect.error(fs.find, "../foo/bar"):eq("/../foo/bar: Invalid Path")
end)
it("returns nothing on non-existent files", function()
expect(fs.find("no/such/file")):same {}
expect(fs.find("no/such/*")):same {}
expect(fs.find("no/*/file")):same {}
end)
it("returns a single file", function()
expect(fs.find("rom")):same { "rom" }
expect(fs.find("rom/motd.txt")):same { "rom/motd.txt" }
end)
it("supports the '*' wildcard", function()
expect(fs.find("rom/*")):same {
"rom/apis",
"rom/autorun",
"rom/help",
"rom/modules",
"rom/motd.txt",
"rom/programs",
"rom/startup.lua",
}
expect(fs.find("rom/*/command")):same {
"rom/apis/command",
"rom/modules/command",
"rom/programs/command",
}
expect(fs.find("rom/*/lua*")):same {
"rom/help/lua.txt",
"rom/programs/lua.lua",
}
end)
it("supports the '?' wildcard", function()
expect(fs.find("rom/programs/mo??.lua")):same {
"rom/programs/motd.lua",
"rom/programs/move.lua",
}
end)
end)
describe("fs.combine", function()
it("removes . and ..", function()
expect(fs.combine("./a/b")):eq("a/b")

View File

@ -119,7 +119,7 @@ loom {
register("data") {
configName = "Datagen"
server()
client()
runDir("run/dataGen")
property("cct.pretty-json")
@ -165,7 +165,6 @@ tasks.processResources {
filesMatching("fabric.mod.json") {
expand(mapOf("version" to modVersion))
}
exclude(".cache")
}
tasks.jar {
@ -251,3 +250,7 @@ publishing {
}
}
}
modrinth {
required.project("fabric-api")
}

View File

@ -4,6 +4,7 @@
package dan200.computercraft.client;
import dan200.computercraft.client.model.EmissiveComputerModel;
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
@ -35,9 +36,12 @@ public static void init() {
ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register);
ClientRegistry.registerMainThread();
ModelLoadingRegistry.INSTANCE.registerModelProvider((manager, out) -> ClientRegistry.registerExtraModels(out));
ModelLoadingRegistry.INSTANCE.registerResourceProvider(loader -> (path, ctx) -> TurtleModelLoader.load(loader, path));
ModelLoadingRegistry.INSTANCE.registerResourceProvider(loader -> (path, ctx) -> EmissiveComputerModel.load(loader, path));
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_COMMAND.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_ADVANCED.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.MONITOR_NORMAL.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.MONITOR_ADVANCED.get(), RenderType.cutout());

View File

@ -0,0 +1,172 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Either;
import dan200.computercraft.api.ComputerCraftAPI;
import net.fabricmc.fabric.api.client.model.ModelProviderException;
import net.fabricmc.fabric.api.client.model.ModelResourceProvider;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.RandomSource;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Wraps a computer's {@link BlockModel}/{@link BakedModel} to render the computer's cursor as an emissive quad.
* <p>
* While Fabric has a quite advanced rendering extension API (including support for custom materials), but unlike Forge
* it doesn't expose this in the model JSON (though externals mods like <a href="https://github.com/vram-guild/json-model-extensions/">JMX</a>
* do handle this).
* <p>
* Instead, we support emissive quads by injecting a custom {@linkplain ModelResourceProvider model loader/provider}
* which targets a hard-coded list of computer models, and wraps the returned model in a custom
* {@linkplain FabricBakedModel} implementation which renders specific quads as emissive.
* <p>
* See also the <code>assets/computercraft/models/block/computer_on.json</code> model, which is the base for all
* emissive computer models.
*/
public final class EmissiveComputerModel {
private static final Set<String> MODELS = Set.of(
"item/computer_advanced",
"block/computer_advanced_on",
"block/computer_advanced_blinking",
"item/computer_command",
"block/computer_command_on",
"block/computer_command_blinking",
"item/computer_normal",
"block/computer_normal_on",
"block/computer_normal_blinking"
);
private EmissiveComputerModel() {
}
public static @Nullable UnbakedModel load(ResourceManager resources, ResourceLocation path) throws ModelProviderException {
if (!path.getNamespace().equals(ComputerCraftAPI.MOD_ID) || !MODELS.contains(path.getPath())) return null;
JsonObject json;
try (var reader = resources.openAsReader(new ResourceLocation(path.getNamespace(), "models/" + path.getPath() + ".json"))) {
json = GsonHelper.parse(reader).getAsJsonObject();
} catch (IOException e) {
throw new ModelProviderException("Failed loading model " + path, e);
}
// Parse a subset of the model JSON
var parent = new ResourceLocation(GsonHelper.getAsString(json, "parent"));
Map<String, Either<Material, String>> textures = new HashMap<>();
if (json.has("textures")) {
var jsonObject = GsonHelper.getAsJsonObject(json, "textures");
for (var entry : jsonObject.entrySet()) {
var texture = entry.getValue().getAsString();
textures.put(entry.getKey(), texture.startsWith("#")
? Either.right(texture.substring(1))
: Either.left(new Material(InventoryMenu.BLOCK_ATLAS, new ResourceLocation(texture)))
);
}
}
return new Unbaked(parent, textures);
}
/**
* An {@link UnbakedModel} which wraps the returned model using {@link Baked}.
* <p>
* This subclasses {@link BlockModel} to allow using these models as a parent of other models.
*/
private static final class Unbaked extends BlockModel {
Unbaked(ResourceLocation parent, Map<String, Either<Material, String>> materials) {
super(parent, List.of(), materials, null, null, ItemTransforms.NO_TRANSFORMS, List.of());
}
@Override
public BakedModel bake(ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state, ResourceLocation location) {
var baked = super.bake(baker, spriteGetter, state, location);
if (!hasTexture("cursor")) return baked;
var render = RendererAccess.INSTANCE.getRenderer();
if (render == null) return baked;
return new Baked(
baked,
spriteGetter.apply(getMaterial("cursor")),
render.materialFinder().find(),
render.materialFinder().emissive(0, true).find()
);
}
}
/**
* A {@link FabricBakedModel} which renders quads using the {@code "cursor"} texture as emissive.
*/
private static final class Baked extends ForwardingBakedModel {
private final TextureAtlasSprite cursor;
private final RenderMaterial defaultMaterial;
private final RenderMaterial emissiveMaterial;
Baked(BakedModel wrapped, TextureAtlasSprite cursor, RenderMaterial defaultMaterial, RenderMaterial emissiveMaterial) {
this.wrapped = wrapped;
this.cursor = cursor;
this.defaultMaterial = defaultMaterial;
this.emissiveMaterial = emissiveMaterial;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
emitQuads(context, state, randomSupplier.get());
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
emitQuads(context, null, randomSupplier.get());
}
private void emitQuads(RenderContext context, @Nullable BlockState state, RandomSource random) {
var emitter = context.getEmitter();
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
var cullFace = ModelHelper.faceFromIndex(faceIdx);
var quads = wrapped.getQuads(state, cullFace, random);
var count = quads.size();
for (var i = 0; i < count; i++) {
final var q = quads.get(i);
emitter.fromVanilla(q, q.getSprite() == cursor ? emissiveMaterial : defaultMaterial, cullFace);
emitter.emit();
}
}
}
}
}

View File

@ -104,6 +104,14 @@
"gui.computercraft.config.http.max_requests.tooltip": "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited.\nRange: > 0",
"gui.computercraft.config.http.max_websockets": "Maximum concurrent websockets",
"gui.computercraft.config.http.max_websockets.tooltip": "The number of websockets a computer can have open at one time. Set to 0 for unlimited.\nRange: > 1",
"gui.computercraft.config.http.proxy": "Proxy",
"gui.computercraft.config.http.proxy.host": "Host name",
"gui.computercraft.config.http.proxy.host.tooltip": "The hostname or IP address of the proxy server.",
"gui.computercraft.config.http.proxy.port": "Port",
"gui.computercraft.config.http.proxy.port.tooltip": "The port of the proxy server.\nRange: 1 ~ 65536",
"gui.computercraft.config.http.proxy.tooltip": "Tunnels HTTP and websocket requests through a proxy server. Only affects HTTP\nrules with \"use_proxy\" set to true (off by default).\nIf authentication is required for the proxy, create a \"computercraft-proxy.pw\"\nfile in the same directory as \"computercraft-server.toml\", containing the\nusername and password separated by a colon, e.g. \"myuser:mypassword\". For\nSOCKS4 proxies only the username is required.",
"gui.computercraft.config.http.proxy.type": "Proxy type",
"gui.computercraft.config.http.proxy.type.tooltip": "The type of proxy to use.\nAllowed Values: HTTP, HTTPS, SOCKS4, SOCKS5",
"gui.computercraft.config.http.rules": "Allow/deny rules",
"gui.computercraft.config.http.rules.tooltip": "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule is an item with a 'host' to match against, and a series of\nproperties. Rules are evaluated in order, meaning earlier rules override later\nones.\nThe host may be a domain name (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or\nCIDR notation (\"127.0.0.0/8\").\nIf no rules, the domain is blocked.",
"gui.computercraft.config.http.tooltip": "Controls the HTTP API",

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -0,0 +1,6 @@
{
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
]
}

View File

@ -4,21 +4,25 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import dan200.computercraft.shared.platform.RegistryWrappers;
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.FabricModelProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.TagsProvider;
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;
@ -43,6 +47,27 @@ public <T extends DataProvider> T add(DataProvider.Factory<T> factory) {
return generator.addProvider(factory);
}
@Override
public <T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output) {
generator.addProvider((FabricDataOutput out) -> {
var ourType = switch (type) {
case SERVER_DATA -> PackOutput.Target.DATA_PACK;
case CLIENT_RESOURCES -> PackOutput.Target.RESOURCE_PACK;
};
return new FabricCodecDataProvider<T>(out, ourType, directory, codec) {
@Override
public String getName() {
return name;
}
@Override
protected void configure(BiConsumer<ResourceLocation, T> provider) {
output.accept(provider);
}
};
});
}
@Override
public void lootTable(List<LootTableProvider.SubProviderEntry> tables) {
for (var table : tables) {

View File

@ -256,12 +256,6 @@ public int getBurnTime(ItemStack stack) {
return fuel == null ? 0 : fuel;
}
@Nullable
@Override
public ResourceLocation getCreativeTabId(CreativeModeTab tab) {
return tab.getId();
}
@Override
public ItemStack getCraftingRemainingItem(ItemStack stack) {
return stack.getRecipeRemainder();

View File

@ -104,6 +104,14 @@
"gui.computercraft.config.http.max_requests.tooltip": "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited.\nRange: > 0",
"gui.computercraft.config.http.max_websockets": "Maximum concurrent websockets",
"gui.computercraft.config.http.max_websockets.tooltip": "The number of websockets a computer can have open at one time. Set to 0 for unlimited.\nRange: > 1",
"gui.computercraft.config.http.proxy": "Proxy",
"gui.computercraft.config.http.proxy.host": "Host name",
"gui.computercraft.config.http.proxy.host.tooltip": "The hostname or IP address of the proxy server.",
"gui.computercraft.config.http.proxy.port": "Port",
"gui.computercraft.config.http.proxy.port.tooltip": "The port of the proxy server.\nRange: 1 ~ 65536",
"gui.computercraft.config.http.proxy.tooltip": "Tunnels HTTP and websocket requests through a proxy server. Only affects HTTP\nrules with \"use_proxy\" set to true (off by default).\nIf authentication is required for the proxy, create a \"computercraft-proxy.pw\"\nfile in the same directory as \"computercraft-server.toml\", containing the\nusername and password separated by a colon, e.g. \"myuser:mypassword\". For\nSOCKS4 proxies only the username is required.",
"gui.computercraft.config.http.proxy.type": "Proxy type",
"gui.computercraft.config.http.proxy.type.tooltip": "The type of proxy to use.\nAllowed Values: HTTP, HTTPS, SOCKS4, SOCKS5",
"gui.computercraft.config.http.rules": "Allow/deny rules",
"gui.computercraft.config.http.rules.tooltip": "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule is an item with a 'host' to match against, and a series of\nproperties. Rules are evaluated in order, meaning earlier rules override later\nones.\nThe host may be a domain name (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or\nCIDR notation (\"127.0.0.0/8\").\nIf no rules, the domain is blocked.",
"gui.computercraft.config.http.tooltip": "Controls the HTTP API",

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -0,0 +1,6 @@
{
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
]
}

View File

@ -4,6 +4,8 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.HolderLookup;
@ -14,18 +16,24 @@
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.ItemTagsProvider;
import net.minecraft.data.tags.TagsProvider;
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 net.minecraftforge.common.data.BlockTagsProvider;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.common.data.JsonCodecProvider;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
@ -46,6 +54,15 @@ public <T extends DataProvider> T add(DataProvider.Factory<T> factory) {
return generator.addProvider(factory);
}
@Override
public <T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output) {
generator.addProvider(out -> {
Map<ResourceLocation, T> map = new HashMap<>();
output.accept(map::put);
return new JsonCodecProvider<>(out, existingFiles, ComputerCraftAPI.MOD_ID, JsonOps.INSTANCE, type, directory, codec, map);
});
}
@Override
public void lootTable(List<LootTableProvider.SubProviderEntry> tables) {
add(out -> new LootTableProvider(out, Set.of(), tables));

View File

@ -96,13 +96,18 @@ public static int size(IItemHandler inventory) {
* <p>
* The returned information contains the same information as each item in
* {@link #list}, as well as additional details like the display name
* (`displayName`), item groups (`itemGroups`), which are the creative tabs
* an item will appear under, and item and item durability (`damage`,
* `maxDamage`, `durability`).
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
* <p>
* Some items include more information (such as enchantments) - it is
* recommended to print it out using @{textutils.serialize} or in the Lua
* REPL, to explore what is available.
* <p>
* :::info Deprecated fields
* Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
* creative tabs an item was available under. This information is no longer available on
* more recent versions of the game, and so this field will always be empty. Do not use this
* field in new code!
* :::
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
@ -119,10 +124,6 @@ public static int size(IItemHandler inventory) {
* print(("%s (%s)"):format(item.displayName, item.name))
* print(("Count: %d/%d"):format(item.count, item.maxCount))
*
* for _, group in pairs(item.itemGroups) do
* print(("Group: %s"):format(group.displayName))
* end
*
* if item.damage then
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
* end

View File

@ -38,7 +38,6 @@
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
@ -52,7 +51,6 @@
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.CreativeModeTabRegistry;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.Tags;
import net.minecraftforge.common.ToolActions;
@ -258,12 +256,6 @@ public int getBurnTime(ItemStack stack) {
return ForgeHooks.getBurnTime(stack, null);
}
@Nullable
@Override
public ResourceLocation getCreativeTabId(CreativeModeTab tab) {
return CreativeModeTabRegistry.getName(tab);
}
@Override
public ItemStack getCraftingRemainingItem(ItemStack stack) {
return stack.getCraftingRemainingItem();