Merge pull request #1485 from cc-tweaked/feature/turtle-upgrade-ui

Allow changing turtle upgrades from the GUI
This commit is contained in:
Jonathan Coates 2023-06-18 08:09:47 +01:00 committed by GitHub
commit 36b9f4ec55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 286 additions and 29 deletions

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

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

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

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

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

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

@ -119,7 +119,7 @@ loom {
register("data") {
configName = "Datagen"
server()
client()
runDir("run/dataGen")
property("cct.pretty-json")

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

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