diff --git a/.github/workflows/gametest.yml b/.github/workflows/gametest.yml new file mode 100644 index 000000000..1ec05830a --- /dev/null +++ b/.github/workflows/gametest.yml @@ -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 diff --git a/.gitignore b/.gitignore index 4afb35865..b9a3321f8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ local.properties # PDT-specific .buildpath -.DS_Store \ No newline at end of file +.DS_Store +/libs/ diff --git a/build.gradle b/build.gradle index 00b21072c..ee35535a0 100644 --- a/build.gradle +++ b/build.gradle @@ -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 + } } } @@ -135,6 +147,17 @@ repositories { includeGroup "maven.modrinth" } } + flatDir { + dirs 'libs' + } + maven { + // Location of maven for CC: Tweaked + name = "squiddev" + url = "https://squiddev.cc/maven/" + content { + includeGroup "org.squiddev" + } + } } dependencies { @@ -162,6 +185,11 @@ dependencies { 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}") + 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("com.ferreusveritas.dynamictrees:DynamicTrees-1.16.5:0.10.0-Beta25") // runtimeOnly fg.deobf("vazkii.arl:AutoRegLib:1.4-35.69") @@ -169,6 +197,7 @@ dependencies { // runtimeOnly fg.deobf("slimeknights.mantle:Mantle:1.16.5-1.6.115") // runtimeOnly fg.deobf("slimeknights.tconstruct:TConstruct:1.16.5-3.1.1.252") // runtimeOnly fg.deobf("maven.modrinth:rubidium:0.5.3") + // implementation fg.deobf("com.railwayteam.railways:railways-1.18.2-1.1.1:all") { transitive = false } // https://discord.com/channels/313125603924639766/725850371834118214/910619168821354497 // Prevent Mixin annotation processor from getting into IntelliJ's annotation processor settings @@ -178,6 +207,12 @@ dependencies { } } +sourceSets.main.java { + if (!cc_tweaked_enable.toBoolean()) { + exclude 'com/simibubi/create/compat/computercraft/implementation/**' + } +} + sourceSets.main.resources { srcDir 'src/generated/resources' exclude '.cache/' diff --git a/gradle.properties b/gradle.properties index 922dc7e44..a90f4e935 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,6 +27,10 @@ jei_version = 11.2.0.254 curios_minecraft_version = 1.19.2 curios_version = 5.1.1.0 +cc_tweaked_enable = true +cc_tweaked_minecraft_version = 1.18.2 +cc_tweaked_version = 1.100.10 + # curseforge information projectId = 328085 curse_type = beta diff --git a/src/generated/resources/assets/create/lang/en_us.json b/src/generated/resources/assets/create/lang/en_us.json index adebf5f68..f620763f3 100644 --- a/src/generated/resources/assets/create/lang/en_us.json +++ b/src/generated/resources/assets/create/lang/en_us.json @@ -1192,6 +1192,8 @@ "create.schematicAndQuill.convert": "Save and Upload Immediately", "create.schematicAndQuill.fallbackName": "My Schematic", "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.error": "Schematic failed to Load - Check Game Logs", @@ -1811,6 +1813,7 @@ "create.display_source.redstone_power.progress_bar": "Progress Bar", "create.display_source.boiler.not_enough_space": "Not enough space ", "create.display_source.boiler.for_boiler_status": "for Boiler Status", + "create.display_source.computer_display_source": "From Computer", "create.display_target.line": "Line %1$s", "create.display_target.page": "Page %1$s", @@ -1833,6 +1836,8 @@ "create.super_glue.not_enough": "Not enough glue in inventory", "create.super_glue.success": "Applying Glue...", + "create.gui.attached_computer.controlled": "This device is being controlled by a computer", + "create.gui.attached_computer.hint": "To use device manually, disconnect all computers and modems", "create.gui.config.overlay1": "Hi :)", "create.gui.config.overlay2": "This is a sample overlay", "create.gui.config.overlay3": "Click or drag with your mouse", @@ -1856,6 +1861,12 @@ "enchantment.create.capacity.desc": "Increases Backtank air capacity.", "enchantment.create.potato_recovery.desc": "Potato Cannon projectiles have a chance to be reused.", + "create.bogey.style.updated_style": "Updated style", + "create.bogey.style.updated_style_and_size": "Updated style and size", + "create.bogey.style.no_other_sizes": "No other sizes", + "create.bogey.style.invalid": "Unnamed style", + "create.bogey.style.standard": "Standard", + "_": "->------------------------] Subtitles [------------------------<-", @@ -2814,7 +2825,7 @@ "create.ponder.mechanical_roller_fill.text_5": "As opposed to 'clear & pave', neither of these modes will cause the rollers to break existing blocks", "create.ponder.mechanical_roller_pave.header": "Clearing and Paving with the Roller", - "create.ponder.mechanical_roller_pave.text_1": "Mechanical rollers help to clean up long tracks or paths conveniently", + "create.ponder.mechanical_roller_pave.text_1": "Mechanical rollers help to clean up terrain around tracks or paths", "create.ponder.mechanical_roller_pave.text_2": "In its default mode, without a material set, it will simply clear blocks like a Drill", "create.ponder.mechanical_roller_pave.text_3": "While disassembled, a suitable paving material can be specified", "create.ponder.mechanical_roller_pave.text_4": "Materials can be supplied via chests or barrels attached to the structure", diff --git a/src/generated/resources/data/create/tags/blocks/girdable_tracks.json b/src/generated/resources/data/create/tags/blocks/girdable_tracks.json new file mode 100644 index 000000000..ef33e72ef --- /dev/null +++ b/src/generated/resources/data/create/tags/blocks/girdable_tracks.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "create:track" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/create/tags/blocks/safe_nbt.json b/src/generated/resources/data/create/tags/blocks/safe_nbt.json index b7e94507e..83992cefa 100644 --- a/src/generated/resources/data/create/tags/blocks/safe_nbt.json +++ b/src/generated/resources/data/create/tags/blocks/safe_nbt.json @@ -18,10 +18,8 @@ "create:andesite_belt_funnel", "create:brass_funnel", "create:brass_belt_funnel", - "create:creative_crate", "create:redstone_link", "create:analog_lever", - "create:placard", "create:pulse_repeater", "create:pulse_extender", "create:clipboard", diff --git a/src/generated/resources/data/create/tags/blocks/tracks.json b/src/generated/resources/data/create/tags/blocks/tracks.json new file mode 100644 index 000000000..ef33e72ef --- /dev/null +++ b/src/generated/resources/data/create/tags/blocks/tracks.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "create:track" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/forge/tags/items/armors/boots.json b/src/generated/resources/data/forge/tags/items/armors/boots.json new file mode 100644 index 000000000..7da61d8ca --- /dev/null +++ b/src/generated/resources/data/forge/tags/items/armors/boots.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "create:copper_diving_boots", + "create:netherite_diving_boots" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/forge/tags/items/armors/chestplates.json b/src/generated/resources/data/forge/tags/items/armors/chestplates.json new file mode 100644 index 000000000..3b3bbadd0 --- /dev/null +++ b/src/generated/resources/data/forge/tags/items/armors/chestplates.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "create:copper_backtank", + "create:netherite_backtank" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/forge/tags/items/armors/helmets.json b/src/generated/resources/data/forge/tags/items/armors/helmets.json new file mode 100644 index 000000000..42f6e845e --- /dev/null +++ b/src/generated/resources/data/forge/tags/items/armors/helmets.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "create:copper_diving_helmet", + "create:netherite_diving_helmet" + ] +} \ No newline at end of file diff --git a/src/main/java/com/simibubi/create/AllBlockEntityTypes.java b/src/main/java/com/simibubi/create/AllBlockEntityTypes.java index f6355af67..f9fa70da7 100644 --- a/src/main/java/com/simibubi/create/AllBlockEntityTypes.java +++ b/src/main/java/com/simibubi/create/AllBlockEntityTypes.java @@ -195,6 +195,7 @@ import com.simibubi.create.content.logistics.block.vault.ItemVaultBlockEntity; import com.simibubi.create.content.logistics.item.LecternControllerBlockEntity; import com.simibubi.create.content.logistics.item.LecternControllerRenderer; import com.simibubi.create.content.logistics.trains.BogeyBlockEntityRenderer; +import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayBlockEntity; import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayRenderer; import com.simibubi.create.content.logistics.trains.management.edgePoint.observer.TrackObserverBlockEntity; @@ -831,8 +832,8 @@ public class AllBlockEntityTypes { public static final BlockEntityEntry TRACK = REGISTRATE .blockEntity("track", TrackBlockEntity::new) .instance(() -> TrackInstance::new) + .validBlocksDeferred(TrackMaterial::allBlocks) .renderer(() -> TrackRenderer::new) - .validBlocks(AllBlocks.TRACK) .register(); public static final BlockEntityEntry FAKE_TRACK = REGISTRATE @@ -851,7 +852,7 @@ public class AllBlockEntityTypes { .renderer(() -> StationRenderer::new) .validBlocks(AllBlocks.TRACK_STATION) .register(); - + public static final BlockEntityEntry SLIDING_DOOR = REGISTRATE.blockEntity("sliding_door", SlidingDoorBlockEntity::new) .renderer(() -> SlidingDoorRenderer::new) @@ -859,10 +860,10 @@ public class AllBlockEntityTypes { AllBlocks.BRASS_DOOR, AllBlocks.COPPER_DOOR) .register(); - public static final BlockEntityEntry COPYCAT = REGISTRATE - .blockEntity("copycat", CopycatBlockEntity::new) - .validBlocks(AllBlocks.COPYCAT_PANEL, AllBlocks.COPYCAT_STEP) - .register(); + public static final BlockEntityEntry COPYCAT = + REGISTRATE.blockEntity("copycat", CopycatBlockEntity::new) + .validBlocks(AllBlocks.COPYCAT_PANEL, AllBlocks.COPYCAT_STEP) + .register(); public static final BlockEntityEntry FLAP_DISPLAY = REGISTRATE .blockEntity("flap_display", FlapDisplayBlockEntity::new) diff --git a/src/main/java/com/simibubi/create/AllBlocks.java b/src/main/java/com/simibubi/create/AllBlocks.java index 7cafbf7db..72fec17d1 100644 --- a/src/main/java/com/simibubi/create/AllBlocks.java +++ b/src/main/java/com/simibubi/create/AllBlocks.java @@ -230,6 +230,8 @@ import com.simibubi.create.content.logistics.block.vault.ItemVaultBlock; import com.simibubi.create.content.logistics.block.vault.ItemVaultCTBehaviour; import com.simibubi.create.content.logistics.block.vault.ItemVaultItem; import com.simibubi.create.content.logistics.item.LecternControllerBlock; +import com.simibubi.create.content.logistics.trains.BogeySizes; +import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayBlock; import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType; import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBlockItem; @@ -718,7 +720,7 @@ public class AllBlocks { .onRegister(movementBehaviour(new BlazeBurnerMovementBehaviour())) .onRegister(interactionBehaviour(new BlazeBurnerInteractionBehaviour())) .item(BlazeBurnerBlockItem::withBlaze) - .model(AssetLookup.customBlockItemModel("blaze_burner", "block_with_blaze")) + .model(AssetLookup.customBlockItemModel("blaze_burner", "block_with_blaze")) .build() .register(); @@ -938,7 +940,7 @@ public class AllBlocks { .onRegister(assignDataBehaviour(new BoilerDisplaySource(), "boiler_status")) .addLayer(() -> RenderType::cutoutMipped) .item(FluidTankItem::new) - .model(AssetLookup.customBlockItemModel("_", "block_single_window")) + .model(AssetLookup.customBlockItemModel("_", "block_single_window")) .build() .register(); @@ -1541,7 +1543,7 @@ public class AllBlocks { .transform(customItemModel()) .register(); - public static final BlockEntry TRACK = REGISTRATE.block("track", TrackBlock::new) + public static final BlockEntry TRACK = REGISTRATE.block("track", TrackMaterial.ANDESITE::createBlock) .initialProperties(Material.STONE) .properties(p -> p.color(MaterialColor.METAL) .strength(0.8F) @@ -1552,6 +1554,8 @@ public class AllBlocks { .onRegister(CreateRegistrate.blockModel(() -> TrackModel::new)) .blockstate(new TrackBlockStateGenerator()::generate) .tag(AllBlockTags.RELOCATION_NOT_SUPPORTED.tag) + .tag(AllBlockTags.TRACKS.tag) + .tag(AllBlockTags.GIRDABLE_TRACKS.tag) .lang("Train Track") .item(TrackBlockItem::new) .model((c, p) -> p.generated(c, Create.asResource("item/" + c.getName()))) @@ -1620,13 +1624,13 @@ public class AllBlocks { .register(); public static final BlockEntry SMALL_BOGEY = - REGISTRATE.block("small_bogey", p -> new StandardBogeyBlock(p, false)) + REGISTRATE.block("small_bogey", p -> new StandardBogeyBlock(p, BogeySizes.SMALL)) .properties(p -> p.color(MaterialColor.PODZOL)) .transform(BuilderTransformers.bogey()) .register(); public static final BlockEntry LARGE_BOGEY = - REGISTRATE.block("large_bogey", p -> new StandardBogeyBlock(p, true)) + REGISTRATE.block("large_bogey", p -> new StandardBogeyBlock(p, BogeySizes.LARGE)) .properties(p -> p.color(MaterialColor.PODZOL)) .transform(BuilderTransformers.bogey()) .register(); @@ -1765,7 +1769,6 @@ public class AllBlocks { REGISTRATE.block("creative_crate", CreativeCrateBlock::new) .transform(BuilderTransformers.crate("creative")) .properties(p -> p.color(MaterialColor.COLOR_PURPLE)) - .tag(AllBlockTags.SAFE_NBT.tag) .register(); public static final BlockEntry DISPLAY_LINK = @@ -1861,7 +1864,6 @@ public class AllBlocks { public static final BlockEntry PLACARD = REGISTRATE.block("placard", PlacardBlock::new) .initialProperties(SharedProperties::copperMetal) .transform(pickaxeOnly()) - .tag(AllBlockTags.SAFE_NBT.tag) .blockstate((c, p) -> p.horizontalFaceBlock(c.get(), AssetLookup.standardModel(c, p))) .simpleItem() .register(); diff --git a/src/main/java/com/simibubi/create/AllBogeyStyles.java b/src/main/java/com/simibubi/create/AllBogeyStyles.java new file mode 100644 index 000000000..dde09ec26 --- /dev/null +++ b/src/main/java/com/simibubi/create/AllBogeyStyles.java @@ -0,0 +1,122 @@ +package com.simibubi.create; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.collect.ImmutableMap; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; +import com.simibubi.create.content.logistics.trains.BogeyRenderer; +import com.simibubi.create.content.logistics.trains.BogeyRenderer.CommonRenderer; +import com.simibubi.create.content.logistics.trains.BogeySizes; +import com.simibubi.create.content.logistics.trains.StandardBogeyRenderer.CommonStandardBogeyRenderer; +import com.simibubi.create.content.logistics.trains.StandardBogeyRenderer.LargeStandardBogeyRenderer; +import com.simibubi.create.content.logistics.trains.StandardBogeyRenderer.SmallStandardBogeyRenderer; +import com.simibubi.create.content.logistics.trains.entity.BogeyStyle; +import com.simibubi.create.foundation.utility.Components; +import com.simibubi.create.foundation.utility.Lang; +import com.tterrag.registrate.util.entry.BlockEntry; + +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +public class AllBogeyStyles { + public static final Map BOGEY_STYLES = new HashMap<>(); + public static final Map> CYCLE_GROUPS = new HashMap<>(); + private static final Map EMPTY_GROUP = ImmutableMap.of(); + + public static Map getCycleGroup(ResourceLocation cycleGroup) { + return CYCLE_GROUPS.getOrDefault(cycleGroup, EMPTY_GROUP); + } + + public static final String STANDARD_CYCLE_GROUP = "standard"; + + public static final BogeyStyle STANDARD = + create("standard", STANDARD_CYCLE_GROUP).commonRenderer(CommonStandardBogeyRenderer::new) + .displayName(Components.translatable("create.bogey.style.standard")) + .size(BogeySizes.SMALL, SmallStandardBogeyRenderer::new, AllBlocks.SMALL_BOGEY) + .size(BogeySizes.LARGE, LargeStandardBogeyRenderer::new, AllBlocks.LARGE_BOGEY) + .build(); + + private static BogeyStyleBuilder create(String name, String cycleGroup) { + return create(Create.asResource(name), Create.asResource(cycleGroup)); + } + + public static BogeyStyleBuilder create(ResourceLocation name, ResourceLocation cycleGroup) { + return new BogeyStyleBuilder(name, cycleGroup); + } + + public static void register() {} + + public static class BogeyStyleBuilder { + protected final Map sizes = new HashMap<>(); + protected final ResourceLocation name; + protected final ResourceLocation cycleGroup; + + protected Component displayName = Lang.translateDirect("bogey.style.invalid"); + protected ResourceLocation soundType = AllSoundEvents.TRAIN2.getId(); + protected CompoundTag defaultData = new CompoundTag(); + protected ParticleOptions contactParticle = ParticleTypes.CRIT; + protected ParticleOptions smokeParticle = ParticleTypes.POOF; + protected Optional> commonRenderer = Optional.empty(); + + public BogeyStyleBuilder(ResourceLocation name, ResourceLocation cycleGroup) { + this.name = name; + this.cycleGroup = cycleGroup; + } + + public BogeyStyleBuilder displayName(Component displayName) { + this.displayName = displayName; + return this; + } + + public BogeyStyleBuilder soundType(ResourceLocation soundType) { + this.soundType = soundType; + return this; + } + + public BogeyStyleBuilder defaultData(CompoundTag defaultData) { + this.defaultData = defaultData; + return this; + } + + public BogeyStyleBuilder size(BogeySizes.BogeySize size, Supplier renderer, + BlockEntry> blockEntry) { + this.size(size, renderer, blockEntry.getId()); + return this; + } + + public BogeyStyleBuilder size(BogeySizes.BogeySize size, Supplier renderer, + ResourceLocation location) { + this.sizes.put(size, new BogeyStyle.SizeData(location, renderer, renderer.get())); + return this; + } + + public BogeyStyleBuilder contactParticle(ParticleOptions contactParticle) { + this.contactParticle = contactParticle; + return this; + } + + public BogeyStyleBuilder smokeParticle(ParticleOptions smokeParticle) { + this.smokeParticle = smokeParticle; + return this; + } + + public BogeyStyleBuilder commonRenderer(Supplier commonRenderer) { + this.commonRenderer = Optional.of(commonRenderer); + return this; + } + + public BogeyStyle build() { + BogeyStyle entry = + new BogeyStyle(name, cycleGroup, displayName, soundType, contactParticle, smokeParticle, defaultData, sizes, commonRenderer); + BOGEY_STYLES.put(name, entry); + CYCLE_GROUPS.computeIfAbsent(cycleGroup, l -> new HashMap<>()).put(name, entry); + return entry; + } + } +} diff --git a/src/main/java/com/simibubi/create/AllItems.java b/src/main/java/com/simibubi/create/AllItems.java index f367359a8..831586dda 100644 --- a/src/main/java/com/simibubi/create/AllItems.java +++ b/src/main/java/com/simibubi/create/AllItems.java @@ -228,14 +228,16 @@ public class AllItems { // wrapped by COPPER_BACKTANK for block placement uses. // must be registered as of 1.18.2 public static final ItemEntry COPPER_BACKTANK_PLACEABLE = REGISTRATE - .item("copper_backtank_placeable", p -> new BacktankBlockItem(AllBlocks.COPPER_BACKTANK.get(), AllItems.COPPER_BACKTANK::get, p)) + .item("copper_backtank_placeable", + p -> new BacktankBlockItem(AllBlocks.COPPER_BACKTANK.get(), AllItems.COPPER_BACKTANK::get, p)) .model((c, p) -> p.withExistingParent(c.getName(), p.mcLoc("item/barrier"))) .register(); // wrapped by NETHERITE_BACKTANK for block placement uses. // must be registered as of 1.18.2 public static final ItemEntry NETHERITE_BACKTANK_PLACEABLE = REGISTRATE - .item("netherite_backtank_placeable", p -> new BacktankBlockItem(AllBlocks.NETHERITE_BACKTANK.get(), AllItems.NETHERITE_BACKTANK::get, p)) + .item("netherite_backtank_placeable", + p -> new BacktankBlockItem(AllBlocks.NETHERITE_BACKTANK.get(), AllItems.NETHERITE_BACKTANK::get, p)) .model((c, p) -> p.withExistingParent(c.getName(), p.mcLoc("item/barrier"))) .register(); @@ -248,6 +250,7 @@ public class AllItems { COPPER_BACKTANK_PLACEABLE)) .model(AssetLookup.customGenericItemModel("_", "item")) .tag(AllItemTags.PRESSURIZED_AIR_SOURCES.tag) + .tag(forgeItemTag("armors/chestplates")) .register(), NETHERITE_BACKTANK = REGISTRATE @@ -257,6 +260,7 @@ public class AllItems { .model(AssetLookup.customGenericItemModel("_", "item")) .properties(p -> p.fireResistant()) .tag(AllItemTags.PRESSURIZED_AIR_SOURCES.tag) + .tag(forgeItemTag("armors/chestplates")) .register(); public static final ItemEntry @@ -265,12 +269,14 @@ public class AllItems { REGISTRATE .item("copper_diving_helmet", p -> new DivingHelmetItem(AllArmorMaterials.COPPER, p, Create.asResource("copper_diving"))) + .tag(forgeItemTag("armors/helmets")) .register(), NETHERITE_DIVING_HELMET = REGISTRATE .item("netherite_diving_helmet", p -> new DivingHelmetItem(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving"))) .properties(p -> p.fireResistant()) + .tag(forgeItemTag("armors/helmets")) .register(); public static final ItemEntry @@ -279,12 +285,14 @@ public class AllItems { REGISTRATE .item("copper_diving_boots", p -> new DivingBootsItem(AllArmorMaterials.COPPER, p, Create.asResource("copper_diving"))) + .tag(forgeItemTag("armors/boots")) .register(), NETHERITE_DIVING_BOOTS = REGISTRATE .item("netherite_diving_boots", p -> new DivingBootsItem(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving"))) .properties(p -> p.fireResistant()) + .tag(forgeItemTag("armors/boots")) .register(); public static final ItemEntry SAND_PAPER = REGISTRATE.item("sand_paper", SandPaperItem::new) diff --git a/src/main/java/com/simibubi/create/AllTags.java b/src/main/java/com/simibubi/create/AllTags.java index 8dee1936a..500d022b9 100644 --- a/src/main/java/com/simibubi/create/AllTags.java +++ b/src/main/java/com/simibubi/create/AllTags.java @@ -14,6 +14,7 @@ import net.minecraft.tags.BlockTags; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; @@ -80,6 +81,8 @@ public class AllTags { SAFE_NBT, SEATS, TOOLBOXES, + TRACKS, + GIRDABLE_TRACKS, TREE_ATTACHMENTS, VALVE_HANDLES, WINDMILL_SAILS, @@ -131,6 +134,10 @@ public class AllTags { .is(tag); } + public boolean matches(ItemStack stack) { + return stack != null && stack.getItem() instanceof BlockItem blockItem && matches(blockItem.getBlock()); + } + public boolean matches(BlockState state) { return state.is(tag); } diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index 42fabc824..1b95e93df 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -2,6 +2,8 @@ package com.simibubi.create; import java.util.Random; +import com.simibubi.create.content.logistics.trains.BogeySizes; + import org.slf4j.Logger; import com.google.gson.Gson; @@ -9,6 +11,7 @@ import com.google.gson.GsonBuilder; import com.mojang.logging.LogUtils; import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour; import com.simibubi.create.compat.Mods; +import com.simibubi.create.compat.computercraft.ComputerCraftProxy; import com.simibubi.create.compat.curios.Curios; import com.simibubi.create.content.contraptions.TorquePropagator; import com.simibubi.create.content.contraptions.fluids.tank.BoilerHeaters; @@ -125,6 +128,8 @@ public class Create { AllFeatures.register(modEventBus); AllPlacementModifiers.register(modEventBus); BuiltinRegistration.register(modEventBus); + BogeySizes.init(); + AllBogeyStyles.register(); AllConfigs.register(modLoadingContext); @@ -134,6 +139,7 @@ public class Create { ContraptionMovementSetting.registerDefaults(); AllArmInteractionPointTypes.register(); BlockSpoutingBehaviour.registerDefaults(); + ComputerCraftProxy.register(); ForgeMod.enableMilkFluid(); CopperRegistries.inject(); diff --git a/src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java b/src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java new file mode 100644 index 000000000..af58070d2 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/event/TrackGraphMergeEvent.java @@ -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; + } +} diff --git a/src/main/java/com/simibubi/create/compat/Mods.java b/src/main/java/com/simibubi/create/compat/Mods.java index 55fe10952..36bd60e52 100644 --- a/src/main/java/com/simibubi/create/compat/Mods.java +++ b/src/main/java/com/simibubi/create/compat/Mods.java @@ -5,7 +5,10 @@ import java.util.function.Supplier; 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.registries.ForgeRegistries; /** * 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, TCONSTRUCT, CURIOS, + + COMPUTERCRAFT, STORAGEDRAWERS, XLPACKETS; @@ -51,4 +56,8 @@ public enum Mods { toExecute.get().run(); } } + + public Block getBlock(String id) { + return ForgeRegistries.BLOCKS.getValue(new ResourceLocation(asId(), id)); + } } diff --git a/src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java b/src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java new file mode 100644 index 000000000..ef2777d1a --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/AbstractComputerBehaviour.java @@ -0,0 +1,57 @@ +package com.simibubi.create.compat.computercraft; + +import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; +import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; +import com.simibubi.create.foundation.blockEntity.behaviour.BehaviourType; + +import net.minecraft.nbt.CompoundTag; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; + +public class AbstractComputerBehaviour extends BlockEntityBehaviour { + + public static final BehaviourType TYPE = new BehaviourType<>(); + + boolean hasAttachedComputer; + + public AbstractComputerBehaviour(SmartBlockEntity 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 boolean isPeripheralCap(Capability cap) { + return false; + } + + public LazyOptional 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; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java b/src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java new file mode 100644 index 000000000..0b40217ab --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/AttachedComputerPacket.java @@ -0,0 +1,37 @@ +package com.simibubi.create.compat.computercraft; + +import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; +import com.simibubi.create.foundation.blockEntity.SyncedBlockEntity; +import com.simibubi.create.foundation.networking.BlockEntityDataPacket; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; + +public class AttachedComputerPacket extends BlockEntityDataPacket { + + 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(SyncedBlockEntity tile) { + if (tile instanceof SmartBlockEntity smartTile) { + smartTile.getBehaviour(AbstractComputerBehaviour.TYPE) + .setHasAttachedComputer(hasAttachedComputer); + } + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java b/src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java new file mode 100644 index 000000000..04c27c8ec --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/ComputerCraftProxy.java @@ -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.blockEntity.SmartBlockEntity; + +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 fallbackFactory; + private static Function computerFactory; + + public static AbstractComputerBehaviour behaviour(SmartBlockEntity sbe) { + if (computerFactory == null) + return fallbackFactory.apply(sbe); + return computerFactory.apply(sbe); + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java b/src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java new file mode 100644 index 000000000..2d55a2554 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/ComputerScreen.java @@ -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 displayTitle; + private final RenderWindowFunction additional; + private final Screen previousScreen; + private final Supplier hasAttachedComputer; + + private AbstractSimiWidget computerWidget; + private IconButton confirmButton; + + public ComputerScreen(Component title, @Nullable RenderWindowFunction additional, Screen previousScreen, Supplier hasAttachedComputer) { + this(title, () -> title, additional, previousScreen, hasAttachedComputer); + } + + public ComputerScreen(Component title, Supplier displayTitle, @Nullable RenderWindowFunction additional, Screen previousScreen, Supplier 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); + + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java b/src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java new file mode 100644 index 000000000..7e58c270d --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/FallbackComputerBehaviour.java @@ -0,0 +1,16 @@ +package com.simibubi.create.compat.computercraft; + +import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; + +public class FallbackComputerBehaviour extends AbstractComputerBehaviour { + + public FallbackComputerBehaviour(SmartBlockEntity te) { + super(te); + } + + @Override + public boolean hasAttachedComputer() { + return false; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java new file mode 100644 index 000000000..43c8a7211 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/ComputerBehaviour.java @@ -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.SpeedControllerBlockEntity; +import com.simibubi.create.content.contraptions.relays.advanced.sequencer.SequencedGearshiftBlockEntity; +import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeBlockEntity; +import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeBlockEntity; +import com.simibubi.create.content.logistics.block.display.DisplayLinkBlockEntity; +import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationBlockEntity; +import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; + +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 PERIPHERAL_CAPABILITY = + CapabilityManager.get(new CapabilityToken<>() { + }); + LazyOptional peripheral; + NonNullSupplier peripheralSupplier; + + public ComputerBehaviour(SmartBlockEntity te) { + super(te); + this.peripheralSupplier = getPeripheralFor(te); + } + + public static NonNullSupplier getPeripheralFor(SmartBlockEntity te) { + if (te instanceof SpeedControllerBlockEntity scte) + return () -> new SpeedControllerPeripheral(scte, scte.targetSpeed); + if (te instanceof DisplayLinkBlockEntity dlte) + return () -> new DisplayLinkPeripheral(dlte); + if (te instanceof SequencedGearshiftBlockEntity sgte) + return () -> new SequencedGearshiftPeripheral(sgte); + if (te instanceof SpeedGaugeBlockEntity sgte) + return () -> new SpeedGaugePeripheral(sgte); + if (te instanceof StressGaugeBlockEntity sgte) + return () -> new StressGaugePeripheral(sgte); + if (te instanceof StationBlockEntity ste) + return () -> new StationPeripheral(ste); + + throw new IllegalArgumentException("No peripheral available for " + te.getType() + .getRegistryName()); + } + + @Override + public boolean isPeripheralCap(Capability cap) { + return cap == PERIPHERAL_CAPABILITY; + } + + @Override + public LazyOptional getPeripheralCapability() { + if (peripheral == null || !peripheral.isPresent()) + peripheral = LazyOptional.of(peripheralSupplier); + return peripheral.cast(); + } + + @Override + public void removePeripheral() { + if (peripheral != null) + peripheral.invalidate(); + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java new file mode 100644 index 000000000..3c957274e --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/CreateLuaTable.java @@ -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 { + + private final Map 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 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 stringKeySet() throws LuaException { + Set 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 tableValues() throws LuaException { + List 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 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 keySet() { + return map.keySet(); + } + + @NotNull + @Override + public Collection values() { + return map.values(); + } + + @NotNull + @Override + public Set> entrySet() { + return map.entrySet(); + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java new file mode 100644 index 000000000..cff49977e --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/DisplayLinkPeripheral.java @@ -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.DisplayLinkBlockEntity; +import com.simibubi.create.content.logistics.block.display.DisplayLinkContext; +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 { + + public static final String TAG_KEY = "ComputerSourceList"; + private final AtomicInteger cursorX = new AtomicInteger(); + private final AtomicInteger cursorY = new AtomicInteger(); + + public DisplayLinkPeripheral(DisplayLinkBlockEntity 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"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java new file mode 100644 index 000000000..26574bd7f --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SequencedGearshiftPeripheral.java @@ -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.SequencedGearshiftBlockEntity; +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 { + + public SequencedGearshiftPeripheral(SequencedGearshiftBlockEntity 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"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java new file mode 100644 index 000000000..a10f594f7 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedControllerPeripheral.java @@ -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.SpeedControllerBlockEntity; +import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.ScrollValueBehaviour; + +import dan200.computercraft.api.lua.LuaFunction; + +public class SpeedControllerPeripheral extends SyncedPeripheral { + + private final ScrollValueBehaviour targetSpeed; + + public SpeedControllerPeripheral(SpeedControllerBlockEntity 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"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java new file mode 100644 index 000000000..a2ff521c5 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SpeedGaugePeripheral.java @@ -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.SpeedGaugeBlockEntity; + +import dan200.computercraft.api.lua.LuaFunction; + +public class SpeedGaugePeripheral extends SyncedPeripheral { + + public SpeedGaugePeripheral(SpeedGaugeBlockEntity tile) { + super(tile); + } + + @LuaFunction + public final float getSpeed() { + return this.tile.getSpeed(); + } + + @NotNull + @Override + public String getType() { + return "Create_Speedometer"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java new file mode 100644 index 000000000..552304fb8 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StationPeripheral.java @@ -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.StationBlockEntity; +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 { + + public StationPeripheral(StationBlockEntity 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.getChannel().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"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java new file mode 100644 index 000000000..480ad12ab --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/StressGaugePeripheral.java @@ -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.StressGaugeBlockEntity; + +import dan200.computercraft.api.lua.LuaFunction; + +public class StressGaugePeripheral extends SyncedPeripheral { + + public StressGaugePeripheral(StressGaugeBlockEntity 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"; + } + +} diff --git a/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java new file mode 100644 index 000000000..59b378619 --- /dev/null +++ b/src/main/java/com/simibubi/create/compat/computercraft/implementation/peripherals/SyncedPeripheral.java @@ -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.blockEntity.SmartBlockEntity; +import com.simibubi.create.foundation.networking.AllPackets; + +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraftforge.network.PacketDistributor; + +public abstract class SyncedPeripheral 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.getChannel().send(PacketDistributor.ALL.noArg(), new AttachedComputerPacket(tile.getBlockPos(), hasAttachedComputer)); + } + + @Override + public boolean equals(@Nullable IPeripheral other) { + return this == other; + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/actors/DrillMovementBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/components/actors/DrillMovementBehaviour.java index 1a7a7e863..9e1408373 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/actors/DrillMovementBehaviour.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/actors/DrillMovementBehaviour.java @@ -4,7 +4,7 @@ import javax.annotation.Nullable; import com.jozufozu.flywheel.api.MaterialManager; import com.jozufozu.flywheel.core.virtual.VirtualRenderWorld; -import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllTags; import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext; import com.simibubi.create.content.contraptions.components.structureMovement.render.ActorInstance; import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionMatrices; @@ -62,7 +62,7 @@ public class DrillMovementBehaviour extends BlockBreakingMovementBehaviour { @Override public boolean canBreak(Level world, BlockPos breakingPos, BlockState state) { return super.canBreak(world, breakingPos, state) && !state.getCollisionShape(world, breakingPos) - .isEmpty() && !AllBlocks.TRACK.has(state); + .isEmpty() && !AllTags.AllBlockTags.TRACKS.matches(state); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlockEntity.java index 804fe8404..3e38d233d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlockEntity.java @@ -3,8 +3,8 @@ package com.simibubi.create.content.contraptions.components.press; import java.util.List; import java.util.Optional; -import com.simibubi.create.AllBlocks; import com.simibubi.create.AllRecipeTypes; +import com.simibubi.create.AllTags; import com.simibubi.create.content.contraptions.components.crafter.MechanicalCraftingRecipe; import com.simibubi.create.content.contraptions.components.press.PressingBehaviour.Mode; import com.simibubi.create.content.contraptions.components.press.PressingBehaviour.PressingBehaviourSpecifics; @@ -68,7 +68,7 @@ public class MechanicalPressBlockEntity extends BasinOperatingBlockEntity implem public void onItemPressed(ItemStack result) { award(AllAdvancements.PRESS); - if (AllBlocks.TRACK.isIn(result)) + if (AllTags.AllBlockTags.TRACKS.matches(result)) tracksCreated += result.getCount(); if (tracksCreated >= 1000) { award(AllAdvancements.TRACK_CRAFTING); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java index d260e44a6..e305af2aa 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java @@ -29,7 +29,7 @@ import com.simibubi.create.content.contraptions.fluids.tank.FluidTankBlock; import com.simibubi.create.content.curiosities.deco.SlidingDoorBlock; import com.simibubi.create.content.logistics.block.redstone.RedstoneLinkBlock; import com.simibubi.create.content.logistics.block.vault.ItemVaultBlock; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.content.logistics.trains.ITrackBlock; import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationBlock; import com.simibubi.create.foundation.config.ContraptionMovementSetting; @@ -338,7 +338,7 @@ public class BlockMovementChecks { return direction == state.getValue(StickerBlock.FACING) && !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite()); } - if (block instanceof IBogeyBlock bogey) + if (block instanceof AbstractBogeyBlock bogey) return bogey.getStickySurfaces(world, pos, state) .contains(direction); if (block instanceof WhistleBlock) diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java index 71b449b01..0e96930e2 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java @@ -64,7 +64,7 @@ import com.simibubi.create.content.curiosities.deco.SlidingDoorBlock; import com.simibubi.create.content.logistics.block.inventories.CreativeCrateBlockEntity; import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock; import com.simibubi.create.content.logistics.block.vault.ItemVaultBlockEntity; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.foundation.blockEntity.IMultiBlockEntityContainer; import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour; import com.simibubi.create.foundation.config.AllConfigs; @@ -347,7 +347,7 @@ public abstract class Contraption { } // Bogeys tend to have sticky sides - if (state.getBlock()instanceof IBogeyBlock bogey) + if (state.getBlock()instanceof AbstractBogeyBlock bogey) for (Direction d : bogey.getStickySurfaces(world, pos, state)) if (!visited.contains(pos.relative(d))) frontier.add(pos.relative(d)); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java index c7bef330b..d3cd16ded 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/gantry/GantryContraptionEntity.java @@ -202,6 +202,7 @@ public class GantryContraptionEntity extends AbstractContraptionEntity { } @Override + @OnlyIn(Dist.CLIENT) public void applyLocalTransforms(PoseStack matrixStack, float partialTicks) {} public void updateClientMotion() { diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelInstance.java b/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelInstance.java index f125d09c2..8fac23bb8 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelInstance.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelInstance.java @@ -42,7 +42,6 @@ public class WaterWheelInstance extends CutoutR return getRotatingMaterial().model(key, () -> { BakedModel model = WaterWheelRenderer.generateModel(key); BlockState state = key.state(); - // TODO waterwheels Direction dir; if (key.large()) { dir = Direction.fromAxisAndDirection(state.getValue(LargeWaterWheelBlock.AXIS), AxisDirection.POSITIVE); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelRenderer.java index dc350ba26..5884b8bb2 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelRenderer.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/waterwheel/WaterWheelRenderer.java @@ -3,9 +3,7 @@ package com.simibubi.create.content.contraptions.components.waterwheel; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Random; -import com.jozufozu.flywheel.core.PartialModel; import com.jozufozu.flywheel.core.StitchedSprite; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.AllPartialModels; @@ -32,6 +30,7 @@ import net.minecraft.util.RandomSource; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.client.model.data.ModelData; import net.minecraftforge.registries.ForgeRegistries; public class WaterWheelRenderer extends KineticBlockEntityRenderer { @@ -41,6 +40,8 @@ public class WaterWheelRenderer extends Kinetic public static final StitchedSprite OAK_LOG_TEMPLATE = new StitchedSprite(new ResourceLocation("block/oak_log")); public static final StitchedSprite OAK_LOG_TOP_TEMPLATE = new StitchedSprite(new ResourceLocation("block/oak_log_top")); + private static final String[] LOG_SUFFIXES = new String[] { "_log", "_stem" }; + protected final boolean large; public WaterWheelRenderer(Context context, boolean large) { @@ -60,9 +61,8 @@ public class WaterWheelRenderer extends Kinetic protected SuperByteBuffer getRotatedModel(T be, BlockState state) { WaterWheelModelKey key = new WaterWheelModelKey(large, state, be.material); return CreateClient.BUFFER_CACHE.get(WATER_WHEEL, key, () -> { - BakedModel model = WaterWheelRenderer.generateModel(key); + BakedModel model = generateModel(key); BlockState state1 = key.state(); - // TODO waterwheels Direction dir; if (key.large()) { dir = Direction.fromAxisAndDirection(state1.getValue(LargeWaterWheelBlock.AXIS), AxisDirection.POSITIVE); @@ -74,25 +74,24 @@ public class WaterWheelRenderer extends Kinetic }); } - public static PartialModel getTemplateModel(boolean large, boolean extension) { - if (large) { + public static BakedModel generateModel(WaterWheelModelKey key) { + BakedModel template; + if (key.large()) { + boolean extension = key.state() + .getValue(LargeWaterWheelBlock.EXTENSION); if (extension) { - return AllPartialModels.LARGE_WATER_WHEEL_EXTENSION; + template = AllPartialModels.LARGE_WATER_WHEEL_EXTENSION.get(); } else { - return AllPartialModels.LARGE_WATER_WHEEL; + template = AllPartialModels.LARGE_WATER_WHEEL.get(); } } else { - return AllPartialModels.WATER_WHEEL; + template = AllPartialModels.WATER_WHEEL.get(); } + + return generateModel(template, key.material()); } - public static BakedModel generateModel(WaterWheelModelKey key) { - boolean extension = key.state() - .getOptionalValue(LargeWaterWheelBlock.EXTENSION) - .orElse(false); - BakedModel template = getTemplateModel(key.large(), extension).get(); - - BlockState planksBlockState = key.material(); + public static BakedModel generateModel(BakedModel template, BlockState planksBlockState) { Block planksBlock = planksBlockState.getBlock(); ResourceLocation id = RegisteredObjects.getKeyOrThrow(planksBlock); String path = id.getPath(); @@ -104,7 +103,7 @@ public class WaterWheelRenderer extends Kinetic Map map = new Reference2ReferenceOpenHashMap<>(); map.put(OAK_PLANKS_TEMPLATE.get(), getSpriteOnSide(planksBlockState, Direction.UP)); - map.put(OAK_LOG_TEMPLATE.get(), getSpriteOnSide(logBlockState, Direction.NORTH)); + map.put(OAK_LOG_TEMPLATE.get(), getSpriteOnSide(logBlockState, Direction.SOUTH)); map.put(OAK_LOG_TOP_TEMPLATE.get(), getSpriteOnSide(logBlockState, Direction.UP)); return BakedModelHelper.generateModel(template, map::get); @@ -114,7 +113,7 @@ public class WaterWheelRenderer extends Kinetic } private static BlockState getLogBlockState(String namespace, String wood) { - for (String suffix : new String[] { "_log", "_stem" }) { + for (String suffix : LOG_SUFFIXES) { Optional state = ForgeRegistries.BLOCKS.getHolder(new ResourceLocation(namespace, wood + suffix)) .map(Holder::value) @@ -125,18 +124,29 @@ public class WaterWheelRenderer extends Kinetic return Blocks.OAK_LOG.defaultBlockState(); } - private static TextureAtlasSprite getSpriteOnSide(BlockState blockstate, Direction side) { - BakedModel blockModel = Minecraft.getInstance() + private static TextureAtlasSprite getSpriteOnSide(BlockState state, Direction side) { + BakedModel model = Minecraft.getInstance() .getBlockRenderer() - .getBlockModel(blockstate); - if (blockModel == null) + .getBlockModel(state); + if (model == null) return null; - @SuppressWarnings("deprecation") - List quads = blockModel.getQuads(blockstate, side, RandomSource.create()); - if (quads.isEmpty()) - return null; - return quads.get(0) - .getSprite(); + RandomSource random = RandomSource.create(); + random.setSeed(42L); + List quads = model.getQuads(state, side, random, ModelData.EMPTY, null); + if (!quads.isEmpty()) { + return quads.get(0) + .getSprite(); + } + random.setSeed(42L); + quads = model.getQuads(state, null, random, ModelData.EMPTY, null); + if (!quads.isEmpty()) { + for (BakedQuad quad : quads) { + if (quad.getDirection() == side) { + return quad.getSprite(); + } + } + } + return model.getParticleIcon(ModelData.EMPTY); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java index da5c00300..5d541ad75 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/OpenEndedPipe.java @@ -25,6 +25,7 @@ import net.minecraft.tags.BlockTags; import net.minecraft.tags.FluidTags; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; 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.Fluids; import net.minecraft.world.phys.AABB; +import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.common.Tags; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fluids.FluidStack; @@ -54,6 +56,7 @@ public class OpenEndedPipe extends FlowSource { registerEffectHandler(new MilkEffectHandler()); registerEffectHandler(new WaterEffectHandler()); registerEffectHandler(new LavaEffectHandler()); + registerEffectHandler(new TeaEffectHandler()); } 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 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)); + } + } + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java index 063a52eae..b5883e2d7 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/HosePulleyFluidHandler.java @@ -125,4 +125,8 @@ public class HosePulleyFluidHandler implements IFluidHandler { return internalTank.isFluidValid(tank, stack); } + public SmartFluidTank getInternalTank() { + return internalTank; + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java index 2ecd2fe59..45fe2475a 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/SpoutRenderer.java @@ -38,14 +38,24 @@ public class SpoutRenderer extends SafeBlockEntityRenderer { .getValue(partialTicks); if (!fluidStack.isEmpty() && level != 0) { + boolean top = fluidStack.getFluid() + .getAttributes() + .isLighterThanAir(); + level = Math.max(level, 0.175f); float min = 2.5f / 16f; float max = min + (11 / 16f); float yOffset = (11 / 16f) * level; + ms.pushPose(); - ms.translate(0, yOffset, 0); - FluidRenderer.renderFluidBox(fluidStack, min, min - yOffset, min, max, min, max, buffer, ms, light, - false); + if (!top) ms.translate(0, yOffset, 0); + else ms.translate(0, max - min, 0); + + FluidRenderer.renderFluidBox(fluidStack, + min, min - yOffset, min, + max, min, max, + buffer, ms, light, false); + ms.popPose(); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java index 7783f5d25..f2b6a7e90 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/EncasedPipeBlock.java @@ -12,6 +12,8 @@ import java.util.function.Supplier; import com.simibubi.create.AllBlockEntityTypes; 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.FluidTransportBehaviour; 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.Level; 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.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; 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.ticks.TickPriority; -public class EncasedPipeBlock extends Block implements IWrenchable, ISpecialBlockItemRequirement, IBE, EncasedBlock { +public class EncasedPipeBlock extends Block + implements IWrenchable, ISpecialBlockItemRequirement, IBE, EncasedBlock, ITransformableBlock { public static final Map FACING_TO_PROPERTY_MAP = PipeBlock.PROPERTY_BY_DIRECTION; private final Supplier casing; @@ -173,4 +178,20 @@ public class EncasedPipeBlock extends Block implements IWrenchable, ISpecialBloc EncasedPipeBlock.transferSixWayProperties(state, defaultBlockState())); 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); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java index 52e002ecd..9ea1f5ef2 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlock.java @@ -7,6 +7,8 @@ import javax.annotation.Nullable; import com.simibubi.create.AllBlockEntityTypes; 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.FluidTransportBehaviour; 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.LevelAccessor; 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.Rotation; import net.minecraft.world.level.block.SimpleWaterloggedBlock; import net.minecraft.world.level.block.entity.BlockEntityType; 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.ticks.TickPriority; -public class FluidPipeBlock extends PipeBlock - implements SimpleWaterloggedBlock, IWrenchableWithBracket, IBE, EncasableBlock { +public class FluidPipeBlock extends PipeBlock implements SimpleWaterloggedBlock, IWrenchableWithBracket, + IBE, EncasableBlock, ITransformableBlock { 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) { 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); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java new file mode 100644 index 000000000..c60a3a2a7 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/FluidPipeBlockRotation.java @@ -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 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; + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/SmartFluidPipeBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/SmartFluidPipeBlock.java index b2026caa2..06b6053d6 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/SmartFluidPipeBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/pipes/SmartFluidPipeBlock.java @@ -92,8 +92,7 @@ public class SmartFluidPipeBlock extends FaceAttachedHorizontalDirectionalBlock boolean blockTypeChanged = state.getBlock() != newState.getBlock(); if (blockTypeChanged && !world.isClientSide) FluidPropagator.propagateChangedPipe(world, pos, state); - if (state.hasBlockEntity() && (blockTypeChanged || !newState.hasBlockEntity())) - world.removeBlockEntity(pos); + IBE.onRemove(state, world, pos, newState); } @Override diff --git a/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java b/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java index d3cb6f53e..09b25e0aa 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java +++ b/src/main/java/com/simibubi/create/content/contraptions/itemAssembly/SequencedAssemblyRecipe.java @@ -44,7 +44,8 @@ public class SequencedAssemblyRecipe implements Recipe { protected List> sequence; protected int loops; protected ProcessingOutput transitionalItem; - protected List resultPool; + + public final List resultPool; public SequencedAssemblyRecipe(ResourceLocation recipeId, SequencedAssemblyRecipeSerializer serializer) { this.id = recipeId; @@ -214,7 +215,7 @@ public class SequencedAssemblyRecipe implements Recipe { public boolean isSpecial() { return true; } - + @Override public RecipeType getType() { return AllRecipeTypes.SEQUENCED_ASSEMBLY.getType(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlockEntity.java index 28f5c8368..80dd29b5b 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlockEntity.java @@ -2,6 +2,11 @@ package com.simibubi.create.content.contraptions.relays.advanced; 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.base.KineticBlockEntity; 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.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; public class SpeedControllerBlockEntity extends KineticBlockEntity { public static final int DEFAULT_SPEED = 16; - protected ScrollValueBehaviour targetSpeed; + public ScrollValueBehaviour targetSpeed; + public AbstractComputerBehaviour computerBehaviour; boolean hasBracket; @@ -50,6 +58,7 @@ public class SpeedControllerBlockEntity extends KineticBlockEntity { targetSpeed.value = DEFAULT_SPEED; targetSpeed.withCallback(i -> this.updateTargetRotation()); behaviours.add(targetSpeed); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); registerAwardables(behaviours, AllAdvancements.SPEED_CONTROLLER); } @@ -125,6 +134,20 @@ public class SpeedControllerBlockEntity extends KineticBlockEntity { .isHorizontal(); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability 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 { @Override diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java index 3834762d8..190053b92 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/ConfigureSequencedGearshiftPacket.java @@ -35,6 +35,9 @@ public class ConfigureSequencedGearshiftPacket extends BlockEntityConfigurationP @Override protected void applySettings(SequencedGearshiftBlockEntity be) { + if (be.computerBehaviour.hasAttachedComputer()) + return; + be.run(-1); be.instructions = Instruction.deserializeAll(instructions); be.sendData(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java index 206cb444f..38af66e29 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/Instruction.java @@ -19,8 +19,12 @@ public class Instruction { } public Instruction(SequencerInstructions instruction, int value) { + this(instruction, InstructionSpeedModifiers.FORWARD, value); + } + + public Instruction(SequencerInstructions instruction, InstructionSpeedModifiers speedModifier, int value) { this.instruction = instruction; - speedModifier = InstructionSpeedModifiers.FORWARD; + this.speedModifier = speedModifier; this.value = value; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java index 93713d075..b1a1f236a 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/InstructionSpeedModifiers.java @@ -1,6 +1,7 @@ package com.simibubi.create.content.contraptions.relays.advanced.sequencer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import com.simibubi.create.foundation.utility.Components; @@ -36,4 +37,11 @@ public enum InstructionSpeedModifiers { return options; } + public static InstructionSpeedModifiers getByModifier(int modifier) { + return Arrays.stream(InstructionSpeedModifiers.values()) + .filter(speedModifier -> speedModifier.value == modifier) + .findAny() + .orElse(InstructionSpeedModifiers.FORWARD); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftBlockEntity.java index 453f5966c..717aa7ecd 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftBlockEntity.java @@ -1,9 +1,17 @@ package com.simibubi.create.content.contraptions.relays.advanced.sequencer; +import java.util.List; 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.relays.encased.SplitShaftBlockEntity; +import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; import com.simibubi.create.foundation.utility.NBTHelper; import net.minecraft.core.BlockPos; @@ -12,6 +20,8 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.world.level.block.entity.BlockEntityType; 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 { @@ -21,6 +31,8 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity { float currentInstructionProgress; int timer; boolean poweredPreviously; + + public AbstractComputerBehaviour computerBehaviour; public record SequenceContext(SequencerInstructions instruction, double relativeValue) { @@ -61,6 +73,12 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity { poweredPreviously = false; } + @Override + public void addBehaviours(List behaviours) { + super.addBehaviours(behaviours); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + } + @Override public void tick() { super.tick(); @@ -103,6 +121,8 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity { } public void onRedstoneUpdate(boolean isPowered, boolean isRunning) { + if (computerBehaviour.hasAttachedComputer()) + return; if (!poweredPreviously && isPowered) risingFlank(); 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); if (instruction == null || instruction.instruction == SequencerInstructions.END) { if (getModifier() != 0) @@ -193,6 +213,20 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity { super.read(compound, clientPacket); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability 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 public float getRotationSpeedModifier(Direction face) { if (isVirtual()) @@ -208,4 +242,8 @@ public class SequencedGearshiftBlockEntity extends SplitShaftBlockEntity { .getSpeedModifier(); } + public Vector getInstructions() { + return this.instructions; + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java index ef80d1cb2..6d703a69c 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/sequencer/SequencedGearshiftScreen.java @@ -4,6 +4,7 @@ import java.util.Vector; import com.mojang.blaze3d.vertex.PoseStack; 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.AllGuiTextures; 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.Lang; -import net.minecraft.core.BlockPos; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; 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 AllGuiTextures background = AllGuiTextures.SEQUENCER; private IconButton confirmButton; + private SequencedGearshiftBlockEntity be; private ListTag compareTag; private Vector instructions; - private BlockPos pos; private Vector> inputs; public SequencedGearshiftScreen(SequencedGearshiftBlockEntity be) { super(Lang.translateDirect("gui.sequenced_gearshift.title")); this.instructions = be.instructions; - this.pos = be.getBlockPos(); + this.be = be; compareTag = Instruction.serializeAll(instructions); } @Override protected void init() { + if (be.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen( + new ComputerScreen(title, this::renderAdditional, this, be.computerBehaviour::hasAttachedComputer)); + setWindowSize(background.width, background.height); setWindowOffset(-20, 0); super.init(); @@ -55,8 +59,7 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { for (int row = 0; row < instructions.size(); row++) initInputsOfRow(row, x, y); - confirmButton = - new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM); + confirmButton = new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM); confirmButton.withCallback(() -> { onClose(); }); @@ -127,6 +130,15 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { 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 protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { int x = guiLeft; @@ -134,6 +146,13 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { 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++) { AllGuiTextures toDraw = AllGuiTextures.SEQUENCER_EMPTY; 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); + renderAdditional(ms, mouseX, mouseY, partialTicks, x, y, background); + } - GuiGameElement.of(renderedItem) - .at(x + background.width + 6, y + background.height - 56, -200) + private void renderAdditional(PoseStack ms, int mouseX, int mouseY, float partialTicks, int guiLeft, int guiTop, + AllGuiTextures background) { + GuiGameElement.of(renderedItem).at(guiLeft + background.width + 6, guiTop + background.height - 56, 100) .scale(5) .render(ms); } @@ -172,7 +195,8 @@ public class SequencedGearshiftScreen extends AbstractSimiScreen { ListTag serialized = Instruction.serializeAll(instructions); if (serialized.equals(compareTag)) return; - AllPackets.getChannel().sendToServer(new ConfigureSequencedGearshiftPacket(pos, serialized)); + AllPackets.getChannel() + .sendToServer(new ConfigureSequencedGearshiftPacket(be.getBlockPos(), serialized)); } @Override diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeBlockEntity.java index c22749cb2..682052bdc 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/GaugeBlockEntity.java @@ -12,7 +12,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.entity.BlockEntityType; 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 dialState; @@ -52,4 +52,5 @@ public class GaugeBlockEntity extends KineticBlockEntity implements IHaveGoggleI return true; } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeBlockEntity.java index 2efbd7d16..2ae2188fd 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/SpeedGaugeBlockEntity.java @@ -2,24 +2,41 @@ package com.simibubi.create.content.contraptions.relays.gauge; 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.foundation.blockEntity.BlockEntityBehaviour; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.utility.Color; import com.simibubi.create.foundation.utility.Lang; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BlockEntityType; 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 AbstractComputerBehaviour computerBehaviour; + public SpeedGaugeBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); } + @Override + public void addBehaviours(List behaviours) { + super.addBehaviours(behaviours); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + } + @Override public void onSpeedChanged(float prevSpeed) { super.onSpeedChanged(prevSpeed); @@ -62,4 +79,19 @@ public class SpeedGaugeBlockEntity extends GaugeBlockEntity { .forGoggles(tooltip); return true; } + + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeBlockEntity.java index 2244fe051..fb7dc9941 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/gauge/StressGaugeBlockEntity.java @@ -2,6 +2,11 @@ package com.simibubi.create.content.contraptions.relays.gauge; 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.foundation.advancement.AllAdvancements; 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.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BlockEntityType; 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 AbstractComputerBehaviour computerBehaviour; + static BlockPos lastSent; public StressGaugeBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { @@ -30,6 +40,7 @@ public class StressGaugeBlockEntity extends GaugeBlockEntity { @Override public void addBehaviours(List behaviours) { super.addBehaviours(behaviours); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); registerAwardables(behaviours, AllAdvancements.STRESSOMETER, AllAdvancements.STRESSOMETER_MAXED); } @@ -141,4 +152,18 @@ public class StressGaugeBlockEntity extends GaugeBlockEntity { award(AllAdvancements.STRESSOMETER_MAXED); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + } diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java index 74d963c6f..0b440efc5 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java @@ -440,7 +440,7 @@ public class ClipboardScreen extends AbstractSimiScreen { if (currentEntries.get(editingIndex).text.getString() .isEmpty() && currentEntries.size() > 1) { currentEntries.remove(editingIndex); - editingIndex -= 1; + editingIndex = Math.max(0, editingIndex - 1); editContext.setCursorToEnd(); return true; } else if (hasControlDown()) { diff --git a/src/main/java/com/simibubi/create/content/curiosities/girder/GirderBlock.java b/src/main/java/com/simibubi/create/content/curiosities/girder/GirderBlock.java index 9766492bb..d7d369c71 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/girder/GirderBlock.java +++ b/src/main/java/com/simibubi/create/content/curiosities/girder/GirderBlock.java @@ -6,6 +6,7 @@ import static net.minecraft.world.level.block.state.properties.BlockStatePropert import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; import com.simibubi.create.AllShapes; +import com.simibubi.create.AllTags; import com.simibubi.create.content.contraptions.base.KineticBlockEntity; import com.simibubi.create.content.contraptions.fluids.pipes.BracketBlock; import com.simibubi.create.content.contraptions.relays.elementary.BracketedBlockEntityBehaviour; @@ -223,7 +224,7 @@ public class GirderBlock extends Block implements SimpleWaterloggedBlock, IWrenc for (Direction d2 : Iterate.directionsInAxis(axis == Axis.X ? Axis.Z : Axis.X)) { BlockState above = level.getBlockState(pos.above() .relative(d2)); - if (AllBlocks.TRACK.has(above)) { + if (AllTags.AllBlockTags.GIRDABLE_TRACKS.matches(above)) { TrackShape shape = above.getValue(TrackBlock.SHAPE); if (shape == (axis == Axis.X ? TrackShape.XO : TrackShape.ZO)) state = state.setValue(updateProperty, true); diff --git a/src/main/java/com/simibubi/create/content/curiosities/tools/BlueprintOverlayRenderer.java b/src/main/java/com/simibubi/create/content/curiosities/tools/BlueprintOverlayRenderer.java index 547b628e5..0a839692b 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/tools/BlueprintOverlayRenderer.java +++ b/src/main/java/com/simibubi/create/content/curiosities/tools/BlueprintOverlayRenderer.java @@ -9,7 +9,6 @@ import java.util.Optional; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; import com.simibubi.create.content.curiosities.tools.BlueprintEntity.BlueprintCraftingInventory; import com.simibubi.create.content.curiosities.tools.BlueprintEntity.BlueprintSection; @@ -106,7 +105,7 @@ public class BlueprintOverlayRenderer { int tracks = info.requiredTracks; while (tracks > 0) { - ingredients.add(Pair.of(AllBlocks.TRACK.asStack(Math.min(64, tracks)), info.hasRequiredTracks)); + ingredients.add(Pair.of(new ItemStack(info.trackMaterial.getBlock(), Math.min(64, tracks)), info.hasRequiredTracks)); tracks -= 64; } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelBlockEntity.java index 4795d42a4..026f017c6 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/belts/tunnel/BrassTunnelBlockEntity.java @@ -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.scrollvalue.INamedIconOptions; 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.utility.BlockHelper; import com.simibubi.create.foundation.utility.Components; @@ -174,7 +175,7 @@ public class BrassTunnelBlockEntity extends BeltTunnelBlockEntity implements IHa return; if (selectionMode.get() != SelectionMode.SYNCHRONIZE || syncedOutputActive) { - distributionProgress = 10; + distributionProgress = AllConfigs.server().logistics.brassTunnelTimer.get(); sendData(); } return; diff --git a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBlockEntity.java index 02fd38104..dc9748b71 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotBlockEntity.java @@ -7,6 +7,7 @@ import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; import net.minecraft.core.BlockPos; 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.state.BlockState; import net.minecraftforge.common.capabilities.Capability; @@ -33,4 +34,8 @@ public class DepotBlockEntity extends SmartBlockEntity { return depotBehaviour.getItemCapability(cap, side); return super.getCapability(cap, side); } + + public ItemStack getHeldItem() { + return depotBehaviour.getHeldItemStack(); + } } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java b/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java index 9ed9b1218..27a8aab6a 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/AllDisplayBehaviours.java @@ -9,6 +9,8 @@ import java.util.Map; import javax.annotation.Nullable; 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.DisplaySource; 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("enchant_power_display_source"), new EnchantPowerDisplaySource()), BlockEntityType.ENCHANTING_TABLE); 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")); + }); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkBlockEntity.java index 43fb1b603..54ae8af36 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/DisplayLinkBlockEntity.java @@ -2,6 +2,11 @@ package com.simibubi.create.content.logistics.block.display; 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.target.DisplayTarget; import com.simibubi.create.foundation.advancement.AllAdvancements; @@ -18,6 +23,8 @@ import net.minecraft.nbt.NbtUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.entity.BlockEntityType; 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 { @@ -31,8 +38,9 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity { public LerpedFloat glow; private boolean sendPulse; - + public int refreshTicks; + public AbstractComputerBehaviour computerBehaviour; public DisplayLinkBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); @@ -44,10 +52,16 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity { glow.chase(0, 0.5f, Chaser.EXP); } + @Override + public void addBehaviours(List behaviours) { + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); + registerAwardables(behaviours, AllAdvancements.DISPLAY_LINK, AllAdvancements.DISPLAY_BOARD); + } + @Override public void tick() { super.tick(); - + if (isVirtual()) { glow.tickChaser(); return; @@ -59,9 +73,9 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity { glow.tickChaser(); return; } - + refreshTicks++; - if (refreshTicks < activeSource.getPassiveRefreshTicks()) + if (refreshTicks < activeSource.getPassiveRefreshTicks() || !activeSource.shouldPassiveReset()) return; tickSource(); } @@ -114,13 +128,8 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity { activeSource.transferData(context, activeTarget, targetLine); sendPulse = true; sendData(); - - award(AllAdvancements.DISPLAY_LINK); - } - @Override - public void addBehaviours(List behaviours) { - registerAwardables(behaviours, AllAdvancements.DISPLAY_LINK, AllAdvancements.DISPLAY_BOARD); + award(AllAdvancements.DISPLAY_LINK); } @Override @@ -133,7 +142,7 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity { protected void write(CompoundTag tag, boolean clientPacket) { super.write(tag, clientPacket); writeGatheredData(tag); - if (clientPacket && activeTarget != null) + if (clientPacket && activeTarget != null) tag.putString("TargetType", activeTarget.id.toString()); if (clientPacket && sendPulse) { sendPulse = false; @@ -173,6 +182,21 @@ public class DisplayLinkBlockEntity extends SmartBlockEntity { sourceConfig = data.copy(); } + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability 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) { this.targetOffset = targetPosition.subtract(worldPosition); } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java b/src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java new file mode 100644 index 000000000..e3e988143 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/source/ComputerDisplaySource.java @@ -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 provideText(DisplayLinkContext context, DisplayTargetStats stats) { + List 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; + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java b/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java index f7470e161..3fff48f59 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/display/source/DisplaySource.java @@ -49,6 +49,10 @@ public abstract class DisplaySource extends DisplayBehaviour { return 100; }; + public boolean shouldPassiveReset() { + return true; + } + protected String getTranslationKey() { return id.getPath(); } diff --git a/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeBlockEntity.java index 8d1fe44fe..3cefc7e76 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/redstone/NixieTubeBlockEntity.java @@ -123,6 +123,10 @@ public class NixieTubeBlockEntity extends SmartBlockEntity { customText = Optional.empty(); } + public int getRedstoneStrength() { + return redstoneStrength; + } + // @Override diff --git a/src/main/java/com/simibubi/create/content/logistics/item/filter/FilterItem.java b/src/main/java/com/simibubi/create/content/logistics/item/filter/FilterItem.java index 9d3ce73ea..48e94c6c8 100644 --- a/src/main/java/com/simibubi/create/content/logistics/item/filter/FilterItem.java +++ b/src/main/java/com/simibubi/create/content/logistics/item/filter/FilterItem.java @@ -127,6 +127,8 @@ public class FilterItem extends Item implements MenuProvider { for (Tag inbt : attributes) { CompoundTag compound = (CompoundTag) inbt; ItemAttribute attribute = ItemAttribute.fromNBT(compound); + if (attribute == null) + continue; boolean inverted = compound.getBoolean("Inverted"); if (count > 3) { list.add(Components.literal("- ...") @@ -194,15 +196,19 @@ public class FilterItem extends Item implements MenuProvider { return test(world, stack, filter, true); } - private static boolean test(Level world, ItemStack stack, ItemStack filter, boolean matchNBT) { + public static boolean test(Level world, ItemStack stack, ItemStack filter, boolean matchNBT) { if (filter.isEmpty()) return true; if (!(filter.getItem() instanceof FilterItem)) - return (matchNBT ? ItemHandlerHelper.canItemStacksStack(filter, stack) : ItemStack.isSame(filter, stack)); + return testDirect(filter, stack, matchNBT); boolean defaults = !filter.hasTag(); + if (defaults) { + return testDirect(filter, stack, matchNBT); + } + if (AllItems.FILTER.get() == filter.getItem()) { ItemStackHandler filterItems = getFilterItems(filter); boolean respectNBT = defaults ? false @@ -211,24 +217,32 @@ public class FilterItem extends Item implements MenuProvider { boolean blacklist = defaults ? false : filter.getTag() .getBoolean("Blacklist"); + boolean isEmpty = true; for (int slot = 0; slot < filterItems.getSlots(); slot++) { ItemStack stackInSlot = filterItems.getStackInSlot(slot); if (stackInSlot.isEmpty()) continue; + isEmpty = false; boolean matches = test(world, stack, stackInSlot, respectNBT); if (matches) return !blacklist; } + if (isEmpty) { + return testDirect(filter, stack, matchNBT); + } return blacklist; } if (AllItems.ATTRIBUTE_FILTER.get() == filter.getItem()) { - WhitelistMode whitelistMode = WhitelistMode.values()[defaults ? 0 - : filter.getTag() - .getInt("WhitelistMode")]; ListTag attributes = defaults ? new ListTag() : filter.getTag() .getList("MatchedAttributes", Tag.TAG_COMPOUND); + if (attributes.isEmpty()) { + return testDirect(filter, stack, matchNBT); + } + WhitelistMode whitelistMode = WhitelistMode.values()[defaults ? 0 + : filter.getTag() + .getInt("WhitelistMode")]; for (Tag inbt : attributes) { CompoundTag compound = (CompoundTag) inbt; ItemAttribute attribute = ItemAttribute.fromNBT(compound); @@ -270,7 +284,7 @@ public class FilterItem extends Item implements MenuProvider { return false; } - private static boolean test(Level world, FluidStack stack, ItemStack filter, boolean matchNBT) { + public static boolean test(Level world, FluidStack stack, ItemStack filter, boolean matchNBT) { if (filter.isEmpty()) return true; if (stack.isEmpty()) @@ -313,4 +327,12 @@ public class FilterItem extends Item implements MenuProvider { return false; } + private static boolean testDirect(ItemStack filter, ItemStack stack, boolean matchNBT) { + if (matchNBT) { + return ItemHandlerHelper.canItemStacksStack(filter, stack); + } else { + return ItemStack.isSame(filter, stack); + } + } + } diff --git a/src/main/java/com/simibubi/create/content/logistics/item/filter/ItemAttribute.java b/src/main/java/com/simibubi/create/content/logistics/item/filter/ItemAttribute.java index be6b26340..7e8939102 100644 --- a/src/main/java/com/simibubi/create/content/logistics/item/filter/ItemAttribute.java +++ b/src/main/java/com/simibubi/create/content/logistics/item/filter/ItemAttribute.java @@ -10,6 +10,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import com.simibubi.create.AllRecipeTypes; import com.simibubi.create.content.contraptions.processing.InWorldProcessing; @@ -78,6 +79,7 @@ public interface ItemAttribute { return attributeType; } + @Nullable static ItemAttribute fromNBT(CompoundTag nbt) { for (ItemAttribute itemAttribute : types) if (itemAttribute.canRead(nbt)) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/AbstractBogeyBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/AbstractBogeyBlock.java new file mode 100644 index 000000000..dff97a9ef --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/AbstractBogeyBlock.java @@ -0,0 +1,361 @@ +package com.simibubi.create.content.logistics.trains; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Vector3f; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllBogeyStyles; +import com.simibubi.create.AllItems; +import com.simibubi.create.content.contraptions.wrench.IWrenchable; +import com.simibubi.create.content.logistics.trains.entity.BogeyStyle; +import com.simibubi.create.content.logistics.trains.entity.Carriage; +import com.simibubi.create.content.logistics.trains.entity.CarriageBogey; +import com.simibubi.create.content.logistics.trains.entity.TravellingPoint; +import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity; +import com.simibubi.create.content.schematics.ISpecialBlockItemRequirement; +import com.simibubi.create.content.schematics.ItemRequirement; +import com.simibubi.create.foundation.block.IBE; +import com.simibubi.create.foundation.block.ProperWaterloggedBlock; +import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.Lang; +import com.simibubi.create.foundation.utility.RegisteredObjects; + +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.registries.ForgeRegistries; + +public abstract class AbstractBogeyBlock extends Block implements IBE, ProperWaterloggedBlock, ISpecialBlockItemRequirement, IWrenchable { + public static final EnumProperty AXIS = BlockStateProperties.HORIZONTAL_AXIS; + static final List BOGEYS = new ArrayList<>(); + public BogeySizes.BogeySize size; + + + public AbstractBogeyBlock(Properties pProperties, BogeySizes.BogeySize size) { + super(pProperties); + registerDefaultState(defaultBlockState().setValue(WATERLOGGED, false)); + this.size = size; + } + + public boolean isOnIncompatibleTrack(Carriage carriage, boolean leading) { + TravellingPoint point = leading ? carriage.getLeadingPoint() : carriage.getTrailingPoint(); + CarriageBogey bogey = leading ? carriage.leadingBogey() : carriage.trailingBogey(); + return point.edge.getTrackMaterial().trackType != getTrackType(bogey.getStyle()); + } + + public Set getValidPathfindingTypes(BogeyStyle style) { + return ImmutableSet.of(getTrackType(style)); + } + + public abstract TrackMaterial.TrackType getTrackType(BogeyStyle style); + + /** + * Only for internal Create use. If you have your own style set, do not call this method + */ + @Deprecated + public static void registerStandardBogey(ResourceLocation block) { + BOGEYS.add(block); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(AXIS, WATERLOGGED); + super.createBlockStateDefinition(builder); + } + + @Override + public BlockState updateShape(BlockState pState, Direction pDirection, BlockState pNeighborState, + LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pNeighborPos) { + updateWater(pLevel, pState, pCurrentPos); + return pState; + } + + @Override + public FluidState getFluidState(BlockState pState) { + return fluidState(pState); + } + + static final EnumSet STICKY_X = EnumSet.of(Direction.EAST, Direction.WEST); + static final EnumSet STICKY_Z = EnumSet.of(Direction.SOUTH, Direction.NORTH); + + public EnumSet getStickySurfaces(BlockGetter world, BlockPos pos, BlockState state) { + return state.getValue(BlockStateProperties.HORIZONTAL_AXIS) == Direction.Axis.X ? STICKY_X : STICKY_Z; + } + + public abstract double getWheelPointSpacing(); + + public abstract double getWheelRadius(); + + public Vec3 getConnectorAnchorOffset(boolean upsideDown) { + return getConnectorAnchorOffset(); + } + + /** + * This should be implemented, but not called directly + */ + protected abstract Vec3 getConnectorAnchorOffset(); + + public boolean allowsSingleBogeyCarriage() { + return true; + } + + public abstract BogeyStyle getDefaultStyle(); + + /** + * Legacy system doesn't capture bogey tile entities when constructing a train + */ + public boolean captureTileEntityForTrain() { + return false; + } + + @OnlyIn(Dist.CLIENT) + public void render(@Nullable BlockState state, float wheelAngle, PoseStack ms, float partialTicks, + MultiBufferSource buffers, int light, int overlay, BogeyStyle style, CompoundTag bogeyData) { + if (style == null) + style = getDefaultStyle(); + + final Optional commonRenderer + = style.getInWorldCommonRenderInstance(); + final BogeyRenderer renderer = style.getInWorldRenderInstance(this.getSize()); + if (state != null) { + ms.translate(.5f, .5f, .5f); + if (state.getValue(AXIS) == Direction.Axis.X) + ms.mulPose(Vector3f.YP.rotationDegrees(90)); + } + ms.translate(0, -1.5 - 1 / 128f, 0); + VertexConsumer vb = buffers.getBuffer(RenderType.cutoutMipped()); + if (bogeyData == null) + bogeyData = new CompoundTag(); + renderer.render(bogeyData, wheelAngle, ms, light, vb, state == null); + CompoundTag finalBogeyData = bogeyData; + commonRenderer.ifPresent(common -> + common.render(finalBogeyData, wheelAngle, ms, light, vb, state == null)); + } + + public BogeySizes.BogeySize getSize() { + return this.size; + } + + public Direction getBogeyUpDirection() { + return Direction.UP; + } + + public boolean isTrackAxisAlongFirstCoordinate(BlockState state) { + return state.getValue(AXIS) == Direction.Axis.X; + } + + @Nullable + public BlockState getMatchingBogey(Direction upDirection, boolean axisAlongFirst) { + if (upDirection != Direction.UP) + return null; + return defaultBlockState().setValue(AXIS, axisAlongFirst ? Direction.Axis.X : Direction.Axis.Z); + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, + BlockHitResult hit) { + if (level.isClientSide) + return InteractionResult.PASS; + ItemStack stack = player.getItemInHand(hand); + + if (!player.isShiftKeyDown() && stack.is(AllItems.WRENCH.get()) && !player.getCooldowns().isOnCooldown(stack.getItem()) + && AllBogeyStyles.BOGEY_STYLES.size() > 1) { + + BlockEntity be = level.getBlockEntity(pos); + + if (!(be instanceof AbstractBogeyBlockEntity sbte)) + return InteractionResult.FAIL; + + player.getCooldowns().addCooldown(stack.getItem(), 20); + BogeyStyle currentStyle = sbte.getStyle(); + + BogeySizes.BogeySize size = getSize(); + + BogeyStyle style = this.getNextStyle(currentStyle); + if (style == currentStyle) + return InteractionResult.PASS; + + Set validSizes = style.validSizes(); + + for (int i = 0; i < BogeySizes.count(); i++) { + if (validSizes.contains(size)) break; + size = size.increment(); + } + + sbte.setBogeyStyle(style); + + CompoundTag defaultData = style.defaultData; + sbte.setBogeyData(sbte.getBogeyData().merge(defaultData)); + + if (size == getSize()) { + player.displayClientMessage(Lang.translateDirect("bogey.style.updated_style") + .append(": ").append(style.displayName), true); + } else { + CompoundTag oldData = sbte.getBogeyData(); + level.setBlock(pos, this.getStateOfSize(sbte, size), 3); + BlockEntity newBlockEntity = level.getBlockEntity(pos); + if (!(newBlockEntity instanceof AbstractBogeyBlockEntity newTileEntity)) + return InteractionResult.FAIL; + newTileEntity.setBogeyData(oldData); + player.displayClientMessage(Lang.translateDirect("bogey.style.updated_style_and_size") + .append(": ").append(style.displayName), true); + } + + return InteractionResult.CONSUME; + } + + return InteractionResult.PASS; + } + + /** + * If, instead of using the style-based cycling system you prefer to use separate blocks, return them from this method + */ + protected List getBogeyBlockCycle() { + return BOGEYS; + } + + + @Override + public BlockState getRotatedBlockState(BlockState state, Direction targetedFace) { + Block block = state.getBlock(); + List bogeyCycle = getBogeyBlockCycle(); + int indexOf = bogeyCycle.indexOf(RegisteredObjects.getKeyOrThrow(block)); + if (indexOf == -1) + return state; + int index = (indexOf + 1) % bogeyCycle.size(); + Direction bogeyUpDirection = getBogeyUpDirection(); + boolean trackAxisAlongFirstCoordinate = isTrackAxisAlongFirstCoordinate(state); + + while (index != indexOf) { + ResourceLocation id = bogeyCycle.get(index); + Block newBlock = ForgeRegistries.BLOCKS.getValue(id); + if (newBlock instanceof AbstractBogeyBlock bogey) { + BlockState matchingBogey = bogey.getMatchingBogey(bogeyUpDirection, trackAxisAlongFirstCoordinate); + if (matchingBogey != null) + return copyProperties(state, matchingBogey); + } + index = (index + 1) % bogeyCycle.size(); + } + + return state; + } + + public BlockState getNextSize(Level level, BlockPos pos) { + BlockEntity te = level.getBlockEntity(pos); + if (te instanceof AbstractBogeyBlockEntity sbte) + return this.getNextSize(sbte); + return level.getBlockState(pos); + } + + /** + * List of BlockState Properties to copy between sizes + */ + public List> propertiesToCopy() { + return ImmutableList.of(WATERLOGGED, AXIS); + } + + // generic method needed to satisfy Property and BlockState's generic requirements + private > BlockState copyProperty(BlockState source, BlockState target, Property property) { + if (source.hasProperty(property) && target.hasProperty(property)) { + return target.setValue(property, source.getValue(property)); + } + return target; + } + + private BlockState copyProperties(BlockState source, BlockState target) { + for (Property property : propertiesToCopy()) + target = copyProperty(source, target, property); + return target; + } + + public BlockState getNextSize(AbstractBogeyBlockEntity sbte) { + BogeySizes.BogeySize size = this.getSize(); + BogeyStyle style = sbte.getStyle(); + BlockState nextBlock = style.getNextBlock(size).defaultBlockState(); + nextBlock = copyProperties(sbte.getBlockState(), nextBlock); + return nextBlock; + } + + public BlockState getStateOfSize(AbstractBogeyBlockEntity sbte, BogeySizes.BogeySize size) { + BogeyStyle style = sbte.getStyle(); + BlockState state = style.getBlockOfSize(size).defaultBlockState(); + return copyProperties(sbte.getBlockState(), state); + } + + public BogeyStyle getNextStyle(Level level, BlockPos pos) { + BlockEntity te = level.getBlockEntity(pos); + if (te instanceof AbstractBogeyBlockEntity sbte) + return this.getNextStyle(sbte.getStyle()); + return getDefaultStyle(); + } + + public BogeyStyle getNextStyle(BogeyStyle style) { + Collection allStyles = style.getCycleGroup().values(); + if (allStyles.size() <= 1) + return style; + List list = new ArrayList<>(allStyles); + return Iterate.cycleValue(list, style); + } + + + @Override + public @NotNull BlockState rotate(@NotNull BlockState pState, Rotation pRotation) { + return switch (pRotation) { + case COUNTERCLOCKWISE_90, CLOCKWISE_90 -> pState.cycle(AXIS); + default -> pState; + }; + } + + @Override + public ItemRequirement getRequiredItems(BlockState state, BlockEntity te) { + return new ItemRequirement(ItemRequirement.ItemUseType.CONSUME, AllBlocks.RAILWAY_CASING.asStack()); + } + + public boolean canBeUpsideDown() { + return false; + } + + public boolean isUpsideDown(BlockState state) { + return false; + } + + public BlockState getVersion(BlockState base, boolean upsideDown) { + return base; + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java b/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java index d81ac4bb0..bff06392f 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java @@ -26,6 +26,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.phys.AABB; @@ -42,6 +43,7 @@ public class BezierConnection implements Iterable { public Couple smoothing; public boolean primary; public boolean hasGirder; + protected TrackMaterial trackMaterial; // runtime @@ -58,19 +60,20 @@ public class BezierConnection implements Iterable { private AABB bounds; public BezierConnection(Couple positions, Couple starts, Couple axes, Couple normals, - boolean primary, boolean girder) { + boolean primary, boolean girder, TrackMaterial material) { tePositions = positions; this.starts = starts; this.axes = axes; this.normals = normals; this.primary = primary; this.hasGirder = girder; + this.trackMaterial = material; resolved = false; } public BezierConnection secondary() { - BezierConnection bezierConnection = - new BezierConnection(tePositions.swap(), starts.swap(), axes.swap(), normals.swap(), !primary, hasGirder); + BezierConnection bezierConnection = new BezierConnection(tePositions.swap(), starts.swap(), axes.swap(), + normals.swap(), !primary, hasGirder, trackMaterial); if (smoothing != null) bezierConnection.smoothing = smoothing.swap(); return bezierConnection; @@ -80,6 +83,26 @@ public class BezierConnection implements Iterable { return secondary().secondary(); } + private static boolean coupleEquals(Couple a, Couple b) { + return (a.getFirst() + .equals(b.getFirst()) + && a.getSecond() + .equals(b.getSecond())) + || (a.getFirst() instanceof Vec3 aFirst && a.getSecond() instanceof Vec3 aSecond + && b.getFirst() instanceof Vec3 bFirst && b.getSecond() instanceof Vec3 bSecond + && aFirst.closerThan(bFirst, 1e-6) && aSecond.closerThan(bSecond, 1e-6)); + } + + public boolean equalsSansMaterial(BezierConnection other) { + return equalsSansMaterialInner(other) || equalsSansMaterialInner(other.secondary()); + } + + private boolean equalsSansMaterialInner(BezierConnection other) { + return this == other || (other != null && coupleEquals(this.tePositions, other.tePositions) + && coupleEquals(this.starts, other.starts) && coupleEquals(this.axes, other.axes) + && coupleEquals(this.normals, other.normals) && this.hasGirder == other.hasGirder); + } + public BezierConnection(CompoundTag compound, BlockPos localTo) { this(Couple.deserializeEach(compound.getList("Positions", Tag.TAG_COMPOUND), NbtUtils::readBlockPos) .map(b -> b.offset(localTo)), @@ -87,7 +110,7 @@ public class BezierConnection implements Iterable { .map(v -> v.add(Vec3.atLowerCornerOf(localTo))), Couple.deserializeEach(compound.getList("Axes", Tag.TAG_COMPOUND), VecHelper::readNBTCompound), Couple.deserializeEach(compound.getList("Normals", Tag.TAG_COMPOUND), VecHelper::readNBTCompound), - compound.getBoolean("Primary"), compound.getBoolean("Girder")); + compound.getBoolean("Primary"), compound.getBoolean("Girder"), TrackMaterial.deserialize(compound.getString("Material"))); if (compound.contains("Smoothing")) smoothing = @@ -105,6 +128,7 @@ public class BezierConnection implements Iterable { compound.put("Starts", starts.serializeEach(VecHelper::writeNBTCompound)); compound.put("Axes", axes.serializeEach(VecHelper::writeNBTCompound)); compound.put("Normals", normals.serializeEach(VecHelper::writeNBTCompound)); + compound.putString("Material", getMaterial().id.toString()); if (smoothing != null) compound.put("Smoothing", smoothing.serializeEach(NBTHelper::intToCompound)); @@ -115,7 +139,7 @@ public class BezierConnection implements Iterable { public BezierConnection(FriendlyByteBuf buffer) { this(Couple.create(buffer::readBlockPos), Couple.create(() -> VecHelper.read(buffer)), Couple.create(() -> VecHelper.read(buffer)), Couple.create(() -> VecHelper.read(buffer)), - buffer.readBoolean(), buffer.readBoolean()); + buffer.readBoolean(), buffer.readBoolean(), TrackMaterial.deserialize(buffer.readUtf())); if (buffer.readBoolean()) smoothing = Couple.create(buffer::readVarInt); } @@ -127,6 +151,7 @@ public class BezierConnection implements Iterable { normals.forEach(v -> VecHelper.write(v, buffer)); buffer.writeBoolean(primary); buffer.writeBoolean(hasGirder); + buffer.writeUtf(getMaterial().id.toString()); buffer.writeBoolean(smoothing != null); if (smoothing != null) smoothing.forEach(buffer::writeVarInt); @@ -333,7 +358,7 @@ public class BezierConnection implements Iterable { Inventory inv = player.getInventory(); int tracks = getTrackItemCost(); while (tracks > 0) { - inv.placeItemBackInInventory(AllBlocks.TRACK.asStack(Math.min(64, tracks))); + inv.placeItemBackInInventory(new ItemStack(getMaterial().getBlock(), Math.min(64, tracks))); tracks -= 64; } int girders = getGirderItemCost(); @@ -361,7 +386,7 @@ public class BezierConnection implements Iterable { continue; Vec3 v = VecHelper.offsetRandomly(segment.position, level.random, .125f) .add(origin); - ItemEntity entity = new ItemEntity(level, v.x, v.y, v.z, AllBlocks.TRACK.asStack()); + ItemEntity entity = new ItemEntity(level, v.x, v.y, v.z, getMaterial().asStack()); entity.setDefaultPickUpDelay(); level.addFreshEntity(entity); if (!hasGirder) @@ -375,7 +400,7 @@ public class BezierConnection implements Iterable { } public void spawnDestroyParticles(Level level) { - BlockParticleOption data = new BlockParticleOption(ParticleTypes.BLOCK, AllBlocks.TRACK.getDefaultState()); + BlockParticleOption data = new BlockParticleOption(ParticleTypes.BLOCK, getMaterial().getBlock().defaultBlockState()); BlockParticleOption girderData = new BlockParticleOption(ParticleTypes.BLOCK, AllBlocks.METAL_GIRDER.getDefaultState()); if (!(level instanceof ServerLevel slevel)) @@ -393,6 +418,14 @@ public class BezierConnection implements Iterable { } } + public TrackMaterial getMaterial() { + return trackMaterial; + } + + public void setMaterial(TrackMaterial material) { + trackMaterial = material; + } + public static class Segment { public int index; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/BogeyBlockEntityRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/BogeyBlockEntityRenderer.java index 3f437fe8c..2afd1dc29 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/BogeyBlockEntityRenderer.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/BogeyBlockEntityRenderer.java @@ -1,7 +1,7 @@ package com.simibubi.create.content.logistics.trains; import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.content.logistics.trains.track.StandardBogeyBlockEntity; +import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity; import com.simibubi.create.foundation.blockEntity.renderer.SafeBlockEntityRenderer; import net.minecraft.client.renderer.MultiBufferSource; @@ -17,11 +17,11 @@ public class BogeyBlockEntityRenderer extends SafeBlockEn protected void renderSafe(T be, float partialTicks, PoseStack ms, MultiBufferSource buffer, int light, int overlay) { BlockState blockState = be.getBlockState(); - float angle = 0; - if (be instanceof StandardBogeyBlockEntity sbte) - angle = sbte.getVirtualAngle(partialTicks); - if (blockState.getBlock()instanceof IBogeyBlock bogey) - bogey.render(blockState, angle, ms, partialTicks, buffer, light, overlay); + if (be instanceof AbstractBogeyBlockEntity sbte) { + float angle = sbte.getVirtualAngle(partialTicks); + if (blockState.getBlock() instanceof AbstractBogeyBlock bogey) + bogey.render(blockState, angle, ms, partialTicks, buffer, light, overlay, sbte.getStyle(), sbte.getBogeyData()); + } } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/BogeyRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/BogeyRenderer.java new file mode 100644 index 000000000..02ea4f185 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/BogeyRenderer.java @@ -0,0 +1,309 @@ +package com.simibubi.create.content.logistics.trains; + +import com.jozufozu.flywheel.api.MaterialManager; +import com.jozufozu.flywheel.core.Materials; +import com.jozufozu.flywheel.core.PartialModel; +import com.jozufozu.flywheel.core.materials.model.ModelData; +import com.jozufozu.flywheel.util.transform.Transform; +import com.mojang.blaze3d.vertex.PoseStack; + +import com.mojang.blaze3d.vertex.VertexConsumer; + +import com.simibubi.create.foundation.render.CachedBufferer; +import com.simibubi.create.foundation.render.SuperByteBuffer; + +import net.minecraft.nbt.CompoundTag; + +import net.minecraft.world.level.block.Blocks; + +import net.minecraft.world.level.block.state.BlockState; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public abstract class BogeyRenderer { + Map contraptionModelData = new HashMap<>(); + + /** + * A common interface for getting transform data for both in-world and in-contraption model data safely from a + * partial model + * + * @param model The key for the model data to instantiate or retrieve + * @param ms The posestack used for contraption model data + * @param inInstancedContraption The type of model needed + * @param size The amount of models needed + * @return A generic transform which can be used for both in-world and in-contraption models + */ + public Transform[] getTransformsFromPartial(PartialModel model, PoseStack ms, boolean inInstancedContraption, int size) { + return (inInstancedContraption) ? transformContraptionModelData(keyFromModel(model), ms) : createModelData(model, size); + } + + /** + * A common interface for getting transform data for both in-world and in-contraption model data safely from a + * blockstate + * + * @param state The key for the model data to instantiate or retrieve + * @param ms The posestack used for contraption model data + * @param inContraption The type of model needed + * @param size The amount of models needed + * @return A generic transform which can be used for both in-world and in-contraption models + */ + public Transform[] getTransformsFromBlockState(BlockState state, PoseStack ms, boolean inContraption, int size) { + return inContraption ? transformContraptionModelData(keyFromModel(state), ms) : createModelData(state, size); + } + + /** + * Used for calling both in-world and in-contraption rendering + * + * @param bogeyData Custom data stored on the bogey able to be used for rendering + * @param wheelAngle The angle of the wheel + * @param ms The posestack to render to + * @param light (Optional) Light used for in-world rendering + * @param vb (Optional) Vertex Consumer used for in-world rendering + */ + @OnlyIn(Dist.CLIENT) + public abstract void render(CompoundTag bogeyData, float wheelAngle, PoseStack ms, int light, VertexConsumer vb, boolean inContraption); + + /** + * Used for calling in-contraption rendering ensuring that falsey data is handled correctly + * + * @param bogeyData Custom data stored on the bogey able to be used for rendering + * @param wheelAngle The angle of the wheel + * @param ms The posestack to render to + */ + @OnlyIn(Dist.CLIENT) + public void render(CompoundTag bogeyData, float wheelAngle, PoseStack ms) { + this.render(bogeyData, wheelAngle, ms, 0, null, true); + } + + public abstract BogeySizes.BogeySize getSize(); + + /** + * Used to collect Contraption Model Data for in-contraption rendering, should not be utilised directly when + * rendering to prevent render type mismatch + * + * @param key The key used to access the model + * @param ms Posestack of the contraption to bind the model data to + * @return A generic transform which can be used for both in-world and in-contraption models + */ + private Transform[] transformContraptionModelData(String key, PoseStack ms) { + ModelData[] modelData = contraptionModelData.get(key); + Arrays.stream(modelData).forEach(modelDataElement -> modelDataElement.setTransform(ms)); + return modelData; + } + + + /** + * Used for in world rendering, creates a set count of model data to be rendered, allowing for a generic response + * when rendering multiple models both in-world and in-contraption for example, with wheels + * + * @param model The partial model of the model data ot be made + * @param size The Amount of models needed + * @return A generic transform which can be used for both in-world and in-contraption models + */ + private Transform[] createModelData(PartialModel model, int size) { + BlockState air = Blocks.AIR.defaultBlockState(); + SuperByteBuffer[] data = { CachedBufferer.partial(model, air) }; + return expandArrayToLength(data, size); + } + + /** + * Used for in world rendering, creates a set count of model data to be rendered, allowing for a generic response + * when rendering multiple models both in-world and in-contraption for example, with wheels + * + * @param state The state of the model data to be made + * @param size Amount of models needed + * @return A generic transform which can be used for both in-world and in-contraption models + */ + private Transform[] createModelData(BlockState state, int size) { + SuperByteBuffer[] data = { CachedBufferer.block(state) }; + return expandArrayToLength(data, size); + } + + /** + * Utility function to clone in-world models to a set size to allow for common handling of rendering with multiple + * instances of the same model for example with wheels + * + * @param data An in-world model to be replicated + * @param size Amount of models needed + * @return A generic transform which can be used for both in-world and in-contraption models + */ + private Transform[] expandArrayToLength(SuperByteBuffer[] data, int size) { + return Arrays.stream(Collections.nCopies(size, data).toArray()) + .flatMap(inner -> Arrays.stream((SuperByteBuffer[]) inner)) + .toArray(SuperByteBuffer[]::new); + } + + /** + * Helper function to collect or create a single model from a partial model used for both in-world and + * in-contraption rendering + * + * @param model The key of the model to be collected or instantiated + * @param ms Posestack to bind the model to if it is within a contraption + * @param inInstancedContraption Type of rendering required + * @return A generic transform which can be used for both in-world and in-contraption models + */ + public Transform getTransformFromPartial(PartialModel model, PoseStack ms, boolean inInstancedContraption) { + BlockState air = Blocks.AIR.defaultBlockState(); + return inInstancedContraption ? contraptionModelData.get(keyFromModel(model))[0].setTransform(ms) + : CachedBufferer.partial(model, air); + } + + /** + * A common interface for getting transform data for blockstates, for a single model + * + * @param state The state of the model to be collected or instantiated + * @param ms Posestack to bind the model to if it is within a contraption + * @param inContraption Type of model required + * @return A generic transform which can be used for both in-world and in-contraption models + */ + public Transform getTransformFromBlockState(BlockState state, PoseStack ms, boolean inContraption) { + return (inContraption) ? contraptionModelData.get(keyFromModel(state))[0].setTransform(ms) + : CachedBufferer.block(state); + } + + /** + * Provides render implementations a point in setup to instantiate all model data to be needed + * + * @param materialManager The material manager + */ + @OnlyIn(Dist.CLIENT) + public abstract void initialiseContraptionModelData(MaterialManager materialManager); + + /** + * Creates instances of models for in-world rendering to a set length from a provided partial model + * + * @param materialManager The material manager + * @param model Partial model to be instanced + * @param count Amount of models neeeded + */ + public void createModelInstances(MaterialManager materialManager, PartialModel model, int count) { + ModelData[] modelData = new ModelData[count]; + materialManager.defaultSolid().material(Materials.TRANSFORMED) + .getModel(model).createInstances(modelData); + contraptionModelData.put(keyFromModel(model), modelData); + } + + /** + * Creates instances of models for in-contraption rendering to a set length from a provided blockstate + * + * @param materialManager The material manager + * @param state Blockstate of the model to be created + * @param count Amount of models needed + */ + public void createModelInstances(MaterialManager materialManager, BlockState state, int count) { + ModelData[] modelData = new ModelData[count]; + materialManager.defaultSolid().material(Materials.TRANSFORMED) + .getModel(state).createInstances(modelData); + contraptionModelData.put(keyFromModel(state), modelData); + } + + /** + * Creates a single instance of models for in-contraption rendering from a provided blockstate + * + * @param materialManager The material manager + * @param state Blockstate of the model to be created + */ + public void createModelInstance(MaterialManager materialManager, BlockState state) { + this.createModelInstances(materialManager, state, 1); + } + + /** + * Helper function to create a single model instance for in-contraption rendering + * + * @param materialManager The material manager + * @param models The type of model to create instances of + */ + public void createModelInstances(MaterialManager materialManager, PartialModel... models) { + for (PartialModel model : models) + createModelInstances(materialManager, model, 1); + } + + /** + * Handles scale for all model data and renders non contraption model data + * + * @param b The model data itself + * @param ms Pose stack to render to + * @param light light level of the scene + * @param vb Vertex Consumber to render to + * @param Generic alias for both contraption and in-world model data + */ + + public static > void finalize(B b, PoseStack ms, int light, @Nullable VertexConsumer vb) { + b.scale(1 - 1/512f); + if (b instanceof SuperByteBuffer byteBuf && vb != null) + byteBuf.light(light).renderInto(ms, vb); + } + + /** + * Automatic handling for setting empty transforms for all model data + * + */ + + public void emptyTransforms() { + for (ModelData[] data : contraptionModelData.values()) + for (ModelData model : data) + model.setEmptyTransform(); + } + + /** + * Automatic handling for updating all model data's light + * + * @param blockLight the blocklight to be applied + * @param skyLight the skylight to be applied + */ + + public void updateLight(int blockLight, int skyLight) { + for (ModelData[] data : contraptionModelData.values()) + for (ModelData model : data) + model.setBlockLight(blockLight).setSkyLight(skyLight); + } + + /** + * Automatic handling for clearing all model data of a contraption + * + */ + + public void remove() { + for (ModelData[] data : contraptionModelData.values()) + for (ModelData model : data) + model.delete(); + contraptionModelData.clear(); + } + + /** + * Create a model key from a partial model, so it can be easily accessed + * + * @param partialModel the model we want a unique key for + * @return Key of the model + */ + + private String keyFromModel(PartialModel partialModel) { + return partialModel.getLocation().toString(); + } + + /** + * Create a model key from a blockstate, so it can be easily accessed + * + * @param state Blockstate of the model + * @return Key of the model + */ + + private String keyFromModel(BlockState state) { + return state.toString(); + } + + public static abstract class CommonRenderer extends BogeyRenderer { + @Override + public BogeySizes.BogeySize getSize() { + return null; + } + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/BogeySizes.java b/src/main/java/com/simibubi/create/content/logistics/trains/BogeySizes.java new file mode 100644 index 000000000..41fc13344 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/BogeySizes.java @@ -0,0 +1,70 @@ +package com.simibubi.create.content.logistics.trains; + +import com.simibubi.create.Create; + +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +public class BogeySizes { + private static final Collection BOGEY_SIZES = new HashSet<>(); + public static final BogeySize SMALL = new BogeySize(Create.ID, "small", 6.5f / 16f); + public static final BogeySize LARGE = new BogeySize(Create.ID, "large", 12.5f / 16f); + + static { + BOGEY_SIZES.add(SMALL); + BOGEY_SIZES.add(LARGE); + } + + public static BogeySize addSize(String modId, String name, float size) { + ResourceLocation location = new ResourceLocation(modId, name); + return addSize(location, size); + } + + public static BogeySize addSize(ResourceLocation location, float size) { + BogeySize customSize = new BogeySize(location, size); + BOGEY_SIZES.add(customSize); + return customSize; + } + + public static List getAllSizesSmallToLarge() { + return BOGEY_SIZES.stream() + .sorted(Comparator.comparing(BogeySize::wheelRadius)) + .collect(Collectors.toList()); + } + + public static List getAllSizesLargeToSmall() { + List sizes = getAllSizesSmallToLarge(); + Collections.reverse(sizes); + return sizes; + } + + public static int count() { + return BOGEY_SIZES.size(); + } + + public record BogeySize(ResourceLocation location, float wheelRadius) { + public BogeySize(String modId, String name, float wheelRadius) { + this(new ResourceLocation(modId, name), wheelRadius); + } + + public BogeySize increment() { + List values = getAllSizesSmallToLarge(); + int ordinal = values.indexOf(this); + return values.get((ordinal + 1) % values.size()); + } + + public boolean is(BogeySize size) { + return size.location == this.location; + } + } + + public static void init() { + + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/GlobalRailwayManager.java b/src/main/java/com/simibubi/create/content/logistics/trains/GlobalRailwayManager.java index 979d64edf..046edb367 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/GlobalRailwayManager.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/GlobalRailwayManager.java @@ -265,13 +265,17 @@ public class GlobalRailwayManager { public void clientTick() { if (isTrackGraphDebugActive()) for (TrackGraph trackGraph : trackNetworks.values()) - TrackGraphVisualizer.debugViewGraph(trackGraph); + TrackGraphVisualizer.debugViewGraph(trackGraph, isTrackGraphDebugExtended()); } - + private static boolean isTrackGraphDebugActive() { return KineticDebugger.isF3DebugModeActive() && AllConfigs.client().showTrackGraphOnF3.get(); } + private static boolean isTrackGraphDebugExtended() { + return AllConfigs.client().showExtendedTrackGraphOnF3.get(); + } + public GlobalRailwayManager sided(LevelAccessor level) { if (level != null && !level.isClientSide()) return this; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/IBogeyBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/IBogeyBlock.java deleted file mode 100644 index dea2966aa..000000000 --- a/src/main/java/com/simibubi/create/content/logistics/trains/IBogeyBlock.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.simibubi.create.content.logistics.trains; - -import static net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; - -import javax.annotation.Nullable; - -import com.jozufozu.flywheel.api.MaterialManager; -import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.content.contraptions.wrench.IWrenchable; -import com.simibubi.create.content.logistics.trains.entity.BogeyInstance; -import com.simibubi.create.content.logistics.trains.entity.CarriageBogey; -import com.simibubi.create.foundation.utility.RegisteredObjects; - -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.registries.ForgeRegistries; - -public interface IBogeyBlock extends IWrenchable { - - static final List BOGEYS = new ArrayList<>(); - - public static void register(ResourceLocation block) { - BOGEYS.add(block); - } - - public EnumSet getStickySurfaces(BlockGetter world, BlockPos pos, BlockState state); - - public double getWheelPointSpacing(); - - public double getWheelRadius(); - - public boolean allowsSingleBogeyCarriage(); - - public Vec3 getConnectorAnchorOffset(); - - @OnlyIn(Dist.CLIENT) - public void render(@Nullable BlockState state, float wheelAngle, PoseStack ms, float partialTicks, - MultiBufferSource buffers, int light, int overlay); - - @OnlyIn(Dist.CLIENT) - public BogeyInstance createInstance(MaterialManager materialManager, CarriageBogey bogey); - - public default Direction getBogeyUpDirection() { - return Direction.UP; - } - - public boolean isTrackAxisAlongFirstCoordinate(BlockState state); - - @Nullable - public BlockState getMatchingBogey(Direction upDirection, boolean axisAlongFirst); - - @Override - default BlockState getRotatedBlockState(BlockState state, Direction targetedFace) { - Block block = state.getBlock(); - int indexOf = BOGEYS.indexOf(RegisteredObjects.getKeyOrThrow(block)); - if (indexOf == -1) - return state; - - int index = (indexOf + 1) % BOGEYS.size(); - Direction bogeyUpDirection = getBogeyUpDirection(); - boolean trackAxisAlongFirstCoordinate = isTrackAxisAlongFirstCoordinate(state); - - while (index != indexOf) { - ResourceLocation id = BOGEYS.get(index); - Block newBlock = ForgeRegistries.BLOCKS.getValue(id); - if (newBlock instanceof IBogeyBlock bogey) { - BlockState matchingBogey = bogey.getMatchingBogey(bogeyUpDirection, trackAxisAlongFirstCoordinate); - if (matchingBogey != null) - return matchingBogey.hasProperty(WATERLOGGED) - ? matchingBogey.setValue(WATERLOGGED, state.getValue(WATERLOGGED)) - : matchingBogey; - } - index = (index + 1) % BOGEYS.size(); - } - - return state; - } - -} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java index 710451666..eef6530ca 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java @@ -25,6 +25,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; @@ -85,20 +86,38 @@ public interface ITrackBlock { Function yOffsetFactory = v -> getYOffsetAt(world, pos, state, v); addToListIfConnected(connectedTo, list, offsetFactory, b -> shape.getNormal(), dimensionFactory, - yOffsetFactory, axis, null); + yOffsetFactory, axis, null, (b, v) -> getMaterialSimple(world, v)); }); return list; } + public static TrackMaterial getMaterialSimple(BlockGetter world, Vec3 pos) { + return getMaterialSimple(world, pos, TrackMaterial.ANDESITE); + } + + public static TrackMaterial getMaterialSimple(BlockGetter world, Vec3 pos, TrackMaterial defaultMaterial) { + if (defaultMaterial == null) + defaultMaterial = TrackMaterial.ANDESITE; + if (world != null) { + Block block = world.getBlockState(new BlockPos(pos)).getBlock(); + if (block instanceof ITrackBlock track) { + return track.getMaterial(); + } + } + return defaultMaterial; + } + public static void addToListIfConnected(@Nullable TrackNodeLocation fromEnd, Collection list, BiFunction offsetFactory, Function normalFactory, Function> dimensionFactory, Function yOffsetFactory, Vec3 axis, - BezierConnection viaTurn) { + BezierConnection viaTurn, BiFunction materialFactory) { Vec3 firstOffset = offsetFactory.apply(0.5d, true); DiscoveredLocation firstLocation = new DiscoveredLocation(dimensionFactory.apply(true), firstOffset).viaTurn(viaTurn) + .materialA(materialFactory.apply(true, offsetFactory.apply(0.0d, true))) + .materialB(materialFactory.apply(true, offsetFactory.apply(1.0d, true))) .withNormal(normalFactory.apply(true)) .withDirection(axis) .withYOffset(yOffsetFactory.apply(firstOffset)); @@ -106,6 +125,8 @@ public interface ITrackBlock { Vec3 secondOffset = offsetFactory.apply(0.5d, false); DiscoveredLocation secondLocation = new DiscoveredLocation(dimensionFactory.apply(false), secondOffset).viaTurn(viaTurn) + .materialA(materialFactory.apply(false, offsetFactory.apply(0.0d, false))) + .materialB(materialFactory.apply(false, offsetFactory.apply(1.0d, false))) .withNormal(normalFactory.apply(false)) .withDirection(axis) .withYOffset(yOffsetFactory.apply(secondOffset)); @@ -169,4 +190,6 @@ public interface ITrackBlock { .normalize()) < 0 ? AxisDirection.POSITIVE : AxisDirection.NEGATIVE); } + TrackMaterial getMaterial(); + } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/StandardBogeyRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/StandardBogeyRenderer.java new file mode 100644 index 000000000..cfd779dd2 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/StandardBogeyRenderer.java @@ -0,0 +1,134 @@ +package com.simibubi.create.content.logistics.trains; + +import static com.simibubi.create.AllPartialModels.BOGEY_DRIVE; +import static com.simibubi.create.AllPartialModels.BOGEY_FRAME; +import static com.simibubi.create.AllPartialModels.BOGEY_PIN; +import static com.simibubi.create.AllPartialModels.BOGEY_PISTON; +import static com.simibubi.create.AllPartialModels.LARGE_BOGEY_WHEELS; +import static com.simibubi.create.AllPartialModels.SMALL_BOGEY_WHEELS; + +import com.jozufozu.flywheel.api.MaterialManager; +import com.jozufozu.flywheel.util.transform.Transform; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.content.contraptions.relays.elementary.ShaftBlock; +import com.simibubi.create.foundation.utility.AngleHelper; +import com.simibubi.create.foundation.utility.Iterate; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; + +public class StandardBogeyRenderer { + public static class CommonStandardBogeyRenderer extends BogeyRenderer.CommonRenderer { + @Override + public void initialiseContraptionModelData(MaterialManager materialManager) { + createModelInstances(materialManager, AllBlocks.SHAFT.getDefaultState() + .setValue(ShaftBlock.AXIS, Direction.Axis.Z), 2); + } + + @Override + public void render(CompoundTag bogeyData, float wheelAngle, PoseStack ms, int light, VertexConsumer vb, boolean inContraption) { + boolean inInstancedContraption = vb == null; + Transform[] shafts = getTransformsFromBlockState(AllBlocks.SHAFT.getDefaultState() + .setValue(ShaftBlock.AXIS, Direction.Axis.Z), ms, inInstancedContraption, 2); + for (int i : Iterate.zeroAndOne) { + shafts[i].translate(-.5f, .25f, i * -1) + .centre() + .rotateZ(wheelAngle) + .unCentre(); + finalize(shafts[i], ms, light, vb); + } + } + } + + + public static class SmallStandardBogeyRenderer extends BogeyRenderer { + @Override + public void initialiseContraptionModelData(MaterialManager materialManager) { + createModelInstances(materialManager, SMALL_BOGEY_WHEELS, 2); + createModelInstances(materialManager, BOGEY_FRAME); + } + + + @Override + public BogeySizes.BogeySize getSize() { + return BogeySizes.SMALL; + } + + @Override + public void render(CompoundTag bogeyData, float wheelAngle, PoseStack ms, int light, VertexConsumer vb, boolean inContraption) { + boolean inInstancedContraption = vb == null; + Transform transform = getTransformFromPartial(BOGEY_FRAME, ms, inInstancedContraption); + finalize(transform, ms, light, vb); + + Transform[] wheels = getTransformsFromPartial(SMALL_BOGEY_WHEELS, ms, inInstancedContraption, 2); + for (int side : Iterate.positiveAndNegative) { + if (!inInstancedContraption) + ms.pushPose(); + Transform wheel = wheels[(side + 1)/2]; + wheel.translate(0, 12 / 16f, side) + .rotateX(wheelAngle); + finalize(wheel, ms, light, vb); + if (!inInstancedContraption) + ms.popPose(); + } + } + } + + public static class LargeStandardBogeyRenderer extends BogeyRenderer { + @Override + public void initialiseContraptionModelData(MaterialManager materialManager) { + createModelInstances(materialManager, LARGE_BOGEY_WHEELS, BOGEY_DRIVE, BOGEY_PISTON, BOGEY_PIN); + createModelInstances(materialManager, AllBlocks.SHAFT.getDefaultState() + .setValue(ShaftBlock.AXIS, Direction.Axis.X), 2); + } + + @Override + public BogeySizes.BogeySize getSize() { + return BogeySizes.LARGE; + } + + @Override + public void render(CompoundTag bogeyData, float wheelAngle, PoseStack ms, int light, VertexConsumer vb, boolean inContraption) { + boolean inInstancedContraption = vb == null; + + Transform[] secondaryShafts = getTransformsFromBlockState(AllBlocks.SHAFT.getDefaultState() + .setValue(ShaftBlock.AXIS, Direction.Axis.X), ms, inInstancedContraption, 2); + + for (int i : Iterate.zeroAndOne) { + Transform secondShaft = secondaryShafts[i]; + secondShaft.translate(-.5f, .25f, .5f + i * -2) + .centre() + .rotateX(wheelAngle) + .unCentre(); + finalize(secondShaft, ms, light, vb); + } + + Transform bogeyDrive = getTransformFromPartial(BOGEY_DRIVE, ms, inInstancedContraption); + finalize(bogeyDrive, ms, light, vb); + + Transform bogeyPiston = getTransformFromPartial(BOGEY_PISTON, ms, inInstancedContraption) + .translate(0, 0, 1 / 4f * Math.sin(AngleHelper.rad(wheelAngle))); + finalize(bogeyPiston, ms, light, vb); + + if (!inInstancedContraption) + ms.pushPose(); + + Transform bogeyWheels = getTransformFromPartial(LARGE_BOGEY_WHEELS, ms, inInstancedContraption) + .translate(0, 1, 0) + .rotateX(wheelAngle); + finalize(bogeyWheels, ms, light, vb); + + Transform bogeyPin = getTransformFromPartial(BOGEY_PIN, ms, inInstancedContraption) + .translate(0, 1, 0) + .rotateX(wheelAngle) + .translate(0, 1 / 4f, 0) + .rotateX(-wheelAngle); + finalize(bogeyPin, ms, light, vb); + + if (!inInstancedContraption) + ms.popPose(); + } + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackEdge.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackEdge.java index c7ebc40df..111c59dd3 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackEdge.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackEdge.java @@ -24,13 +24,19 @@ public class TrackEdge { BezierConnection turn; EdgeData edgeData; boolean interDimensional; + TrackMaterial trackMaterial; - public TrackEdge(TrackNode node1, TrackNode node2, BezierConnection turn) { + public TrackEdge(TrackNode node1, TrackNode node2, BezierConnection turn, TrackMaterial trackMaterial) { this.interDimensional = !node1.location.dimension.equals(node2.location.dimension); this.edgeData = new EdgeData(this); this.node1 = node1; this.node2 = node2; this.turn = turn; + this.trackMaterial = trackMaterial; + } + + public TrackMaterial getTrackMaterial() { + return trackMaterial; } public boolean isTurn() { @@ -230,13 +236,15 @@ public class TrackEdge { public CompoundTag write(DimensionPalette dimensions) { CompoundTag baseCompound = isTurn() ? turn.write(BlockPos.ZERO) : new CompoundTag(); baseCompound.put("Signals", edgeData.write(dimensions)); + baseCompound.putString("Material", getTrackMaterial().id.toString()); return baseCompound; } public static TrackEdge read(TrackNode node1, TrackNode node2, CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) { TrackEdge trackEdge = - new TrackEdge(node1, node2, tag.contains("Positions") ? new BezierConnection(tag, BlockPos.ZERO) : null); + new TrackEdge(node1, node2, tag.contains("Positions") ? new BezierConnection(tag, BlockPos.ZERO) : null, + TrackMaterial.deserialize(tag.getString("Material"))); trackEdge.edgeData = EdgeData.read(tag.getCompound("Signals"), trackEdge, graph, dimensions); return trackEdge; } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java index 897713e94..8bb92da9f 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraph.java @@ -393,14 +393,15 @@ public class TrackGraph { return connectionsFrom.get(nodes.getSecond()); } - public void connectNodes(LevelAccessor reader, TrackNodeLocation location, TrackNodeLocation location2, + public void connectNodes(LevelAccessor reader, DiscoveredLocation location, DiscoveredLocation location2, @Nullable BezierConnection turn) { TrackNode node1 = nodes.get(location); TrackNode node2 = nodes.get(location2); boolean bezier = turn != null; - TrackEdge edge = new TrackEdge(node1, node2, turn); - TrackEdge edge2 = new TrackEdge(node2, node1, bezier ? turn.secondary() : null); + TrackMaterial material = bezier ? turn.getMaterial() : location2.materialA; + TrackEdge edge = new TrackEdge(node1, node2, turn, material); + TrackEdge edge2 = new TrackEdge(node2, node1, bezier ? turn.secondary() : null, material); for (TrackGraph graph : Create.RAILWAYS.trackNetworks.values()) { for (TrackNode otherNode1 : graph.nodes.values()) { diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSync.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSync.java index 0ece1166a..a82977da8 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSync.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSync.java @@ -60,7 +60,7 @@ public class TrackGraphSync { public void edgeAdded(TrackGraph graph, TrackNode node1, TrackNode node2, TrackEdge edge) { flushGraphPacket(graph); currentGraphSyncPacket.addedEdges - .add(Pair.of(Couple.create(node1.getNetId(), node2.getNetId()), edge.getTurn())); + .add(Pair.of(Pair.of(Couple.create(node1.getNetId(), node2.getNetId()), edge.getTrackMaterial()), edge.getTurn())); currentPayload++; } @@ -82,7 +82,7 @@ public class TrackGraphSync { if (currentGraphSyncPacket.addedNodes.remove(nodeId) == null) currentGraphSyncPacket.removedNodes.add(nodeId); currentGraphSyncPacket.addedEdges.removeIf(pair -> { - Couple ids = pair.getFirst(); + Couple ids = pair.getFirst().getFirst(); return ids.getFirst() .intValue() == nodeId || ids.getSecond() @@ -156,7 +156,7 @@ public class TrackGraphSync { graph.connectionsByNode.get(node) .forEach((node2, edge) -> { Couple key = Couple.create(node.getNetId(), node2.getNetId()); - currentPacket.addedEdges.add(Pair.of(key, edge.getTurn())); + currentPacket.addedEdges.add(Pair.of(Pair.of(key, edge.getTrackMaterial()), edge.getTurn())); currentPacket.syncEdgeData(node, node2, edge); }); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSyncPacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSyncPacket.java index 33e5e0790..7707db41b 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSyncPacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphSyncPacket.java @@ -22,7 +22,7 @@ import net.minecraft.world.phys.Vec3; public class TrackGraphSyncPacket extends TrackGraphPacket { Map> addedNodes; - List, BezierConnection>> addedEdges; + List, TrackMaterial>, BezierConnection>> addedEdges; List removedNodes; List addedEdgePoints; List removedEdgePoints; @@ -79,7 +79,7 @@ public class TrackGraphSyncPacket extends TrackGraphPacket { size = buffer.readVarInt(); for (int i = 0; i < size; i++) addedEdges.add( - Pair.of(Couple.create(buffer::readVarInt), buffer.readBoolean() ? new BezierConnection(buffer) : null)); + Pair.of(Pair.of(Couple.create(buffer::readVarInt), TrackMaterial.deserialize(buffer.readUtf())), buffer.readBoolean() ? new BezierConnection(buffer) : null)); size = buffer.readVarInt(); for (int i = 0; i < size; i++) @@ -134,8 +134,9 @@ public class TrackGraphSyncPacket extends TrackGraphPacket { buffer.writeVarInt(addedEdges.size()); addedEdges.forEach(pair -> { - pair.getFirst() + pair.getFirst().getFirst() .forEach(buffer::writeVarInt); + buffer.writeUtf(pair.getFirst().getSecond().id.toString()); BezierConnection turn = pair.getSecond(); buffer.writeBoolean(turn != null); if (turn != null) @@ -192,13 +193,13 @@ public class TrackGraphSyncPacket extends TrackGraphPacket { graph.loadNode(nodeLocation.getFirst(), nodeId, nodeLocation.getSecond()); } - for (Pair, BezierConnection> pair : addedEdges) { - Couple nodes = pair.getFirst() + for (Pair, TrackMaterial>, BezierConnection> pair : addedEdges) { + Couple nodes = pair.getFirst().getFirst() .map(graph::getNode); TrackNode node1 = nodes.getFirst(); TrackNode node2 = nodes.getSecond(); if (node1 != null && node2 != null) - graph.putConnection(node1, node2, new TrackEdge(node1, node2, pair.getSecond())); + graph.putConnection(node1, node2, new TrackEdge(node1, node2, pair.getSecond(), pair.getFirst().getSecond())); } for (TrackEdgePoint edgePoint : addedEdgePoints) @@ -268,4 +269,4 @@ public class TrackGraphSyncPacket extends TrackGraphPacket { updatedEdgeData.put(key, Pair.of(groupType, list)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphVisualizer.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphVisualizer.java index 9122261df..d7ed0979c 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphVisualizer.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphVisualizer.java @@ -37,7 +37,6 @@ public class TrackGraphVisualizer { Vec3 camera = cameraEntity.getEyePosition(); Outliner outliner = CreateClient.OUTLINER; - boolean ctrl = false; // AllKeys.isKeyDown(GLFW.GLFW_KEY_LEFT_CONTROL); Map allGroups = Create.RAILWAYS.sided(null).signalEdgeGroups; float width = 1 / 8f; @@ -213,7 +212,7 @@ public class TrackGraphVisualizer { } } - public static void debugViewGraph(TrackGraph graph) { + public static void debugViewGraph(TrackGraph graph, boolean extended) { Minecraft mc = Minecraft.getInstance(); Entity cameraEntity = mc.cameraEntity; if (cameraEntity == null) @@ -266,6 +265,17 @@ public class TrackGraphVisualizer { yOffset = new Vec3(0, (other.hashCode() > hashCode ? 6 : 4) / 16f, 0); if (!edge.isTurn()) { + if (extended) { + Vec3 materialPos = edge.getPosition(graph, 0.5) + .add(0, 1, 0); + CreateClient.OUTLINER.showItem(Pair.of(edge, edge.edgeData), materialPos, + edge.getTrackMaterial() + .asStack()); + CreateClient.OUTLINER.showAABB(edge.edgeData, AABB.ofSize(materialPos, .25, 0, .25) + .move(0, -0.5, 0)) + .lineWidth(1 / 16f) + .colored(graph.color); + } CreateClient.OUTLINER.showLine(edge, edge.getPosition(graph, 0) .add(yOffset), edge.getPosition(graph, 1) @@ -277,6 +287,16 @@ public class TrackGraphVisualizer { Vec3 previous = null; BezierConnection turn = edge.getTurn(); + if (extended) { + Vec3 materialPos = edge.getPosition(graph, 0.5) + .add(0, 1, 0); + CreateClient.OUTLINER.showItem(Pair.of(edge, edge.edgeData), materialPos, edge.getTrackMaterial() + .asStack()); + CreateClient.OUTLINER.showAABB(edge.edgeData, AABB.ofSize(materialPos, .25, 0, .25) + .move(0, -0.5, 0)) + .lineWidth(1 / 16f) + .colored(graph.color); + } for (int i = 0; i <= turn.getSegmentCount(); i++) { Vec3 current = edge.getPosition(graph, i * 1f / turn.getSegmentCount()); if (previous != null) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackMaterial.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackMaterial.java new file mode 100644 index 000000000..8d8d35a38 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackMaterial.java @@ -0,0 +1,171 @@ +package com.simibubi.create.content.logistics.trains; + +import static com.simibubi.create.content.logistics.trains.TrackMaterialFactory.make; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.core.PartialModel; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllPartialModels; +import com.simibubi.create.Create; +import com.simibubi.create.content.logistics.trains.track.TrackBlock; +import com.tterrag.registrate.util.nullness.NonNullSupplier; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; + +public class TrackMaterial { + public static final Map ALL = new HashMap<>(); + + public static final TrackMaterial ANDESITE = make(Create.asResource("andesite")) + .lang("Andesite") + .block(NonNullSupplier.lazy(() -> AllBlocks.TRACK)) + .particle(Create.asResource("block/palettes/stone_types/polished/andesite_cut_polished")) + .defaultModels() + .build(); + + public final ResourceLocation id; + public final String langName; + public final NonNullSupplier> trackBlock; + public final Ingredient sleeperIngredient; + public final Ingredient railsIngredient; + public final ResourceLocation particle; + public final TrackType trackType; + + @Nullable + private final TrackMaterial.TrackType.TrackBlockFactory customFactory; + + @OnlyIn(Dist.CLIENT) + protected TrackModelHolder modelHolder; + + @OnlyIn(Dist.CLIENT) + public TrackModelHolder getModelHolder() { + return modelHolder; + } + + public TrackMaterial(ResourceLocation id, String langName, NonNullSupplier> trackBlock, + ResourceLocation particle, Ingredient sleeperIngredient, Ingredient railsIngredient, + TrackType trackType, Supplier> modelHolder) { + this(id, langName, trackBlock, particle, sleeperIngredient, railsIngredient, trackType, modelHolder, null); + } + + public TrackMaterial(ResourceLocation id, String langName, NonNullSupplier> trackBlock, + ResourceLocation particle, Ingredient sleeperIngredient, Ingredient railsIngredient, + TrackType trackType, Supplier> modelHolder, + @Nullable TrackType.TrackBlockFactory customFactory) { + this.id = id; + this.langName = langName; + this.trackBlock = trackBlock; + this.sleeperIngredient = sleeperIngredient; + this.railsIngredient = railsIngredient; + this.particle = particle; + this.trackType = trackType; + this.customFactory = customFactory; + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.modelHolder = modelHolder.get().get()); + ALL.put(this.id, this); + } + + public NonNullSupplier getBlockSupplier() { + return this.trackBlock.get(); + } + + public TrackBlock getBlock() { + return getBlockSupplier().get(); + } + + public ItemStack asStack() { + return asStack(1); + } + + public ItemStack asStack(int count) { + return new ItemStack(getBlock(), count); + } + + public TrackBlock createBlock(BlockBehaviour.Properties properties) { + return (this.customFactory != null ? this.customFactory : this.trackType.factory) + .create(properties, this); + } + + public boolean isFromMod(String modId) { + return this.id.getNamespace().equals(modId); + } + + public static List allFromMod(String modid) { + return ALL.values().stream().filter(tm -> tm.isFromMod(modid)).toList(); + } + + public static List> allBlocksFromMod(String modid) { + List> list = new ArrayList<>(); + for (TrackMaterial material : allFromMod(modid)) { + list.add(material.getBlockSupplier()); + } + return list; + } + + public static List> allBlocks() { + List> list = new ArrayList<>(); + for (TrackMaterial material : ALL.values()) { + list.add(material.getBlockSupplier()); + } + return list; + } + + public String resourceName() { + return this.id.getPath(); + } + + public static TrackMaterial deserialize(String serializedName) { + if (serializedName.isBlank()) // Data migrating from 0.5 + return ANDESITE; + + ResourceLocation id = ResourceLocation.tryParse(serializedName); + if (ALL.containsKey(id)) + return ALL.get(id); + + Create.LOGGER.error("Failed to locate serialized track material: " + serializedName); + return ANDESITE; + } + + public static class TrackType { + @FunctionalInterface + public interface TrackBlockFactory { + TrackBlock create(BlockBehaviour.Properties properties, TrackMaterial material); + } + + public static final TrackType STANDARD = new TrackType(Create.asResource("standard"), TrackBlock::new); + + public final ResourceLocation id; + protected final TrackBlockFactory factory; + + public TrackType(ResourceLocation id, TrackBlockFactory factory) { + this.id = id; + this.factory = factory; + } + } + + public static TrackMaterial fromItem(Item item) { + if (item instanceof BlockItem blockItem && blockItem.getBlock() instanceof ITrackBlock trackBlock) + return trackBlock.getMaterial(); + return TrackMaterial.ANDESITE; + } + + @OnlyIn(Dist.CLIENT) + public record TrackModelHolder(PartialModel tie, PartialModel segment_left, PartialModel segment_right) { + static final TrackModelHolder DEFAULT = new TrackModelHolder(AllPartialModels.TRACK_TIE, + AllPartialModels.TRACK_SEGMENT_LEFT, AllPartialModels.TRACK_SEGMENT_RIGHT); + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackMaterialFactory.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackMaterialFactory.java new file mode 100644 index 000000000..19c15180c --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackMaterialFactory.java @@ -0,0 +1,142 @@ +package com.simibubi.create.content.logistics.trains; + +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.core.PartialModel; +import com.simibubi.create.AllTags; +import com.simibubi.create.content.logistics.trains.track.TrackBlock; +import com.tterrag.registrate.util.nullness.NonNullSupplier; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; + +public class TrackMaterialFactory { + private final ResourceLocation id; + private String langName; + private NonNullSupplier> trackBlock; + private Ingredient sleeperIngredient = Ingredient.EMPTY; + private Ingredient railsIngredient = Ingredient.fromValues(Stream.of(new Ingredient.TagValue(AllTags.forgeItemTag("nuggets/iron")), new Ingredient.TagValue(AllTags.forgeItemTag("nuggets/zinc")))); + private ResourceLocation particle; + private TrackMaterial.TrackType trackType = TrackMaterial.TrackType.STANDARD; + + @Nullable + private TrackMaterial.TrackType.TrackBlockFactory customFactory = null; + + @OnlyIn(Dist.CLIENT) + private TrackMaterial.TrackModelHolder modelHolder; + @OnlyIn(Dist.CLIENT) + private PartialModel tieModel; + @OnlyIn(Dist.CLIENT) + private PartialModel leftSegmentModel; + @OnlyIn(Dist.CLIENT) + private PartialModel rightSegmentModel; + + public TrackMaterialFactory(ResourceLocation id) { + this.id = id; + } + + public static TrackMaterialFactory make(ResourceLocation id) { // Convenience function for static import + return new TrackMaterialFactory(id); + } + + public TrackMaterialFactory lang(String langName) { + this.langName = langName; + return this; + } + + public TrackMaterialFactory block(NonNullSupplier> trackBlock) { + this.trackBlock = trackBlock; + return this; + } + + public TrackMaterialFactory defaultModels() { // was setBuiltin + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.modelHolder = TrackMaterial.TrackModelHolder.DEFAULT); + return this; + } + + public TrackMaterialFactory sleeper(Ingredient sleeperIngredient) { + this.sleeperIngredient = sleeperIngredient; + return this; + } + + public TrackMaterialFactory sleeper(ItemLike... items) { + this.sleeperIngredient = Ingredient.of(items); + return this; + } + + public TrackMaterialFactory rails(Ingredient railsIngredient) { + this.railsIngredient = railsIngredient; + return this; + } + + public TrackMaterialFactory rails(ItemLike... items) { + this.railsIngredient = Ingredient.of(items); + return this; + } + + public TrackMaterialFactory noRecipeGen() { + this.railsIngredient = Ingredient.EMPTY; + this.sleeperIngredient = Ingredient.EMPTY; + return this; + } + + public TrackMaterialFactory particle(ResourceLocation particle) { + this.particle = particle; + return this; + } + + public TrackMaterialFactory trackType(TrackMaterial.TrackType trackType) { + this.trackType = trackType; + return this; + } + + public TrackMaterialFactory standardModels() { // was defaultModels + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + String namespace = id.getNamespace(); + String prefix = "block/track/" + id.getPath() + "/"; + tieModel = new PartialModel(new ResourceLocation(namespace, prefix + "tie")); + leftSegmentModel = new PartialModel(new ResourceLocation(namespace, prefix + "segment_left")); + rightSegmentModel = new PartialModel(new ResourceLocation(namespace, prefix + "segment_right")); + }); + return this; + } + + public TrackMaterialFactory customModels(Supplier> tieModel, Supplier> leftSegmentModel, Supplier> rightSegmentModel) { + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + this.tieModel = tieModel.get().get(); + this.leftSegmentModel = leftSegmentModel.get().get(); + this.rightSegmentModel = rightSegmentModel.get().get(); + }); + return this; + } + + public TrackMaterialFactory customBlockFactory(TrackMaterial.TrackType.TrackBlockFactory factory) { + this.customFactory = factory; + return this; + } + + public TrackMaterial build() { + assert trackBlock != null; + assert langName != null; + assert particle != null; + assert trackType != null; + assert sleeperIngredient != null; + assert railsIngredient != null; + assert id != null; + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + assert modelHolder != null; + if (tieModel != null || leftSegmentModel != null || rightSegmentModel != null) { + assert tieModel != null && leftSegmentModel != null && rightSegmentModel != null; + modelHolder = new TrackMaterial.TrackModelHolder(tieModel, leftSegmentModel, rightSegmentModel); + } + }); + return new TrackMaterial(id, langName, trackBlock, particle, sleeperIngredient, railsIngredient, trackType, () -> () -> modelHolder, customFactory); + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java index 621e27d95..d0e0a487b 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java @@ -25,8 +25,8 @@ public class TrackNodeLocation extends Vec3i { this(vec.x, vec.y, vec.z); } - public TrackNodeLocation(double p_121865_, double p_121866_, double p_121867_) { - super(Math.round(p_121865_ * 2), Math.floor(p_121866_) * 2, Math.round(p_121867_ * 2)); + public TrackNodeLocation(double x, double y, double z) { + super(Math.round(x * 2), Math.floor(y) * 2, Math.round(z * 2)); } public TrackNodeLocation in(Level level) { @@ -122,9 +122,11 @@ public class TrackNodeLocation extends Vec3i { boolean forceNode = false; Vec3 direction; Vec3 normal; + TrackMaterial materialA; + TrackMaterial materialB; - public DiscoveredLocation(Level level, double p_121865_, double p_121866_, double p_121867_) { - super(p_121865_, p_121866_, p_121867_); + public DiscoveredLocation(Level level, double x, double y, double z) { + super(x, y, z); in(level); } @@ -137,6 +139,22 @@ public class TrackNodeLocation extends Vec3i { this(level.dimension(), vec); } + public DiscoveredLocation materialA(TrackMaterial material) { + this.materialA = material; + return this; + } + + public DiscoveredLocation materialB(TrackMaterial material) { + this.materialB = material; + return this; + } + + public DiscoveredLocation materials(TrackMaterial materialA, TrackMaterial materialB) { + this.materialA = materialA; + this.materialB = materialB; + return this; + } + public DiscoveredLocation viaTurn(BezierConnection turn) { this.turn = turn; if (turn != null) @@ -176,6 +194,10 @@ public class TrackNodeLocation extends Vec3i { return forceNode; } + public boolean differentMaterials() { + return materialA != materialB; + } + public boolean notInLineWith(Vec3 direction) { return this.direction != null && Math.max(direction.dot(this.direction), direction.dot(this.direction.scale(-1))) < 7 / 8f; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java index 611787364..e401a04cf 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackPropagator.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Set; 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.management.edgePoint.signal.SignalPropagator; @@ -16,6 +17,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; public class TrackPropagator { @@ -135,6 +137,7 @@ public class TrackPropagator { if (graph == null) graph = other; else { + MinecraftForge.EVENT_BUS.post(new TrackGraphMergeEvent(other, graph)); other.transferAll(graph); manager.removeGraphAndGroup(other); sync.graphRemoved(other); @@ -234,10 +237,12 @@ public class TrackPropagator { return true; if (location.shouldForceNode()) return true; + if (location.differentMaterials()) + return true; if (next.stream() .anyMatch(DiscoveredLocation::shouldForceNode)) return true; - + Vec3 direction = location.direction; if (direction != null && next.stream() .anyMatch(dl -> dl.notInLineWith(direction))) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/BackupBogeyRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/BackupBogeyRenderer.java new file mode 100644 index 000000000..80494a94a --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/BackupBogeyRenderer.java @@ -0,0 +1,22 @@ +package com.simibubi.create.content.logistics.trains.entity; + +import com.jozufozu.flywheel.api.MaterialManager; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.simibubi.create.content.logistics.trains.BogeyRenderer; + +import net.minecraft.nbt.CompoundTag; + +public class BackupBogeyRenderer extends BogeyRenderer.CommonRenderer { + public static BackupBogeyRenderer INSTANCE = new BackupBogeyRenderer(); + + @Override + public void render(CompoundTag bogeyData, float wheelAngle, PoseStack ms, int light, VertexConsumer vb, boolean inContraption) { + + } + + @Override + public void initialiseContraptionModelData(MaterialManager materialManager) { + + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyInstance.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyInstance.java index 4c0b2a000..2e512847d 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyInstance.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyInstance.java @@ -1,235 +1,69 @@ package com.simibubi.create.content.logistics.trains.entity; -import com.jozufozu.flywheel.api.Material; +import java.util.Optional; + import com.jozufozu.flywheel.api.MaterialManager; -import com.jozufozu.flywheel.core.Materials; -import com.jozufozu.flywheel.core.materials.model.ModelData; import com.jozufozu.flywheel.util.AnimationTickHolder; import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.AllBlocks; -import com.simibubi.create.AllPartialModels; -import com.simibubi.create.content.contraptions.relays.elementary.ShaftBlock; -import com.simibubi.create.foundation.utility.AngleHelper; -import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.content.logistics.trains.BogeyRenderer; +import com.simibubi.create.content.logistics.trains.BogeySizes; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.LightLayer; import net.minecraft.world.phys.Vec3; -public sealed class BogeyInstance { +public final class BogeyInstance { + private final BogeySizes.BogeySize size; + private final BogeyStyle style; public final CarriageBogey bogey; - private final ModelData[] shafts; + public final BogeyRenderer renderer; + public final Optional commonRenderer; - protected BogeyInstance(CarriageBogey bogey, MaterialManager materialManager) { + public BogeyInstance(CarriageBogey bogey, BogeyStyle style, BogeySizes.BogeySize size, + MaterialManager materialManager) { this.bogey = bogey; + this.size = size; + this.style = style; - shafts = new ModelData[2]; - - materialManager.defaultSolid() - .material(Materials.TRANSFORMED) - .getModel(AllBlocks.SHAFT.getDefaultState() - .setValue(ShaftBlock.AXIS, Direction.Axis.Z)) - .createInstances(shafts); + this.renderer = this.style.createRendererInstance(this.size); + this.commonRenderer = this.style.getNewCommonRenderInstance(); + commonRenderer.ifPresent(bogeyRenderer -> bogeyRenderer.initialiseContraptionModelData(materialManager)); + renderer.initialiseContraptionModelData(materialManager); } - public void remove() { - for (ModelData shaft : shafts) - shaft.delete(); - } - - public void hiddenFrame() { + void hiddenFrame() { beginFrame(0, null); } - + public void beginFrame(float wheelAngle, PoseStack ms) { if (ms == null) { - for (int i : Iterate.zeroAndOne) - shafts[i].setEmptyTransform(); + renderer.emptyTransforms(); return; } - for (int i : Iterate.zeroAndOne) - shafts[i].setTransform(ms) - .translate(-.5f, .25f, i * -1) - .centre() - .rotateZ(wheelAngle) - .unCentre(); + commonRenderer.ifPresent(bogeyRenderer -> bogeyRenderer.render(bogey.bogeyData, wheelAngle, ms)); + renderer.render(bogey.bogeyData, wheelAngle, ms); } public void updateLight(BlockAndTintGetter world, CarriageContraptionEntity entity) { var lightPos = new BlockPos(getLightPos(entity)); - - updateLight(world.getBrightness(LightLayer.BLOCK, lightPos), world.getBrightness(LightLayer.SKY, lightPos)); + commonRenderer + .ifPresent(bogeyRenderer -> bogeyRenderer.updateLight(world.getBrightness(LightLayer.BLOCK, lightPos), + world.getBrightness(LightLayer.SKY, lightPos))); + renderer.updateLight(world.getBrightness(LightLayer.BLOCK, lightPos), + world.getBrightness(LightLayer.SKY, lightPos)); } private Vec3 getLightPos(CarriageContraptionEntity entity) { - if (bogey.getAnchorPosition() != null) { - return bogey.getAnchorPosition(); - } else { - return entity.getLightProbePosition(AnimationTickHolder.getPartialTicks()); - } + return bogey.getAnchorPosition() != null ? bogey.getAnchorPosition() + : entity.getLightProbePosition(AnimationTickHolder.getPartialTicks()); } - public void updateLight(int blockLight, int skyLight) { - for (ModelData shaft : shafts) { - shaft.setBlockLight(blockLight) - .setSkyLight(skyLight); - } - } - - public static final class Frame extends BogeyInstance { - - private final ModelData frame; - private final ModelData[] wheels; - - public Frame(CarriageBogey bogey, MaterialManager materialManager) { - super(bogey, materialManager); - - frame = materialManager.defaultSolid() - .material(Materials.TRANSFORMED) - .getModel(AllPartialModels.BOGEY_FRAME) - .createInstance(); - - wheels = new ModelData[2]; - - materialManager.defaultSolid() - .material(Materials.TRANSFORMED) - .getModel(AllPartialModels.SMALL_BOGEY_WHEELS) - .createInstances(wheels); - } - - @Override - public void beginFrame(float wheelAngle, PoseStack ms) { - super.beginFrame(wheelAngle, ms); - - if (ms == null) { - frame.setEmptyTransform(); - for (int side : Iterate.positiveAndNegative) - wheels[(side + 1) / 2].setEmptyTransform(); - return; - } - - frame.setTransform(ms); - - for (int side : Iterate.positiveAndNegative) { - wheels[(side + 1) / 2].setTransform(ms) - .translate(0, 12 / 16f, side) - .rotateX(wheelAngle); - } - } - - @Override - public void updateLight(int blockLight, int skyLight) { - super.updateLight(blockLight, skyLight); - frame.setBlockLight(blockLight) - .setSkyLight(skyLight); - for (ModelData wheel : wheels) - wheel.setBlockLight(blockLight) - .setSkyLight(skyLight); - } - - @Override - public void remove() { - super.remove(); - frame.delete(); - for (ModelData wheel : wheels) - wheel.delete(); - } - } - - public static final class Drive extends BogeyInstance { - - private final ModelData[] secondShaft; - private final ModelData drive; - private final ModelData piston; - private final ModelData wheels; - private final ModelData pin; - - public Drive(CarriageBogey bogey, MaterialManager materialManager) { - super(bogey, materialManager); - Material mat = materialManager.defaultSolid() - .material(Materials.TRANSFORMED); - - secondShaft = new ModelData[2]; - - mat.getModel(AllBlocks.SHAFT.getDefaultState() - .setValue(ShaftBlock.AXIS, Direction.Axis.X)) - .createInstances(secondShaft); - - drive = mat.getModel(AllPartialModels.BOGEY_DRIVE) - .createInstance(); - piston = mat.getModel(AllPartialModels.BOGEY_PISTON) - .createInstance(); - wheels = mat.getModel(AllPartialModels.LARGE_BOGEY_WHEELS) - .createInstance(); - pin = mat.getModel(AllPartialModels.BOGEY_PIN) - .createInstance(); - } - - @Override - public void beginFrame(float wheelAngle, PoseStack ms) { - super.beginFrame(wheelAngle, ms); - - if (ms == null) { - for (int i : Iterate.zeroAndOne) - secondShaft[i].setEmptyTransform(); - drive.setEmptyTransform(); - piston.setEmptyTransform(); - wheels.setEmptyTransform(); - pin.setEmptyTransform(); - return; - } - - for (int i : Iterate.zeroAndOne) - secondShaft[i].setTransform(ms) - .translate(-.5f, .25f, .5f + i * -2) - .centre() - .rotateX(wheelAngle) - .unCentre(); - - drive.setTransform(ms); - piston.setTransform(ms) - .translate(0, 0, 1 / 4f * Math.sin(AngleHelper.rad(wheelAngle))); - - wheels.setTransform(ms) - .translate(0, 1, 0) - .rotateX(wheelAngle); - pin.setTransform(ms) - .translate(0, 1, 0) - .rotateX(wheelAngle) - .translate(0, 1 / 4f, 0) - .rotateX(-wheelAngle); - } - - @Override - public void updateLight(int blockLight, int skyLight) { - super.updateLight(blockLight, skyLight); - for (ModelData shaft : secondShaft) - shaft.setBlockLight(blockLight) - .setSkyLight(skyLight); - drive.setBlockLight(blockLight) - .setSkyLight(skyLight); - piston.setBlockLight(blockLight) - .setSkyLight(skyLight); - wheels.setBlockLight(blockLight) - .setSkyLight(skyLight); - pin.setBlockLight(blockLight) - .setSkyLight(skyLight); - } - - @Override - public void remove() { - super.remove(); - for (ModelData shaft : secondShaft) - shaft.delete(); - drive.delete(); - piston.delete(); - wheels.delete(); - pin.delete(); - } + @FunctionalInterface + interface BogeyInstanceFactory { + BogeyInstance create(CarriageBogey bogey, BogeySizes.BogeySize size, MaterialManager materialManager); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyStyle.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyStyle.java new file mode 100644 index 000000000..2b3efc8d7 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/BogeyStyle.java @@ -0,0 +1,114 @@ +package com.simibubi.create.content.logistics.trains.entity; + +import com.jozufozu.flywheel.api.MaterialManager; +import com.simibubi.create.AllBogeyStyles; +import com.simibubi.create.AllSoundEvents; +import com.simibubi.create.content.logistics.trains.BogeyRenderer; + +import com.simibubi.create.content.logistics.trains.BogeyRenderer.CommonRenderer; +import com.simibubi.create.content.logistics.trains.BogeySizes; + +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.level.block.Block; +import net.minecraftforge.registries.ForgeRegistries; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + + +public class BogeyStyle { + private final Optional> commonRendererFactory; + + public final ResourceLocation name; + public final ResourceLocation cycleGroup; + private final Optional commonRenderer; + private final Map sizes; + public final Component displayName; + public final ResourceLocation soundType; + public final ParticleOptions contactParticle; + public final ParticleOptions smokeParticle; + public final CompoundTag defaultData; + + public BogeyStyle(ResourceLocation name, ResourceLocation cycleGroup, Component displayName, ResourceLocation soundType, ParticleOptions contactParticle, ParticleOptions smokeParticle, + CompoundTag defaultData, Map sizes, Optional> commonRenderer) { + this.name = name; + this.cycleGroup = cycleGroup; + this.displayName = displayName; + this.soundType = soundType; + this.contactParticle = contactParticle; + this.smokeParticle = smokeParticle; + this.defaultData = defaultData; + + this.sizes = sizes; + + this.commonRendererFactory = commonRenderer; + this.commonRenderer = commonRenderer.map(Supplier::get); + } + + public Map getCycleGroup() { + return AllBogeyStyles.getCycleGroup(cycleGroup); + } + + public Block getNextBlock(BogeySizes.BogeySize currentSize) { + return Stream.iterate(currentSize.increment(), BogeySizes.BogeySize::increment) + .filter(sizes::containsKey) + .findFirst() + .map(size -> ForgeRegistries.BLOCKS.getValue(sizes.get(size).block())) + .orElse(ForgeRegistries.BLOCKS.getValue(sizes.get(currentSize).block())); + } + + public Block getBlockOfSize(BogeySizes.BogeySize size) { + return ForgeRegistries.BLOCKS.getValue(sizes.get(size).block()); + } + + public Set validSizes() { + return sizes.keySet(); + } + + @NotNull + public SoundEvent getSoundType() { + AllSoundEvents.SoundEntry entry = AllSoundEvents.ALL.get(this.soundType); + if (entry == null || entry.getMainEvent() == null) entry = AllSoundEvents.TRAIN2; + return entry.getMainEvent(); + } + + public BogeyRenderer createRendererInstance(BogeySizes.BogeySize size) { + return this.sizes.get(size).createRenderInstance(); + } + + public BogeyRenderer getInWorldRenderInstance(BogeySizes.BogeySize size) { + SizeData sizeData = this.sizes.get(size); + return sizeData != null ? sizeData.getInWorldInstance() : BackupBogeyRenderer.INSTANCE; + } + + public Optional getInWorldCommonRenderInstance() { + return this.commonRenderer; + } + + public Optional getNewCommonRenderInstance() { + return this.commonRendererFactory.map(Supplier::get); + } + + public BogeyInstance createInstance(CarriageBogey bogey, BogeySizes.BogeySize size, MaterialManager materialManager) { + return new BogeyInstance(bogey, this, size, materialManager); + } + + public record SizeData(ResourceLocation block, Supplier rendererFactory, BogeyRenderer instance) { + public BogeyRenderer createRenderInstance() { + return rendererFactory.get(); + } + + public BogeyRenderer getInWorldInstance() { + return instance; + } + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java index 72be60165..45d129f94 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Carriage.java @@ -85,6 +85,11 @@ public class Carriage { bogey2.carriage = this; } + public boolean isOnIncompatibleTrack() { + return leadingBogey().type.isOnIncompatibleTrack(this, true) + || trailingBogey().type.isOnIncompatibleTrack(this, false); + } + public void setTrain(Train train) { this.train = train; } @@ -103,7 +108,7 @@ public class Carriage { DimensionalCarriageEntity dimensional = getDimensional(level); dimensional.alignEntity(entity); - dimensional.removeAndSaveEntity(entity, false); + dimensional.removeAndSaveEntity(entity, true); } public DimensionalCarriageEntity getDimensional(Level level) { @@ -306,6 +311,9 @@ public class Carriage { double leadingWheelSpacing = leadingBogey.type.getWheelPointSpacing(); double trailingWheelSpacing = trailingBogey.type.getWheelPointSpacing(); + boolean leadingUpsideDown = leadingBogey.isUpsideDown(); + boolean trailingUpsideDown = trailingBogey.isUpsideDown(); + for (boolean leading : Iterate.trueAndFalse) { TravellingPoint point = leading ? getLeadingPoint() : getTrailingPoint(); TravellingPoint otherPoint = !leading ? getLeadingPoint() : getTrailingPoint(); @@ -321,26 +329,31 @@ public class Carriage { dce.positionAnchor = dimension.equals(leadingBogeyDim) ? leadingBogey.getAnchorPosition() : pivoted(dce, dimension, point, - leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2); + leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2, + leadingUpsideDown, trailingUpsideDown); + + boolean backAnchorFlip = trailingBogey.isUpsideDown() ^ leadingBogey.isUpsideDown(); if (isOnTwoBogeys()) { dce.rotationAnchors.setFirst(dimension.equals(leadingBogeyDim) ? leadingBogey.getAnchorPosition() : pivoted(dce, dimension, point, - leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2)); - dce.rotationAnchors.setSecond(dimension.equals(trailingBogeyDim) ? trailingBogey.getAnchorPosition() + leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2, + leadingUpsideDown, trailingUpsideDown)); + dce.rotationAnchors.setSecond(dimension.equals(trailingBogeyDim) ? trailingBogey.getAnchorPosition(backAnchorFlip) : pivoted(dce, dimension, point, - leading ? leadingWheelSpacing / 2 + bogeySpacing : trailingWheelSpacing / 2)); + leading ? leadingWheelSpacing / 2 + bogeySpacing : trailingWheelSpacing / 2, + leadingUpsideDown, trailingUpsideDown)); } else { if (dimension.equals(otherDimension)) { dce.rotationAnchors = leadingBogey.points.map(tp -> tp.getPosition(train.graph)); } else { - dce.rotationAnchors - .setFirst(leadingBogey.points.getFirst() == point ? point.getPosition(train.graph) - : pivoted(dce, dimension, point, leadingWheelSpacing)); - dce.rotationAnchors - .setSecond(leadingBogey.points.getSecond() == point ? point.getPosition(train.graph) - : pivoted(dce, dimension, point, leadingWheelSpacing)); + dce.rotationAnchors.setFirst(leadingBogey.points.getFirst() == point + ? point.getPosition(train.graph) + : pivoted(dce, dimension, point, leadingWheelSpacing, leadingUpsideDown, trailingUpsideDown)); + dce.rotationAnchors.setSecond(leadingBogey.points.getSecond() == point + ? point.getPosition(train.graph) + : pivoted(dce, dimension, point, leadingWheelSpacing, leadingUpsideDown, trailingUpsideDown)); } } @@ -358,15 +371,16 @@ public class Carriage { } private Vec3 pivoted(DimensionalCarriageEntity dce, ResourceKey dimension, TravellingPoint start, - double offset) { + double offset, boolean leadingUpsideDown, boolean trailingUpsideDown) { if (train.graph == null) return dce.pivot == null ? null : dce.pivot.getLocation(); TrackNodeLocation pivot = dce.findPivot(dimension, start == getLeadingPoint()); if (pivot == null) return null; - Vec3 startVec = start.getPosition(train.graph); + boolean flipped = start != getLeadingPoint() && (leadingUpsideDown != trailingUpsideDown); + Vec3 startVec = start.getPosition(train.graph, flipped); Vec3 portalVec = pivot.getLocation() - .add(0, 1, 0); + .add(0, leadingUpsideDown ? -1.0 : 1.0, 0); return VecHelper.lerp((float) (offset / startVec.distanceTo(portalVec)), startVec, portalVec); } @@ -725,8 +739,8 @@ public class Carriage { } - private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean portal) { - if (!portal) { + private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean capture) { + if (!capture) { sp.stopRiding(); return; } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageBogey.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageBogey.java index 8927939cb..b11b7fee2 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageBogey.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageBogey.java @@ -1,15 +1,21 @@ package com.simibubi.create.content.logistics.trains.entity; +import static com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity.BOGEY_DATA_KEY; +import static com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity.BOGEY_STYLE_KEY; + import javax.annotation.Nullable; import com.jozufozu.flywheel.api.MaterialManager; +import com.simibubi.create.AllBogeyStyles; import com.simibubi.create.Create; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.content.logistics.trains.DimensionPalette; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; import com.simibubi.create.content.logistics.trains.TrackGraph; +import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity; import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.RegisteredObjects; import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.animation.LerpedFloat; @@ -27,11 +33,15 @@ import net.minecraftforge.registries.ForgeRegistries; public class CarriageBogey { - public Carriage carriage; + public static final String UPSIDE_DOWN_KEY = "UpsideDown"; + public Carriage carriage; boolean isLeading; - IBogeyBlock type; + public CompoundTag bogeyData; + + AbstractBogeyBlock type; + boolean upsideDown; Couple points; LerpedFloat wheelAngle; @@ -42,8 +52,15 @@ public class CarriageBogey { int derailAngle; - public CarriageBogey(IBogeyBlock type, TravellingPoint point, TravellingPoint point2) { + public CarriageBogey(AbstractBogeyBlock type, boolean upsideDown, CompoundTag bogeyData, TravellingPoint point, TravellingPoint point2) { this.type = type; + this.upsideDown = type.canBeUpsideDown() && upsideDown; + point.upsideDown = this.upsideDown; + point2.upsideDown = this.upsideDown; + if (bogeyData == null || bogeyData.isEmpty()) + bogeyData = this.createBogeyData(); // Prevent Crash When Updating + bogeyData.putBoolean(UPSIDE_DOWN_KEY, upsideDown); + this.bogeyData = bogeyData; points = Couple.create(point, point2); wheelAngle = LerpedFloat.angular(); yaw = LerpedFloat.angular(); @@ -100,11 +117,15 @@ public class CarriageBogey { } public TravellingPoint leading() { - return points.getFirst(); + TravellingPoint point = points.getFirst(); + point.upsideDown = isUpsideDown(); + return point; } public TravellingPoint trailing() { - return points.getSecond(); + TravellingPoint point = points.getSecond(); + point.upsideDown = isUpsideDown(); + return point; } public double getStress() { @@ -118,18 +139,25 @@ public class CarriageBogey { @Nullable public Vec3 getAnchorPosition() { + return getAnchorPosition(false); + } + + @Nullable + public Vec3 getAnchorPosition(boolean flipUpsideDown) { if (leading().edge == null) return null; return points.getFirst() - .getPosition(carriage.train.graph) + .getPosition(carriage.train.graph, flipUpsideDown) .add(points.getSecond() - .getPosition(carriage.train.graph)) + .getPosition(carriage.train.graph, flipUpsideDown)) .scale(.5); } public void updateCouplingAnchor(Vec3 entityPos, float entityXRot, float entityYRot, int bogeySpacing, float partialTicks, boolean leading) { - Vec3 thisOffset = type.getConnectorAnchorOffset(); + boolean selfUpsideDown = isUpsideDown(); + boolean leadingUpsideDown = carriage.leadingBogey().isUpsideDown(); + Vec3 thisOffset = type.getConnectorAnchorOffset(selfUpsideDown); thisOffset = thisOffset.multiply(1, 1, leading ? -1 : 1); thisOffset = VecHelper.rotate(thisOffset, pitch.getValue(partialTicks), Axis.X); @@ -141,6 +169,8 @@ public class CarriageBogey { thisOffset = VecHelper.rotate(thisOffset, 180, Axis.Y); thisOffset = VecHelper.rotate(thisOffset, -entityXRot, Axis.X); thisOffset = VecHelper.rotate(thisOffset, entityYRot + 90, Axis.Y); + if (selfUpsideDown != leadingUpsideDown) + thisOffset = thisOffset.add(0, selfUpsideDown ? -2 : 2, 0); couplingAnchors.set(leading, entityPos.add(thisOffset)); } @@ -150,23 +180,46 @@ public class CarriageBogey { tag.putString("Type", RegisteredObjects.getKeyOrThrow((Block) type) .toString()); tag.put("Points", points.serializeEach(tp -> tp.write(dimensions))); + tag.putBoolean("UpsideDown", upsideDown); + bogeyData.putBoolean(UPSIDE_DOWN_KEY, upsideDown); + NBTHelper.writeResourceLocation(bogeyData, BOGEY_STYLE_KEY, getStyle().name); + tag.put(BOGEY_DATA_KEY, bogeyData); return tag; } public static CarriageBogey read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) { ResourceLocation location = new ResourceLocation(tag.getString("Type")); - IBogeyBlock type = (IBogeyBlock) ForgeRegistries.BLOCKS.getValue(location); + AbstractBogeyBlock type = (AbstractBogeyBlock) ForgeRegistries.BLOCKS.getValue(location); + boolean upsideDown = tag.getBoolean("UpsideDown"); Couple points = Couple.deserializeEach(tag.getList("Points", Tag.TAG_COMPOUND), c -> TravellingPoint.read(c, graph, dimensions)); - CarriageBogey carriageBogey = new CarriageBogey(type, points.getFirst(), points.getSecond()); - return carriageBogey; + CompoundTag data = tag.getCompound(AbstractBogeyBlockEntity.BOGEY_DATA_KEY); + return new CarriageBogey(type, upsideDown, data, points.getFirst(), points.getSecond()); } public BogeyInstance createInstance(MaterialManager materialManager) { - return type.createInstance(materialManager, this); + return this.getStyle().createInstance(this, type.getSize(), materialManager); + } + + public BogeyStyle getStyle() { + ResourceLocation location = NBTHelper.readResourceLocation(this.bogeyData, BOGEY_STYLE_KEY); + BogeyStyle style = AllBogeyStyles.BOGEY_STYLES.get(location); + return style != null ? style : AllBogeyStyles.STANDARD; // just for safety + } + + private CompoundTag createBogeyData() { + BogeyStyle style = type != null ? type.getDefaultStyle() : AllBogeyStyles.STANDARD; + CompoundTag nbt = style.defaultData != null ? style.defaultData : new CompoundTag(); + NBTHelper.writeResourceLocation(nbt, BOGEY_STYLE_KEY, style.name); + nbt.putBoolean(UPSIDE_DOWN_KEY, isUpsideDown()); + return nbt; } void setLeading() { isLeading = true; } + + public boolean isUpsideDown() { + return type.canBeUpsideDown() && upsideDown; + } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraption.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraption.java index 59102e71d..de323e7ec 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraption.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraption.java @@ -22,7 +22,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.ren import com.simibubi.create.content.contraptions.components.structureMovement.train.TrainCargoManager; import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock; import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock.HeatLevel; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Lang; @@ -71,7 +71,7 @@ public class CarriageContraption extends Contraption { // render public int portalCutoffMin; public int portalCutoffMax; - + static final IItemHandlerModifiable fallbackItems = new ItemStackHandler(); static final IFluidHandler fallbackFluids = new FluidTank(0); @@ -162,11 +162,13 @@ public class CarriageContraption extends Contraption { .getStep(), toLocalPos(pos)); } - if (blockState.getBlock() instanceof IBogeyBlock) { + if (blockState.getBlock() instanceof AbstractBogeyBlock bogey) { + boolean captureTE = bogey.captureTileEntityForTrain(); bogeys++; if (bogeys == 2) secondBogeyPos = pos; - return Pair.of(new StructureBlockInfo(pos, blockState, null), null); + return Pair.of(new StructureBlockInfo(pos, blockState, captureTE ? getBlockEntityNBT(world, pos) : null), + captureTE ? world.getBlockEntity(pos) : null); } if (AllBlocks.BLAZE_BURNER.has(blockState) @@ -235,7 +237,7 @@ public class CarriageContraption extends Contraption { protected MountedStorageManager getStorageForSpawnPacket() { return storageProxy; } - + @Override protected ContraptionType getType() { return ContraptionType.CARRIAGE; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntityRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntityRenderer.java index 5de635e4a..02b219074 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntityRenderer.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntityRenderer.java @@ -13,6 +13,7 @@ import net.minecraft.client.renderer.culling.Frustum; import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.core.BlockPos; import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.Vec3; public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer { @@ -37,7 +38,7 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer MultiBufferSource buffers, int overlay) { if (!entity.validForRender || entity.firstPositionUpdate) return; - + super.render(entity, yaw, partialTicks, ms, buffers, overlay); Carriage carriage = entity.getCarriage(); @@ -65,8 +66,9 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer translateBogey(ms, bogey, bogeySpacing, viewYRot, viewXRot, partialTicks); int light = getBogeyLightCoords(entity, bogey, partialTicks); + bogey.type.render(null, bogey.wheelAngle.getValue(partialTicks), ms, partialTicks, buffers, light, - overlay); + overlay, bogey.getStyle(), bogey.bogeyData); ms.popPose(); } @@ -80,6 +82,8 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer public static void translateBogey(PoseStack ms, CarriageBogey bogey, int bogeySpacing, float viewYRot, float viewXRot, float partialTicks) { + boolean selfUpsideDown = bogey.isUpsideDown(); + boolean leadingUpsideDown = bogey.carriage.leadingBogey().isUpsideDown(); TransformStack.cast(ms) .rotateY(viewYRot + 90) .rotateX(-viewXRot) @@ -90,7 +94,9 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer .rotateY(-viewYRot - 90) .rotateY(bogey.yaw.getValue(partialTicks)) .rotateX(bogey.pitch.getValue(partialTicks)) - .translate(0, .5f, 0); + .translate(0, .5f, 0) + .rotateZ(selfUpsideDown ? 180 : 0) + .translateY(selfUpsideDown != leadingUpsideDown ? 2 : 0); } public static int getBogeyLightCoords(CarriageContraptionEntity entity, CarriageBogey bogey, float partialTicks) { diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionInstance.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionInstance.java index 4d18e2525..6797c05cd 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionInstance.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionInstance.java @@ -7,6 +7,7 @@ import com.jozufozu.flywheel.util.AnimationTickHolder; import com.jozufozu.flywheel.util.transform.TransformStack; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Vector3f; +import com.simibubi.create.content.logistics.trains.BogeyRenderer; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; @@ -31,7 +32,8 @@ public class CarriageContraptionInstance extends EntityInstance + bogey.getStyle().createInstance(bogey, bogey.type.getSize(), manager), materialManager); updateLight(); } @@ -98,8 +100,10 @@ public class CarriageContraptionInstance extends EntityInstance { - if (instance != null) - instance.remove(); + if (instance != null) { + instance.commonRenderer.ifPresent(BogeyRenderer::remove); + instance.renderer.remove(); + } }); } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageCouplingRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageCouplingRenderer.java index 2543b6489..ef5ca2e75 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageCouplingRenderer.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageCouplingRenderer.java @@ -79,7 +79,7 @@ public class CarriageCouplingRenderer { float margin = 3 / 16f; double couplingDistance = train.carriageSpacing.get(i) - 2 * margin - - bogey1.type.getConnectorAnchorOffset().z - bogey2.type.getConnectorAnchorOffset().z; + - bogey1.type.getConnectorAnchorOffset(bogey1.isUpsideDown()).z - bogey2.type.getConnectorAnchorOffset(bogey2.isUpsideDown()).z; int couplingSegments = (int) Math.round(couplingDistance * 4); double stretch = ((anchor2.distanceTo(anchor) - 2 * margin) * 4) / couplingSegments; for (int j = 0; j < couplingSegments; j++) { diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageParticles.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageParticles.java index b8fae41d9..4639b0539 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageParticles.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageParticles.java @@ -8,7 +8,6 @@ import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; import net.minecraft.client.Minecraft; import net.minecraft.core.Direction.Axis; -import net.minecraft.core.particles.ParticleTypes; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; @@ -109,7 +108,7 @@ public class CarriageParticles { m = m.add(contraptionMotion.scale(.75f)); - level.addParticle(spark ? ParticleTypes.CRIT : ParticleTypes.POOF, v.x, v.y, v.z, m.x, m.y, m.z); + level.addParticle(spark ? bogey.getStyle().contactParticle : bogey.getStyle().smokeParticle, v.x, v.y, v.z, m.x, m.y, m.z); } } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageSounds.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageSounds.java index 1c5a0349b..89127ddc6 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageSounds.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageSounds.java @@ -3,6 +3,7 @@ package com.simibubi.create.content.logistics.trains.entity; import com.simibubi.create.AllSoundEvents; import com.simibubi.create.AllSoundEvents.SoundEntry; import com.simibubi.create.content.logistics.trains.entity.Carriage.DimensionalCarriageEntity; +import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.animation.LerpedFloat; import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; @@ -30,6 +31,9 @@ public class CarriageSounds { LoopingSound sharedWheelSoundSeated; LoopingSound sharedHonkSound; + Couple bogeySounds; + SoundEvent closestBogeySound; + boolean arrived; int tick; @@ -37,6 +41,10 @@ public class CarriageSounds { public CarriageSounds(CarriageContraptionEntity entity) { this.entity = entity; + bogeySounds = entity.getCarriage().bogeys.map(bogey -> + bogey != null && bogey.getStyle() != null ? bogey.getStyle().getSoundType() + : AllSoundEvents.TRAIN2.getMainEvent()); + closestBogeySound = bogeySounds.getFirst(); distanceFactor = LerpedFloat.linear(); speedFactor = LerpedFloat.linear(); approachFactor = LerpedFloat.linear(); @@ -80,6 +88,15 @@ public class CarriageSounds { double distance1 = toBogey1.length(); double distance2 = toBogey2.length(); + Couple bogeys = entity.getCarriage().bogeys; + CarriageBogey relevantBogey = bogeys.get(distance1 > distance2); + if (relevantBogey == null) { + relevantBogey = bogeys.getFirst(); + } + if (relevantBogey != null) { + closestBogeySound = relevantBogey.getStyle().getSoundType(); + } + Vec3 toCarriage = distance1 > distance2 ? toBogey2 : toBogey1; double distance = Math.min(distance1, distance2); Vec3 soundLocation = cam.add(toCarriage); @@ -98,7 +115,7 @@ public class CarriageSounds { seatCrossfade.tickChaser(); minecartEsqueSound = playIfMissing(mc, minecartEsqueSound, AllSoundEvents.TRAIN.getMainEvent()); - sharedWheelSound = playIfMissing(mc, sharedWheelSound, AllSoundEvents.TRAIN2.getMainEvent()); + sharedWheelSound = playIfMissing(mc, sharedWheelSound, closestBogeySound); sharedWheelSoundSeated = playIfMissing(mc, sharedWheelSoundSeated, AllSoundEvents.TRAIN3.getMainEvent()); float volume = Math.min(Math.min(speedFactor.getValue(), distanceFactor.getValue() / 100), @@ -206,7 +223,7 @@ public class CarriageSounds { public void submitSharedSoundVolume(Vec3 location, float volume) { Minecraft mc = Minecraft.getInstance(); minecartEsqueSound = playIfMissing(mc, minecartEsqueSound, AllSoundEvents.TRAIN.getMainEvent()); - sharedWheelSound = playIfMissing(mc, sharedWheelSound, AllSoundEvents.TRAIN2.getMainEvent()); + sharedWheelSound = playIfMissing(mc, sharedWheelSound, closestBogeySound); sharedWheelSoundSeated = playIfMissing(mc, sharedWheelSoundSeated, AllSoundEvents.TRAIN3.getMainEvent()); boolean approach = true; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java index 6797b063d..138f96269 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java @@ -14,6 +14,8 @@ import java.util.UUID; import javax.annotation.Nullable; +import com.simibubi.create.content.logistics.trains.TrackMaterial; + import org.apache.commons.lang3.mutable.MutableDouble; import org.apache.commons.lang3.mutable.MutableObject; @@ -555,6 +557,21 @@ public class Navigation { if (graph == null) return; + // Cache the list of track types that the train can travel on + Set validTypes = new HashSet<>(); + for (int i = 0; i < train.carriages.size(); i++) { + Carriage carriage = train.carriages.get(i); + if (i == 0) { + validTypes.addAll(carriage.leadingBogey().type.getValidPathfindingTypes(carriage.leadingBogey().getStyle())); + } else { + validTypes.retainAll(carriage.leadingBogey().type.getValidPathfindingTypes(carriage.leadingBogey().getStyle())); + } + if (carriage.isOnTwoBogeys()) + validTypes.retainAll(carriage.trailingBogey().type.getValidPathfindingTypes(carriage.trailingBogey().getStyle())); + } + if (validTypes.isEmpty()) // if there are no valid track types, a route can't be found + return; + Map penalties = new IdentityHashMap<>(); boolean costRelevant = maxCost >= 0; if (costRelevant) { @@ -674,6 +691,8 @@ public class Navigation { continue; for (Entry target : validTargets) { + if (!validTypes.contains(target.getValue().getTrackMaterial().trackType)) + continue; TrackNode newNode = target.getKey(); TrackEdge newEdge = target.getValue(); double newDistance = newEdge.getLength() + distance; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java index 24f64c52a..f9949dde4 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java @@ -42,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.schedule.ScheduleRuntime; 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.config.AllConfigs; import com.simibubi.create.foundation.networking.AllPackets; @@ -66,6 +67,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Explosion.BlockInteraction; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.fluids.FluidStack; @@ -86,6 +88,7 @@ public class Train { public boolean honk = false; public UUID id; + @Nullable public UUID owner; public TrackGraph graph; public Navigation navigation; @@ -124,7 +127,7 @@ public class Train { public int honkPitch; public float accumulatedSteamRelease; - + int tickOffset; double[] stress; @@ -277,7 +280,7 @@ public class Train { int carriageCount = carriages.size(); boolean stalled = false; double maxStress = 0; - + if (carriageWaitingForChunks != -1) distance = 0; @@ -313,11 +316,17 @@ public class Train { if (leadingAnchor == null || trailingAnchor == null) continue; - total += leadingAnchor.distanceTo(trailingAnchor); + double distanceTo = leadingAnchor.distanceToSqr(trailingAnchor); + if (carriage.leadingBogey().isUpsideDown() != previousCarriage.trailingBogey().isUpsideDown()) { + distanceTo = Math.sqrt(distanceTo - 4); + } else { + distanceTo = Math.sqrt(distanceTo); + } + total += distanceTo; entries++; } } - + if (entries > 0) actual = total / entries; @@ -369,13 +378,13 @@ public class Train { .getLeadingPoint(); double totalStress = derailed ? 0 : leadingStress + trailingStress; - + boolean first = i == 0; boolean last = i == carriageCount - 1; int carriageType = first ? last ? Carriage.BOTH : Carriage.FIRST : last ? Carriage.LAST : Carriage.MIDDLE; double actualDistance = carriage.travel(level, graph, distance + totalStress, toFollowForward, toFollowBackward, carriageType); - blocked |= carriage.blocked; + blocked |= carriage.blocked || carriage.isOnIncompatibleTrack(); boolean onTwoBogeys = carriage.isOnTwoBogeys(); maxStress = Math.max(maxStress, onTwoBogeys ? carriage.bogeySpacing - carriage.getAnchorDiff() : 0); @@ -722,9 +731,20 @@ public class Train { if (entity.getContraption()instanceof CarriageContraption cc) cc.returnStorageForDisassembly(carriage.storage); entity.setPos(Vec3 - .atLowerCornerOf(pos.relative(assemblyDirection, backwards ? offset + carriage.bogeySpacing : offset))); + .atLowerCornerOf(pos.relative(assemblyDirection, backwards ? offset + carriage.bogeySpacing : offset).below(carriage.leadingBogey().isUpsideDown() ? 2 : 0))); entity.disassemble(); + for (CarriageBogey bogey : carriage.bogeys) { + if (bogey == null) + continue; + Vec3 bogeyPosition = bogey.getAnchorPosition(); + if (bogeyPosition == null) continue; + BlockEntity be = level.getBlockEntity(new BlockPos(bogeyPosition)); + if (!(be instanceof AbstractBogeyBlockEntity sbte)) + continue; + sbte.setBogeyData(bogey.bogeyData); + } + offset += carriage.bogeySpacing; if (i < carriageSpacing.size()) @@ -944,7 +964,7 @@ public class Train { occupiedObservers.clear(); cachedObserverFiltering.clear(); - TravellingPoint signalScout = new TravellingPoint(node1, node2, edge, position); + TravellingPoint signalScout = new TravellingPoint(node1, node2, edge, position, false); Map allGroups = Create.RAILWAYS.signalEdgeGroups; MutableObject prevGroup = new MutableObject<>(null); @@ -1087,7 +1107,8 @@ public class Train { public CompoundTag write(DimensionPalette dimensions) { CompoundTag tag = new CompoundTag(); tag.putUUID("Id", id); - tag.putUUID("Owner", owner); + if (owner != null) + tag.putUUID("Owner", owner); if (graph != null) tag.putUUID("Graph", graph.id); tag.put("Carriages", NBTHelper.writeCompoundList(carriages, c -> c.write(dimensions))); @@ -1133,7 +1154,7 @@ public class Train { public static Train read(CompoundTag tag, Map trackNetworks, DimensionPalette dimensions) { 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; TrackGraph graph = graphId == null ? null : trackNetworks.get(graphId); List carriages = new ArrayList<>(); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java index a3bfaae95..f16014085 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainPacket.java @@ -6,12 +6,13 @@ import java.util.Map; import java.util.UUID; import com.simibubi.create.CreateClient; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.foundation.networking.SimplePacketBase; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.RegisteredObjects; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.Block; @@ -36,18 +37,23 @@ public class TrainPacket extends SimplePacketBase { if (!add) return; - UUID owner = buffer.readUUID(); + UUID owner = null; + if (buffer.readBoolean()) + owner = buffer.readUUID(); + List carriages = new ArrayList<>(); List carriageSpacing = new ArrayList<>(); int size = buffer.readVarInt(); for (int i = 0; i < size; i++) { Couple bogies = Couple.create(null, null); - for (boolean first : Iterate.trueAndFalse) { - if (!first && !buffer.readBoolean()) + for (boolean isFirst : Iterate.trueAndFalse) { + if (!isFirst && !buffer.readBoolean()) continue; - IBogeyBlock type = (IBogeyBlock) ForgeRegistries.BLOCKS.getValue(buffer.readResourceLocation()); - bogies.set(first, new CarriageBogey(type, new TravellingPoint(), new TravellingPoint())); + AbstractBogeyBlock type = (AbstractBogeyBlock) ForgeRegistries.BLOCKS.getValue(buffer.readResourceLocation()); + boolean upsideDown = buffer.readBoolean(); + CompoundTag data = buffer.readNbt(); + bogies.set(isFirst, new CarriageBogey(type, upsideDown, data, new TravellingPoint(), new TravellingPoint())); } int spacing = buffer.readVarInt(); carriages.add(new Carriage(bogies.getFirst(), bogies.getSecond(), spacing)); @@ -72,7 +78,9 @@ public class TrainPacket extends SimplePacketBase { if (!add) return; - buffer.writeUUID(train.owner); + buffer.writeBoolean(train.owner != null); + if (train.owner != null) + buffer.writeUUID(train.owner); buffer.writeVarInt(train.carriages.size()); for (Carriage carriage : train.carriages) { @@ -85,6 +93,8 @@ public class TrainPacket extends SimplePacketBase { } CarriageBogey bogey = carriage.bogeys.get(first); buffer.writeResourceLocation(RegisteredObjects.getKeyOrThrow((Block) bogey.type)); + buffer.writeBoolean(bogey.upsideDown); + buffer.writeNbt(bogey.bogeyData); } buffer.writeVarInt(carriage.bogeySpacing); } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainRelocator.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainRelocator.java index 2d57b938e..76a3438b6 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainRelocator.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TrainRelocator.java @@ -115,10 +115,13 @@ public class TrainRelocator { BlockPos blockPos = blockhit.getBlockPos(); BezierTrackPointLocation hoveredBezier = null; + boolean upsideDown = relocating.carriages.get(0).leadingBogey().isUpsideDown(); + Vec3 offset = upsideDown ? new Vec3(0, -0.5, 0) : Vec3.ZERO; + if (simulate && toVisualise != null && lastHoveredResult != null) { for (int i = 0; i < toVisualise.size() - 1; i++) { - Vec3 vec1 = toVisualise.get(i); - Vec3 vec2 = toVisualise.get(i + 1); + Vec3 vec1 = toVisualise.get(i).add(offset); + Vec3 vec2 = toVisualise.get(i + 1).add(offset); CreateClient.OUTLINER.showLine(Pair.of(relocating, i), vec1.add(0, -.925f, 0), vec2.add(0, -.925f, 0)) .colored(lastHoveredResult || i != toVisualise.size() - 2 ? 0x95CD41 : 0xEA5C2B) .disableLineNormals() @@ -150,7 +153,7 @@ public class TrainRelocator { boolean direction = bezierSelection != null && lookAngle.dot(bezierSelection.direction()) < 0; boolean result = relocate(relocating, mc.level, blockPos, hoveredBezier, direction, lookAngle, true); if (!simulate && result) { - relocating.carriages.forEach(c -> c.forEachPresentEntity(e -> e.nonDamageTicks = 10)); + relocating.carriages.forEach(c -> c.forEachPresentEntity(e -> e.nonDamageTicks = 10)); AllPackets.getChannel().sendToServer(new TrainRelocationPacket(relocatingTrain, blockPos, hoveredBezier, direction, lookAngle, relocatingEntityId)); } @@ -182,7 +185,7 @@ public class TrainRelocator { if (edge == null) return false; - TravellingPoint probe = new TravellingPoint(node1, node2, edge, graphLocation.position); + TravellingPoint probe = new TravellingPoint(node1, node2, edge, graphLocation.position, false); IEdgePointListener ignoreSignals = probe.ignoreEdgePoints(); ITurnListener ignoreTurns = probe.ignoreTurns(); List, Double>> recordedLocations = new ArrayList<>(); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TravellingPoint.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TravellingPoint.java index 60725fae6..3251d6b49 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/TravellingPoint.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/TravellingPoint.java @@ -38,6 +38,7 @@ public class TravellingPoint { public TrackEdge edge; public double position; public boolean blocked; + public boolean upsideDown; public static enum SteerDirection { NONE(0), LEFT(-1), RIGHT(1); @@ -64,11 +65,12 @@ public class TravellingPoint { public TravellingPoint() {} - public TravellingPoint(TrackNode node1, TrackNode node2, TrackEdge edge, double position) { + public TravellingPoint(TrackNode node1, TrackNode node2, TrackEdge edge, double position, boolean upsideDown) { this.node1 = node1; this.node2 = node2; this.edge = edge; this.position = position; + this.upsideDown = upsideDown; } public IEdgePointListener ignoreEdgePoints() { @@ -395,13 +397,18 @@ public class TravellingPoint { } public Vec3 getPosition(@Nullable TrackGraph trackGraph) { - return getPositionWithOffset(trackGraph, 0); + return getPosition(trackGraph, false); } - public Vec3 getPositionWithOffset(@Nullable TrackGraph trackGraph, double offset) { + public Vec3 getPosition(@Nullable TrackGraph trackGraph, boolean flipUpsideDown) { + return getPositionWithOffset(trackGraph, 0, flipUpsideDown); + } + + public Vec3 getPositionWithOffset(@Nullable TrackGraph trackGraph, double offset, boolean flipUpsideDown) { double t = (position + offset) / edge.getLength(); return edge.getPosition(trackGraph, t) - .add(edge.getNormal(trackGraph, t)); + .add(edge.getNormal(trackGraph, t) + .scale(upsideDown ^ flipUpsideDown ? -1 : 1)); } public void migrateTo(List locations) { @@ -422,12 +429,13 @@ public class TravellingPoint { tag.put("Nodes", nodes.map(TrackNode::getLocation) .serializeEach(loc -> loc.write(dimensions))); tag.putDouble("Position", position); + tag.putBoolean("UpsideDown", upsideDown); return tag; } public static TravellingPoint read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) { if (graph == null) - return new TravellingPoint(null, null, null, 0); + return new TravellingPoint(null, null, null, 0, false); Couple locs = tag.contains("Nodes") ? Couple.deserializeEach(tag.getList("Nodes", Tag.TAG_COMPOUND), c -> TrackNodeLocation.read(c, dimensions)) @@ -435,11 +443,11 @@ public class TravellingPoint { : Couple.create(null, null); if (locs.either(Objects::isNull)) - return new TravellingPoint(null, null, null, 0); + return new TravellingPoint(null, null, null, 0, false); double position = tag.getDouble("Position"); return new TravellingPoint(locs.getFirst(), locs.getSecond(), graph.getConnectionsFrom(locs.getFirst()) - .get(locs.getSecond()), position); + .get(locs.getSecond()), position, tag.getBoolean("UpsideDown")); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java index 29853d561..ece16fcef 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/FlapDisplaySection.java @@ -90,11 +90,11 @@ public class FlapDisplaySection { int max = Math.max(4, (int) (cyclingOptions.length * 1.75f)); if (spinningTicks > max) return 0; - + spinningTicks++; if (spinningTicks <= max && spinningTicks < 2) return spinningTicks == 1 ? 0 : spinning.length; - + int spinningFlaps = 0; for (int i = 0; i < spinning.length; i++) { int increasingChance = Mth.clamp(8 - spinningTicks, 1, 10); @@ -108,11 +108,11 @@ public class FlapDisplaySection { spinning[i + 1] &= continueSpin; if (spinningTicks > max) spinning[i] = false; - + if (spinning[i]) spinningFlaps++; } - + return spinningFlaps; } @@ -170,10 +170,14 @@ public class FlapDisplaySection { return !singleFlap; } + public Component getText() { + return component; + } + public static String[] getFlapCycle(String key) { return LOADED_FLAP_CYCLES.computeIfAbsent(key, k -> Lang.translateDirect("flap_display.cycles." + key) .getString() .split(";")); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java index 605d6c03f..24a4ed405 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/display/GlobalTrainDisplayData.java @@ -30,10 +30,11 @@ public class GlobalTrainDisplayData { } public static List prepare(String filter, int maxLines) { + String regex = filter.isBlank() ? filter : "\\Q" + filter.replace("*", "\\E.*\\Q") + "\\E"; return statusByDestination.entrySet() .stream() .filter(e -> e.getKey() - .matches(filter.replace("*", ".*"))) + .matches(regex)) .flatMap(e -> e.getValue() .stream()) .sorted() diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java index 2231b98fa..2db8e7cdc 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AbstractStationScreen.java @@ -7,6 +7,7 @@ import com.jozufozu.flywheel.core.PartialModel; import com.jozufozu.flywheel.util.transform.TransformStack; import com.mojang.blaze3d.vertex.PoseStack; 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.Train; 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.element.GuiGameElement; import com.simibubi.create.foundation.gui.widget.IconButton; +import com.simibubi.create.foundation.utility.Components; import net.minecraft.world.level.block.state.properties.BlockStateProperties; @@ -39,6 +41,10 @@ public abstract class AbstractStationScreen extends AbstractSimiScreen { @Override protected void init() { + if (blockEntity.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen(new ComputerScreen(title, () -> Components.literal(station.name), + this::renderAdditional, this, blockEntity.computerBehaviour::hasAttachedComputer)); + setWindowSize(background.width, background.height); super.init(); clearWidgets(); @@ -71,17 +77,29 @@ public abstract class AbstractStationScreen extends AbstractSimiScreen { return w; } + @Override + public void tick() { + super.tick(); + + if (blockEntity.computerBehaviour.hasAttachedComputer()) + minecraft.setScreen(new ComputerScreen(title, () -> Components.literal(station.name), + this::renderAdditional, this, blockEntity.computerBehaviour::hasAttachedComputer)); + } + @Override protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { int x = guiLeft; int y = guiTop; 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(); TransformStack msr = TransformStack.cast(ms); msr.pushPose() - .translate(x + background.width + 4, y + background.height + 4, 100) + .translate(guiLeft + background.width + 4, guiTop + background.height + 4, 100) .scale(40) .rotateX(-22) .rotateY(63); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java index f2d76cbc6..f809c156d 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java @@ -9,20 +9,26 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.UUID; +import java.util.function.Consumer; import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; import com.simibubi.create.AllSoundEvents; 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.structureMovement.AssemblyException; import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlockEntity; import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform; import com.simibubi.create.content.logistics.block.depot.DepotBehaviour; import com.simibubi.create.content.logistics.block.display.DisplayLinkBlock; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; +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.TrackEdge; import com.simibubi.create.content.logistics.trains.TrackGraph; @@ -39,6 +45,7 @@ import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePoi import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBehaviour; import com.simibubi.create.content.logistics.trains.management.schedule.Schedule; import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleItem; +import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.block.ProperWaterloggedBlock; import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; @@ -48,10 +55,12 @@ import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Lang; 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.animation.LerpedFloat; import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Direction; @@ -61,12 +70,15 @@ import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.SoundType; +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.levelgen.structure.BoundingBox; @@ -87,6 +99,7 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab protected int failedCarriageIndex; protected AssemblyException lastException; protected DepotBehaviour depotBehaviour; + public AbstractComputerBehaviour computerBehaviour; // for display UUID imminentTrain; @@ -119,6 +132,7 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab depotBehaviour.addSubBehaviours(behaviours); registerAwardables(behaviours, AllAdvancements.CONTRAPTION_ACTORS, AllAdvancements.TRAIN, AllAdvancements.LONG_TRAIN, AllAdvancements.CONDUCTOR); + behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this)); } @Override @@ -192,7 +206,8 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab Direction assemblyDirection; int assemblyLength; int[] bogeyLocations; - IBogeyBlock[] bogeyTypes; + AbstractBogeyBlock[] bogeyTypes; + boolean[] upsideDownBogeys; int bogeyCount; @Override @@ -269,14 +284,30 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab return false; BlockPos up = new BlockPos(track.getUpNormal(level, pos, state)); + BlockPos down = new BlockPos(track.getUpNormal(level, pos, state).scale(-1)); int bogeyOffset = pos.distManhattan(edgePoint.getGlobalPosition()) - 1; + if (!isValidBogeyOffset(bogeyOffset)) { - for (int i = -1; i <= 1; i++) { - BlockPos bogeyPos = pos.relative(assemblyDirection, i) - .offset(up); - BlockState blockState = level.getBlockState(bogeyPos); - if (blockState.getBlock() instanceof IBogeyBlock bogey) { - level.setBlock(bogeyPos, bogey.getRotatedBlockState(blockState, Direction.DOWN), 3); + for (boolean upsideDown : Iterate.falseAndTrue) { + for (int i = -1; i <= 1; i++) { + BlockPos bogeyPos = pos.relative(assemblyDirection, i) + .offset(upsideDown ? down : up); + BlockState blockState = level.getBlockState(bogeyPos); + if (!(blockState.getBlock() instanceof AbstractBogeyBlock bogey)) + continue; + BlockEntity be = level.getBlockEntity(bogeyPos); + if (!(be instanceof AbstractBogeyBlockEntity oldTE)) + continue; + CompoundTag oldData = oldTE.getBogeyData(); + BlockState newBlock = bogey.getNextSize(oldTE); + if (newBlock.getBlock() == bogey) + player.displayClientMessage(Lang.translateDirect("bogey.style.no_other_sizes") + .withStyle(ChatFormatting.RED), true); + level.setBlock(bogeyPos, newBlock, 3); + BlockEntity newEntity = level.getBlockEntity(bogeyPos); + if (!(newEntity instanceof AbstractBogeyBlockEntity newTE)) + continue; + newTE.setBogeyData(oldData); bogey.playRotateSound(level, bogeyPos); return true; } @@ -291,7 +322,9 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab return false; } - BlockPos targetPos = pos.offset(up); + boolean upsideDown = (player.getViewXRot(1.0F) < 0 && (track.getBogeyAnchor(level, pos, state)).getBlock() instanceof AbstractBogeyBlock bogey && bogey.canBeUpsideDown()); + + BlockPos targetPos = upsideDown ? pos.offset(down) : pos.offset(up); if (level.getBlockState(targetPos) .getDestroySpeed(level, targetPos) == -1) { return false; @@ -299,7 +332,11 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab level.destroyBlock(targetPos, true); - BlockState bogeyAnchor = ProperWaterloggedBlock.withWater(level, track.getBogeyAnchor(level, pos, state), pos); + BlockState bogeyAnchor = track.getBogeyAnchor(level, pos, state); + if (bogeyAnchor.getBlock() instanceof AbstractBogeyBlock bogey) { + bogeyAnchor = bogey.getVersion(bogeyAnchor, upsideDown); + } + bogeyAnchor = ProperWaterloggedBlock.withWater(level, bogeyAnchor, pos); level.setBlock(targetPos, bogeyAnchor, 3); player.displayClientMessage(Lang.translateDirect("train_assembly.bogey_created"), true); SoundType soundtype = bogeyAnchor.getBlock() @@ -317,6 +354,63 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab 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() { BlockState state = getBlockState(); return state.hasProperty(StationBlock.ASSEMBLING) && state.getValue(StationBlock.ASSEMBLING); @@ -344,6 +438,42 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab 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 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() { if (!edgePoint.hasValidTrack()) return; @@ -373,9 +503,12 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab if (bogeyLocations == null) bogeyLocations = new int[maxBogeyCount]; if (bogeyTypes == null) - bogeyTypes = new IBogeyBlock[maxBogeyCount]; + bogeyTypes = new AbstractBogeyBlock[maxBogeyCount]; + if (upsideDownBogeys == null) + upsideDownBogeys = new boolean[maxBogeyCount]; Arrays.fill(bogeyLocations, -1); Arrays.fill(bogeyTypes, null); + Arrays.fill(upsideDownBogeys, false); for (int i = 0; i < MAX_LENGTH; i++) { if (i == MAX_LENGTH - 1) { @@ -388,10 +521,19 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab } BlockState potentialBogeyState = level.getBlockState(bogeyOffset.offset(currentPos)); - if (potentialBogeyState.getBlock() instanceof IBogeyBlock bogey && bogeyIndex < bogeyLocations.length) { - bogeyTypes[bogeyIndex] = bogey; - bogeyLocations[bogeyIndex] = i; - bogeyIndex++; + BlockPos upsideDownBogeyOffset = new BlockPos(bogeyOffset.getX(), bogeyOffset.getY()*-1, bogeyOffset.getZ()); + if (bogeyIndex < bogeyLocations.length) { + if (potentialBogeyState.getBlock() instanceof AbstractBogeyBlock bogey && !bogey.isUpsideDown(potentialBogeyState)) { + bogeyTypes[bogeyIndex] = bogey; + bogeyLocations[bogeyIndex] = i; + upsideDownBogeys[bogeyIndex] = false; + bogeyIndex++; + } else if ((potentialBogeyState = level.getBlockState(upsideDownBogeyOffset.offset(currentPos))).getBlock() instanceof AbstractBogeyBlock bogey && bogey.isUpsideDown(potentialBogeyState)) { + bogeyTypes[bogeyIndex] = bogey; + bogeyLocations[bogeyIndex] = i; + upsideDownBogeys[bogeyIndex] = true; + bogeyIndex++; + } } currentPos.move(assemblyDirection); @@ -412,6 +554,14 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab 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) { if ((i < 3 || bogeyCount == 0) && i != 0) return false; @@ -551,7 +701,7 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab return; } - points.add(new TravellingPoint(node, secondNode, edge, positionOnEdge)); + points.add(new TravellingPoint(node, secondNode, edge, positionOnEdge, false)); } secondNode = node; @@ -578,10 +728,11 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab spacing.add(bogeyLocations[bogeyIndex] - bogeyLocations[bogeyIndex - 1]); CarriageContraption contraption = new CarriageContraption(assemblyDirection); BlockPos bogeyPosOffset = trackPosition.offset(bogeyOffset); + BlockPos upsideDownBogeyPosOffset = trackPosition.offset(new BlockPos(bogeyOffset.getX(), bogeyOffset.getY() * -1, bogeyOffset.getZ())); try { int offset = bogeyLocations[bogeyIndex] + 1; - boolean success = contraption.assemble(level, bogeyPosOffset.relative(assemblyDirection, offset)); + boolean success = contraption.assemble(level, upsideDownBogeys[bogeyIndex] ? upsideDownBogeyPosOffset.relative(assemblyDirection, offset) : bogeyPosOffset.relative(assemblyDirection, offset)); atLeastOneForwardControls |= contraption.hasForwardControls(); contraption.setSoundQueueOffset(offset); if (!success) { @@ -594,24 +745,28 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab return; } - IBogeyBlock typeOfFirstBogey = bogeyTypes[bogeyIndex]; + AbstractBogeyBlock typeOfFirstBogey = bogeyTypes[bogeyIndex]; + boolean firstBogeyIsUpsideDown = upsideDownBogeys[bogeyIndex]; + BlockPos firstBogeyPos = contraption.anchor; + AbstractBogeyBlockEntity firstBogeyTileEntity = (AbstractBogeyBlockEntity) level.getBlockEntity(firstBogeyPos); CarriageBogey firstBogey = - new CarriageBogey(typeOfFirstBogey, points.get(pointIndex), points.get(pointIndex + 1)); + new CarriageBogey(typeOfFirstBogey, firstBogeyIsUpsideDown, firstBogeyTileEntity.getBogeyData(), points.get(pointIndex), points.get(pointIndex + 1)); CarriageBogey secondBogey = null; BlockPos secondBogeyPos = contraption.getSecondBogeyPos(); int bogeySpacing = 0; if (secondBogeyPos != null) { if (bogeyIndex == bogeyCount - 1 || !secondBogeyPos - .equals(bogeyPosOffset.relative(assemblyDirection, bogeyLocations[bogeyIndex + 1] + 1))) { + .equals((upsideDownBogeys[bogeyIndex + 1] ? upsideDownBogeyPosOffset : bogeyPosOffset).relative(assemblyDirection, bogeyLocations[bogeyIndex + 1] + 1))) { exception(new AssemblyException(Lang.translateDirect("train_assembly.not_connected_in_order")), contraptions.size() + 1); return; } - + AbstractBogeyBlockEntity secondBogeyTileEntity = + (AbstractBogeyBlockEntity) level.getBlockEntity(secondBogeyPos); bogeySpacing = bogeyLocations[bogeyIndex + 1] - bogeyLocations[bogeyIndex]; - secondBogey = new CarriageBogey(bogeyTypes[bogeyIndex + 1], points.get(pointIndex + 2), - points.get(pointIndex + 3)); + secondBogey = new CarriageBogey(bogeyTypes[bogeyIndex + 1], upsideDownBogeys[bogeyIndex + 1], secondBogeyTileEntity.getBogeyData(), + points.get(pointIndex + 2), points.get(pointIndex + 3)); bogeyIndex++; } else if (!typeOfFirstBogey.allowsSingleBogeyCarriage()) { @@ -701,12 +856,20 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab } @Override - public LazyOptional getCapability(Capability cap, Direction side) { + public @NotNull LazyOptional getCapability(@NotNull Capability cap, Direction side) { if (isItemHandlerCap(cap)) return depotBehaviour.getItemCapability(cap, side); + if (computerBehaviour.isPeripheralCap(cap)) + return computerBehaviour.getPeripheralCapability(); return super.getCapability(cap, side); } + @Override + public void invalidateCaps() { + super.invalidateCaps(); + computerBehaviour.removePeripheral(); + } + private void applyAutoSchedule() { ItemStack stack = getAutoSchedule(); if (!AllItems.SCHEDULE.isIn(stack)) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java index 5f545f510..7b3bee2f0 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java @@ -1,21 +1,14 @@ 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.logistics.trains.GraphLocation; -import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.foundation.networking.BlockEntityConfigurationPacket; -import com.simibubi.create.foundation.utility.VecHelper; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; 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.block.state.BlockState; -import net.minecraft.world.phys.Vec3; public class StationEditPacket extends BlockEntityConfigurationPacket { @@ -101,22 +94,15 @@ public class StationEditPacket extends BlockEntityConfigurationPacket T enumData(String key, Class enumClass) { T[] enumConstants = enumClass.getEnumConstants(); return enumConstants[data.getInt(key) % enumConstants.length]; } - + protected String textData(String key) { return data.getString(key); } - + protected int intData(String key) { return data.getInt(key); } - + } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java index 0c3f25f55..f9ab12a18 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/FluidThresholdCondition.java @@ -68,7 +68,8 @@ public class FluidThresholdCondition extends CargoThresholdCondition { @Override protected void readAdditional(CompoundTag tag) { super.readAdditional(tag); - compareStack = ItemStack.of(tag.getCompound("Bucket")); + if (tag.contains("Bucket")) + compareStack = ItemStack.of(tag.getCompound("Bucket")); } @Override @@ -139,4 +140,4 @@ public class FluidThresholdCondition extends CargoThresholdCondition { Math.max(0, getThreshold() + offset), Lang.translateDirect("schedule.condition.threshold.buckets")); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java index 78905d201..81122ad73 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ItemThresholdCondition.java @@ -69,7 +69,8 @@ public class ItemThresholdCondition extends CargoThresholdCondition { @Override protected void readAdditional(CompoundTag tag) { super.readAdditional(tag); - stack = ItemStack.of(tag.getCompound("Item")); + if (tag.contains("Item")) + stack = ItemStack.of(tag.getCompound("Item")); } @Override @@ -131,4 +132,4 @@ public class ItemThresholdCondition extends CargoThresholdCondition { Math.max(0, getThreshold() + offset), Lang.translateDirect("schedule.condition.threshold." + (inStacks() ? "stacks" : "items"))); } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java index 9bb8f4190..896c9a23f 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/RedstoneLinkCondition.java @@ -107,7 +107,8 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition { @Override 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 @@ -118,7 +119,7 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition { .titled(Lang.translateDirect("schedule.condition.redstone_link.frequency_state")), "Inverted"); } - + @Override public MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag) { return Lang.translateDirect("schedule.condition.redstone_link.status"); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java index a8e307d6a..7da150cce 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/condition/ScheduleWaitCondition.java @@ -16,16 +16,17 @@ import net.minecraft.world.level.Level; public abstract class ScheduleWaitCondition extends ScheduleDataEntry { public abstract boolean tickCompletion(Level level, Train train, CompoundTag context); - + protected void requestStatusToUpdate(CompoundTag context) { context.putInt("StatusVersion", context.getInt("StatusVersion") + 1); } - + public final CompoundTag write() { CompoundTag tag = new CompoundTag(); + CompoundTag dataCopy = data.copy(); + writeAdditional(dataCopy); tag.putString("Id", getId().toString()); - tag.put("Data", data.copy()); - writeAdditional(tag); + tag.put("Data", dataCopy); return tag; } @@ -43,11 +44,14 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry { } 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); + CompoundTag data = tag.getCompound("Data"); + condition.readAdditional(data); + condition.data = data; return condition; } public abstract MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag); -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java index cc3554094..6e1e56354 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/schedule/destination/ScheduleInstruction.java @@ -16,9 +16,10 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry { public final CompoundTag write() { CompoundTag tag = new CompoundTag(); + CompoundTag dataCopy = data.copy(); + writeAdditional(dataCopy); tag.putString("Id", getId().toString()); - tag.put("Data", data.copy()); - writeAdditional(tag); + tag.put("Data", dataCopy); return tag; } @@ -36,9 +37,12 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry { } 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); + CompoundTag data = tag.getCompound("Data"); + scheduleDestination.readAdditional(data); + scheduleDestination.data = data; return scheduleDestination; } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/AbstractBogeyBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/AbstractBogeyBlockEntity.java new file mode 100644 index 000000000..548e99151 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/AbstractBogeyBlockEntity.java @@ -0,0 +1,120 @@ +package com.simibubi.create.content.logistics.trains.track; + +import static com.simibubi.create.content.logistics.trains.entity.CarriageBogey.UPSIDE_DOWN_KEY; + +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.AllBogeyStyles; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; +import com.simibubi.create.content.logistics.trains.entity.BogeyStyle; +import com.simibubi.create.foundation.blockEntity.CachedRenderBBBlockEntity; +import com.simibubi.create.foundation.utility.NBTHelper; +import com.simibubi.create.foundation.utility.animation.LerpedFloat; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; + +public abstract class AbstractBogeyBlockEntity extends CachedRenderBBBlockEntity { + public static final String BOGEY_STYLE_KEY = "BogeyStyle"; + public static final String BOGEY_DATA_KEY = "BogeyData"; + + private CompoundTag bogeyData; + + public AbstractBogeyBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + public abstract BogeyStyle getDefaultStyle(); + + public CompoundTag getBogeyData() { + if (this.bogeyData == null || !this.bogeyData.contains(BOGEY_STYLE_KEY)) + this.bogeyData = this.createBogeyData(); + return this.bogeyData; + } + + public void setBogeyData(@NotNull CompoundTag newData) { + if (!newData.contains(BOGEY_STYLE_KEY)) { + ResourceLocation style = getDefaultStyle().name; + NBTHelper.writeResourceLocation(newData, BOGEY_STYLE_KEY, style); + } + this.bogeyData = newData; + } + + public void setBogeyStyle(@NotNull BogeyStyle style) { + ResourceLocation location = style.name; + CompoundTag data = this.getBogeyData(); + NBTHelper.writeResourceLocation(data, BOGEY_STYLE_KEY, location); + markUpdated(); + } + + @NotNull + public BogeyStyle getStyle() { + CompoundTag data = this.getBogeyData(); + ResourceLocation currentStyle = NBTHelper.readResourceLocation(data, BOGEY_STYLE_KEY); + BogeyStyle style = AllBogeyStyles.BOGEY_STYLES.get(currentStyle); + if (style == null) { + setBogeyStyle(getDefaultStyle()); + return getStyle(); + } + return style; + } + + @Override + protected void saveAdditional(@NotNull CompoundTag pTag) { + CompoundTag data = this.getBogeyData(); + if (data != null) pTag.put(BOGEY_DATA_KEY, data); // Now contains style + super.saveAdditional(pTag); + } + + @Override + public void load(CompoundTag pTag) { + if (pTag.contains(BOGEY_DATA_KEY)) + this.bogeyData = pTag.getCompound(BOGEY_DATA_KEY); + else + this.bogeyData = this.createBogeyData(); + super.load(pTag); + } + + private CompoundTag createBogeyData() { + CompoundTag nbt = new CompoundTag(); + NBTHelper.writeResourceLocation(nbt, BOGEY_STYLE_KEY, getDefaultStyle().name); + boolean upsideDown = false; + if (getBlockState().getBlock() instanceof AbstractBogeyBlock bogeyBlock) + upsideDown = bogeyBlock.isUpsideDown(getBlockState()); + nbt.putBoolean(UPSIDE_DOWN_KEY, upsideDown); + return nbt; + } + + @Override + protected AABB createRenderBoundingBox() { + return super.createRenderBoundingBox().inflate(2); + } + + // Ponder + LerpedFloat virtualAnimation = LerpedFloat.angular(); + + public float getVirtualAngle(float partialTicks) { + return virtualAnimation.getValue(partialTicks); + } + + public void animate(float distanceMoved) { + BlockState blockState = getBlockState(); + if (!(blockState.getBlock() instanceof AbstractBogeyBlock type)) + return; + double angleDiff = 360 * distanceMoved / (Math.PI * 2 * type.getWheelRadius()); + double newWheelAngle = (virtualAnimation.getValue() - angleDiff) % 360; + virtualAnimation.setValue(newWheelAngle); + } + + private void markUpdated() { + setChanged(); + Level level = getLevel(); + if (level != null) + getLevel().sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3); + } +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/CurvedTrackInteraction.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/CurvedTrackInteraction.java index 8c402a753..b29acbd29 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/CurvedTrackInteraction.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/CurvedTrackInteraction.java @@ -1,7 +1,7 @@ package com.simibubi.create.content.logistics.trains.track; -import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; +import com.simibubi.create.AllTags; import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBlockItem; import com.simibubi.create.content.logistics.trains.track.TrackBlockOutline.BezierPointSelection; import com.simibubi.create.foundation.networking.AllPackets; @@ -117,7 +117,7 @@ public class CurvedTrackInteraction { if (event.isUseItem()) { ItemStack heldItem = player.getMainHandItem(); Item item = heldItem.getItem(); - if (AllBlocks.TRACK.isIn(heldItem)) { + if (AllTags.AllBlockTags.TRACKS.matches(heldItem)) { player.displayClientMessage(Lang.translateDirect("track.turn_start") .withStyle(ChatFormatting.RED), true); player.swing(InteractionHand.MAIN_HAND); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/PlaceExtendedCurvePacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/PlaceExtendedCurvePacket.java index e332c5d8d..a833dd67a 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/PlaceExtendedCurvePacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/PlaceExtendedCurvePacket.java @@ -1,6 +1,6 @@ package com.simibubi.create.content.logistics.trains.track; -import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllTags; import com.simibubi.create.foundation.networking.SimplePacketBase; import net.minecraft.nbt.CompoundTag; @@ -14,7 +14,7 @@ public class PlaceExtendedCurvePacket extends SimplePacketBase { boolean mainHand; boolean ctrlDown; - + public PlaceExtendedCurvePacket(boolean mainHand, boolean ctrlDown) { this.mainHand = mainHand; this.ctrlDown = ctrlDown; @@ -36,7 +36,7 @@ public class PlaceExtendedCurvePacket extends SimplePacketBase { context.enqueueWork(() -> { ServerPlayer sender = context.getSender(); ItemStack stack = sender.getItemInHand(mainHand ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND); - if (!AllBlocks.TRACK.isIn(stack) || !stack.hasTag()) + if (!AllTags.AllBlockTags.TRACKS.matches(stack) || !stack.hasTag()) return; CompoundTag tag = stack.getTag(); tag.putBoolean("ExtendCurve", true); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlock.java index 3507a4d84..ded15125d 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlock.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlock.java @@ -1,87 +1,36 @@ package com.simibubi.create.content.logistics.trains.track; -import java.util.EnumSet; - -import com.jozufozu.flywheel.api.MaterialManager; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Vector3f; import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.AllBlocks; -import com.simibubi.create.AllPartialModels; -import com.simibubi.create.content.contraptions.relays.elementary.ShaftBlock; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; -import com.simibubi.create.content.logistics.trains.entity.BogeyInstance; -import com.simibubi.create.content.logistics.trains.entity.CarriageBogey; +import com.simibubi.create.AllBogeyStyles; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; +import com.simibubi.create.content.logistics.trains.BogeySizes; +import com.simibubi.create.content.logistics.trains.TrackMaterial; +import com.simibubi.create.content.logistics.trains.entity.BogeyStyle; import com.simibubi.create.content.schematics.ISpecialBlockItemRequirement; -import com.simibubi.create.content.schematics.ItemRequirement; -import com.simibubi.create.content.schematics.ItemRequirement.ItemUseType; import com.simibubi.create.foundation.block.IBE; import com.simibubi.create.foundation.block.ProperWaterloggedBlock; -import com.simibubi.create.foundation.render.CachedBufferer; -import com.simibubi.create.foundation.utility.AngleHelper; -import com.simibubi.create.foundation.utility.Iterate; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.Rotation; -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.StateDefinition.Builder; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -public class StandardBogeyBlock extends Block - implements IBogeyBlock, IBE, ProperWaterloggedBlock, ISpecialBlockItemRequirement { +public class StandardBogeyBlock extends AbstractBogeyBlock + implements IBE, ProperWaterloggedBlock, ISpecialBlockItemRequirement { - public static final EnumProperty AXIS = BlockStateProperties.HORIZONTAL_AXIS; - private final boolean large; - - public StandardBogeyBlock(Properties p_i48440_1_, boolean large) { - super(p_i48440_1_); - this.large = large; + public StandardBogeyBlock(Properties props, BogeySizes.BogeySize size) { + super(props, size); registerDefaultState(defaultBlockState().setValue(WATERLOGGED, false)); } @Override - protected void createBlockStateDefinition(Builder builder) { - builder.add(AXIS, WATERLOGGED); - super.createBlockStateDefinition(builder); - } - - static final EnumSet STICKY_X = EnumSet.of(Direction.EAST, Direction.WEST); - static final EnumSet STICKY_Z = EnumSet.of(Direction.SOUTH, Direction.NORTH); - - @Override - public EnumSet getStickySurfaces(BlockGetter world, BlockPos pos, BlockState state) { - return state.getValue(BlockStateProperties.HORIZONTAL_AXIS) == Axis.X ? STICKY_X : STICKY_Z; - } - - @Override - public BlockState updateShape(BlockState pState, Direction pDirection, BlockState pNeighborState, - LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pNeighborPos) { - updateWater(pLevel, pState, pCurrentPos); - return pState; - } - - @Override - public FluidState getFluidState(BlockState pState) { - return fluidState(pState); + public TrackMaterial.TrackType getTrackType(BogeyStyle style) { + return TrackMaterial.TrackType.STANDARD; } @Override @@ -91,7 +40,7 @@ public class StandardBogeyBlock extends Block @Override public double getWheelRadius() { - return (large ? 12.5 : 6.5) / 16d; + return (size == BogeySizes.LARGE ? 12.5 : 6.5) / 16d; } @Override @@ -100,122 +49,8 @@ public class StandardBogeyBlock extends Block } @Override - public boolean allowsSingleBogeyCarriage() { - return true; - } - - @Override - public BlockState getMatchingBogey(Direction upDirection, boolean axisAlongFirst) { - if (upDirection != Direction.UP) - return null; - return defaultBlockState().setValue(AXIS, axisAlongFirst ? Axis.X : Axis.Z); - } - - @Override - public boolean isTrackAxisAlongFirstCoordinate(BlockState state) { - return state.getValue(AXIS) == Axis.X; - } - - @Override - @OnlyIn(Dist.CLIENT) - public void render(BlockState state, float wheelAngle, PoseStack ms, float partialTicks, MultiBufferSource buffers, - int light, int overlay) { - if (state != null) { - ms.translate(.5f, .5f, .5f); - if (state.getValue(AXIS) == Axis.X) - ms.mulPose(Vector3f.YP.rotationDegrees(90)); - } - - ms.translate(0, -1.5 - 1 / 128f, 0); - - VertexConsumer vb = buffers.getBuffer(RenderType.cutoutMipped()); - BlockState air = Blocks.AIR.defaultBlockState(); - - for (int i : Iterate.zeroAndOne) - CachedBufferer.block(AllBlocks.SHAFT.getDefaultState() - .setValue(ShaftBlock.AXIS, Axis.Z)) - .translate(-.5f, .25f, i * -1) - .centre() - .rotateZ(wheelAngle) - .unCentre() - .light(light) - .renderInto(ms, vb); - - if (large) { - renderLargeBogey(wheelAngle, ms, light, vb, air); - } else { - renderBogey(wheelAngle, ms, light, vb, air); - } - } - - private void renderBogey(float wheelAngle, PoseStack ms, int light, VertexConsumer vb, BlockState air) { - CachedBufferer.partial(AllPartialModels.BOGEY_FRAME, air) - .scale(1 - 1 / 512f) - .light(light) - .renderInto(ms, vb); - - for (int side : Iterate.positiveAndNegative) { - ms.pushPose(); - CachedBufferer.partial(AllPartialModels.SMALL_BOGEY_WHEELS, air) - .translate(0, 12 / 16f, side) - .rotateX(wheelAngle) - .light(light) - .renderInto(ms, vb); - ms.popPose(); - } - } - - private void renderLargeBogey(float wheelAngle, PoseStack ms, int light, VertexConsumer vb, BlockState air) { - for (int i : Iterate.zeroAndOne) - CachedBufferer.block(AllBlocks.SHAFT.getDefaultState() - .setValue(ShaftBlock.AXIS, Axis.X)) - .translate(-.5f, .25f, .5f + i * -2) - .centre() - .rotateX(wheelAngle) - .unCentre() - .light(light) - .renderInto(ms, vb); - - CachedBufferer.partial(AllPartialModels.BOGEY_DRIVE, air) - .scale(1 - 1 / 512f) - .light(light) - .renderInto(ms, vb); - CachedBufferer.partial(AllPartialModels.BOGEY_PISTON, air) - .translate(0, 0, 1 / 4f * Math.sin(AngleHelper.rad(wheelAngle))) - .light(light) - .renderInto(ms, vb); - - ms.pushPose(); - CachedBufferer.partial(AllPartialModels.LARGE_BOGEY_WHEELS, air) - .translate(0, 1, 0) - .rotateX(wheelAngle) - .light(light) - .renderInto(ms, vb); - CachedBufferer.partial(AllPartialModels.BOGEY_PIN, air) - .translate(0, 1, 0) - .rotateX(wheelAngle) - .translate(0, 1 / 4f, 0) - .rotateX(-wheelAngle) - .light(light) - .renderInto(ms, vb); - ms.popPose(); - } - - @Override - public BogeyInstance createInstance(MaterialManager materialManager, CarriageBogey bogey) { - if (large) { - return new BogeyInstance.Drive(bogey, materialManager); - } else { - return new BogeyInstance.Frame(bogey, materialManager); - } - } - - @Override - public BlockState rotate(BlockState pState, Rotation pRotation) { - return switch (pRotation) { - case COUNTERCLOCKWISE_90, CLOCKWISE_90 -> pState.cycle(AXIS); - default -> pState; - }; + public BogeyStyle getDefaultStyle() { + return AllBogeyStyles.STANDARD; } @Override @@ -234,9 +69,4 @@ public class StandardBogeyBlock extends Block return AllBlockEntityTypes.BOGEY.get(); } - @Override - public ItemRequirement getRequiredItems(BlockState state, BlockEntity be) { - return new ItemRequirement(ItemUseType.CONSUME, AllBlocks.RAILWAY_CASING.asStack()); - } - } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlockEntity.java index 255c98862..5efe440f4 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/StandardBogeyBlockEntity.java @@ -1,40 +1,20 @@ package com.simibubi.create.content.logistics.trains.track; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; -import com.simibubi.create.foundation.blockEntity.CachedRenderBBBlockEntity; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; +import com.simibubi.create.AllBogeyStyles; +import com.simibubi.create.content.logistics.trains.entity.BogeyStyle; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; -public class StandardBogeyBlockEntity extends CachedRenderBBBlockEntity { +public class StandardBogeyBlockEntity extends AbstractBogeyBlockEntity { public StandardBogeyBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); } @Override - protected AABB createRenderBoundingBox() { - return super.createRenderBoundingBox().inflate(2); + public BogeyStyle getDefaultStyle() { + return AllBogeyStyles.STANDARD; } - - // Ponder - - LerpedFloat virtualAnimation = LerpedFloat.angular(); - - public float getVirtualAngle(float partialTicks) { - return virtualAnimation.getValue(partialTicks); - } - - public void animate(float distanceMoved) { - BlockState blockState = getBlockState(); - if (!(blockState.getBlock() instanceof IBogeyBlock type)) - return; - double angleDiff = 360 * distanceMoved / (Math.PI * 2 * type.getWheelRadius()); - double newWheelAngle = (virtualAnimation.getValue() - angleDiff) % 360; - virtualAnimation.setValue(newWheelAngle); - } - } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java index 59417c709..a4ab87f75 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java @@ -29,12 +29,14 @@ import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllPartialModels; import com.simibubi.create.AllShapes; +import com.simibubi.create.AllTags; import com.simibubi.create.content.contraptions.components.structureMovement.glue.SuperGlueEntity; import com.simibubi.create.content.contraptions.particle.CubeParticleData; import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.content.curiosities.girder.GirderBlock; import com.simibubi.create.content.logistics.trains.BezierConnection; import com.simibubi.create.content.logistics.trains.ITrackBlock; +import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.content.logistics.trains.TrackNodeLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.TrackPropagator; @@ -55,6 +57,8 @@ import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.VecHelper; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.minecraft.ChatFormatting; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; @@ -114,11 +118,14 @@ public class TrackBlock extends Block public static final EnumProperty SHAPE = EnumProperty.create("shape", TrackShape.class); public static final BooleanProperty HAS_BE = BooleanProperty.create("turn"); - public TrackBlock(Properties p_49795_) { + protected final TrackMaterial material; + + public TrackBlock(Properties p_49795_, TrackMaterial material) { super(p_49795_); registerDefaultState(defaultBlockState().setValue(SHAPE, TrackShape.ZO) .setValue(HAS_BE, false) .setValue(WATERLOGGED, false)); + this.material = material; } @Override @@ -390,7 +397,7 @@ public class TrackBlock extends Block (d, b) -> axis.scale(b ? 0 : fromCenter ? -d : d) .add(center), b -> shape.getNormal(), b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, v -> 0, - axis, null); + axis, null, (b, v) -> ITrackBlock.getMaterialSimple(world, v)); } else list = ITrackBlock.super.getConnected(world, pos, state, linear, connectedTo); @@ -406,7 +413,8 @@ public class TrackBlock extends Block Map connections = trackTE.getConnections(); connections.forEach((connectedPos, bc) -> ITrackBlock.addToListIfConnected(connectedTo, list, (d, b) -> d == 1 ? Vec3.atLowerCornerOf(bc.tePositions.get(b)) : bc.starts.get(b), bc.normals::get, - b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, bc::yOffsetAt, null, bc)); + b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, bc::yOffsetAt, null, bc, + (b, v) -> ITrackBlock.getMaterialSimple(world, v, bc.getMaterial()))); if (trackTE.boundLocation == null || !(world instanceof ServerLevel level)) return list; @@ -418,7 +426,7 @@ public class TrackBlock extends Block return list; BlockPos boundPos = trackTE.boundLocation.getSecond(); BlockState boundState = otherLevel.getBlockState(boundPos); - if (!AllBlocks.TRACK.has(boundState)) + if (!AllTags.AllBlockTags.TRACKS.matches(boundState)) return list; Vec3 center = Vec3.atBottomCenterOf(pos) @@ -432,7 +440,8 @@ public class TrackBlock extends Block getTrackAxes(world, pos, state).forEach(axis -> { ITrackBlock.addToListIfConnected(connectedTo, list, (d, b) -> (b ? axis : boundAxis).scale(d) .add(b ? center : boundCenter), b -> (b ? shape : boundShape).getNormal(), - b -> b ? level.dimension() : otherLevel.dimension(), v -> 0, axis, null); + b -> b ? level.dimension() : otherLevel.dimension(), v -> 0, axis, null, + (b, v) -> ITrackBlock.getMaterialSimple(b ? level : otherLevel, v)); }); return list; @@ -481,7 +490,7 @@ public class TrackBlock extends Block if (!entry.getValue() .isInside(pos)) continue; - if (world.getBlockEntity(entry.getKey())instanceof StationBlockEntity station) + if (world.getBlockEntity(entry.getKey()) instanceof StationBlockEntity station) if (station.trackClicked(player, hand, this, state, pos)) return InteractionResult.SUCCESS; } @@ -497,7 +506,7 @@ public class TrackBlock extends Block BlockPos girderPos = pPos.below() .offset(vec3.z * side, 0, vec3.x * side); BlockState girderState = pLevel.getBlockState(girderPos); - if (girderState.getBlock()instanceof GirderBlock girderBlock + if (girderState.getBlock() instanceof GirderBlock girderBlock && !blockTicks.hasScheduledTick(girderPos, girderBlock)) pLevel.scheduleTick(girderPos, girderBlock, 1); } @@ -702,7 +711,7 @@ public class TrackBlock extends Block Vec3 normal = null; Vec3 offset = null; - if (bezierPoint != null && world.getBlockEntity(pos)instanceof TrackBlockEntity trackTE) { + if (bezierPoint != null && world.getBlockEntity(pos) instanceof TrackBlockEntity trackTE) { BezierConnection bc = trackTE.connections.get(bezierPoint.curveTarget()); if (bc != null) { double length = Mth.floor(bc.getLength() * 2); @@ -747,7 +756,8 @@ public class TrackBlock extends Block msr.rotateCentered(Direction.UP, Mth.PI); } - if (bezierPoint == null && world.getBlockEntity(pos)instanceof TrackBlockEntity trackTE && trackTE.isTilted()) { + if (bezierPoint == null && world.getBlockEntity(pos) instanceof TrackBlockEntity trackTE + && trackTE.isTilted()) { double yOffset = 0; for (BezierConnection bc : trackTE.connections.values()) yOffset += bc.starts.getFirst().y - pos.getY(); @@ -773,7 +783,8 @@ public class TrackBlock extends Block @Override public ItemRequirement getRequiredItems(BlockState state, BlockEntity be) { - int trackAmount = 1; + int sameTypeTrackAmount = 1; + Object2IntMap otherTrackAmounts = new Object2IntArrayMap<>(); int girderAmount = 0; if (be instanceof TrackBlockEntity track) { @@ -781,15 +792,27 @@ public class TrackBlock extends Block .values()) { if (!bezierConnection.isPrimary()) continue; - trackAmount += bezierConnection.getTrackItemCost(); + TrackMaterial material = bezierConnection.getMaterial(); + if (material == getMaterial()) { + sameTypeTrackAmount += bezierConnection.getTrackItemCost(); + } else { + otherTrackAmounts.put(material, otherTrackAmounts.getOrDefault(material, 0) + 1); + } girderAmount += bezierConnection.getGirderItemCost(); } } List stacks = new ArrayList<>(); - while (trackAmount > 0) { - stacks.add(AllBlocks.TRACK.asStack(Math.min(trackAmount, 64))); - trackAmount -= 64; + while (sameTypeTrackAmount > 0) { + stacks.add(new ItemStack(state.getBlock(), Math.min(sameTypeTrackAmount, 64))); + sameTypeTrackAmount -= 64; + } + for (TrackMaterial material : otherTrackAmounts.keySet()) { + int amt = otherTrackAmounts.getOrDefault(material, 0); + while (amt > 0) { + stacks.add(material.asStack(Math.min(amt, 64))); + amt -= 64; + } } while (girderAmount > 0) { stacks.add(AllBlocks.METAL_GIRDER.asStack(Math.min(girderAmount, 64))); @@ -799,6 +822,11 @@ public class TrackBlock extends Block return new ItemRequirement(ItemUseType.CONSUME, stacks); } + @Override + public TrackMaterial getMaterial() { + return material; + } + public static class RenderProperties extends ReducedDestroyEffects implements MultiPosDestructionHandler { @Override @Nullable diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java index 25c3ff996..7a62a94ac 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java @@ -10,6 +10,7 @@ import java.util.Set; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllTags; import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlockEntity; import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform; import com.simibubi.create.content.logistics.trains.BezierConnection; @@ -124,6 +125,9 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable } public void addConnection(BezierConnection connection) { + // don't replace existing connections with different materials + if (connections.containsKey(connection.getKey()) && connection.equalsSansMaterial(connections.get(connection.getKey()))) + return; connections.put(connection.getKey(), connection); level.scheduleTick(worldPosition, getBlockState().getBlock(), 1); notifyUpdate(); @@ -325,7 +329,7 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable .getLevel(boundLocation.getFirst()); if (otherLevel == null) return; - if (AllBlocks.TRACK.has(otherLevel.getBlockState(boundLocation.getSecond()))) + if (AllTags.AllBlockTags.TRACKS.matches(otherLevel.getBlockState(boundLocation.getSecond()))) otherLevel.destroyBlock(boundLocation.getSecond(), false); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java index 4dacd7502..36af97662 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java @@ -2,6 +2,7 @@ package com.simibubi.create.content.logistics.trains.track; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllSoundEvents; +import com.simibubi.create.AllTags; import com.simibubi.create.content.logistics.trains.ITrackBlock; import com.simibubi.create.content.logistics.trains.track.TrackPlacement.PlacementInfo; import com.simibubi.create.foundation.networking.AllPackets; @@ -118,7 +119,7 @@ public class TrackBlockItem extends BlockItem { return InteractionResult.SUCCESS; stack = player.getMainHandItem(); - if (AllBlocks.TRACK.isIn(stack)) { + if (AllTags.AllBlockTags.TRACKS.matches(stack)) { stack.setTag(null); player.setItemInHand(pContext.getHand(), stack); } @@ -161,7 +162,7 @@ public class TrackBlockItem extends BlockItem { @OnlyIn(Dist.CLIENT) public static void sendExtenderPacket(PlayerInteractEvent.RightClickBlock event) { ItemStack stack = event.getItemStack(); - if (!AllBlocks.TRACK.isIn(stack) || !stack.hasTag()) + if (!AllTags.AllBlockTags.TRACKS.matches(stack) || !stack.hasTag()) return; if (Minecraft.getInstance().options.keySprint.isDown()) AllPackets.getChannel() diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java index f0f0c7e1d..fd8472f6e 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java @@ -8,8 +8,8 @@ import java.util.function.Consumer; import com.jozufozu.flywheel.util.transform.TransformStack; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import com.simibubi.create.AllBlocks; import com.simibubi.create.AllShapes; +import com.simibubi.create.AllTags; import com.simibubi.create.content.logistics.trains.BezierConnection; import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.AnimationTickHolder; @@ -162,7 +162,7 @@ public class TrackBlockOutline { .rotateXRadians(angles.x) .translate(-.5, -.125f, -.5); - boolean holdingTrack = AllBlocks.TRACK.isIn(Minecraft.getInstance().player.getMainHandItem()); + boolean holdingTrack = AllTags.AllBlockTags.TRACKS.matches(Minecraft.getInstance().player.getMainHandItem()); renderShape(AllShapes.TRACK_ORTHO.get(Direction.SOUTH), ms, vb, holdingTrack ? false : null); ms.popPose(); } @@ -190,7 +190,7 @@ public class TrackBlockOutline { ms.pushPose(); ms.translate(pos.getX() - camPos.x, pos.getY() - camPos.y, pos.getZ() - camPos.z); - boolean holdingTrack = AllBlocks.TRACK.isIn(Minecraft.getInstance().player.getMainHandItem()); + boolean holdingTrack = AllTags.AllBlockTags.TRACKS.matches(Minecraft.getInstance().player.getMainHandItem()); TrackShape shape = blockstate.getValue(TrackBlock.SHAPE); boolean canConnectFrom = !shape.isJunction() && !(mc.level.getBlockEntity(pos)instanceof TrackBlockEntity tbe && tbe.isTilted()); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackInstance.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackInstance.java index 708442730..e84014455 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackInstance.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackInstance.java @@ -20,6 +20,7 @@ import com.simibubi.create.AllPartialModels; import com.simibubi.create.content.logistics.trains.BezierConnection; import com.simibubi.create.content.logistics.trains.BezierConnection.GirderAngles; import com.simibubi.create.content.logistics.trains.BezierConnection.SegmentAngles; +import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; @@ -38,7 +39,7 @@ public class TrackInstance extends BlockEntityInstance { @Override public void update() { - if (blockEntity.connections.isEmpty()) + if (blockEntity.connections.isEmpty()) return; remove(); @@ -110,11 +111,13 @@ public class TrackInstance extends BlockEntityInstance { leftLightPos = new BlockPos[segCount]; rightLightPos = new BlockPos[segCount]; - mat.getModel(AllPartialModels.TRACK_TIE) + TrackMaterial.TrackModelHolder modelHolder = bc.getMaterial().getModelHolder(); + + mat.getModel(modelHolder.tie()) .createInstances(ties); - mat.getModel(AllPartialModels.TRACK_SEGMENT_LEFT) + mat.getModel(modelHolder.segment_left()) .createInstances(left); - mat.getModel(AllPartialModels.TRACK_SEGMENT_RIGHT) + mat.getModel(modelHolder.segment_right()) .createInstances(right); SegmentAngles[] segments = bc.getBakedSegments(); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java index e4e8cc40b..779791afa 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java @@ -6,16 +6,17 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import com.simibubi.create.AllBlocks; +import com.jozufozu.flywheel.util.Color; import com.simibubi.create.AllSpecialTextures; +import com.simibubi.create.AllTags; import com.simibubi.create.CreateClient; import com.simibubi.create.content.curiosities.tools.BlueprintOverlayRenderer; import com.simibubi.create.content.logistics.trains.BezierConnection; import com.simibubi.create.content.logistics.trains.ITrackBlock; +import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.foundation.block.ProperWaterloggedBlock; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.utility.AngleHelper; -import com.simibubi.create.foundation.utility.Color; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Lang; @@ -46,6 +47,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult.Type; @@ -57,6 +59,11 @@ import net.minecraftforge.items.ItemHandlerHelper; public class TrackPlacement { public static class PlacementInfo { + + public PlacementInfo(TrackMaterial material) { + this.trackMaterial = material; + } + BezierConnection curve = null; boolean valid = false; int end1Extent = 0; @@ -68,6 +75,7 @@ public class TrackPlacement { public int requiredPavement = 0; public boolean hasRequiredPavement = false; + public final TrackMaterial trackMaterial; // for visualisation Vec3 end1; @@ -109,7 +117,7 @@ public class TrackPlacement { && hoveringMaxed == maximiseTurn && lookAngle == hoveringAngle) return cached; - PlacementInfo info = new PlacementInfo(); + PlacementInfo info = new PlacementInfo(TrackMaterial.fromItem(stack.getItem())); hoveringMaxed = maximiseTurn; hoveringAngle = lookAngle; hoveringPos = pos2; @@ -195,7 +203,7 @@ public class TrackPlacement { BlockPos targetPos2 = pos2.offset(offset2.x, offset2.y, offset2.z); info.curve = new BezierConnection(Couple.create(targetPos1, targetPos2), Couple.create(end1.add(offset1), end2.add(offset2)), Couple.create(normedAxis1, normedAxis2), - Couple.create(normal1, normal2), true, girder); + Couple.create(normal1, normal2), true, girder, TrackMaterial.fromItem(stack.getItem())); } // S curve or Straight @@ -355,7 +363,7 @@ public class TrackPlacement { info.curve = skipCurve ? null : new BezierConnection(Couple.create(targetPos1, targetPos2), Couple.create(end1.add(offset1), end2.add(offset2)), Couple.create(normedAxis1, normedAxis2), - Couple.create(normal1, normal2), true, girder); + Couple.create(normal1, normal2), true, girder, TrackMaterial.fromItem(stack.getItem())); info.valid = true; @@ -400,7 +408,7 @@ public class TrackPlacement { continue; ItemStack stackInSlot = (offhand ? inv.offhand : inv.items).get(i); - boolean isTrack = AllBlocks.TRACK.isIn(stackInSlot); + boolean isTrack = AllTags.AllBlockTags.TRACKS.matches(stackInSlot) && stackInSlot.is(stack.getItem()); if (!isTrack && (!shouldPave || offhandItem.getItem() != stackInSlot.getItem())) continue; if (isTrack ? foundTracks >= tracks : foundPavement >= pavement) @@ -473,6 +481,18 @@ public class TrackPlacement { info.requiredPavement += TrackPaver.paveCurve(level, info.curve, block, simulate, visited); } + private static BlockState copyProperties(BlockState from, BlockState onto) { + for (Property property : onto.getProperties()) { + if (from.hasProperty(property)) + onto = onto.setValue(property, from.getValue(property)); + } + return onto; + } + + private static BlockState copyProperties(BlockState from, BlockState onto, boolean keepFrom) { + return keepFrom ? from : copyProperties(from, onto); + } + private static PlacementInfo placeTracks(Level level, PlacementInfo info, BlockState state1, BlockState state2, BlockPos targetPos1, BlockPos targetPos2, boolean simulate) { info.requiredTracks = 0; @@ -500,7 +520,8 @@ public class TrackPlacement { Vec3 offset = axis.scale(i); BlockPos offsetPos = pos.offset(offset.x, offset.y, offset.z); BlockState stateAtPos = level.getBlockState(offsetPos); - BlockState toPlace = state; + // copy over all shared properties from the shaped state to the correct track material block + BlockState toPlace = copyProperties(state, info.trackMaterial.getBlock().defaultBlockState()); boolean canPlace = stateAtPos.getMaterial() .isReplaceable(); @@ -523,15 +544,16 @@ public class TrackPlacement { return info; if (!simulate) { + BlockState onto = info.trackMaterial.getBlock().defaultBlockState(); BlockState stateAtPos = level.getBlockState(targetPos1); level.setBlock(targetPos1, ProperWaterloggedBlock.withWater(level, - (stateAtPos.getBlock() == state1.getBlock() ? stateAtPos : state1).setValue(TrackBlock.HAS_BE, true), - targetPos1), 3); + (AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : copyProperties(state1, onto)) + .setValue(TrackBlock.HAS_BE, true), targetPos1), 3); stateAtPos = level.getBlockState(targetPos2); level.setBlock(targetPos2, ProperWaterloggedBlock.withWater(level, - (stateAtPos.getBlock() == state2.getBlock() ? stateAtPos : state2).setValue(TrackBlock.HAS_BE, true), - targetPos2), 3); + (AllTags.AllBlockTags.TRACKS.matches(stateAtPos) ? stateAtPos : copyProperties(state2, onto)) + .setValue(TrackBlock.HAS_BE, true), targetPos2), 3); } BlockEntity te1 = level.getBlockEntity(targetPos1); @@ -582,10 +604,10 @@ public class TrackPlacement { return; InteractionHand hand = InteractionHand.MAIN_HAND; - if (!AllBlocks.TRACK.isIn(stack)) { + if (!AllTags.AllBlockTags.TRACKS.matches(stack)) { stack = player.getOffhandItem(); hand = InteractionHand.OFF_HAND; - if (!AllBlocks.TRACK.isIn(stack)) + if (!AllTags.AllBlockTags.TRACKS.matches(stack)) return; } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRenderer.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRenderer.java index 467feca51..de4bbff2c 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRenderer.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRenderer.java @@ -3,9 +3,6 @@ package com.simibubi.create.content.logistics.trains.track; import static com.simibubi.create.AllPartialModels.GIRDER_SEGMENT_BOTTOM; import static com.simibubi.create.AllPartialModels.GIRDER_SEGMENT_MIDDLE; import static com.simibubi.create.AllPartialModels.GIRDER_SEGMENT_TOP; -import static com.simibubi.create.AllPartialModels.TRACK_SEGMENT_LEFT; -import static com.simibubi.create.AllPartialModels.TRACK_SEGMENT_RIGHT; -import static com.simibubi.create.AllPartialModels.TRACK_TIE; import com.jozufozu.flywheel.backend.Backend; import com.mojang.blaze3d.vertex.PoseStack; @@ -14,6 +11,7 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import com.simibubi.create.content.logistics.trains.BezierConnection; import com.simibubi.create.content.logistics.trains.BezierConnection.GirderAngles; import com.simibubi.create.content.logistics.trains.BezierConnection.SegmentAngles; +import com.simibubi.create.content.logistics.trains.TrackMaterial; import com.simibubi.create.foundation.blockEntity.renderer.SafeBlockEntityRenderer; import com.simibubi.create.foundation.render.CachedBufferer; import com.simibubi.create.foundation.utility.AngleHelper; @@ -62,7 +60,9 @@ public class TrackRenderer extends SafeBlockEntityRenderer { SegmentAngles segment = segments[i]; int light = LevelRenderer.getLightColor(level, segment.lightPosition.offset(tePosition)); - CachedBufferer.partial(TRACK_TIE, air) + TrackMaterial.TrackModelHolder modelHolder = bc.getMaterial().getModelHolder(); + + CachedBufferer.partial(modelHolder.tie(), air) .mulPose(segment.tieTransform.pose()) .mulNormal(segment.tieTransform.normal()) .light(light) @@ -70,7 +70,7 @@ public class TrackRenderer extends SafeBlockEntityRenderer { for (boolean first : Iterate.trueAndFalse) { Pose transform = segment.railTransforms.get(first); - CachedBufferer.partial(first ? TRACK_SEGMENT_LEFT : TRACK_SEGMENT_RIGHT, air) + CachedBufferer.partial(first ? modelHolder.segment_left() : modelHolder.segment_right(), air) .mulPose(transform.pose()) .mulNormal(transform.normal()) .light(light) diff --git a/src/main/java/com/simibubi/create/content/schematics/ItemRequirement.java b/src/main/java/com/simibubi/create/content/schematics/ItemRequirement.java index 67e1684f8..235ff5ae3 100644 --- a/src/main/java/com/simibubi/create/content/schematics/ItemRequirement.java +++ b/src/main/java/com/simibubi/create/content/schematics/ItemRequirement.java @@ -1,12 +1,13 @@ package com.simibubi.create.content.schematics; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.simibubi.create.foundation.utility.NBTProcessors; + import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.decoration.ItemFrame; @@ -50,7 +51,9 @@ public class ItemRequirement { } public ItemRequirement(ItemUseType usage, List requiredItems) { - this(requiredItems.stream().map(req -> new StackRequirement(req, usage)).collect(Collectors.toList())); + this(requiredItems.stream() + .map(req -> new StackRequirement(req, usage)) + .collect(Collectors.toList())); } public static ItemRequirement of(BlockState state, BlockEntity be) { @@ -79,14 +82,18 @@ public class ItemRequirement { return INVALID; // double slab needs two items - if (state.hasProperty(BlockStateProperties.SLAB_TYPE) && state.getValue(BlockStateProperties.SLAB_TYPE) == SlabType.DOUBLE) + if (state.hasProperty(BlockStateProperties.SLAB_TYPE) + && state.getValue(BlockStateProperties.SLAB_TYPE) == SlabType.DOUBLE) return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, 2)); if (block instanceof TurtleEggBlock) - return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, state.getValue(TurtleEggBlock.EGGS).intValue())); + return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, state.getValue(TurtleEggBlock.EGGS) + .intValue())); if (block instanceof SeaPickleBlock) - return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, state.getValue(SeaPickleBlock.PICKLES).intValue())); + return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, state.getValue(SeaPickleBlock.PICKLES) + .intValue())); if (block instanceof SnowLayerBlock) - return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, state.getValue(SnowLayerBlock.LAYERS).intValue())); + return new ItemRequirement(ItemUseType.CONSUME, new ItemStack(item, state.getValue(SnowLayerBlock.LAYERS) + .intValue())); if (block instanceof FarmBlock || block instanceof DirtPathBlock) return new ItemRequirement(ItemUseType.CONSUME, Items.DIRT); if (block instanceof AbstractBannerBlock && be instanceof BannerBlockEntity bannerBE) @@ -101,22 +108,20 @@ public class ItemRequirement { if (entity instanceof ItemFrame itemFrame) { ItemStack frame = new ItemStack(Items.ITEM_FRAME); - ItemStack displayedItem = itemFrame.getItem(); + ItemStack displayedItem = NBTProcessors.withUnsafeNBTDiscarded(itemFrame.getItem()); if (displayedItem.isEmpty()) return new ItemRequirement(ItemUseType.CONSUME, Items.ITEM_FRAME); - return new ItemRequirement(ItemUseType.CONSUME, Arrays.asList(frame, displayedItem)); + return new ItemRequirement(List.of(new ItemRequirement.StackRequirement(frame, ItemUseType.CONSUME), + new ItemRequirement.StrictNbtStackRequirement(displayedItem, ItemUseType.CONSUME))); } if (entity instanceof ArmorStand armorStand) { - List requirements = new ArrayList<>(); - requirements.add(new ItemStack(Items.ARMOR_STAND)); - armorStand.getAllSlots().forEach(requirements::add); - return new ItemRequirement(ItemUseType.CONSUME, requirements); - } - - ItemStack pickedStack = entity.getPickResult(); - if (pickedStack != null) { - return new ItemRequirement(ItemUseType.CONSUME, pickedStack); + List requirements = new ArrayList<>(); + requirements.add(new StackRequirement(new ItemStack(Items.ARMOR_STAND), ItemUseType.CONSUME)); + armorStand.getAllSlots() + .forEach(s -> requirements + .add(new StrictNbtStackRequirement(NBTProcessors.withUnsafeNBTDiscarded(s), ItemUseType.CONSUME))); + return new ItemRequirement(requirements); } return INVALID; @@ -142,9 +147,8 @@ public class ItemRequirement { if (other.isEmpty()) return this; - return new ItemRequirement( - Stream.concat(requiredItems.stream(), other.requiredItems.stream()).collect(Collectors.toList()) - ); + return new ItemRequirement(Stream.concat(requiredItems.stream(), other.requiredItems.stream()) + .collect(Collectors.toList())); } public enum ItemUseType { diff --git a/src/main/java/com/simibubi/create/content/schematics/SchematicExport.java b/src/main/java/com/simibubi/create/content/schematics/SchematicExport.java new file mode 100644 index 000000000..6bad9f5a6 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/schematics/SchematicExport.java @@ -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) { + } +} diff --git a/src/main/java/com/simibubi/create/content/schematics/SchematicWorld.java b/src/main/java/com/simibubi/create/content/schematics/SchematicWorld.java index 573616dcd..424e07f20 100644 --- a/src/main/java/com/simibubi/create/content/schematics/SchematicWorld.java +++ b/src/main/java/com/simibubi/create/content/schematics/SchematicWorld.java @@ -11,6 +11,7 @@ import java.util.stream.Stream; import com.simibubi.create.Create; import com.simibubi.create.foundation.utility.BBHelper; +import com.simibubi.create.foundation.utility.NBTProcessors; import com.simibubi.create.foundation.utility.worldWrappers.WrappedWorld; import net.minecraft.core.BlockPos; @@ -18,6 +19,7 @@ import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.decoration.ItemFrame; import net.minecraft.world.entity.player.Player; @@ -73,14 +75,12 @@ public class SchematicWorld extends WrappedWorld implements ServerLevelAccessor @Override public boolean addFreshEntity(Entity entityIn) { - if (entityIn instanceof ItemFrame) - ((ItemFrame) entityIn).getItem() - .setTag(null); - if (entityIn instanceof ArmorStand) { - ArmorStand armorStandEntity = (ArmorStand) entityIn; - armorStandEntity.getAllSlots() - .forEach(stack -> stack.setTag(null)); - } + if (entityIn instanceof ItemFrame itemFrame) + itemFrame.setItem(NBTProcessors.withUnsafeNBTDiscarded(itemFrame.getItem())); + if (entityIn instanceof ArmorStand armorStand) + for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) + armorStand.setItemSlot(equipmentSlot, + NBTProcessors.withUnsafeNBTDiscarded(armorStand.getItemBySlot(equipmentSlot))); return entities.add(entityIn); } diff --git a/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java b/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java index 1a7e4af0a..a68056da4 100644 --- a/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java +++ b/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java @@ -8,6 +8,7 @@ import java.nio.file.Paths; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -16,8 +17,8 @@ import java.util.stream.Stream; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; 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.item.SchematicAndQuillItem; import com.simibubi.create.content.schematics.item.SchematicItem; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.CSchematics; @@ -25,17 +26,13 @@ import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.FilesHelper; import com.simibubi.create.foundation.utility.Lang; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; 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.state.BlockState; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; -import net.minecraft.world.phys.AABB; public class ServerSchematicLoader { @@ -163,7 +160,6 @@ public class ServerSchematicLoader { protected boolean validateSchematicSizeOnServer(ServerPlayer player, long size) { Integer maxFileSize = getConfig().maxTotalSchematicSize.get(); if (size > maxFileSize * 1000) { - player.sendSystemMessage(Lang.translateDirect("schematics.uploadTooLarge") .append(Components.literal(" (" + size / 1000 + " KB)."))); player.sendSystemMessage(Lang.translateDirect("schematics.maxAllowedSize") @@ -283,10 +279,9 @@ public class ServerSchematicLoader { public void handleInstantSchematic(ServerPlayer player, String schematic, Level world, BlockPos pos, BlockPos bounds) { - String playerPath = getSchematicPath() + "/" + player.getGameProfile() - .getName(); - String playerSchematicId = player.getGameProfile() - .getName() + "/" + schematic; + String playerName = player.getGameProfile().getName(); + String playerPath = getSchematicPath() + "/" + playerName; + String playerSchematicId = playerName + "/" + schematic; FilesHelper.createFolderIfMissing(playerPath); // Unsupported Format @@ -309,43 +304,43 @@ public class ServerSchematicLoader { if (!AllItems.SCHEMATIC_AND_QUILL.isIn(player.getMainHandItem())) 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 stream = Files.list(dir)) { + List files = stream.toList(); + if (files.size() < getConfig().maxSchematics.get()) + return true; + Optional 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 { - // Delete schematic with same name - Files.deleteIfExists(path); - - // Too many Schematics - long count; - try (Stream list = Files.list(Paths.get(playerPath))) { - count = list.count(); - } - - if (count >= getConfig().maxSchematics.get()) { - Stream list2 = Files.list(Paths.get(playerPath)); - Optional 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(); - } + return Files.getLastModifiedTime(file).toMillis(); } catch (IOException e) { - Create.LOGGER.error("Exception Thrown in direct Schematic Upload: " + playerSchematicId); - e.printStackTrace(); + Create.LOGGER.error("Error getting modification time of file " + file.getFileName(), e); + throw new IllegalStateException(e); } } diff --git a/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java b/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java index 2a2173bf8..3e6785bf0 100644 --- a/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java +++ b/src/main/java/com/simibubi/create/content/schematics/client/SchematicAndQuillHandler.java @@ -1,13 +1,8 @@ package com.simibubi.create.content.schematics.client; import java.io.IOException; -import java.io.OutputStream; import java.nio.file.Files; 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.AllKeys; @@ -15,34 +10,29 @@ import com.simibubi.create.AllSpecialTextures; import com.simibubi.create.Create; import com.simibubi.create.CreateClient; 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.foundation.gui.ScreenOpener; import com.simibubi.create.foundation.networking.AllPackets; 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.RaycastHelper; import com.simibubi.create.foundation.utility.RaycastHelper.PredicateTraceResult; import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.outliner.Outliner; +import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.AxisDirection; import net.minecraft.core.Vec3i; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.context.BlockPlaceContext; 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.BlockHitResult; import net.minecraft.world.phys.HitResult.Type; @@ -52,8 +42,8 @@ public class SchematicAndQuillHandler { private Object outlineSlot = new Object(); - private BlockPos firstPos; - private BlockPos secondPos; + public BlockPos firstPos; + public BlockPos secondPos; private BlockPos selectedPos; private Direction selectedFace; private int range = 10; @@ -213,58 +203,31 @@ public class SchematicAndQuillHandler { } public void saveSchematic(String string, boolean convertImmediately) { - StructureTemplate t = new StructureTemplate(); - BoundingBox bb = BoundingBox.fromCorners(firstPos, secondPos); - BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ()); - BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan()); - Level level = Minecraft.getInstance().level; - - t.fillFromWorld(level, origin, bounds, true, Blocks.AIR); - - if (string.isEmpty()) - string = Lang.translateDirect("schematicAndQuill.fallbackName") - .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); + SchematicExportResult result = SchematicExport.saveSchematic( + SchematicExport.SCHEMATICS, string, false, + Minecraft.getInstance().level, firstPos, secondPos + ); + LocalPlayer player = Minecraft.getInstance().player; + if (result == null) { + Lang.translate("schematicAndQuill.failed") + .style(ChatFormatting.RED) + .sendStatus(player); + return; } + Path file = result.file(); + Lang.translate("schematicAndQuill.saved", file) + .sendStatus(player); firstPos = null; secondPos = null; - LocalPlayer player = Minecraft.getInstance().player; - Lang.translate("schematicAndQuill.saved", filepath) - .sendStatus(player); - if (!convertImmediately) return; - if (!Files.exists(path)) { - Create.LOGGER.error("Missing Schematic file: " + path.toString()); - return; - } try { - if (!ClientSchematicLoader.validateSizeLimitation(Files.size(path))) + if (!ClientSchematicLoader.validateSizeLimitation(Files.size(file))) return; - AllPackets.getChannel().sendToServer(new InstantSchematicPacket(filename, origin, bounds)); - + AllPackets.getChannel() + .sendToServer(new InstantSchematicPacket(result.fileName(), result.origin(), result.bounds())); } catch (IOException e) { - Create.LOGGER.error("Error finding Schematic file: " + path.toString()); - e.printStackTrace(); - return; + Create.LOGGER.error("Error instantly uploading Schematic file: " + file, e); } } @@ -272,4 +235,4 @@ public class SchematicAndQuillHandler { return CreateClient.OUTLINER; } -} \ No newline at end of file +} diff --git a/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java b/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java index 3fb353c40..4f9723252 100644 --- a/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java +++ b/src/main/java/com/simibubi/create/content/schematics/client/SchematicPromptScreen.java @@ -109,5 +109,4 @@ public class SchematicPromptScreen extends AbstractSimiScreen { CreateClient.SCHEMATIC_AND_QUILL_HANDLER.saveSchematic(nameField.getValue(), convertImmediately); onClose(); } - } diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java index 3cde71d84..9b6747132 100644 --- a/src/main/java/com/simibubi/create/events/ClientEvents.java +++ b/src/main/java/com/simibubi/create/events/ClientEvents.java @@ -300,14 +300,14 @@ public class ClientEvents { if (AllFluids.CHOCOLATE.get() .isSame(fluid)) { - event.scaleFarPlaneDistance(1f / 32f); + event.scaleFarPlaneDistance(1f / 32f * AllConfigs.client().chocolateTransparencyMultiplier.getF()); event.setCanceled(true); return; } if (AllFluids.HONEY.get() .isSame(fluid)) { - event.scaleFarPlaneDistance(1f / 8f); + event.scaleFarPlaneDistance(1f / 8f * AllConfigs.client().honeyTransparencyMultiplier.getF()); event.setCanceled(true); return; } diff --git a/src/main/java/com/simibubi/create/events/CommonEvents.java b/src/main/java/com/simibubi/create/events/CommonEvents.java index de476b1db..5b4bbc499 100644 --- a/src/main/java/com/simibubi/create/events/CommonEvents.java +++ b/src/main/java/com/simibubi/create/events/CommonEvents.java @@ -50,9 +50,11 @@ import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.forgespi.language.IModFileInfo; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.registries.NewRegistryEvent; @EventBusSubscriber public class CommonEvents { @@ -78,7 +80,7 @@ public class CommonEvents { ToolboxHandler.playerLogin(player); Create.RAILWAYS.playerLogin(player); } - + @SubscribeEvent public static void playerLoggedOut(PlayerLoggedOutEvent event) { Player player = event.getEntity(); @@ -131,7 +133,7 @@ public class CommonEvents { public static void onEntityEnterSection(EntityEvent.EnteringSection event) { CarriageEntityHandler.onEntityEnterSection(event); } - + @SubscribeEvent public static void addReloadListeners(AddReloadListenerEvent event) { event.addListener(RecipeFinder.LISTENER); @@ -208,7 +210,5 @@ public class CommonEvents { }); } } - } - } diff --git a/src/main/java/com/simibubi/create/foundation/command/AllCommands.java b/src/main/java/com/simibubi/create/foundation/command/AllCommands.java index 1b67a386e..5c532a142 100644 --- a/src/main/java/com/simibubi/create/foundation/command/AllCommands.java +++ b/src/main/java/com/simibubi/create/foundation/command/AllCommands.java @@ -11,6 +11,8 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.loading.FMLLoader; public class AllCommands { @@ -20,7 +22,7 @@ public class AllCommands { LiteralCommandNode util = buildUtilityCommands(); - LiteralCommandNode createRoot = dispatcher.register(Commands.literal("create") + LiteralArgumentBuilder root = Commands.literal("create") .requires(cs -> cs.hasPermission(0)) // general purpose .then(new ToggleDebugCommand().register()) @@ -38,8 +40,12 @@ public class AllCommands { .then(GlueCommand.register()) // utility - .then(util) - ); + .then(util); + + if (!FMLLoader.isProduction() && FMLLoader.getDist() == Dist.CLIENT) + root.then(CreateTestCommand.register()); + + LiteralCommandNode createRoot = dispatcher.register(root); createRoot.addChild(buildRedirect("u", util)); @@ -60,7 +66,8 @@ public class AllCommands { .then(CameraDistanceCommand.register()) .then(CameraAngleCommand.register()) .then(FlySpeedCommand.register()) - .then(KillTPSCommand.register()) + //.then(DebugValueCommand.register()) + //.then(KillTPSCommand.register()) .build(); } diff --git a/src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java b/src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java new file mode 100644 index 000000000..f42edbed2 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/command/CreateTestCommand.java @@ -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 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 getSuggestions(CommandContext 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 findInDir(Path dir, SuggestionsBuilder builder) { + try (Stream 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(); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/command/DebugValueCommand.java b/src/main/java/com/simibubi/create/foundation/command/DebugValueCommand.java new file mode 100644 index 000000000..5272bdc59 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/command/DebugValueCommand.java @@ -0,0 +1,41 @@ +package com.simibubi.create.foundation.command; + +import com.mojang.brigadier.arguments.FloatArgumentType; + +import com.simibubi.create.Create; + +import net.minecraft.SharedConstants; + +import org.apache.commons.lang3.mutable.MutableInt; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.simibubi.create.foundation.utility.Components; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.BaseCommandBlock; +import net.minecraft.world.level.block.CommandBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.CommandBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class DebugValueCommand { + + public static float value = 0; + + public static ArgumentBuilder register() { + return Commands.literal("debugValue") + .requires(cs -> cs.hasPermission(4)) + .then(Commands.argument("value", FloatArgumentType.floatArg()) + .executes((ctx) -> { + value = FloatArgumentType.getFloat(ctx, "value"); + ctx.getSource().sendSuccess(Components.literal("Set value to: "+value), true); + return 1; + })); + + } +} diff --git a/src/main/java/com/simibubi/create/foundation/config/CClient.java b/src/main/java/com/simibubi/create/foundation/config/CClient.java index 4fffd25fe..1b0c8cb7e 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CClient.java +++ b/src/main/java/com/simibubi/create/foundation/config/CClient.java @@ -31,7 +31,14 @@ public class CClient extends ConfigBase { public final ConfigInt ingameMenuConfigButtonOffsetX = i(-4, Integer.MIN_VALUE, Integer.MAX_VALUE, "ingameMenuConfigButtonOffsetX", Comments.ingameMenuConfigButtonOffsetX); 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 public final ConfigGroup overlay = group(1, "goggleOverlay", @@ -77,6 +84,7 @@ public class CClient extends ConfigBase { public final ConfigGroup trains = group(1, "trains", Comments.trains); public final ConfigFloat mountedZoomMultiplier = f(3, 0, "mountedZoomMultiplier", Comments.mountedZoomMultiplier); public final ConfigBool showTrackGraphOnF3 = b(false, "showTrackGraphOnF3", Comments.showTrackGraphOnF3); + public final ConfigBool showExtendedTrackGraphOnF3 = b(false, "showExtendedTrackGraphOnF3", Comments.showExtendedTrackGraphOnF3); @Override public String getName() { @@ -147,6 +155,10 @@ public class CClient extends ConfigBase { static String trains = "Railway related settings"; 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 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"; } } diff --git a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java index a786847e3..97435638f 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CLogistics.java +++ b/src/main/java/com/simibubi/create/foundation/config/CLogistics.java @@ -8,6 +8,7 @@ public class CLogistics extends ConfigBase { public final ConfigInt linkRange = i(256, 1, "linkRange", Comments.linkRange); public final ConfigInt displayLinkRange = i(64, 1, "displayLinkRange", Comments.displayLinkRange); public final ConfigInt vaultCapacity = i(20, 1, "vaultCapacity", Comments.vaultCapacity); + public final ConfigInt brassTunnelTimer = i(10, 1, 10, "brassTunnelTimer", Comments.brassTunnelTimer); @Override 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."; 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 brassTunnelTimer = "The amount of ticks a brass tunnel waits between distributions."; } } diff --git a/src/main/java/com/simibubi/create/foundation/data/BuilderTransformers.java b/src/main/java/com/simibubi/create/foundation/data/BuilderTransformers.java index 52432c7b9..0f0dcc916 100644 --- a/src/main/java/com/simibubi/create/foundation/data/BuilderTransformers.java +++ b/src/main/java/com/simibubi/create/foundation/data/BuilderTransformers.java @@ -37,7 +37,7 @@ import com.simibubi.create.content.curiosities.frames.CopycatBlock; import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelBlock; import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelBlock.Shape; import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelItem; -import com.simibubi.create.content.logistics.trains.IBogeyBlock; +import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock; import com.simibubi.create.content.logistics.trains.track.StandardBogeyBlock; import com.simibubi.create.foundation.block.BlockStressDefaults; import com.simibubi.create.foundation.block.ItemUseOverrides; @@ -95,6 +95,7 @@ public class BuilderTransformers { .build(); } + @SuppressWarnings("deprecation") public static NonNullUnaryOperator> bogey() { return b -> b.initialProperties(SharedProperties::softMetal) .properties(p -> p.sound(SoundType.NETHERITE_BLOCK)) @@ -103,7 +104,7 @@ public class BuilderTransformers { .blockstate((c, p) -> BlockStateGen.horizontalAxisBlock(c, p, s -> p.models() .getExistingFile(p.modLoc("block/track/bogey/top")))) .loot((p, l) -> p.dropOther(l, AllBlocks.RAILWAY_CASING.get())) - .onRegister(block -> IBogeyBlock.register(RegisteredObjects.getKeyOrThrow(block))); + .onRegister(block -> AbstractBogeyBlock.registerStandardBogey(RegisteredObjects.getKeyOrThrow(block))); } public static NonNullUnaryOperator> copycat() { diff --git a/src/main/java/com/simibubi/create/foundation/data/CreateBlockEntityBuilder.java b/src/main/java/com/simibubi/create/foundation/data/CreateBlockEntityBuilder.java index 8f964d489..cdf40b42d 100644 --- a/src/main/java/com/simibubi/create/foundation/data/CreateBlockEntityBuilder.java +++ b/src/main/java/com/simibubi/create/foundation/data/CreateBlockEntityBuilder.java @@ -1,6 +1,9 @@ package com.simibubi.create.foundation.data; +import java.util.ArrayList; +import java.util.Collection; import java.util.function.BiFunction; +import java.util.function.Supplier; import javax.annotation.Nullable; @@ -13,7 +16,9 @@ import com.tterrag.registrate.builders.BuilderCallback; import com.tterrag.registrate.util.OneTimeEventReceiver; import com.tterrag.registrate.util.nullness.NonNullSupplier; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.util.NonNullPredicate; import net.minecraftforge.fml.DistExecutor; @@ -25,6 +30,9 @@ public class CreateBlockEntityBuilder extends BlockEnt private NonNullSupplier>> instanceFactory; private NonNullPredicate renderNormally; + private Collection>>> deferredValidBlocks = + new ArrayList<>(); + public static BlockEntityBuilder create(AbstractRegistrate owner, P parent, String name, BuilderCallback callback, BlockEntityFactory factory) { return new CreateBlockEntityBuilder<>(owner, parent, name, callback, factory); @@ -35,15 +43,35 @@ public class CreateBlockEntityBuilder extends BlockEnt super(owner, parent, name, callback, factory); } - public CreateBlockEntityBuilder instance(NonNullSupplier>> instanceFactory) { + public CreateBlockEntityBuilder validBlocksDeferred( + NonNullSupplier>> blocks) { + deferredValidBlocks.add(blocks); + return this; + } + + @Override + protected BlockEntityType createEntry() { + deferredValidBlocks.stream() + .map(Supplier::get) + .flatMap(Collection::stream) + .forEach(this::validBlock); + return super.createEntry(); + } + + public CreateBlockEntityBuilder instance( + NonNullSupplier>> instanceFactory) { return instance(instanceFactory, true); } - public CreateBlockEntityBuilder instance(NonNullSupplier>> instanceFactory, boolean renderNormally) { + public CreateBlockEntityBuilder instance( + NonNullSupplier>> instanceFactory, + boolean renderNormally) { return instance(instanceFactory, be -> renderNormally); } - public CreateBlockEntityBuilder instance(NonNullSupplier>> instanceFactory, NonNullPredicate renderNormally) { + public CreateBlockEntityBuilder instance( + NonNullSupplier>> instanceFactory, + NonNullPredicate renderNormally) { if (this.instanceFactory == null) { DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> this::registerInstance); } @@ -56,7 +84,8 @@ public class CreateBlockEntityBuilder extends BlockEnt protected void registerInstance() { OneTimeEventReceiver.addModListener(FMLClientSetupEvent.class, $ -> { - NonNullSupplier>> instanceFactory = this.instanceFactory; + NonNullSupplier>> instanceFactory = + this.instanceFactory; if (instanceFactory != null) { NonNullPredicate renderNormally = this.renderNormally; InstancedRenderRegistry.configure(getEntry()) diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index f9429281b..2333dd062 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -177,7 +177,7 @@ public enum AllGuiTextures implements ScreenElement { SPEECH_TOOLTIP_BACKGROUND("widgets", 0, 24, 8, 8), SPEECH_TOOLTIP_COLOR("widgets", 8, 24, 8, 8), - + TRAIN_HUD_SPEED_BG("widgets", 0, 190, 182, 5), TRAIN_HUD_SPEED("widgets", 0, 185, 182, 5), TRAIN_HUD_THROTTLE("widgets", 0, 195, 182, 5), @@ -189,7 +189,10 @@ public enum AllGuiTextures implements ScreenElement { TRAIN_PROMPT("widgets", 0, 230, 256, 16), // PlacementIndicator - PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256); + PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256), + + // ComputerCraft + COMPUTER("computer", 200, 102); ; diff --git a/src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java new file mode 100644 index 000000000..da309104b --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/MainMixin.java @@ -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. + *

+ * 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; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java new file mode 100644 index 000000000..f78936a54 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/TestCommandMixin.java @@ -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; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java b/src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java new file mode 100644 index 000000000..3c3c280d6 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/accessor/GameTestHelperAccessor.java @@ -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); +} diff --git a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java index 5773e4f30..d2d02a55f 100644 --- a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java +++ b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java @@ -8,6 +8,7 @@ import java.util.function.Function; import java.util.function.Supplier; 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.structureMovement.ContraptionBlockChangedPacket; import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionColliderLockPacket; @@ -206,7 +207,8 @@ public enum AllPackets { SET_FIRE_IMMUNE(NetheriteDivingHandler.SetFireImmunePacket.class, NetheriteDivingHandler.SetFireImmunePacket::new, PLAY_TO_CLIENT), CONTRAPTION_COLLIDER_LOCK(ContraptionColliderLockPacket.class, ContraptionColliderLockPacket::new, PLAY_TO_CLIENT), - + ATTACHED_COMPUTER(AttachedComputerPacket.class, AttachedComputerPacket::new, PLAY_TO_CLIENT), + ; public static final ResourceLocation CHANNEL_NAME = Create.asResource("main"); diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java index f55d23d7f..ce9678e95 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java @@ -3,6 +3,9 @@ package com.simibubi.create.foundation.ponder.content; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; 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.track.TrackBlock; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.ponder.PonderRegistrationHelper; import com.simibubi.create.foundation.ponder.PonderRegistry; @@ -19,9 +22,14 @@ import com.simibubi.create.foundation.ponder.content.trains.TrackScenes; import com.simibubi.create.foundation.ponder.content.trains.TrainScenes; import com.simibubi.create.foundation.ponder.content.trains.TrainSignalScenes; import com.simibubi.create.foundation.ponder.content.trains.TrainStationScenes; +import com.tterrag.registrate.util.entry.BlockEntry; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; public class PonderIndex { @@ -320,7 +328,14 @@ public class PonderIndex { .addStoryBoard("threshold_switch", DetectorScenes::thresholdSwitch); // Trains - HELPER.forComponents(AllBlocks.TRACK) + HELPER.forComponents(TrackMaterial.allBlocks() + .stream() + .map((trackSupplier) -> new BlockEntry( + // note: these blocks probably WON'T be in the Create Registrate, but a simple + // code trace reveals the Entry's registrate isn't used + Create.REGISTRATE, RegistryObject.create(trackSupplier.get() + .getRegistryName(), ForgeRegistries.BLOCKS))) + .toList()) .addStoryBoard("train_track/placement", TrackScenes::placement) .addStoryBoard("train_track/portal", TrackScenes::portal) .addStoryBoard("train_track/chunks", TrackScenes::chunks); @@ -584,6 +599,12 @@ public class PonderIndex { .add(Blocks.COMMAND_BLOCK) .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) .add(AllBlocks.ORANGE_NIXIE_TUBE) .add(AllBlocks.DISPLAY_BOARD) diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/RollerScenes.java b/src/main/java/com/simibubi/create/foundation/ponder/content/RollerScenes.java index f36863473..fa5596abe 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/RollerScenes.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/RollerScenes.java @@ -64,7 +64,7 @@ public class RollerScenes { scene.overlay.showText(60) .pointAt(util.vector.topOf(util.grid.at(6, 2, 4))) .attachKeyFrame() - .text("Mechanical rollers help to clean up long tracks or paths conveniently") + .text("Mechanical rollers help to clean up terrain around tracks or paths") .placeNearTarget(); scene.idle(70); diff --git a/src/main/java/com/simibubi/create/foundation/ponder/instruction/AnimateBlockEntityInstruction.java b/src/main/java/com/simibubi/create/foundation/ponder/instruction/AnimateBlockEntityInstruction.java index 64d6d4321..8eb15e148 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/instruction/AnimateBlockEntityInstruction.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/instruction/AnimateBlockEntityInstruction.java @@ -7,7 +7,7 @@ import java.util.function.Function; import com.simibubi.create.content.contraptions.components.deployer.DeployerBlockEntity; import com.simibubi.create.content.contraptions.components.structureMovement.bearing.IBearingBlockEntity; import com.simibubi.create.content.contraptions.components.structureMovement.pulley.PulleyBlockEntity; -import com.simibubi.create.content.logistics.trains.track.StandardBogeyBlockEntity; +import com.simibubi.create.content.logistics.trains.track.AbstractBogeyBlockEntity; import com.simibubi.create.foundation.ponder.PonderScene; import com.simibubi.create.foundation.ponder.PonderWorld; @@ -34,7 +34,7 @@ public class AnimateBlockEntityInstruction extends TickingInstruction { public static AnimateBlockEntityInstruction bogey(BlockPos location, float totalDelta, int ticks) { float movedPerTick = totalDelta / ticks; return new AnimateBlockEntityInstruction(location, totalDelta, ticks, - (w, f) -> castIfPresent(w, location, StandardBogeyBlockEntity.class) + (w, f) -> castIfPresent(w, location, AbstractBogeyBlockEntity.class) .ifPresent(bte -> bte.animate(f.equals(totalDelta) ? 0 : movedPerTick)), (w) -> 0f); } diff --git a/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java b/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java index a4b3c8b78..536ffa943 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/FilesHelper.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; 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; String filename; - String filepath; + Path filepath; do { filename = slug(name) + ((index == 0) ? "" : "_" + index) + "." + extension; index++; - filepath = folderPath + "/" + filename; - } while (Files.exists(Paths.get(filepath))); + filepath = folderPath.resolve(filename); + } while (Files.exists(filepath)); return filename; } diff --git a/src/main/java/com/simibubi/create/foundation/utility/Iterate.java b/src/main/java/com/simibubi/create/foundation/utility/Iterate.java index e8e541aa1..0e072cf38 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/Iterate.java +++ b/src/main/java/com/simibubi/create/foundation/utility/Iterate.java @@ -45,4 +45,13 @@ public class Iterate { public static List hereBelowAndAbove(BlockPos pos) { return Arrays.asList(pos, pos.below(), pos.above()); } + + public static T cycleValue(List list, T current) { + int currentIndex = list.indexOf(current); + if (currentIndex == -1) { + throw new IllegalArgumentException("Current value not found in list"); + } + int nextIndex = (currentIndex + 1) % list.size(); + return list.get(nextIndex); + } } diff --git a/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java b/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java index 603e313e9..0dead96f9 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java @@ -13,6 +13,7 @@ import net.minecraft.nbt.FloatTag; import net.minecraft.nbt.IntTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.AABB; @@ -118,4 +119,12 @@ public class NBTHelper { return compoundTag.getInt("V"); } + public static void writeResourceLocation(CompoundTag nbt, String key, ResourceLocation location) { + nbt.putString(key, location.toString()); + } + + public static ResourceLocation readResourceLocation(CompoundTag nbt, String key) { + return new ResourceLocation(nbt.getString(key)); + } + } diff --git a/src/main/java/com/simibubi/create/foundation/utility/NBTProcessors.java b/src/main/java/com/simibubi/create/foundation/utility/NBTProcessors.java index 81a9357a4..f2dc7a70d 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/NBTProcessors.java +++ b/src/main/java/com/simibubi/create/foundation/utility/NBTProcessors.java @@ -1,15 +1,20 @@ package com.simibubi.create.foundation.utility; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.function.UnaryOperator; import javax.annotation.Nullable; +import com.simibubi.create.AllBlockEntityTypes; + import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.SpawnerBlockEntity; @@ -23,7 +28,8 @@ public final class NBTProcessors { processors.put(type, processor); } - public static synchronized void addSurvivalProcessor(BlockEntityType type, UnaryOperator processor) { + public static synchronized void addSurvivalProcessor(BlockEntityType type, + UnaryOperator processor) { survivalProcessors.put(type, processor); } @@ -54,15 +60,62 @@ public final class NBTProcessors { } return data; }); + addProcessor(AllBlockEntityTypes.CREATIVE_CRATE.get(), itemProcessor("Filter")); + addProcessor(AllBlockEntityTypes.PLACARD.get(), itemProcessor("Item")); + } + + public static UnaryOperator itemProcessor(String tagKey) { + return data -> { + CompoundTag compound = data.getCompound(tagKey); + if (!compound.contains("tag", 10)) + return data; + CompoundTag itemTag = compound.getCompound("tag"); + if (itemTag == null) + return data; + HashSet keys = new HashSet<>(itemTag.getAllKeys()); + for (String key : keys) + if (isUnsafeItemNBTKey(key)) + itemTag.remove(key); + if (itemTag.isEmpty()) + compound.remove("tag"); + return data; + }; + } + + public static ItemStack withUnsafeNBTDiscarded(ItemStack stack) { + if (stack.getTag() == null) + return stack; + ItemStack copy = stack.copy(); + stack.getTag() + .getAllKeys() + .stream() + .filter(NBTProcessors::isUnsafeItemNBTKey) + .forEach(copy::removeTagKey); + if (copy.getTag() + .isEmpty()) + copy.setTag(null); + return copy; + } + + public static boolean isUnsafeItemNBTKey(String name) { + if (name.equals(EnchantedBookItem.TAG_STORED_ENCHANTMENTS)) + return false; + if (name.equals("Enchantments")) + return false; + if (name.contains("Potion")) + return false; + if (name.contains("Damage")) + return false; + return true; } public static boolean textComponentHasClickEvent(String json) { Component component = Component.Serializer.fromJson(json.isEmpty() ? "\"\"" : json); - return component != null && component.getStyle() != null && component.getStyle().getClickEvent() != null; + return component != null && component.getStyle() != null && component.getStyle() + .getClickEvent() != null; } - private NBTProcessors() { - } + private NBTProcessors() {} @Nullable public static CompoundTag process(BlockEntity blockEntity, CompoundTag compound, boolean survival) { diff --git a/src/main/java/com/simibubi/create/foundation/utility/StringHelper.java b/src/main/java/com/simibubi/create/foundation/utility/StringHelper.java new file mode 100644 index 000000000..78521b4e5 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/StringHelper.java @@ -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(); + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/outliner/ItemOutline.java b/src/main/java/com/simibubi/create/foundation/utility/outliner/ItemOutline.java new file mode 100644 index 000000000..7f974cabb --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/outliner/ItemOutline.java @@ -0,0 +1,51 @@ +package com.simibubi.create.foundation.utility.outliner; + +import com.jozufozu.flywheel.util.transform.TransformStack; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.simibubi.create.AllSpecialTextures; +import com.simibubi.create.foundation.render.RenderTypes; +import com.simibubi.create.foundation.render.SuperRenderTypeBuffer; +import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.VecHelper; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; + +import java.util.*; + +public class ItemOutline extends Outline { + + protected Vec3 pos; + protected ItemStack stack; + + public ItemOutline(Vec3 pos, ItemStack stack) { + this.pos = pos; + this.stack = stack; + } + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, Vec3 camera, float pt) { + Minecraft mc = Minecraft.getInstance(); + ms.pushPose(); + + TransformStack.cast(ms) + .translate(pos.x - camera.x, pos.y - camera.y, pos.z - camera.z) + .scale(params.alpha); + + mc.getItemRenderer().render(stack, ItemTransforms.TransformType.FIXED, false, ms, + buffer, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, + mc.getItemRenderer().getModel(stack, null, null, 0)); + + ms.popPose(); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/utility/outliner/Outliner.java b/src/main/java/com/simibubi/create/foundation/utility/outliner/Outliner.java index b1b297597..ac6f32699 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/outliner/Outliner.java +++ b/src/main/java/com/simibubi/create/foundation/utility/outliner/Outliner.java @@ -14,6 +14,7 @@ import com.simibubi.create.foundation.utility.outliner.Outline.OutlineParams; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; @@ -81,6 +82,13 @@ public class Outliner { // + public OutlineParams showItem(Object slot, Vec3 pos, ItemStack stack) { + ItemOutline outline = new ItemOutline(pos, stack); + OutlineEntry entry = new OutlineEntry(outline); + outlines.put(slot, entry); + return entry.getOutline().getParams(); + } + public void keep(Object slot) { if (outlines.containsKey(slot)) outlines.get(slot).ticksTillRemoval = 1; diff --git a/src/main/java/com/simibubi/create/gametest/CreateGameTests.java b/src/main/java/com/simibubi/create/gametest/CreateGameTests.java new file mode 100644 index 000000000..f50450caa --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/CreateGameTests.java @@ -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 generateTests() { + return CreateTestFunction.getTestsFrom(testHolders); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/TESTING.md b/src/main/java/com/simibubi/create/gametest/TESTING.md new file mode 100644 index 000000000..3dec254c9 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/TESTING.md @@ -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. diff --git a/src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java new file mode 100644 index 000000000..9a3a27162 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateGameTestHelper.java @@ -0,0 +1,448 @@ +package com.simibubi.create.gametest.infrastructure; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelBlockEntity.SelectionMode; +import com.simibubi.create.content.logistics.block.redstone.NixieTubeBlockEntity; +import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; +import com.simibubi.create.foundation.blockEntity.IMultiBlockEntityContainer; +import com.simibubi.create.foundation.blockEntity.behaviour.BehaviourType; +import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.ScrollOptionBehaviour; +import com.simibubi.create.foundation.blockEntity.behaviour.scrollvalue.ScrollValueBehaviour; +import com.simibubi.create.foundation.item.ItemHelper; +import com.simibubi.create.foundation.mixin.accessor.GameTestHelperAccessor; +import com.simibubi.create.foundation.utility.RegisteredObjects; + +import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +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.Blocks; +import net.minecraft.world.level.block.LeverBlock; +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 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; + +/** + * 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) { + NixieTubeBlockEntity nixie = getBlockEntity(AllBlockEntityTypes.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 getBlockEntity(BlockEntityType 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 IMultiBlockEntityContainer}, get the controller for it. + */ + public T getControllerBlockEntity(BlockEntityType type, BlockPos anySegment) { + T be = getBlockEntity(type, anySegment).getControllerBE(); + 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 BlockEntityBehaviour} from the given position, failing if not present. + */ + public T getBehavior(BlockPos pos, BehaviourType type) { + T behavior = BlockEntityBehaviour.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 getFirstEntity(EntityType type, BlockPos pos) { + List 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 List getEntitiesBetween(EntityType type, BlockPos pos1, BlockPos pos2) { + BoundingBox box = BoundingBox.fromCorners(absolutePos(pos1), absolutePos(pos2)); + List entities = getLevel().getEntities(type, e -> box.isInside(e.blockPosition())); + return (List) entities; + } + + + // transfer - fluids + + public IFluidHandler fluidStorageAt(BlockPos pos) { + BlockEntity be = getBlockEntity(pos); + if (be == null) + fail("BlockEntity not present"); + Optional 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 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 getItemContent(BlockPos pos) { + IItemHandler handler = itemStorageAt(pos); + Object2LongMap 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 content, BlockPos pos) { + IItemHandler handler = itemStorageAt(pos); + Object2LongMap 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 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); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java new file mode 100644 index 000000000..16cfcd163 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/infrastructure/CreateTestFunction.java @@ -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 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 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 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 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)); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java b/src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java new file mode 100644 index 000000000..cb24dc5ce --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/infrastructure/GameTestGroup.java @@ -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; +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java b/src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java new file mode 100644 index 000000000..524475898 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestContraptions.java @@ -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 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 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"); +// } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestFluids.java b/src/main/java/com/simibubi/create/gametest/tests/TestFluids.java new file mode 100644 index 000000000..8e4d06f06 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestFluids.java @@ -0,0 +1,150 @@ +package com.simibubi.create.gametest.tests; + +import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.content.contraptions.fluids.actors.HosePulleyFluidHandler; +import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeBlockEntity; +import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeBlockEntity; +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(() -> { + StressGaugeBlockEntity stress = helper.getBlockEntity(AllBlockEntityTypes.STRESSOMETER.get(), stressometer); + SpeedGaugeBlockEntity speed = helper.getBlockEntity(AllBlockEntityTypes.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)); + } + }); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestItems.java b/src/main/java/com/simibubi/create/gametest/tests/TestItems.java new file mode 100644 index 000000000..fe41664e7 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestItems.java @@ -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.AllBlockEntityTypes; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllItems; +import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelBlockEntity.SelectionMode; +import com.simibubi.create.content.logistics.block.depot.DepotBlockEntity; +import com.simibubi.create.content.logistics.block.redstone.NixieTubeBlockEntity; +import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayBlockEntity; +import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayLayout; +import com.simibubi.create.content.logistics.trains.management.display.FlapDisplaySection; +import com.simibubi.create.foundation.utility.Components; +import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper; +import com.simibubi.create.gametest.infrastructure.GameTestGroup; + +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 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); + DepotBlockEntity depot1 = helper.getBlockEntity(AllBlockEntityTypes.DEPOT.get(), depot1Pos); + BlockPos depot2Pos = new BlockPos(1, 2, 1); + DepotBlockEntity depot2 = helper.getBlockEntity(AllBlockEntityTypes.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 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 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 tunnels = List.of( + new BlockPos(3, 3, 1), + new BlockPos(3, 3, 2), + new BlockPos(3, 3, 3) + ); + List 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 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 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 outputs) { + BlockPos lever = new BlockPos(2, 3, 2); + List 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 redstoneBlocks = List.of( + new BlockPos(3, 4, 1), + new BlockPos(3, 4, 2), + new BlockPos(3, 4, 3) + ); + List tunnels = List.of( + new BlockPos(5, 3, 1), + new BlockPos(5, 3, 2), + new BlockPos(5, 3, 3) + ); + List 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); + NixieTubeBlockEntity chestNixie = helper.getBlockEntity(AllBlockEntityTypes.NIXIE_TUBE.get(), chestNixiePos); + + BlockPos doubleChest = new BlockPos(2, 2, 3); + long totalDoubleChestItems = helper.getTotalItems(doubleChest); + BlockPos doubleChestNixiePos = new BlockPos(1, 3, 3); + NixieTubeBlockEntity doubleChestNixie = helper.getBlockEntity(AllBlockEntityTypes.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 depots = Stream.of( + new BlockPos(2, 2, 1), + new BlockPos(1, 2, 1) + ).map(pos -> helper.getBlockEntity(AllBlockEntityTypes.DEPOT.get(), pos)).toList(); + List levers = List.of( + new BlockPos(2, 5, 0), + new BlockPos(1, 5, 0) + ); + levers.forEach(helper::pullLever); + FlapDisplayBlockEntity display = helper.getBlockEntity(AllBlockEntityTypes.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(); + + DepotBlockEntity 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 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); + }); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestMisc.java b/src/main/java/com/simibubi/create/gametest/tests/TestMisc.java new file mode 100644 index 000000000..cfd3209c9 --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestMisc.java @@ -0,0 +1,66 @@ +package com.simibubi.create.gametest.tests; + +import static com.simibubi.create.gametest.infrastructure.CreateGameTestHelper.FIFTEEN_SECONDS; + +import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.content.schematics.SchematicExport; +import com.simibubi.create.content.schematics.block.SchematicannonBlockEntity; +import com.simibubi.create.content.schematics.block.SchematicannonBlockEntity.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; + +@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); + SchematicannonBlockEntity cannon = helper.getBlockEntity(AllBlockEntityTypes.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); + }); + } +} diff --git a/src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java b/src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java new file mode 100644 index 000000000..ca2767c7d --- /dev/null +++ b/src/main/java/com/simibubi/create/gametest/tests/TestProcessing.java @@ -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 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)); + } +} diff --git a/src/main/resources/assets/create/lang/default/interface.json b/src/main/resources/assets/create/lang/default/interface.json index 6df56d4e4..db0568ce7 100644 --- a/src/main/resources/assets/create/lang/default/interface.json +++ b/src/main/resources/assets/create/lang/default/interface.json @@ -322,6 +322,8 @@ "create.schematicAndQuill.convert": "Save and Upload Immediately", "create.schematicAndQuill.fallbackName": "My Schematic", "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.error": "Schematic failed to Load - Check Game Logs", @@ -961,6 +963,7 @@ "create.display_source.redstone_power.progress_bar": "Progress Bar", "create.display_source.boiler.not_enough_space": "Not enough space ", "create.display_source.boiler.for_boiler_status": "for Boiler Status", + "create.display_source.computer_display_source": "From Computer", "create.display_target.line": "Line %1$s", "create.display_target.page": "Page %1$s", @@ -983,6 +986,9 @@ "create.super_glue.not_enough": "Not enough glue in inventory", "create.super_glue.success": "Applying Glue...", + "create.gui.attached_computer.controlled": "This device is being controlled by a computer", + "create.gui.attached_computer.hint": "To use device manually, disconnect all computers and modems", + "create.gui.config.overlay1": "Hi :)", "create.gui.config.overlay2": "This is a sample overlay", "create.gui.config.overlay3": "Click or drag with your mouse", @@ -1002,8 +1008,14 @@ "create.contraption.minecart_contraption_too_big": "This Cart Contraption seems too big to pick up", "create.contraption.minecart_contraption_illegal_pickup": "A mystical force is binding this Cart Contraption to the world", - + "enchantment.create.capacity.desc": "Increases Backtank air capacity.", - "enchantment.create.potato_recovery.desc": "Potato Cannon projectiles have a chance to be reused." + "enchantment.create.potato_recovery.desc": "Potato Cannon projectiles have a chance to be reused.", + + "create.bogey.style.updated_style": "Updated style", + "create.bogey.style.updated_style_and_size": "Updated style and size", + "create.bogey.style.no_other_sizes": "No other sizes", + "create.bogey.style.invalid": "Unnamed style", + "create.bogey.style.standard": "Standard" } diff --git a/src/main/resources/assets/create/textures/gui/computer.png b/src/main/resources/assets/create/textures/gui/computer.png new file mode 100644 index 000000000..293bedad7 Binary files /dev/null and b/src/main/resources/assets/create/textures/gui/computer.png differ diff --git a/src/main/resources/create.mixins.json b/src/main/resources/create.mixins.json index cd2f55d78..421b60afd 100644 --- a/src/main/resources/create.mixins.json +++ b/src/main/resources/create.mixins.json @@ -13,10 +13,13 @@ "EnchantmentMixin", "EntityMixin", "LavaSwimmingMixin", + "MainMixin", "MapItemSavedDataMixin", + "TestCommandMixin", "accessor.AbstractProjectileDispenseBehaviorAccessor", "accessor.DispenserBlockAccessor", "accessor.FallingBlockEntityAccessor", + "accessor.GameTestHelperAccessor", "accessor.LivingEntityAccessor", "accessor.NbtAccounterAccessor", "accessor.ServerLevelAccessor" diff --git a/src/main/resources/data/create/structures/gametest/contraptions/arrow_dispenser.nbt b/src/main/resources/data/create/structures/gametest/contraptions/arrow_dispenser.nbt new file mode 100644 index 000000000..62320b6d4 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/arrow_dispenser.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/crop_farming.nbt b/src/main/resources/data/create/structures/gametest/contraptions/crop_farming.nbt new file mode 100644 index 000000000..45e0887b8 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/crop_farming.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/mounted_fluid_drain.nbt b/src/main/resources/data/create/structures/gametest/contraptions/mounted_fluid_drain.nbt new file mode 100644 index 000000000..85f7e9666 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/mounted_fluid_drain.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt b/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt new file mode 100644 index 000000000..dead6de25 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/ploughing.nbt b/src/main/resources/data/create/structures/gametest/contraptions/ploughing.nbt new file mode 100644 index 000000000..64329baa3 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/ploughing.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/redstone_contacts.nbt b/src/main/resources/data/create/structures/gametest/contraptions/redstone_contacts.nbt new file mode 100644 index 000000000..60b165139 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/redstone_contacts.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/train_observer.nbt b/src/main/resources/data/create/structures/gametest/contraptions/train_observer.nbt new file mode 100644 index 000000000..84b841857 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/train_observer.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/3_pipe_combine.nbt b/src/main/resources/data/create/structures/gametest/fluids/3_pipe_combine.nbt new file mode 100644 index 000000000..b7a9d2b54 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/3_pipe_combine.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/3_pipe_split.nbt b/src/main/resources/data/create/structures/gametest/fluids/3_pipe_split.nbt new file mode 100644 index 000000000..88f648db6 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/3_pipe_split.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt b/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt new file mode 100644 index 000000000..f42e51fe6 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt new file mode 100644 index 000000000..fef3d5ff4 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt new file mode 100644 index 000000000..cfa909345 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/steam_engine.nbt b/src/main/resources/data/create/structures/gametest/fluids/steam_engine.nbt new file mode 100644 index 000000000..4edf9e7bd Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/steam_engine.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/andesite_tunnel_split.nbt b/src/main/resources/data/create/structures/gametest/items/andesite_tunnel_split.nbt new file mode 100644 index 000000000..ab4e2beaf Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/andesite_tunnel_split.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/arm_purgatory.nbt b/src/main/resources/data/create/structures/gametest/items/arm_purgatory.nbt new file mode 100644 index 000000000..fa1aa5fcf Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/arm_purgatory.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt b/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt new file mode 100644 index 000000000..40e3c9614 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/belt_coaster.nbt b/src/main/resources/data/create/structures/gametest/items/belt_coaster.nbt new file mode 100644 index 000000000..df3cb2642 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/belt_coaster.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_filtering.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_filtering.nbt new file mode 100644 index 000000000..c877ddd0a Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_filtering.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_prefer_nearest.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_prefer_nearest.nbt new file mode 100644 index 000000000..bdbbacd9e Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_prefer_nearest.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_round_robin.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_round_robin.nbt new file mode 100644 index 000000000..310e8b882 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_round_robin.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_single_split.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_single_split.nbt new file mode 100644 index 000000000..ec6eaf4e3 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_single_split.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_split.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_split.nbt new file mode 100644 index 000000000..059f61080 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_split.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/brass_tunnel_sync_input.nbt b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_sync_input.nbt new file mode 100644 index 000000000..284d4e143 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/brass_tunnel_sync_input.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt b/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt new file mode 100644 index 000000000..61719d373 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/depot_display.nbt b/src/main/resources/data/create/structures/gametest/items/depot_display.nbt new file mode 100644 index 000000000..f266d3e7e Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/depot_display.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt b/src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt new file mode 100644 index 000000000..65d268da8 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/storages.nbt b/src/main/resources/data/create/structures/gametest/items/storages.nbt new file mode 100644 index 000000000..f42e7a870 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/storages.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/vault_comparator_output.nbt b/src/main/resources/data/create/structures/gametest/items/vault_comparator_output.nbt new file mode 100644 index 000000000..51b8830d6 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/vault_comparator_output.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/misc/schematicannon.nbt b/src/main/resources/data/create/structures/gametest/misc/schematicannon.nbt new file mode 100644 index 000000000..f31d452c6 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/misc/schematicannon.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/misc/shearing.nbt b/src/main/resources/data/create/structures/gametest/misc/shearing.nbt new file mode 100644 index 000000000..2656d5a5b Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/misc/shearing.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/brass_mixing.nbt b/src/main/resources/data/create/structures/gametest/processing/brass_mixing.nbt new file mode 100644 index 000000000..a10e5e468 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/brass_mixing.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/brass_mixing_2.nbt b/src/main/resources/data/create/structures/gametest/processing/brass_mixing_2.nbt new file mode 100644 index 000000000..44de42dfe Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/brass_mixing_2.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/crushing_wheel_crafting.nbt b/src/main/resources/data/create/structures/gametest/processing/crushing_wheel_crafting.nbt new file mode 100644 index 000000000..099e093a9 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/crushing_wheel_crafting.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/iron_compacting.nbt b/src/main/resources/data/create/structures/gametest/processing/iron_compacting.nbt new file mode 100644 index 000000000..6a0ece131 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/iron_compacting.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/precision_mechanism_crafting.nbt b/src/main/resources/data/create/structures/gametest/processing/precision_mechanism_crafting.nbt new file mode 100644 index 000000000..3820415b9 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/precision_mechanism_crafting.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/sand_washing.nbt b/src/main/resources/data/create/structures/gametest/processing/sand_washing.nbt new file mode 100644 index 000000000..75ade7053 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/sand_washing.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/stone_cobble_sand_crushing.nbt b/src/main/resources/data/create/structures/gametest/processing/stone_cobble_sand_crushing.nbt new file mode 100644 index 000000000..d56e9423d Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/stone_cobble_sand_crushing.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/track_crafting.nbt b/src/main/resources/data/create/structures/gametest/processing/track_crafting.nbt new file mode 100644 index 000000000..6aa705296 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/track_crafting.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/water_filling_bottle.nbt b/src/main/resources/data/create/structures/gametest/processing/water_filling_bottle.nbt new file mode 100644 index 000000000..d2b527bdf Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/water_filling_bottle.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/processing/wheat_milling.nbt b/src/main/resources/data/create/structures/gametest/processing/wheat_milling.nbt new file mode 100644 index 000000000..3511d4d05 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/processing/wheat_milling.nbt differ diff --git a/wiki/Lua-Display-Link.md b/wiki/Lua-Display-Link.md new file mode 100644 index 000000000..d75969da1 --- /dev/null +++ b/wiki/Lua-Display-Link.md @@ -0,0 +1,88 @@ +| Method | Description | +|---------------------------------------|------------------------------------------------------| +| [`setCursorPos(x, y)`](#setCursorPos) | Sets the cursor position | +| [`getCursorPos()`](#getCursorPos) | Gets the current cursor position | +| [`getSize()`](#getSize) | Gets the display size of the connected target | +| [`isColor()`](#isColor) | Whether the connected display target supports color | +| [`isColour()`](#isColour) | Whether the connected display target supports colour | +| [`write(text)`](#writetext) | Writes text at the current cursor position | +| [`clearLine()`](#clearLine) | Clears the line at the current cursor position | +| [`clear()`](#clear) | Clears the whole display | +| [`update()`](#update) | Pushes an update to the display target | + +--- +### `setCursorPos(x, y)` +Sets the cursor position. Can be outside the bounds of the connected display. + +**Parameters** +- _x:_ `number` The cursor x position. +- _y:_ `number` The cursor y position. + +--- +### `getCursorPos()` +Gets the current cursor position. + +**Returns** +- `number` The cursor x position. +- `number` The cursor y position. + +--- +### `getSize()` +Gets the size of the connected display target. + +**Returns** +- `number` The width of the display. +- `number` The height of the display. + +--- +### `isColor()` +Checks whether the connected display target supports color. + +**Returns** +- `boolean` Whether the display supports color. + +--- +### `isColour()` +Checks whether the connected display target supports colour. + +**Returns** +- `boolean` Whether the display supports colour. + +--- +### `write(text)` +Writes text at the current cursor position, moving the cursor to the end of the text. +This only writes to an internal buffer. For the changes to show up on the display [`update()`](#update) must be used. +If the cursor is outside the bounds of the connected display, the text will not show up on the display. + +This will overwrite any text currently at the cursor position. + +**Parameters** +- _text:_ `string` The text to write. + +**See also** +- [`update()`](#update) To push the changes to the display target. + +--- +### `clearLine()` +Clears the line at the current cursor position. + +**See also** +- [`update()`](#update) To push the changes to the display target. + +--- +### `clear()` +Clears the whole display. + +**See also** +- [`update()`](#update) To push the changes to the display target. + +--- +### `update()` +Pushes any changes to the connected display target. +Any changes made are only made to an internal buffer. +For them to show up on the display they must be pushed to the display using this function. +This allows for this peripheral to be better multithreaded and for users to be able to change multiple lines at once by +using multiple [`write(text)`](#writetext) calls and then one [`update()`](#update) call. + +**See also** +- [`write(text)`](#writetext) To write text to the display target. diff --git a/wiki/Lua-Rotation-Speed-Controller.md b/wiki/Lua-Rotation-Speed-Controller.md new file mode 100644 index 000000000..ffd8ca785 --- /dev/null +++ b/wiki/Lua-Rotation-Speed-Controller.md @@ -0,0 +1,18 @@ +| Method | Description | +|-------------------------------------------------|----------------------------------------| +| [`setTargetSpeed(speed)`](#setTargetSpeedspeed) | Sets the target rotation speed | +| [`getTargetSpeed()`](#getTargetSpeed) | Gets the current target rotation speed | + +--- +### `setTargetSpeed(speed)` +Sets the rotation speed controller's target speed. + +**Parameters** +- _speed:_ `number` The target speed in RPM. Must be an integer within the range of [-256..256]. Values outside of this range will be clamped. + +--- +### `getTargetSpeed()` +Gets the rotation speed controller's current target speed. + +**Returns** +- `number` The current target rotation speed in RPM. diff --git a/wiki/Lua-Sequenced-Gearshift.md b/wiki/Lua-Sequenced-Gearshift.md new file mode 100644 index 000000000..9e7813322 --- /dev/null +++ b/wiki/Lua-Sequenced-Gearshift.md @@ -0,0 +1,28 @@ +| Method | Description | +|--------------------------------------------------------|--------------------------------------------------------------| +| [`rotate(angle, [modifier])`](#rotateangle-modifier) | Rotates shaft by a set angle | +| [`move(distance, [modifier])`](#movedistance-modifier) | Rotates shaft to move Piston/Pulley/Gantry by a set distance | +| [`isRunning()`](#isRunning) | Whether the gearshift is currently spinning | + +--- +### `rotate(angle, [modifier])` +Rotates connected components by a set angle. + +**Parameters** +- _angle:_ `number` Angle to rotate the shaft by in degrees. Must be a positive integer. To do backwards rotation, set _modifier_ to a negative value. +- _modifier?:_ `number = 1` Speed modifier which can be used to reverse rotation. Must be an integer within the range of [-2..2]. Values out of this range are ignored and the default of 1 is used. + +--- +### `move(distance, [modifier])` +Rotates connected components to move connected piston, pulley or gantry contractions by a set distance. + +**Parameters** +- _distance:_ `number` Distance to move connected piston, pulley or gantry contraptions by. Must be a positive integer. To do backwards movement, set _modifier_ to a negative value. +- _modifier?:_ `number = 1` Speed modifier which can be used to reverse direction. Must be an integer within the range of [-2..2]. Values out of this range are ignored and the default of 1 is used. + +--- +### `isRunning()` +Checks if the sequenced gearshift is currently spinning. + +**Returns** +- `boolean` Whether the sequenced gearshift is currently spinning. diff --git a/wiki/Lua-Speedometer.md b/wiki/Lua-Speedometer.md new file mode 100644 index 000000000..868714e0d --- /dev/null +++ b/wiki/Lua-Speedometer.md @@ -0,0 +1,10 @@ +| Method | Description | +|---------------------------|---------------------------------| +| [`getSpeed()`](#getSpeed) | Gets the current rotation speed | + +--- +### `getSpeed()` +Gets the current rotation speed of the attached components. + +**Returns** +- `number` The current rotation speed in RPM. diff --git a/wiki/Lua-Stressometer.md b/wiki/Lua-Stressometer.md new file mode 100644 index 000000000..0e609f5d0 --- /dev/null +++ b/wiki/Lua-Stressometer.md @@ -0,0 +1,18 @@ +| Method | Description | +|---------------------------------------------|--------------------------------| +| [`getStress()`](#getStress) | Gets the current stress level | +| [`getStressCapacity()`](#getStressCapacity) | Gets the total stress capacity | + +--- +### `getStress()` +Gets the connected network's current stress level. + +**Returns** +- `number` The current stress level in SU. + +--- +### `getStressCapacity()` +Gets the connected network's total stress capacity. + +**Returns** +- `number` The total stress capacity in SU. diff --git a/wiki/Lua-Train-Schedule.md b/wiki/Lua-Train-Schedule.md new file mode 100644 index 000000000..e221985ae --- /dev/null +++ b/wiki/Lua-Train-Schedule.md @@ -0,0 +1,195 @@ +Train schedules are represented by a table in Lua. The table contains a list of entries where each entry has a single instruction and multiple conditions. +Each instruction and condition has a `data` table that stores specific data about the instruction or condition. + +```lua +schedule = { + cyclic = true, -- Does the schedule repeat itself after the end has been reached? + entries = { -- List of entries, each entry contains a single instruction and multiple conditions. + { + instruction = { + id = "create:destination", -- The different instructions are described below. + data = { -- Data that is stored about the instruction. Different for each instruction type. + text = "Station 1", + }, + }, + conditions = { -- List of lists of conditions. The outer list is the "OR" list + { -- and the inner lists are "AND" lists. + { + id = "create:delay", -- The different conditions are described below. + data = { -- Data that is stored about the condition. Different for each condition type. + value = 5, + time_unit = 1, + }, + }, + { + id = "create:powered", + data = {}, + }, + }, + { + { + id = "create:time_of_day", + data = { + rotation = 0, + hour = 14, + minute = 0, + }, + }, + }, + }, + }, + }, +} +``` +--- +## Instructions +| ID | Description | +|----------------------------------------------|---------------------------------| +| [`"create:destination"`](#createdestination) | Move to a certain train station | +| [`"create:rename"`](#createrename) | Change the schedule title | +| [`"create:throttle"`](#createthrottle) | Change the train's throttle | + +--- +### `"create:destination"` +Moves the train to the chosen train station. This instruction must have at least one condition. + +**Data** +- _text:_ `string` The name of the station to travel to. Can include * as a wildcard. + +--- +### `"create:rename"` +Renames the schedule. This name shows up on display link targets. This instruction cannot have conditions. + +**Data** +- _text:_ `string` The name to rename the schedule to. + +--- +### `"create:throttle"` +Changes the throttle of the train. This instruction cannot have conditions. + +**Data** +- _value:_ `number` The throttle to set the train to. Must be an integer within the range of [5..100]. + +--- +## Conditions +Conditions are stored in a list of lists of conditions. The inner lists contain conditions that get `AND`'ed together. They must all be met for that group to be true. +The outer list contains the `AND`'ed groups of conditions that get `OR`'ed together. Only one of the groups needs to be true for the schedule to move onto the next instruction. + +| ID | Description | +|-----------------------------------------------------|-----------------------------------------------------| +| [`"create:delay"`](#createdelay) | Wait for a certain delay | +| [`"create:time_of_day"`](#createtimeofday) | Wait for a specific time of day | +| [`"create:fluid_threshold"`](#createfluidthreshold) | Wait for a certain amount of fluid to be on board | +| [`"create:item_threshold"`](#createitemthreshold) | Wait for a certain amount of items to be on board | +| [`"create:redstone_link"`](#createredstonelink) | Wait for a redstone link to be powered | +| [`"create:player_count"`](#createplayercount) | Wait for a certain amount of players to be on board | +| [`"create:idle"`](#createidle) | Wait for cargo loading inactivity | +| [`"create:unloaded"`](#createunloaded) | Wait for the current chunk to be unloaded | +| [`"create:powered"`](#createpowered) | Wait for the station to be powered | + +--- +### `"create:delay"` +Wait for a set delay. Can be measured in ticks, seconds or minutes. + +**Data** +- _value:_ `number` The amount of time to wait for. +- _time_unit:_ `number` The unit of time. 0 for ticks, 1 for seconds and 2 for minutes. + +--- +### `"create:time_of_day"` +Wait for a time of day, then repeat at a specified interval. + +**Data** +- _hour:_ `number` The hour of the day to wait for in a 24-hour format. Must be an integer within the range of [0..23]. +- _minute:_ `number` The minute of the hour to wait for. Must be an integer within the range of [0..59]. +- _rotation:_ `number` The interval to repeat at after the time of day has been met. Check the rotation table below for valid values. Must be an integer within the range of [0..9]. + +**Rotation** + +| Rotation | Time Interval | +|----------|------------------| +| 0 | Every Day | +| 1 | Every 12 Hours | +| 2 | Every 6 Hours | +| 3 | Every 4 Hours | +| 4 | Every 3 Hours | +| 5 | Every 2 Hours | +| 6 | Every Hour | +| 7 | Every 45 Minutes | +| 8 | Every 30 Minutes | +| 9 | Every 15 Minutes | + +--- +### `"create:fluid_threshold"` +Wait for a certain amount of a specific fluid to be loaded onto the train. + +**Data** +- _bucket:_ `table` The bucket item of the fluid. +- _threshold:_ `number` The threshold in number of buckets of fluid. Must be a positive integer. +- _operator:_ `number` Whether the condition should wait for the train to be loaded above the threshold, below the threshold or exactly at the threshold. 0 for greater than, 1 for less than, 2 for equal to. +- _measure:_ `number` The unit to measure the fluid in. This condition supports buckets as the only unit. Set to 0. + +**See also** +- [Items](#items) How items are represented in Lua. + +--- +### `"create:item_threshold"` +Wait for a certain amount of a specific item to be loaded onto the train. + +**Data** +- _item:_ `table` The item. +- _threshold:_ `number` The threshold of items. Must be a positive integer. +- _operator:_ `number` Whether the condition should wait for the train to be loaded above the threshold, below the threshold or exactly at the threshold. 0 for greater than, 1 for less than, 2 for equal to. +- _measure:_ `number` The unit to measure the items in. 0 for items. 1 for stacks of items. + +**See also** +- [Items](#items) How items are represented in Lua. + +--- +### `"create:redstone_link"` +Wait for a redstone link to be powered. + +**Data** +- _frequency:_ `{ table... }` A list of the two items making up the redstone link frequency. +- _inverted:_ `number` Whether the redstone link should be powered or not to meet the condition. 0 for powered. 1 for not powered. + +**See also** +- [Items](#items) How items are represented in Lua. + +--- +### `"create:player_count"` +Wait for a certain amount of players to be seated on the train. + +**Data** +- _count:_ `number` The number of players to be seated on the train. Must be a positive integer. +- _exact:_ `number` Whether the seated player count has to be exact to meet the condition. 0 for the exact amount of players seated, 1 for a greater than or equal amount of seated players. + +--- +### `"create:idle"` +Wait for a period of inactivity in loading or unloading the train. Can be measured in ticks, seconds or minutes. + +**Data** +- _value:_ `number` The amount of idle time to meet the condition. Must be a positive integer. +- _time_unit:_ `number` The unit of time. 0 for ticks, 1 for seconds and 2 for minutes. + +--- +### `"create:unloaded"` +Wait for the chunk the train is in to be unloaded. + +--- +### `"create:powered"` +Wait for the station to be powered with a redstone signal. + +--- +## Items +In Lua, items are represented with an ID and a count. + +```lua +item = { + id = "minecraft:stone", + count = 1, +} +``` + +- _id:_ `string` The ID of the item. +- _count:_ `number` The amount of items in the stack. For the purposes of working with train schedules the count should always be 1. Must be an integer. diff --git a/wiki/Lua-Train-Station.md b/wiki/Lua-Train-Station.md new file mode 100644 index 000000000..c8a39ae45 --- /dev/null +++ b/wiki/Lua-Train-Station.md @@ -0,0 +1,179 @@ +| Method | Description | +|-----------------------------------------------------------------|----------------------------------------------------| +| [`assemble()`](#assemble) | Assembles a new train at the station | +| [`disassemble()`](#disassemble) | Disassembles the currently present train | +| [`setAssemblyMode(assemblyMode)`](#setAssemblyModeassemblyMode) | Sets the station's assembly mode | +| [`isInAssemblyMode()`](#isInAssemblyMode) | Whether the station is in assembly mode | +| [`getStationName()`](#getStationName) | Gets the station's current name | +| [`setStationName(name)`](#setStationNamename) | Sets the station's name | +| [`isTrainPresent()`](#isTrainPresent) | Whether a train is present at the station | +| [`isTrainImminent()`](#isTrainImminent) | Whether a train is imminent to the station | +| [`isTrainEnroute()`](#isTrainEnroute) | Whether a train is enroute to the station | +| [`getTrainName()`](#getTrainName) | Gets the currently present train's name | +| [`setTrainName(name)`](#setTrainNamename) | Sets the currently present train's name | +| [`hasSchedule()`](#hasSchedule) | Whether the currently present train has a schedule | +| [`getSchedule()`](#getSchedule) | Gets the currently present train's schedule | +| [`setSchedule(schedule)`](#setScheduleschedule) | Sets the currently present train's schedule | + +--- +### `assemble()` +Assembles a new train at the station. The station must be in assembly mode prior to calling this function. +This function also causes the station to exit assembly mode after the train is done assembing. + +**Throws** +- If the station is not in assembly mode. +- If the station is not connected to a track. +- If the train failed to assemble. +- If the station failed to exit assembly mode. + +**See also** +- [`setAssemblyMode(assemblyMode)`](#setAssemblyModeassemblyMode) To set the assembly mode of the station. + +--- +### `disassemble()` +Disassembles the station's currently present train. The station must not be in assembly mode. + +**Throws** +- If the station is in assembly mode. +- If the station is not connected to a track. +- If there is currently no train present at the station. +- If the train failed to disassemble. + +**See also** +- [`setAssemblyMode(assemblyMode)`](#setAssemblyModeassemblyMode) To set the assembly mode of the station. + +--- +### `setAssemblyMode(assemblyMode)` +Sets the station's assembly mode. + +**Parameters** +- _assemblyMode:_ `boolean` Whether the station should be in assembly mode. + +**Throws** +- If the station fails to enter or exit assembly mode. +- If the station is not connected to a track. + +--- +### `isInAssemblyMode()` +Checks whether the station is in assembly mode. + +**Returns** +- `boolean` Whether the station is in assembly mode. + +--- +### `getStationName()` +Gets the station's current name. + +**Returns** +- `string` The station's current name. + +**Throws** +- If the station is not connected to a track. + +--- +### `setStationName(name)` +Sets the station's name. + +**Parameters** +- _name:_ `string` What to set the station's name to. + +**Throws** +- If the station name fails to be set. +- If the station is not connected to a track. + +--- +### `isTrainPresent()` +Checks whether a train is currently present at the station. + +**Returns** +- `boolean` Whether a train is present at the station. + +**Throws** +- If the station is not connected to a track. + +--- +### `isTrainImminent()` +Checks whether a train is imminently arriving at the station. +Imminent is defined as being within 30 blocks of the station. +This will not be true if the train has arrived and stopped at the station. + +**Returns** +- `boolean` Whether a train is imminent to the station. + +**Throws** +- If the station is not connected to a track. + +**See also** +- [`isTrainPresent()`](#isTrainPresent) To check if a train is present at the station. + +--- +### `isTrainEnroute()` +Checks whether a train is enroute and navigating to the station. + +**Returns** +- `boolean` Whether a train is enroute to the station. + +**Throws** +- If the station is not connected to a track. + +--- +### `getTrainName()` +Gets the currently present train's name. + +**Returns** +- `string` The currently present train's name. + +**Throws** +- If the station is not connected to a track. +- If there is currently no train present at the station. + +--- +### `setTrainName(name)` +Sets the currently present train's name. + +**Parameters** +- _name:_ `string` What to set the currently present train's name to. + +**Throws** +- If the station is not connected to a track. +- If there is currently no train present at the station. + +--- +### `hasSchedule()` +Checks whether the currently present train has a schedule. + +**Returns** +- `boolean` Whether the currently present train has a schedule. + +**Throws** +- If the station is not connected to a track. +- If there is currently no train present at the station. + +--- +### `getSchedule()` +Gets the currently present train's schedule. + +**Returns** +- `table` The train's schedule + +**Throws** +- If the station is not connected to a track. +- If there is currently no train present at the station. +- If the present train doesn't have a schedule. + +**See also** +- [Lua Train Schedules](#Lua-Train-Schedules) How train schedules are represented in Lua. + +--- +### `setSchedule(schedule)` +Sets the currently present train's schedule. This will overwrite the currently set schedule. + +**Parameters** +- _schedule:_ `table` The schedule to set the present train to. + +**Throws** +- If the station is not connected to a track. +- If there is currently no train present at the station. + +**See also** +- [Lua Train Schedules](#Lua-Train-Schedules) How train schedules are represented in Lua. diff --git a/wiki/README.md b/wiki/README.md new file mode 100644 index 000000000..cf91b97ae --- /dev/null +++ b/wiki/README.md @@ -0,0 +1,2 @@ +Just before this PR is about to be merged this /wiki folder will be removed from the PR and the pages will be added to +the wiki section of the Create GitHub under API Reference