Merge branch 'mc1.18/dev' into mc1.18/0.5.1

This commit is contained in:
simibubi 2023-05-12 15:13:45 +02:00
commit ab221b9d5b
141 changed files with 4206 additions and 292 deletions

23
.github/workflows/gametest.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: gametest
on: [ pull_request, push, workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v3
- name: setup Java
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
cache: gradle
- name: make gradle wrapper executable
run: chmod +x ./gradlew
- name: run gametests
run: ./gradlew prepareRunGameTestServer runGameTestServer --no-daemon

View file

@ -90,6 +90,18 @@ minecraft {
} }
} }
} }
gameTestServer {
workingDirectory project.file('run/gametest')
arg '-mixin.config=create.mixins.json'
property 'forge.logging.console.level', 'info'
mods {
create {
source sourceSets.main
}
}
setForceExit false
}
} }
} }
@ -138,6 +150,14 @@ repositories {
flatDir { flatDir {
dirs 'libs' dirs 'libs'
} }
maven {
// Location of maven for CC: Tweaked
name = "squiddev"
url = "https://squiddev.cc/maven/"
content {
includeGroup "org.squiddev"
}
}
} }
dependencies { dependencies {
@ -165,6 +185,11 @@ dependencies {
compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_minecraft_version}-${curios_version}:api") compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_minecraft_version}-${curios_version}:api")
runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_minecraft_version}-${curios_version}") runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:${curios_minecraft_version}-${curios_version}")
if (cc_tweaked_enable.toBoolean()) {
compileOnly fg.deobf("org.squiddev:cc-tweaked-${cc_tweaked_minecraft_version}:${cc_tweaked_version}:api")
runtimeOnly fg.deobf("org.squiddev:cc-tweaked-${cc_tweaked_minecraft_version}:${cc_tweaked_version}")
}
// implementation fg.deobf("curse.maven:druidcraft-340991:3101903") // implementation fg.deobf("curse.maven:druidcraft-340991:3101903")
// implementation fg.deobf("com.ferreusveritas.dynamictrees:DynamicTrees-1.16.5:0.10.0-Beta25") // implementation fg.deobf("com.ferreusveritas.dynamictrees:DynamicTrees-1.16.5:0.10.0-Beta25")
// runtimeOnly fg.deobf("vazkii.arl:AutoRegLib:1.4-35.69") // runtimeOnly fg.deobf("vazkii.arl:AutoRegLib:1.4-35.69")
@ -182,6 +207,12 @@ dependencies {
} }
} }
sourceSets.main.java {
if (!cc_tweaked_enable.toBoolean()) {
exclude 'com/simibubi/create/compat/computercraft/implementation/**'
}
}
sourceSets.main.resources { sourceSets.main.resources {
srcDir 'src/generated/resources' srcDir 'src/generated/resources'
exclude '.cache/' exclude '.cache/'

View file

@ -27,6 +27,10 @@ jei_version = 9.7.0.209
curios_minecraft_version = 1.18.2 curios_minecraft_version = 1.18.2
curios_version = 5.0.7.0 curios_version = 5.0.7.0
cc_tweaked_enable = true
cc_tweaked_minecraft_version = 1.18.2
cc_tweaked_version = 1.100.10
# curseforge information # curseforge information
projectId = 328085 projectId = 328085
curse_type = beta curse_type = beta

View file

@ -582,7 +582,7 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo
7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json 7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json
b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json
fcaad84ac4ebdb1e6d9301b77245ce855dbde503 assets/create/lang/en_ud.json fcaad84ac4ebdb1e6d9301b77245ce855dbde503 assets/create/lang/en_ud.json
a0e7d027d022330c5358d75165a76383d86ba122 assets/create/lang/en_us.json 5bca3a0eafdf9c29f5d6b692fd395741e4a815e6 assets/create/lang/en_us.json
487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json 487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json
b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json
3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json 3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json

View file

@ -1188,6 +1188,8 @@
"create.schematicAndQuill.convert": "Save and Upload Immediately", "create.schematicAndQuill.convert": "Save and Upload Immediately",
"create.schematicAndQuill.fallbackName": "My Schematic", "create.schematicAndQuill.fallbackName": "My Schematic",
"create.schematicAndQuill.saved": "Saved as %1$s", "create.schematicAndQuill.saved": "Saved as %1$s",
"create.schematicAndQuill.failed": "Failed to save schematic, check logs for details",
"create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details",
"create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead", "create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead",
"create.schematic.error": "Schematic failed to Load - Check Game Logs", "create.schematic.error": "Schematic failed to Load - Check Game Logs",

View file

@ -11,6 +11,7 @@ import com.google.gson.GsonBuilder;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour;
import com.simibubi.create.compat.Mods; import com.simibubi.create.compat.Mods;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.compat.curios.Curios; import com.simibubi.create.compat.curios.Curios;
import com.simibubi.create.content.contraptions.TorquePropagator; import com.simibubi.create.content.contraptions.TorquePropagator;
import com.simibubi.create.content.contraptions.fluids.tank.BoilerHeaters; import com.simibubi.create.content.contraptions.fluids.tank.BoilerHeaters;
@ -140,6 +141,7 @@ public class Create {
ContraptionMovementSetting.registerDefaults(); ContraptionMovementSetting.registerDefaults();
AllArmInteractionPointTypes.register(); AllArmInteractionPointTypes.register();
BlockSpoutingBehaviour.registerDefaults(); BlockSpoutingBehaviour.registerDefaults();
ComputerCraftProxy.register();
ForgeMod.enableMilkFluid(); ForgeMod.enableMilkFluid();
CopperRegistries.inject(); CopperRegistries.inject();

View file

@ -0,0 +1,22 @@
package com.simibubi.create.api.event;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import net.minecraftforge.eventbus.api.Event;
public class TrackGraphMergeEvent extends Event{
private TrackGraph mergedInto, mergedFrom;
public TrackGraphMergeEvent(TrackGraph from, TrackGraph into) {
mergedInto = into;
mergedFrom = from;
}
public TrackGraph getGraphMergedInto() {
return mergedInto;
}
public TrackGraph getGraphMergedFrom() {
return mergedFrom;
}
}

View file

@ -5,7 +5,10 @@ import java.util.function.Supplier;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import net.minecraftforge.registries.ForgeRegistries;
/** /**
* For compatibility with and without another mod present, we have to define load conditions of the specific code * For compatibility with and without another mod present, we have to define load conditions of the specific code
@ -14,6 +17,8 @@ public enum Mods {
DYNAMICTREES, DYNAMICTREES,
TCONSTRUCT, TCONSTRUCT,
CURIOS, CURIOS,
COMPUTERCRAFT,
STORAGEDRAWERS, STORAGEDRAWERS,
XLPACKETS; XLPACKETS;
@ -51,4 +56,8 @@ public enum Mods {
toExecute.get().run(); toExecute.get().run();
} }
} }
public Block getBlock(String id) {
return ForgeRegistries.BLOCKS.getValue(new ResourceLocation(asId(), id));
}
} }

View file

@ -0,0 +1,57 @@
package com.simibubi.create.compat.computercraft;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import net.minecraft.nbt.CompoundTag;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
public class AbstractComputerBehaviour extends TileEntityBehaviour {
public static final BehaviourType<AbstractComputerBehaviour> TYPE = new BehaviourType<>();
boolean hasAttachedComputer;
public AbstractComputerBehaviour(SmartTileEntity te) {
super(te);
this.hasAttachedComputer = false;
}
@Override
public void read(CompoundTag nbt, boolean clientPacket) {
hasAttachedComputer = nbt.getBoolean("HasAttachedComputer");
super.read(nbt, clientPacket);
}
@Override
public void write(CompoundTag nbt, boolean clientPacket) {
nbt.putBoolean("HasAttachedComputer", hasAttachedComputer);
super.write(nbt, clientPacket);
}
public <T> boolean isPeripheralCap(Capability<T> cap) {
return false;
}
public <T> LazyOptional<T> getPeripheralCapability() {
return LazyOptional.empty();
}
public void removePeripheral() {}
public void setHasAttachedComputer(boolean hasAttachedComputer) {
this.hasAttachedComputer = hasAttachedComputer;
}
public boolean hasAttachedComputer() {
return hasAttachedComputer;
}
@Override
public BehaviourType<?> getType() {
return TYPE;
}
}

View file

@ -0,0 +1,37 @@
package com.simibubi.create.compat.computercraft;
import com.simibubi.create.foundation.networking.TileEntityDataPacket;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.SyncedTileEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
public class AttachedComputerPacket extends TileEntityDataPacket<SyncedTileEntity> {
private final boolean hasAttachedComputer;
public AttachedComputerPacket(BlockPos tilePos, boolean hasAttachedComputer) {
super(tilePos);
this.hasAttachedComputer = hasAttachedComputer;
}
public AttachedComputerPacket(FriendlyByteBuf buffer) {
super(buffer);
this.hasAttachedComputer = buffer.readBoolean();
}
@Override
protected void writeData(FriendlyByteBuf buffer) {
buffer.writeBoolean(hasAttachedComputer);
}
@Override
protected void handlePacket(SyncedTileEntity tile) {
if (tile instanceof SmartTileEntity smartTile) {
smartTile.getBehaviour(AbstractComputerBehaviour.TYPE)
.setHasAttachedComputer(hasAttachedComputer);
}
}
}

View file

@ -0,0 +1,30 @@
package com.simibubi.create.compat.computercraft;
import java.util.function.Function;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.compat.computercraft.implementation.ComputerBehaviour;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
public class ComputerCraftProxy {
public static void register() {
fallbackFactory = FallbackComputerBehaviour::new;
Mods.COMPUTERCRAFT.executeIfInstalled(() -> ComputerCraftProxy::registerWithDependency);
}
private static void registerWithDependency() {
/* Comment if computercraft.implementation is not in the source set */
computerFactory = ComputerBehaviour::new;
}
private static Function<SmartTileEntity, ? extends AbstractComputerBehaviour> fallbackFactory;
private static Function<SmartTileEntity, ? extends AbstractComputerBehaviour> computerFactory;
public static AbstractComputerBehaviour behaviour(SmartTileEntity ste) {
if (computerFactory == null)
return fallbackFactory.apply(ste);
return computerFactory.apply(ste);
}
}

View file

@ -0,0 +1,96 @@
package com.simibubi.create.compat.computercraft;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.foundation.gui.AbstractSimiScreen;
import com.simibubi.create.foundation.gui.AllGuiTextures;
import com.simibubi.create.foundation.gui.AllIcons;
import com.simibubi.create.foundation.gui.element.GuiGameElement;
import com.simibubi.create.foundation.gui.widget.AbstractSimiWidget;
import com.simibubi.create.foundation.gui.widget.ElementWidget;
import com.simibubi.create.foundation.gui.widget.IconButton;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
public class ComputerScreen extends AbstractSimiScreen {
private final AllGuiTextures background = AllGuiTextures.COMPUTER;
private final Supplier<Component> displayTitle;
private final RenderWindowFunction additional;
private final Screen previousScreen;
private final Supplier<Boolean> hasAttachedComputer;
private AbstractSimiWidget computerWidget;
private IconButton confirmButton;
public ComputerScreen(Component title, @Nullable RenderWindowFunction additional, Screen previousScreen, Supplier<Boolean> hasAttachedComputer) {
this(title, () -> title, additional, previousScreen, hasAttachedComputer);
}
public ComputerScreen(Component title, Supplier<Component> displayTitle, @Nullable RenderWindowFunction additional, Screen previousScreen, Supplier<Boolean> hasAttachedComputer) {
super(title);
this.displayTitle = displayTitle;
this.additional = additional;
this.previousScreen = previousScreen;
this.hasAttachedComputer = hasAttachedComputer;
}
@Override
public void tick() {
if (!hasAttachedComputer.get())
minecraft.setScreen(previousScreen);
super.tick();
}
@Override
protected void init() {
setWindowSize(background.width, background.height);
super.init();
int x = guiLeft;
int y = guiTop;
Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> {
computerWidget = new ElementWidget(x + 33, y + 38)
.showingElement(GuiGameElement.of(Mods.COMPUTERCRAFT.getBlock("computer_advanced")));
computerWidget.getToolTip().add(Lang.translate("gui.attached_computer.hint").component());
addRenderableWidget(computerWidget);
});
confirmButton = new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM);
confirmButton.withCallback(this::onClose);
addRenderableWidget(confirmButton);
}
@Override
protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) {
int x = guiLeft;
int y = guiTop;
background.render(ms, x, y, this);
font.draw(ms, displayTitle.get(), x + background.width / 2.0F - font.width(displayTitle.get()) / 2.0F, y + 4, 0x442000);
font.drawWordWrap(Lang.translate("gui.attached_computer.controlled").component(), x + 55, y + 32, 111, 0x7A7A7A);
if (additional != null)
additional.render(ms, mouseX, mouseY, partialTicks, x, y, background);
}
@FunctionalInterface
public interface RenderWindowFunction {
void render(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop, AllGuiTextures background);
}
}

View file

@ -0,0 +1,16 @@
package com.simibubi.create.compat.computercraft;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
public class FallbackComputerBehaviour extends AbstractComputerBehaviour {
public FallbackComputerBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean hasAttachedComputer() {
return false;
}
}

View file

@ -0,0 +1,74 @@
package com.simibubi.create.compat.computercraft.implementation;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.implementation.peripherals.DisplayLinkPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.SequencedGearshiftPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedControllerPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.SpeedGaugePeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.StationPeripheral;
import com.simibubi.create.compat.computercraft.implementation.peripherals.StressGaugePeripheral;
import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity;
import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencedGearshiftTileEntity;
import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity;
import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity;
import com.simibubi.create.content.logistics.block.display.DisplayLinkTileEntity;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationTileEntity;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullSupplier;
public class ComputerBehaviour extends AbstractComputerBehaviour {
protected static final Capability<IPeripheral> PERIPHERAL_CAPABILITY =
CapabilityManager.get(new CapabilityToken<>() {
});
LazyOptional<IPeripheral> peripheral;
NonNullSupplier<IPeripheral> peripheralSupplier;
public ComputerBehaviour(SmartTileEntity te) {
super(te);
this.peripheralSupplier = getPeripheralFor(te);
}
public static NonNullSupplier<IPeripheral> getPeripheralFor(SmartTileEntity te) {
if (te instanceof SpeedControllerTileEntity scte)
return () -> new SpeedControllerPeripheral(scte, scte.targetSpeed);
if (te instanceof DisplayLinkTileEntity dlte)
return () -> new DisplayLinkPeripheral(dlte);
if (te instanceof SequencedGearshiftTileEntity sgte)
return () -> new SequencedGearshiftPeripheral(sgte);
if (te instanceof SpeedGaugeTileEntity sgte)
return () -> new SpeedGaugePeripheral(sgte);
if (te instanceof StressGaugeTileEntity sgte)
return () -> new StressGaugePeripheral(sgte);
if (te instanceof StationTileEntity ste)
return () -> new StationPeripheral(ste);
throw new IllegalArgumentException("No peripheral available for " + te.getType()
.getRegistryName());
}
@Override
public <T> boolean isPeripheralCap(Capability<T> cap) {
return cap == PERIPHERAL_CAPABILITY;
}
@Override
public <T> LazyOptional<T> getPeripheralCapability() {
if (peripheral == null || !peripheral.isPresent())
peripheral = LazyOptional.of(peripheralSupplier);
return peripheral.cast();
}
@Override
public void removePeripheral() {
if (peripheral != null)
peripheral.invalidate();
}
}

View file

@ -0,0 +1,172 @@
package com.simibubi.create.compat.computercraft.implementation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaTable;
import dan200.computercraft.api.lua.LuaValues;
public class CreateLuaTable implements LuaTable<Object, Object> {
private final Map<Object, Object> map;
public CreateLuaTable() {
this.map = new HashMap<>();
}
public CreateLuaTable(Map<?, ?> map) {
this.map = new HashMap<>(map);
}
public boolean getBoolean(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof Boolean))
throw LuaValues.badField(key, "boolean", LuaValues.getType(value));
return (Boolean) value;
}
public String getString(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof String))
throw LuaValues.badField(key, "string", LuaValues.getType(value));
return (String) value;
}
public CreateLuaTable getTable(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof Map<?, ?>))
throw LuaValues.badField(key, "table", LuaValues.getType(value));
return new CreateLuaTable((Map<?, ?>) value);
}
public Optional<Boolean> getOptBoolean(String key) throws LuaException {
Object value = get(key);
if (value == null)
return Optional.empty();
if (!(value instanceof Boolean))
throw LuaValues.badField(key, "boolean", LuaValues.getType(value));
return Optional.of((Boolean) value);
}
public Set<String> stringKeySet() throws LuaException {
Set<String> stringSet = new HashSet<>();
for (Object key : keySet()) {
if (!(key instanceof String))
throw new LuaException("key " + key + " is not string (got " + LuaValues.getType(key) + ")");
stringSet.add((String) key);
}
return Collections.unmodifiableSet(stringSet);
}
public Collection<CreateLuaTable> tableValues() throws LuaException {
List<CreateLuaTable> tables = new ArrayList<>();
for (int i = 1; i <= size(); i++) {
Object value = get((double) i);
if (!(value instanceof Map<?, ?>))
throw new LuaException("value " + value + " is not table (got " + LuaValues.getType(value) + ")");
tables.add(new CreateLuaTable((Map<?, ?>) value));
}
return Collections.unmodifiableList(tables);
}
public Map<Object, Object> getMap() {
return map;
}
@Nullable
@Override
public Object put(Object key, Object value) {
return map.put(key, value);
}
public void putBoolean(String key, boolean value) {
map.put(key, value);
}
public void putDouble(String key, double value) {
map.put(key, value);
}
public void putString(String key, String value) {
map.put(key, value);
}
public void putTable(String key, CreateLuaTable value) {
map.put(key, value);
}
public void putTable(int i, CreateLuaTable value) {
map.put(i, value);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return map.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return map.containsValue(o);
}
@Override
public Object get(Object o) {
return map.get(o);
}
@NotNull
@Override
public Set<Object> keySet() {
return map.keySet();
}
@NotNull
@Override
public Collection<Object> values() {
return map.values();
}
@NotNull
@Override
public Set<Entry<Object, Object>> entrySet() {
return map.entrySet();
}
}

View file

@ -0,0 +1,108 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.content.logistics.block.display.DisplayLinkContext;
import com.simibubi.create.content.logistics.block.display.DisplayLinkTileEntity;
import com.simibubi.create.content.logistics.block.display.target.DisplayTargetStats;
import dan200.computercraft.api.lua.LuaFunction;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
public class DisplayLinkPeripheral extends SyncedPeripheral<DisplayLinkTileEntity> {
public static final String TAG_KEY = "ComputerSourceList";
private final AtomicInteger cursorX = new AtomicInteger();
private final AtomicInteger cursorY = new AtomicInteger();
public DisplayLinkPeripheral(DisplayLinkTileEntity tile) {
super(tile);
}
@LuaFunction
public final void setCursorPos(int x, int y) {
cursorX.set(x - 1);
cursorY.set(y - 1);
}
@LuaFunction
public final Object[] getCursorPos() {
return new Object[] {cursorX.get() + 1, cursorY.get() + 1};
}
@LuaFunction(mainThread = true)
public final Object[] getSize() {
DisplayTargetStats stats = tile.activeTarget.provideStats(new DisplayLinkContext(tile.getLevel(), tile));
return new Object[]{stats.maxRows(), stats.maxColumns()};
}
@LuaFunction
public final boolean isColor() {
return false;
}
@LuaFunction
public final boolean isColour() {
return false;
}
@LuaFunction
public final void write(String text) {
ListTag tag = tile.getSourceConfig().getList(TAG_KEY, Tag.TAG_STRING);
int x = cursorX.get();
int y = cursorY.get();
for (int i = tag.size(); i <= y; i++) {
tag.add(StringTag.valueOf(""));
}
StringBuilder builder = new StringBuilder(tag.getString(y));
builder.append(" ".repeat(Math.max(0, x - builder.length())));
builder.replace(x, x + text.length(), text);
tag.set(y, StringTag.valueOf(builder.toString()));
synchronized (tile) {
tile.getSourceConfig().put(TAG_KEY, tag);
}
cursorX.set(x + text.length());
}
@LuaFunction
public final void clearLine() {
ListTag tag = tile.getSourceConfig().getList(TAG_KEY, Tag.TAG_STRING);
if (tag.size() > cursorY.get())
tag.set(cursorY.get(), StringTag.valueOf(""));
synchronized (tile) {
tile.getSourceConfig().put(TAG_KEY, tag);
}
}
@LuaFunction
public final void clear() {
synchronized (tile) {
tile.getSourceConfig().put(TAG_KEY, new ListTag());
}
}
@LuaFunction(mainThread = true)
public final void update() {
tile.tickSource();
}
@NotNull
@Override
public String getType() {
return "Create_DisplayLink";
}
}

View file

@ -0,0 +1,54 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.content.contraptions.relays.advanced.sequencer.Instruction;
import com.simibubi.create.content.contraptions.relays.advanced.sequencer.InstructionSpeedModifiers;
import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencedGearshiftTileEntity;
import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencerInstructions;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
public class SequencedGearshiftPeripheral extends SyncedPeripheral<SequencedGearshiftTileEntity> {
public SequencedGearshiftPeripheral(SequencedGearshiftTileEntity tile) {
super(tile);
}
@LuaFunction(mainThread = true)
public final void rotate(IArguments arguments) throws LuaException {
runInstruction(arguments, SequencerInstructions.TURN_ANGLE);
}
@LuaFunction(mainThread = true)
public final void move(IArguments arguments) throws LuaException {
runInstruction(arguments, SequencerInstructions.TURN_DISTANCE);
}
@LuaFunction
public final boolean isRunning() {
return !this.tile.isIdle();
}
private void runInstruction(IArguments arguments, SequencerInstructions instructionType) throws LuaException {
int speedModifier = arguments.count() > 1 ? arguments.getInt(1) : 1;
this.tile.getInstructions().clear();
this.tile.getInstructions().add(new Instruction(
instructionType,
InstructionSpeedModifiers.getByModifier(speedModifier),
Math.abs(arguments.getInt(0))));
this.tile.getInstructions().add(new Instruction(SequencerInstructions.END));
this.tile.run(0);
}
@NotNull
@Override
public String getType() {
return "Create_SequencedGearshift";
}
}

View file

@ -0,0 +1,35 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity;
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueBehaviour;
import dan200.computercraft.api.lua.LuaFunction;
public class SpeedControllerPeripheral extends SyncedPeripheral<SpeedControllerTileEntity> {
private final ScrollValueBehaviour targetSpeed;
public SpeedControllerPeripheral(SpeedControllerTileEntity tile, ScrollValueBehaviour targetSpeed) {
super(tile);
this.targetSpeed = targetSpeed;
}
@LuaFunction(mainThread = true)
public final void setTargetSpeed(int speed) {
this.targetSpeed.setValue(speed);
}
@LuaFunction
public final float getTargetSpeed() {
return this.targetSpeed.getValue();
}
@NotNull
@Override
public String getType() {
return "Create_RotationSpeedController";
}
}

View file

@ -0,0 +1,26 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity;
import dan200.computercraft.api.lua.LuaFunction;
public class SpeedGaugePeripheral extends SyncedPeripheral<SpeedGaugeTileEntity> {
public SpeedGaugePeripheral(SpeedGaugeTileEntity tile) {
super(tile);
}
@LuaFunction
public final float getSpeed() {
return this.tile.getSpeed();
}
@NotNull
@Override
public String getType() {
return "Create_Speedometer";
}
}

View file

@ -0,0 +1,269 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import java.util.Map;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.compat.computercraft.implementation.CreateLuaTable;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.GlobalStation;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationTileEntity;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket;
import com.simibubi.create.content.logistics.trains.management.schedule.Schedule;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.StringHelper;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import net.minecraft.nbt.ByteTag;
import net.minecraft.nbt.CollectionTag;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.DoubleTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraftforge.network.PacketDistributor;
public class StationPeripheral extends SyncedPeripheral<StationTileEntity> {
public StationPeripheral(StationTileEntity tile) {
super(tile);
}
@LuaFunction(mainThread = true)
public final void assemble() throws LuaException {
if (!tile.isAssembling())
throw new LuaException("station must be in assembly mode");
tile.assemble(null);
if (tile.getStation() == null || tile.getStation().getPresentTrain() == null)
throw new LuaException("failed to assemble train");
if (!tile.exitAssemblyMode())
throw new LuaException("failed to exit assembly mode");
}
@LuaFunction(mainThread = true)
public final void disassemble() throws LuaException {
if (tile.isAssembling())
throw new LuaException("station must not be in assembly mode");
getTrainOrThrow();
if (!tile.enterAssemblyMode(null))
throw new LuaException("could not disassemble train");
}
@LuaFunction(mainThread = true)
public final void setAssemblyMode(boolean assemblyMode) throws LuaException {
if (assemblyMode) {
if (!tile.enterAssemblyMode(null))
throw new LuaException("failed to enter assembly mode");
} else {
if (!tile.exitAssemblyMode())
throw new LuaException("failed to exit assembly mode");
}
}
@LuaFunction
public final boolean isInAssemblyMode() {
return tile.isAssembling();
}
@LuaFunction
public final String getStationName() throws LuaException {
GlobalStation station = tile.getStation();
if (station == null)
throw new LuaException("station is not connected to a track");
return station.name;
}
@LuaFunction(mainThread = true)
public final void setStationName(String name) throws LuaException {
if (!tile.updateName(name))
throw new LuaException("could not set station name");
}
@LuaFunction
public final boolean isTrainPresent() throws LuaException {
GlobalStation station = tile.getStation();
if (station == null)
throw new LuaException("station is not connected to a track");
return station.getPresentTrain() != null;
}
@LuaFunction
public final boolean isTrainImminent() throws LuaException {
GlobalStation station = tile.getStation();
if (station == null)
throw new LuaException("station is not connected to a track");
return station.getImminentTrain() != null;
}
@LuaFunction
public final boolean isTrainEnroute() throws LuaException {
GlobalStation station = tile.getStation();
if (station == null)
throw new LuaException("station is not connected to a track");
return station.getNearestTrain() != null;
}
@LuaFunction
public final String getTrainName() throws LuaException {
Train train = getTrainOrThrow();
return train.name.getString();
}
@LuaFunction(mainThread = true)
public final void setTrainName(String name) throws LuaException {
Train train = getTrainOrThrow();
train.name = Components.literal(name);
AllPackets.channel.send(PacketDistributor.ALL.noArg(), new TrainEditPacket.TrainEditReturnPacket(train.id, name, train.icon.getId()));
}
@LuaFunction
public final boolean hasSchedule() throws LuaException {
Train train = getTrainOrThrow();
return train.runtime.getSchedule() != null;
}
@LuaFunction
public final CreateLuaTable getSchedule() throws LuaException {
Train train = getTrainOrThrow();
Schedule schedule = train.runtime.getSchedule();
if (schedule == null)
throw new LuaException("train doesn't have a schedule");
return fromCompoundTag(schedule.write());
}
@LuaFunction(mainThread = true)
public final void setSchedule(IArguments arguments) throws LuaException {
Train train = getTrainOrThrow();
Schedule schedule = Schedule.fromTag(toCompoundTag(new CreateLuaTable(arguments.getTable(0))));
boolean autoSchedule = train.runtime.getSchedule() == null || train.runtime.isAutoSchedule;
train.runtime.setSchedule(schedule, autoSchedule);
}
private @NotNull Train getTrainOrThrow() throws LuaException {
GlobalStation station = tile.getStation();
if (station == null)
throw new LuaException("station is not connected to a track");
Train train = station.getPresentTrain();
if (train == null)
throw new LuaException("there is no train present");
return train;
}
private static @NotNull CreateLuaTable fromCompoundTag(CompoundTag tag) throws LuaException {
return (CreateLuaTable) fromNBTTag(null, tag);
}
private static @NotNull Object fromNBTTag(@Nullable String key, Tag tag) throws LuaException {
byte type = tag.getId();
if (type == Tag.TAG_BYTE && key != null && key.equals("Count"))
return ((NumericTag) tag).getAsByte();
else if (type == Tag.TAG_BYTE)
return ((NumericTag) tag).getAsByte() != 0;
else if (type == Tag.TAG_SHORT || type == Tag.TAG_INT || type == Tag.TAG_LONG)
return ((NumericTag) tag).getAsLong();
else if (type == Tag.TAG_FLOAT || type == Tag.TAG_DOUBLE)
return ((NumericTag) tag).getAsDouble();
else if (type == Tag.TAG_STRING)
return tag.getAsString();
else if (type == Tag.TAG_LIST || type == Tag.TAG_BYTE_ARRAY || type == Tag.TAG_INT_ARRAY || type == Tag.TAG_LONG_ARRAY) {
CreateLuaTable list = new CreateLuaTable();
CollectionTag<?> listTag = (CollectionTag<?>) tag;
for (int i = 0; i < listTag.size(); i++) {
list.put(i + 1, fromNBTTag(null, listTag.get(i)));
}
return list;
} else if (type == Tag.TAG_COMPOUND) {
CreateLuaTable table = new CreateLuaTable();
CompoundTag compoundTag = (CompoundTag) tag;
for (String compoundKey : compoundTag.getAllKeys()) {
table.put(
StringHelper.camelCaseToSnakeCase(compoundKey),
fromNBTTag(compoundKey, compoundTag.get(compoundKey))
);
}
return table;
}
throw new LuaException("unknown tag type " + tag.getType().getName());
}
private static @NotNull CompoundTag toCompoundTag(CreateLuaTable table) throws LuaException {
return (CompoundTag) toNBTTag(null, table.getMap());
}
private static @NotNull Tag toNBTTag(@Nullable String key, Object value) throws LuaException {
if (value instanceof Boolean v)
return ByteTag.valueOf(v);
else if (value instanceof Byte || (key != null && key.equals("count")))
return ByteTag.valueOf(((Number) value).byteValue());
else if (value instanceof Number v) {
// If number is numerical integer
if (v.intValue() == v.doubleValue())
return IntTag.valueOf(v.intValue());
else
return DoubleTag.valueOf(v.doubleValue());
} else if (value instanceof String v)
return StringTag.valueOf(v);
else if (value instanceof Map<?, ?> v && v.containsKey(1.0)) { // List
ListTag list = new ListTag();
for (Object o : v.values()) {
list.add(toNBTTag(null, o));
}
return list;
} else if (value instanceof Map<?, ?> v) { // Table/Map
CompoundTag compound = new CompoundTag();
for (Object objectKey : v.keySet()) {
if (!(objectKey instanceof String compoundKey))
throw new LuaException("table key is not of type string");
compound.put(
// Items serialize their resource location as "id" and not as "Id".
// This check is needed to see if the 'i' should be left lowercase or not.
// Items store "count" in the same compound tag, so we can check for its presence to see if this is a serialized item
compoundKey.equals("id") && v.containsKey("count") ? "id" : StringHelper.snakeCaseToCamelCase(compoundKey),
toNBTTag(compoundKey, v.get(compoundKey))
);
}
return compound;
}
throw new LuaException("unknown object type " + value.getClass().getName());
}
@NotNull
@Override
public String getType() {
return "Create_Station";
}
}

View file

@ -0,0 +1,31 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity;
import dan200.computercraft.api.lua.LuaFunction;
public class StressGaugePeripheral extends SyncedPeripheral<StressGaugeTileEntity> {
public StressGaugePeripheral(StressGaugeTileEntity tile) {
super(tile);
}
@LuaFunction
public final float getStress() {
return this.tile.getNetworkStress();
}
@LuaFunction
public final float getStressCapacity() {
return this.tile.getNetworkCapacity();
}
@NotNull
@Override
public String getType() {
return "Create_Stressometer";
}
}

View file

@ -0,0 +1,50 @@
package com.simibubi.create.compat.computercraft.implementation.peripherals;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.compat.computercraft.AttachedComputerPacket;
import com.simibubi.create.compat.computercraft.implementation.ComputerBehaviour;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraftforge.network.PacketDistributor;
public abstract class SyncedPeripheral<T extends SmartTileEntity> implements IPeripheral {
protected final T tile;
private final AtomicInteger computers = new AtomicInteger();
public SyncedPeripheral(T tile) {
this.tile = tile;
}
@Override
public void attach(@NotNull IComputerAccess computer) {
computers.incrementAndGet();
updateTile();
}
@Override
public void detach(@NotNull IComputerAccess computer) {
computers.decrementAndGet();
updateTile();
}
private void updateTile() {
boolean hasAttachedComputer = computers.get() > 0;
tile.getBehaviour(ComputerBehaviour.TYPE).setHasAttachedComputer(hasAttachedComputer);
AllPackets.channel.send(PacketDistributor.ALL.noArg(), new AttachedComputerPacket(tile.getBlockPos(), hasAttachedComputer));
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return this == other;
}
}

View file

@ -202,6 +202,7 @@ public class GantryContraptionEntity extends AbstractContraptionEntity {
} }
@Override @Override
@OnlyIn(Dist.CLIENT)
public void applyLocalTransforms(PoseStack matrixStack, float partialTicks) {} public void applyLocalTransforms(PoseStack matrixStack, float partialTicks) {}
public void updateClientMotion() { public void updateClientMotion() {

View file

@ -25,6 +25,7 @@ import net.minecraft.tags.BlockTags;
import net.minecraft.tags.FluidTags; import net.minecraft.tags.FluidTags;
import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -39,6 +40,7 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraftforge.common.ForgeConfig;
import net.minecraftforge.common.Tags; import net.minecraftforge.common.Tags;
import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
@ -54,6 +56,7 @@ public class OpenEndedPipe extends FlowSource {
registerEffectHandler(new MilkEffectHandler()); registerEffectHandler(new MilkEffectHandler());
registerEffectHandler(new WaterEffectHandler()); registerEffectHandler(new WaterEffectHandler());
registerEffectHandler(new LavaEffectHandler()); registerEffectHandler(new LavaEffectHandler());
registerEffectHandler(new TeaEffectHandler());
} }
private Level world; private Level world;
@ -443,4 +446,23 @@ public class OpenEndedPipe extends FlowSource {
} }
} }
public static class TeaEffectHandler implements IEffectHandler {
@Override
public boolean canApplyEffects(OpenEndedPipe pipe, FluidStack fluid) {
return fluid.getFluid().isSame(AllFluids.TEA.get());
}
@Override
public void applyEffects(OpenEndedPipe pipe, FluidStack fluid) {
Level world = pipe.getWorld();
if (world.getGameTime() % 5 != 0)
return;
List<LivingEntity> entities = world
.getEntitiesOfClass(LivingEntity.class, pipe.getAOE(), LivingEntity::isAffectedByPotions);
for (LivingEntity entity : entities) {
entity.addEffect(new MobEffectInstance(MobEffects.DIG_SPEED, 21, 0, false, false, false));
}
}
}
} }

View file

@ -125,4 +125,8 @@ public class HosePulleyFluidHandler implements IFluidHandler {
return internalTank.isFluidValid(tank, stack); return internalTank.isFluidValid(tank, stack);
} }
public SmartFluidTank getInternalTank() {
return internalTank;
}
} }

View file

@ -38,14 +38,24 @@ public class SpoutRenderer extends SafeBlockEntityRenderer<SpoutBlockEntity> {
.getValue(partialTicks); .getValue(partialTicks);
if (!fluidStack.isEmpty() && level != 0) { if (!fluidStack.isEmpty() && level != 0) {
boolean top = fluidStack.getFluid()
.getAttributes()
.isLighterThanAir();
level = Math.max(level, 0.175f); level = Math.max(level, 0.175f);
float min = 2.5f / 16f; float min = 2.5f / 16f;
float max = min + (11 / 16f); float max = min + (11 / 16f);
float yOffset = (11 / 16f) * level; float yOffset = (11 / 16f) * level;
ms.pushPose(); ms.pushPose();
ms.translate(0, yOffset, 0); if (!top) ms.translate(0, yOffset, 0);
FluidRenderer.renderFluidBox(fluidStack, min, min - yOffset, min, max, min, max, buffer, ms, light, else ms.translate(0, max - min, 0);
false);
FluidRenderer.renderFluidBox(fluidStack,
min, min - yOffset, min,
max, min, max,
buffer, ms, light, false);
ms.popPose(); ms.popPose();
} }

View file

@ -13,6 +13,8 @@ import java.util.function.Supplier;
import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.AllBlockEntityTypes;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlock;
import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator; import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour; import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour;
import com.simibubi.create.content.contraptions.relays.elementary.EncasedBlock; import com.simibubi.create.content.contraptions.relays.elementary.EncasedBlock;
@ -36,7 +38,9 @@ import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.PipeBlock; import net.minecraft.world.level.block.PipeBlock;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ -46,7 +50,8 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import net.minecraft.world.ticks.TickPriority; import net.minecraft.world.ticks.TickPriority;
public class EncasedPipeBlock extends Block implements IWrenchable, ISpecialBlockItemRequirement, IBE<FluidPipeBlockEntity>, EncasedBlock { public class EncasedPipeBlock extends Block
implements IWrenchable, ISpecialBlockItemRequirement, IBE<FluidPipeBlockEntity>, EncasedBlock, ITransformableBlock {
public static final Map<Direction, BooleanProperty> FACING_TO_PROPERTY_MAP = PipeBlock.PROPERTY_BY_DIRECTION; public static final Map<Direction, BooleanProperty> FACING_TO_PROPERTY_MAP = PipeBlock.PROPERTY_BY_DIRECTION;
private final Supplier<Block> casing; private final Supplier<Block> casing;
@ -173,4 +178,20 @@ public class EncasedPipeBlock extends Block implements IWrenchable, ISpecialBloc
EncasedPipeBlock.transferSixWayProperties(state, defaultBlockState())); EncasedPipeBlock.transferSixWayProperties(state, defaultBlockState()));
FluidTransportBehaviour.loadFlows(level, pos); FluidTransportBehaviour.loadFlows(level, pos);
} }
@Override
public BlockState rotate(BlockState pState, Rotation pRotation) {
return FluidPipeBlockRotation.rotate(pState, pRotation);
}
@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return FluidPipeBlockRotation.mirror(pState, pMirror);
}
@Override
public BlockState transform(BlockState state, StructureTransform transform) {
return FluidPipeBlockRotation.transform(state, transform);
}
} }

View file

@ -8,6 +8,8 @@ import javax.annotation.Nullable;
import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.AllBlockEntityTypes;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlock;
import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform;
import com.simibubi.create.content.contraptions.fluids.FluidPropagator; import com.simibubi.create.content.contraptions.fluids.FluidPropagator;
import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour; import com.simibubi.create.content.contraptions.fluids.FluidTransportBehaviour;
import com.simibubi.create.content.contraptions.relays.elementary.BracketedBlockEntityBehaviour; import com.simibubi.create.content.contraptions.relays.elementary.BracketedBlockEntityBehaviour;
@ -36,7 +38,9 @@ import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.PipeBlock; import net.minecraft.world.level.block.PipeBlock;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SimpleWaterloggedBlock; import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ -50,8 +54,8 @@ import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.ticks.TickPriority; import net.minecraft.world.ticks.TickPriority;
public class FluidPipeBlock extends PipeBlock public class FluidPipeBlock extends PipeBlock implements SimpleWaterloggedBlock, IWrenchableWithBracket,
implements SimpleWaterloggedBlock, IWrenchableWithBracket, IBE<FluidPipeBlockEntity>, EncasableBlock { IBE<FluidPipeBlockEntity>, EncasableBlock, ITransformableBlock {
private static final VoxelShape OCCLUSION_BOX = Block.box(4, 4, 4, 12, 12, 12); private static final VoxelShape OCCLUSION_BOX = Block.box(4, 4, 4, 12, 12, 12);
@ -337,4 +341,20 @@ public class FluidPipeBlock extends PipeBlock
public VoxelShape getOcclusionShape(BlockState pState, BlockGetter pLevel, BlockPos pPos) { public VoxelShape getOcclusionShape(BlockState pState, BlockGetter pLevel, BlockPos pPos) {
return OCCLUSION_BOX; return OCCLUSION_BOX;
} }
@Override
public BlockState rotate(BlockState pState, Rotation pRotation) {
return FluidPipeBlockRotation.rotate(pState, pRotation);
}
@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return FluidPipeBlockRotation.mirror(pState, pMirror);
}
@Override
public BlockState transform(BlockState state, StructureTransform transform) {
return FluidPipeBlockRotation.transform(state, transform);
}
} }

View file

@ -0,0 +1,49 @@
package com.simibubi.create.content.contraptions.fluids.pipes;
import java.util.Map;
import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.PipeBlock;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
public class FluidPipeBlockRotation {
public static final Map<Direction, BooleanProperty> FACING_TO_PROPERTY_MAP = PipeBlock.PROPERTY_BY_DIRECTION;
public static BlockState rotate(BlockState state, Rotation rotation) {
BlockState rotated = state;
for (Direction direction : Iterate.horizontalDirections)
rotated = rotated.setValue(FACING_TO_PROPERTY_MAP.get(rotation.rotate(direction)),
state.getValue(FACING_TO_PROPERTY_MAP.get(direction)));
return rotated;
}
public static BlockState mirror(BlockState state, Mirror mirror) {
BlockState mirrored = state;
for (Direction direction : Iterate.horizontalDirections)
mirrored = mirrored.setValue(FACING_TO_PROPERTY_MAP.get(mirror.mirror(direction)),
state.getValue(FACING_TO_PROPERTY_MAP.get(direction)));
return mirrored;
}
public static BlockState transform(BlockState state, StructureTransform transform) {
if (transform.mirror != null)
state = mirror(state, transform.mirror);
if (transform.rotationAxis == Direction.Axis.Y)
return rotate(state, transform.rotation);
BlockState rotated = state;
for (Direction direction : Iterate.directions)
rotated = rotated.setValue(FACING_TO_PROPERTY_MAP.get(transform.rotateFacing(direction)),
state.getValue(FACING_TO_PROPERTY_MAP.get(direction)));
return rotated;
}
}

View file

@ -44,7 +44,8 @@ public class SequencedAssemblyRecipe implements Recipe<RecipeWrapper> {
protected List<SequencedRecipe<?>> sequence; protected List<SequencedRecipe<?>> sequence;
protected int loops; protected int loops;
protected ProcessingOutput transitionalItem; protected ProcessingOutput transitionalItem;
protected List<ProcessingOutput> resultPool;
public final List<ProcessingOutput> resultPool;
public SequencedAssemblyRecipe(ResourceLocation recipeId, SequencedAssemblyRecipeSerializer serializer) { public SequencedAssemblyRecipe(ResourceLocation recipeId, SequencedAssemblyRecipeSerializer serializer) {
this.id = recipeId; this.id = recipeId;

View file

@ -2,6 +2,11 @@ package com.simibubi.create.content.contraptions.relays.advanced;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.contraptions.RotationPropagator; import com.simibubi.create.content.contraptions.RotationPropagator;
import com.simibubi.create.content.contraptions.base.KineticBlockEntity; import com.simibubi.create.content.contraptions.base.KineticBlockEntity;
import com.simibubi.create.content.contraptions.components.motor.KineticScrollValueBehaviour; import com.simibubi.create.content.contraptions.components.motor.KineticScrollValueBehaviour;
@ -20,11 +25,14 @@ import net.minecraft.core.Direction;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
public class SpeedControllerBlockEntity extends KineticBlockEntity { public class SpeedControllerBlockEntity extends KineticBlockEntity {
public static final int DEFAULT_SPEED = 16; public static final int DEFAULT_SPEED = 16;
protected ScrollValueBehaviour targetSpeed; public ScrollValueBehaviour targetSpeed;
public AbstractComputerBehaviour computerBehaviour;
boolean hasBracket; boolean hasBracket;
@ -50,6 +58,7 @@ public class SpeedControllerBlockEntity extends KineticBlockEntity {
targetSpeed.value = DEFAULT_SPEED; targetSpeed.value = DEFAULT_SPEED;
targetSpeed.withCallback(i -> this.updateTargetRotation()); targetSpeed.withCallback(i -> this.updateTargetRotation());
behaviours.add(targetSpeed); behaviours.add(targetSpeed);
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
registerAwardables(behaviours, AllAdvancements.SPEED_CONTROLLER); registerAwardables(behaviours, AllAdvancements.SPEED_CONTROLLER);
} }
@ -125,6 +134,20 @@ public class SpeedControllerBlockEntity extends KineticBlockEntity {
.isHorizontal(); .isHorizontal();
} }
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
private class ControllerValueBoxTransform extends ValueBoxTransform.Sided { private class ControllerValueBoxTransform extends ValueBoxTransform.Sided {
@Override @Override

View file

@ -35,6 +35,9 @@ public class ConfigureSequencedGearshiftPacket extends BlockEntityConfigurationP
@Override @Override
protected void applySettings(SequencedGearshiftBlockEntity be) { protected void applySettings(SequencedGearshiftBlockEntity be) {
if (be.computerBehaviour.hasAttachedComputer())
return;
be.run(-1); be.run(-1);
be.instructions = Instruction.deserializeAll(instructions); be.instructions = Instruction.deserializeAll(instructions);
be.sendData(); be.sendData();

View file

@ -19,8 +19,12 @@ public class Instruction {
} }
public Instruction(SequencerInstructions instruction, int value) { public Instruction(SequencerInstructions instruction, int value) {
this(instruction, InstructionSpeedModifiers.FORWARD, value);
}
public Instruction(SequencerInstructions instruction, InstructionSpeedModifiers speedModifier, int value) {
this.instruction = instruction; this.instruction = instruction;
speedModifier = InstructionSpeedModifiers.FORWARD; this.speedModifier = speedModifier;
this.value = value; this.value = value;
} }

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.contraptions.relays.advanced.sequencer; package com.simibubi.create.content.contraptions.relays.advanced.sequencer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Components;
@ -36,4 +37,11 @@ public enum InstructionSpeedModifiers {
return options; return options;
} }
public static InstructionSpeedModifiers getByModifier(int modifier) {
return Arrays.stream(InstructionSpeedModifiers.values())
.filter(speedModifier -> speedModifier.value == modifier)
.findAny()
.orElse(InstructionSpeedModifiers.FORWARD);
}
} }

View file

@ -1,9 +1,17 @@
package com.simibubi.create.content.contraptions.relays.advanced.sequencer; package com.simibubi.create.content.contraptions.relays.advanced.sequencer;
import java.util.List;
import java.util.Vector; import java.util.Vector;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.contraptions.base.KineticBlockEntity; import com.simibubi.create.content.contraptions.base.KineticBlockEntity;
import com.simibubi.create.content.contraptions.relays.encased.SplitShaftBlockEntity; import com.simibubi.create.content.contraptions.relays.encased.SplitShaftBlockEntity;
import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour;
import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -12,6 +20,8 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity { public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
@ -22,6 +32,8 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
int timer; int timer;
boolean poweredPreviously; boolean poweredPreviously;
public AbstractComputerBehaviour computerBehaviour;
public record SequenceContext(SequencerInstructions instruction, double relativeValue) { public record SequenceContext(SequencerInstructions instruction, double relativeValue) {
public static SequenceContext fromGearshift(SequencerInstructions instruction, double kineticSpeed, public static SequenceContext fromGearshift(SequencerInstructions instruction, double kineticSpeed,
@ -61,6 +73,12 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
poweredPreviously = false; poweredPreviously = false;
} }
@Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
super.addBehaviours(behaviours);
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
}
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
@ -103,6 +121,8 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
} }
public void onRedstoneUpdate(boolean isPowered, boolean isRunning) { public void onRedstoneUpdate(boolean isPowered, boolean isRunning) {
if (computerBehaviour.hasAttachedComputer())
return;
if (!poweredPreviously && isPowered) if (!poweredPreviously && isPowered)
risingFlank(); risingFlank();
poweredPreviously = isPowered; poweredPreviously = isPowered;
@ -136,7 +156,7 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
} }
} }
protected void run(int instructionIndex) { public void run(int instructionIndex) {
Instruction instruction = getInstruction(instructionIndex); Instruction instruction = getInstruction(instructionIndex);
if (instruction == null || instruction.instruction == SequencerInstructions.END) { if (instruction == null || instruction.instruction == SequencerInstructions.END) {
if (getModifier() != 0) if (getModifier() != 0)
@ -193,6 +213,20 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
super.read(compound, clientPacket); super.read(compound, clientPacket);
} }
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
@Override @Override
public float getRotationSpeedModifier(Direction face) { public float getRotationSpeedModifier(Direction face) {
if (isVirtual()) if (isVirtual())
@ -208,4 +242,8 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity {
.getSpeedModifier(); .getSpeedModifier();
} }
public Vector<Instruction> getInstructions() {
return this.instructions;
}
} }

View file

@ -4,6 +4,7 @@ import java.util.Vector;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.compat.computercraft.ComputerScreen;
import com.simibubi.create.foundation.gui.AbstractSimiScreen; import com.simibubi.create.foundation.gui.AbstractSimiScreen;
import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllGuiTextures;
import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.AllIcons;
@ -15,7 +16,6 @@ import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.ListTag; import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -25,22 +25,26 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen {
private final ItemStack renderedItem = AllBlocks.SEQUENCED_GEARSHIFT.asStack(); private final ItemStack renderedItem = AllBlocks.SEQUENCED_GEARSHIFT.asStack();
private final AllGuiTextures background = AllGuiTextures.SEQUENCER; private final AllGuiTextures background = AllGuiTextures.SEQUENCER;
private IconButton confirmButton; private IconButton confirmButton;
private SequencedGearshiftBlockEntity be;
private ListTag compareTag; private ListTag compareTag;
private Vector<Instruction> instructions; private Vector<Instruction> instructions;
private BlockPos pos;
private Vector<Vector<ScrollInput>> inputs; private Vector<Vector<ScrollInput>> inputs;
public SequencedGearshiftScreen(SequencedGearshiftBlockEntity be) { public SequencedGearshiftScreen(SequencedGearshiftBlockEntity be) {
super(Lang.translateDirect("gui.sequenced_gearshift.title")); super(Lang.translateDirect("gui.sequenced_gearshift.title"));
this.instructions = be.instructions; this.instructions = be.instructions;
this.pos = be.getBlockPos(); this.be = be;
compareTag = Instruction.serializeAll(instructions); compareTag = Instruction.serializeAll(instructions);
} }
@Override @Override
protected void init() { protected void init() {
if (be.computerBehaviour.hasAttachedComputer())
minecraft.setScreen(
new ComputerScreen(title, this::renderAdditional, this, be.computerBehaviour::hasAttachedComputer));
setWindowSize(background.width, background.height); setWindowSize(background.width, background.height);
setWindowOffset(-20, 0); setWindowOffset(-20, 0);
super.init(); super.init();
@ -55,8 +59,7 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen {
for (int row = 0; row < instructions.size(); row++) for (int row = 0; row < instructions.size(); row++)
initInputsOfRow(row, x, y); initInputsOfRow(row, x, y);
confirmButton = confirmButton = new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM);
new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM);
confirmButton.withCallback(() -> { confirmButton.withCallback(() -> {
onClose(); onClose();
}); });
@ -127,6 +130,15 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen {
modifier.setState(instruction.speedModifier.ordinal()); modifier.setState(instruction.speedModifier.ordinal());
} }
@Override
public void tick() {
super.tick();
if (be.computerBehaviour.hasAttachedComputer())
minecraft.setScreen(
new ComputerScreen(title, this::renderAdditional, this, be.computerBehaviour::hasAttachedComputer));
}
@Override @Override
protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) {
int x = guiLeft; int x = guiLeft;
@ -134,6 +146,13 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen {
background.render(ms, x, y, this); background.render(ms, x, y, this);
for (int row = 0; row < instructions.capacity(); row++) {
AllGuiTextures toDraw = AllGuiTextures.SEQUENCER_EMPTY;
int yOffset = toDraw.height * row;
toDraw.render(ms, x, y + 14 + yOffset, this);
}
for (int row = 0; row < instructions.capacity(); row++) { for (int row = 0; row < instructions.capacity(); row++) {
AllGuiTextures toDraw = AllGuiTextures.SEQUENCER_EMPTY; AllGuiTextures toDraw = AllGuiTextures.SEQUENCER_EMPTY;
int yOffset = toDraw.height * row; int yOffset = toDraw.height * row;
@ -157,9 +176,13 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen {
} }
font.draw(ms, title, x + (background.width - 8) / 2 - font.width(title) / 2, y + 4, 0x592424); font.draw(ms, title, x + (background.width - 8) / 2 - font.width(title) / 2, y + 4, 0x592424);
renderAdditional(ms, mouseX, mouseY, partialTicks, x, y, background);
}
GuiGameElement.of(renderedItem) private void renderAdditional(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop,
.<GuiGameElement.GuiRenderBuilder>at(x + background.width + 6, y + background.height - 56, -200) AllGuiTextures background) {
GuiGameElement.of(renderedItem).<GuiGameElement
.GuiRenderBuilder>at(guiLeft + background.width + 6, guiTop + background.height - 56, 100)
.scale(5) .scale(5)
.render(ms); .render(ms);
} }
@ -172,7 +195,8 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen {
ListTag serialized = Instruction.serializeAll(instructions); ListTag serialized = Instruction.serializeAll(instructions);
if (serialized.equals(compareTag)) if (serialized.equals(compareTag))
return; return;
AllPackets.getChannel().sendToServer(new ConfigureSequencedGearshiftPacket(pos, serialized)); AllPackets.getChannel()
.sendToServer(new ConfigureSequencedGearshiftPacket(be.getBlockPos(), serialized));
} }
@Override @Override

View file

@ -12,7 +12,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
public class GaugeBlockEntity extends KineticBlockEntity implements IHaveGoggleInformation { public abstract class GaugeBlockEntity extends KineticBlockEntity implements IHaveGoggleInformation {
public float dialTarget; public float dialTarget;
public float dialState; public float dialState;
@ -52,4 +52,5 @@ public class GaugeBlockEntity extends KineticBlockEntity implements IHaveGoggleI
return true; return true;
} }
} }

View file

@ -2,24 +2,41 @@ package com.simibubi.create.content.contraptions.relays.gauge;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.contraptions.base.IRotate.SpeedLevel; import com.simibubi.create.content.contraptions.base.IRotate.SpeedLevel;
import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.utility.Color; import com.simibubi.create.foundation.utility.Color;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
public class SpeedGaugeBlockEntity extends GaugeBlockEntity { public class SpeedGaugeBlockEntity extends GaugeBlockEntity {
public AbstractComputerBehaviour computerBehaviour;
public SpeedGaugeBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) { public SpeedGaugeBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state); super(type, pos, state);
} }
@Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
super.addBehaviours(behaviours);
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
}
@Override @Override
public void onSpeedChanged(float prevSpeed) { public void onSpeedChanged(float prevSpeed) {
super.onSpeedChanged(prevSpeed); super.onSpeedChanged(prevSpeed);
@ -62,4 +79,19 @@ public class SpeedGaugeBlockEntity extends GaugeBlockEntity {
.forGoggles(tooltip); .forGoggles(tooltip);
return true; return true;
} }
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
} }

View file

@ -2,6 +2,11 @@ package com.simibubi.create.content.contraptions.relays.gauge;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.contraptions.base.IRotate.StressImpact; import com.simibubi.create.content.contraptions.base.IRotate.StressImpact;
import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour;
@ -13,14 +18,19 @@ import com.simibubi.create.foundation.utility.LangBuilder;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
public class StressGaugeBlockEntity extends GaugeBlockEntity { public class StressGaugeBlockEntity extends GaugeBlockEntity {
public AbstractComputerBehaviour computerBehaviour;
static BlockPos lastSent; static BlockPos lastSent;
public StressGaugeBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) { public StressGaugeBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
@ -30,6 +40,7 @@ public class StressGaugeBlockEntity extends GaugeBlockEntity {
@Override @Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) { public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
super.addBehaviours(behaviours); super.addBehaviours(behaviours);
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
registerAwardables(behaviours, AllAdvancements.STRESSOMETER, AllAdvancements.STRESSOMETER_MAXED); registerAwardables(behaviours, AllAdvancements.STRESSOMETER, AllAdvancements.STRESSOMETER_MAXED);
} }
@ -141,4 +152,18 @@ public class StressGaugeBlockEntity extends GaugeBlockEntity {
award(AllAdvancements.STRESSOMETER_MAXED); award(AllAdvancements.STRESSOMETER_MAXED);
} }
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
} }

View file

@ -26,6 +26,7 @@ import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringB
import com.simibubi.create.foundation.blockEntity.behaviour.filtering.SidedFilteringBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.filtering.SidedFilteringBehaviour;
import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.INamedIconOptions; import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.INamedIconOptions;
import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.ScrollOptionBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.ScrollOptionBehaviour;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.AllIcons;
import com.simibubi.create.foundation.utility.BlockHelper; import com.simibubi.create.foundation.utility.BlockHelper;
import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Components;
@ -174,7 +175,7 @@ public class BrassTunnelBlockEntity extends BeltTunnelBlockEntity implements IHa
return; return;
if (selectionMode.get() != SelectionMode.SYNCHRONIZE || syncedOutputActive) { if (selectionMode.get() != SelectionMode.SYNCHRONIZE || syncedOutputActive) {
distributionProgress = 10; distributionProgress = AllConfigs.server().logistics.brassTunnelTimer.get();
sendData(); sendData();
} }
return; return;

View file

@ -7,6 +7,7 @@ import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.Capability;
@ -33,4 +34,8 @@ public class DepotBlockEntity extends SmartBlockEntity {
return depotBehaviour.getItemCapability(cap, side); return depotBehaviour.getItemCapability(cap, side);
return super.getCapability(cap, side); return super.getCapability(cap, side);
} }
public ItemStack getHeldItem() {
return depotBehaviour.getHeldItemStack();
}
} }

View file

@ -9,6 +9,8 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.content.logistics.block.display.source.ComputerDisplaySource;
import com.simibubi.create.content.logistics.block.display.source.DeathCounterDisplaySource; import com.simibubi.create.content.logistics.block.display.source.DeathCounterDisplaySource;
import com.simibubi.create.content.logistics.block.display.source.DisplaySource; import com.simibubi.create.content.logistics.block.display.source.DisplaySource;
import com.simibubi.create.content.logistics.block.display.source.EnchantPowerDisplaySource; import com.simibubi.create.content.logistics.block.display.source.EnchantPowerDisplaySource;
@ -241,5 +243,14 @@ public class AllDisplayBehaviours {
assignBlockEntity(register(Create.asResource("scoreboard_display_source"), new ScoreboardDisplaySource()), BlockEntityType.COMMAND_BLOCK); assignBlockEntity(register(Create.asResource("scoreboard_display_source"), new ScoreboardDisplaySource()), BlockEntityType.COMMAND_BLOCK);
assignBlockEntity(register(Create.asResource("enchant_power_display_source"), new EnchantPowerDisplaySource()), BlockEntityType.ENCHANTING_TABLE); assignBlockEntity(register(Create.asResource("enchant_power_display_source"), new EnchantPowerDisplaySource()), BlockEntityType.ENCHANTING_TABLE);
assignBlock(register(Create.asResource("redstone_power_display_source"), new RedstonePowerDisplaySource()), Blocks.TARGET); assignBlock(register(Create.asResource("redstone_power_display_source"), new RedstonePowerDisplaySource()), Blocks.TARGET);
Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> {
DisplayBehaviour computerDisplaySource = register(Create.asResource("computer_display_source"), new ComputerDisplaySource());
assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "wired_modem_full"));
assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_normal"));
assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_advanced"));
assignTile(computerDisplaySource, new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_command"));
});
} }
} }

View file

@ -2,6 +2,11 @@ package com.simibubi.create.content.logistics.block.display;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.logistics.block.display.source.DisplaySource; import com.simibubi.create.content.logistics.block.display.source.DisplaySource;
import com.simibubi.create.content.logistics.block.display.target.DisplayTarget; import com.simibubi.create.content.logistics.block.display.target.DisplayTarget;
import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.advancement.AllAdvancements;
@ -18,6 +23,8 @@ import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
public class DisplayLinkBlockEntity extends SmartBlockEntity { public class DisplayLinkBlockEntity extends SmartBlockEntity {
@ -33,6 +40,7 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity {
private boolean sendPulse; private boolean sendPulse;
public int refreshTicks; public int refreshTicks;
public AbstractComputerBehaviour computerBehaviour;
public DisplayLinkBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) { public DisplayLinkBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state); super(type, pos, state);
@ -44,6 +52,12 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity {
glow.chase(0, 0.5f, Chaser.EXP); glow.chase(0, 0.5f, Chaser.EXP);
} }
@Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
registerAwardables(behaviours, AllAdvancements.DISPLAY_LINK, AllAdvancements.DISPLAY_BOARD);
}
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
@ -61,7 +75,7 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity {
} }
refreshTicks++; refreshTicks++;
if (refreshTicks < activeSource.getPassiveRefreshTicks()) if (refreshTicks < activeSource.getPassiveRefreshTicks() || !activeSource.shouldPassiveReset())
return; return;
tickSource(); tickSource();
} }
@ -118,11 +132,6 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity {
award(AllAdvancements.DISPLAY_LINK); award(AllAdvancements.DISPLAY_LINK);
} }
@Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
registerAwardables(behaviours, AllAdvancements.DISPLAY_LINK, AllAdvancements.DISPLAY_BOARD);
}
@Override @Override
public void writeSafe(CompoundTag tag) { public void writeSafe(CompoundTag tag) {
super.writeSafe(tag); super.writeSafe(tag);
@ -173,6 +182,21 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity {
sourceConfig = data.copy(); sourceConfig = data.copy();
} }
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
public void target(BlockPos targetPosition) { public void target(BlockPos targetPosition) {
this.targetOffset = targetPosition.subtract(worldPosition); this.targetOffset = targetPosition.subtract(worldPosition);
} }

View file

@ -0,0 +1,33 @@
package com.simibubi.create.content.logistics.block.display.source;
import java.util.ArrayList;
import java.util.List;
import com.simibubi.create.content.logistics.block.display.DisplayLinkContext;
import com.simibubi.create.content.logistics.block.display.target.DisplayTargetStats;
import com.simibubi.create.foundation.utility.Components;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.MutableComponent;
public class ComputerDisplaySource extends DisplaySource {
@Override
public List<MutableComponent> provideText(DisplayLinkContext context, DisplayTargetStats stats) {
List<MutableComponent> components = new ArrayList<>();
ListTag tag = context.sourceConfig().getList("ComputerSourceList", Tag.TAG_STRING);
for (int i = 0; i < tag.size(); i++) {
components.add(Components.literal(tag.getString(i)));
}
return components;
}
@Override
public boolean shouldPassiveReset() {
return false;
}
}

View file

@ -49,6 +49,10 @@ public abstract class DisplaySource extends DisplayBehaviour {
return 100; return 100;
}; };
public boolean shouldPassiveReset() {
return true;
}
protected String getTranslationKey() { protected String getTranslationKey() {
return id.getPath(); return id.getPath();
} }

View file

@ -123,6 +123,10 @@ public class NixieTubeBlockEntity extends SmartBlockEntity {
customText = Optional.empty(); customText = Optional.empty();
} }
public int getRedstoneStrength() {
return redstoneStrength;
}
// //
@Override @Override

View file

@ -8,6 +8,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.api.event.TrackGraphMergeEvent;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalPropagator; import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalPropagator;
@ -16,6 +17,7 @@ import net.minecraft.util.Mth;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
public class TrackPropagator { public class TrackPropagator {
@ -135,6 +137,7 @@ public class TrackPropagator {
if (graph == null) if (graph == null)
graph = other; graph = other;
else { else {
MinecraftForge.EVENT_BUS.post(new TrackGraphMergeEvent(other, graph));
other.transferAll(graph); other.transferAll(graph);
manager.removeGraphAndGroup(other); manager.removeGraphAndGroup(other);
sync.graphRemoved(other); sync.graphRemoved(other);

View file

@ -108,7 +108,7 @@ public class Carriage {
DimensionalCarriageEntity dimensional = getDimensional(level); DimensionalCarriageEntity dimensional = getDimensional(level);
dimensional.alignEntity(entity); dimensional.alignEntity(entity);
dimensional.removeAndSaveEntity(entity, false); dimensional.removeAndSaveEntity(entity, true);
} }
public DimensionalCarriageEntity getDimensional(Level level) { public DimensionalCarriageEntity getDimensional(Level level) {
@ -739,8 +739,8 @@ public class Carriage {
} }
private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean portal) { private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean capture) {
if (!portal) { if (!capture) {
sp.stopRiding(); sp.stopRiding();
return; return;
} }

View file

@ -17,10 +17,6 @@ import java.util.function.Consumer;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.mutable.MutableObject;
@ -46,6 +42,7 @@ import com.simibubi.create.content.logistics.trains.management.edgePoint.station
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationBlockEntity; import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationBlockEntity;
import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleRuntime; import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleRuntime;
import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleRuntime.State; import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleRuntime.State;
import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity;
import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.networking.AllPackets;
@ -70,6 +67,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Explosion.BlockInteraction; import net.minecraft.world.level.Explosion.BlockInteraction;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
@ -90,6 +88,7 @@ public class Train {
public boolean honk = false; public boolean honk = false;
public UUID id; public UUID id;
@Nullable
public UUID owner; public UUID owner;
public TrackGraph graph; public TrackGraph graph;
public Navigation navigation; public Navigation navigation;
@ -1108,7 +1107,8 @@ public class Train {
public CompoundTag write(DimensionPalette dimensions) { public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag(); CompoundTag tag = new CompoundTag();
tag.putUUID("Id", id); tag.putUUID("Id", id);
tag.putUUID("Owner", owner); if (owner != null)
tag.putUUID("Owner", owner);
if (graph != null) if (graph != null)
tag.putUUID("Graph", graph.id); tag.putUUID("Graph", graph.id);
tag.put("Carriages", NBTHelper.writeCompoundList(carriages, c -> c.write(dimensions))); tag.put("Carriages", NBTHelper.writeCompoundList(carriages, c -> c.write(dimensions)));
@ -1154,7 +1154,7 @@ public class Train {
public static Train read(CompoundTag tag, Map<UUID, TrackGraph> trackNetworks, DimensionPalette dimensions) { public static Train read(CompoundTag tag, Map<UUID, TrackGraph> trackNetworks, DimensionPalette dimensions) {
UUID id = tag.getUUID("Id"); UUID id = tag.getUUID("Id");
UUID owner = tag.getUUID("Owner"); UUID owner = tag.contains("Owner") ? tag.getUUID("Owner") : null;
UUID graphId = tag.contains("Graph") ? tag.getUUID("Graph") : null; UUID graphId = tag.contains("Graph") ? tag.getUUID("Graph") : null;
TrackGraph graph = graphId == null ? null : trackNetworks.get(graphId); TrackGraph graph = graphId == null ? null : trackNetworks.get(graphId);
List<Carriage> carriages = new ArrayList<>(); List<Carriage> carriages = new ArrayList<>();

View file

@ -37,7 +37,10 @@ public class TrainPacket extends SimplePacketBase {
if (!add) if (!add)
return; return;
UUID owner = buffer.readUUID(); UUID owner = null;
if (buffer.readBoolean())
owner = buffer.readUUID();
List<Carriage> carriages = new ArrayList<>(); List<Carriage> carriages = new ArrayList<>();
List<Integer> carriageSpacing = new ArrayList<>(); List<Integer> carriageSpacing = new ArrayList<>();
@ -75,7 +78,9 @@ public class TrainPacket extends SimplePacketBase {
if (!add) if (!add)
return; return;
buffer.writeUUID(train.owner); buffer.writeBoolean(train.owner != null);
if (train.owner != null)
buffer.writeUUID(train.owner);
buffer.writeVarInt(train.carriages.size()); buffer.writeVarInt(train.carriages.size());
for (Carriage carriage : train.carriages) { for (Carriage carriage : train.carriages) {

View file

@ -170,6 +170,10 @@ public class FlapDisplaySection {
return !singleFlap; return !singleFlap;
} }
public Component getText() {
return component;
}
public static String[] getFlapCycle(String key) { public static String[] getFlapCycle(String key) {
return LOADED_FLAP_CYCLES.computeIfAbsent(key, k -> Lang.translateDirect("flap_display.cycles." + key) return LOADED_FLAP_CYCLES.computeIfAbsent(key, k -> Lang.translateDirect("flap_display.cycles." + key)
.getString() .getString()

View file

@ -30,10 +30,11 @@ public class GlobalTrainDisplayData {
} }
public static List<TrainDeparturePrediction> prepare(String filter, int maxLines) { public static List<TrainDeparturePrediction> prepare(String filter, int maxLines) {
String regex = filter.isBlank() ? filter : "\\Q" + filter.replace("*", "\\E.*\\Q") + "\\E";
return statusByDestination.entrySet() return statusByDestination.entrySet()
.stream() .stream()
.filter(e -> e.getKey() .filter(e -> e.getKey()
.matches(filter.replace("*", ".*"))) .matches(regex))
.flatMap(e -> e.getValue() .flatMap(e -> e.getValue()
.stream()) .stream())
.sorted() .sorted()

View file

@ -7,6 +7,7 @@ import com.jozufozu.flywheel.core.PartialModel;
import com.jozufozu.flywheel.util.transform.TransformStack; import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.CreateClient; import com.simibubi.create.CreateClient;
import com.simibubi.create.compat.computercraft.ComputerScreen;
import com.simibubi.create.content.logistics.trains.entity.Carriage; import com.simibubi.create.content.logistics.trains.entity.Carriage;
import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.entity.TrainIconType; import com.simibubi.create.content.logistics.trains.entity.TrainIconType;
@ -15,6 +16,7 @@ import com.simibubi.create.foundation.gui.AllGuiTextures;
import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.AllIcons;
import com.simibubi.create.foundation.gui.element.GuiGameElement; import com.simibubi.create.foundation.gui.element.GuiGameElement;
import com.simibubi.create.foundation.gui.widget.IconButton; import com.simibubi.create.foundation.gui.widget.IconButton;
import com.simibubi.create.foundation.utility.Components;
import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BlockStateProperties;
@ -39,6 +41,10 @@ public abstract class AbstractStationScreen extends AbstractSimiScreen {
@Override @Override
protected void init() { protected void init() {
if (te.computerBehaviour.hasAttachedComputer())
minecraft.setScreen(new ComputerScreen(title, () -> Components.literal(station.name),
this::renderAdditional, this, te.computerBehaviour::hasAttachedComputer));
setWindowSize(background.width, background.height); setWindowSize(background.width, background.height);
super.init(); super.init();
clearWidgets(); clearWidgets();
@ -71,17 +77,29 @@ public abstract class AbstractStationScreen extends AbstractSimiScreen {
return w; return w;
} }
@Override
public void tick() {
super.tick();
if (te.computerBehaviour.hasAttachedComputer())
minecraft.setScreen(new ComputerScreen(title, () -> Components.literal(station.name),
this::renderAdditional, this, te.computerBehaviour::hasAttachedComputer));
}
@Override @Override
protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) {
int x = guiLeft; int x = guiLeft;
int y = guiTop; int y = guiTop;
background.render(ms, x, y, this); background.render(ms, x, y, this);
renderAdditional(ms, mouseX, mouseY, partialTicks, x, y, background);
}
private void renderAdditional(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop, AllGuiTextures background) {
ms.pushPose(); ms.pushPose();
TransformStack msr = TransformStack.cast(ms); TransformStack msr = TransformStack.cast(ms);
msr.pushPose() msr.pushPose()
.translate(x + background.width + 4, y + background.height + 4, 100) .translate(guiLeft + background.width + 4, guiTop + background.height + 4, 100)
.scale(40) .scale(40)
.rotateX(-22) .rotateX(-22)
.rotateY(63); .rotateY(63);

View file

@ -9,13 +9,18 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.AllSoundEvents; import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.compat.computercraft.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
import com.simibubi.create.content.contraptions.components.actors.DoorControlBehaviour; import com.simibubi.create.content.contraptions.components.actors.DoorControlBehaviour;
import com.simibubi.create.content.contraptions.components.structureMovement.AssemblyException; import com.simibubi.create.content.contraptions.components.structureMovement.AssemblyException;
import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlockEntity; import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlockEntity;
@ -23,6 +28,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.Str
import com.simibubi.create.content.logistics.block.depot.DepotBehaviour; import com.simibubi.create.content.logistics.block.depot.DepotBehaviour;
import com.simibubi.create.content.logistics.block.display.DisplayLinkBlock; import com.simibubi.create.content.logistics.block.display.DisplayLinkBlock;
import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.ITrackBlock; import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.TrackEdge; import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.TrackGraph;
@ -49,6 +55,7 @@ import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.WorldAttached; import com.simibubi.create.foundation.utility.WorldAttached;
import com.simibubi.create.foundation.utility.animation.LerpedFloat; import com.simibubi.create.foundation.utility.animation.LerpedFloat;
import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser;
@ -63,9 +70,11 @@ import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.SoundType;
@ -90,6 +99,7 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
protected int failedCarriageIndex; protected int failedCarriageIndex;
protected AssemblyException lastException; protected AssemblyException lastException;
protected DepotBehaviour depotBehaviour; protected DepotBehaviour depotBehaviour;
public AbstractComputerBehaviour computerBehaviour;
// for display // for display
UUID imminentTrain; UUID imminentTrain;
@ -122,6 +132,7 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
depotBehaviour.addSubBehaviours(behaviours); depotBehaviour.addSubBehaviours(behaviours);
registerAwardables(behaviours, AllAdvancements.CONTRAPTION_ACTORS, AllAdvancements.TRAIN, registerAwardables(behaviours, AllAdvancements.CONTRAPTION_ACTORS, AllAdvancements.TRAIN,
AllAdvancements.LONG_TRAIN, AllAdvancements.CONDUCTOR); AllAdvancements.LONG_TRAIN, AllAdvancements.CONDUCTOR);
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
} }
@Override @Override
@ -343,6 +354,63 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
return true; return true;
} }
public boolean enterAssemblyMode(@Nullable ServerPlayer sender) {
if (isAssembling())
return false;
tryDisassembleTrain(sender);
if (!tryEnterAssemblyMode())
return false;
BlockState newState = getBlockState().setValue(StationBlock.ASSEMBLING, true);
level.setBlock(getBlockPos(), newState, 3);
refreshBlockState();
refreshAssemblyInfo();
updateStationState(station -> station.assembling = true);
GlobalStation station = getStation();
if (station != null) {
for (Train train : Create.RAILWAYS.sided(level).trains.values()) {
if (train.navigation.destination != station)
continue;
GlobalStation preferredDestination = train.runtime.startCurrentInstruction();
train.navigation.startNavigation(preferredDestination != null ? preferredDestination : station, Double.MAX_VALUE, false);
}
}
return true;
}
public boolean exitAssemblyMode() {
if (!isAssembling())
return false;
cancelAssembly();
BlockState newState = getBlockState().setValue(StationBlock.ASSEMBLING, false);
level.setBlock(getBlockPos(), newState, 3);
refreshBlockState();
return updateStationState(station -> station.assembling = false);
}
public boolean tryDisassembleTrain(@Nullable ServerPlayer sender) {
GlobalStation station = getStation();
if (station == null)
return false;
Train train = station.getPresentTrain();
if (train == null)
return false;
BlockPos trackPosition = edgePoint.getGlobalPosition();
if (!train.disassemble(getAssemblyDirection(), trackPosition.above()))
return false;
dropSchedule(sender);
return true;
}
public boolean isAssembling() { public boolean isAssembling() {
BlockState state = getBlockState(); BlockState state = getBlockState();
return state.hasProperty(StationBlock.ASSEMBLING) && state.getValue(StationBlock.ASSEMBLING); return state.hasProperty(StationBlock.ASSEMBLING) && state.getValue(StationBlock.ASSEMBLING);
@ -370,6 +438,42 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
return true; return true;
} }
public void dropSchedule(@Nullable ServerPlayer sender) {
GlobalStation station = getStation();
if (station == null)
return;
Train train = station.getPresentTrain();
if (train == null)
return;
ItemStack schedule = train.runtime.returnSchedule();
if (schedule.isEmpty())
return;
if (sender != null && sender.getMainHandItem().isEmpty()) {
sender.getInventory()
.placeItemBackInInventory(schedule);
return;
}
Vec3 v = VecHelper.getCenterOf(getBlockPos());
ItemEntity itemEntity = new ItemEntity(getLevel(), v.x, v.y, v.z, schedule);
itemEntity.setDeltaMovement(Vec3.ZERO);
getLevel().addFreshEntity(itemEntity);
}
private boolean updateStationState(Consumer<GlobalStation> updateState) {
GlobalStation station = getStation();
GraphLocation graphLocation = edgePoint.determineGraphLocation();
if (station == null || graphLocation == null)
return false;
updateState.accept(station);
Create.RAILWAYS.sync.pointAdded(graphLocation.graph, station);
Create.RAILWAYS.markTracksDirty();
return true;
}
public void refreshAssemblyInfo() { public void refreshAssemblyInfo() {
if (!edgePoint.hasValidTrack()) if (!edgePoint.hasValidTrack())
return; return;
@ -450,6 +554,14 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
map.put(worldPosition, BoundingBox.fromCorners(startPosition, trackEnd)); map.put(worldPosition, BoundingBox.fromCorners(startPosition, trackEnd));
} }
public boolean updateName(String name) {
if (!updateStationState(station -> station.name = name))
return false;
notifyUpdate();
return true;
}
public boolean isValidBogeyOffset(int i) { public boolean isValidBogeyOffset(int i) {
if ((i < 3 || bogeyCount == 0) && i != 0) if ((i < 3 || bogeyCount == 0) && i != 0)
return false; return false;
@ -744,12 +856,20 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
} }
@Override @Override
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) { public <T> @NotNull LazyOptional<T> getCapability(@NotNull Capability<T> cap, Direction side) {
if (isItemHandlerCap(cap)) if (isItemHandlerCap(cap))
return depotBehaviour.getItemCapability(cap, side); return depotBehaviour.getItemCapability(cap, side);
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side); return super.getCapability(cap, side);
} }
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.removePeripheral();
}
private void applyAutoSchedule() { private void applyAutoSchedule() {
ItemStack stack = getAutoSchedule(); ItemStack stack = getAutoSchedule();
if (!AllItems.SCHEDULE.isIn(stack)) if (!AllItems.SCHEDULE.isIn(stack))

View file

@ -1,21 +1,14 @@
package com.simibubi.create.content.logistics.trains.management.edgePoint.station; package com.simibubi.create.content.logistics.trains.management.edgePoint.station;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.actors.DoorControl; import com.simibubi.create.content.contraptions.components.actors.DoorControl;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.foundation.networking.BlockEntityConfigurationPacket; import com.simibubi.create.foundation.networking.BlockEntityConfigurationPacket;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public class StationEditPacket extends BlockEntityConfigurationPacket<StationBlockEntity> { public class StationEditPacket extends BlockEntityConfigurationPacket<StationBlockEntity> {
@ -101,22 +94,15 @@ public class StationEditPacket extends BlockEntityConfigurationPacket<StationBlo
BlockState blockState = level.getBlockState(blockPos); BlockState blockState = level.getBlockState(blockPos);
if (dropSchedule) { if (dropSchedule) {
scheduleDropRequested(player, be); be.dropSchedule(player);
return; return;
} }
if (doorControl != null) if (doorControl != null)
be.doorControls.set(doorControl); be.doorControls.set(doorControl);
if (!name.isBlank()) { if (!name.isBlank())
GlobalStation station = be.getStation(); be.updateName(name);
GraphLocation graphLocation = be.edgePoint.determineGraphLocation();
if (station != null && graphLocation != null) {
station.name = name;
Create.RAILWAYS.sync.pointAdded(graphLocation.graph, station);
Create.RAILWAYS.markTracksDirty();
}
}
if (!(blockState.getBlock() instanceof StationBlock)) if (!(blockState.getBlock() instanceof StationBlock))
return; return;
@ -132,89 +118,17 @@ public class StationEditPacket extends BlockEntityConfigurationPacket<StationBlo
assemblyComplete = be.getStation() != null && be.getStation() assemblyComplete = be.getStation() != null && be.getStation()
.getPresentTrain() != null; .getPresentTrain() != null;
} else { } else {
if (disassembleAndEnterMode(player, be)) if (be.tryDisassembleTrain(player) && be.tryEnterAssemblyMode())
be.refreshAssemblyInfo(); be.refreshAssemblyInfo();
} }
if (!assemblyComplete) if (!assemblyComplete)
return; return;
} }
if (isAssemblyMode == assemblyMode)
return;
BlockState newState = blockState.cycle(StationBlock.ASSEMBLING); if (assemblyMode)
Boolean nowAssembling = newState.getValue(StationBlock.ASSEMBLING); be.enterAssemblyMode(player);
else
if (nowAssembling) { be.exitAssemblyMode();
if (!disassembleAndEnterMode(player, be))
return;
} else {
be.cancelAssembly();
}
level.setBlock(blockPos, newState, 3);
be.refreshBlockState();
if (nowAssembling)
be.refreshAssemblyInfo();
GlobalStation station = be.getStation();
GraphLocation graphLocation = be.edgePoint.determineGraphLocation();
if (station != null && graphLocation != null) {
station.assembling = nowAssembling;
Create.RAILWAYS.sync.pointAdded(graphLocation.graph, station);
Create.RAILWAYS.markTracksDirty();
if (nowAssembling)
for (Train train : Create.RAILWAYS.sided(level).trains.values()) {
if (train.navigation.destination != station)
continue;
GlobalStation preferredDestination = train.runtime.startCurrentInstruction();
if (preferredDestination != null)
train.navigation.startNavigation(preferredDestination, Double.MAX_VALUE, false);
else
train.navigation.startNavigation(station, Double.MAX_VALUE, false);
}
}
}
private void scheduleDropRequested(ServerPlayer sender, StationBlockEntity be) {
GlobalStation station = be.getStation();
if (station == null)
return;
Train train = station.getPresentTrain();
if (train == null)
return;
ItemStack schedule = train.runtime.returnSchedule();
dropSchedule(sender, be, schedule);
}
private boolean disassembleAndEnterMode(ServerPlayer sender, StationBlockEntity be) {
GlobalStation station = be.getStation();
if (station != null) {
Train train = station.getPresentTrain();
BlockPos trackPosition = be.edgePoint.getGlobalPosition();
ItemStack schedule = train == null ? ItemStack.EMPTY : train.runtime.returnSchedule();
if (train != null && !train.disassemble(be.getAssemblyDirection(), trackPosition.above()))
return false;
dropSchedule(sender, be, schedule);
}
return be.tryEnterAssemblyMode();
}
private void dropSchedule(ServerPlayer sender, StationBlockEntity be, ItemStack schedule) {
if (schedule.isEmpty())
return;
if (sender.getMainHandItem()
.isEmpty()) {
sender.getInventory()
.placeItemBackInInventory(schedule);
return;
}
Vec3 v = VecHelper.getCenterOf(be.getBlockPos());
ItemEntity itemEntity = new ItemEntity(be.getLevel(), v.x, v.y, v.z, schedule);
itemEntity.setDeltaMovement(Vec3.ZERO);
be.getLevel()
.addFreshEntity(itemEntity);
} }
@Override @Override

View file

@ -359,6 +359,8 @@ public class StationScreen extends AbstractStationScreen {
@Override @Override
public void removed() { public void removed() {
super.removed(); super.removed();
if (nameBox == null || trainNameBox == null)
return;
AllPackets.getChannel() AllPackets.getChannel()
.sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), switchingToAssemblyMode, .sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), switchingToAssemblyMode,
nameBox.getValue(), doorControl)); nameBox.getValue(), doorControl));

View file

@ -25,6 +25,8 @@ public interface IScheduleInput {
public abstract CompoundTag getData(); public abstract CompoundTag getData();
public abstract void setData(CompoundTag data);
public default int slotsTargeted() { public default int slotsTargeted() {
return 0; return 0;
} }

View file

@ -15,6 +15,12 @@ public abstract class ScheduleDataEntry implements IScheduleInput {
return data; return data;
} }
@Override
public void setData(CompoundTag data) {
this.data = data;
readAdditional(data);
}
protected void writeAdditional(CompoundTag tag) {}; protected void writeAdditional(CompoundTag tag) {};
protected void readAdditional(CompoundTag tag) {}; protected void readAdditional(CompoundTag tag) {};

View file

@ -68,7 +68,8 @@ public class FluidThresholdCondition extends CargoThresholdCondition {
@Override @Override
protected void readAdditional(CompoundTag tag) { protected void readAdditional(CompoundTag tag) {
super.readAdditional(tag); super.readAdditional(tag);
compareStack = ItemStack.of(tag.getCompound("Bucket")); if (tag.contains("Bucket"))
compareStack = ItemStack.of(tag.getCompound("Bucket"));
} }
@Override @Override

View file

@ -69,7 +69,8 @@ public class ItemThresholdCondition extends CargoThresholdCondition {
@Override @Override
protected void readAdditional(CompoundTag tag) { protected void readAdditional(CompoundTag tag) {
super.readAdditional(tag); super.readAdditional(tag);
stack = ItemStack.of(tag.getCompound("Item")); if (tag.contains("Item"))
stack = ItemStack.of(tag.getCompound("Item"));
} }
@Override @Override

View file

@ -107,7 +107,8 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition {
@Override @Override
protected void readAdditional(CompoundTag tag) { protected void readAdditional(CompoundTag tag) {
freq = Couple.deserializeEach(tag.getList("Frequency", Tag.TAG_COMPOUND), c -> Frequency.of(ItemStack.of(c))); if (tag.contains("Frequency"))
freq = Couple.deserializeEach(tag.getList("Frequency", Tag.TAG_COMPOUND), c -> Frequency.of(ItemStack.of(c)));
} }
@Override @Override

View file

@ -23,9 +23,10 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry {
public final CompoundTag write() { public final CompoundTag write() {
CompoundTag tag = new CompoundTag(); CompoundTag tag = new CompoundTag();
CompoundTag dataCopy = data.copy();
writeAdditional(dataCopy);
tag.putString("Id", getId().toString()); tag.putString("Id", getId().toString());
tag.put("Data", data.copy()); tag.put("Data", dataCopy);
writeAdditional(tag);
return tag; return tag;
} }
@ -43,8 +44,11 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry {
} }
ScheduleWaitCondition condition = supplier.get(); ScheduleWaitCondition condition = supplier.get();
condition.data = tag.getCompound("Data"); // Left around for migration purposes. Data added in writeAdditional has moved into the "Data" tag
condition.readAdditional(tag); condition.readAdditional(tag);
CompoundTag data = tag.getCompound("Data");
condition.readAdditional(data);
condition.data = data;
return condition; return condition;
} }

View file

@ -16,9 +16,10 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry {
public final CompoundTag write() { public final CompoundTag write() {
CompoundTag tag = new CompoundTag(); CompoundTag tag = new CompoundTag();
CompoundTag dataCopy = data.copy();
writeAdditional(dataCopy);
tag.putString("Id", getId().toString()); tag.putString("Id", getId().toString());
tag.put("Data", data.copy()); tag.put("Data", dataCopy);
writeAdditional(tag);
return tag; return tag;
} }
@ -36,8 +37,11 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry {
} }
ScheduleInstruction scheduleDestination = supplier.get(); ScheduleInstruction scheduleDestination = supplier.get();
scheduleDestination.data = tag.getCompound("Data"); // Left around for migration purposes. Data added in writeAdditional has moved into the "Data" tag
scheduleDestination.readAdditional(tag); scheduleDestination.readAdditional(tag);
CompoundTag data = tag.getCompound("Data");
scheduleDestination.readAdditional(data);
scheduleDestination.data = data;
return scheduleDestination; return scheduleDestination;
} }

View file

@ -0,0 +1,75 @@
package com.simibubi.create.content.schematics;
import com.simibubi.create.Create;
import com.simibubi.create.content.schematics.item.SchematicAndQuillItem;
import com.simibubi.create.foundation.utility.FilesHelper;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLPaths;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class SchematicExport {
public static final Path SCHEMATICS = FMLPaths.GAMEDIR.get().resolve("schematics");
/**
* Save a schematic to a file from a world.
* @param dir the directory the schematic will be created in
* @param fileName the ideal name of the schematic, may not be the name of the created file
* @param overwrite whether overwriting an existing schematic is allowed
* @param level the level where the schematic structure is placed
* @param first the first corner of the schematic area
* @param second the second corner of the schematic area
* @return a SchematicExportResult, or null if an error occurred.
*/
@Nullable
public static SchematicExportResult saveSchematic(Path dir, String fileName, boolean overwrite, Level level, BlockPos first, BlockPos second) {
BoundingBox bb = BoundingBox.fromCorners(first, second);
BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ());
BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan());
StructureTemplate structure = new StructureTemplate();
structure.fillFromWorld(level, origin, bounds, true, Blocks.AIR);
CompoundTag data = structure.save(new CompoundTag());
SchematicAndQuillItem.replaceStructureVoidWithAir(data);
SchematicAndQuillItem.clampGlueBoxes(level, new AABB(origin, origin.offset(bounds)), data);
if (fileName.isEmpty())
fileName = Lang.translateDirect("schematicAndQuill.fallbackName").getString();
if (!overwrite)
fileName = FilesHelper.findFirstValidFilename(fileName, dir, "nbt");
if (!fileName.endsWith(".nbt"))
fileName += ".nbt";
Path file = dir.resolve(fileName).toAbsolutePath();
try {
Files.createDirectories(dir);
boolean overwritten = Files.deleteIfExists(file);
try (OutputStream out = Files.newOutputStream(file, StandardOpenOption.CREATE)) {
NbtIo.writeCompressed(data, out);
}
return new SchematicExportResult(file, dir, fileName, overwritten, origin, bounds);
} catch (IOException e) {
Create.LOGGER.error("An error occurred while saving schematic [" + fileName + "]", e);
return null;
}
}
public record SchematicExportResult(Path file, Path dir, String fileName, boolean overwritten, BlockPos origin, BlockPos bounds) {
}
}

View file

@ -8,6 +8,7 @@ import java.nio.file.Paths;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -16,8 +17,8 @@ import java.util.stream.Stream;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult;
import com.simibubi.create.content.schematics.block.SchematicTableBlockEntity; import com.simibubi.create.content.schematics.block.SchematicTableBlockEntity;
import com.simibubi.create.content.schematics.item.SchematicAndQuillItem;
import com.simibubi.create.content.schematics.item.SchematicItem; import com.simibubi.create.content.schematics.item.SchematicItem;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.config.CSchematics; import com.simibubi.create.foundation.config.CSchematics;
@ -25,18 +26,14 @@ import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.FilesHelper; import com.simibubi.create.foundation.utility.FilesHelper;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.ChatFormatting;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.phys.AABB;
public class ServerSchematicLoader { public class ServerSchematicLoader {
@ -284,10 +281,9 @@ public class ServerSchematicLoader {
public void handleInstantSchematic(ServerPlayer player, String schematic, Level world, BlockPos pos, public void handleInstantSchematic(ServerPlayer player, String schematic, Level world, BlockPos pos,
BlockPos bounds) { BlockPos bounds) {
String playerPath = getSchematicPath() + "/" + player.getGameProfile() String playerName = player.getGameProfile().getName();
.getName(); String playerPath = getSchematicPath() + "/" + playerName;
String playerSchematicId = player.getGameProfile() String playerSchematicId = playerName + "/" + schematic;
.getName() + "/" + schematic;
FilesHelper.createFolderIfMissing(playerPath); FilesHelper.createFolderIfMissing(playerPath);
// Unsupported Format // Unsupported Format
@ -310,43 +306,43 @@ public class ServerSchematicLoader {
if (!AllItems.SCHEMATIC_AND_QUILL.isIn(player.getMainHandItem())) if (!AllItems.SCHEMATIC_AND_QUILL.isIn(player.getMainHandItem()))
return; return;
// if there's too many schematics, delete oldest
Path playerSchematics = Paths.get(playerPath);
if (!tryDeleteOldestSchematic(playerSchematics))
return;
SchematicExportResult result = SchematicExport.saveSchematic(
playerSchematics, schematic, true,
world, pos, pos.offset(bounds).offset(-1, -1, -1)
);
if (result != null)
player.setItemInHand(InteractionHand.MAIN_HAND, SchematicItem.create(schematic, playerName));
else Lang.translate("schematicAndQuill.instant_failed")
.style(ChatFormatting.RED)
.sendStatus(player);
}
private boolean tryDeleteOldestSchematic(Path dir) {
try (Stream<Path> stream = Files.list(dir)) {
List<Path> files = stream.toList();
if (files.size() < getConfig().maxSchematics.get())
return true;
Optional<Path> oldest = files.stream().min(Comparator.comparingLong(this::getLastModifiedTime));
Files.delete(oldest.orElseThrow());
return true;
} catch (IOException | IllegalStateException e) {
Create.LOGGER.error("Error deleting oldest schematic", e);
return false;
}
}
private long getLastModifiedTime(Path file) {
try { try {
// Delete schematic with same name return Files.getLastModifiedTime(file).toMillis();
Files.deleteIfExists(path);
// Too many Schematics
long count;
try (Stream<Path> list = Files.list(Paths.get(playerPath))) {
count = list.count();
}
if (count >= getConfig().maxSchematics.get()) {
Stream<Path> list2 = Files.list(Paths.get(playerPath));
Optional<Path> lastFilePath = list2.filter(f -> !Files.isDirectory(f))
.min(Comparator.comparingLong(f -> f.toFile()
.lastModified()));
list2.close();
if (lastFilePath.isPresent())
Files.deleteIfExists(lastFilePath.get());
}
StructureTemplate t = new StructureTemplate();
t.fillFromWorld(world, pos, bounds, true, Blocks.AIR);
try (OutputStream outputStream = Files.newOutputStream(path)) {
CompoundTag nbttagcompound = t.save(new CompoundTag());
SchematicAndQuillItem.replaceStructureVoidWithAir(nbttagcompound);
SchematicAndQuillItem.clampGlueBoxes(world, new AABB(pos, pos.offset(bounds)), nbttagcompound);
NbtIo.writeCompressed(nbttagcompound, outputStream);
player.setItemInHand(InteractionHand.MAIN_HAND, SchematicItem.create(schematic, player.getGameProfile()
.getName()));
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) { } catch (IOException e) {
Create.LOGGER.error("Exception Thrown in direct Schematic Upload: " + playerSchematicId); Create.LOGGER.error("Error getting modification time of file " + file.getFileName(), e);
e.printStackTrace(); throw new IllegalStateException(e);
} }
} }

View file

@ -1,13 +1,8 @@
package com.simibubi.create.content.schematics.client; package com.simibubi.create.content.schematics.client;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.apache.commons.io.IOUtils;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.AllKeys; import com.simibubi.create.AllKeys;
@ -15,34 +10,29 @@ import com.simibubi.create.AllSpecialTextures;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.CreateClient; import com.simibubi.create.CreateClient;
import com.simibubi.create.content.schematics.ClientSchematicLoader; import com.simibubi.create.content.schematics.ClientSchematicLoader;
import com.simibubi.create.content.schematics.item.SchematicAndQuillItem; import com.simibubi.create.content.schematics.SchematicExport;
import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult;
import com.simibubi.create.content.schematics.packet.InstantSchematicPacket; import com.simibubi.create.content.schematics.packet.InstantSchematicPacket;
import com.simibubi.create.foundation.gui.ScreenOpener; import com.simibubi.create.foundation.gui.ScreenOpener;
import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.AnimationTickHolder; import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.FilesHelper;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.RaycastHelper; import com.simibubi.create.foundation.utility.RaycastHelper;
import com.simibubi.create.foundation.utility.RaycastHelper.PredicateTraceResult; import com.simibubi.create.foundation.utility.RaycastHelper.PredicateTraceResult;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.outliner.Outliner; import com.simibubi.create.foundation.utility.outliner.Outliner;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.Direction.AxisDirection; import net.minecraft.core.Direction.AxisDirection;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult.Type; import net.minecraft.world.phys.HitResult.Type;
@ -52,8 +42,8 @@ public class SchematicAndQuillHandler {
private Object outlineSlot = new Object(); private Object outlineSlot = new Object();
private BlockPos firstPos; public BlockPos firstPos;
private BlockPos secondPos; public BlockPos secondPos;
private BlockPos selectedPos; private BlockPos selectedPos;
private Direction selectedFace; private Direction selectedFace;
private int range = 10; private int range = 10;
@ -212,58 +202,31 @@ public class SchematicAndQuillHandler {
} }
public void saveSchematic(String string, boolean convertImmediately) { public void saveSchematic(String string, boolean convertImmediately) {
StructureTemplate t = new StructureTemplate(); SchematicExportResult result = SchematicExport.saveSchematic(
BoundingBox bb = BoundingBox.fromCorners(firstPos, secondPos); SchematicExport.SCHEMATICS, string, false,
BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ()); Minecraft.getInstance().level, firstPos, secondPos
BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan()); );
Level level = Minecraft.getInstance().level; LocalPlayer player = Minecraft.getInstance().player;
if (result == null) {
t.fillFromWorld(level, origin, bounds, true, Blocks.AIR); Lang.translate("schematicAndQuill.failed")
.style(ChatFormatting.RED)
if (string.isEmpty()) .sendStatus(player);
string = Lang.translateDirect("schematicAndQuill.fallbackName") return;
.getString();
String folderPath = "schematics";
FilesHelper.createFolderIfMissing(folderPath);
String filename = FilesHelper.findFirstValidFilename(string, folderPath, "nbt");
String filepath = folderPath + "/" + filename;
Path path = Paths.get(filepath);
OutputStream outputStream = null;
try {
outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE);
CompoundTag nbttagcompound = t.save(new CompoundTag());
SchematicAndQuillItem.replaceStructureVoidWithAir(nbttagcompound);
SchematicAndQuillItem.clampGlueBoxes(level, new AABB(origin, origin.offset(bounds)), nbttagcompound);
NbtIo.writeCompressed(nbttagcompound, outputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null)
IOUtils.closeQuietly(outputStream);
} }
Path file = result.file();
Lang.translate("schematicAndQuill.saved", file)
.sendStatus(player);
firstPos = null; firstPos = null;
secondPos = null; secondPos = null;
LocalPlayer player = Minecraft.getInstance().player;
Lang.translate("schematicAndQuill.saved", filepath)
.sendStatus(player);
if (!convertImmediately) if (!convertImmediately)
return; return;
if (!Files.exists(path)) {
Create.LOGGER.error("Missing Schematic file: " + path.toString());
return;
}
try { try {
if (!ClientSchematicLoader.validateSizeLimitation(Files.size(path))) if (!ClientSchematicLoader.validateSizeLimitation(Files.size(file)))
return; return;
AllPackets.getChannel().sendToServer(new InstantSchematicPacket(filename, origin, bounds)); AllPackets.getChannel()
.sendToServer(new InstantSchematicPacket(result.fileName(), result.origin(), result.bounds()));
} catch (IOException e) { } catch (IOException e) {
Create.LOGGER.error("Error finding Schematic file: " + path.toString()); Create.LOGGER.error("Error instantly uploading Schematic file: " + file, e);
e.printStackTrace();
return;
} }
} }

View file

@ -109,5 +109,4 @@ public class SchematicPromptScreen extends AbstractSimiScreen {
CreateClient.SCHEMATIC_AND_QUILL_HANDLER.saveSchematic(nameField.getValue(), convertImmediately); CreateClient.SCHEMATIC_AND_QUILL_HANDLER.saveSchematic(nameField.getValue(), convertImmediately);
onClose(); onClose();
} }
} }

View file

@ -295,14 +295,14 @@ public class ClientEvents {
if (AllFluids.CHOCOLATE.get() if (AllFluids.CHOCOLATE.get()
.isSame(fluid)) { .isSame(fluid)) {
event.scaleFarPlaneDistance(1f / 32f); event.scaleFarPlaneDistance(1f / 32f * AllConfigs.CLIENT.chocolateTransparencyMultiplier.getF());
event.setCanceled(true); event.setCanceled(true);
return; return;
} }
if (AllFluids.HONEY.get() if (AllFluids.HONEY.get()
.isSame(fluid)) { .isSame(fluid)) {
event.scaleFarPlaneDistance(1f / 8f); event.scaleFarPlaneDistance(1f / 8f * AllConfigs.CLIENT.honeyTransparencyMultiplier.getF());
event.setCanceled(true); event.setCanceled(true);
return; return;
} }

View file

@ -11,6 +11,8 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.FMLLoader;
public class AllCommands { public class AllCommands {
@ -20,7 +22,7 @@ public class AllCommands {
LiteralCommandNode<CommandSourceStack> util = buildUtilityCommands(); LiteralCommandNode<CommandSourceStack> util = buildUtilityCommands();
LiteralCommandNode<CommandSourceStack> createRoot = dispatcher.register(Commands.literal("create") LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("create")
.requires(cs -> cs.hasPermission(0)) .requires(cs -> cs.hasPermission(0))
// general purpose // general purpose
.then(new ToggleDebugCommand().register()) .then(new ToggleDebugCommand().register())
@ -38,8 +40,12 @@ public class AllCommands {
.then(GlueCommand.register()) .then(GlueCommand.register())
// utility // utility
.then(util) .then(util);
);
if (!FMLLoader.isProduction() && FMLLoader.getDist() == Dist.CLIENT)
root.then(CreateTestCommand.register());
LiteralCommandNode<CommandSourceStack> createRoot = dispatcher.register(root);
createRoot.addChild(buildRedirect("u", util)); createRoot.addChild(buildRedirect("u", util));

View file

@ -0,0 +1,111 @@
package com.simibubi.create.foundation.command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.simibubi.create.CreateClient;
import com.simibubi.create.content.schematics.SchematicExport;
import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult;
import com.simibubi.create.content.schematics.client.SchematicAndQuillHandler;
import com.simibubi.create.foundation.utility.Components;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerLevel;
import net.minecraftforge.fml.loading.FMLPaths;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
/**
* This command allows for quick exporting of GameTests.
* It is only registered in a client development environment. It is not safe in production or multiplayer.
*/
public class CreateTestCommand {
private static final Path gametests = FMLPaths.GAMEDIR.get()
.getParent()
.resolve("src/main/resources/data/create/structures/gametest")
.toAbsolutePath();
public static ArgumentBuilder<CommandSourceStack, ?> register() {
return literal("test")
.then(literal("export")
.then(argument("path", StringArgumentType.greedyString())
.suggests(CreateTestCommand::getSuggestions)
.executes(ctx -> handleExport(
ctx.getSource(),
ctx.getSource().getLevel(),
StringArgumentType.getString(ctx, "path")
))
)
);
}
private static int handleExport(CommandSourceStack source, ServerLevel level, String path) {
SchematicAndQuillHandler handler = CreateClient.SCHEMATIC_AND_QUILL_HANDLER;
if (handler.firstPos == null || handler.secondPos == null) {
source.sendFailure(Components.literal("You must select an area with the Schematic and Quill first."));
return 0;
}
SchematicExportResult result = SchematicExport.saveSchematic(
gametests, path, true,
level, handler.firstPos, handler.secondPos
);
if (result == null)
source.sendFailure(Components.literal("Failed to export, check logs").withStyle(ChatFormatting.RED));
else {
sendSuccess(source, "Successfully exported test!", ChatFormatting.GREEN);
sendSuccess(source, "Overwritten: " + result.overwritten(), ChatFormatting.AQUA);
sendSuccess(source, "File: " + result.file(), ChatFormatting.GRAY);
}
return 0;
}
private static void sendSuccess(CommandSourceStack source, String text, ChatFormatting color) {
source.sendSuccess(Components.literal(text).withStyle(color), true);
}
// find existing tests and folders for autofill
private static CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context,
SuggestionsBuilder builder) throws CommandSyntaxException {
String path = builder.getRemaining();
if (!path.contains("/") || path.contains(".."))
return findInDir(gametests, builder);
int lastSlash = path.lastIndexOf("/");
Path subDir = gametests.resolve(path.substring(0, lastSlash));
if (Files.exists(subDir))
findInDir(subDir, builder);
return builder.buildFuture();
}
private static CompletableFuture<Suggestions> findInDir(Path dir, SuggestionsBuilder builder) {
try (Stream<Path> paths = Files.list(dir)) {
paths.filter(p -> Files.isDirectory(p) || p.toString().endsWith(".nbt"))
.forEach(path -> {
String file = path.toString()
.replaceAll("\\\\", "/")
.substring(gametests.toString().length() + 1);
if (Files.isDirectory(path))
file += "/";
builder.suggest(file);
});
} catch (IOException e) {
throw new RuntimeException(e);
}
return builder.buildFuture();
}
}

View file

@ -31,7 +31,14 @@ public class CClient extends ConfigBase {
public final ConfigInt ingameMenuConfigButtonOffsetX = i(-4, Integer.MIN_VALUE, Integer.MAX_VALUE, "ingameMenuConfigButtonOffsetX", public final ConfigInt ingameMenuConfigButtonOffsetX = i(-4, Integer.MIN_VALUE, Integer.MAX_VALUE, "ingameMenuConfigButtonOffsetX",
Comments.ingameMenuConfigButtonOffsetX); Comments.ingameMenuConfigButtonOffsetX);
public final ConfigBool ignoreFabulousWarning = b(false, "ignoreFabulousWarning", public final ConfigBool ignoreFabulousWarning = b(false, "ignoreFabulousWarning",
Comments.ignoreFabulousWarning); Comments.ignoreFabulousWarning);
// custom fluid fog
public final ConfigGroup fluidFogSettings = group(1, "fluidFogSettings", Comments.fluidFogSettings);
public final ConfigFloat honeyTransparencyMultiplier =
f(1, .125f, 256, "honey", Comments.honeyTransparencyMultiplier);
public final ConfigFloat chocolateTransparencyMultiplier =
f(1, .125f, 256, "chocolate", Comments.chocolateTransparencyMultiplier);
//overlay group //overlay group
public final ConfigGroup overlay = group(1, "goggleOverlay", public final ConfigGroup overlay = group(1, "goggleOverlay",
@ -149,6 +156,9 @@ public class CClient extends ConfigBase {
static String mountedZoomMultiplier = "How far away the Camera should zoom when seated on a train"; static String mountedZoomMultiplier = "How far away the Camera should zoom when seated on a train";
static String showTrackGraphOnF3 = "Display nodes and edges of a Railway Network while f3 debug mode is active"; static String showTrackGraphOnF3 = "Display nodes and edges of a Railway Network while f3 debug mode is active";
static String showExtendedTrackGraphOnF3 = "Additionally display materials of a Rail Network while f3 debug mode is active"; static String showExtendedTrackGraphOnF3 = "Additionally display materials of a Rail Network while f3 debug mode is active";
static String fluidFogSettings = "Configure your vision range when submerged in Create's custom fluids";
static String honeyTransparencyMultiplier = "The vision range through honey will be multiplied by this factor";
static String chocolateTransparencyMultiplier = "The vision range though chocolate will be multiplied by this factor";
} }
} }

View file

@ -8,6 +8,7 @@ public class CLogistics extends ConfigBase {
public final ConfigInt linkRange = i(256, 1, "linkRange", Comments.linkRange); public final ConfigInt linkRange = i(256, 1, "linkRange", Comments.linkRange);
public final ConfigInt displayLinkRange = i(64, 1, "displayLinkRange", Comments.displayLinkRange); public final ConfigInt displayLinkRange = i(64, 1, "displayLinkRange", Comments.displayLinkRange);
public final ConfigInt vaultCapacity = i(20, 1, "vaultCapacity", Comments.vaultCapacity); public final ConfigInt vaultCapacity = i(20, 1, "vaultCapacity", Comments.vaultCapacity);
public final ConfigInt brassTunnelTimer = i(10, 1, 10, "brassTunnelTimer", Comments.brassTunnelTimer);
@Override @Override
public String getName() { public String getName() {
@ -24,6 +25,7 @@ public class CLogistics extends ConfigBase {
"The amount of ticks a portable storage interface waits for transfers until letting contraptions move along."; "The amount of ticks a portable storage interface waits for transfers until letting contraptions move along.";
static String mechanicalArmRange = "Maximum distance in blocks a Mechanical Arm can reach across."; static String mechanicalArmRange = "Maximum distance in blocks a Mechanical Arm can reach across.";
static String vaultCapacity = "The total amount of stacks a vault can hold per block in size."; static String vaultCapacity = "The total amount of stacks a vault can hold per block in size.";
static String brassTunnelTimer = "The amount of ticks a brass tunnel waits between distributions.";
} }
} }

View file

@ -189,7 +189,10 @@ public enum AllGuiTextures implements ScreenElement {
TRAIN_PROMPT("widgets", 0, 230, 256, 16), TRAIN_PROMPT("widgets", 0, 230, 256, 16),
// PlacementIndicator // PlacementIndicator
PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256); PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256),
// ComputerCraft
COMPUTER("computer", 200, 102);
; ;

View file

@ -0,0 +1,53 @@
package com.simibubi.create.foundation.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.gametest.framework.GameTestRunner;
import net.minecraft.gametest.framework.GameTestServer;
import net.minecraft.server.Main;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.Collection;
@Mixin(Main.class)
public class MainMixin {
/**
* Forge completely bypasses vanilla's
* {@link GameTestServer#create(Thread, LevelStorageAccess, PackRepository, Collection, BlockPos)},
* which causes tests to generate at bedrock level in a regular world. This causes interference
* (ex. darkness, liquids, gravel) that makes tests fail and act inconsistently. Replacing the server Forge
* makes with one made by vanilla's factory causes tests to run on a superflat, as they should.
* <p>
* The system property 'create.useOriginalGametestServer' may be set to true to avoid this behavior.
* This may be desirable for other mods which pull Create into their development environments.
*/
@ModifyVariable(
method = "lambda$main$5",
at = @At(
value = "STORE",
ordinal = 0
),
require = 0 // don't crash if this fails
)
private static MinecraftServer create$correctlyInitializeGametestServer(MinecraftServer original) {
if (original instanceof GameTestServer && !Boolean.getBoolean("create.useOriginalGametestServer")) {
return GameTestServer.create(
original.getRunningThread(),
original.storageSource,
original.getPackRepository(),
GameTestRunner.groupTestsIntoBatches(GameTestRegistry.getAllTestFunctions()),
BlockPos.ZERO
);
}
return original;
}
}

View file

@ -0,0 +1,46 @@
package com.simibubi.create.foundation.mixin;
import com.simibubi.create.gametest.infrastructure.CreateTestFunction;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.gametest.framework.MultipleTestTracker;
import net.minecraft.gametest.framework.TestCommand;
import net.minecraft.gametest.framework.TestFunction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import javax.annotation.Nullable;
@Mixin(TestCommand.class)
public class TestCommandMixin {
@Redirect(
method = "runTest(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/gametest/framework/MultipleTestTracker;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/gametest/framework/GameTestRegistry;getTestFunction(Ljava/lang/String;)Lnet/minecraft/gametest/framework/TestFunction;"
),
require = 0 // don't crash if this fails. non-critical
)
private static TestFunction create$getCorrectTestFunction(String testName,
ServerLevel level, BlockPos pos, @Nullable MultipleTestTracker tracker) {
StructureBlockEntity be = (StructureBlockEntity) level.getBlockEntity(pos);
CompoundTag data = be.getTileData();
if (!data.contains("CreateTestFunction", Tag.TAG_STRING))
return GameTestRegistry.getTestFunction(testName);
String name = data.getString("CreateTestFunction");
CreateTestFunction function = CreateTestFunction.NAMES_TO_FUNCTIONS.get(name);
if (function == null)
throw new IllegalStateException("Structure block has CreateTestFunction attached, but test [" + name + "] doesn't exist");
return function;
}
}

View file

@ -0,0 +1,17 @@
package com.simibubi.create.foundation.mixin.accessor;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.GameTestInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(GameTestHelper.class)
public interface GameTestHelperAccessor {
@Accessor
GameTestInfo getTestInfo();
@Accessor
boolean getFinalCheckAdded();
@Accessor
void setFinalCheckAdded(boolean value);
}

View file

@ -8,6 +8,7 @@ import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.compat.computercraft.AttachedComputerPacket;
import com.simibubi.create.content.contraptions.components.actors.controls.ContraptionDisableActorPacket; import com.simibubi.create.content.contraptions.components.actors.controls.ContraptionDisableActorPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionBlockChangedPacket; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionBlockChangedPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionColliderLockPacket; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionColliderLockPacket;
@ -202,6 +203,7 @@ public enum AllPackets {
SET_FIRE_IMMUNE(NetheriteDivingHandler.SetFireImmunePacket.class, NetheriteDivingHandler.SetFireImmunePacket::new, SET_FIRE_IMMUNE(NetheriteDivingHandler.SetFireImmunePacket.class, NetheriteDivingHandler.SetFireImmunePacket::new,
PLAY_TO_CLIENT), PLAY_TO_CLIENT),
CONTRAPTION_COLLIDER_LOCK(ContraptionColliderLockPacket.class, ContraptionColliderLockPacket::new, PLAY_TO_CLIENT), CONTRAPTION_COLLIDER_LOCK(ContraptionColliderLockPacket.class, ContraptionColliderLockPacket::new, PLAY_TO_CLIENT),
ATTACHED_COMPUTER(AttachedComputerPacket.class, AttachedComputerPacket::new, PLAY_TO_CLIENT),
; ;

View file

@ -3,6 +3,7 @@ package com.simibubi.create.foundation.ponder.content;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.content.logistics.trains.TrackMaterial;
import com.simibubi.create.content.logistics.trains.track.TrackBlock; import com.simibubi.create.content.logistics.trains.track.TrackBlock;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
@ -23,7 +24,9 @@ import com.simibubi.create.foundation.ponder.content.trains.TrainSignalScenes;
import com.simibubi.create.foundation.ponder.content.trains.TrainStationScenes; import com.simibubi.create.foundation.ponder.content.trains.TrainStationScenes;
import com.tterrag.registrate.util.entry.BlockEntry; import com.tterrag.registrate.util.entry.BlockEntry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject; import net.minecraftforge.registries.RegistryObject;
@ -596,6 +599,12 @@ public class PonderIndex {
.add(Blocks.COMMAND_BLOCK) .add(Blocks.COMMAND_BLOCK)
.add(Blocks.TARGET); .add(Blocks.TARGET);
Mods.COMPUTERCRAFT.executeIfInstalled(() -> () -> {
Block computer = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(Mods.COMPUTERCRAFT.asId(), "computer_advanced"));
if (computer != null)
PonderRegistry.TAGS.forTag(PonderTag.DISPLAY_SOURCES).add(computer);
});
PonderRegistry.TAGS.forTag(PonderTag.DISPLAY_TARGETS) PonderRegistry.TAGS.forTag(PonderTag.DISPLAY_TARGETS)
.add(AllBlocks.ORANGE_NIXIE_TUBE) .add(AllBlocks.ORANGE_NIXIE_TUBE)
.add(AllBlocks.DISPLAY_BOARD) .add(AllBlocks.DISPLAY_BOARD)

View file

@ -5,6 +5,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
@ -27,15 +28,15 @@ public class FilesHelper {
} }
} }
public static String findFirstValidFilename(String name, String folderPath, String extension) { public static String findFirstValidFilename(String name, Path folderPath, String extension) {
int index = 0; int index = 0;
String filename; String filename;
String filepath; Path filepath;
do { do {
filename = slug(name) + ((index == 0) ? "" : "_" + index) + "." + extension; filename = slug(name) + ((index == 0) ? "" : "_" + index) + "." + extension;
index++; index++;
filepath = folderPath + "/" + filename; filepath = folderPath.resolve(filename);
} while (Files.exists(Paths.get(filepath))); } while (Files.exists(filepath));
return filename; return filename;
} }

View file

@ -0,0 +1,46 @@
package com.simibubi.create.foundation.utility;
import java.util.Locale;
public class StringHelper {
public static String snakeCaseToCamelCase(String text) {
StringBuilder builder = new StringBuilder();
builder.append(text.substring(0, 1).toUpperCase(Locale.ROOT));
for (int i = 1; i < text.length(); i++) {
int j = text.indexOf('_', i);
if (j == -1) {
builder.append(text.substring(i));
break;
}
builder.append(text.substring(i, j).toLowerCase(Locale.ROOT));
builder.append(text.substring(j + 1, j + 2).toUpperCase(Locale.ROOT));
i = j + 1;
}
return builder.toString();
}
public static String camelCaseToSnakeCase(String text) {
StringBuilder builder = new StringBuilder();
for (char c : text.toCharArray()) {
if (Character.isUpperCase(c)) {
builder.append('_');
builder.append(Character.toLowerCase(c));
} else {
builder.append(c);
}
}
if (builder.length() > 0 && builder.charAt(0) == '_')
builder.deleteCharAt(0);
return builder.toString();
}
}

View file

@ -0,0 +1,39 @@
package com.simibubi.create.gametest;
import java.util.Collection;
import com.simibubi.create.gametest.infrastructure.CreateTestFunction;
import com.simibubi.create.gametest.tests.TestContraptions;
import com.simibubi.create.gametest.tests.TestFluids;
import com.simibubi.create.gametest.tests.TestItems;
import com.simibubi.create.gametest.tests.TestMisc;
import com.simibubi.create.gametest.tests.TestProcessing;
import net.minecraft.gametest.framework.GameTestGenerator;
import net.minecraft.gametest.framework.TestFunction;
import net.minecraftforge.event.RegisterGameTestsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
@EventBusSubscriber(bus = Bus.MOD)
public class CreateGameTests {
private static final Class<?>[] testHolders = {
TestContraptions.class,
TestFluids.class,
TestItems.class,
TestMisc.class,
TestProcessing.class
};
@SubscribeEvent
public static void registerTests(RegisterGameTestsEvent event) {
event.register(CreateGameTests.class);
}
@GameTestGenerator
public static Collection<TestFunction> generateTests() {
return CreateTestFunction.getTestsFrom(testHolders);
}
}

View file

@ -0,0 +1,15 @@
# Adding to GameTests
#### Adding Tests
All tests must be static, take a `CreateGameTestHelper`, return void, and be annotated with `@GameTest`.
Non-annotated methods will be ignored. The annotation must also specify a structure template.
Classes holding registered tests must be annotated with `GameTestGroup`.
#### Adding Groups/Classes
Added test classes must be added to the list in `CreateGameTests`. They must be annotated with
`@GameTestGroup` and given a structure path.
#### Exporting Structures
Structures can be quickly exported using the `/create test export` command (or `/c test export`).
Select an area with the Schematic and Quill, and run it to quickly export a test structure
directly to the correct directory.

View file

@ -0,0 +1,452 @@
package com.simibubi.create.gametest.infrastructure;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import com.simibubi.create.foundation.mixin.accessor.GameTestHelperAccessor;
import it.unimi.dsi.fastutil.objects.Object2LongArrayMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LeverBlock;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemHandlerHelper;
import org.jetbrains.annotations.Contract;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity.SelectionMode;
import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity;
import com.simibubi.create.foundation.item.ItemHelper;
import com.simibubi.create.foundation.tileEntity.IMultiTileContainer;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollOptionBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueBehaviour;
import com.simibubi.create.foundation.utility.RegisteredObjects;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.GameTestInfo;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
/**
* A helper class expanding the functionality of {@link GameTestHelper}.
* This class may replace the default helper parameter if a test is registered through {@link CreateTestFunction}.
*/
public class CreateGameTestHelper extends GameTestHelper {
public static final int TICKS_PER_SECOND = 20;
public static final int TEN_SECONDS = 10 * TICKS_PER_SECOND;
public static final int FIFTEEN_SECONDS = 15 * TICKS_PER_SECOND;
public static final int TWENTY_SECONDS = 20 * TICKS_PER_SECOND;
private CreateGameTestHelper(GameTestInfo testInfo) {
super(testInfo);
}
public static CreateGameTestHelper of(GameTestHelper original) {
GameTestHelperAccessor access = (GameTestHelperAccessor) original;
CreateGameTestHelper helper = new CreateGameTestHelper(access.getTestInfo());
//noinspection DataFlowIssue // accessor applied at runtime
GameTestHelperAccessor newAccess = (GameTestHelperAccessor) helper;
newAccess.setFinalCheckAdded(access.getFinalCheckAdded());
return helper;
}
// blocks
/**
* Flip the direction of any block with the {@link BlockStateProperties#FACING} property.
*/
public void flipBlock(BlockPos pos) {
BlockState original = getBlockState(pos);
if (!original.hasProperty(BlockStateProperties.FACING))
fail("FACING property not in block: " + Registry.BLOCK.getId(original.getBlock()));
Direction facing = original.getValue(BlockStateProperties.FACING);
BlockState reversed = original.setValue(BlockStateProperties.FACING, facing.getOpposite());
setBlock(pos, reversed);
}
public void assertNixiePower(BlockPos pos, int strength) {
NixieTubeTileEntity nixie = getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), pos);
int actualStrength = nixie.getRedstoneStrength();
if (actualStrength != strength)
fail("Expected nixie tube at %s to have power of %s, got %s".formatted(pos, strength, actualStrength));
}
/**
* Turn off a lever.
*/
public void powerLever(BlockPos pos) {
assertBlockPresent(Blocks.LEVER, pos);
if (!getBlockState(pos).getValue(LeverBlock.POWERED)) {
pullLever(pos);
}
}
/**
* Turn on a lever.
*/
public void unpowerLever(BlockPos pos) {
assertBlockPresent(Blocks.LEVER, pos);
if (getBlockState(pos).getValue(LeverBlock.POWERED)) {
pullLever(pos);
}
}
/**
* Set the {@link SelectionMode} of a belt tunnel at the given position.
* @param pos
* @param mode
*/
public void setTunnelMode(BlockPos pos, SelectionMode mode) {
ScrollValueBehaviour behavior = getBehavior(pos, ScrollOptionBehaviour.TYPE);
behavior.setValue(mode.ordinal());
}
// block entities
/**
* Get the block entity of the expected type. If the type does not match, this fails the test.
*/
public <T extends BlockEntity> T getBlockEntity(BlockEntityType<T> type, BlockPos pos) {
BlockEntity be = getBlockEntity(pos);
BlockEntityType<?> actualType = be == null ? null : be.getType();
if (actualType != type) {
String actualId = actualType == null ? "null" : RegisteredObjects.getKeyOrThrow(actualType).toString();
String error = "Expected block entity at pos [%s] with type [%s], got [%s]".formatted(
pos, RegisteredObjects.getKeyOrThrow(type), actualId
);
fail(error);
}
return (T) be;
}
/**
* Given any segment of an {@link IMultiTileContainer}, get the controller for it.
*/
public <T extends BlockEntity & IMultiTileContainer> T getControllerBlockEntity(BlockEntityType<T> type, BlockPos anySegment) {
T be = getBlockEntity(type, anySegment).getControllerTE();
if (be == null)
fail("Could not get block entity controller with type [%s] from pos [%s]".formatted(RegisteredObjects.getKeyOrThrow(type), anySegment));
return be;
}
/**
* Get the expected {@link TileEntityBehaviour} from the given position, failing if not present.
*/
public <T extends TileEntityBehaviour> T getBehavior(BlockPos pos, BehaviourType<T> type) {
T behavior = TileEntityBehaviour.get(getLevel(), absolutePos(pos), type);
if (behavior == null)
fail("Behavior at " + pos + " missing, expected " + type.getName());
return behavior;
}
// entities
/**
* Spawn an item entity at the given position with no velocity.
*/
public ItemEntity spawnItem(BlockPos pos, ItemStack stack) {
Vec3 spawn = Vec3.atCenterOf(absolutePos(pos));
ServerLevel level = getLevel();
ItemEntity item = new ItemEntity(level, spawn.x, spawn.y, spawn.z, stack, 0, 0, 0);
level.addFreshEntity(item);
return item;
}
/**
* Spawn item entities given an item and amount. The amount will be split into multiple entities if
* larger than the item's max stack size.
*/
public void spawnItems(BlockPos pos, Item item, int amount) {
while (amount > 0) {
int toSpawn = Math.min(amount, item.getMaxStackSize());
amount -= toSpawn;
ItemStack stack = new ItemStack(item, toSpawn);
spawnItem(pos, stack);
}
}
/**
* Get the first entity found at the given position.
*/
public <T extends Entity> T getFirstEntity(EntityType<T> type, BlockPos pos) {
List<T> list = getEntitiesBetween(type, pos.north().east().above(), pos.south().west().below());
if (list.isEmpty())
fail("No entities at pos: " + pos);
return list.get(0);
}
/**
* Get a list of all entities between two positions, inclusive.
*/
public <T extends Entity> List<T> getEntitiesBetween(EntityType<T> type, BlockPos pos1, BlockPos pos2) {
BoundingBox box = BoundingBox.fromCorners(absolutePos(pos1), absolutePos(pos2));
List<? extends T> entities = getLevel().getEntities(type, e -> box.isInside(e.blockPosition()));
return (List<T>) entities;
}
// transfer - fluids
public IFluidHandler fluidStorageAt(BlockPos pos) {
BlockEntity be = getBlockEntity(pos);
if (be == null)
fail("BlockEntity not present");
Optional<IFluidHandler> handler = be.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY).resolve();
if (handler.isEmpty())
fail("handler not present");
return handler.get();
}
/**
* Get the content of the tank at the pos.
* content is determined by what the tank allows to be extracted.
*/
public FluidStack getTankContents(BlockPos tank) {
IFluidHandler handler = fluidStorageAt(tank);
return handler.drain(Integer.MAX_VALUE, FluidAction.SIMULATE);
}
/**
* Get the total capacity of a tank at the given position.
*/
public long getTankCapacity(BlockPos pos) {
IFluidHandler handler = fluidStorageAt(pos);
long total = 0;
for (int i = 0; i < handler.getTanks(); i++) {
total += handler.getTankCapacity(i);
}
return total;
}
/**
* Get the total fluid amount across all fluid tanks at the given positions.
*/
public long getFluidInTanks(BlockPos... tanks) {
long total = 0;
for (BlockPos tank : tanks) {
total += getTankContents(tank).getAmount();
}
return total;
}
/**
* Assert that the given fluid stack is present in the given tank. The tank might also hold more than the fluid.
*/
public void assertFluidPresent(FluidStack fluid, BlockPos pos) {
FluidStack contained = getTankContents(pos);
if (!fluid.isFluidEqual(contained))
fail("Different fluids");
if (fluid.getAmount() != contained.getAmount())
fail("Different amounts");
}
/**
* Assert that the given tank holds no fluid.
*/
public void assertTankEmpty(BlockPos pos) {
assertFluidPresent(FluidStack.EMPTY, pos);
}
public void assertTanksEmpty(BlockPos... tanks) {
for (BlockPos tank : tanks) {
assertTankEmpty(tank);
}
}
// transfer - items
public IItemHandler itemStorageAt(BlockPos pos) {
BlockEntity be = getBlockEntity(pos);
if (be == null)
fail("BlockEntity not present");
Optional<IItemHandler> handler = be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve();
if (handler.isEmpty())
fail("handler not present");
return handler.get();
}
/**
* Get a map of contained items to their amounts. This is not safe for NBT!
*/
public Object2LongMap<Item> getItemContent(BlockPos pos) {
IItemHandler handler = itemStorageAt(pos);
Object2LongMap<Item> map = new Object2LongArrayMap<>();
for (int i = 0; i < handler.getSlots(); i++) {
ItemStack stack = handler.getStackInSlot(i);
if (stack.isEmpty())
continue;
Item item = stack.getItem();
long amount = map.getLong(item);
amount += stack.getCount();
map.put(item, amount);
}
return map;
}
/**
* Get the combined total of all ItemStacks inside the inventory.
*/
public long getTotalItems(BlockPos pos) {
IItemHandler storage = itemStorageAt(pos);
long total = 0;
for (int i = 0; i < storage.getSlots(); i++) {
total += storage.getStackInSlot(i).getCount();
}
return total;
}
/**
* Of the provided items, assert that at least one is present in the given inventory.
*/
public void assertAnyContained(BlockPos pos, Item... items) {
IItemHandler handler = itemStorageAt(pos);
boolean noneFound = true;
for (int i = 0; i < handler.getSlots(); i++) {
for (Item item : items) {
if (handler.getStackInSlot(i).is(item)) {
noneFound = false;
break;
}
}
}
if (noneFound)
fail("No matching items " + Arrays.toString(items) + " found in handler at pos: " + pos);
}
/**
* Assert that the inventory contains all the provided content.
*/
public void assertContentPresent(Object2LongMap<Item> content, BlockPos pos) {
IItemHandler handler = itemStorageAt(pos);
Object2LongMap<Item> map = new Object2LongArrayMap<>(content);
for (int i = 0; i < handler.getSlots(); i++) {
ItemStack stack = handler.getStackInSlot(i);
if (stack.isEmpty())
continue;
Item item = stack.getItem();
long amount = map.getLong(item);
amount -= stack.getCount();
if (amount == 0)
map.removeLong(item);
else map.put(item, amount);
}
if (!map.isEmpty())
fail("Storage missing content: " + map);
}
/**
* Assert that all the given inventories hold no items.
*/
public void assertContainersEmpty(List<BlockPos> positions) {
for (BlockPos pos : positions) {
assertContainerEmpty(pos);
}
}
/**
* Assert that the given inventory holds no items.
*/
@Override
public void assertContainerEmpty(@NotNull BlockPos pos) {
IItemHandler storage = itemStorageAt(pos);
for (int i = 0; i < storage.getSlots(); i++) {
if (!storage.getStackInSlot(i).isEmpty())
fail("Storage not empty");
}
}
/** @see CreateGameTestHelper#assertContainerContains(BlockPos, ItemStack) */
public void assertContainerContains(BlockPos pos, ItemLike item) {
assertContainerContains(pos, item.asItem());
}
/** @see CreateGameTestHelper#assertContainerContains(BlockPos, ItemStack) */
@Override
public void assertContainerContains(@NotNull BlockPos pos, @NotNull Item item) {
assertContainerContains(pos, new ItemStack(item));
}
/**
* Assert that the inventory holds at least the given ItemStack. It may also hold more than the stack.
*/
public void assertContainerContains(BlockPos pos, ItemStack item) {
IItemHandler storage = itemStorageAt(pos);
ItemStack extracted = ItemHelper.extract(storage, stack -> ItemHandlerHelper.canItemStacksStack(stack, item), item.getCount(), true);
if (extracted.isEmpty())
fail("item not present: " + item);
}
// time
/**
* Fail unless the desired number seconds have passed since test start.
*/
public void assertSecondsPassed(int seconds) {
if (getTick() < (long) seconds * TICKS_PER_SECOND)
fail("Waiting for %s seconds to pass".formatted(seconds));
}
/**
* Get the total number of seconds that have passed since test start.
*/
public long secondsPassed() {
return getTick() % 20;
}
/**
* Run an action later, once enough time has passed.
*/
public void whenSecondsPassed(int seconds, Runnable run) {
runAfterDelay((long) seconds * TICKS_PER_SECOND, run);
}
// numbers
/**
* Assert that a number is <1 away from its expected value
*/
public void assertCloseEnoughTo(double value, double expected) {
assertInRange(value, expected - 1, expected + 1);
}
public void assertInRange(double value, double min, double max) {
if (value < min)
fail("Value %s below expected min of %s".formatted(value, min));
if (value > max)
fail("Value %s greater than expected max of %s".formatted(value, max));
}
// misc
@Contract("_->fail") // make IDEA happier
@Override
public void fail(@NotNull String exceptionMessage) {
super.fail(exceptionMessage);
}
}

View file

@ -0,0 +1,122 @@
package com.simibubi.create.gametest.infrastructure;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestGenerator;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.StructureUtils;
import net.minecraft.gametest.framework.TestFunction;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* An extension to game tests implementing functionality for {@link CreateGameTestHelper} and {@link GameTestGroup}.
* To use, create a {@link GameTestGenerator} that provides tests using {@link #getTestsFrom(Class[])}.
*/
public class CreateTestFunction extends TestFunction {
// for structure blocks and /test runthis
public static final Map<String, CreateTestFunction> NAMES_TO_FUNCTIONS = new HashMap<>();
public final String fullName;
public final String simpleName;
protected CreateTestFunction(String fullName, String simpleName, String pBatchName, String pTestName,
String pStructureName, Rotation pRotation, int pMaxTicks, long pSetupTicks,
boolean pRequired, int pRequiredSuccesses, int pMaxAttempts, Consumer<GameTestHelper> pFunction) {
super(pBatchName, pTestName, pStructureName, pRotation, pMaxTicks, pSetupTicks, pRequired, pRequiredSuccesses, pMaxAttempts, pFunction);
this.fullName = fullName;
this.simpleName = simpleName;
NAMES_TO_FUNCTIONS.put(fullName, this);
}
@Override
public String getTestName() {
return simpleName;
}
/**
* Get all Create test functions from the given classes. This enables functionality
* of {@link CreateGameTestHelper} and {@link GameTestGroup}.
*/
public static Collection<TestFunction> getTestsFrom(Class<?>... classes) {
return Stream.of(classes)
.map(Class::getDeclaredMethods)
.flatMap(Stream::of)
.map(CreateTestFunction::of)
.filter(Objects::nonNull)
.sorted(Comparator.comparing(TestFunction::getTestName))
.toList();
}
@Nullable
public static TestFunction of(Method method) {
GameTest gt = method.getAnnotation(GameTest.class);
if (gt == null) // skip non-test methods
return null;
Class<?> owner = method.getDeclaringClass();
GameTestGroup group = owner.getAnnotation(GameTestGroup.class);
String simpleName = owner.getSimpleName() + '.' + method.getName();
validateTestMethod(method, gt, owner, group, simpleName);
String structure = "%s:gametest/%s/%s".formatted(group.namespace(), group.path(), gt.template());
Rotation rotation = StructureUtils.getRotationForRotationSteps(gt.rotationSteps());
String fullName = owner.getName() + "." + method.getName();
return new CreateTestFunction(
// use structure for test name since that's what MC fills structure blocks with for some reason
fullName, simpleName, gt.batch(), structure, structure, rotation, gt.timeoutTicks(), gt.setupTicks(),
gt.required(), gt.requiredSuccesses(), gt.attempts(), asConsumer(method)
);
}
private static void validateTestMethod(Method method, GameTest gt, Class<?> owner, GameTestGroup group, String simpleName) {
if (gt.template().isEmpty())
throw new IllegalArgumentException(simpleName + " must provide a template structure");
if (!Modifier.isStatic(method.getModifiers()))
throw new IllegalArgumentException(simpleName + " must be static");
if (method.getReturnType() != void.class)
throw new IllegalArgumentException(simpleName + " must return void");
if (method.getParameterCount() != 1 || method.getParameterTypes()[0] != CreateGameTestHelper.class)
throw new IllegalArgumentException(simpleName + " must take 1 parameter of type CreateGameTestHelper");
if (group == null)
throw new IllegalArgumentException(owner.getName() + " must be annotated with @GameTestGroup");
}
private static Consumer<GameTestHelper> asConsumer(Method method) {
return (helper) -> {
try {
method.invoke(null, helper);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
}
@Override
public void run(@NotNull GameTestHelper helper) {
// give structure block test info
StructureBlockEntity be = (StructureBlockEntity) helper.getBlockEntity(BlockPos.ZERO);
be.getTileData().putString("CreateTestFunction", fullName);
super.run(CreateGameTestHelper.of(helper));
}
}

View file

@ -0,0 +1,25 @@
package com.simibubi.create.gametest.infrastructure;
import com.simibubi.create.Create;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Allows for test method declarations to be concise by moving subdirectories and namespaces to the class level.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface GameTestGroup {
/**
* The subdirectory to search for test structures in.
*/
String path();
/**
* The namespace to search for test structures in.
*/
String namespace() default Create.ID;
}

View file

@ -0,0 +1,98 @@
package com.simibubi.create.gametest.tests;
import java.util.List;
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.projectile.Arrow;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.fluids.FluidStack;
@GameTestGroup(path = "contraptions")
public class TestContraptions {
@GameTest(template = "arrow_dispenser", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void arrowDispenser(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(2, 3, 1);
helper.pullLever(lever);
BlockPos pos1 = new BlockPos(0, 5, 0);
BlockPos pos2 = new BlockPos(4, 5, 4);
helper.succeedWhen(() -> {
helper.assertSecondsPassed(7);
List<Arrow> arrows = helper.getEntitiesBetween(EntityType.ARROW, pos1, pos2);
if (arrows.size() != 4)
helper.fail("Expected 4 arrows");
helper.powerLever(lever); // disassemble contraption
BlockPos dispenser = new BlockPos(2, 5, 2);
// there should be 1 left over
helper.assertContainerContains(dispenser, Items.ARROW);
});
}
@GameTest(template = "crop_farming", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void cropFarming(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(4, 3, 1);
helper.pullLever(lever);
BlockPos output = new BlockPos(1, 3, 12);
helper.succeedWhen(() -> helper.assertAnyContained(output, Items.WHEAT, Items.POTATO, Items.CARROT));
}
@GameTest(template = "mounted_item_extract", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
public static void mountedItemExtract(CreateGameTestHelper helper) {
BlockPos barrel = new BlockPos(1, 3, 2);
Object2LongMap<Item> content = helper.getItemContent(barrel);
BlockPos lever = new BlockPos(1, 5, 1);
helper.pullLever(lever);
BlockPos outputPos = new BlockPos(4, 2, 1);
helper.succeedWhen(() -> {
helper.assertContentPresent(content, outputPos); // verify all extracted
helper.powerLever(lever);
helper.assertContainerEmpty(barrel); // verify nothing left
});
}
@GameTest(template = "mounted_fluid_drain", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void mountedFluidDrain(CreateGameTestHelper helper) {
BlockPos tank = new BlockPos(1, 3, 2);
FluidStack fluid = helper.getTankContents(tank);
if (fluid.isEmpty())
helper.fail("Tank empty");
BlockPos lever = new BlockPos(1, 5, 1);
helper.pullLever(lever);
BlockPos output = new BlockPos(4, 2, 1);
helper.succeedWhen(() -> {
helper.assertFluidPresent(fluid, output); // verify all extracted
helper.powerLever(lever); // disassemble contraption
helper.assertTankEmpty(tank); // verify nothing left
});
}
@GameTest(template = "ploughing")
public static void ploughing(CreateGameTestHelper helper) {
BlockPos dirt = new BlockPos(4, 2, 1);
BlockPos lever = new BlockPos(3, 3, 2);
helper.pullLever(lever);
helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.FARMLAND, dirt));
}
@GameTest(template = "redstone_contacts")
public static void redstoneContacts(CreateGameTestHelper helper) {
BlockPos end = new BlockPos(5, 10, 1);
BlockPos lever = new BlockPos(1, 3, 2);
helper.pullLever(lever);
helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, end));
}
// FIXME: trains do not enjoy being loaded in structures
// https://gist.github.com/TropheusJ/f2d0a7df48360d2e078d0987c115c6ef
// @GameTest(template = "train_observer")
// public static void trainObserver(CreateGameTestHelper helper) {
// helper.fail("NYI");
// }
}

View file

@ -0,0 +1,152 @@
package com.simibubi.create.gametest.tests;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.fluids.actors.HosePulleyFluidHandler;
import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity;
import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity;
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RedStoneWireBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.RedstoneSide;
import net.minecraft.world.level.material.Fluids;
import net.minecraftforge.fluids.FluidAttributes;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
@GameTestGroup(path = "fluids")
public class TestFluids {
@GameTest(template = "hose_pulley_transfer", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
public static void hosePulleyTransfer(CreateGameTestHelper helper) {
// there was supposed to be redstone here built in, but it kept popping off, so put it there manually
BlockPos brokenRedstone = new BlockPos(4, 8, 3);
BlockState redstone = Blocks.REDSTONE_WIRE.defaultBlockState()
.setValue(RedStoneWireBlock.NORTH, RedstoneSide.NONE)
.setValue(RedStoneWireBlock.SOUTH, RedstoneSide.NONE)
.setValue(RedStoneWireBlock.EAST, RedstoneSide.UP)
.setValue(RedStoneWireBlock.WEST, RedstoneSide.SIDE)
.setValue(RedStoneWireBlock.POWER, 14);
helper.setBlock(brokenRedstone, redstone);
// pump
BlockPos lever = new BlockPos(6, 9, 3);
helper.pullLever(lever);
helper.succeedWhen(() -> {
helper.assertSecondsPassed(15);
// check filled
BlockPos filledLowerCorner = new BlockPos(8, 3, 2);
BlockPos filledUpperCorner = new BlockPos(10, 5, 4);
BlockPos.betweenClosed(filledLowerCorner, filledUpperCorner)
.forEach(pos -> helper.assertBlockPresent(Blocks.WATER, pos));
// check emptied
BlockPos emptiedLowerCorner = new BlockPos(2, 3, 2);
BlockPos emptiedUpperCorner = new BlockPos(4, 5, 4);
BlockPos.betweenClosed(emptiedLowerCorner, emptiedUpperCorner)
.forEach(pos -> helper.assertBlockPresent(Blocks.AIR, pos));
// check nothing left in pulley
BlockPos pulleyPos = new BlockPos(8, 7, 4);
IFluidHandler storage = helper.fluidStorageAt(pulleyPos);
if (storage instanceof HosePulleyFluidHandler hose) {
IFluidHandler internalTank = hose.getInternalTank();
if (!internalTank.drain(1, FluidAction.SIMULATE).isEmpty())
helper.fail("Pulley not empty");
} else {
helper.fail("Not a pulley");
}
});
}
@GameTest(template = "in_world_pumping_out")
public static void inWorldPumpingOutput(CreateGameTestHelper helper) {
BlockPos pumpPos = new BlockPos(3, 2, 2);
BlockPos waterPos = pumpPos.west();
BlockPos basinPos = pumpPos.east();
helper.flipBlock(pumpPos);
helper.succeedWhen(() -> {
helper.assertBlockPresent(Blocks.WATER, waterPos);
helper.assertTankEmpty(basinPos);
});
}
@GameTest(template = "in_world_pumping_in")
public static void inWorldPumpingPickup(CreateGameTestHelper helper) {
BlockPos pumpPos = new BlockPos(3, 2, 2);
BlockPos basinPos = pumpPos.east();
BlockPos waterPos = pumpPos.west();
FluidStack expectedResult = new FluidStack(Fluids.WATER, FluidAttributes.BUCKET_VOLUME);
helper.flipBlock(pumpPos);
helper.succeedWhen(() -> {
helper.assertBlockPresent(Blocks.AIR, waterPos);
helper.assertFluidPresent(expectedResult, basinPos);
});
}
@GameTest(template = "steam_engine")
public static void steamEngine(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(4, 3, 3);
helper.pullLever(lever);
BlockPos stressometer = new BlockPos(5, 2, 5);
BlockPos speedometer = new BlockPos(4, 2, 5);
helper.succeedWhen(() -> {
StressGaugeTileEntity stress = helper.getBlockEntity(AllTileEntities.STRESSOMETER.get(), stressometer);
SpeedGaugeTileEntity speed = helper.getBlockEntity(AllTileEntities.SPEEDOMETER.get(), speedometer);
float capacity = stress.getNetworkCapacity();
helper.assertCloseEnoughTo(capacity, 2048);
float rotationSpeed = Mth.abs(speed.getSpeed());
helper.assertCloseEnoughTo(rotationSpeed, 16);
});
}
@GameTest(template = "3_pipe_combine", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
public static void threePipeCombine(CreateGameTestHelper helper) {
BlockPos tank1Pos = new BlockPos(5, 2, 1);
BlockPos tank2Pos = tank1Pos.south();
BlockPos tank3Pos = tank2Pos.south();
long initialContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos);
BlockPos pumpPos = new BlockPos(2, 2, 2);
helper.flipBlock(pumpPos);
helper.succeedWhen(() -> {
helper.assertSecondsPassed(13);
// make sure fully drained
helper.assertTanksEmpty(tank1Pos, tank2Pos, tank3Pos);
// and fully moved
BlockPos outputTankPos = new BlockPos(1, 2, 2);
long moved = helper.getFluidInTanks(outputTankPos);
if (moved != initialContents)
helper.fail("Wrong amount of fluid amount. expected [%s], got [%s]".formatted(initialContents, moved));
// verify nothing was duped or deleted
});
}
@GameTest(template = "3_pipe_split", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void threePipeSplit(CreateGameTestHelper helper) {
BlockPos pumpPos = new BlockPos(2, 2, 2);
BlockPos tank1Pos = new BlockPos(5, 2, 1);
BlockPos tank2Pos = tank1Pos.south();
BlockPos tank3Pos = tank2Pos.south();
BlockPos outputTankPos = new BlockPos(1, 2, 2);
long totalContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos, outputTankPos);
helper.flipBlock(pumpPos);
helper.succeedWhen(() -> {
helper.assertSecondsPassed(7);
FluidStack contents = helper.getTankContents(outputTankPos);
if (!contents.isEmpty()) {
helper.fail("Tank not empty: " + contents.getAmount());
}
long newTotalContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos);
if (newTotalContents != totalContents) {
helper.fail("Wrong total fluid amount. expected [%s], got [%s]".formatted(totalContents, newTotalContents));
}
});
}
}

View file

@ -0,0 +1,331 @@
package com.simibubi.create.gametest.tests;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity.SelectionMode;
import com.simibubi.create.content.logistics.block.depot.DepotTileEntity;
import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity;
import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayLayout;
import com.simibubi.create.content.logistics.trains.management.display.FlapDisplaySection;
import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayTileEntity;
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
import com.simibubi.create.foundation.utility.Components;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.EnchantmentInstance;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RedstoneLampBlock;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemHandlerHelper;
@GameTestGroup(path = "items")
public class TestItems {
@GameTest(template = "andesite_tunnel_split")
public static void andesiteTunnelSplit(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(2, 6, 2);
helper.pullLever(lever);
Map<BlockPos, ItemStack> outputs = Map.of(
new BlockPos(2, 2, 1), new ItemStack(AllItems.BRASS_INGOT.get(), 1),
new BlockPos(3, 2, 1), new ItemStack(AllItems.BRASS_INGOT.get(), 1),
new BlockPos(4, 2, 2), new ItemStack(AllItems.BRASS_INGOT.get(), 3)
);
helper.succeedWhen(() -> outputs.forEach(helper::assertContainerContains));
}
@GameTest(template = "arm_purgatory", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void armPurgatory(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(2, 3, 2);
BlockPos depot1Pos = new BlockPos(3, 2, 1);
DepotTileEntity depot1 = helper.getBlockEntity(AllTileEntities.DEPOT.get(), depot1Pos);
BlockPos depot2Pos = new BlockPos(1, 2, 1);
DepotTileEntity depot2 = helper.getBlockEntity(AllTileEntities.DEPOT.get(), depot2Pos);
helper.pullLever(lever);
helper.succeedWhen(() -> {
helper.assertSecondsPassed(5);
ItemStack held1 = depot1.getHeldItem();
boolean held1Empty = held1.isEmpty();
int held1Count = held1.getCount();
ItemStack held2 = depot2.getHeldItem();
boolean held2Empty = held2.isEmpty();
int held2Count = held2.getCount();
if (held1Empty && held2Empty)
helper.fail("No item present");
if (!held1Empty && held1Count != 1)
helper.fail("Unexpected count on depot 1: " + held1Count);
if (!held2Empty && held2Count != 1)
helper.fail("Unexpected count on depot 2: " + held2Count);
});
}
@GameTest(template = "attribute_filters", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void attributeFilters(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(2, 3, 1);
BlockPos end = new BlockPos(11, 2, 2);
Map<BlockPos, ItemStack> outputs = Map.of(
new BlockPos(3, 2, 1), new ItemStack(AllBlocks.BRASS_BLOCK.get()),
new BlockPos(4, 2, 1), new ItemStack(Items.APPLE),
new BlockPos(5, 2, 1), new ItemStack(Items.WATER_BUCKET),
new BlockPos(6, 2, 1), EnchantedBookItem.createForEnchantment(
new EnchantmentInstance(Enchantments.ALL_DAMAGE_PROTECTION, 1)
),
new BlockPos(7, 2, 1), Util.make(
new ItemStack(Items.NETHERITE_SWORD),
s -> s.setDamageValue(1)
),
new BlockPos(8, 2, 1), new ItemStack(Items.IRON_HELMET),
new BlockPos(9, 2, 1), new ItemStack(Items.COAL),
new BlockPos(10, 2, 1), new ItemStack(Items.POTATO)
);
helper.pullLever(lever);
helper.succeedWhen(() -> {
outputs.forEach(helper::assertContainerContains);
helper.assertContainerEmpty(end);
});
}
@GameTest(template = "belt_coaster", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void beltCoaster(CreateGameTestHelper helper) {
BlockPos input = new BlockPos(1, 5, 6);
BlockPos output = new BlockPos(3, 8, 6);
BlockPos lever = new BlockPos(1, 5, 5);
helper.pullLever(lever);
helper.succeedWhen(() -> {
long outputItems = helper.getTotalItems(output);
if (outputItems != 27)
helper.fail("Expected 27 items, got " + outputItems);
long remainingItems = helper.getTotalItems(input);
if (remainingItems != 2)
helper.fail("Expected 2 items remaining, got " + remainingItems);
});
}
@GameTest(template = "brass_tunnel_filtering")
public static void brassTunnelFiltering(CreateGameTestHelper helper) {
Map<BlockPos, ItemStack> outputs = Map.of(
new BlockPos(3, 2, 2), new ItemStack(Items.COPPER_INGOT, 13),
new BlockPos(4, 2, 3), new ItemStack(AllItems.ZINC_INGOT.get(), 4),
new BlockPos(4, 2, 4), new ItemStack(Items.IRON_INGOT, 2),
new BlockPos(4, 2, 5), new ItemStack(Items.GOLD_INGOT, 24),
new BlockPos(3, 2, 6), new ItemStack(Items.DIAMOND, 17)
);
BlockPos lever = new BlockPos(2, 3, 2);
helper.pullLever(lever);
helper.succeedWhen(() -> outputs.forEach(helper::assertContainerContains));
}
@GameTest(template = "brass_tunnel_prefer_nearest", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void brassTunnelPreferNearest(CreateGameTestHelper helper) {
List<BlockPos> tunnels = List.of(
new BlockPos(3, 3, 1),
new BlockPos(3, 3, 2),
new BlockPos(3, 3, 3)
);
List<BlockPos> out = List.of(
new BlockPos(5, 2, 1),
new BlockPos(5, 2, 2),
new BlockPos(5, 2, 3)
);
BlockPos lever = new BlockPos(2, 3, 2);
helper.pullLever(lever);
// tunnels reconnect and lose their modes
tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, SelectionMode.PREFER_NEAREST));
helper.succeedWhen(() ->
out.forEach(pos ->
helper.assertContainerContains(pos, AllBlocks.BRASS_CASING.get())
)
);
}
@GameTest(template = "brass_tunnel_round_robin", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void brassTunnelRoundRobin(CreateGameTestHelper helper) {
List<BlockPos> outputs = List.of(
new BlockPos(7, 3, 1),
new BlockPos(7, 3, 2),
new BlockPos(7, 3, 3)
);
brassTunnelModeTest(helper, SelectionMode.ROUND_ROBIN, outputs);
}
@GameTest(template = "brass_tunnel_split")
public static void brassTunnelSplit(CreateGameTestHelper helper) {
List<BlockPos> outputs = List.of(
new BlockPos(7, 2, 1),
new BlockPos(7, 2, 2),
new BlockPos(7, 2, 3)
);
brassTunnelModeTest(helper, SelectionMode.SPLIT, outputs);
}
private static void brassTunnelModeTest(CreateGameTestHelper helper, SelectionMode mode, List<BlockPos> outputs) {
BlockPos lever = new BlockPos(2, 3, 2);
List<BlockPos> tunnels = List.of(
new BlockPos(3, 3, 1),
new BlockPos(3, 3, 2),
new BlockPos(3, 3, 3)
);
helper.pullLever(lever);
tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, mode));
helper.succeedWhen(() -> {
long items = 0;
for (BlockPos out : outputs) {
helper.assertContainerContains(out, AllBlocks.BRASS_CASING.get());
items += helper.getTotalItems(out);
}
if (items != 10)
helper.fail("expected 10 items, got " + items);
});
}
@GameTest(template = "brass_tunnel_sync_input", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void brassTunnelSyncInput(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(1, 3, 2);
List<BlockPos> redstoneBlocks = List.of(
new BlockPos(3, 4, 1),
new BlockPos(3, 4, 2),
new BlockPos(3, 4, 3)
);
List<BlockPos> tunnels = List.of(
new BlockPos(5, 3, 1),
new BlockPos(5, 3, 2),
new BlockPos(5, 3, 3)
);
List<BlockPos> outputs = List.of(
new BlockPos(7, 2, 1),
new BlockPos(7, 2, 2),
new BlockPos(7, 2, 3)
);
helper.pullLever(lever);
tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, SelectionMode.SYNCHRONIZE));
helper.succeedWhen(() -> {
if (helper.secondsPassed() < 9) {
helper.setBlock(redstoneBlocks.get(0), Blocks.AIR);
helper.assertSecondsPassed(3);
outputs.forEach(helper::assertContainerEmpty);
helper.setBlock(redstoneBlocks.get(1), Blocks.AIR);
helper.assertSecondsPassed(6);
outputs.forEach(helper::assertContainerEmpty);
helper.setBlock(redstoneBlocks.get(2), Blocks.AIR);
helper.assertSecondsPassed(9);
} else {
outputs.forEach(out -> helper.assertContainerContains(out, AllBlocks.BRASS_CASING.get()));
}
});
}
@GameTest(template = "content_observer_counting")
public static void contentObserverCounting(CreateGameTestHelper helper) {
BlockPos chest = new BlockPos(3, 2, 1);
long totalChestItems = helper.getTotalItems(chest);
BlockPos chestNixiePos = new BlockPos(2, 3, 1);
NixieTubeTileEntity chestNixie = helper.getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), chestNixiePos);
BlockPos doubleChest = new BlockPos(2, 2, 3);
long totalDoubleChestItems = helper.getTotalItems(doubleChest);
BlockPos doubleChestNixiePos = new BlockPos(1, 3, 3);
NixieTubeTileEntity doubleChestNixie = helper.getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), doubleChestNixiePos);
helper.succeedWhen(() -> {
String chestNixieText = chestNixie.getFullText().getString();
long chestNixieReading = Long.parseLong(chestNixieText);
if (chestNixieReading != totalChestItems)
helper.fail("Chest nixie detected %s, expected %s".formatted(chestNixieReading, totalChestItems));
String doubleChestNixieText = doubleChestNixie.getFullText().getString();
long doubleChestNixieReading = Long.parseLong(doubleChestNixieText);
if (doubleChestNixieReading != totalDoubleChestItems)
helper.fail("Double chest nixie detected %s, expected %s".formatted(doubleChestNixieReading, totalDoubleChestItems));
});
}
@GameTest(template = "depot_display", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void depotDisplay(CreateGameTestHelper helper) {
BlockPos displayPos = new BlockPos(5, 3, 1);
List<DepotTileEntity> depots = Stream.of(
new BlockPos(2, 2, 1),
new BlockPos(1, 2, 1)
).map(pos -> helper.getBlockEntity(AllTileEntities.DEPOT.get(), pos)).toList();
List<BlockPos> levers = List.of(
new BlockPos(2, 5, 0),
new BlockPos(1, 5, 0)
);
levers.forEach(helper::pullLever);
FlapDisplayTileEntity display = helper.getBlockEntity(AllTileEntities.FLAP_DISPLAY.get(), displayPos).getController();
helper.succeedWhen(() -> {
for (int i = 0; i < 2; i++) {
FlapDisplayLayout line = display.getLines().get(i);
MutableComponent textComponent = Components.empty();
line.getSections().stream().map(FlapDisplaySection::getText).forEach(textComponent::append);
String text = textComponent.getString().toLowerCase(Locale.ROOT).trim();
DepotTileEntity depot = depots.get(i);
ItemStack item = depot.getHeldItem();
String name = Registry.ITEM.getKey(item.getItem()).getPath();
if (!name.equals(text))
helper.fail("Text mismatch: wanted [" + name + "], got: " + text);
}
});
}
@GameTest(template = "stockpile_switch")
public static void stockpileSwitch(CreateGameTestHelper helper) {
BlockPos chest = new BlockPos(1, 2, 1);
BlockPos lamp = new BlockPos(2, 3, 1);
helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, false);
IItemHandler chestStorage = helper.itemStorageAt(chest);
for (int i = 0; i < 18; i++) { // insert 18 stacks
ItemHandlerHelper.insertItem(chestStorage, new ItemStack(Items.DIAMOND, 64), false);
}
helper.succeedWhen(() -> helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, true));
}
@GameTest(template = "storages", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void storages(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(12, 3, 2);
BlockPos startChest = new BlockPos(13, 3, 1);
Object2LongMap<Item> originalContent = helper.getItemContent(startChest);
BlockPos endShulker = new BlockPos(1, 3, 1);
helper.pullLever(lever);
helper.succeedWhen(() -> helper.assertContentPresent(originalContent, endShulker));
}
@GameTest(template = "vault_comparator_output")
public static void vaultComparatorOutput(CreateGameTestHelper helper) {
BlockPos smallInput = new BlockPos(1, 4, 1);
BlockPos smallNixie = new BlockPos(3, 2, 1);
helper.assertNixiePower(smallNixie, 0);
helper.whenSecondsPassed(1, () -> helper.spawnItems(smallInput, Items.BREAD, 64 * 9));
BlockPos medInput = new BlockPos(1, 5, 4);
BlockPos medNixie = new BlockPos(4, 2, 4);
helper.assertNixiePower(medNixie, 0);
helper.whenSecondsPassed(2, () -> helper.spawnItems(medInput, Items.BREAD, 64 * 77));
BlockPos bigInput = new BlockPos(1, 6, 8);
BlockPos bigNixie = new BlockPos(5, 2, 7);
helper.assertNixiePower(bigNixie, 0);
helper.whenSecondsPassed(3, () -> helper.spawnItems(bigInput, Items.BREAD, 64 * 240));
helper.succeedWhen(() -> {
helper.assertNixiePower(smallNixie, 7);
helper.assertNixiePower(medNixie, 7);
helper.assertNixiePower(bigNixie, 7);
});
}
}

View file

@ -0,0 +1,67 @@
package com.simibubi.create.gametest.tests;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.schematics.SchematicExport;
import com.simibubi.create.content.schematics.block.SchematicannonTileEntity;
import com.simibubi.create.content.schematics.block.SchematicannonTileEntity.State;
import com.simibubi.create.content.schematics.item.SchematicItem;
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.animal.Sheep;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import static com.simibubi.create.gametest.infrastructure.CreateGameTestHelper.FIFTEEN_SECONDS;
@GameTestGroup(path = "misc")
public class TestMisc {
@GameTest(template = "schematicannon", timeoutTicks = FIFTEEN_SECONDS)
public static void schematicannon(CreateGameTestHelper helper) {
// load the structure
BlockPos whiteEndBottom = helper.absolutePos(new BlockPos(5, 2, 1));
BlockPos redEndTop = helper.absolutePos(new BlockPos(5, 4, 7));
ServerLevel level = helper.getLevel();
SchematicExport.saveSchematic(
SchematicExport.SCHEMATICS.resolve("uploaded/Deployer"), "schematicannon_gametest", true,
level, whiteEndBottom, redEndTop
);
ItemStack schematic = SchematicItem.create("schematicannon_gametest.nbt", "Deployer");
// deploy to pos
BlockPos anchor = helper.absolutePos(new BlockPos(1, 2, 1));
schematic.getOrCreateTag().putBoolean("Deployed", true);
schematic.getOrCreateTag().put("Anchor", NbtUtils.writeBlockPos(anchor));
// setup cannon
BlockPos cannonPos = new BlockPos(3, 2, 6);
SchematicannonTileEntity cannon = helper.getBlockEntity(AllTileEntities.SCHEMATICANNON.get(), cannonPos);
cannon.inventory.setStackInSlot(0, schematic);
// run
cannon.state = State.RUNNING;
cannon.statusMsg = "running";
helper.succeedWhen(() -> {
if (cannon.state != State.STOPPED) {
helper.fail("Schematicannon not done");
}
BlockPos lastBlock = new BlockPos(1, 4, 7);
helper.assertBlockPresent(Blocks.RED_WOOL, lastBlock);
});
}
@GameTest(template = "shearing")
public static void shearing(CreateGameTestHelper helper) {
BlockPos sheepPos = new BlockPos(2, 1, 2);
Sheep sheep = helper.getFirstEntity(EntityType.SHEEP, sheepPos);
sheep.shear(SoundSource.NEUTRAL);
helper.succeedWhen(() -> {
helper.assertItemEntityPresent(Items.WHITE_WOOL, sheepPos, 2);
});
}
}

View file

@ -0,0 +1,129 @@
package com.simibubi.create.gametest.tests;
import java.util.List;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.itemAssembly.SequencedAssemblyRecipe;
import com.simibubi.create.content.contraptions.processing.ProcessingOutput;
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
import com.simibubi.create.foundation.item.ItemHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionUtils;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraftforge.items.IItemHandler;
@GameTestGroup(path = "processing")
public class TestProcessing {
@GameTest(template = "brass_mixing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void brassMixing(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(2, 3, 2);
BlockPos chest = new BlockPos(7, 3, 1);
helper.pullLever(lever);
helper.succeedWhen(() -> helper.assertContainerContains(chest, AllItems.BRASS_INGOT.get()));
}
@GameTest(template = "brass_mixing_2", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
public static void brassMixing2(CreateGameTestHelper helper) {
BlockPos basinLever = new BlockPos(3, 3, 1);
BlockPos armLever = new BlockPos(3, 3, 5);
BlockPos output = new BlockPos(1, 2, 3);
helper.pullLever(armLever);
helper.whenSecondsPassed(7, () -> helper.pullLever(armLever));
helper.whenSecondsPassed(10, () -> helper.pullLever(basinLever));
helper.succeedWhen(() -> helper.assertContainerContains(output, AllItems.BRASS_INGOT.get()));
}
@GameTest(template = "crushing_wheel_crafting", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void crushingWheelCrafting(CreateGameTestHelper helper) {
BlockPos chest = new BlockPos(1, 4, 3);
List<BlockPos> levers = List.of(
new BlockPos(2, 3, 2),
new BlockPos(6, 3, 2),
new BlockPos(3, 7, 3)
);
levers.forEach(helper::pullLever);
ItemStack expected = new ItemStack(AllBlocks.CRUSHING_WHEEL.get(), 2);
helper.succeedWhen(() -> helper.assertContainerContains(chest, expected));
}
@GameTest(template = "precision_mechanism_crafting", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
public static void precisionMechanismCrafting(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(6, 3, 6);
BlockPos output = new BlockPos(11, 3, 1);
helper.pullLever(lever);
SequencedAssemblyRecipe recipe = (SequencedAssemblyRecipe) helper.getLevel().getRecipeManager()
.byKey(Create.asResource("sequenced_assembly/precision_mechanism"))
.orElseThrow(() -> new GameTestAssertException("Precision Mechanism recipe not found"));
Item result = recipe.getResultItem().getItem();
Item[] possibleResults = recipe.resultPool.stream()
.map(ProcessingOutput::getStack)
.map(ItemStack::getItem)
.filter(item -> item != result)
.toArray(Item[]::new);
helper.succeedWhen(() -> {
helper.assertContainerContains(output, result);
helper.assertAnyContained(output, possibleResults);
});
}
@GameTest(template = "sand_washing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void sandWashing(CreateGameTestHelper helper) {
BlockPos leverPos = new BlockPos(5, 3, 1);
helper.pullLever(leverPos);
BlockPos chestPos = new BlockPos(8, 3, 2);
helper.succeedWhen(() -> helper.assertContainerContains(chestPos, Items.CLAY_BALL));
}
@GameTest(template = "stone_cobble_sand_crushing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void stoneCobbleSandCrushing(CreateGameTestHelper helper) {
BlockPos chest = new BlockPos(1, 6, 2);
BlockPos lever = new BlockPos(2, 3, 1);
helper.pullLever(lever);
ItemStack expected = new ItemStack(Items.SAND, 5);
helper.succeedWhen(() -> helper.assertContainerContains(chest, expected));
}
@GameTest(template = "track_crafting", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
public static void trackCrafting(CreateGameTestHelper helper) {
BlockPos output = new BlockPos(7, 3, 2);
BlockPos lever = new BlockPos(2, 3, 1);
helper.pullLever(lever);
ItemStack expected = new ItemStack(AllBlocks.TRACK.get(), 6);
helper.succeedWhen(() -> {
helper.assertContainerContains(output, expected);
IItemHandler handler = helper.itemStorageAt(output);
ItemHelper.extract(handler, stack -> stack.sameItem(expected), 6, false);
helper.assertContainerEmpty(output);
});
}
@GameTest(template = "water_filling_bottle")
public static void waterFillingBottle(CreateGameTestHelper helper) {
BlockPos lever = new BlockPos(3, 3, 3);
BlockPos output = new BlockPos(2, 2, 4);
ItemStack expected = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER);
helper.pullLever(lever);
helper.succeedWhen(() -> helper.assertContainerContains(output, expected));
}
@GameTest(template = "wheat_milling")
public static void wheatMilling(CreateGameTestHelper helper) {
BlockPos output = new BlockPos(1, 2, 1);
BlockPos lever = new BlockPos(1, 7, 1);
helper.pullLever(lever);
ItemStack expected = new ItemStack(AllItems.WHEAT_FLOUR.get(), 3);
helper.succeedWhen(() -> helper.assertContainerContains(output, expected));
}
}

View file

@ -322,6 +322,8 @@
"create.schematicAndQuill.convert": "Save and Upload Immediately", "create.schematicAndQuill.convert": "Save and Upload Immediately",
"create.schematicAndQuill.fallbackName": "My Schematic", "create.schematicAndQuill.fallbackName": "My Schematic",
"create.schematicAndQuill.saved": "Saved as %1$s", "create.schematicAndQuill.saved": "Saved as %1$s",
"create.schematicAndQuill.failed": "Failed to save schematic, check logs for details",
"create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details",
"create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead", "create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead",
"create.schematic.error": "Schematic failed to Load - Check Game Logs", "create.schematic.error": "Schematic failed to Load - Check Game Logs",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 B

View file

@ -13,10 +13,13 @@
"EnchantmentMixin", "EnchantmentMixin",
"EntityMixin", "EntityMixin",
"LavaSwimmingMixin", "LavaSwimmingMixin",
"MainMixin",
"MapItemSavedDataMixin", "MapItemSavedDataMixin",
"TestCommandMixin",
"accessor.AbstractProjectileDispenseBehaviorAccessor", "accessor.AbstractProjectileDispenseBehaviorAccessor",
"accessor.DispenserBlockAccessor", "accessor.DispenserBlockAccessor",
"accessor.FallingBlockEntityAccessor", "accessor.FallingBlockEntityAccessor",
"accessor.GameTestHelperAccessor",
"accessor.LivingEntityAccessor", "accessor.LivingEntityAccessor",
"accessor.NbtAccounterAccessor", "accessor.NbtAccounterAccessor",
"accessor.ServerLevelAccessor" "accessor.ServerLevelAccessor"

Some files were not shown because too many files have changed in this diff Show more