diff --git a/.gitignore b/.gitignore index 5baa99ba..82e424a6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ logs/ *.dot src/generated/ + +raw-assets/ diff --git a/build.gradle b/build.gradle index e06df6b7..af9be32f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,14 @@ buildscript { repositories { maven { url = 'https://maven.minecraftforge.net' } + maven { url = 'https://repo.spongepowered.org/repository/maven-public' } mavenCentral() mavenLocal() } dependencies { classpath "net.minecraftforge.gradle:ForgeGradle:5.1.+" + classpath "org.spongepowered:mixingradle:${mixingradle_version}" } } @@ -17,6 +19,7 @@ plugins { } apply plugin: "net.minecraftforge.gradle" +apply plugin: 'org.spongepowered.mixin' def semver = { -> try { @@ -130,6 +133,7 @@ minecraft { property 'mixin.env.remapRefMap', 'true' property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg" + arg '-mixin.config=compactmachines.mixin.json' args '--username', 'Dev' args '--width', 1920 args '--height', 1080 @@ -157,6 +161,8 @@ minecraft { property 'mixin.env.remapRefMap', 'true' property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg" + arg '-mixin.config=compactmachines.mixin.json' + mods { compactmachines { source sourceSets.tunnels @@ -215,7 +221,7 @@ minecraft { property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg" forceExit false - + mods { compactmachines { source sourceSets.tunnels @@ -246,7 +252,11 @@ repositories { // TheOneProbe maven { - name 'tterrag maven' + url "https://maven.k-4u.nl" + } + + maven { + name "tterrag maven" url "https://maven.tterrag.com/" } } @@ -263,36 +273,45 @@ dependencies { // The One Probe implementation(fg.deobf("curse.maven:theoneprobe-245211:3671753")) -// compileOnly fg.deobf("mcjty.theoneprobe:TheOneProbe-1.16:${top_version}:api") -// runtimeOnly fg.deobf("mcjty.theoneprobe:TheOneProbe-1.16:${top_version}") - - // Nicephore - Screenshots and Stuff - runtimeOnly(fg.deobf("curse.maven:nicephore-401014:3651214")) - - // Shut up Experimental Settings - so we don't have to deal with that CONSTANTLY - runtimeOnly(fg.deobf("curse.maven:shutupexperimental-407174:3544525")) - -// // Testing Mods - Trash Cans, Pipez, Create, Refined Pipes, Pretty Pipes, Refined Storage -// runtimeOnly(fg.deobf("curse.maven:SuperMartijn642-454372:3649270")) -// runtimeOnly(fg.deobf("curse.maven:trashcans-394535:3597654")) -// runtimeOnly(fg.deobf("curse.maven:pipez-443900:3569514")) -// runtimeOnly(fg.deobf("curse.maven:flywheel-486392:3687357")) -// runtimeOnly(fg.deobf("curse.maven:create-328085:3687358")) -// runtimeOnly(fg.deobf("curse.maven:refinedpipes-370696:3570151")) -// runtimeOnly(fg.deobf("curse.maven:prettypipes-376737:3573145")) -// runtimeOnly(fg.deobf("curse.maven:refinedstorage-243076:3623324")) -// -// // Scalable Cat's Force, BdLib, Advanced Generators -// runtimeOnly(fg.deobf("curse.maven:scalable-320926:3634756")) -// runtimeOnly(fg.deobf("curse.maven:bdlib-70496:3663149")) -// runtimeOnly(fg.deobf("curse.maven:advgen-223622:3665335")) -// -// // Immersive Eng - 7.1.0-145 (Dec 31) -// runtimeOnly(fg.deobf("curse.maven:immersiveeng-231951:3587149")) - - // Mekanism + Mek Generators - Tunnel testing -// runtimeOnly(fg.deobf("curse.maven:mekanism-268560:3206392")) -// runtimeOnly(fg.deobf("curse.maven:mekanismgenerators-268566:3206395")) + + if (!System.getenv().containsKey("CI") && include_test_mods) { + // Nicephore - Screenshots and Stuff + runtimeOnly(fg.deobf("curse.maven:nicephore-401014:3741832")) + + // // Testing Mods - Trash Cans, Pipez, Create, Refined Pipes, Pretty Pipes, Refined Storage + runtimeOnly(fg.deobf("curse.maven:SuperMartijn642-454372:3649270")) + runtimeOnly(fg.deobf("curse.maven:trashcans-394535:3597654")) + runtimeOnly(fg.deobf("curse.maven:pipez-443900:3760255")) + + runtimeOnly(fg.deobf("curse.maven:flywheel-486392:3737402")) + runtimeOnly(fg.deobf("curse.maven:create-328085:3737418")) + + // runtimeOnly(fg.deobf("curse.maven:refinedpipes-370696:3570151")) + // runtimeOnly(fg.deobf("curse.maven:prettypipes-376737:3573145")) + // runtimeOnly(fg.deobf("curse.maven:refinedstorage-243076:3623324")) + // + // // Scalable Cat's Force, BdLib, Advanced Generators + // runtimeOnly(fg.deobf("curse.maven:scalable-320926:3634756")) + // runtimeOnly(fg.deobf("curse.maven:bdlib-70496:3663149")) + // runtimeOnly(fg.deobf("curse.maven:advgen-223622:3665335")) + // + // // Immersive Eng - 7.1.0-145 (Dec 31) + // runtimeOnly(fg.deobf("curse.maven:immersiveeng-231951:3587149")) + + // FTB Chunks + runtimeOnly(fg.deobf("curse.maven:architectury-forge-419699:3781711")) + runtimeOnly(fg.deobf("curse.maven:ftb-teams-404468:3725501")) + runtimeOnly(fg.deobf("curse.maven:ftblib-404465:3725485")) + runtimeOnly(fg.deobf("curse.maven:ftbchunks-314906:3780113")) + + // Mekanism + Mek Generators - Tunnel testing + runtimeOnly(fg.deobf("curse.maven:mekanism-268560:3743835")) + runtimeOnly(fg.deobf("curse.maven:mekanismgenerators-268566:3743837")) + } +} + +mixin { + add sourceSets.main, "${mod_id}.refmap.json" } processResources { @@ -323,7 +342,8 @@ jar { "Implementation-Title" : project.name, "Implementation-Version" : archiveVersion, "Implementation-Vendor" : "", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'MixinConfigs': "${mod_id}.mixin.json" ]) } } diff --git a/gradle.properties b/gradle.properties index a4be0278..be7d233e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,9 +10,15 @@ mod_id=compactmachines # Dependencies and Libs jei_mc_version=1.18.2 -jei_version=9.5.2.135 +jei_version=9.7.0.194 #top_version=1.16-3.1.4-22 # Curseforge cf_project=224218 cf_release_type=release + +# Plugins that shall not be named +mixin_version = 0.8.5 +mixingradle_version = 0.7-SNAPSHOT + +include_test_mods = true \ No newline at end of file diff --git a/src/api/java/dev/compactmods/machines/api/core/Advancements.java b/src/api/java/dev/compactmods/machines/api/core/Advancements.java index c2517fa3..bc8c231a 100644 --- a/src/api/java/dev/compactmods/machines/api/core/Advancements.java +++ b/src/api/java/dev/compactmods/machines/api/core/Advancements.java @@ -15,4 +15,6 @@ public class Advancements { public static final ResourceLocation CLAIMED_LARGE_MACHINE = new ResourceLocation(Constants.MOD_ID, "claimed_machine_large"); public static final ResourceLocation CLAIMED_GIANT_MACHINE = new ResourceLocation(Constants.MOD_ID, "claimed_machine_giant"); public static final ResourceLocation CLAIMED_MAX_MACHINE = new ResourceLocation(Constants.MOD_ID, "claimed_machine_max"); + + public static final ResourceLocation RECURSIVE_ROOMS = new ResourceLocation(Constants.MOD_ID, "recursion"); } diff --git a/src/api/java/dev/compactmods/machines/api/core/CMCommands.java b/src/api/java/dev/compactmods/machines/api/core/CMCommands.java index e2656512..bd1a920f 100644 --- a/src/api/java/dev/compactmods/machines/api/core/CMCommands.java +++ b/src/api/java/dev/compactmods/machines/api/core/CMCommands.java @@ -7,26 +7,29 @@ public class CMCommands { public static final ResourceLocation LEVEL_REGISTERED = new ResourceLocation(MOD_ID, "level_registered"); public static final ResourceLocation LEVEL_NOT_FOUND = new ResourceLocation(MOD_ID, "level_not_found"); - public static final ResourceLocation ROOM_DATA_NOT_FOUND = new ResourceLocation(MOD_ID, "room_data_not_found"); /** * Used for displaying the number of registered machines via summary commands. */ - public static final ResourceLocation MACHINE_REG_COUNT = new ResourceLocation(MOD_ID, "machine_reg_count"); + public static final ResourceLocation MACHINE_REG_DIM = new ResourceLocation(MOD_ID, "summary.machines.dimension"); + public static final ResourceLocation MACHINE_REG_TOTAL = new ResourceLocation(MOD_ID, "summary.machines.total"); /** * Used for displaying the number of registered rooms via summary commands. */ public static final ResourceLocation ROOM_REG_COUNT = new ResourceLocation(MOD_ID, "room_reg_count"); public static final ResourceLocation NOT_A_MACHINE_BLOCK = new ResourceLocation(MOD_ID, "not_a_machine_block"); + + /** + * Shows a machine is not bound. Takes in a single param, the machine position in world. + */ public static final ResourceLocation MACHINE_NOT_BOUND = new ResourceLocation(MOD_ID, "machine_not_bound"); - public static final ResourceLocation REBIND_HAS_TUNNEL_CONNECTED = new ResourceLocation(MOD_ID, "rebind_tunnel_connected"); - public static final ResourceLocation CMD_ROOM_NOT_REGISTERED = new ResourceLocation(MOD_ID, "room_not_registered"); - public static final ResourceLocation CMD_MACHINE_NOT_REGISTERED = new ResourceLocation(MOD_ID, "machine_not_registered"); - public static final ResourceLocation CMD_BAD_STATE = new ResourceLocation(MOD_ID, "incorrect_machine_state"); - public static final ResourceLocation NO_PLAYER_HISTORY = new ResourceLocation(MOD_ID, "no_player_history"); public static final ResourceLocation WRONG_DIMENSION = new ResourceLocation(MOD_ID, "not_in_compact_dimension"); public static final ResourceLocation NOT_IN_COMPACT_DIMENSION = new ResourceLocation(MOD_ID, "not_in_compact_dim"); public static final ResourceLocation FAILED_CMD_FILE_ERROR = new ResourceLocation(MOD_ID, "failed_command_file_error"); + + public static final ResourceLocation CANNOT_GIVE_MACHINE = new ResourceLocation(MOD_ID, "cannot_give_machine_item"); + public static final ResourceLocation MACHINE_GIVEN = new ResourceLocation(MOD_ID, "machine_given_successfully"); + public static final ResourceLocation NO_REBIND_TUNNEL_PRESENT = new ResourceLocation(MOD_ID, "cannot_rebind_tunnel_present"); } diff --git a/src/api/java/dev/compactmods/machines/api/core/Tooltips.java b/src/api/java/dev/compactmods/machines/api/core/Tooltips.java index a9588a1f..e92ab282 100644 --- a/src/api/java/dev/compactmods/machines/api/core/Tooltips.java +++ b/src/api/java/dev/compactmods/machines/api/core/Tooltips.java @@ -7,6 +7,7 @@ public abstract class Tooltips { public static final ResourceLocation UNKNOWN_PLAYER_NAME = new ResourceLocation(Constants.MOD_ID, "unknown_player"); public static final ResourceLocation TUNNEL_TYPE = new ResourceLocation(Constants.MOD_ID, "tunnel_type"); public static final ResourceLocation UNKNOWN_TUNNEL_TYPE = new ResourceLocation(Constants.MOD_ID, "unknown_tunnel_type"); + public static final ResourceLocation ROOM_NAME = new ResourceLocation(Constants.MOD_ID, "room_name"); public static abstract class Machines { public static final ResourceLocation ID = new ResourceLocation(Constants.MOD_ID, "machine.id"); diff --git a/src/api/java/dev/compactmods/machines/api/location/IDimensionalPosition.java b/src/api/java/dev/compactmods/machines/api/location/IDimensionalPosition.java index 159c2aca..db7b4e3c 100644 --- a/src/api/java/dev/compactmods/machines/api/location/IDimensionalPosition.java +++ b/src/api/java/dev/compactmods/machines/api/location/IDimensionalPosition.java @@ -13,8 +13,6 @@ public interface IDimensionalPosition { - IDimensionalPosition relative(Direction direction, float amount); - BlockPos getBlockPosition(); Vec3 getExactPosition(); diff --git a/src/api/java/dev/compactmods/machines/api/room/MachineRoomConnections.java b/src/api/java/dev/compactmods/machines/api/room/MachineRoomConnections.java index 59d87201..86755f55 100644 --- a/src/api/java/dev/compactmods/machines/api/room/MachineRoomConnections.java +++ b/src/api/java/dev/compactmods/machines/api/room/MachineRoomConnections.java @@ -1,6 +1,10 @@ package dev.compactmods.machines.api.room; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; import javax.annotation.Nonnull; import java.util.Collection; @@ -8,18 +12,18 @@ public interface MachineRoomConnections { @Nonnull - Optional getConnectedRoom(int machineId); + Optional getConnectedRoom(ResourceKey machineLevel, BlockPos machinePos); @Nonnull Collection getMachinesFor(ChunkPos chunkPos); - void registerMachine(int machine); + void registerMachine(ResourceKey machineLevel, BlockPos machinePos); void registerRoom(ChunkPos roomChunk); - void connectMachineToRoom(int machine, ChunkPos room); - void changeMachineLink(int machine, ChunkPos newRoom); + void connectMachineToRoom(ResourceKey machineLevel, BlockPos machinePos, ChunkPos room); + void changeMachineLink(ResourceKey machineLevel, BlockPos machinePos, ChunkPos newRoom); - void disconnect(int machine); + void disconnect(ResourceKey level, BlockPos pos); void unregisterRoom(ChunkPos room); } diff --git a/src/api/java/dev/compactmods/machines/api/room/history/IRoomHistoryItem.java b/src/api/java/dev/compactmods/machines/api/room/history/IRoomHistoryItem.java index e8e45b26..57b536ae 100644 --- a/src/api/java/dev/compactmods/machines/api/room/history/IRoomHistoryItem.java +++ b/src/api/java/dev/compactmods/machines/api/room/history/IRoomHistoryItem.java @@ -1,10 +1,11 @@ package dev.compactmods.machines.api.room.history; +import dev.compactmods.machines.api.location.IDimensionalBlockPosition; import dev.compactmods.machines.api.location.IDimensionalPosition; public interface IRoomHistoryItem { IDimensionalPosition getEntryLocation(); - int getMachine(); + IDimensionalBlockPosition getMachine(); } diff --git a/src/datagen/java/dev/compactmods/machines/datagen/AdvancementGenerator.java b/src/datagen/java/dev/compactmods/machines/datagen/AdvancementGenerator.java index de7eacb2..cb78dd35 100644 --- a/src/datagen/java/dev/compactmods/machines/datagen/AdvancementGenerator.java +++ b/src/datagen/java/dev/compactmods/machines/datagen/AdvancementGenerator.java @@ -4,7 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.advancement.trigger.ClaimedMachineTrigger; +import dev.compactmods.machines.advancement.trigger.BasicPlayerAdvTrigger; import dev.compactmods.machines.advancement.trigger.HowDidYouGetHereTrigger; import dev.compactmods.machines.api.core.Advancements; import dev.compactmods.machines.core.Registration; @@ -87,6 +87,17 @@ private void generateAdvancements(Consumer consumer) { .build()) .save(consumer, Advancements.HOW_DID_YOU_GET_HERE.toString()); + Advancement.Builder.advancement() + .parent(root) + .addCriterion("recursion", BasicPlayerAdvTrigger.Instance.create(Advancements.RECURSIVE_ROOMS)) + .display(new DisplayBuilder() + .frame(FrameType.CHALLENGE) + .item(new ItemStack(Registration.PERSONAL_SHRINKING_DEVICE.get())) + .id(Advancements.RECURSIVE_ROOMS) + .toast(false).hidden(true) + .build()) + .save(consumer, Advancements.RECURSIVE_ROOMS.toString()); + Advancement.Builder.advancement() .parent(root) .addCriterion("obtained_wall", InventoryChangeTrigger.TriggerInstance.hasItems(Registration.BLOCK_BREAKABLE_WALL.get())) @@ -118,7 +129,7 @@ private void generateAdvancements(Consumer consumer) { private void machineAdvancement(Consumer consumer, Advancement root, ResourceLocation advancement, Supplier item) { Advancement.Builder.advancement() .parent(root) - .addCriterion("claimed_machine", ClaimedMachineTrigger.Instance.create(advancement)) + .addCriterion("claimed_machine", BasicPlayerAdvTrigger.Instance.create(advancement)) .display(new DisplayBuilder() .frame(FrameType.TASK) .item(new ItemStack(item.get())) diff --git a/src/datagen/java/dev/compactmods/machines/datagen/BlockLootGenerator.java b/src/datagen/java/dev/compactmods/machines/datagen/BlockLootGenerator.java index f9afa297..92508856 100644 --- a/src/datagen/java/dev/compactmods/machines/datagen/BlockLootGenerator.java +++ b/src/datagen/java/dev/compactmods/machines/datagen/BlockLootGenerator.java @@ -4,6 +4,7 @@ import com.mojang.datafixers.util.Pair; import dev.compactmods.machines.api.machine.MachineNbt; import dev.compactmods.machines.core.Registration; +import dev.compactmods.machines.room.data.CopyRoomBindingFunction; import net.minecraft.data.DataGenerator; import net.minecraft.data.loot.BlockLoot; import net.minecraft.data.loot.LootTableProvider; @@ -66,16 +67,12 @@ protected void addTables() { registerCompactMachineBlockDrops(Registration.MACHINE_BLOCK_MAXIMUM, Registration.MACHINE_BLOCK_ITEM_MAXIMUM); } - private final LootItemFunction.Builder CopyOwnerAndReferenceFunction = CopyNbtFunction.copyData(ContextNbtProvider.BLOCK_ENTITY) - .copy(MachineNbt.OWNER, MachineNbt.OWNER) - .copy(MachineNbt.ID, MachineNbt.ID); - private void registerCompactMachineBlockDrops(RegistryObject block, RegistryObject item) { LootPool.Builder builder = LootPool.lootPool() .name(block.get().getRegistryName().toString()) .setRolls(ConstantValue.exactly(1)) .when(ExplosionCondition.survivesExplosion()) - .apply(CopyOwnerAndReferenceFunction) + .apply(CopyRoomBindingFunction.binding()) .add(LootItem.lootTableItem(item.get())); this.add(block.get(), LootTable.lootTable().withPool(builder)); diff --git a/src/datagen/java/dev/compactmods/machines/datagen/LevelBiomeGenerator.java b/src/datagen/java/dev/compactmods/machines/datagen/LevelBiomeGenerator.java index 006eaa65..c35e4349 100644 --- a/src/datagen/java/dev/compactmods/machines/datagen/LevelBiomeGenerator.java +++ b/src/datagen/java/dev/compactmods/machines/datagen/LevelBiomeGenerator.java @@ -105,7 +105,6 @@ private void writeDimensions(HashMap biomes, HashMap consumer) { final DimensionType dim = new DimensionTypeBuilder() - .ambientLight(15.0f) .bedWorks(false) .respawnAnchorWorks(false) .fixedTime(18000L) diff --git a/src/datagen/java/dev/compactmods/machines/datagen/RecipeGenerator.java b/src/datagen/java/dev/compactmods/machines/datagen/RecipeGenerator.java index b188b0cc..e38b8530 100644 --- a/src/datagen/java/dev/compactmods/machines/datagen/RecipeGenerator.java +++ b/src/datagen/java/dev/compactmods/machines/datagen/RecipeGenerator.java @@ -8,14 +8,15 @@ import net.minecraft.data.recipes.FinishedRecipe; import net.minecraft.data.recipes.RecipeProvider; import net.minecraft.data.recipes.ShapedRecipeBuilder; -import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; import net.minecraftforge.common.Tags; import net.minecraftforge.common.crafting.ConditionalRecipe; +import org.jetbrains.annotations.NotNull; import java.util.Objects; import java.util.function.Consumer; @@ -26,13 +27,13 @@ public RecipeGenerator(DataGenerator generatorIn) { } @Override - protected void buildCraftingRecipes(Consumer consumer) { - ShapedRecipeBuilder.shaped(Registration.ITEM_BREAKABLE_WALL.get(), 16) - .pattern(" R ") - .pattern(" I ") - .define('R', Tags.Items.DUSTS_REDSTONE) - .define('I', Tags.Items.STORAGE_BLOCKS_IRON) - .unlockedBy("picked_up_iron", RecipeProvider.has(Tags.Items.STORAGE_BLOCKS_IRON)) + protected void buildCraftingRecipes(@NotNull Consumer consumer) { + ShapedRecipeBuilder.shaped(Registration.ITEM_BREAKABLE_WALL.get(), 8) + .pattern("DDD") + .pattern("D D") + .pattern("DDD") + .define('D', Items.POLISHED_DEEPSLATE) + .unlockedBy("picked_up_deepslate", RecipeProvider.has(Tags.Items.COBBLESTONE_DEEPSLATE)) .save(consumer); ShapedRecipeBuilder.shaped(Registration.PERSONAL_SHRINKING_DEVICE.get()) @@ -74,12 +75,12 @@ protected void buildCraftingRecipes(Consumer consumer) { } private void addMachineRecipes(Consumer consumer) { - registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_TINY.get(), ItemTags.PLANKS); + registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_TINY.get(), Tags.Items.STORAGE_BLOCKS_COPPER); registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_SMALL.get(), Tags.Items.STORAGE_BLOCKS_IRON); registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_NORMAL.get(), Tags.Items.STORAGE_BLOCKS_GOLD); registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_GIANT.get(), Tags.Items.STORAGE_BLOCKS_DIAMOND); registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_LARGE.get(), Tags.Items.OBSIDIAN); - registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_MAXIMUM.get(), Tags.Items.STORAGE_BLOCKS_EMERALD); + registerMachineRecipe(consumer, Registration.MACHINE_BLOCK_ITEM_MAXIMUM.get(), Tags.Items.STORAGE_BLOCKS_NETHERITE); } protected void registerMachineRecipe(Consumer consumer, ItemLike out, TagKey center) { @@ -96,8 +97,7 @@ protected void registerMachineRecipe(Consumer consumer, ItemLike if (center != null) recipe.define('C', center); - recipe - .unlockedBy("has_recipe", RecipeProvider.has(wall)); + recipe.unlockedBy("has_recipe", RecipeProvider.has(wall)); ConditionalRecipe.builder() .addCondition(new EnableVanillaRecipesConfigCondition()) diff --git a/src/datagen/java/dev/compactmods/machines/datagen/lang/BaseLangGenerator.java b/src/datagen/java/dev/compactmods/machines/datagen/lang/BaseLangGenerator.java index 9ad4239d..ae01e8e8 100644 --- a/src/datagen/java/dev/compactmods/machines/datagen/lang/BaseLangGenerator.java +++ b/src/datagen/java/dev/compactmods/machines/datagen/lang/BaseLangGenerator.java @@ -32,7 +32,7 @@ public BaseLangGenerator(DataGenerator gen, String locale) { @SuppressWarnings("unused") protected String getDirectionTranslation(Direction dir) { - return dir.getSerializedName(); + return capitalize(dir.getSerializedName()); } protected String getMachineTranslation() { @@ -48,7 +48,7 @@ protected void addTranslations() { // Direction Names for (var dir : Direction.values()) { - add(CompactMachines.MOD_ID + ".direction." + dir.name(), getDirectionTranslation(dir)); + add(CompactMachines.MOD_ID + ".direction." + dir.getSerializedName(), getDirectionTranslation(dir)); } } @@ -98,6 +98,10 @@ protected void addAdvancementTranslations() { .description("Which machine is the player in?!"); advancement(Advancements.ROOT).title("Compact Machines").noDesc(); + + advancement(Advancements.RECURSIVE_ROOMS) + .title("Recursive Rooms") + .description("To understand recursion, you must first understand recursion."); } protected AdvancementLangBuilder advancement(ResourceLocation advancement) { diff --git a/src/datagen/java/dev/compactmods/machines/datagen/lang/EnglishLangGenerator.java b/src/datagen/java/dev/compactmods/machines/datagen/lang/EnglishLangGenerator.java index 22cffdf5..6ac97258 100644 --- a/src/datagen/java/dev/compactmods/machines/datagen/lang/EnglishLangGenerator.java +++ b/src/datagen/java/dev/compactmods/machines/datagen/lang/EnglishLangGenerator.java @@ -34,12 +34,12 @@ protected void addTranslations() { addCommand(CMCommands.NOT_IN_COMPACT_DIMENSION, "Cannot use that command outside of a machine room."); addCommand(CMCommands.FAILED_CMD_FILE_ERROR, "Failed to execute command; there was a file error. Check logs."); - addCommand(CMCommands.MACHINE_NOT_BOUND, "Machine at %s does not have an associated ID."); - addCommand(CMCommands.ROOM_REG_COUNT, "Number of registered machine blocks: %s"); + addCommand(CMCommands.MACHINE_NOT_BOUND, "Machine at %s is not bound to a room."); + addCommand(CMCommands.ROOM_REG_COUNT, "Number of registered rooms: %s"); + addCommand(CMCommands.MACHINE_REG_DIM, "[%s]: %s"); + addCommand(CMCommands.MACHINE_REG_TOTAL, "Total: %s"); addCommand(CMCommands.LEVEL_REGISTERED, "Compact Machine dimension found."); addCommand(CMCommands.LEVEL_NOT_FOUND, "Compact Machine dimension could not be found."); - addCommand(CMCommands.CMD_ROOM_NOT_REGISTERED, ""); - addCommand(CMCommands.ROOM_DATA_NOT_FOUND, ""); addAdvancementTranslations(); @@ -72,8 +72,16 @@ protected void addTranslations() { addTooltip(Tooltips.TUNNEL_TYPE, "Type ID: %1$s"); addTooltip(Tooltips.UNKNOWN_TUNNEL_TYPE, "Unknown Tunnel Type (%s)"); + addTooltip(Tooltips.ROOM_NAME, "Bound to room: %s"); + addCommand(CMCommands.CANNOT_GIVE_MACHINE, "Failed to give a new machine to player."); + addCommand(CMCommands.MACHINE_GIVEN, "Created a new machine item and gave it to %s."); + + addMessage(Messages.UNKNOWN_ROOM_CHUNK, "Unknown room at %s; please verify it exists."); + add("itemGroup." + CompactMachines.MOD_ID, "Compact Machines"); + add("biome." + CompactMachines.MOD_ID + ".machine", "Compact Machine"); + add("compactmachines.psd.pages.machines.title", "Compact Machines"); add("compactmachines.psd.pages.machines", "Compact Machines are the core mechanic of this mod. They allow you to build large " + "rooms in a single block space connected to the outside world. They come in various sizes ranging from 3x3x3 to 13x13x13.\n\n" + diff --git a/src/main/java/dev/compactmods/machines/CompactMachines.java b/src/main/java/dev/compactmods/machines/CompactMachines.java index 6d8a3b10..f356030e 100644 --- a/src/main/java/dev/compactmods/machines/CompactMachines.java +++ b/src/main/java/dev/compactmods/machines/CompactMachines.java @@ -1,10 +1,15 @@ package dev.compactmods.machines; +import dev.compactmods.machines.command.argument.RoomPositionArgument; import dev.compactmods.machines.config.CommonConfig; import dev.compactmods.machines.config.EnableVanillaRecipesConfigCondition; import dev.compactmods.machines.config.ServerConfig; import dev.compactmods.machines.core.Registration; import dev.compactmods.machines.core.Tunnels; +import dev.compactmods.machines.core.UIRegistration; +import dev.compactmods.machines.graph.CMGraphRegistration; +import net.minecraft.commands.synchronization.ArgumentTypes; +import net.minecraft.commands.synchronization.EmptyArgumentSerializer; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; import net.minecraftforge.common.crafting.CraftingHelper; @@ -14,6 +19,8 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; import javax.annotation.Nonnull; @@ -22,6 +29,7 @@ public class CompactMachines { public static final String MOD_ID = "compactmachines"; public static final Logger LOGGER = LogManager.getLogger(); + public static final Marker CONN_MARKER = MarkerManager.getMarker("cm_connections"); public static final CreativeModeTab COMPACT_MACHINES_ITEMS = new CreativeModeTab(MOD_ID) { @Override @@ -37,12 +45,16 @@ public CompactMachines() { // Register blocks and items var eb = FMLJavaModLoadingContext.get().getModEventBus(); Registration.init(eb); + UIRegistration.init(eb); Tunnels.init(eb); + CMGraphRegistration.init(eb); ModLoadingContext mlCtx = ModLoadingContext.get(); mlCtx.registerConfig(ModConfig.Type.COMMON, CommonConfig.CONFIG); mlCtx.registerConfig(ModConfig.Type.SERVER, ServerConfig.CONFIG); CraftingHelper.register(EnableVanillaRecipesConfigCondition.Serializer.INSTANCE); + + ArgumentTypes.register("room_pos", RoomPositionArgument.class, new EmptyArgumentSerializer<>(RoomPositionArgument::room)); } } diff --git a/src/main/java/dev/compactmods/machines/advancement/AdvancementTriggers.java b/src/main/java/dev/compactmods/machines/advancement/AdvancementTriggers.java index 60a650f6..8423cede 100644 --- a/src/main/java/dev/compactmods/machines/advancement/AdvancementTriggers.java +++ b/src/main/java/dev/compactmods/machines/advancement/AdvancementTriggers.java @@ -1,7 +1,7 @@ package dev.compactmods.machines.advancement; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.advancement.trigger.ClaimedMachineTrigger; +import dev.compactmods.machines.advancement.trigger.BasicPlayerAdvTrigger; import dev.compactmods.machines.advancement.trigger.HowDidYouGetHereTrigger; import dev.compactmods.machines.api.core.Advancements; import dev.compactmods.machines.room.RoomSize; @@ -9,20 +9,22 @@ public class AdvancementTriggers { + public static final BasicPlayerAdvTrigger RECURSIVE_ROOMS = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.RECURSIVE_ROOMS)); + public static final HowDidYouGetHereTrigger HOW_DID_YOU_GET_HERE = CriteriaTriggers.register(new HowDidYouGetHereTrigger()); - public static final ClaimedMachineTrigger CLAIMED_TINY = CriteriaTriggers.register(new ClaimedMachineTrigger(Advancements.CLAIMED_TINY_MACHINE)); - public static final ClaimedMachineTrigger CLAIMED_SMALL = CriteriaTriggers.register(new ClaimedMachineTrigger(Advancements.CLAIMED_SMALL_MACHINE)); - public static final ClaimedMachineTrigger CLAIMED_NORMAL = CriteriaTriggers.register(new ClaimedMachineTrigger(Advancements.CLAIMED_NORMAL_MACHINE)); - public static final ClaimedMachineTrigger CLAIMED_LARGE = CriteriaTriggers.register(new ClaimedMachineTrigger(Advancements.CLAIMED_LARGE_MACHINE)); - public static final ClaimedMachineTrigger CLAIMED_GIANT = CriteriaTriggers.register(new ClaimedMachineTrigger(Advancements.CLAIMED_GIANT_MACHINE)); - public static final ClaimedMachineTrigger CLAIMED_MAX = CriteriaTriggers.register(new ClaimedMachineTrigger(Advancements.CLAIMED_MAX_MACHINE)); + public static final BasicPlayerAdvTrigger CLAIMED_TINY = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.CLAIMED_TINY_MACHINE)); + public static final BasicPlayerAdvTrigger CLAIMED_SMALL = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.CLAIMED_SMALL_MACHINE)); + public static final BasicPlayerAdvTrigger CLAIMED_NORMAL = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.CLAIMED_NORMAL_MACHINE)); + public static final BasicPlayerAdvTrigger CLAIMED_LARGE = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.CLAIMED_LARGE_MACHINE)); + public static final BasicPlayerAdvTrigger CLAIMED_GIANT = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.CLAIMED_GIANT_MACHINE)); + public static final BasicPlayerAdvTrigger CLAIMED_MAX = CriteriaTriggers.register(new BasicPlayerAdvTrigger(Advancements.CLAIMED_MAX_MACHINE)); public static void init() { CompactMachines.LOGGER.trace("Registering advancement triggers."); } - public static ClaimedMachineTrigger getTriggerForMachineClaim(RoomSize machineSize) { + public static BasicPlayerAdvTrigger getTriggerForMachineClaim(RoomSize machineSize) { switch (machineSize) { case TINY: return CLAIMED_TINY; case SMALL: return CLAIMED_SMALL; diff --git a/src/main/java/dev/compactmods/machines/advancement/trigger/BaseAdvancementTrigger.java b/src/main/java/dev/compactmods/machines/advancement/trigger/BaseAdvancementTrigger.java new file mode 100644 index 00000000..b0e51063 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/advancement/trigger/BaseAdvancementTrigger.java @@ -0,0 +1,33 @@ +package dev.compactmods.machines.advancement.trigger; + +import dev.compactmods.machines.advancement.GenericAdvancementTriggerListenerList; +import net.minecraft.advancements.CriterionTrigger; +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; + +public abstract class BaseAdvancementTrigger implements CriterionTrigger { + + private final GenericAdvancementTriggerListenerList listeners = new GenericAdvancementTriggerListenerList<>(); + + @Override + public void addPlayerListener(PlayerAdvancements advancements, Listener list) { + listeners.addPlayerListener(advancements, list); + } + + @Override + public void removePlayerListener(PlayerAdvancements advancements, Listener list) { + listeners.removePlayerListener(advancements, list); + } + + @Override + public void removePlayerListeners(PlayerAdvancements advancements) { + listeners.removePlayerListeners(advancements); + } + + public void trigger(ServerPlayer player) { + final var listeners = this.listeners.getListeners(player); + if(listeners != null) + listeners.trigger(); + } +} diff --git a/src/main/java/dev/compactmods/machines/advancement/trigger/BasicPlayerAdvTrigger.java b/src/main/java/dev/compactmods/machines/advancement/trigger/BasicPlayerAdvTrigger.java new file mode 100644 index 00000000..34a8ef2b --- /dev/null +++ b/src/main/java/dev/compactmods/machines/advancement/trigger/BasicPlayerAdvTrigger.java @@ -0,0 +1,37 @@ +package dev.compactmods.machines.advancement.trigger; + +import com.google.gson.JsonObject; +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; +import net.minecraft.advancements.critereon.DeserializationContext; +import net.minecraft.advancements.critereon.EntityPredicate; +import net.minecraft.resources.ResourceLocation; + +public class BasicPlayerAdvTrigger extends BaseAdvancementTrigger { + + private final ResourceLocation advancementId; + + public BasicPlayerAdvTrigger(ResourceLocation advancementId) { + this.advancementId = advancementId; + } + + @Override + public ResourceLocation getId() { + return advancementId; + } + + @Override + public Instance createInstance(JsonObject json, DeserializationContext conditions) { + return new Instance(this.advancementId, EntityPredicate.Composite.fromJson(json, "player", conditions)); + } + + public static class Instance extends AbstractCriterionTriggerInstance { + + public Instance(ResourceLocation advId, EntityPredicate.Composite player) { + super(advId, player); + } + + public static Instance create(ResourceLocation advancement) { + return new Instance(advancement, EntityPredicate.Composite.ANY); + } + } +} diff --git a/src/main/java/dev/compactmods/machines/advancement/trigger/ClaimedMachineTrigger.java b/src/main/java/dev/compactmods/machines/advancement/trigger/ClaimedMachineTrigger.java deleted file mode 100644 index 5897e84a..00000000 --- a/src/main/java/dev/compactmods/machines/advancement/trigger/ClaimedMachineTrigger.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.compactmods.machines.advancement.trigger; - -import com.google.gson.JsonObject; -import dev.compactmods.machines.advancement.GenericAdvancementTriggerListener; -import dev.compactmods.machines.advancement.GenericAdvancementTriggerListenerList; -import net.minecraft.advancements.CriterionTrigger; -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; -import net.minecraft.advancements.critereon.DeserializationContext; -import net.minecraft.advancements.critereon.EntityPredicate; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.PlayerAdvancements; -import net.minecraft.server.level.ServerPlayer; - -public class ClaimedMachineTrigger implements CriterionTrigger { - - private final GenericAdvancementTriggerListenerList listeners = new GenericAdvancementTriggerListenerList<>(); - private final ResourceLocation advancementId; - - public ClaimedMachineTrigger(ResourceLocation advancementId) { - this.advancementId = advancementId; - } - - @Override - public ResourceLocation getId() { - return advancementId; - } - - @Override - public void addPlayerListener(PlayerAdvancements advancements, Listener list) { - listeners.addPlayerListener(advancements, list); - } - - @Override - public void removePlayerListener(PlayerAdvancements advancements, Listener list) { - listeners.removePlayerListener(advancements, list); - } - - @Override - public void removePlayerListeners(PlayerAdvancements advancements) { - listeners.removePlayerListeners(advancements); - } - - @Override - public Instance createInstance(JsonObject json, DeserializationContext conditions) { - return new Instance(this.advancementId, EntityPredicate.Composite.fromJson(json, "player", conditions)); - } - - public void trigger(ServerPlayer player) { - final GenericAdvancementTriggerListener listeners = this.listeners.getListeners(player); - if(listeners != null) - listeners.trigger(); - } - - public static class Instance extends AbstractCriterionTriggerInstance { - - public Instance(ResourceLocation advId, EntityPredicate.Composite player) { - super(advId, player); - } - - public static Instance create(ResourceLocation advancement) { - return new Instance(advancement, EntityPredicate.Composite.ANY); - } - } -} diff --git a/src/main/java/dev/compactmods/machines/advancement/trigger/HowDidYouGetHereTrigger.java b/src/main/java/dev/compactmods/machines/advancement/trigger/HowDidYouGetHereTrigger.java index a8cc834b..6407bcf7 100644 --- a/src/main/java/dev/compactmods/machines/advancement/trigger/HowDidYouGetHereTrigger.java +++ b/src/main/java/dev/compactmods/machines/advancement/trigger/HowDidYouGetHereTrigger.java @@ -1,54 +1,24 @@ package dev.compactmods.machines.advancement.trigger; import com.google.gson.JsonObject; -import dev.compactmods.machines.advancement.GenericAdvancementTriggerListener; -import dev.compactmods.machines.advancement.GenericAdvancementTriggerListenerList; import dev.compactmods.machines.api.core.Advancements; -import net.minecraft.advancements.CriterionTrigger; -import net.minecraft.server.PlayerAdvancements; import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.EntityPredicate; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.advancements.critereon.DeserializationContext; import net.minecraft.resources.ResourceLocation; -import net.minecraft.advancements.CriterionTrigger.Listener; - -public class HowDidYouGetHereTrigger implements CriterionTrigger { - - private final GenericAdvancementTriggerListenerList listeners = new GenericAdvancementTriggerListenerList<>(); +public class HowDidYouGetHereTrigger extends BaseAdvancementTrigger { @Override public ResourceLocation getId() { return Advancements.HOW_DID_YOU_GET_HERE; } - @Override - public void addPlayerListener(PlayerAdvancements advancements, Listener list) { - listeners.addPlayerListener(advancements, list); - } - - @Override - public void removePlayerListener(PlayerAdvancements advancements, Listener list) { - listeners.removePlayerListener(advancements, list); - } - - @Override - public void removePlayerListeners(PlayerAdvancements advancements) { - listeners.removePlayerListeners(advancements); - } - @Override public Instance createInstance(JsonObject json, DeserializationContext conditions) { return new Instance(EntityPredicate.Composite.fromJson(json, "player", conditions)); } - public void trigger(ServerPlayer player) { - final GenericAdvancementTriggerListener listeners = this.listeners.getListeners(player); - if(listeners != null) - listeners.trigger(); - } - public static class Instance extends AbstractCriterionTriggerInstance { public Instance(EntityPredicate.Composite player) { diff --git a/src/main/java/dev/compactmods/machines/client/ClientEventHandler.java b/src/main/java/dev/compactmods/machines/client/ClientEventHandler.java index 61148b40..a8884b64 100644 --- a/src/main/java/dev/compactmods/machines/client/ClientEventHandler.java +++ b/src/main/java/dev/compactmods/machines/client/ClientEventHandler.java @@ -1,7 +1,12 @@ package dev.compactmods.machines.client; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.room.client.MachineRoomScreen; import dev.compactmods.machines.core.Tunnels; +import dev.compactmods.machines.core.UIRegistration; +import dev.compactmods.machines.tunnel.client.TunnelColors; +import dev.compactmods.machines.tunnel.client.TunnelItemColor; +import net.minecraft.client.gui.screens.MenuScreens; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; import net.minecraftforge.api.distmarker.Dist; @@ -27,5 +32,7 @@ public static void onBlockColors(final ColorHandlerEvent.Block colors) { public static void onClientSetup(final FMLClientSetupEvent client) { RenderType cutout = RenderType.cutoutMipped(); ItemBlockRenderTypes.setRenderLayer(Tunnels.BLOCK_TUNNEL_WALL.get(), cutout); + + MenuScreens.register(UIRegistration.MACHINE_MENU.get(), MachineRoomScreen::new); } } diff --git a/src/main/java/dev/compactmods/machines/client/gui/widget/PSDIconButton.java b/src/main/java/dev/compactmods/machines/client/gui/widget/PSDIconButton.java new file mode 100644 index 00000000..aca54cfc --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/gui/widget/PSDIconButton.java @@ -0,0 +1,47 @@ +package dev.compactmods.machines.client.gui.widget; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.compactmods.machines.core.CompactMachinesNet; +import dev.compactmods.machines.core.Registration; +import dev.compactmods.machines.room.client.MachineRoomScreen; +import dev.compactmods.machines.room.network.PlayerRequestedTeleportPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ChunkPos; +import net.minecraftforge.client.gui.widget.ExtendedButton; +import org.jetbrains.annotations.NotNull; + +public class PSDIconButton extends ExtendedButton { + private final MachineRoomScreen parent; + + public PSDIconButton(MachineRoomScreen parent, int xPos, int yPos) { + super(xPos, yPos, 20, 22, new TextComponent(""), PSDIconButton::onClicked); + this.active = false; + this.parent = parent; + } + + @Override + public void render(@NotNull PoseStack pose, int mouseX, int mouseY, float partialTicks) { + super.render(pose, mouseX, mouseY, partialTicks); + + this.parent.getMinecraft().getItemRenderer().renderAndDecorateItem( + new ItemStack(Registration.PERSONAL_SHRINKING_DEVICE.get()), + x + 2, y + 2, 40); + } + + private static void onClicked(Button button) { + if (button instanceof PSDIconButton psd && button.active) { + var menu = psd.parent.getMenu(); + var mach = psd.parent.getMachine(); + var room = menu.getRoom(); + CompactMachinesNet.CHANNEL.sendToServer(new PlayerRequestedTeleportPacket(mach, room)); + } + } + + public void setEnabled(boolean has) { + this.active = has; + } +} diff --git a/src/main/java/dev/compactmods/machines/client/level/EmptyLevelEntityGetter.java b/src/main/java/dev/compactmods/machines/client/level/EmptyLevelEntityGetter.java new file mode 100644 index 00000000..1c42fa2b --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/level/EmptyLevelEntityGetter.java @@ -0,0 +1,45 @@ +package dev.compactmods.machines.client.level; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.level.entity.LevelEntityGetter; +import net.minecraft.world.phys.AABB; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.UUID; +import java.util.function.Consumer; + +public class EmptyLevelEntityGetter implements LevelEntityGetter { + @Nullable + @Override + public Entity get(int p_156931_) { + return null; + } + + @Nullable + @Override + public Entity get(UUID p_156939_) { + return null; + } + + @Override + public Iterable getAll() { + return Collections.emptySet(); + } + + @Override + public void get(EntityTypeTest p_156935_, Consumer p_156936_) { + + } + + @Override + public void get(AABB p_156937_, Consumer p_156938_) { + + } + + @Override + public void get(EntityTypeTest p_156932_, AABB p_156933_, Consumer p_156934_) { + + } +} diff --git a/src/main/java/dev/compactmods/machines/client/level/FakeSpawnInfo.java b/src/main/java/dev/compactmods/machines/client/level/FakeSpawnInfo.java new file mode 100644 index 00000000..f7ad3847 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/level/FakeSpawnInfo.java @@ -0,0 +1,120 @@ +package dev.compactmods.machines.client.level; + +import net.minecraft.world.Difficulty; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.storage.WritableLevelData; + +/** + * Credits to Immersive Engineering manual code for a baseline in 1.18.x. + * Source: https://github.com/BluSunrize/ImmersiveEngineering/blob/1.18.2/src/main/java/blusunrize/immersiveengineering/common/util/fakeworld/FakeSpawnInfo.java + */ +public class FakeSpawnInfo implements WritableLevelData { + private static final GameRules RULES = new GameRules(); + + private int spawnX; + private int spawnY; + private int spawnZ; + private float spawnAngle; + + @Override + public void setXSpawn(int x) + { + spawnX = x; + } + + @Override + public void setYSpawn(int y) + { + spawnY = y; + } + + @Override + public void setZSpawn(int z) + { + spawnZ = z; + } + + @Override + public void setSpawnAngle(float angle) + { + spawnAngle = angle; + } + + @Override + public int getXSpawn() + { + return spawnX; + } + + @Override + public int getYSpawn() + { + return spawnY; + } + + @Override + public int getZSpawn() + { + return spawnZ; + } + + @Override + public float getSpawnAngle() + { + return spawnAngle; + } + + @Override + public long getGameTime() + { + return 0; + } + + @Override + public long getDayTime() + { + return 0; + } + + @Override + public boolean isThundering() + { + return false; + } + + @Override + public boolean isRaining() + { + return false; + } + + @Override + public void setRaining(boolean isRaining) + { + + } + + @Override + public boolean isHardcore() + { + return false; + } + + @Override + public GameRules getGameRules() + { + return RULES; + } + + @Override + public Difficulty getDifficulty() + { + return Difficulty.PEACEFUL; + } + + @Override + public boolean isDifficultyLocked() + { + return false; + } +} diff --git a/src/main/java/dev/compactmods/machines/client/level/RenderingLevel.java b/src/main/java/dev/compactmods/machines/client/level/RenderingLevel.java new file mode 100644 index 00000000..20f00d70 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/level/RenderingLevel.java @@ -0,0 +1,186 @@ +package dev.compactmods.machines.client.level; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.*; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.profiling.InactiveProfiler; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.TickingBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.entity.LevelEntityGetter; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.ticks.BlackholeTickAccess; +import net.minecraft.world.ticks.LevelTickAccess; + +import javax.annotation.Nullable; +import java.util.*; + +public class RenderingLevel extends Level { + + private final TemplateChunkProvider chunkProvider; + + public RenderingLevel(StructureTemplate blocks) { + super(new FakeSpawnInfo(), Level.OVERWORLD, Holder.direct(DimensionType.DEFAULT_OVERWORLD), + () -> InactiveProfiler.INSTANCE, true, false, 0); + + if(!blocks.palettes.isEmpty()) { + StructurePlaceSettings s = new StructurePlaceSettings(); + var p = s.getRandomPalette(blocks.palettes, null); + this.chunkProvider = new TemplateChunkProvider(p.blocks(), this, (po) -> true); + } else { + this.chunkProvider = new TemplateChunkProvider(Collections.emptyList(), this, p -> true); + } + } + + @Override + public boolean isClientSide() { + return true; + } + + @Override + public void sendBlockUpdated(BlockPos p_46612_, BlockState p_46613_, BlockState p_46614_, int p_46615_) { + + } + + @Override + public void playSound(@Nullable Player p_46543_, double p_46544_, double p_46545_, double p_46546_, SoundEvent p_46547_, SoundSource p_46548_, float p_46549_, float p_46550_) { + + } + + @Override + public void playSound(@Nullable Player p_46551_, Entity p_46552_, SoundEvent p_46553_, SoundSource p_46554_, float p_46555_, float p_46556_) { + + } + + @Override + public String gatherChunkSourceStats() { + return ""; + } + + @Nullable + @Override + public Entity getEntity(int p_46492_) { + return null; + } + + @Nullable + @Override + public MapItemSavedData getMapData(String p_46650_) { + return null; + } + + @Override + public void setMapData(String p_151533_, MapItemSavedData p_151534_) { + + } + + @Override + public int getFreeMapId() { + return 0; + } + + @Override + public void destroyBlockProgress(int p_46506_, BlockPos p_46507_, int p_46508_) { + + } + + @Override + public Scoreboard getScoreboard() { + return new Scoreboard(); + } + + @Override + public RecipeManager getRecipeManager() { + return new RecipeManager(); + } + + @Override + protected LevelEntityGetter getEntities() { + return new EmptyLevelEntityGetter(); + } + + @Override + public LevelTickAccess getBlockTicks() { + return BlackholeTickAccess.emptyLevelList(); + } + + @Override + public LevelTickAccess getFluidTicks() { + return BlackholeTickAccess.emptyLevelList(); + } + + @Override + public ChunkSource getChunkSource() { + return chunkProvider; + } + + @Override + public void levelEvent(@Nullable Player p_46771_, int p_46772_, BlockPos p_46773_, int p_46774_) { + + } + + @Override + public void gameEvent(@Nullable Entity p_151549_, GameEvent p_151550_, BlockPos p_151551_) { + + } + + @Override + public RegistryAccess registryAccess() { + return Minecraft.getInstance().level.registryAccess(); + } + + @Override + public float getShade(Direction p_45522_, boolean p_45523_) { + return 1; + } + + @Override + public List players() { + return Collections.emptyList(); + } + + @Override + public Holder getUncachedNoiseBiome(int p_204159_, int p_204160_, int p_204161_) { + return Holder.direct(registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getOrThrow(Biomes.PLAINS)); + } + + @Override + public int getBrightness(LightLayer p_45518_, BlockPos p_45519_) { + return Level.MAX_BRIGHTNESS; + } + + @Override + public long getGameTime() { + return Minecraft.getInstance().level.getGameTime(); + } + + public void tbe() { + tickBlockEntities(); + } + + @Override + protected void tickBlockEntities() { + super.tickBlockEntities(); + chunkProvider.chunks() + .filter(ca -> ca instanceof TemplateChunk) + .map(TemplateChunk.class::cast) + .toList() + .forEach(TemplateChunk::tick); + } +} diff --git a/src/main/java/dev/compactmods/machines/client/level/TemplateChunk.java b/src/main/java/dev/compactmods/machines/client/level/TemplateChunk.java new file mode 100644 index 00000000..b96442fc --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/level/TemplateChunk.java @@ -0,0 +1,105 @@ +package dev.compactmods.machines.client.level; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.advancement.GenericAdvancementTriggerListener; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.TickingBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.EmptyLevelChunk; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.ticks.TickContainerAccess; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +/** + * Taken from Immersive Engineering's manual code. + * Source: https://github.com/BluSunrize/ImmersiveEngineering/blob/1.18.2/src/main/java/blusunrize/immersiveengineering/common/util/fakeworld/TemplateChunk.java + */ +public class TemplateChunk extends EmptyLevelChunk { + private final Map blocksInChunk; + private final Map tiles; + private final Predicate shouldShow; + private final Map> tickers; + + public TemplateChunk(RenderingLevel worldIn, ChunkPos chunkPos, Map blocksInChunk, Predicate shouldShow) { + super(worldIn, chunkPos, worldIn.getUncachedNoiseBiome(0, 0, 0)); + this.shouldShow = shouldShow; + this.blocksInChunk = new HashMap<>(); + + tiles = new HashMap<>(); + tickers = new HashMap<>(); + + for (var pos : blocksInChunk.keySet()) { + final var blockInfo = blocksInChunk.get(pos); + this.blocksInChunk.put(pos, blockInfo.state); + + if(blockInfo.nbt != null) { + BlockEntity tile = BlockEntity.loadStatic(blockInfo.pos, blockInfo.state, blockInfo.nbt); + if (tile != null) { + tile.setLevel(worldIn); + tile.setBlockState(blockInfo.state); + tiles.put(blockInfo.pos, tile); + tile.onLoad(); + + if(blockInfo.state.getBlock() instanceof EntityBlock eb) { + final BlockEntityTicker ticker = eb.getTicker(worldIn, blockInfo.state, tile.getType()); + if(ticker != null) + this.tickers.put(pos, (BlockEntityTicker) ticker); + } + } + } + } + } + + @Override + public int getHeight() { + return super.getHeight(); + } + + public void tick() { + this.tickers.forEach((pos, ticker) -> ticker.tick(this.getLevel(), pos, getBlockState(pos), tiles.get(pos))); + } + + @Nonnull + @Override + public BlockState getBlockState(@Nonnull BlockPos pos) { + if (shouldShow.test(pos)) { + var state = blocksInChunk.get(pos); + if (state != null) + return state; + } + + return Blocks.VOID_AIR.defaultBlockState(); + } + + @Nonnull + @Override + public FluidState getFluidState(@Nonnull BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + + + @Nullable + @Override + public BlockEntity getBlockEntity(@Nonnull BlockPos pos, @Nonnull EntityCreationType creationMode) { + if (!shouldShow.test(pos)) + return null; + return tiles.get(pos); + } + +} \ No newline at end of file diff --git a/src/main/java/dev/compactmods/machines/client/level/TemplateChunkProvider.java b/src/main/java/dev/compactmods/machines/client/level/TemplateChunkProvider.java new file mode 100644 index 00000000..e7492524 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/level/TemplateChunkProvider.java @@ -0,0 +1,106 @@ +package dev.compactmods.machines.client.level; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.EmptyLevelChunk; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Taken from Immersive Engineering's manual code. + * Source: https://github.com/BluSunrize/ImmersiveEngineering/blob/1.18.2/src/main/java/blusunrize/immersiveengineering/common/util/fakeworld/TemplateChunkProvider.java + */ +public class TemplateChunkProvider extends ChunkSource { + private final Map chunks; + private final RenderingLevel world; + private final LevelLightEngine lightManager; + + public TemplateChunkProvider(List blocks, RenderingLevel world, Predicate shouldShow) { + this.world = world; + this.lightManager = new LevelLightEngine(this, true, true); + + HashMap blockInfo = new HashMap<>(); + blocks.forEach(sbi -> { + blockInfo.put(sbi.pos, sbi); + }); + + chunks = loadChunkData(blockInfo, world, shouldShow); + } + + @NotNull + private Map loadChunkData(Map blocks, RenderingLevel world, Predicate shouldShow) { + Map> byChunk = new HashMap<>(); + for(var info : blocks.entrySet()) + { + final var bc = byChunk.computeIfAbsent(new ChunkPos(info.getKey()), $ -> new HashMap<>()); + bc.put(info.getKey(), info.getValue()); + } + + return byChunk.entrySet().stream() + .map(e -> Pair.of(e.getKey(), new TemplateChunk(world, e.getKey(), e.getValue(), shouldShow))) + .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); + } + + public Stream chunks() { + return this.chunks.values().stream(); + } + + + @Nullable + @Override + public ChunkAccess getChunk(int chunkX, int chunkZ, @Nonnull ChunkStatus requiredStatus, boolean load) + { + return chunks.computeIfAbsent(new ChunkPos(chunkX, chunkZ), p -> { + return new EmptyLevelChunk(world, p, world.getUncachedNoiseBiome(0, 0, 0)); + }); + } + + @Override + public void tick(BooleanSupplier p_202162_, boolean p_202163_) { + + } + + @Nonnull + @Override + public String gatherStats() + { + return "?"; + } + + @Override + public int getLoadedChunksCount() + { + return 0; + } + + @Nonnull + @Override + public LevelLightEngine getLightEngine() + { + return lightManager; + } + + @Nonnull + @Override + public BlockGetter getLevel() + { + return world; + } +} diff --git a/src/main/java/dev/compactmods/machines/client/render/RenderTypes.java b/src/main/java/dev/compactmods/machines/client/render/RenderTypes.java new file mode 100644 index 00000000..20ebcd9f --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/render/RenderTypes.java @@ -0,0 +1,61 @@ +package dev.compactmods.machines.client.render; + +import com.google.common.collect.ImmutableMap; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.blaze3d.vertex.VertexFormatElement; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.client.shader.CM4Shaders; +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; + +import static com.mojang.blaze3d.vertex.DefaultVertexFormat.*; + +public class RenderTypes extends RenderStateShard { + public static final VertexFormat BLOCK_WITH_OVERLAY = new VertexFormat( + ImmutableMap.builder() + .put("Position", ELEMENT_POSITION) + .put("Color", ELEMENT_COLOR) + .put("UV0", ELEMENT_UV0) + .put("UV1", ELEMENT_UV1) + .put("UV2", ELEMENT_UV2) + .put("Normal", ELEMENT_NORMAL) + .put("Padding", ELEMENT_PADDING) + .build() + ); + + protected static final RenderStateShard.LightmapStateShard LIGHTMAP_DISABLED = new RenderStateShard.LightmapStateShard(false); + + protected static final RenderStateShard.ShaderStateShard FULLBRIGHT_BLOCKS = new RenderStateShard.ShaderStateShard(CM4Shaders::fullbright); + + public static final RenderType TRANSLUCENT_FULLBRIGHT = RenderType.create( + CompactMachines.MOD_ID + ":full_bright", + BLOCK_WITH_OVERLAY, VertexFormat.Mode.QUADS, + 256, false, false, + RenderType.CompositeState.builder() + .setShaderState(FULLBRIGHT_BLOCKS) + .setLightmapState(LIGHTMAP_DISABLED) + .setOverlayState(OVERLAY) + .setTextureState(BLOCK_SHEET_MIPPED) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .createCompositeState(false) + ); + + protected static final RenderStateShard.ShaderStateShard WALL_BLOCKS = new RenderStateShard.ShaderStateShard(CM4Shaders::wall); + + public static final RenderType WALLS = RenderType.create( + CompactMachines.MOD_ID + ":wall", + BLOCK_WITH_OVERLAY, VertexFormat.Mode.QUADS, + 256, false, false, + RenderType.CompositeState.builder() + .setShaderState(WALL_BLOCKS) + .setLightmapState(LIGHTMAP_DISABLED) + .setOverlayState(OVERLAY) + .setTextureState(BLOCK_SHEET_MIPPED) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .createCompositeState(false) + ); + + public RenderTypes(String p_110161_, Runnable p_110162_, Runnable p_110163_) { + super(p_110161_, p_110162_, p_110163_); + } +} diff --git a/src/main/java/dev/compactmods/machines/client/render/SuperRenderTypeBuffer.java b/src/main/java/dev/compactmods/machines/client/render/SuperRenderTypeBuffer.java new file mode 100644 index 00000000..5b9e42d2 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/render/SuperRenderTypeBuffer.java @@ -0,0 +1,98 @@ +package dev.compactmods.machines.client.render; + +import java.util.SortedMap; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import net.minecraft.Util; +import net.minecraft.client.renderer.ChunkBufferBuilderPack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.resources.model.ModelBakery; + +/** + * Copied from Create's ponder rendering code. Stripping this down for now, WIP + */ +public class SuperRenderTypeBuffer implements MultiBufferSource { + + private static final SuperRenderTypeBuffer INSTANCE = new SuperRenderTypeBuffer(); + + public static SuperRenderTypeBuffer getInstance() { + return INSTANCE; + } + + private SuperRenderTypeBufferPhase earlyBuffer; + private SuperRenderTypeBufferPhase defaultBuffer; + private SuperRenderTypeBufferPhase lateBuffer; + + public SuperRenderTypeBuffer() { + earlyBuffer = new SuperRenderTypeBufferPhase(); + defaultBuffer = new SuperRenderTypeBufferPhase(); + lateBuffer = new SuperRenderTypeBufferPhase(); + } + + public VertexConsumer getEarlyBuffer(RenderType type) { + return earlyBuffer.bufferSource.getBuffer(type); + } + + @Override + public VertexConsumer getBuffer(RenderType type) { + return defaultBuffer.bufferSource.getBuffer(type); + } + + public VertexConsumer getLateBuffer(RenderType type) { + return lateBuffer.bufferSource.getBuffer(type); + } + + public void draw() { + earlyBuffer.bufferSource.endBatch(); + defaultBuffer.bufferSource.endBatch(); + lateBuffer.bufferSource.endBatch(); + } + + public void draw(RenderType type) { + earlyBuffer.bufferSource.endBatch(type); + defaultBuffer.bufferSource.endBatch(type); + lateBuffer.bufferSource.endBatch(type); + } + + private static class SuperRenderTypeBufferPhase { + + // Visible clones from RenderBuffers + private final ChunkBufferBuilderPack fixedBufferPack = new ChunkBufferBuilderPack(); + private final SortedMap fixedBuffers = Util.make(new Object2ObjectLinkedOpenHashMap<>(), map -> { + map.put(Sheets.solidBlockSheet(), fixedBufferPack.builder(RenderType.solid())); + map.put(Sheets.cutoutBlockSheet(), fixedBufferPack.builder(RenderType.cutout())); + map.put(Sheets.bannerSheet(), fixedBufferPack.builder(RenderType.cutoutMipped())); + map.put(Sheets.translucentCullBlockSheet(), fixedBufferPack.builder(RenderType.translucent())); + put(map, Sheets.shieldSheet()); + put(map, Sheets.bedSheet()); + put(map, Sheets.shulkerBoxSheet()); + put(map, Sheets.signSheet()); + put(map, Sheets.chestSheet()); + put(map, RenderType.translucentNoCrumbling()); + put(map, RenderType.armorGlint()); + put(map, RenderType.armorEntityGlint()); + put(map, RenderType.glint()); + put(map, RenderType.glintDirect()); + put(map, RenderType.glintTranslucent()); + put(map, RenderType.entityGlint()); + put(map, RenderType.entityGlintDirect()); + put(map, RenderType.waterMask()); + ModelBakery.DESTROY_TYPES.forEach((p_173062_) -> { + put(map, p_173062_); + }); + }); + private final MultiBufferSource.BufferSource bufferSource = MultiBufferSource.immediateWithBuffers(fixedBuffers, new BufferBuilder(256)); + + private static void put(Object2ObjectLinkedOpenHashMap map, RenderType type) { + map.put(type, new BufferBuilder(type.bufferSize())); + } + + } + +} + diff --git a/src/main/java/dev/compactmods/machines/client/shader/CM4Shaders.java b/src/main/java/dev/compactmods/machines/client/shader/CM4Shaders.java new file mode 100644 index 00000000..14746082 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/shader/CM4Shaders.java @@ -0,0 +1,39 @@ +package dev.compactmods.machines.client.shader; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import dev.compactmods.machines.CompactMachines; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.io.IOException; + +@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = CompactMachines.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class CM4Shaders +{ + private static ShaderInstance blockFullbrightShader; + private static ShaderInstance wallShader; + + @SubscribeEvent + public static void registerShaders(final RegisterShadersEvent ev) throws IOException + { + ev.registerShader( + new ShaderInstance(ev.getResourceManager(), new ResourceLocation(CompactMachines.MOD_ID, "block_fullbright"), DefaultVertexFormat.BLOCK), + shader -> blockFullbrightShader = shader + ); + + ev.registerShader( + new ShaderInstance(ev.getResourceManager(), new ResourceLocation(CompactMachines.MOD_ID, "wall"), DefaultVertexFormat.BLOCK), + shader -> wallShader = shader + ); + } + + public static ShaderInstance wall() { return wallShader; } + public static ShaderInstance fullbright() + { + return blockFullbrightShader; + } +} diff --git a/src/main/java/dev/compactmods/machines/client/util/TransformingVertexBuilder.java b/src/main/java/dev/compactmods/machines/client/util/TransformingVertexBuilder.java new file mode 100644 index 00000000..58592063 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/client/util/TransformingVertexBuilder.java @@ -0,0 +1,225 @@ +/* + * BluSunrize + * Copyright (c) 2020 + * + * This code is licensed under "Blu's License of Common Sense" + * + * Source: https://github.com/BluSunrize/ImmersiveEngineering/blob/1.18.2/src/main/java/blusunrize/immersiveengineering/client/utils/TransformingVertexBuilder.java + */ + +package dev.compactmods.machines.client.util; + +import com.google.common.base.Preconditions; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.blaze3d.vertex.VertexFormatElement; +import com.mojang.math.Vector3f; +import com.mojang.math.Vector4f; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static com.mojang.blaze3d.vertex.DefaultVertexFormat.*; + +public class TransformingVertexBuilder implements VertexConsumer +{ + private final VertexConsumer base; + private final PoseStack transform; + private final List> allObjects = new ArrayList<>(); + private final ObjectWithGlobal uv = new ObjectWithGlobal<>(this); + private final ObjectWithGlobal pos = new ObjectWithGlobal<>(this); + private final ObjectWithGlobal overlay = new ObjectWithGlobal<>(this); + private final ObjectWithGlobal lightmap = new ObjectWithGlobal<>(this); + private final ObjectWithGlobal normal = new ObjectWithGlobal<>(this); + private final ObjectWithGlobal color = new ObjectWithGlobal<>(this); + private final VertexFormat format; + + public TransformingVertexBuilder(VertexConsumer base, PoseStack transform, VertexFormat format) + { + this.base = base; + this.transform = transform; + this.format = format; + } + + public TransformingVertexBuilder(VertexConsumer base, VertexFormat format) + { + this(base, new PoseStack(), format); + } + + public TransformingVertexBuilder(MultiBufferSource buffer, RenderType type, PoseStack transform) + { + this(buffer.getBuffer(type), transform, type.format()); + } + + public TransformingVertexBuilder(MultiBufferSource buffer, RenderType type) + { + this(buffer, type, new PoseStack()); + } + + @Nonnull + @Override + public VertexConsumer vertex(double x, double y, double z) + { + pos.putData(new Vec3(x, y, z)); + return this; + } + + @Nonnull + @Override + public VertexConsumer color(int red, int green, int blue, int alpha) + { + color.putData(new Vector4f(red/255f, green/255f, blue/255f, alpha/255f)); + return this; + } + + @Nonnull + @Override + public VertexConsumer uv(float u, float v) + { + uv.putData(new Vec2(u, v)); + return this; + } + + @Nonnull + @Override + public VertexConsumer overlayCoords(int u, int v) + { + overlay.putData(new Vec2i(u, v)); + return this; + } + + @Nonnull + @Override + public VertexConsumer uv2(int u, int v) + { + lightmap.putData(new Vec2i(u, v)); + return this; + } + + @Nonnull + @Override + public VertexConsumer normal(float x, float y, float z) + { + normal.putData(new Vector3f(x, y, z)); + return this; + } + + @Override + public void endVertex() + { + for(VertexFormatElement element : format.getElements()) + { + if(element==ELEMENT_POSITION) + pos.ifPresent(pos -> base.vertex(transform.last().pose(), (float)pos.x, (float)pos.y, (float)pos.z)); + else if(element==ELEMENT_COLOR) + color.ifPresent(c -> base.color(c.x(), c.y(), c.z(), c.w())); + else if(element==ELEMENT_UV0) + uv.ifPresent(uv -> base.uv(uv.x, uv.y)); + else if(element==ELEMENT_UV1) + overlay.ifPresent(overlay -> base.overlayCoords(overlay.x, overlay.y)); + else if(element==ELEMENT_UV2) + lightmap.ifPresent(lightmap -> base.uv2(lightmap.x, lightmap.y)); + else if(element==ELEMENT_NORMAL) + normal.ifPresent( + normal -> base.normal(transform.last().normal(), normal.x(), normal.y(), normal.z()) + ); + } + base.endVertex(); + allObjects.forEach(ObjectWithGlobal::clear); + } + + public void defaultColor(float r, float g, float b, float a) + { + color.setGlobal(new Vector4f(r, g, b, a)); + } + + @Override + public void defaultColor(int r, int g, int b, int a) + { + defaultColor(r/255f, g/255f, b/255f, a/255f); + } + + @Override + public void unsetDefaultColor() + { + color.setGlobal(null); + } + + public void setLight(int light) + { + lightmap.setGlobal(new Vec2i(light&255, light >> 16)); + } + + public void setNormal(float x, float y, float z) + { + Vector3f vec = new Vector3f(x, y, z); + vec.normalize(); + normal.setGlobal(vec); + } + + public void setOverlay(int packedOverlayIn) + { + overlay.setGlobal(new Vec2i(packedOverlayIn&0xffff, packedOverlayIn >> 16)); + } + + private record Vec2i(int x, int y) + { + } + + private static class ObjectWithGlobal + { + @Nullable + private T obj; + private boolean isGlobal; + + public ObjectWithGlobal(TransformingVertexBuilder builder) + { + builder.allObjects.add(this); + } + + public void putData(T newVal) + { + Preconditions.checkState(obj==null||(isGlobal&&obj.equals(newVal))); + obj = newVal; + } + + public void setGlobal(@Nullable T obj) + { + this.obj = obj; + isGlobal = obj!=null; + } + + public T read() + { + T ret = Preconditions.checkNotNull(obj); + if(!isGlobal) + obj = null; + return ret; + } + + public boolean hasValue() + { + return obj!=null; + } + + public void ifPresent(Consumer out) + { + if(hasValue()) + out.accept(read()); + } + + public void clear() + { + if(!isGlobal) + obj = null; + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/compactmods/machines/command/CMCommandRoot.java b/src/main/java/dev/compactmods/machines/command/CMCommandRoot.java index 8f8a17ea..03069e84 100644 --- a/src/main/java/dev/compactmods/machines/command/CMCommandRoot.java +++ b/src/main/java/dev/compactmods/machines/command/CMCommandRoot.java @@ -3,8 +3,12 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.command.argument.RoomPositionArgument; +import dev.compactmods.machines.command.data.CMDataSubcommand; import dev.compactmods.machines.command.subcommand.*; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.synchronization.ArgumentTypes; +import net.minecraft.commands.synchronization.EmptyArgumentSerializer; public class CMCommandRoot { @@ -13,8 +17,12 @@ public static void register(CommandDispatcher dispatcher) { root.then(CMEjectSubcommand.make()); root.then(CMSummarySubcommand.make()); root.then(CMRebindSubcommand.make()); + root.then(CMUnbindSubcommand.make()); root.then(CMReaddDimensionSubcommand.make()); root.then(CMRoomsSubcommand.make()); + root.then(CMDataSubcommand.make()); + root.then(CMGiveMachineSubcommand.make()); + dispatcher.register(root); } } diff --git a/src/main/java/dev/compactmods/machines/command/argument/RoomCoordinates.java b/src/main/java/dev/compactmods/machines/command/argument/RoomCoordinates.java new file mode 100644 index 00000000..edeee10d --- /dev/null +++ b/src/main/java/dev/compactmods/machines/command/argument/RoomCoordinates.java @@ -0,0 +1,21 @@ +package dev.compactmods.machines.command.argument; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.coordinates.WorldCoordinate; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.phys.Vec3; + +public class RoomCoordinates { + private final WorldCoordinate x; + private final WorldCoordinate z; + + public RoomCoordinates(WorldCoordinate x, WorldCoordinate z) { + this.x = x; + this.z = z; + } + + public ChunkPos get(CommandSourceStack stack) { + Vec3 vec3 = stack.getPosition(); + return new ChunkPos((int) this.x.get(vec3.x), (int) this.z.get(vec3.z)); + } +} diff --git a/src/main/java/dev/compactmods/machines/command/argument/RoomPositionArgument.java b/src/main/java/dev/compactmods/machines/command/argument/RoomPositionArgument.java new file mode 100644 index 00000000..05babe4a --- /dev/null +++ b/src/main/java/dev/compactmods/machines/command/argument/RoomPositionArgument.java @@ -0,0 +1,51 @@ +package dev.compactmods.machines.command.argument; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.coordinates.WorldCoordinate; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.level.ChunkPos; + +import java.util.concurrent.CompletableFuture; + +public class RoomPositionArgument implements ArgumentType { + public static final SimpleCommandExceptionType ERROR_NOT_COMPLETE = new SimpleCommandExceptionType(new TranslatableComponent("argument.pos2d.incomplete")); + + public static RoomPositionArgument room() { + return new RoomPositionArgument(); + } + + public static ChunkPos get(CommandContext ctx, String room) { + var arg = ctx.getArgument(room, RoomCoordinates.class); + return arg.get(ctx.getSource()); + } + + @Override + public RoomCoordinates parse(StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + if (!reader.canRead()) { + throw ERROR_NOT_COMPLETE.createWithContext(reader); + } else { + WorldCoordinate chunkX = WorldCoordinate.parseInt(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + WorldCoordinate chunkZ = WorldCoordinate.parseInt(reader); + return new RoomCoordinates(chunkX, chunkZ); + } else { + reader.setCursor(i); + throw ERROR_NOT_COMPLETE.createWithContext(reader); + } + } + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return Suggestions.empty(); + } +} diff --git a/src/main/java/dev/compactmods/machines/command/data/CMDataCommand.java b/src/main/java/dev/compactmods/machines/command/data/CMDataSubcommand.java similarity index 64% rename from src/main/java/dev/compactmods/machines/command/data/CMDataCommand.java rename to src/main/java/dev/compactmods/machines/command/data/CMDataSubcommand.java index c42d3571..2aaef861 100644 --- a/src/main/java/dev/compactmods/machines/command/data/CMDataCommand.java +++ b/src/main/java/dev/compactmods/machines/command/data/CMDataSubcommand.java @@ -1,13 +1,12 @@ package dev.compactmods.machines.command.data; -import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -public class CMDataCommand { - public static void register(CommandDispatcher dispatcher) { - final LiteralArgumentBuilder root = LiteralArgumentBuilder.literal("cmdata"); +public class CMDataSubcommand { + public static LiteralArgumentBuilder make() { + final LiteralArgumentBuilder root = LiteralArgumentBuilder.literal("data"); var export = Commands.literal("export"); export.then(CMMachineDataExportCommand.makeMachineCsv()); @@ -15,8 +14,7 @@ public static void register(CommandDispatcher dispatcher) { export.then(CMRoomDataExportCommand.makeRoomCsv()); root.then(export); - // root.then(CMFixBiomeSubcommand.register()); - dispatcher.register(root); + return root; } diff --git a/src/main/java/dev/compactmods/machines/command/data/CMMachineDataExportCommand.java b/src/main/java/dev/compactmods/machines/command/data/CMMachineDataExportCommand.java index 1bc8b8f3..8b6fb93b 100644 --- a/src/main/java/dev/compactmods/machines/command/data/CMMachineDataExportCommand.java +++ b/src/main/java/dev/compactmods/machines/command/data/CMMachineDataExportCommand.java @@ -2,23 +2,16 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.core.CMCommands; import dev.compactmods.machines.api.room.MachineRoomConnections; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.i18n.TranslationUtil; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.machine.data.MachineToRoomConnections; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.util.CsvOutput; -import net.minecraft.world.level.ChunkPos; import javax.annotation.Nonnull; import java.io.BufferedWriter; import java.io.IOException; -import java.nio.file.Files; +// TODO public class CMMachineDataExportCommand { public static ArgumentBuilder makeMachineCsv() { @@ -31,35 +24,35 @@ private static int execAll(CommandContext ctx) { var src = ctx.getSource(); var serv = src.getServer(); - final CompactMachineData machines; - final MachineRoomConnections connections; - try { - machines = CompactMachineData.get(serv); - connections = MachineToRoomConnections.get(serv); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return -1; - } - - var outdir = src.getServer().getFile(CompactMachines.MOD_ID); - var out = outdir.toPath() - .resolve("machines.csv") - .toAbsolutePath(); - - try { - Files.createDirectories(outdir.toPath()); - - var writer = Files.newBufferedWriter(out); - CsvOutput builder = makeCsv(writer); - - machines.stream().forEach(room -> writeMachine(connections, room, builder)); - - writer.close(); - } catch (IOException e) { - CompactMachines.LOGGER.error(e); - src.sendFailure(TranslationUtil.command(CMCommands.FAILED_CMD_FILE_ERROR)); - return -1; - } +// final CompactMachineGraph machines; +// final MachineToRoomConnections connections; +// try { +// machines = CompactMachineGraph.forDimension(serv); +// connections = MachineToRoomConnections.get(serv); +// } catch (MissingDimensionException e) { +// CompactMachines.LOGGER.fatal(e); +// return -1; +// } +// +// var outdir = src.getServer().getFile(CompactMachines.MOD_ID); +// var out = outdir.toPath() +// .resolve("machines.csv") +// .toAbsolutePath(); +// +// try { +// Files.createDirectories(outdir.toPath()); +// +// var writer = Files.newBufferedWriter(out); +// CsvOutput builder = makeCsv(writer); +// +// machines.getMachines().forEach(node -> writeMachine(connections, node, builder)); +// +// writer.close(); +// } catch (IOException e) { +// CompactMachines.LOGGER.error(e); +// src.sendFailure(TranslationUtil.command(CMCommands.FAILED_CMD_FILE_ERROR)); +// return -1; +// } return 0; } @@ -77,24 +70,24 @@ private static CsvOutput makeCsv(BufferedWriter writer) throws IOException { .build(writer); } - private static void writeMachine(MachineRoomConnections connections, CompactMachineData.MachineData mach, CsvOutput builder) { - try { - int id = mach.getMachineId(); - var loc = mach.getLocation(); - var placedAt = loc.getBlockPosition(); - - var room = connections.getConnectedRoom(id).orElse(new ChunkPos(-1, -1)); - builder.writeRow( - id, - loc.getDimension().location().toString(), - placedAt.getX(), - placedAt.getY(), - placedAt.getZ(), - room.x, - room.z - ); - } catch (IOException e) { - CompactMachines.LOGGER.error(e); - } + private static void writeMachine(MachineRoomConnections connections, CsvOutput builder) { +// try { +// int id = mach.getMachineId(); +// var loc = mach.getLocation(); +// var placedAt = loc.getBlockPosition(); +// +// var room = connections.getConnectedRoom(id).orElse(new ChunkPos(-1, -1)); +// builder.writeRow( +// id, +// loc.getDimension().location().toString(), +// placedAt.getX(), +// placedAt.getY(), +// placedAt.getZ(), +// room.x, +// room.z +// ); +// } catch (IOException e) { +// CompactMachines.LOGGER.error(e); +// } } } diff --git a/src/main/java/dev/compactmods/machines/command/data/CMRoomDataExportCommand.java b/src/main/java/dev/compactmods/machines/command/data/CMRoomDataExportCommand.java index 2fb64adc..b1fd0461 100644 --- a/src/main/java/dev/compactmods/machines/command/data/CMRoomDataExportCommand.java +++ b/src/main/java/dev/compactmods/machines/command/data/CMRoomDataExportCommand.java @@ -32,14 +32,9 @@ public class CMRoomDataExportCommand { private static int execAll(CommandContext ctx) { var src = ctx.getSource(); var serv = src.getServer(); - var compact = src.getServer().getLevel(Registration.COMPACT_DIMENSION); + var compact = serv.getLevel(Registration.COMPACT_DIMENSION); - final CompactRoomData rooms; - try { - rooms = CompactRoomData.get(serv); - } catch (MissingDimensionException e) { - throw new CommandRuntimeException(new TextComponent(e.getMessage())); - } + final CompactRoomData rooms = CompactRoomData.get(compact); var outdir = src.getServer().getFile(CompactMachines.MOD_ID); var out = outdir.toPath() diff --git a/src/main/java/dev/compactmods/machines/command/data/CMTunnelDataExportCommand.java b/src/main/java/dev/compactmods/machines/command/data/CMTunnelDataExportCommand.java index d0fe3897..274f6981 100644 --- a/src/main/java/dev/compactmods/machines/command/data/CMTunnelDataExportCommand.java +++ b/src/main/java/dev/compactmods/machines/command/data/CMTunnelDataExportCommand.java @@ -1,20 +1,22 @@ package dev.compactmods.machines.command.data; -import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.core.CMCommands; +import dev.compactmods.machines.command.argument.RoomPositionArgument; import dev.compactmods.machines.core.MissingDimensionException; import dev.compactmods.machines.core.Registration; import dev.compactmods.machines.i18n.TranslationUtil; import dev.compactmods.machines.room.data.CompactRoomData; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.CsvOutput; -import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.ChunkPos; import javax.annotation.Nonnull; import java.io.BufferedWriter; @@ -25,8 +27,7 @@ public class CMTunnelDataExportCommand { public static ArgumentBuilder makeTunnelCsv() { var chunk = Commands - .argument("chunkx", IntegerArgumentType.integer()) - .then(Commands.argument("chunkz", IntegerArgumentType.integer())) + .argument("room", RoomPositionArgument.room()) .executes(CMTunnelDataExportCommand::exec); return Commands.literal("tunnels") @@ -38,15 +39,9 @@ public class CMTunnelDataExportCommand { private static int execAll(CommandContext ctx) { var src = ctx.getSource(); var serv = src.getServer(); - var compact = src.getServer().getLevel(Registration.COMPACT_DIMENSION); + var compact = serv.getLevel(Registration.COMPACT_DIMENSION); - final CompactRoomData rooms; - try { - rooms = CompactRoomData.get(serv); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return -1; - } + final CompactRoomData rooms = CompactRoomData.get(compact); var outdir = src.getServer().getFile(CompactMachines.MOD_ID); var out = outdir.toPath() @@ -60,8 +55,11 @@ private static int execAll(CommandContext ctx) { CsvOutput builder = makeTunnelCsvOut(writer); rooms.stream().forEach(roomChunk -> { - var chunk1 = compact.getChunk(roomChunk.x, roomChunk.z); - writeRoomTunnels(chunk1, builder); + try { + writeRoomTunnels(compact, roomChunk, builder); + } catch (MissingDimensionException e) { + CompactMachines.LOGGER.error(e); + } }); writer.close(); @@ -78,14 +76,12 @@ public static int exec(CommandContext ctx) throws CommandSyn var src = ctx.getSource(); ServerPlayer player = src.getPlayerOrException(); - final int chunkx = IntegerArgumentType.getInteger(ctx, "chunkx"); - final int chunkz = IntegerArgumentType.getInteger(ctx, "chunkz"); - - var chunk1 = src.getLevel().getChunk(chunkx, chunkz); + final var room = RoomPositionArgument.get(ctx, "room"); + final var compactDim = src.getServer().getLevel(Registration.COMPACT_DIMENSION); var outdir = src.getServer().getFile(CompactMachines.MOD_ID); var out = outdir.toPath() - .resolve(String.format("tunnels_%s_%s.csv", chunkx, chunkz)) + .resolve(String.format("tunnels_%s_%s.csv", room.x, room.z)) .toAbsolutePath(); try { @@ -93,13 +89,15 @@ public static int exec(CommandContext ctx) throws CommandSyn var writer = Files.newBufferedWriter(out); CsvOutput builder = makeTunnelCsvOut(writer); - writeRoomTunnels(chunk1, builder); + writeRoomTunnels(compactDim, room, builder); writer.close(); } catch (IOException e) { CompactMachines.LOGGER.error(e); src.sendFailure(TranslationUtil.command(CMCommands.FAILED_CMD_FILE_ERROR)); return -1; + } catch (MissingDimensionException e) { + CompactMachines.LOGGER.error(e); } return 0; @@ -115,26 +113,20 @@ private static CsvOutput makeTunnelCsvOut(BufferedWriter writer) throws IOExcept .build(writer); } - private static void writeRoomTunnels(LevelChunk chunk1, CsvOutput builder) { - // TODO Reimplement -// chunk1.getCapability(Capabilities.ROOM_TUNNELS).ifPresent(tunnels -> { -// tunnels.streamLocations().forEach(pos -> { -// tunnels.locatedAt(pos).ifPresent(conn -> { -// try { -// if(chunk1.getBlockEntity(pos) instanceof TunnelWallEntity tun) { -// builder.writeRow( -// conn.type().getRegistryName().toString(), -// conn.side().getSerializedName(), -// pos.getX(), pos.getY(), pos.getZ(), -// tun.getMachine() -// ); -// } -// } catch (IOException e) { -// CompactMachines.LOGGER.error(e); -// } -// }); -// }); -// -// }); + private static void writeRoomTunnels(ServerLevel compactDim, ChunkPos room, CsvOutput builder) throws MissingDimensionException { + final var graph = TunnelConnectionGraph.forRoom(compactDim, room); + graph.tunnels().forEach(info -> { + var pos = info.location(); + try { + builder.writeRow( + info.type().toString(), + info.side().getSerializedName(), + pos.getX(), pos.getY(), pos.getZ(), + info.machine() + ); + } catch (IOException e) { + CompactMachines.LOGGER.warn("Error writing tunnel record.", e); + } + }); } } diff --git a/src/main/java/dev/compactmods/machines/command/subcommand/CMEjectSubcommand.java b/src/main/java/dev/compactmods/machines/command/subcommand/CMEjectSubcommand.java index 273820ae..06e07201 100644 --- a/src/main/java/dev/compactmods/machines/command/subcommand/CMEjectSubcommand.java +++ b/src/main/java/dev/compactmods/machines/command/subcommand/CMEjectSubcommand.java @@ -15,10 +15,10 @@ public class CMEjectSubcommand { public static ArgumentBuilder make() { return Commands.literal("eject") - .requires(cs -> cs.hasPermission(2)) .executes(CMEjectSubcommand::execExecutingPlayer) .then(Commands.argument("player", EntityArgument.player()) - .executes(CMEjectSubcommand::execSpecificPlayer)); + .requires(cs -> cs.hasPermission(Commands.LEVEL_GAMEMASTERS)) + .executes(CMEjectSubcommand::execSpecificPlayer)); } private static int execSpecificPlayer(CommandContext ctx) throws CommandSyntaxException { diff --git a/src/main/java/dev/compactmods/machines/command/subcommand/CMGiveMachineSubcommand.java b/src/main/java/dev/compactmods/machines/command/subcommand/CMGiveMachineSubcommand.java new file mode 100644 index 00000000..aa2596e9 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/command/subcommand/CMGiveMachineSubcommand.java @@ -0,0 +1,67 @@ +package dev.compactmods.machines.command.subcommand; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.api.core.CMCommands; +import dev.compactmods.machines.api.core.Messages; +import dev.compactmods.machines.command.argument.RoomPositionArgument; +import dev.compactmods.machines.config.ServerConfig; +import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.i18n.TranslationUtil; +import dev.compactmods.machines.machine.CompactMachineItem; +import dev.compactmods.machines.room.RoomSize; +import dev.compactmods.machines.room.Rooms; +import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import net.minecraft.commands.CommandRuntimeException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.world.item.ItemStack; + +public class CMGiveMachineSubcommand { + + public static LiteralArgumentBuilder make() { + final var subRoot = Commands.literal("give") + .requires(cs -> cs.hasPermission(ServerConfig.giveMachineLevel())); + + subRoot.then(Commands.argument("player", EntityArgument.player()) + .then(Commands.argument("room", RoomPositionArgument.room()) + .executes(CMGiveMachineSubcommand::giveMachine))); + + return subRoot; + } + + private static int giveMachine(CommandContext ctx) throws CommandSyntaxException { + final var src = ctx.getSource(); + final var server = src.getServer(); + + final var player = EntityArgument.getPlayer(ctx, "player"); + final var roomPos = RoomPositionArgument.get(ctx, "room"); + + if(!Rooms.exists(server, roomPos)) { + CompactMachines.LOGGER.error("Error giving player a new machine block: room not found."); + src.sendFailure(TranslationUtil.message(Messages.UNKNOWN_ROOM_CHUNK, "%s, %s".formatted(roomPos.x, roomPos.z))); + return -1; + } + + try { + final RoomSize size = Rooms.sizeOf(server, roomPos); + + ItemStack newItem = new ItemStack(CompactMachineItem.getItemBySize(size)); + CompactMachineItem.setRoom(newItem, roomPos); + + if(!player.addItem(newItem)) { + src.sendFailure(TranslationUtil.command(CMCommands.CANNOT_GIVE_MACHINE)); + } else { + src.sendSuccess(TranslationUtil.command(CMCommands.MACHINE_GIVEN, player.getDisplayName()), true); + } + } catch (NonexistentRoomException e) { + CompactMachines.LOGGER.fatal(e); + } + + return 0; + } +} + diff --git a/src/main/java/dev/compactmods/machines/command/subcommand/CMRebindSubcommand.java b/src/main/java/dev/compactmods/machines/command/subcommand/CMRebindSubcommand.java index c0401cce..b7c796b1 100644 --- a/src/main/java/dev/compactmods/machines/command/subcommand/CMRebindSubcommand.java +++ b/src/main/java/dev/compactmods/machines/command/subcommand/CMRebindSubcommand.java @@ -5,27 +5,26 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.core.CMCommands; -import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.command.argument.RoomPositionArgument; +import dev.compactmods.machines.config.ServerConfig; +import dev.compactmods.machines.core.Registration; +import dev.compactmods.machines.machine.graph.DimensionMachineGraph; import dev.compactmods.machines.i18n.TranslationUtil; import dev.compactmods.machines.machine.CompactMachineBlockEntity; -import dev.compactmods.machines.machine.Machines; -import dev.compactmods.machines.machine.exceptions.InvalidMachineStateException; -import dev.compactmods.machines.machine.exceptions.NonexistentMachineException; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import net.minecraft.commands.CommandRuntimeException; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; -import net.minecraft.commands.arguments.coordinates.ColumnPosArgument; -import net.minecraft.world.level.ChunkPos; public class CMRebindSubcommand { public static LiteralArgumentBuilder make() { - final LiteralArgumentBuilder subRoot = LiteralArgumentBuilder.literal("rebind"); + final var subRoot = Commands.literal("rebind") + .requires(cs -> cs.hasPermission(ServerConfig.rebindLevel())); subRoot.then(Commands.argument("pos", BlockPosArgument.blockPos()) - .then(Commands.argument("bindTo", ColumnPosArgument.columnPos()) + .then(Commands.argument("bindTo", RoomPositionArgument.room()) .executes(CMRebindSubcommand::doRebind))); return subRoot; @@ -34,33 +33,31 @@ public static LiteralArgumentBuilder make() { private static int doRebind(CommandContext ctx) throws CommandSyntaxException { final var server = ctx.getSource().getServer(); final var level = ctx.getSource().getLevel(); + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + if(compactDim == null) { + throw new CommandRuntimeException(TranslationUtil.command(CMCommands.LEVEL_NOT_FOUND)); + } final var rebindingMachine = BlockPosArgument.getLoadedBlockPos(ctx, "pos"); - final var roomPos = ColumnPosArgument.getColumnPos(ctx, "bindTo"); - final var roomPos2 = new ChunkPos(roomPos.x, roomPos.z); + final var roomPos = RoomPositionArgument.get(ctx, "bindTo"); + + CompactMachines.LOGGER.debug("Binding machine at {} to room chunk {}", rebindingMachine, roomPos); - if(!(level.getBlockEntity(rebindingMachine) instanceof CompactMachineBlockEntity machineData)) { + if(!(level.getBlockEntity(rebindingMachine) instanceof CompactMachineBlockEntity machine)) { CompactMachines.LOGGER.error("Refusing to rebind block at {}; block has invalid machine data.", rebindingMachine); throw new CommandRuntimeException(TranslationUtil.command(CMCommands.NOT_A_MACHINE_BLOCK)); } - if(!machineData.mapped()) { - CompactMachines.LOGGER.error("Refusing to change binding for machine at {}; machine has no ID assigned yet. (Unmapped)", rebindingMachine); - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.MACHINE_NOT_BOUND, rebindingMachine.toShortString())); - } + machine.getConnectedRoom().ifPresentOrElse(currentRoom -> { + final var currentRoomTunnels = TunnelConnectionGraph.forRoom(compactDim, currentRoom); + final var firstTunnel = currentRoomTunnels.getConnections(machine.getLevelPosition()).findFirst(); + firstTunnel.ifPresent(ft -> { + throw new CommandRuntimeException(TranslationUtil.command(CMCommands.NO_REBIND_TUNNEL_PRESENT, ft)); + }); - try { - Machines.changeLink(server, machineData.machineId, roomPos2); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.error("Failed to rebind a machine to a different room: room data not found", e); - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.ROOM_DATA_NOT_FOUND)); - } catch (NonexistentRoomException e) { - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.CMD_ROOM_NOT_REGISTERED)); - } catch (NonexistentMachineException e) { - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.CMD_MACHINE_NOT_REGISTERED)); - } catch (InvalidMachineStateException e) { - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.CMD_BAD_STATE)); - } + // No tunnels - clear to rebind + machine.setConnectedRoom(roomPos); + }, () -> machine.setConnectedRoom(roomPos)); return 0; } diff --git a/src/main/java/dev/compactmods/machines/command/subcommand/CMRoomsSubcommand.java b/src/main/java/dev/compactmods/machines/command/subcommand/CMRoomsSubcommand.java index 7ffe92de..0e89e3d7 100644 --- a/src/main/java/dev/compactmods/machines/command/subcommand/CMRoomsSubcommand.java +++ b/src/main/java/dev/compactmods/machines/command/subcommand/CMRoomsSubcommand.java @@ -11,10 +11,11 @@ import dev.compactmods.machines.i18n.TranslationUtil; import dev.compactmods.machines.machine.CompactMachineBlock; import dev.compactmods.machines.machine.CompactMachineBlockEntity; -import dev.compactmods.machines.machine.Machines; +import dev.compactmods.machines.room.RoomSize; import dev.compactmods.machines.room.Rooms; import dev.compactmods.machines.room.data.CompactRoomData; import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandRuntimeException; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -49,28 +50,15 @@ public static LiteralArgumentBuilder make() { private static int fetchByMachineBlock(CommandContext ctx) throws CommandSyntaxException { final var block = BlockPosArgument.getLoadedBlockPos(ctx, "pos"); final var level = ctx.getSource().getLevel(); - final var server = ctx.getSource().getServer(); if(!(level.getBlockState(block).getBlock() instanceof CompactMachineBlock b)) throw new CommandRuntimeException(TranslationUtil.command(CMCommands.NOT_A_MACHINE_BLOCK)); if(level.getBlockEntity(block) instanceof CompactMachineBlockEntity be) { - if(!be.mapped()) - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.MACHINE_NOT_BOUND, block.toShortString())); - - try { - final var info = Machines.getConnectedRoom(server, be.machineId) - .orElseThrow(() -> new CommandRuntimeException(TranslationUtil.command(CMCommands.ROOM_DATA_NOT_FOUND))); - - final var size = Rooms.sizeOf(server, info.chunk()); - - final var m = TranslationUtil.message(Messages.MACHINE_ROOM_INFO, block, size, info.chunk()); + be.getConnectedRoom().ifPresent(room -> { + final var m = TranslationUtil.message(Messages.MACHINE_ROOM_INFO, block, b.getSize(), room); ctx.getSource().sendSuccess(m, false); - } catch (MissingDimensionException e) { - e.printStackTrace(); - } catch (NonexistentRoomException e) { - e.printStackTrace(); - } + }); } return 0; @@ -91,11 +79,12 @@ private static int findByContainingPlayer(CommandContext ctx final var roomSize = Rooms.sizeOf(server, playerChunk); final var m = TranslationUtil.message(Messages.PLAYER_ROOM_INFO, player.getDisplayName(), playerChunk.toString(), roomSize); ctx.getSource().sendSuccess(m, false); - } catch (MissingDimensionException e) { - throw new CommandRuntimeException(TranslationUtil.message(Messages.UNREGISTERED_CM_DIM)); } catch (NonexistentRoomException e) { CompactMachines.LOGGER.error("Player is inside an unregistered chunk ({}) in the compact world.", playerChunk, e); - throw new CommandRuntimeException(TranslationUtil.message(Messages.UNKNOWN_ROOM_CHUNK)); + final var tc = new TextComponent("%s, %s".formatted(playerChunk.x, playerChunk.z)) + .withStyle(ChatFormatting.RED); + + throw new CommandRuntimeException(TranslationUtil.message(Messages.UNKNOWN_ROOM_CHUNK, tc)); } return 0; @@ -104,19 +93,15 @@ private static int findByContainingPlayer(CommandContext ctx public static int findByOwner(CommandContext ctx) throws CommandSyntaxException { final var owner = EntityArgument.getPlayer(ctx, "owner"); final var server = ctx.getSource().getServer(); + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); - try { - final var rooms = CompactRoomData.get(server); - rooms.streamRooms() - .filter(r -> r.getOwner().equals(owner.getUUID())) - .forEach(data -> { - ctx.getSource().sendSuccess(new TextComponent("Room: " + new ChunkPos(data.getCenter())), false); - }); - } + final var rooms = CompactRoomData.get(compactDim); + rooms.streamRooms() + .filter(r -> r.getOwner().equals(owner.getUUID())) + .forEach(data -> { + ctx.getSource().sendSuccess(new TextComponent("Room: " + new ChunkPos(data.getCenter())), false); + }); - catch(MissingDimensionException e) { - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.LEVEL_NOT_FOUND)); - } return 0; } } diff --git a/src/main/java/dev/compactmods/machines/command/subcommand/CMSummarySubcommand.java b/src/main/java/dev/compactmods/machines/command/subcommand/CMSummarySubcommand.java index 3c974fd4..ffec87db 100644 --- a/src/main/java/dev/compactmods/machines/command/subcommand/CMSummarySubcommand.java +++ b/src/main/java/dev/compactmods/machines/command/subcommand/CMSummarySubcommand.java @@ -3,26 +3,26 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.core.CMCommands; -import dev.compactmods.machines.core.MissingDimensionException; import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.machine.data.CompactMachineData; +import dev.compactmods.machines.machine.graph.DimensionMachineGraph; import dev.compactmods.machines.room.data.CompactRoomData; import dev.compactmods.machines.i18n.TranslationUtil; import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandRuntimeException; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; + +import java.util.HashMap; public class CMSummarySubcommand { public static ArgumentBuilder make() { return Commands.literal("summary") - .requires(cs -> cs.hasPermission(Commands.LEVEL_ALL)) .executes(CMSummarySubcommand::exec); } - private static int exec(CommandContext ctx) throws CommandSyntaxException { + private static int exec(CommandContext ctx) { var src = ctx.getSource(); var serv = src.getServer(); @@ -33,25 +33,24 @@ private static int exec(CommandContext ctx) throws CommandSy src.sendSuccess(TranslationUtil.command(CMCommands.LEVEL_NOT_FOUND).withStyle(ChatFormatting.RED), false); } - try { - final var machineData = CompactMachineData.get(serv); + HashMap, Long> levelCounts = new HashMap<>(); + serv.getAllLevels().forEach(sl -> { + final var machineData = DimensionMachineGraph.forDimension(sl); + long numRegistered = machineData.getMachines().count(); - long numRegistered = machineData.stream().count(); - src.sendSuccess(TranslationUtil.command(CMCommands.MACHINE_REG_COUNT, numRegistered), false); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.LEVEL_NOT_FOUND)); - } + if(numRegistered > 0) { + src.sendSuccess(TranslationUtil.command(CMCommands.MACHINE_REG_DIM, sl.dimension().toString(), numRegistered), false); + levelCounts.put(sl.dimension(), numRegistered); + } + }); - try { - final var roomData = CompactRoomData.get(serv); + long grandTotal = levelCounts.values().stream().reduce(0L, Long::sum); + src.sendSuccess(TranslationUtil.command(CMCommands.MACHINE_REG_TOTAL, grandTotal).withStyle(ChatFormatting.GOLD), false); - long numRegistered = roomData.stream().count(); - src.sendSuccess(TranslationUtil.command(CMCommands.ROOM_REG_COUNT, numRegistered), false); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.LEVEL_NOT_FOUND)); - } + final var roomData = CompactRoomData.get(compactLevel); + + long numRegistered = roomData.stream().count(); + src.sendSuccess(TranslationUtil.command(CMCommands.ROOM_REG_COUNT, numRegistered), false); return 0; } diff --git a/src/main/java/dev/compactmods/machines/command/subcommand/CMUnbindSubcommand.java b/src/main/java/dev/compactmods/machines/command/subcommand/CMUnbindSubcommand.java new file mode 100644 index 00000000..124c7643 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/command/subcommand/CMUnbindSubcommand.java @@ -0,0 +1,60 @@ +package dev.compactmods.machines.command.subcommand; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.api.core.CMCommands; +import dev.compactmods.machines.command.argument.RoomPositionArgument; +import dev.compactmods.machines.config.ServerConfig; +import dev.compactmods.machines.core.Registration; +import dev.compactmods.machines.i18n.TranslationUtil; +import dev.compactmods.machines.machine.CompactMachineBlockEntity; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; +import net.minecraft.commands.CommandRuntimeException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; + +public class CMUnbindSubcommand { + + public static LiteralArgumentBuilder make() { + final var subRoot = Commands.literal("unbind") + .requires(cs -> cs.hasPermission(ServerConfig.rebindLevel())); + + subRoot.then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(CMUnbindSubcommand::doUnbind)); + + return subRoot; + } + + private static int doUnbind(CommandContext ctx) throws CommandSyntaxException { + final var server = ctx.getSource().getServer(); + final var level = ctx.getSource().getLevel(); + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + if (compactDim == null) { + throw new CommandRuntimeException(TranslationUtil.command(CMCommands.LEVEL_NOT_FOUND)); + } + + final var rebindingMachine = BlockPosArgument.getLoadedBlockPos(ctx, "pos"); + + if(!(level.getBlockEntity(rebindingMachine) instanceof CompactMachineBlockEntity machine)) { + CompactMachines.LOGGER.error("Refusing to rebind block at {}; block has invalid machine data.", rebindingMachine); + throw new CommandRuntimeException(TranslationUtil.command(CMCommands.NOT_A_MACHINE_BLOCK)); + } + + machine.getConnectedRoom().ifPresentOrElse(currentRoom -> { + final var currentRoomTunnels = TunnelConnectionGraph.forRoom(compactDim, currentRoom); + final var firstTunnel = currentRoomTunnels.getConnections(machine.getLevelPosition()).findFirst(); + firstTunnel.ifPresent(ft -> { + throw new CommandRuntimeException(TranslationUtil.command(CMCommands.NO_REBIND_TUNNEL_PRESENT, ft)); + }); + + machine.disconnect(); + }, () -> { + throw new CommandRuntimeException(TranslationUtil.command(CMCommands.MACHINE_NOT_BOUND, rebindingMachine.toShortString())); + }); + + return 0; + } +} diff --git a/src/main/java/dev/compactmods/machines/compat/theoneprobe/TheOneProbeMain.java b/src/main/java/dev/compactmods/machines/compat/theoneprobe/TheOneProbeMain.java index 5a268f48..a8145fda 100644 --- a/src/main/java/dev/compactmods/machines/compat/theoneprobe/TheOneProbeMain.java +++ b/src/main/java/dev/compactmods/machines/compat/theoneprobe/TheOneProbeMain.java @@ -1,10 +1,11 @@ package dev.compactmods.machines.compat.theoneprobe; -import java.util.function.Function; import dev.compactmods.machines.compat.theoneprobe.providers.CompactMachineProvider; import dev.compactmods.machines.compat.theoneprobe.providers.TunnelProvider; import mcjty.theoneprobe.api.ITheOneProbe; +import java.util.function.Function; + public class TheOneProbeMain implements Function { static ITheOneProbe PROBE; @@ -16,4 +17,5 @@ public Void apply(Object o) { return null; } + } diff --git a/src/main/java/dev/compactmods/machines/compat/theoneprobe/elements/MachineTunnelElement.java b/src/main/java/dev/compactmods/machines/compat/theoneprobe/elements/MachineTunnelElement.java new file mode 100644 index 00000000..395e951c --- /dev/null +++ b/src/main/java/dev/compactmods/machines/compat/theoneprobe/elements/MachineTunnelElement.java @@ -0,0 +1,33 @@ +package dev.compactmods.machines.compat.theoneprobe.elements; + +import com.mojang.blaze3d.vertex.PoseStack; +import mcjty.theoneprobe.api.IElement; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +public class MachineTunnelElement implements IElement { + @Override + public void render(PoseStack poseStack, int i, int i1) { + + } + + @Override + public int getWidth() { + return 0; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public void toBytes(FriendlyByteBuf friendlyByteBuf) { + + } + + @Override + public ResourceLocation getID() { + return null; + } +} diff --git a/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/CompactMachineProvider.java b/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/CompactMachineProvider.java index 85c346e9..7e399531 100644 --- a/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/CompactMachineProvider.java +++ b/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/CompactMachineProvider.java @@ -2,22 +2,18 @@ import com.mojang.authlib.GameProfile; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.core.CMCommands; -import dev.compactmods.machines.api.core.Messages; import dev.compactmods.machines.api.core.Tooltips; -import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.core.Registration; import dev.compactmods.machines.i18n.TranslationUtil; import dev.compactmods.machines.machine.CompactMachineBlock; import dev.compactmods.machines.machine.CompactMachineBlockEntity; -import dev.compactmods.machines.machine.Machines; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import dev.compactmods.machines.room.data.CompactRoomData; import dev.compactmods.machines.tunnel.TunnelItem; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import mcjty.theoneprobe.api.*; import mcjty.theoneprobe.apiimpl.styles.ItemStyle; import mcjty.theoneprobe.apiimpl.styles.LayoutStyle; import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandRuntimeException; import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Player; @@ -40,33 +36,24 @@ public void addProbeInfo(ProbeMode probeMode, IProbeInfo info, Player player, Le return; final var server = level.getServer(); + if (server == null) + return; + + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + final var te = level.getBlockEntity(hitData.getPos()); if (te instanceof CompactMachineBlockEntity machine) { - if (machine.mapped()) { - MutableComponent id = TranslationUtil - .tooltip(Tooltips.Machines.ID, machine.machineId, hitData.getSideHit()) - .withStyle(ChatFormatting.GREEN); - - info.text(id); - - try { - Machines.getConnectedRoom(server, machine.machineId).ifPresent(roomInfo -> { - final var boundTo = TranslationUtil.tooltip(Tooltips.Machines.BOUND_TO, roomInfo.chunk()); - info.text(boundTo); - }); - } catch (MissingDimensionException e) { - e.printStackTrace(); - } catch (NonexistentRoomException e) { - e.printStackTrace(); - } - } else { + machine.getConnectedRoom().ifPresentOrElse(room -> { + final var boundTo = TranslationUtil.tooltip(Tooltips.Machines.BOUND_TO, room); + info.text(boundTo); + }, () -> { MutableComponent newMachine = TranslationUtil .message(new ResourceLocation(CompactMachines.MOD_ID, "new_machine")) .withStyle(ChatFormatting.GREEN); info.text(newMachine); - } + }); machine.getOwnerUUID().ifPresent(ownerID -> { // Owner Name @@ -81,44 +68,50 @@ public void addProbeInfo(ProbeMode probeMode, IProbeInfo info, Player player, Le } }); - machine.getInternalChunkPos().ifPresent(room -> { - try { - final var tunnels = RoomTunnelData.get(server, room); - final var graph = tunnels.getGraph(); - - final var applied = graph.getTypesForSide(machine.machineId, hitData.getSideHit()) - .collect(Collectors.toSet()); - - switch (probeMode) { - case NORMAL: - final var group = info.horizontal(new LayoutStyle() - .alignment(ElementAlignment.ALIGN_TOPLEFT) - .padding(0) - .spacing(0)); - - applied.forEach(tn -> { - ItemStack item = TunnelItem.createStack(tn); - group.item(item, new ItemStyle().bounds(8, 8)); - }); - break; - - case EXTENDED: - final var tgg = info.vertical(new LayoutStyle().alignment(ElementAlignment.ALIGN_TOPLEFT)); - applied.forEach(tn -> { - final var tg = tgg.horizontal(new LayoutStyle() + machine.getConnectedRoom().ifPresent(room -> { + if (compactDim == null) + return; + + final var roomData = CompactRoomData.get(compactDim); + final var graph = TunnelConnectionGraph.forRoom(compactDim, room); + + final var applied = graph.getTypesForSide(machine.getLevelPosition(), hitData.getSideHit()) + .collect(Collectors.toSet()); + + switch (probeMode) { + case NORMAL: + final var group = info.horizontal(new LayoutStyle() + .alignment(ElementAlignment.ALIGN_TOPLEFT) + .padding(0) + .spacing(0)); + + applied.forEach(tn -> { + ItemStack item = TunnelItem.createStack(tn); + group.item(item, new ItemStyle().bounds(8, 8)); + }); + break; + + case EXTENDED: + final var tgg = info.vertical(new LayoutStyle().alignment(ElementAlignment.ALIGN_TOPLEFT)); + applied.forEach(tn -> { + final var tg = tgg.horizontal(new LayoutStyle() .alignment(ElementAlignment.ALIGN_CENTER) .hPadding(2).vPadding(2) .spacing(0)); - ItemStack item = TunnelItem.createStack(tn); - tg.item(item, new ItemStyle().bounds(8, 8)); - tg.itemLabel(item); - }); - break; - } - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); + ItemStack item = TunnelItem.createStack(tn); + tg.item(item, new ItemStyle().bounds(8, 8)); + tg.itemLabel(item); + }); + break; } + + final var rd = roomData.forRoom(room); + rd.ifPresent(r -> { +// final var el = new RoomPreviewElement(new RoomPreview(room, r.getSize())); +// el.loadBlocks(server, r); +// info.element(el); + }); }); } } diff --git a/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/TunnelProvider.java b/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/TunnelProvider.java index ff8bc88f..16ba5f1d 100644 --- a/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/TunnelProvider.java +++ b/src/main/java/dev/compactmods/machines/compat/theoneprobe/providers/TunnelProvider.java @@ -42,7 +42,9 @@ public void addProbeInfo(ProbeMode probeMode, IProbeInfo info, Player playerEnti IProbeInfo v = info.vertical(info.defaultLayoutStyle().spacing(-1)); if (level.getBlockEntity(hitData.getPos()) instanceof TunnelWallEntity tile) { - final var connectedTo = tile.getConnectedPosition(); + + + if (probeMode == ProbeMode.EXTENDED) { TunnelDefinition definition = tile.getTunnelType(); @@ -65,13 +67,15 @@ public void addProbeInfo(ProbeMode probeMode, IProbeInfo info, Player playerEnti .item(new ItemStack(Items.COMPASS)) .text(new TranslatableComponent(sideTranslated)); - ServerLevel connectedWorld = (ServerLevel) level; - BlockPos outPosBlock = connectedTo.getBlockPosition(); + final var connectedTo = tile.getConnectedPosition(); + if(connectedTo != null) { + ServerLevel connectedWorld = (ServerLevel) level; + BlockPos outPosBlock = connectedTo.getBlockPosition(); + + try { + final var state = connectedTo.state(level.getServer()); - try { - // If connected block isn't air, show a connected block line - if(connectedTo instanceof IDimensionalBlockPosition dbp) { - final var state = dbp.state(level.getServer()); + // If connected block isn't air, show a connected block line if (!state.isAir()) { String blockName = IProbeInfo.STARTLOC + state.getBlock().getDescriptionId() + IProbeInfo.ENDLOC; HitResult trace = new BlockHitResult( @@ -86,9 +90,9 @@ public void addProbeInfo(ProbeMode probeMode, IProbeInfo info, Player playerEnti .item(pick) .text(new TranslatableComponent(CompactMachines.MOD_ID.concat(".connected_block"), blockName)); } + } catch (Exception ex) { + // no-op: we don't want to spam the log here } - } catch (Exception ex) { - // no-op: we don't want to spam the log here } } } diff --git a/src/main/java/dev/compactmods/machines/config/ServerConfig.java b/src/main/java/dev/compactmods/machines/config/ServerConfig.java index 75159948..8ef60c05 100644 --- a/src/main/java/dev/compactmods/machines/config/ServerConfig.java +++ b/src/main/java/dev/compactmods/machines/config/ServerConfig.java @@ -1,7 +1,9 @@ package dev.compactmods.machines.config; import com.electronwill.nightconfig.core.EnumGetMethod; +import dev.compactmods.machines.client.level.EmptyLevelEntityGetter; import dev.compactmods.machines.core.EnumMachinePlayersBreakHandling; +import net.minecraft.commands.Commands; import net.minecraftforge.common.ForgeConfigSpec; import java.util.Arrays; @@ -15,6 +17,9 @@ public class ServerConfig { public static ForgeConfigSpec.IntValue MACHINE_FLOOR_Y; + private static ForgeConfigSpec.IntValue REBIND_LEVEL; + private static ForgeConfigSpec.IntValue GIVE_MACHINE; + static { generateConfig(); } @@ -41,11 +46,31 @@ private static void generateConfig() { EnumGetMethod.NAME_IGNORECASE); MACHINE_FLOOR_Y = builder - .comment("The Y-level to spawn machine floors at.") + .comment("The Y-dimension to spawn machine floors at.") .defineInRange("floor", 40, 10, 200); builder.pop(); + builder + .push("commands") + .push("permLevels") + .comment("Specifies requirements for running administrative commands. Requires a server restart to take effect.") + .comment("0 = ALL, 1 = ADMIN, 2 = OP, 4 = OWNER"); + + + REBIND_LEVEL = builder.defineInRange("rebind", Commands.LEVEL_ALL, Commands.LEVEL_ALL, Commands.LEVEL_OWNERS); + GIVE_MACHINE = builder.defineInRange("give", Commands.LEVEL_ALL, Commands.LEVEL_ALL, Commands.LEVEL_OWNERS); + + builder.pop(2); + CONFIG = builder.build(); } + + public static int rebindLevel() { + return REBIND_LEVEL.get(); + } + + public static int giveMachineLevel() { + return GIVE_MACHINE.get(); + } } diff --git a/src/main/java/dev/compactmods/machines/core/CompactMachinesNet.java b/src/main/java/dev/compactmods/machines/core/CompactMachinesNet.java new file mode 100644 index 00000000..6a410690 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/core/CompactMachinesNet.java @@ -0,0 +1,49 @@ +package dev.compactmods.machines.core; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.room.network.PlayerRequestedTeleportPacket; +import dev.compactmods.machines.tunnel.network.TunnelAddedPacket; +import dev.compactmods.machines.util.VersionUtil; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; + +public class CompactMachinesNet { + private static final ArtifactVersion PROTOCOL_VERSION; + + + static { + if(FMLEnvironment.production) { + PROTOCOL_VERSION = new DefaultArtifactVersion(ModList.get().getModFileById(CompactMachines.MOD_ID).versionString()); + } else { + PROTOCOL_VERSION = new DefaultArtifactVersion("9.99.999"); + } + } + + public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( + new ResourceLocation(CompactMachines.MOD_ID, "main"), + PROTOCOL_VERSION::toString, + clientVer -> VersionUtil.checkMajor(clientVer, PROTOCOL_VERSION), + serverVer -> VersionUtil.checkMajor(serverVer, PROTOCOL_VERSION) + ); + + + public static void setupMessages() { + CHANNEL.messageBuilder(TunnelAddedPacket.class, 1, NetworkDirection.PLAY_TO_CLIENT) + .encoder(TunnelAddedPacket::encode) + .decoder(TunnelAddedPacket::new) + .consumer(TunnelAddedPacket::handle) + .add(); + + CHANNEL.messageBuilder(PlayerRequestedTeleportPacket.class, 2, NetworkDirection.PLAY_TO_SERVER) + .encoder(PlayerRequestedTeleportPacket::encode) + .decoder(PlayerRequestedTeleportPacket::new) + .consumer(PlayerRequestedTeleportPacket::handle) + .add(); + } +} diff --git a/src/main/java/dev/compactmods/machines/core/ModBusEvents.java b/src/main/java/dev/compactmods/machines/core/ModBusEvents.java index 81203afe..feacff0a 100644 --- a/src/main/java/dev/compactmods/machines/core/ModBusEvents.java +++ b/src/main/java/dev/compactmods/machines/core/ModBusEvents.java @@ -2,7 +2,7 @@ import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.advancement.AdvancementTriggers; -import dev.compactmods.machines.network.NetworkHandler; +import dev.compactmods.machines.room.network.RoomNetworkHandler; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -13,7 +13,8 @@ public class ModBusEvents { @SubscribeEvent public static void setup(final FMLCommonSetupEvent event) { CompactMachines.LOGGER.trace("Initializing network handler."); - NetworkHandler.initialize(); + CompactMachinesNet.setupMessages(); + RoomNetworkHandler.setupMessages(); CompactMachines.LOGGER.trace("Registering advancement triggers."); AdvancementTriggers.init(); diff --git a/src/main/java/dev/compactmods/machines/core/ServerEventHandler.java b/src/main/java/dev/compactmods/machines/core/ServerEventHandler.java index 2135949c..f602eb9f 100644 --- a/src/main/java/dev/compactmods/machines/core/ServerEventHandler.java +++ b/src/main/java/dev/compactmods/machines/core/ServerEventHandler.java @@ -2,10 +2,21 @@ import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.command.CMCommandRoot; -import dev.compactmods.machines.command.data.CMDataCommand; +import dev.compactmods.machines.command.data.CMDataSubcommand; +import net.minecraft.network.protocol.game.ClientboundInitializeBorderPacket; +import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.border.BorderChangeListener; +import net.minecraft.world.level.border.WorldBorder; import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.network.PacketDistributor; + +import java.util.stream.Collectors; @Mod.EventBusSubscriber(modid = CompactMachines.MOD_ID) public class ServerEventHandler { @@ -14,6 +25,53 @@ public class ServerEventHandler { public static void onCommandsRegister(final RegisterCommandsEvent event) { final var dispatcher = event.getDispatcher(); CMCommandRoot.register(dispatcher); - CMDataCommand.register(dispatcher); + CMDataSubcommand.make(); + } + + @SubscribeEvent + public static void onWorldLoaded(final WorldEvent.Load evt) { + if(evt.getWorld() instanceof ServerLevel sl && sl.dimension().equals(Registration.COMPACT_DIMENSION)) + { + final var serv = sl.getServer(); + final var owBorder = serv.overworld().getWorldBorder(); + final var cwBorder = sl.getWorldBorder(); + + // Filter border listeners down to the compact world, then remove them from the OW listener list + final var listeners = owBorder.listeners.stream() + .filter(border -> border instanceof BorderChangeListener.DelegateBorderChangeListener) + .map(BorderChangeListener.DelegateBorderChangeListener.class::cast) + .filter(list -> list.worldBorder == cwBorder) + .collect(Collectors.toSet()); + + for(var listener : listeners) + owBorder.removeListener(listener); + + // Fix set compact world border if it was loaded weirdly + cwBorder.setCenter(0, 0); + cwBorder.setSize(WorldBorder.MAX_SIZE); + PacketDistributor.DIMENSION.with(() -> Registration.COMPACT_DIMENSION) + .send(new ClientboundSetBorderSizePacket(cwBorder)); + + } + } + + @SubscribeEvent + public static void onPlayerLogin(final PlayerEvent.PlayerLoggedInEvent evt) { + final var player = evt.getPlayer(); + if(player.level.dimension().equals(Registration.COMPACT_DIMENSION) && player instanceof ServerPlayer sp) { + // Send a fake world border to the player instead of the "real" one in overworld + sp.connection.send(new ClientboundInitializeBorderPacket(new WorldBorder())); + } + } + + @SubscribeEvent + public static void onPlayerDimChange(final PlayerEvent.PlayerChangedDimensionEvent evt) { + if(evt.getTo().equals(Registration.COMPACT_DIMENSION)) { + final var player = evt.getPlayer(); + if(player instanceof ServerPlayer sp) { + // Send a fake world border to the player instead of the "real" one in overworld + sp.connection.send(new ClientboundInitializeBorderPacket(new WorldBorder())); + } + } } } diff --git a/src/main/java/dev/compactmods/machines/core/Tunnels.java b/src/main/java/dev/compactmods/machines/core/Tunnels.java index 092623b1..98498cfb 100644 --- a/src/main/java/dev/compactmods/machines/core/Tunnels.java +++ b/src/main/java/dev/compactmods/machines/core/Tunnels.java @@ -1,5 +1,6 @@ package dev.compactmods.machines.core; +import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.tunnels.TunnelDefinition; import dev.compactmods.machines.tunnel.TunnelItem; import dev.compactmods.machines.tunnel.TunnelWallBlock; @@ -30,11 +31,11 @@ public class Tunnels { // region Setup - public static final DeferredRegister DEFINITIONS = DeferredRegister.create(TunnelDefinition.class, MOD_ID); + public static final ResourceLocation DEFINITIONS_RL = new ResourceLocation(MOD_ID, "tunnel_types"); + public static final DeferredRegister DEFINITIONS = DeferredRegister.create(DEFINITIONS_RL, MOD_ID); - public static final Supplier> TUNNEL_DEF_REGISTRY = DEFINITIONS.makeRegistry("tunnel_types", - () -> new RegistryBuilder() - .setType(TunnelDefinition.class)); + public static final Supplier> TUNNEL_DEF_REGISTRY = DEFINITIONS.makeRegistry(TunnelDefinition.class, + () -> new RegistryBuilder().setName(DEFINITIONS_RL)); public static void init(IEventBus bus) { DEFINITIONS.register(bus); @@ -46,7 +47,9 @@ public static boolean isRegistered(ResourceLocation id) { } public static TunnelDefinition getDefinition(ResourceLocation id) { - return isRegistered(id) ? TUNNEL_DEF_REGISTRY.get().getValue(id) : Tunnels.UNKNOWN.get(); + if (isRegistered(id)) return TUNNEL_DEF_REGISTRY.get().getValue(id); + CompactMachines.LOGGER.warn("Unknown tunnel requested: {}", id); + return Tunnels.UNKNOWN.get(); } // ================================================================================================================ diff --git a/src/main/java/dev/compactmods/machines/core/UIRegistration.java b/src/main/java/dev/compactmods/machines/core/UIRegistration.java new file mode 100644 index 00000000..50d40ff4 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/core/UIRegistration.java @@ -0,0 +1,29 @@ +package dev.compactmods.machines.core; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.room.menu.MachineRoomMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraftforge.common.extensions.IForgeMenuType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class UIRegistration { + private static final DeferredRegister> CONTAINERS = DeferredRegister.create(ForgeRegistries.CONTAINERS, CompactMachines.MOD_ID); + + public static final RegistryObject> MACHINE_MENU = CONTAINERS.register("machine", () -> IForgeMenuType.create( + ((windowId, inv, data) -> { + data.readBlockPos(); + final var mach = data.readWithCodec(LevelBlockPosition.CODEC); + final var room = data.readChunkPos(); + + return new MachineRoomMenu(windowId, room, mach); + }) + )); + + public static void init(IEventBus bus) { + CONTAINERS.register(bus); + } +} diff --git a/src/main/java/dev/compactmods/machines/graph/CMGraphRegistration.java b/src/main/java/dev/compactmods/machines/graph/CMGraphRegistration.java new file mode 100644 index 00000000..18da8948 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/CMGraphRegistration.java @@ -0,0 +1,46 @@ +package dev.compactmods.machines.graph; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.machine.graph.CompactMachineNode; +import dev.compactmods.machines.room.graph.CompactMachineRoomNode; +import dev.compactmods.machines.tunnel.graph.TunnelNode; +import dev.compactmods.machines.tunnel.graph.TunnelTypeNode; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.RegistryBuilder; +import net.minecraftforge.registries.RegistryObject; + +import java.util.function.Supplier; + +public class CMGraphRegistration { + + public static final ResourceLocation NODES_RL = new ResourceLocation(CompactMachines.MOD_ID, "graph_nodes"); + public static final DeferredRegister NODE_TYPES = DeferredRegister.create(NODES_RL, CompactMachines.MOD_ID); + public static final Supplier> NODE_TYPE_REG = NODE_TYPES.makeRegistry(IGraphNodeType.class, + () -> new RegistryBuilder().setName(NODES_RL)); + + public static final ResourceLocation EDGES_RL = new ResourceLocation(CompactMachines.MOD_ID, "graph_edges"); + public static final DeferredRegister EDGE_TYPES = DeferredRegister.create(EDGES_RL, CompactMachines.MOD_ID); + public static final Supplier> EDGE_TYPE_REG = EDGE_TYPES.makeRegistry(IGraphEdgeType.class, + () -> new RegistryBuilder().setName(EDGES_RL)); + + + public static final RegistryObject MACH_NODE = NODE_TYPES.register("machine", () -> GraphNodeType.MACHINE); + public static final RegistryObject DIM_NODE = NODE_TYPES.register("dimension", () -> GraphNodeType.DIMENSION); + public static final RegistryObject ROOM_NODE = NODE_TYPES.register("room", () -> GraphNodeType.ROOM); + public static final RegistryObject TUNNEL_NODE = NODE_TYPES.register("tunnel", () -> GraphNodeType.TUNNEL); + public static final RegistryObject TUNNEL_TYPE_NODE = NODE_TYPES.register("tunnel_type", () -> GraphNodeType.TUNNEL_TYPE); + + public static final RegistryObject MACHINE_LINK = EDGE_TYPES.register("machine_link", () -> GraphEdgeType.MACHINE_LINK); + + // Tunnel edges + public static final RegistryObject TUNNEL_TYPE = EDGE_TYPES.register("tunnel_type", () -> GraphEdgeType.TUNNEL_TYPE); + public static final RegistryObject TUNNEL_MACHINE_LINK = EDGE_TYPES.register("tunnel_machine", () -> GraphEdgeType.TUNNEL_MACHINE); + + public static void init(IEventBus bus) { + NODE_TYPES.register(bus); + EDGE_TYPES.register(bus); + } +} diff --git a/src/main/java/dev/compactmods/machines/graph/CompactGraphs.java b/src/main/java/dev/compactmods/machines/graph/CompactGraphs.java deleted file mode 100644 index 58208849..00000000 --- a/src/main/java/dev/compactmods/machines/graph/CompactGraphs.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.compactmods.machines.graph; - -import com.mojang.serialization.Codec; -import dev.compactmods.machines.machine.graph.CompactMachineNode; -import dev.compactmods.machines.tunnel.graph.TunnelMachineEdge; -import dev.compactmods.machines.tunnel.graph.TunnelNode; -import dev.compactmods.machines.tunnel.graph.TunnelTypeEdge; -import dev.compactmods.machines.tunnel.graph.TunnelTypeNode; -import net.minecraft.resources.ResourceLocation; - -import javax.annotation.Nullable; -import java.util.Locale; - -public class CompactGraphs { - - @Nullable - @SuppressWarnings("unchecked") - public static Codec getCodecForNode(ResourceLocation nodeType) { - Codec codec = switch (nodeType.getPath().toLowerCase(Locale.ROOT)) { - case "machine" -> CompactMachineNode.CODEC; - case "tunnel" -> TunnelNode.CODEC; - case "tunnel_type" -> TunnelTypeNode.CODEC; - default -> null; - }; - - if (codec == null) - return null; - - return (Codec) codec; - } - - @Nullable - @SuppressWarnings("unchecked") - public static Codec getCodecForEdge(ResourceLocation edgeType) { - Codec codec = switch (edgeType.getPath().toLowerCase(Locale.ROOT)) { - case "tunnel_machine" -> TunnelMachineEdge.CODEC; - case "tunnel_type" -> TunnelTypeEdge.CODEC; - default -> null; - }; - - if (codec == null) - return null; - - return (Codec) codec; - } -} diff --git a/src/main/java/dev/compactmods/machines/graph/CompactMachineConnectionGraph.java b/src/main/java/dev/compactmods/machines/graph/CompactMachineConnectionGraph.java deleted file mode 100644 index aa9751e8..00000000 --- a/src/main/java/dev/compactmods/machines/graph/CompactMachineConnectionGraph.java +++ /dev/null @@ -1,191 +0,0 @@ -package dev.compactmods.machines.graph; - -import com.google.common.collect.ImmutableList; -import com.google.common.graph.MutableValueGraph; -import com.google.common.graph.ValueGraphBuilder; -import com.mojang.serialization.Codec; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.codec.CodecExtensions; -import dev.compactmods.machines.machine.graph.CompactMachineNode; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import dev.compactmods.machines.room.graph.CompactMachineRoomNode; -import net.minecraft.world.level.ChunkPos; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Stores information on how external machines connect to the rooms in the compact machine - * dimension. - */ -public class CompactMachineConnectionGraph { - - private final MutableValueGraph graph; - private final Map machines; - private final Map rooms; - - public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( - CompactMachineConnectionInfo.CODEC.listOf() - .fieldOf("connections") - .forGetter(CompactMachineConnectionGraph::buildConnections) - ).apply(i, CompactMachineConnectionGraph::new)); - - - - public CompactMachineConnectionGraph() { - graph = ValueGraphBuilder - .directed() - .build(); - - machines = new HashMap<>(); - rooms = new HashMap<>(); - } - - private CompactMachineConnectionGraph(List connections) { - this(); - - for(CompactMachineConnectionInfo i : connections) { - addRoom(i.roomChunk); - for(int connectedMachine : i.machines()) { - addMachine(connectedMachine); - connectMachineToRoom(connectedMachine, i.roomChunk); - } - } - } - - private List buildConnections() { - List result = new ArrayList<>(); - this.rooms.forEach((chunk, node) -> { - try { - Collection machines = this.getMachinesFor(chunk); - CompactMachineConnectionInfo roomInfo = new CompactMachineConnectionInfo(chunk, machines); - result.add(roomInfo); - } catch (NonexistentRoomException e) { - CompactMachines.LOGGER.error(e); - } - }); - - return result; - } - - public void addMachine(int machine) { - if(this.machines.containsKey(machine)) - return; - - CompactMachineNode node = new CompactMachineNode(machine); - graph.addNode(node); - machines.put(machine, node); - } - - public void addRoom(ChunkPos roomChunk) { - if(this.rooms.containsKey(roomChunk)) - return; - - CompactMachineRoomNode node = new CompactMachineRoomNode(roomChunk); - graph.addNode(node); - rooms.put(roomChunk, node); - } - - public void connectMachineToRoom(int machine, ChunkPos room) { - if(!machines.containsKey(machine)) - addMachine(machine); - - if(!rooms.containsKey(room)) - addRoom(room); - - CompactMachineNode machineNode = machines.get(machine); - CompactMachineRoomNode roomNode = rooms.get(room); - - graph.putEdgeValue(machineNode, roomNode, DefaultEdges.machineToRoom()); - } - - public Collection getMachinesFor(ChunkPos machineChunk) throws NonexistentRoomException { - var node = this.rooms.get(machineChunk); - if(node == null) - throw new NonexistentRoomException(machineChunk); - - var inbound = graph.predecessors(node); - return inbound.stream() - .filter(ibn -> ibn instanceof CompactMachineNode) - .map(ibn -> (CompactMachineNode) ibn) - .map(CompactMachineNode::machineId) - .collect(Collectors.toSet()); - } - - public Optional getConnectedRoom(int machine) { - if(!this.machines.containsKey(machine)) - return Optional.empty(); - - var node = this.machines.get(machine); - var connected = this.graph.successors(node); - return connected.stream() - .filter(n -> n instanceof CompactMachineRoomNode) - .map(n -> (CompactMachineRoomNode) n) - .map(CompactMachineRoomNode::pos) - .findFirst(); - } - - public Stream getMachines() { - return this.machines.values().stream(); - } - - public void disconnectAndUnregister(int machine) { - if(!machines.containsKey(machine)) - return; - - final var node = machines.get(machine); - graph.removeNode(node); - machines.remove(machine); - } - - public void removeRoom(ChunkPos room) { - if(!this.rooms.containsKey(room)) - return; - - graph.removeNode(rooms.get(room)); - rooms.remove(room); - } - - public void disconnect(int machine) { - if(!machines.containsKey(machine)) - return; - - final var node = machines.get(machine); - graph.successors(node).stream() - .filter(cn -> cn instanceof CompactMachineRoomNode) - .forEach(room -> graph.removeEdge(node, room)); - } - - /** - * Data structure for serialization. Do not use directly. - */ - private static class CompactMachineConnectionInfo { - private final ChunkPos roomChunk; - private final List connectedMachines; - - public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( - CodecExtensions.CHUNKPOS - .fieldOf("machine") - .forGetter(CompactMachineConnectionInfo::room), - - Codec.INT.listOf() - .fieldOf("connections") - .forGetter(CompactMachineConnectionInfo::machines) - ).apply(i, CompactMachineConnectionInfo::new)); - - public CompactMachineConnectionInfo(ChunkPos roomChunk, Collection connections) { - this.roomChunk = roomChunk; - this.connectedMachines = ImmutableList.copyOf(connections); - } - - public ChunkPos room() { - return this.roomChunk; - } - - public List machines() { - return this.connectedMachines; - } - } -} diff --git a/src/main/java/dev/compactmods/machines/graph/DefaultEdges.java b/src/main/java/dev/compactmods/machines/graph/DefaultEdges.java deleted file mode 100644 index 843315db..00000000 --- a/src/main/java/dev/compactmods/machines/graph/DefaultEdges.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.compactmods.machines.graph; - -import dev.compactmods.machines.machine.graph.MachineLinkEdge; - -public class DefaultEdges { - - public static IGraphEdge machineToRoom(){ - return new MachineLinkEdge(); - } - -} diff --git a/src/main/java/dev/compactmods/machines/graph/DimensionGraphNode.java b/src/main/java/dev/compactmods/machines/graph/DimensionGraphNode.java new file mode 100644 index 00000000..4a328166 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/DimensionGraphNode.java @@ -0,0 +1,27 @@ +package dev.compactmods.machines.graph; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; + +import java.util.Objects; + +public record DimensionGraphNode(ResourceKey dimension) implements IGraphNode { + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + ResourceKey.codec(Registry.DIMENSION_REGISTRY).fieldOf("dim").forGetter(DimensionGraphNode::dimension) + ).apply(i, DimensionGraphNode::new)); + + @Override + public String toString() { + return "DimensionGraphNode[" + + "dimension=" + dimension + ']'; + } + + @Override + public IGraphNodeType getType() { + return CMGraphRegistration.DIM_NODE.get(); + } +} diff --git a/src/main/java/dev/compactmods/machines/graph/GraphEdgeType.java b/src/main/java/dev/compactmods/machines/graph/GraphEdgeType.java new file mode 100644 index 00000000..8d60165b --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/GraphEdgeType.java @@ -0,0 +1,44 @@ +package dev.compactmods.machines.graph; + +import com.mojang.serialization.Codec; +import dev.compactmods.machines.machine.graph.MachineRoomEdge; +import dev.compactmods.machines.tunnel.graph.TunnelMachineEdge; +import dev.compactmods.machines.tunnel.graph.TunnelTypeEdge; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +public enum GraphEdgeType implements IGraphEdgeType { + TUNNEL_TYPE(TunnelTypeEdge.CODEC), + MACHINE_LINK(MachineRoomEdge.CODEC), + TUNNEL_MACHINE(TunnelMachineEdge.CODEC); + + private final Codec codec; + private ResourceLocation regName; + + @SuppressWarnings("unchecked") + GraphEdgeType(Codec codec) { + this.codec = (Codec) codec; + } + + @Override + public Codec codec() { + return codec; + } + + @Override + public IGraphEdgeType setRegistryName(ResourceLocation name) { + this.regName = name; + return this; + } + + @Nullable + @Override + public ResourceLocation getRegistryName() { + return regName; + } + + @Override + public Class getRegistryType() { + return CMGraphRegistration.EDGE_TYPE_REG.get().getRegistrySuperType(); + } +} diff --git a/src/main/java/dev/compactmods/machines/graph/GraphNodeBase.java b/src/main/java/dev/compactmods/machines/graph/GraphNodeBase.java new file mode 100644 index 00000000..7aed1776 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/GraphNodeBase.java @@ -0,0 +1,8 @@ +package dev.compactmods.machines.graph; + +import net.minecraftforge.registries.ForgeRegistryEntry; + +public abstract class GraphNodeBase + extends ForgeRegistryEntry + implements IGraphNode { +} diff --git a/src/main/java/dev/compactmods/machines/graph/GraphNodeType.java b/src/main/java/dev/compactmods/machines/graph/GraphNodeType.java new file mode 100644 index 00000000..afcc3e0c --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/GraphNodeType.java @@ -0,0 +1,48 @@ +package dev.compactmods.machines.graph; + +import com.mojang.serialization.Codec; +import dev.compactmods.machines.machine.graph.CompactMachineNode; +import dev.compactmods.machines.room.graph.CompactMachineRoomNode; +import dev.compactmods.machines.tunnel.graph.TunnelNode; +import dev.compactmods.machines.tunnel.graph.TunnelTypeNode; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +public enum GraphNodeType implements IGraphNodeType { + MACHINE(CompactMachineNode.CODEC), + TUNNEL(TunnelNode.CODEC), + ROOM(CompactMachineRoomNode.CODEC), + TUNNEL_TYPE(TunnelTypeNode.CODEC), + DIMENSION(DimensionGraphNode.CODEC); + + private final Codec codec; + private ResourceLocation regName; + + @SuppressWarnings("unchecked") + GraphNodeType(Codec codec) { + this.codec = (Codec) codec; + } + + @Override + @SuppressWarnings("unchecked") + public Codec codec() { + return codec; + } + + @Override + public IGraphNodeType setRegistryName(ResourceLocation name) { + this.regName = name; + return this; + } + + @Nullable + @Override + public ResourceLocation getRegistryName() { + return regName; + } + + @Override + public Class getRegistryType() { + return CMGraphRegistration.NODE_TYPE_REG.get().getRegistrySuperType(); + } +} diff --git a/src/main/java/dev/compactmods/machines/graph/IGraphEdge.java b/src/main/java/dev/compactmods/machines/graph/IGraphEdge.java index 2d45c2bd..0ede60fb 100644 --- a/src/main/java/dev/compactmods/machines/graph/IGraphEdge.java +++ b/src/main/java/dev/compactmods/machines/graph/IGraphEdge.java @@ -1,7 +1,7 @@ package dev.compactmods.machines.graph; -import com.mojang.serialization.Codec; +import org.jetbrains.annotations.NotNull; public interface IGraphEdge { - Codec codec(); + @NotNull IGraphEdgeType getEdgeType(); } diff --git a/src/main/java/dev/compactmods/machines/graph/IGraphEdgeType.java b/src/main/java/dev/compactmods/machines/graph/IGraphEdgeType.java new file mode 100644 index 00000000..aa6f2de8 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/IGraphEdgeType.java @@ -0,0 +1,8 @@ +package dev.compactmods.machines.graph; + +import com.mojang.serialization.Codec; +import net.minecraftforge.registries.IForgeRegistryEntry; + +public interface IGraphEdgeType extends IForgeRegistryEntry { + Codec codec(); +} diff --git a/src/main/java/dev/compactmods/machines/graph/IGraphNode.java b/src/main/java/dev/compactmods/machines/graph/IGraphNode.java index 7ebd2cfb..fc75c20d 100644 --- a/src/main/java/dev/compactmods/machines/graph/IGraphNode.java +++ b/src/main/java/dev/compactmods/machines/graph/IGraphNode.java @@ -1,8 +1,7 @@ package dev.compactmods.machines.graph; -import com.mojang.serialization.Codec; +import net.minecraftforge.registries.IForgeRegistryEntry; public interface IGraphNode { - - Codec codec(); + IGraphNodeType getType(); } diff --git a/src/main/java/dev/compactmods/machines/graph/IGraphNodeType.java b/src/main/java/dev/compactmods/machines/graph/IGraphNodeType.java new file mode 100644 index 00000000..87fc3dc3 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/graph/IGraphNodeType.java @@ -0,0 +1,9 @@ +package dev.compactmods.machines.graph; + +import com.mojang.serialization.Codec; +import net.minecraftforge.registries.IForgeRegistryEntry; + +public interface IGraphNodeType extends IForgeRegistryEntry { + + Codec codec(); +} diff --git a/src/main/java/dev/compactmods/machines/item/PersonalShrinkingDevice.java b/src/main/java/dev/compactmods/machines/item/PersonalShrinkingDevice.java index 4c78b96c..4dcabf16 100644 --- a/src/main/java/dev/compactmods/machines/item/PersonalShrinkingDevice.java +++ b/src/main/java/dev/compactmods/machines/item/PersonalShrinkingDevice.java @@ -63,27 +63,23 @@ public InteractionResultHolder use(Level world, Player player, Intera } if (world instanceof ServerLevel && player instanceof ServerPlayer serverPlayer) { - if (serverPlayer.level.dimension() == Registration.COMPACT_DIMENSION) { - ServerLevel serverWorld = serverPlayer.getLevel(); + ServerLevel playerDim = serverPlayer.getLevel(); + if (playerDim.dimension().equals(Registration.COMPACT_DIMENSION)) { if (player.isShiftKeyDown()) { ChunkPos machineChunk = new ChunkPos(player.blockPosition()); - try { - final CompactRoomData intern = CompactRoomData.get(serverWorld.getServer()); + final CompactRoomData intern = CompactRoomData.get(playerDim); - // Use internal data to set new spawn point - intern.setSpawn(machineChunk, player.position()); + // Use internal data to set new spawn point + intern.setSpawn(machineChunk, player.position()); - MutableComponent tc = TranslationUtil.message(Messages.ROOM_SPAWNPOINT_SET) - .withStyle(ChatFormatting.GREEN); + MutableComponent tc = TranslationUtil.message(Messages.ROOM_SPAWNPOINT_SET) + .withStyle(ChatFormatting.GREEN); - player.displayClientMessage(tc, true); + player.displayClientMessage(tc, true); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - } } else { - PlayerUtil.teleportPlayerOutOfMachine(serverWorld, serverPlayer); + PlayerUtil.teleportPlayerOutOfMachine(playerDim, serverPlayer); } } } diff --git a/src/main/java/dev/compactmods/machines/core/LevelBlockPosition.java b/src/main/java/dev/compactmods/machines/location/LevelBlockPosition.java similarity index 88% rename from src/main/java/dev/compactmods/machines/core/LevelBlockPosition.java rename to src/main/java/dev/compactmods/machines/location/LevelBlockPosition.java index c37d489c..9f38c34e 100644 --- a/src/main/java/dev/compactmods/machines/core/LevelBlockPosition.java +++ b/src/main/java/dev/compactmods/machines/location/LevelBlockPosition.java @@ -1,4 +1,4 @@ -package dev.compactmods.machines.core; +package dev.compactmods.machines.location; import javax.annotation.Nonnull; import java.util.Objects; @@ -8,7 +8,6 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.location.IDimensionalBlockPosition; -import dev.compactmods.machines.api.location.IDimensionalPosition; import dev.compactmods.machines.api.codec.CodecExtensions; import dev.compactmods.machines.util.LocationUtil; import net.minecraft.core.BlockPos; @@ -32,10 +31,6 @@ public class LevelBlockPosition implements INBTSerializable, IDimen private Vec3 position; private Vec3 rotation; - /* - Note: We'd use the actual world registry key here, but it static loads the world and does a bunch - of initialization, making it impossible to unit test without booting a whole server up. - */ public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( ResourceKey.codec(Registry.DIMENSION_REGISTRY).fieldOf("dim").forGetter(LevelBlockPosition::getDimension), CodecExtensions.VECTOR3D.fieldOf("pos").forGetter(LevelBlockPosition::getExactPosition), @@ -45,15 +40,21 @@ public class LevelBlockPosition implements INBTSerializable, IDimen private LevelBlockPosition() { } + public LevelBlockPosition(IDimensionalBlockPosition base) { + this.dimension = base.dimensionKey(); + this.position = base.getExactPosition(); + this.rotation = Vec3.ZERO; + } + public LevelBlockPosition(ResourceKey world, BlockPos positionBlock) { this(world, Vec3.ZERO, Vec3.ZERO); this.position = new Vec3(positionBlock.getX(), positionBlock.getY(), positionBlock.getZ()); + this.rotation = Vec3.ZERO; } public LevelBlockPosition(ResourceKey world, Vec3 positionBlock) { this(world, positionBlock, Vec3.ZERO); this.dimension = world; - this.rotation = Vec3.ZERO; } @@ -77,17 +78,10 @@ public BlockState state(MinecraftServer server) { } @Override - public IDimensionalPosition relative(Direction direction) { + public IDimensionalBlockPosition relative(Direction direction) { return new LevelBlockPosition(this.dimension, this.position.add(direction.getStepX(), direction.getStepY(), direction.getStepZ())); } - @Override - public IDimensionalPosition relative(Direction direction, float amount) { - Vec3 a = new Vec3(direction.getStepX(), direction.getStepY(), direction.getStepZ()); - a = a.multiply(amount, amount, amount); - return new LevelBlockPosition(this.dimension, this.position.add(a)); - } - public boolean isLoaded(MinecraftServer server) { final var level = level(server); return level.isLoaded(LocationUtil.vectorToBlockPos(position)); @@ -102,6 +96,9 @@ public static LevelBlockPosition fromNBT(CompoundTag nbt) { @Override public CompoundTag serializeNBT() { + if(this.rotation == null) + this.rotation = Vec3.ZERO; + DataResult nbt = CODEC.encodeStart(NbtOps.INSTANCE, this); return (CompoundTag) nbt.result().orElse(null); } diff --git a/src/main/java/dev/compactmods/machines/location/PreciseDimensionalPosition.java b/src/main/java/dev/compactmods/machines/location/PreciseDimensionalPosition.java new file mode 100644 index 00000000..fb04c2a8 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/location/PreciseDimensionalPosition.java @@ -0,0 +1,113 @@ +package dev.compactmods.machines.location; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.api.codec.CodecExtensions; +import dev.compactmods.machines.api.location.IDimensionalPosition; +import dev.compactmods.machines.util.MathUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +import java.util.Objects; +import java.util.Optional; + +public final class PreciseDimensionalPosition implements IDimensionalPosition { + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + ResourceKey.codec(Registry.DIMENSION_REGISTRY).fieldOf("dim").forGetter(PreciseDimensionalPosition::dimension), + CodecExtensions.VECTOR3D.fieldOf("pos").forGetter(PreciseDimensionalPosition::position), + CodecExtensions.VECTOR3D.optionalFieldOf("rot", Vec3.ZERO).forGetter(x -> x.rotation) + ).apply(i, PreciseDimensionalPosition::new)); + + private final ResourceKey dimension; + private final Vec3 position; + private final Vec3 rotation; + + public PreciseDimensionalPosition(ResourceKey dimension, Vec3 position) { + this.dimension = dimension; + this.position = position; + this.rotation = Vec3.ZERO; + } + + public PreciseDimensionalPosition(ResourceKey dimension, Vec3 position, Vec3 rotation) { + this.dimension = dimension; + this.position = position; + this.rotation = rotation; + } + + public static PreciseDimensionalPosition fromPlayer(Player player) { + return new PreciseDimensionalPosition(player.level.dimension(), player.position(), player.getLookAngle()); + } + + @Override + public BlockPos getBlockPosition() { + return new BlockPos(position.x, position.y, position.z); + } + + @Override + public Vec3 getExactPosition() { + return position; + } + + public Optional rotation() { + return Optional.ofNullable(rotation); + } + + @Override + public ResourceKey dimensionKey() { + return dimension; + } + + @Override + public ServerLevel level(MinecraftServer server) { + return server.getLevel(dimension); + } + + @Override + public IDimensionalPosition relative(Direction direction) { + final var newPos = position.add(direction.getStepX(), direction.getStepY(), direction.getStepZ()); + return new PreciseDimensionalPosition(dimension, newPos); + } + + @Override + public Optional getRotation() { + return Optional.empty(); + } + + public ResourceKey dimension() { + return dimension; + } + + public Vec3 position() { + return position; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (PreciseDimensionalPosition) obj; + return Objects.equals(this.dimension, that.dimension) && + Objects.equals(this.position, that.position); + } + + @Override + public int hashCode() { + return Objects.hash(dimension, position); + } + + @Override + public String toString() { + return "PreciseDimensionalPosition[" + + "dimension=" + dimension + ", " + + "position=" + position + ']'; + } + +} diff --git a/src/main/java/dev/compactmods/machines/machine/CompactMachineBlock.java b/src/main/java/dev/compactmods/machines/machine/CompactMachineBlock.java index fc0ca3c9..6df42e4f 100644 --- a/src/main/java/dev/compactmods/machines/machine/CompactMachineBlock.java +++ b/src/main/java/dev/compactmods/machines/machine/CompactMachineBlock.java @@ -1,19 +1,24 @@ package dev.compactmods.machines.machine; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.machine.MachineNbt; import dev.compactmods.machines.config.ServerConfig; -import dev.compactmods.machines.core.EnumMachinePlayersBreakHandling; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.machine.data.CompactMachineData; +import dev.compactmods.machines.core.*; +import dev.compactmods.machines.location.PreciseDimensionalPosition; +import dev.compactmods.machines.machine.graph.DimensionMachineGraph; +import dev.compactmods.machines.location.LevelBlockPosition; import dev.compactmods.machines.room.RoomSize; +import dev.compactmods.machines.room.Rooms; +import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import dev.compactmods.machines.room.history.PlayerRoomHistoryItem; +import dev.compactmods.machines.room.menu.MachineRoomMenu; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import dev.compactmods.machines.util.PlayerUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.LivingEntity; @@ -28,6 +33,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; +import net.minecraftforge.network.NetworkHooks; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -106,7 +112,7 @@ public void neighborChanged(BlockState state, Level world, BlockPos pos, Block c CompactMachines.LOGGER.warn("Warning: Compact Dimension was null! Cannot fetch internal state for machine neighbor change listener."); } - // TODO - Send notification to level tunnel listeners (API) + // TODO - Send notification to dimension tunnel listeners (API) } } @@ -127,12 +133,10 @@ public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGett Block given = getBySize(this.size); ItemStack stack = new ItemStack(given, 1); - CompoundTag nbt = stack.getOrCreateTag(); - // nbt.putString("size", this.size.getName()); - - CompactMachineBlockEntity tileEntity = (CompactMachineBlockEntity) world.getBlockEntity(pos); - if (tileEntity != null && tileEntity.mapped()) { - nbt.putInt(MachineNbt.ID, tileEntity.machineId); + if (world.getBlockEntity(pos) instanceof CompactMachineBlockEntity tile) { + tile.getConnectedRoom().ifPresent(room -> { + CompactMachineItem.setRoom(stack, room); + }); } return stack; @@ -144,13 +148,8 @@ public void setPlacedBy(Level worldIn, BlockPos pos, BlockState state, @Nullable if (worldIn.isClientSide()) return; - if (worldIn.getBlockEntity(pos) instanceof CompactMachineBlockEntity tile) { - // The machine already has data for some reason - if (tile.machineId != -1) - return; - + if (worldIn.getBlockEntity(pos) instanceof CompactMachineBlockEntity tile && worldIn instanceof ServerLevel sl) { // TODO - Custom machine names - if (!stack.hasTag()) return; @@ -158,12 +157,12 @@ public void setPlacedBy(Level worldIn, BlockPos pos, BlockState state, @Nullable if (nbt == null) return; - if (nbt.contains(MachineNbt.ID)) { - int machineID = nbt.getInt(MachineNbt.ID); - tile.setMachineId(machineID); - } - - tile.doPostPlaced(); + // Machine was previously bound to a room - make a new binding post-place + CompactMachineItem.getRoom(stack).ifPresent(room -> { + final var g = DimensionMachineGraph.forDimension(sl); + g.connectMachineToRoom(pos, room); + tile.syncConnectedRoom(); + }); } } @@ -173,20 +172,58 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player if (level.isClientSide()) return InteractionResult.SUCCESS; - // TODO - Open GUI with machine preview + MinecraftServer server = level.getServer(); ItemStack mainItem = player.getMainHandItem(); - if (mainItem.isEmpty()) - return InteractionResult.PASS; + + if (mainItem.isEmpty() && level.getBlockEntity(pos) instanceof CompactMachineBlockEntity machine) { + if (state.getBlock() instanceof CompactMachineBlock cmBlock) { + machine.getConnectedRoom().ifPresent(room -> { + var size = cmBlock.getSize(); + NetworkHooks.openGui((ServerPlayer) player, MachineRoomMenu.makeProvider(server, room, machine.getLevelPosition()), (buf) -> { + buf.writeBlockPos(pos); + buf.writeWithCodec(LevelBlockPosition.CODEC, machine.getLevelPosition()); + buf.writeChunkPos(room); + }); + }); + } + } // TODO - Item tags instead of direct item reference here if (mainItem.getItem() == Registration.PERSONAL_SHRINKING_DEVICE.get()) { // Try teleport to compact machine dimension - PlayerUtil.teleportPlayerIntoMachine(level, player, pos, size); + if (level.getBlockEntity(pos) instanceof CompactMachineBlockEntity tile) { + tile.getConnectedRoom().ifPresentOrElse(room -> { + try { + PlayerUtil.teleportPlayerIntoMachine(level, player, pos); + } catch (MissingDimensionException e) { + e.printStackTrace(); + } + }, () -> createAndEnterRoom(player, server, tile)); + } } return InteractionResult.SUCCESS; } + private void createAndEnterRoom(Player player, MinecraftServer server, CompactMachineBlockEntity tile) { + try { + final var newRoomPos = Rooms.createNew(server, size, player.getUUID()); + tile.setConnectedRoom(newRoomPos); + + PlayerUtil.teleportPlayerIntoRoom(server, player, newRoomPos, true); + + // Mark the player as inside the machine, set external spawn, and yeet + player.getCapability(Capabilities.ROOM_HISTORY).ifPresent(hist -> { + var entry = PreciseDimensionalPosition.fromPlayer(player); + hist.addHistory(new PlayerRoomHistoryItem(entry, tile.getLevelPosition())); + }); + } catch (MissingDimensionException e) { + CompactMachines.LOGGER.error("Error occurred while generating new room and machine info for first player entry.", e); + } catch (NonexistentRoomException e) { + CompactMachines.LOGGER.error("Error occurred while generating new room and machine info for first player entry.", e); + } + } + public RoomSize getSize() { return this.size; } @@ -206,14 +243,21 @@ public void onRemove(BlockState oldState, Level level, BlockPos pos, BlockState return; } - if (level.getBlockEntity(pos) instanceof CompactMachineBlockEntity entity) { - if (entity.mapped()) { - try { - final CompactMachineData machines = CompactMachineData.get(server); - machines.remove(entity.machineId); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - } + if (level instanceof ServerLevel sl) { + final var serv = sl.getServer(); + final var compactDim = serv.getLevel(Registration.COMPACT_DIMENSION); + + if (level.getBlockEntity(pos) instanceof CompactMachineBlockEntity entity) { + entity.getConnectedRoom().ifPresent(room -> { + final var dimGraph = DimensionMachineGraph.forDimension(sl); + dimGraph.disconnect(pos); + + if (compactDim == null) + return; + + final var tunnels = TunnelConnectionGraph.forRoom(compactDim, room); + tunnels.unregister(pos); + }); } } diff --git a/src/main/java/dev/compactmods/machines/machine/CompactMachineBlockEntity.java b/src/main/java/dev/compactmods/machines/machine/CompactMachineBlockEntity.java index 9b607a01..3b9a276b 100644 --- a/src/main/java/dev/compactmods/machines/machine/CompactMachineBlockEntity.java +++ b/src/main/java/dev/compactmods/machines/machine/CompactMachineBlockEntity.java @@ -2,21 +2,18 @@ import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.machine.MachineNbt; -import dev.compactmods.machines.api.room.IRoomInformation; -import dev.compactmods.machines.api.room.MachineRoomConnections; -import dev.compactmods.machines.core.Capabilities; -import dev.compactmods.machines.core.LevelBlockPosition; +import dev.compactmods.machines.location.LevelBlockPosition; import dev.compactmods.machines.core.MissingDimensionException; import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.machine.data.MachineToRoomConnections; -import dev.compactmods.machines.room.data.CompactRoomData; +import dev.compactmods.machines.machine.graph.DimensionMachineGraph; +import dev.compactmods.machines.machine.graph.CompactMachineNode; +import dev.compactmods.machines.machine.graph.legacy.LegacyMachineConnections; +import dev.compactmods.machines.room.graph.CompactMachineRoomNode; import dev.compactmods.machines.tunnel.TunnelWallEntity; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.entity.BlockEntity; @@ -27,18 +24,24 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.util.Optional; import java.util.UUID; public class CompactMachineBlockEntity extends BlockEntity implements ICapabilityProvider { - public int machineId = -1; + private static final String ROOM_NBT = "room_pos"; + private static final String LEGACY_MACH_ID = "machine_id"; + public long nextSpawnTick = 0; protected UUID owner; protected String schema; protected boolean locked = false; private ChunkPos roomChunk; - private LazyOptional room = LazyOptional.empty(); + private int legacyMachineId = -1; + + private WeakReference graphNode; + private WeakReference roomNode; public CompactMachineBlockEntity(BlockPos pos, BlockState state) { super(Registration.MACHINE_TILE_ENTITY.get(), pos, state); @@ -47,26 +50,24 @@ public CompactMachineBlockEntity(BlockPos pos, BlockState state) { @Nonnull @Override public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { - if (cap == Capabilities.ROOM) return room.cast(); - - if(level instanceof ServerLevel sl) { - return getInternalChunkPos().map(roomId -> { + if (level instanceof ServerLevel sl) { + return getConnectedRoom().map(roomId -> { try { final var serv = sl.getServer(); + final var compactDim = serv.getLevel(Registration.COMPACT_DIMENSION); - final var tunnels = RoomTunnelData.get(serv, roomId); - final var graph = tunnels.getGraph(); + final var graph = TunnelConnectionGraph.forRoom(compactDim, roomId); - final var supportingTunnels = graph.getTunnelsSupporting(machineId, side, cap); + final var supportingTunnels = graph.getTunnelsSupporting(getLevelPosition(), side, cap); final var firstSupported = supportingTunnels.findFirst(); if (firstSupported.isEmpty()) return super.getCapability(cap, side); final var compact = serv.getLevel(Registration.COMPACT_DIMENSION); - if(compact == null) + if (compact == null) throw new MissingDimensionException(); - if(compact.getBlockEntity(firstSupported.get()) instanceof TunnelWallEntity tunnel) { + if (compact.getBlockEntity(firstSupported.get()) instanceof TunnelWallEntity tunnel) { return tunnel.getTunnelCapability(cap, side); } else { return super.getCapability(cap, side); @@ -81,34 +82,19 @@ public LazyOptional getCapability(@Nonnull Capability cap, @Nullable D return super.getCapability(cap, side); } - @Nullable - private IRoomInformation getRoom() { - if (level instanceof ServerLevel sl) { - return getInternalChunkPos().map(c -> { - final var compact = sl.getServer().getLevel(Registration.COMPACT_DIMENSION); - if(compact != null) { - var inChunk = compact.getChunk(c.x, c.z); - return inChunk.getCapability(Capabilities.ROOM).orElseThrow(RuntimeException::new); - } - - return null; - }).orElse(null); - } - - return null; - } - @Override public void onLoad() { super.onLoad(); - this.room = LazyOptional.of(this::getRoom); + if (this.legacyMachineId != -1) + this.updateLegacyData(); + + this.syncConnectedRoom(); } @Override public void load(@Nonnull CompoundTag nbt) { super.load(nbt); - machineId = nbt.getInt(MachineNbt.ID); // TODO customName = nbt.getString("CustomName"); if (nbt.contains(MachineNbt.OWNER)) { owner = nbt.getUUID(MachineNbt.OWNER); @@ -116,6 +102,10 @@ public void load(@Nonnull CompoundTag nbt) { owner = null; } + if (nbt.contains(LEGACY_MACH_ID)) { + this.legacyMachineId = nbt.getInt(LEGACY_MACH_ID); + } + nextSpawnTick = nbt.getLong("spawntick"); if (nbt.contains("schema")) { schema = nbt.getString("schema"); @@ -130,9 +120,27 @@ public void load(@Nonnull CompoundTag nbt) { } } + private void updateLegacyData() { + if (level instanceof ServerLevel sl) { + try { + final var legacy = LegacyMachineConnections.get(sl.getServer()); + + DimensionMachineGraph graph = DimensionMachineGraph.forDimension(sl); + graph.addMachine(worldPosition); + + final ChunkPos oldRoom = legacy.getConnectedRoom(this.legacyMachineId); + CompactMachines.LOGGER.info(CompactMachines.CONN_MARKER, "Rebinding machine {} ({}/{}) to room {}", legacyMachineId, worldPosition, level.dimension(), roomChunk); + + this.roomChunk = oldRoom; + graph.connectMachineToRoom(worldPosition, roomChunk); + } catch (MissingDimensionException e) { + CompactMachines.LOGGER.fatal(CompactMachines.CONN_MARKER, "Could not load connection info from legacy data; machine at {} in dimension {} will be unmapped.", worldPosition, level.dimension()); + } + } + } + @Override protected void saveAdditional(CompoundTag nbt) { - nbt.putInt(MachineNbt.ID, machineId); // nbt.putString("CustomName", customName.getString()); if (owner != null) { @@ -149,53 +157,51 @@ protected void saveAdditional(CompoundTag nbt) { @Override public CompoundTag getUpdateTag() { - CompoundTag base = super.getUpdateTag(); - base.putInt("machine", this.machineId); + CompoundTag data = super.getUpdateTag(); + + getConnectedRoom().ifPresent(room -> { + data.putIntArray(ROOM_NBT, new int[]{room.x, room.z}); + }); if (level instanceof ServerLevel) { // TODO - Internal player list if (this.owner != null) - base.putUUID("owner", this.owner); + data.putUUID("owner", this.owner); } - return base; + return data; } - public Optional getInternalChunkPos() { - if (level instanceof ServerLevel) { - if(roomChunk != null) + public Optional getConnectedRoom() { + if (level instanceof ServerLevel sl) { + if (roomChunk != null) return Optional.of(roomChunk); - MinecraftServer serv = level.getServer(); - if (serv == null) - return Optional.empty(); + final var graph = DimensionMachineGraph.forDimension(sl); - MachineRoomConnections connections; - try { - connections = MachineToRoomConnections.get(serv); - } catch (MissingDimensionException e) { - return Optional.empty(); - } - - var chunk = connections.getConnectedRoom(this.machineId); + var chunk = graph.getConnectedRoom(worldPosition); chunk.ifPresent(c -> this.roomChunk = c); return chunk; } - return Optional.empty(); + return Optional.ofNullable(roomChunk); } @Override public void handleUpdateTag(CompoundTag tag) { super.handleUpdateTag(tag); - this.machineId = tag.getInt("machine"); if (tag.contains("players")) { CompoundTag players = tag.getCompound("players"); // playerData = CompactMachinePlayerData.fromNBT(players); } + if (tag.contains(ROOM_NBT)) { + int[] room = tag.getIntArray(ROOM_NBT); + this.roomChunk = new ChunkPos(room[0], room[1]); + } + if (tag.contains("owner")) owner = tag.getUUID("owner"); } @@ -208,77 +214,48 @@ public void setOwner(UUID owner) { this.owner = owner; } - public void setMachineId(int id) { - this.machineId = id; - this.updateMapping(); - } - public boolean hasPlayersInside() { // TODO return false; } - public void doPostPlaced() { - if (this.level == null || this.level.isClientSide) { - return; - } + public LevelBlockPosition getLevelPosition() { + return new LevelBlockPosition(level.dimension(), worldPosition); + } - MinecraftServer serv = this.level.getServer(); - if (serv == null) - return; + public void syncConnectedRoom() { + if (this.level == null || this.level.isClientSide) return; - LevelBlockPosition dp = new LevelBlockPosition( - this.level.dimension(), - this.worldPosition - ); + if (level instanceof ServerLevel sl) { + final var graph = DimensionMachineGraph.forDimension(sl); + graph.getMachineNode(worldPosition).ifPresent(node -> { + this.graphNode = new WeakReference<>(node); + }); - try { - CompactMachineData extern = CompactMachineData.get(serv); - extern.setMachineLocation(this.machineId, dp); + this.getConnectedRoom() + .flatMap(room -> graph.getRoomNode(this.roomChunk)) + .ifPresent(roomNode -> this.roomNode = new WeakReference<>(roomNode)); this.setChanged(); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); } } - public boolean mapped() { - return getInternalChunkPos().isPresent(); + public void setConnectedRoom(ChunkPos room) { + if(level instanceof ServerLevel sl) { + final var dimMachines = DimensionMachineGraph.forDimension(sl); + dimMachines.connectMachineToRoom(worldPosition, room); + syncConnectedRoom(); + } } - public Optional getSpawn() { - if (level instanceof ServerLevel serverWorld) { - MinecraftServer serv = serverWorld.getServer(); - - MachineRoomConnections connections = null; - try { - connections = MachineToRoomConnections.get(serv); - } catch (MissingDimensionException e) { - return Optional.empty(); - } - - Optional connectedRoom = connections.getConnectedRoom(machineId); - - if (connectedRoom.isEmpty()) - return Optional.empty(); - - try { - final var roomData = CompactRoomData.get(serv); + public void disconnect() { + if(level instanceof ServerLevel sl) { + final var dimMachines = DimensionMachineGraph.forDimension(sl); + dimMachines.disconnect(worldPosition); - ChunkPos chunk = connectedRoom.get(); - return Optional.ofNullable(roomData.getSpawn(chunk)); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return Optional.empty(); - } + this.roomChunk = null; + this.graphNode.clear(); + setChanged(); } - - return Optional.empty(); - } - - public void updateMapping() { - this.room.invalidate(); - this.roomChunk = null; - this.setChanged(); } } diff --git a/src/main/java/dev/compactmods/machines/machine/CompactMachineItem.java b/src/main/java/dev/compactmods/machines/machine/CompactMachineItem.java index 1b414d7f..4e6b2328 100644 --- a/src/main/java/dev/compactmods/machines/machine/CompactMachineItem.java +++ b/src/main/java/dev/compactmods/machines/machine/CompactMachineItem.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.UUID; import com.mojang.authlib.GameProfile; +import dev.compactmods.machines.api.codec.CodecExtensions; import dev.compactmods.machines.api.core.Tooltips; import dev.compactmods.machines.api.machine.MachineNbt; import dev.compactmods.machines.core.Registration; @@ -14,6 +15,8 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.gui.screens.Screen; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.TextComponent; @@ -21,15 +24,19 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; public class CompactMachineItem extends BlockItem { + private static final String ROOM_NBT = "room_pos"; + public CompactMachineItem(Block blockIn, Properties builder) { super(blockIn, builder); } + @Deprecated(forRemoval = true) public static Optional getMachineId(ItemStack stack) { if (!stack.hasTag()) return Optional.empty(); @@ -54,35 +61,43 @@ public static Item getItemBySize(RoomSize size) { }; } + public static Optional getRoom(ItemStack stack) { + if (!stack.hasTag()) + return Optional.empty(); + + var tag = stack.getTag(); + if(!tag.contains(ROOM_NBT)) { + return Optional.empty(); + } + + var roomNbt = tag.getIntArray(ROOM_NBT); + return Optional.of(new ChunkPos(roomNbt[0], roomNbt[1])); + } + + public static void setRoom(ItemStack stack, ChunkPos room) { + var tag = stack.getOrCreateTag(); + tag.putIntArray(ROOM_NBT, new int[] { room.x, room.z }); + } + @Override public void appendHoverText(ItemStack stack, @Nullable Level worldIn, List tooltip, TooltipFlag flagIn) { super.appendHoverText(stack, worldIn, tooltip, flagIn); // We need NBT data for the rest of this if (stack.hasTag()) { - CompoundTag nbt = stack.getTag(); if(nbt == null) return; - getMachineId(stack).ifPresent(id -> { - tooltip.add(TranslationUtil.tooltip(Tooltips.Machines.ID, id)); + // Try room binding; if failed, try old machine ID binding + getRoom(stack).ifPresentOrElse(room -> { + // TODO - Server-synced room name list + tooltip.add(TranslationUtil.tooltip(Tooltips.ROOM_NAME, room)); + }, () -> { + getMachineId(stack).ifPresent(id -> { + tooltip.add(TranslationUtil.tooltip(Tooltips.Machines.ID, id)); + }); }); - - if (nbt.contains(MachineNbt.OWNER)) { - UUID owner = nbt.getUUID(MachineNbt.OWNER); - Optional playerProfile = PlayerUtil.getProfileByUUID(worldIn, owner); - - MutableComponent player = playerProfile - .map(p -> (MutableComponent) new TextComponent(p.getName())) - .orElse(TranslationUtil.tooltip(Tooltips.UNKNOWN_PLAYER_NAME)); - - MutableComponent ownerText = TranslationUtil.tooltip(Tooltips.Machines.OWNER) - .append(player); - - tooltip.add(ownerText); - } - } if (Screen.hasShiftDown()) { diff --git a/src/main/java/dev/compactmods/machines/machine/Machines.java b/src/main/java/dev/compactmods/machines/machine/Machines.java deleted file mode 100644 index 59eacf5e..00000000 --- a/src/main/java/dev/compactmods/machines/machine/Machines.java +++ /dev/null @@ -1,157 +0,0 @@ -package dev.compactmods.machines.machine; - -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.core.CMCommands; -import dev.compactmods.machines.api.location.IDimensionalPosition; -import dev.compactmods.machines.api.room.IRoomInformation; -import dev.compactmods.machines.api.room.MachineRoomConnections; -import dev.compactmods.machines.core.LevelBlockPosition; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.i18n.TranslationUtil; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.machine.data.MachineToRoomConnections; -import dev.compactmods.machines.machine.exceptions.InvalidMachineStateException; -import dev.compactmods.machines.machine.exceptions.NonexistentMachineException; -import dev.compactmods.machines.room.RoomInformation; -import dev.compactmods.machines.room.RoomSize; -import dev.compactmods.machines.room.Rooms; -import dev.compactmods.machines.room.data.CompactRoomData; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; -import net.minecraft.commands.CommandRuntimeException; -import net.minecraft.core.BlockPos; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; -import net.minecraftforge.common.util.LazyOptional; - -import java.util.Optional; - -public class Machines { - public static boolean createAndLink(MinecraftServer server, Level level, BlockPos machinePos, CompactMachineBlockEntity tile, ChunkPos room) { - try { - int nextId = createNew(server, level, machinePos); - tile.setMachineId(nextId); - return link(server, nextId, room); - - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal("Critical error while trying to create a new machine and link it to a new room.", e); - return false; - } - } - - public static int createNew(MinecraftServer server, Level level, BlockPos machinePos) throws MissingDimensionException { - final var machines = CompactMachineData.get(server); - final var connections = MachineToRoomConnections.get(server); - - int nextId = machines.getNextMachineId(); - machines.setMachineLocation(nextId, new LevelBlockPosition(level.dimension(), machinePos)); - connections.registerMachine(nextId); - return nextId; - } - - public static boolean link(MinecraftServer server, int machine, ChunkPos room) { - try { - MachineRoomConnections connections = MachineToRoomConnections.get(server); - connections.connectMachineToRoom(machine, room); - return true; - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.error("Could not load world saved data while creating new machine and room.", e); - return false; - } - } - - public static boolean destroy(MinecraftServer server, int machine) { - MachineRoomConnections connections; - try { - connections = MachineToRoomConnections.get(server); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.error("Could not load world saved data while creating new machine and room.", e); - return false; - } - - connections.getConnectedRoom(machine).ifPresent(room -> { - try { - var tunnels = RoomTunnelData.get(server, room); - final var tunnelGraph = tunnels.getGraph(); - tunnelGraph.deleteMachine(machine); - tunnels.setDirty(); - } catch (MissingDimensionException e) { - e.printStackTrace(); - } - - }); - - connections.disconnect(machine); - return true; - } - - public static void changeLink(MinecraftServer server, int machine, ChunkPos room) throws MissingDimensionException, NonexistentRoomException, - NonexistentMachineException, InvalidMachineStateException { - - final var machineData = CompactMachineData.get(server); - final var roomData = CompactRoomData.get(server); - final var roomConnections = MachineToRoomConnections.get(server); - - final var currentRoomPos = roomConnections.getConnectedRoom(machine) - .orElseThrow(() -> new NonexistentRoomException(room)); - - final var machineInfo = machineData.getMachineLocation(machine) - .orElseThrow(() -> new NonexistentMachineException(machine)); - - final var machinePos = machineInfo.getBlockPosition(); - final var tunnelData = RoomTunnelData.get(server, currentRoomPos); - final var tunnelGraph = tunnelData.getGraph(); - final var connectedTunnels = tunnelGraph.getConnections(machine).toList(); - - if(!connectedTunnels.isEmpty()) { - final var firstConnected = connectedTunnels.get(0); - CompactMachines.LOGGER.error("Refusing to rebind machine to a different room: tunnel bound at {}", firstConnected); - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.REBIND_HAS_TUNNEL_CONNECTED, firstConnected)); - } - - if(!roomData.isRegistered(room)) { - CompactMachines.LOGGER.error("Refusing to rebind machine to a different room: target room is not registered."); - throw new CommandRuntimeException(TranslationUtil.command(CMCommands.CMD_ROOM_NOT_REGISTERED, room)); - } - - final var level = machineInfo.level(server); - - final var machineState = level.getBlockState(machinePos); - if(!(machineState.getBlock() instanceof CompactMachineBlock machineBlock)) { - CompactMachines.LOGGER.error("Refusing to rebind block at {}; not a machine block.", machinePos); - throw new InvalidMachineStateException(machinePos, machineState, "Not a machine block."); - } - - final var targetRoomData = roomData.getData(room); - if(targetRoomData.getSize() != machineBlock.getSize()) { - CompactMachines.LOGGER.error("Refusing to rebind block at {}; wrong size.", machinePos); - throw new InvalidMachineStateException(machinePos, machineState, "Not the correct size."); - } - - roomConnections.changeMachineLink(machine, room); - if(level.getBlockEntity(machineInfo.getBlockPosition()) instanceof CompactMachineBlockEntity be) { - be.updateMapping(); - } - } - - public static LazyOptional location(MinecraftServer server, int machine) throws MissingDimensionException { - final var machineData = CompactMachineData.get(server); - return machineData.getMachineLocation(machine); - } - - public static Optional getConnectedRoom(MinecraftServer server, int machine) throws MissingDimensionException, NonexistentRoomException { - final var roomConnections = MachineToRoomConnections.get(server); - final var compactLevel = server.getLevel(Registration.COMPACT_DIMENSION); - - final var connected = roomConnections.getConnectedRoom(machine); - if(connected.isEmpty()) - return Optional.empty(); - - final var cp = connected.get(); - final RoomSize size = Rooms.sizeOf(server, cp); - - return Optional.of(new RoomInformation(compactLevel, cp, size)); - } -} diff --git a/src/main/java/dev/compactmods/machines/machine/data/CompactMachineData.java b/src/main/java/dev/compactmods/machines/machine/data/CompactMachineData.java deleted file mode 100644 index 3b602888..00000000 --- a/src/main/java/dev/compactmods/machines/machine/data/CompactMachineData.java +++ /dev/null @@ -1,176 +0,0 @@ -package dev.compactmods.machines.machine.data; - -import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.location.IDimensionalPosition; -import dev.compactmods.machines.api.codec.NbtListCollector; -import dev.compactmods.machines.core.LevelBlockPosition; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.nbt.Tag; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.saveddata.SavedData; -import net.minecraft.world.level.storage.DimensionDataStorage; -import net.minecraftforge.common.util.LazyOptional; - -import javax.annotation.Nonnull; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Stream; - -/** - * Holds information on the external points of a machine, ie the actual machine blocks. - * - * @deprecated This data should move to the connection graph or to the machine blocks, to be removed in 1.19 - */ -@Deprecated(forRemoval = true, since = "4.0.7") -public class CompactMachineData extends SavedData { - - /** - * File storage name. - */ - public final static String DATA_NAME = CompactMachines.MOD_ID + "_machines"; - - /** - * Specifies locations of machine blocks, ie in the overworld. - * This is used for things like tunnel handling and forced ejections. - */ - private final Map data = new HashMap<>(); - private final Map> locations = new HashMap<>(); - - - @Nonnull - public static CompactMachineData get(MinecraftServer server) throws MissingDimensionException { - ServerLevel compactWorld = server.getLevel(Registration.COMPACT_DIMENSION); - if (compactWorld == null) { - CompactMachines.LOGGER.error("No compact dimension found. Report this."); - throw new MissingDimensionException(); - } - - DimensionDataStorage sd = compactWorld.getDataStorage(); - return sd.computeIfAbsent(CompactMachineData::fromNbt, CompactMachineData::new, DATA_NAME); - } - - public static CompactMachineData fromNbt(CompoundTag nbt) { - CompactMachineData machines = new CompactMachineData(); - if (nbt.contains("locations")) { - ListTag nbtLocations = nbt.getList("locations", Tag.TAG_COMPOUND); - nbtLocations.forEach(nbtLoc -> { - DataResult res = MachineData.CODEC.parse(NbtOps.INSTANCE, nbtLoc); - res.resultOrPartial(err -> CompactMachines.LOGGER.error("Error while processing machine data: " + err)) - .ifPresent(machineInfo -> machines.data.put(machineInfo.machineId, machineInfo)); - }); - } - - return machines; - } - - @Override - @Nonnull - public CompoundTag save(@Nonnull CompoundTag nbt) { - if (!data.isEmpty()) { - ListTag nbtLocations = data.values() - .stream() - .map(entry -> { - DataResult nbtRes = MachineData.CODEC.encodeStart(NbtOps.INSTANCE, entry); - return nbtRes.resultOrPartial(err -> CompactMachines.LOGGER.error("Error serializing machine data: " + err)); - }) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(NbtListCollector.toNbtList()); - - nbt.put("locations", nbtLocations); - } - - return nbt; - } - - public void setMachineLocation(int machineId, LevelBlockPosition position) { - // TODO - Packet/Event for machine changing external location (tunnels) - if (data.containsKey(machineId)) { - data.get(machineId).setLocation(position); - } else { - data.put(machineId, new MachineData(machineId, position)); - } - - if (locations.containsKey(machineId)) { - locations.get(machineId).invalidate(); - locations.remove(machineId); - } - - this.setDirty(); - } - - public LazyOptional getMachineLocation(int machineId) { - if (!data.containsKey(machineId)) - return LazyOptional.empty(); - - if (locations.containsKey(machineId)) - return locations.get(machineId); - - var lazy = LazyOptional.of(() -> { - MachineData machineData = this.data.get(machineId); - return (IDimensionalPosition) machineData.location; - }); - - locations.put(machineId, lazy); - return lazy; - } - - public void remove(int id) { - data.remove(id); - locations.remove(id); - setDirty(); - } - - public Stream stream() { - return data.values().stream(); - } - - public int getNextMachineId() { - // TODO - Optimize for gaps, in-memory during data loading process - int i = 1; - while (true) { - if(data.containsKey(i)){ - i++; - continue; - } - - return i; - } - } - - public static class MachineData { - private final int machineId; - public LevelBlockPosition location; - - public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( - Codec.INT.fieldOf("machine").forGetter(MachineData::getMachineId), - LevelBlockPosition.CODEC.fieldOf("location").forGetter(MachineData::getLocation) - ).apply(i, MachineData::new)); - - public MachineData(int machineId, LevelBlockPosition location) { - this.machineId = machineId; - this.location = location; - } - - public int getMachineId() { - return this.machineId; - } - - public LevelBlockPosition getLocation() { - return this.location; - } - - public void setLocation(LevelBlockPosition position) { - this.location = position; - } - } -} diff --git a/src/main/java/dev/compactmods/machines/machine/data/MachineToRoomConnections.java b/src/main/java/dev/compactmods/machines/machine/data/MachineToRoomConnections.java deleted file mode 100644 index b13bfeaf..00000000 --- a/src/main/java/dev/compactmods/machines/machine/data/MachineToRoomConnections.java +++ /dev/null @@ -1,143 +0,0 @@ -package dev.compactmods.machines.machine.data; - -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.room.MachineRoomConnections; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.graph.CompactMachineConnectionGraph; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.saveddata.SavedData; -import net.minecraft.world.level.storage.DimensionDataStorage; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; - -public class MachineToRoomConnections extends SavedData implements MachineRoomConnections { - public static final String DATA_NAME = CompactMachines.MOD_ID + "_connections"; - - private CompactMachineConnectionGraph graph; - - public MachineToRoomConnections() { - graph = new CompactMachineConnectionGraph(); - } - - @Nonnull - public static MachineRoomConnections get(MinecraftServer server) throws MissingDimensionException { - ServerLevel compactWorld = server.getLevel(Registration.COMPACT_DIMENSION); - if (compactWorld == null) { - CompactMachines.LOGGER.error("No compact dimension found. Report this."); - throw new MissingDimensionException(); - } - - DimensionDataStorage sd = compactWorld.getDataStorage(); - return sd.computeIfAbsent(MachineToRoomConnections::fromNbt, MachineToRoomConnections::new, DATA_NAME); - } - - static MachineToRoomConnections fromNbt(CompoundTag nbt) { - MachineToRoomConnections c = new MachineToRoomConnections(); - if (nbt.contains("graph")) { - CompoundTag graphNbt = nbt.getCompound("graph"); - CompactMachineConnectionGraph.CODEC.parse(NbtOps.INSTANCE, graphNbt) - .resultOrPartial(CompactMachines.LOGGER::error) - .ifPresent(g -> c.graph = g); - } - - return c; - } - - @Nonnull - @Override - public CompoundTag save(@Nonnull CompoundTag nbt) { - CompactMachineConnectionGraph.CODEC - .encodeStart(NbtOps.INSTANCE, graph) - .resultOrPartial(CompactMachines.LOGGER::error) - .ifPresent(gNbt -> nbt.put("graph", gNbt)); - - return nbt; - } - - /** - * @deprecated Integer machines are planned to be removed in 1.19; plan is to replace them with dimensional positions - * @param machineId - * @return - */ - @Override - @Nonnull - public Optional getConnectedRoom(int machineId) { - return graph.getConnectedRoom(machineId); - } - - /** - * @param chunkPos Room to get machine IDs for - * @return - * @deprecated Integer machines are planned to be removed in 1.19; plan is to replace them with dimensional positions - */ - @Override - @Nonnull - @Deprecated(since = "1.7.0") - public Collection getMachinesFor(ChunkPos chunkPos) { - try { - return graph.getMachinesFor(chunkPos); - } catch (NonexistentRoomException e) { - CompactMachines.LOGGER.error("Tried to get machine info for nonexistent room: " + chunkPos, e); - return Collections.emptySet(); - } - } - - /** - * @deprecated Integer machines are planned to be removed in 1.19; plan is to replace them with dimensional positions - * @param machine - */ - @Override - public void registerMachine(int machine) { - graph.addMachine(machine); - setDirty(); - } - - @Override - public void registerRoom(ChunkPos roomChunk) { - graph.addRoom(roomChunk); - setDirty(); - } - - @Override - public void unregisterRoom(ChunkPos roomChunk) { - graph.removeRoom(roomChunk); - setDirty(); - } - - /** - * @deprecated Integer machines are planned to be removed in 1.19; plan is to replace them with dimensional positions - * @param machine - * @param room - */ - @Override - public void connectMachineToRoom(int machine, ChunkPos room) { - graph.connectMachineToRoom(machine, room); - setDirty(); - } - - @Override - public void changeMachineLink(int machine, ChunkPos newRoom) { - graph.disconnect(machine); - graph.connectMachineToRoom(machine, newRoom); - setDirty(); - } - - /** - * @deprecated Integer machines are planned to be removed in 1.19; plan is to replace them with dimensional positions - * @param machine - */ - @Override - public void disconnect(int machine) { - graph.disconnectAndUnregister(machine); - setDirty(); - } -} diff --git a/src/main/java/dev/compactmods/machines/machine/graph/CompactMachineNode.java b/src/main/java/dev/compactmods/machines/machine/graph/CompactMachineNode.java index 4df44565..65515c6b 100644 --- a/src/main/java/dev/compactmods/machines/machine/graph/CompactMachineNode.java +++ b/src/main/java/dev/compactmods/machines/machine/graph/CompactMachineNode.java @@ -3,42 +3,41 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.graph.CMGraphRegistration; import dev.compactmods.machines.graph.IGraphNode; +import dev.compactmods.machines.graph.IGraphNodeType; +import dev.compactmods.machines.location.LevelBlockPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; import java.util.Objects; /** * Represents a machine's external point. This can be either inside a machine or in a dimension somewhere. */ -public record CompactMachineNode(int machineId) implements IGraphNode { +public record CompactMachineNode(ResourceKey dimension, BlockPos position) + implements IGraphNode { public static final ResourceLocation TYPE = new ResourceLocation(CompactMachines.MOD_ID, "machine"); public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( - Codec.INT.fieldOf("machine").forGetter(CompactMachineNode::machineId), + Level.RESOURCE_KEY_CODEC.fieldOf("dimension").forGetter(CompactMachineNode::dimension), + BlockPos.CODEC.fieldOf("position").forGetter(CompactMachineNode::position), ResourceLocation.CODEC.fieldOf("type").forGetter(x -> TYPE) - ).apply(i, (id, type) -> new CompactMachineNode(id))); + ).apply(i, (dim, pos, type) -> new CompactMachineNode(dim, pos))); - public String label() { - return "Compact Machine #" + machineId; + public String toString() { + return "Compact Machine {%s}".formatted(position); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CompactMachineNode that = (CompactMachineNode) o; - return machineId == that.machineId; - } - - @Override - public int hashCode() { - return Objects.hash(machineId); + public LevelBlockPosition dimpos() { + return new LevelBlockPosition(dimension, position); } @Override - public Codec codec() { - return CODEC; + public IGraphNodeType getType() { + return CMGraphRegistration.MACH_NODE.get(); } } diff --git a/src/main/java/dev/compactmods/machines/machine/graph/DimensionMachineGraph.java b/src/main/java/dev/compactmods/machines/machine/graph/DimensionMachineGraph.java new file mode 100644 index 00000000..f8e13cda --- /dev/null +++ b/src/main/java/dev/compactmods/machines/machine/graph/DimensionMachineGraph.java @@ -0,0 +1,243 @@ +package dev.compactmods.machines.machine.graph; + +import com.google.common.collect.ImmutableList; +import com.google.common.graph.MutableValueGraph; +import com.google.common.graph.ValueGraphBuilder; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.api.codec.CodecExtensions; +import dev.compactmods.machines.graph.IGraphEdge; +import dev.compactmods.machines.graph.IGraphNode; +import dev.compactmods.machines.graph.IGraphNodeType; +import dev.compactmods.machines.room.graph.CompactMachineRoomNode; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.saveddata.SavedData; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Stores information on how external machines connect to the rooms in the compact machine + * dimension. Per-dimension since 4.3.0. + */ +public class DimensionMachineGraph extends SavedData { + + private final ResourceKey level; + private final MutableValueGraph graph; + private final Map machines; + private final Map rooms; + + public static final String DATA_KEY = "machine_connections"; + private final Codec> CONN_CODEC = CompactMachineConnectionInfo.CODEC + .listOf() + .fieldOf("connections") + .codec(); + + private DimensionMachineGraph(ResourceKey level) { + this.level = level; + graph = ValueGraphBuilder + .directed() + .build(); + + machines = new HashMap<>(); + rooms = new HashMap<>(); + } + + private DimensionMachineGraph(ResourceKey level, @Nonnull CompoundTag nbt) { + this(level); + + if (nbt.contains("graph")) { + CompoundTag graphNbt = nbt.getCompound("graph"); + + final var connectionData = CONN_CODEC.parse(NbtOps.INSTANCE, graphNbt) + .resultOrPartial(CompactMachines.LOGGER::error) + .orElseThrow(); + + loadConnections(connectionData); + } + } + + private void loadConnections(List connectionInfo) { + for (CompactMachineConnectionInfo i : connectionInfo) { + addRoom(i.roomChunk); + for (var connectedMachine : i.machines()) { + addMachine(connectedMachine); + connectMachineToRoom(connectedMachine, i.roomChunk); + } + } + } + + public static DimensionMachineGraph forDimension(ServerLevel dimension) { + final var dimStore = dimension.getDataStorage(); + return dimStore.computeIfAbsent(tag -> new DimensionMachineGraph(dimension.dimension(), tag), + () -> new DimensionMachineGraph(dimension.dimension()), DATA_KEY); + } + + private List buildConnections() { + List result = new ArrayList<>(); + this.rooms.forEach((chunk, node) -> { + Collection machines = this.getMachinesFor(chunk); + CompactMachineConnectionInfo roomInfo = new CompactMachineConnectionInfo(chunk, machines); + result.add(roomInfo); + }); + + return result; + } + + public void addMachine(BlockPos machine) { + if (this.machines.containsKey(machine)) + return; + + CompactMachineNode node = new CompactMachineNode(this.level, machine); + graph.addNode(node); + machines.put(machine, node); + + this.setDirty(); + } + + public void addRoom(ChunkPos roomChunk) { + if (this.rooms.containsKey(roomChunk)) + return; + + CompactMachineRoomNode node = new CompactMachineRoomNode(roomChunk); + graph.addNode(node); + rooms.put(roomChunk, node); + + this.setDirty(); + } + + public void connectMachineToRoom(BlockPos machine, ChunkPos room) { + if (!machines.containsKey(machine)) + addMachine(machine); + + if (!rooms.containsKey(room)) + addRoom(room); + + CompactMachineNode machineNode = machines.get(machine); + CompactMachineRoomNode roomNode = rooms.get(room); + + graph.putEdgeValue(machineNode, roomNode, new MachineRoomEdge()); + + this.setDirty(); + } + + public Collection getMachinesFor(ChunkPos room) { + if(!rooms.containsKey(room)) + return Collections.emptySet(); + + var node = this.rooms.get(room); + var inbound = graph.predecessors(node); + + return inbound.stream() + .filter(CompactMachineNode.class::isInstance) + .map(CompactMachineNode.class::cast) + .map(CompactMachineNode::position) + .collect(Collectors.toSet()); + } + + public Optional getConnectedRoom(BlockPos machinePos) { + if (!this.machines.containsKey(machinePos)) + return Optional.empty(); + + var node = this.machines.get(machinePos); + var connected = this.graph.successors(node); + return connected.stream() + .filter(n -> n instanceof CompactMachineRoomNode) + .map(n -> (CompactMachineRoomNode) n) + .map(CompactMachineRoomNode::pos) + .findFirst(); + } + + public Stream getMachines() { + return this.machines.values().stream(); + } + + public void disconnectAndUnregister(int machine) { + if (!machines.containsKey(machine)) + return; + + final var node = machines.get(machine); + graph.removeNode(node); + machines.remove(machine); + } + + public void removeRoom(ChunkPos room) { + if (!this.rooms.containsKey(room)) + return; + + graph.removeNode(rooms.get(room)); + rooms.remove(room); + } + + public void disconnect(BlockPos machine) { + if (!machines.containsKey(machine)) + return; + + final var node = machines.get(machine); + graph.successors(node).stream() + .filter(cn -> cn instanceof CompactMachineRoomNode) + .forEach(room -> graph.removeEdge(node, room)); + + setDirty(); + } + + public Optional getMachineNode(BlockPos worldPosition) { + return Optional.ofNullable(machines.get(worldPosition)); + } + + public Optional getRoomNode(ChunkPos room) { + return Optional.ofNullable(rooms.get(room)); + } + + @Nonnull + @Override + public CompoundTag save(@Nonnull CompoundTag nbt) { + final var connData = buildConnections(); + + CONN_CODEC.encodeStart(NbtOps.INSTANCE, connData) + .resultOrPartial(CompactMachines.LOGGER::error) + .ifPresent(gNbt -> nbt.put("graph", gNbt)); + + return nbt; + } + + /** + * Data structure for serialization. Do not use directly. + */ + private static class CompactMachineConnectionInfo { + private final ChunkPos roomChunk; + private final List connectedMachines; + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + CodecExtensions.CHUNKPOS + .fieldOf("room") + .forGetter(CompactMachineConnectionInfo::room), + + BlockPos.CODEC.listOf() + .fieldOf("machines") + .forGetter(CompactMachineConnectionInfo::machines) + ).apply(i, CompactMachineConnectionInfo::new)); + + public CompactMachineConnectionInfo(ChunkPos roomChunk, Collection connections) { + this.roomChunk = roomChunk; + this.connectedMachines = ImmutableList.copyOf(connections); + } + + public ChunkPos room() { + return this.roomChunk; + } + + public List machines() { + return this.connectedMachines; + } + } +} diff --git a/src/main/java/dev/compactmods/machines/machine/graph/MachineLinkEdge.java b/src/main/java/dev/compactmods/machines/machine/graph/MachineLinkEdge.java deleted file mode 100644 index 59e9fe9a..00000000 --- a/src/main/java/dev/compactmods/machines/machine/graph/MachineLinkEdge.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.compactmods.machines.machine.graph; - -import com.mojang.serialization.Codec; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.graph.IGraphEdge; -import dev.compactmods.machines.tunnel.graph.TunnelMachineEdge; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; - -public record MachineLinkEdge() implements IGraphEdge { - private static final ResourceLocation TYPE = new ResourceLocation(CompactMachines.MOD_ID, "tunnel_machine"); - - public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( - Direction.CODEC.fieldOf("side").forGetter(TunnelMachineEdge::side), - ResourceLocation.CODEC.fieldOf("type").forGetter(x -> TYPE) - ).apply(i, (side, t) -> new TunnelMachineEdge(side))); - - @Override - public Codec codec() { - return CODEC; - } -} diff --git a/src/main/java/dev/compactmods/machines/machine/graph/MachineRoomEdge.java b/src/main/java/dev/compactmods/machines/machine/graph/MachineRoomEdge.java new file mode 100644 index 00000000..0df79615 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/machine/graph/MachineRoomEdge.java @@ -0,0 +1,33 @@ +package dev.compactmods.machines.machine.graph; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.graph.CMGraphRegistration; +import dev.compactmods.machines.graph.IGraphEdge; +import dev.compactmods.machines.graph.IGraphEdgeType; +import org.jetbrains.annotations.NotNull; + +public record MachineRoomEdge() implements IGraphEdge { + + public static final Codec CODEC = Codec.unit(MachineRoomEdge::new); + + @Override + public boolean equals(Object obj) { + return obj == this || obj != null && obj.getClass() == this.getClass(); + } + + @Override + public int hashCode() { + return 1; + } + + @Override + public String toString() { + return "MachineRoomEdge[]"; + } + + @Override + public @NotNull IGraphEdgeType getEdgeType() { + return CMGraphRegistration.MACHINE_LINK.get(); + } +} diff --git a/src/main/java/dev/compactmods/machines/machine/graph/legacy/LegacyMachineConnections.java b/src/main/java/dev/compactmods/machines/machine/graph/legacy/LegacyMachineConnections.java new file mode 100644 index 00000000..5cd92fb1 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/machine/graph/legacy/LegacyMachineConnections.java @@ -0,0 +1,81 @@ +package dev.compactmods.machines.machine.graph.legacy; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.api.codec.CodecExtensions; +import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.core.Registration; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.saveddata.SavedData; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LegacyMachineConnections extends SavedData { + + private static final String DATA_KEY = "compactmachines_connections"; + + private final Map machineMapping; + + private LegacyMachineConnections() { + this.machineMapping = new HashMap<>(); + } + + public static LegacyMachineConnections get(MinecraftServer server) throws MissingDimensionException { + var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + if(compactDim == null) + throw new MissingDimensionException(); + + return compactDim.getDataStorage().get(LegacyMachineConnections::load, DATA_KEY); + } + + private static LegacyMachineConnections load(CompoundTag tag) { + if(!tag.contains("graph")) + return null; + + var graphTag = tag.getCompound("graph"); + if(!graphTag.contains("connections")) + return null; + + LegacyMachineConnections tmp = new LegacyMachineConnections(); + final var connections = ConnectionInfoTag.CODEC.listOf() + .fieldOf("connections") + .codec() + .parse(NbtOps.INSTANCE, graphTag) + .getOrThrow(false, CompactMachines.LOGGER::error); + + // load all connections into result + connections.forEach(conn -> conn.machines.forEach(mid -> tmp.machineMapping.putIfAbsent(mid, conn.room))); + + return tmp; + } + + @Override + public CompoundTag save(CompoundTag tag) { + return tag; + } + + public ChunkPos getConnectedRoom(int legacyMachineId) { + if(!machineMapping.containsKey(legacyMachineId)) + return ChunkPos.ZERO; + + return machineMapping.get(legacyMachineId); + } + + private record ConnectionInfoTag(ChunkPos room, List machines) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + CodecExtensions.CHUNKPOS + .fieldOf("machine") + .forGetter(ConnectionInfoTag::room), + + Codec.INT.listOf() + .fieldOf("connections") + .forGetter(ConnectionInfoTag::machines) + ).apply(i, ConnectionInfoTag::new)); + } +} diff --git a/src/main/java/dev/compactmods/machines/machine/graph/legacy/LegacyMachineLocationsGraph.java b/src/main/java/dev/compactmods/machines/machine/graph/legacy/LegacyMachineLocationsGraph.java new file mode 100644 index 00000000..cbf0a6bb --- /dev/null +++ b/src/main/java/dev/compactmods/machines/machine/graph/legacy/LegacyMachineLocationsGraph.java @@ -0,0 +1,71 @@ +package dev.compactmods.machines.machine.graph.legacy; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.core.Registration; +import dev.compactmods.machines.location.LevelBlockPosition; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.saveddata.SavedData; + +import java.util.HashMap; +import java.util.Map; + +public class LegacyMachineLocationsGraph extends SavedData { + + private static final String DATA_KEY = "compactmachines_machines"; + + private final Map machineMapping; + + private LegacyMachineLocationsGraph() { + this.machineMapping = new HashMap<>(); + } + + public static LegacyMachineLocationsGraph get(MinecraftServer server) throws MissingDimensionException { + var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + if(compactDim == null) + throw new MissingDimensionException(); + + return compactDim.getDataStorage().get(LegacyMachineLocationsGraph::load, DATA_KEY); + } + + private static LegacyMachineLocationsGraph load(CompoundTag tag) { + LegacyMachineLocationsGraph tmp = new LegacyMachineLocationsGraph(); + + if(tag.contains("locations")) { + final var locations = MachineData.CODEC.listOf() + .fieldOf("locations") + .codec() + .parse(NbtOps.INSTANCE, tag) + .getOrThrow(false, CompactMachines.LOGGER::error); + + locations.forEach(md -> tmp.machineMapping.putIfAbsent(md.machine, md.location)); + } + + return tmp; + } + + @Override + public CompoundTag save(CompoundTag tag) { + return tag; + } + + public LevelBlockPosition getLocation(int legacyMachineId) { + if(!machineMapping.containsKey(legacyMachineId)) + return null; + + return this.machineMapping.get(legacyMachineId); + } + + private record MachineData(LevelBlockPosition location, int machine) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + LevelBlockPosition.CODEC.fieldOf("location").forGetter(MachineData::location), + Codec.INT.fieldOf("machine").forGetter(MachineData::machine) + ).apply(i, MachineData::new)); + } +} diff --git a/src/main/java/dev/compactmods/machines/mixin/ModelDataRefreshMixin.java b/src/main/java/dev/compactmods/machines/mixin/ModelDataRefreshMixin.java new file mode 100644 index 00000000..2a22a9b5 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/mixin/ModelDataRefreshMixin.java @@ -0,0 +1,34 @@ +package dev.compactmods.machines.mixin; + +import dev.compactmods.machines.client.level.RenderingLevel; +import net.minecraft.client.Minecraft; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.model.ModelDataManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@OnlyIn(Dist.CLIENT) +@Mixin(ModelDataManager.class) +public class ModelDataRefreshMixin { + + /** + * Normally ModelDataManager will throw an exception if a tile entity tries + * to refresh its model data from a world the client isn't currently in, + * but we need that to not happen for tile entities in fake schematic + * worlds, so in those cases just do nothing instead. + */ + @Inject(at = @At("HEAD"), method = "requestModelDataRefresh", cancellable = true, remap = false) + private static void requestModelDataRefresh(BlockEntity te, CallbackInfo ci) { + if (te != null) { + Level world = te.getLevel(); + if (world != Minecraft.getInstance().level && world instanceof RenderingLevel) + ci.cancel(); + } + } + +} diff --git a/src/main/java/dev/compactmods/machines/network/CMPacketTargets.java b/src/main/java/dev/compactmods/machines/network/CMPacketTargets.java deleted file mode 100644 index 4a60f6ea..00000000 --- a/src/main/java/dev/compactmods/machines/network/CMPacketTargets.java +++ /dev/null @@ -1,63 +0,0 @@ -package dev.compactmods.machines.network; - -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.machine.data.MachineToRoomConnections; -import net.minecraft.core.BlockPos; -import net.minecraft.network.protocol.Packet; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraftforge.network.NetworkDirection; -import net.minecraftforge.network.PacketDistributor; - -import java.util.HashMap; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public class CMPacketTargets { - - public static final PacketDistributor TRACKING_ROOM = new PacketDistributor<>( - CMPacketTargets::trackingRoom, NetworkDirection.PLAY_TO_CLIENT); - - private static Consumer> trackingRoom(PacketDistributor dist, Supplier supplier) { - LevelChunk roomChunk = supplier.get(); - Level level = roomChunk.getLevel(); - - HashMap trackingPlayersGlobal = new HashMap<>(); - - if (level instanceof ServerLevel serverWorld) { - MinecraftServer server = serverWorld.getServer(); - - try { - final var connections = MachineToRoomConnections.get(server); - final var machines = CompactMachineData.get(server); - - var linked = connections.getMachinesFor(roomChunk.getPos()); - - for (int machine : linked) { - machines.getMachineLocation(machine).ifPresent(loc -> { - final var machineWorld = loc.level(server); - BlockPos machineWorldLocation = loc.getBlockPosition(); - ChunkPos machineWorldChunk = new ChunkPos(machineWorldLocation); - - machineWorld.getChunkSource().chunkMap.getPlayers(machineWorldChunk, false).forEach(player -> { - if (!trackingPlayersGlobal.containsKey(player.getUUID())) - trackingPlayersGlobal.put(player.getUUID(), player.connection); - }); - }); - } - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - } - } - - return pack -> trackingPlayersGlobal.values().forEach(conn -> conn.send(pack)); - } -} diff --git a/src/main/java/dev/compactmods/machines/network/NetworkHandler.java b/src/main/java/dev/compactmods/machines/network/NetworkHandler.java deleted file mode 100644 index 5fcac0bd..00000000 --- a/src/main/java/dev/compactmods/machines/network/NetworkHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.compactmods.machines.network; - -import dev.compactmods.machines.CompactMachines; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.network.NetworkDirection; -import net.minecraftforge.network.NetworkRegistry; -import net.minecraftforge.network.simple.SimpleChannel; - -public class NetworkHandler { - private static final String PROTOCOL_VERSION = "4.0.0"; - public static final SimpleChannel MAIN_CHANNEL = NetworkRegistry.newSimpleChannel( - new ResourceLocation(CompactMachines.MOD_ID, "main"), - () -> PROTOCOL_VERSION, - PROTOCOL_VERSION::equals, - PROTOCOL_VERSION::equals - ); - - public static void initialize() { - MAIN_CHANNEL.messageBuilder(TunnelAddedPacket.class, 1, NetworkDirection.PLAY_TO_CLIENT) - .encoder(TunnelAddedPacket::encode) - .decoder(TunnelAddedPacket::new) - .consumer(TunnelAddedPacket::handle) - .add(); - } -} diff --git a/src/main/java/dev/compactmods/machines/room/Rooms.java b/src/main/java/dev/compactmods/machines/room/Rooms.java index 1b4fdd70..b0aa31ee 100644 --- a/src/main/java/dev/compactmods/machines/room/Rooms.java +++ b/src/main/java/dev/compactmods/machines/room/Rooms.java @@ -1,43 +1,37 @@ package dev.compactmods.machines.room; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.api.location.IDimensionalBlockPosition; +import dev.compactmods.machines.api.location.IDimensionalPosition; import dev.compactmods.machines.config.ServerConfig; +import dev.compactmods.machines.location.LevelBlockPosition; import dev.compactmods.machines.core.MissingDimensionException; import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.machine.data.MachineToRoomConnections; +import dev.compactmods.machines.machine.graph.DimensionMachineGraph; import dev.compactmods.machines.room.data.CompactRoomData; import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import dev.compactmods.machines.tunnel.TunnelWallEntity; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; import dev.compactmods.machines.util.CompactStructureGenerator; import dev.compactmods.machines.util.MathUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import javax.naming.OperationNotSupportedException; -import java.util.Optional; +import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; import java.util.stream.Stream; public class Rooms { public static ChunkPos createNew(MinecraftServer serv, RoomSize size, UUID owner) throws MissingDimensionException { - - CompactRoomData rooms = CompactRoomData.get(serv); - var connections = MachineToRoomConnections.get(serv); final var compactWorld = serv.getLevel(Registration.COMPACT_DIMENSION); - if (connections == null) { - throw new MissingDimensionException("Could not load world saved data while creating new room."); - } if (compactWorld == null) throw new MissingDimensionException(); + CompactRoomData rooms = CompactRoomData.get(compactWorld); + int nextPosition = rooms.getNextSpiralPosition(); Vec3i location = MathUtil.getRegionPositionByIndex(nextPosition); @@ -48,8 +42,6 @@ public static ChunkPos createNew(MinecraftServer serv, RoomSize size, UUID owner CompactStructureGenerator.generateCompactStructure(compactWorld, size, newCenter); ChunkPos machineChunk = new ChunkPos(newCenter); - connections.registerRoom(machineChunk); - try { rooms.createNew() .owner(owner) @@ -57,92 +49,124 @@ public static ChunkPos createNew(MinecraftServer serv, RoomSize size, UUID owner .chunk(machineChunk) .register(); } catch (OperationNotSupportedException e) { + // room already registered somehow CompactMachines.LOGGER.warn(e); } return machineChunk; } - public static boolean destroy(MinecraftServer server, ChunkPos room) throws MissingDimensionException, NonexistentRoomException { - var roomData = CompactRoomData.get(server); - if (!roomData.isRegistered(room)) { - throw new NonexistentRoomException(room); - } - - final var level = server.getLevel(Registration.COMPACT_DIMENSION); - if (level == null) - throw new MissingDimensionException(); - - final var roomBounds = roomData.getBounds(room); - final var innerBounds = roomBounds.deflate(1); + // TODO - Revisit with furnace recipe +// public static boolean destroy(MinecraftServer server, ChunkPos room) throws MissingDimensionException, NonexistentRoomException { +// final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); +// if (compactDim == null) +// throw new MissingDimensionException(); +// +// var roomData = CompactRoomData.get(compactDim); +// if (!roomData.isRegistered(room)) { +// throw new NonexistentRoomException(room); +// } +// +// final var roomBounds = roomData.getBounds(room); +// final var innerBounds = roomBounds.deflate(1); +// +// final var states = compactDim.getBlockStates(innerBounds) +// .collect(Collectors.toSet()); +// +// final var nonAir = states.stream() +// .filter(state -> !state.isAir()) +// .findAny(); +// +// if (nonAir.isPresent()) { +// CompactMachines.LOGGER.error("Refusing to delete room at {}; non-air blocks exist inside the room. First match: {}", room, nonAir.get()); +// return false; +// } +// +// // clear tunnel connection info +// final var tunnels = RoomTunnelData.getFile(server, room); +// final var filename = RoomTunnelData.getDataFilename(room); +// if (!tunnels.delete()) { +// CompactMachines.LOGGER.warn("Could not delete tunnel data for room {}; clearing the connection graph as an alternative.", room); +// CompactMachines.LOGGER.warn("Data file to delete: {}", filename); +// +// var td = RoomTunnelData.forRoom(server, room); +// td.getGraph().clear(); +// td.setDirty(); +// } else { +// // File deletion successful, delete cached data +// final var compactDataCache = compactDim.getDataStorage().cache; +// compactDataCache.remove(filename); +// } +// +// // reset everything for the room boundary +// BlockPos.betweenClosedStream(roomBounds.inflate(1)) +// .forEach(p -> compactDim.setBlock(p, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL)); +// +// // Remove room registration +// roomData.remove(room); +// +// // Disconnect all machines +// var conns = MachineToRoomConnections.forDimension(server); +// var d = CompactMachineData.get(server); +// +// var connected = conns.getMachinesFor(room); +// for (int mid : connected) { +// var location = d.getMachineLocation(mid); +// location.ifPresent(p -> { +// var pos = p.getBlockPosition(); +// var l = p.level(server); +// if (l.getBlockEntity(pos) instanceof TunnelWallEntity tunn) { +// tunn.disconnect(); +// } +// }); +// } +// +// conns.unregisterRoom(room); +// return true; +// } + + public static Stream getConnectedMachines(MinecraftServer server, ChunkPos room) { + return server.levelKeys().stream() + .map(server::getLevel) + .filter(Objects::nonNull) + .filter(sl -> sl.getDataStorage().cache.containsKey(DimensionMachineGraph.DATA_KEY)) + .flatMap(sl -> { + final var graph = DimensionMachineGraph.forDimension(sl); + return graph.getMachinesFor(room).stream() + .map(bp -> new LevelBlockPosition(sl.dimension(), bp)); + }); + } - final var states = level.getBlockStates(innerBounds) - .collect(Collectors.toSet()); + public static RoomSize sizeOf(MinecraftServer server, ChunkPos room) throws NonexistentRoomException { + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + return CompactRoomData.get(compactDim) + .getData(room) + .getSize(); + } - final var nonAir = states.stream() - .filter(state -> !state.isAir()) - .findAny(); + public static IDimensionalPosition getSpawn(MinecraftServer server, ChunkPos room) { + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + return CompactRoomData.get(compactDim).getSpawn(room); + } - if (nonAir.isPresent()) { - CompactMachines.LOGGER.error("Refusing to delete room at {}; non-air blocks exist inside the room. First match: {}", room, nonAir.get()); - return false; - } + public static boolean exists(MinecraftServer server, ChunkPos room) { + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); + return CompactRoomData.get(compactDim).isRegistered(room); + } - // clear tunnel connection info - final var tunnels = RoomTunnelData.getFile(server, room); - final var filename = RoomTunnelData.getDataFilename(room); - if (!tunnels.delete()) { - CompactMachines.LOGGER.warn("Could not delete tunnel data for room {}; clearing the connection graph as an alternative.", room); - CompactMachines.LOGGER.warn("Data file to delete: {}", filename); - - var td = RoomTunnelData.get(server, room); - td.getGraph().clear(); - td.setDirty(); - } else { - // File deletion successful, delete cached data - final var compactDataCache = level.getDataStorage().cache; - compactDataCache.remove(filename); - } + public static StructureTemplate getInternalBlocks(MinecraftServer server, ChunkPos room) throws MissingDimensionException, NonexistentRoomException { + final var tem = new StructureTemplate(); - // reset everything for the room boundary - BlockPos.betweenClosedStream(roomBounds.inflate(1)) - .forEach(p -> level.setBlock(p, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL)); - - // Remove room registration - roomData.remove(room); - - // Disconnect all machines - var conns = MachineToRoomConnections.get(server); - var d = CompactMachineData.get(server); - - var connected = conns.getMachinesFor(room); - for (int mid : connected) { - var location = d.getMachineLocation(mid); - location.ifPresent(p -> { - var pos = p.getBlockPosition(); - var l = p.level(server); - if (l.getBlockEntity(pos) instanceof TunnelWallEntity tunn) { - tunn.disconnect(); - } - }); - } + final var compactDim = server.getLevel(Registration.COMPACT_DIMENSION); - conns.unregisterRoom(room); - return true; - } + final var data = CompactRoomData.get(compactDim); + final var roomInfo = data.getData(room); - public static Stream getConnectedMachines(MinecraftServer server, ChunkPos room) { - try { - var conns = MachineToRoomConnections.get(server); - return conns.getMachinesFor(room).stream(); - } catch (MissingDimensionException e) { - return Stream.empty(); - } - } + final var bounds = roomInfo.getRoomBounds(); + final int inside = roomInfo.getSize().getInternalSize(); + tem.fillFromWorld(compactDim, new BlockPos(bounds.minX, bounds.minY - 1, bounds.minZ), + new Vec3i(inside, inside + 1, inside), false, null); - public static RoomSize sizeOf(MinecraftServer server, ChunkPos room) throws MissingDimensionException, NonexistentRoomException { - return CompactRoomData.get(server) - .getData(room) - .getSize(); + return tem; } } diff --git a/src/main/java/dev/compactmods/machines/room/TeleportationEventHandler.java b/src/main/java/dev/compactmods/machines/room/TeleportationEventHandler.java index 0b59484c..dd358b24 100644 --- a/src/main/java/dev/compactmods/machines/room/TeleportationEventHandler.java +++ b/src/main/java/dev/compactmods/machines/room/TeleportationEventHandler.java @@ -7,6 +7,7 @@ import dev.compactmods.machines.room.data.CompactRoomData; import dev.compactmods.machines.i18n.TranslationUtil; import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.MinecraftServer; @@ -53,19 +54,23 @@ public static void onEntityTeleport(final EntityTeleportEvent evt) { * @return True if teleportation should be cancelled; false otherwise. */ private static boolean cancelOutOfBoxTeleport(Entity entity, Vec3 target) { - MinecraftServer serv = entity.getServer(); - if (serv == null) + final var level = entity.level; + if (!level.dimension().equals(Registration.COMPACT_DIMENSION)) return false; - ChunkPos machineChunk = new ChunkPos(entity.chunkPosition().x, entity.chunkPosition().z); + if(level instanceof ServerLevel compactDim) { + ChunkPos machineChunk = new ChunkPos(entity.chunkPosition().x, entity.chunkPosition().z); - try { - final CompactRoomData intern = CompactRoomData.get(serv); - return !intern.getBounds(machineChunk).contains(target); - } catch (MissingDimensionException | NonexistentRoomException e) { - CompactMachines.LOGGER.error(e); - return false; + try { + final CompactRoomData intern = CompactRoomData.get(compactDim); + return !intern.getBounds(machineChunk).contains(target); + } catch (NonexistentRoomException e) { + CompactMachines.LOGGER.error(e); + return false; + } } + + return false; } private static void doEntityTeleportHandle(EntityEvent evt, Vec3 target, Entity ent) { diff --git a/src/main/java/dev/compactmods/machines/room/client/MachineRoomScreen.java b/src/main/java/dev/compactmods/machines/room/client/MachineRoomScreen.java new file mode 100644 index 00000000..1fce0ca1 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/client/MachineRoomScreen.java @@ -0,0 +1,230 @@ +package dev.compactmods.machines.room.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector3f; +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.client.gui.widget.PSDIconButton; +import dev.compactmods.machines.client.level.RenderingLevel; +import dev.compactmods.machines.client.render.RenderTypes; +import dev.compactmods.machines.client.render.SuperRenderTypeBuffer; +import dev.compactmods.machines.client.util.TransformingVertexBuilder; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.core.Registration; +import dev.compactmods.machines.room.menu.MachineRoomMenu; +import dev.compactmods.machines.room.network.PlayerStartedRoomTrackingPacket; +import dev.compactmods.machines.room.network.RoomNetworkHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraftforge.client.model.data.EmptyModelData; +import net.minecraftforge.client.model.data.IModelData; + +public class MachineRoomScreen extends AbstractContainerScreen { + + private final Inventory inv; + protected double rotateX = 45.0f; + protected double rotateY = 20.0f; + private PSDIconButton psdButton; + private RenderingLevel renderer; + + public MachineRoomScreen(MachineRoomMenu menu, Inventory inv, Component title) { + super(menu, inv, title); + this.imageWidth = 248; + this.imageHeight = 239; + this.titleLabelY = 5; + this.inv = inv; + + // Send packet to server for block data + RoomNetworkHandler.CHANNEL.sendToServer(new PlayerStartedRoomTrackingPacket(menu.getRoom())); + updateBlockRender(); + } + + @Override + protected void init() { + super.init(); + + this.psdButton = addRenderableWidget(new PSDIconButton(this, leftPos + 220, topPos + 210)); + if (hasPsdItem()) + this.psdButton.setEnabled(true); + } + + public void updateBlockRender() { + var struct = menu.getBlocks(); + this.renderer = new RenderingLevel(struct); + } + + private boolean hasPsdItem() { + return inv.contains(new ItemStack(Registration.PERSONAL_SHRINKING_DEVICE.get())); + } + + @Override + protected void containerTick() { + super.containerTick(); + psdButton.setEnabled(this.inv.player.isCreative() || hasPsdItem()); + renderer.tbe(); + } + + @Override + public boolean mouseDragged(double mx, double my, int mButton, double dx, double dy) { + var s = super.mouseDragged(mx, my, mButton, dx, dy); + if (!s) return false; + + rotateX += dx; + rotateY += dy; + return true; + } + + @Override + protected void renderLabels(PoseStack pose, int mouseX, int mouseY) { + pose.pushPose(); + pose.translate(0, 0, 500); + + pose.translate(this.imageWidth / 2f, 0, 0); + + var p = new TextComponent("Room Preview"); + Screen.drawCenteredString(pose, font, p, 0, this.titleLabelY, 0xFFFFFFFF); + + var room = menu.getRoom(); + var rt = new TextComponent("(%s, %s)".formatted(room.x, room.z)); + pose.scale(0.8f, 0.8f, 0.8f); + Screen.drawCenteredString(pose, font, rt, 0,this.titleLabelY + font.lineHeight + 2, 0xFFCCCCCC); + pose.popPose(); + } + + @Override + public void render(PoseStack pose, int mouseX, int mouseY, float partial) { + this.renderBackground(pose); + super.render(pose, mouseX, mouseY, partial); + + var buffer = SuperRenderTypeBuffer.getInstance(); + + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.backupProjectionMatrix(); + + // has to be outside of MS transforms, important for vertex sorting + Matrix4f matrix4f = new Matrix4f(RenderSystem.getProjectionMatrix()); + matrix4f.multiplyWithTranslation(0, 0, 800); + RenderSystem.setProjectionMatrix(matrix4f); + + PoseStack.Pose lastEntryBeforeTry = pose.last(); + + var cam = minecraft.cameraEntity; + + + try { + pose.pushPose(); + pose.translate(0, 0, -800); + + final var blockRenderer = Minecraft.getInstance().getBlockRenderer(); + final var beRenderer = Minecraft.getInstance().getBlockEntityRenderDispatcher(); + + var struct = menu.getBlocks(); + + pose.pushPose(); + { + // pose.translate(s, s, s); + + pose.translate(getGuiLeft() + (getXSize() / 2d), getGuiTop() + 135, 150); + + float zoom = switch (struct.getSize().getX()) { + case 3 -> 23.5f; + case 5 -> 19.5f; + case 7 -> 15.5f; + case 9 -> 14.5f; + case 11 -> 11.5f; + case 13 -> 10.5f; + default -> 10.5f; + }; + + pose.scale(zoom, -zoom, zoom); + + pose.mulPose(Vector3f.XP.rotationDegrees((float) rotateY)); + pose.mulPose(Vector3f.YP.rotationDegrees((float) rotateX)); + + final var tSize = struct.getSize(); + final float s = tSize.getX() / 2f; + pose.translate(-s, -s + 1, -s); + + final var transformer = new TransformingVertexBuilder(buffer, RenderTypes.WALLS); + + var bb = struct.getBoundingBox(new StructurePlaceSettings(), BlockPos.ZERO); + + var as = new ArmorStand(renderer, 0, 0, 0); + minecraft.cameraEntity = as; + + BlockPos.betweenClosedStream(bb).forEach(pos -> { + pose.pushPose(); + { + pose.translate(pos.getX(), pos.getY(), pos.getZ()); + + final var state = renderer.getBlockState(pos); + transformer.setOverlay(OverlayTexture.RED_OVERLAY_V); + + IModelData modelData = EmptyModelData.INSTANCE; + if (state.hasBlockEntity()) { + final var be = renderer.getBlockEntity(pos); + if (be != null) { + modelData = be.getModelData(); + final var ber = beRenderer.getRenderer(be); + if (ber != null) { + ber.render(be, 1f, pose, buffer, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY); + } + } + } + + try { + pose.pushPose(); + + for (var type : RenderType.chunkBufferLayers()) { + if(!ItemBlockRenderTypes.canRenderInLayer(state, type)) + continue; + + blockRenderer.renderBatched(state, pos, renderer, pose, buffer.getBuffer(type), true, renderer.random, modelData); + } + + pose.popPose(); + } catch (Exception e) { + } + } + pose.popPose(); + }); + } + pose.popPose(); + pose.popPose(); + } catch (Exception e) { + while (lastEntryBeforeTry != pose.last()) + pose.popPose(); + } + + minecraft.cameraEntity = cam; + + buffer.draw(); + RenderSystem.restoreProjectionMatrix(); + } + + @Override + protected void renderBg(PoseStack pose, float p_97788_, int p_97789_, int p_97790_) { + RenderSystem.setShaderTexture(0, new ResourceLocation(CompactMachines.MOD_ID, "textures/gui/room_menu.png")); + + int i = (this.width - this.imageWidth) / 2; + int j = (this.height - this.imageHeight) / 2; + // this.blit(pose, leftPos, topPos, 0, 0, this.imageWidth, this.imageHeight); + } + + public LevelBlockPosition getMachine() { + return menu.getMachine(); + } +} diff --git a/src/main/java/dev/compactmods/machines/room/data/CMLootFunctions.java b/src/main/java/dev/compactmods/machines/room/data/CMLootFunctions.java new file mode 100644 index 00000000..5b979acf --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/data/CMLootFunctions.java @@ -0,0 +1,24 @@ +package dev.compactmods.machines.room.data; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.room.data.CopyRoomBindingFunction; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = CompactMachines.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class CMLootFunctions { + + public static LootItemFunctionType COPY_ROOM_BINDING; + + @SubscribeEvent + static void onLootSerializing(final RegistryEvent.Register evt) { + COPY_ROOM_BINDING = Registry.register(Registry.LOOT_FUNCTION_TYPE, + new ResourceLocation(CompactMachines.MOD_ID, "copy_room_binding"), + new LootItemFunctionType(new CopyRoomBindingFunction.Serializer())); + } +} diff --git a/src/main/java/dev/compactmods/machines/room/data/CompactRoomData.java b/src/main/java/dev/compactmods/machines/room/data/CompactRoomData.java index c65ff506..4120614a 100644 --- a/src/main/java/dev/compactmods/machines/room/data/CompactRoomData.java +++ b/src/main/java/dev/compactmods/machines/room/data/CompactRoomData.java @@ -7,8 +7,7 @@ import dev.compactmods.machines.api.codec.CodecExtensions; import dev.compactmods.machines.api.codec.NbtListCollector; import dev.compactmods.machines.config.ServerConfig; -import dev.compactmods.machines.core.LevelBlockPosition; -import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.location.LevelBlockPosition; import dev.compactmods.machines.core.Registration; import dev.compactmods.machines.room.RoomSize; import dev.compactmods.machines.room.exceptions.NonexistentRoomException; @@ -18,7 +17,6 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.saveddata.SavedData; @@ -45,14 +43,8 @@ public CompactRoomData() { } @Nonnull - public static CompactRoomData get(MinecraftServer server) throws MissingDimensionException { - ServerLevel compactWorld = server.getLevel(Registration.COMPACT_DIMENSION); - if (compactWorld == null) { - CompactMachines.LOGGER.error("No compact dimension found. Report this."); - throw new MissingDimensionException("Compact dimension not found."); - } - - DimensionDataStorage sd = compactWorld.getDataStorage(); + public static CompactRoomData get(ServerLevel compactDim) { + DimensionDataStorage sd = compactDim.getDataStorage(); return sd.computeIfAbsent(CompactRoomData::fromNbt, CompactRoomData::new, DATA_NAME); } @@ -112,6 +104,13 @@ private void register(ChunkPos pos, RoomData data) throws OperationNotSupportedE setDirty(); } + public Optional forRoom(ChunkPos room) { + if(roomData.containsKey(room)) + return Optional.ofNullable(roomData.get(room)); + + return Optional.empty(); + } + public Stream streamRooms() { return roomData.values().stream(); } @@ -146,7 +145,7 @@ public AABB getBounds(ChunkPos roomChunk) throws NonexistentRoomException { if (!roomData.containsKey(roomChunk)) throw new NonexistentRoomException(roomChunk); - return roomData.get(roomChunk).getMachineBounds(); + return roomData.get(roomChunk).getRoomBounds(); } public NewRoomRegistration createNew() { @@ -276,7 +275,7 @@ public void setSpawn(Vec3 newSpawn) { this.spawn = newSpawn; } - public AABB getMachineBounds() { + public AABB getRoomBounds() { return size.getBounds(this.center); } } diff --git a/src/main/java/dev/compactmods/machines/room/data/CopyRoomBindingFunction.java b/src/main/java/dev/compactmods/machines/room/data/CopyRoomBindingFunction.java new file mode 100644 index 00000000..d3da387f --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/data/CopyRoomBindingFunction.java @@ -0,0 +1,51 @@ +package dev.compactmods.machines.room.data; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import dev.compactmods.machines.machine.CompactMachineBlockEntity; +import dev.compactmods.machines.machine.CompactMachineItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction; +import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; + +public class CopyRoomBindingFunction extends LootItemConditionalFunction { + + protected CopyRoomBindingFunction(LootItemCondition[] conditions) { + super(conditions); + } + + public static Builder binding() { + return simpleBuilder(CopyRoomBindingFunction::new); + } + + @Override + protected ItemStack run(ItemStack stack, LootContext ctx) { + var data = ctx.getParam(LootContextParams.BLOCK_ENTITY); + if(data instanceof CompactMachineBlockEntity machine) { + machine.getConnectedRoom().ifPresent(room -> { + CompactMachineItem.setRoom(stack, room); + }); + } + + return stack; + } + + @Override + public LootItemFunctionType getType() { + return CMLootFunctions.COPY_ROOM_BINDING; + } + + public static class Serializer extends LootItemConditionalFunction.Serializer { + public void serialize(JsonObject json, CopyRoomBindingFunction func, JsonSerializationContext ctx) { + super.serialize(json, func, ctx); + } + + public CopyRoomBindingFunction deserialize(JsonObject json, JsonDeserializationContext ctx, LootItemCondition[] conditions) { + return new CopyRoomBindingFunction(conditions); + } + } +} diff --git a/src/main/java/dev/compactmods/machines/room/data/RoomPreview.java b/src/main/java/dev/compactmods/machines/room/data/RoomPreview.java new file mode 100644 index 00000000..021e43f6 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/data/RoomPreview.java @@ -0,0 +1,14 @@ +package dev.compactmods.machines.room.data; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.api.codec.CodecExtensions; +import dev.compactmods.machines.room.RoomSize; +import net.minecraft.world.level.ChunkPos; + +public record RoomPreview(ChunkPos chunk, RoomSize size) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + CodecExtensions.CHUNKPOS.fieldOf("pos").forGetter(RoomPreview::chunk), + RoomSize.CODEC.fieldOf("size").forGetter(RoomPreview::size) + ).apply(i, RoomPreview::new)); +} diff --git a/src/main/java/dev/compactmods/machines/room/graph/CompactMachineRoomNode.java b/src/main/java/dev/compactmods/machines/room/graph/CompactMachineRoomNode.java index 1f427f48..d2438d68 100644 --- a/src/main/java/dev/compactmods/machines/room/graph/CompactMachineRoomNode.java +++ b/src/main/java/dev/compactmods/machines/room/graph/CompactMachineRoomNode.java @@ -4,7 +4,10 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; import dev.compactmods.machines.api.codec.CodecExtensions; +import dev.compactmods.machines.graph.CMGraphRegistration; +import dev.compactmods.machines.graph.GraphNodeBase; import dev.compactmods.machines.graph.IGraphNode; +import dev.compactmods.machines.graph.IGraphNodeType; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.ChunkPos; @@ -23,20 +26,13 @@ public record CompactMachineRoomNode(ChunkPos pos) implements IGraphNode { ).apply(i, (pos, type) -> new CompactMachineRoomNode(pos))); @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CompactMachineRoomNode that = (CompactMachineRoomNode) o; - return pos.equals(that.pos); + public String toString() { + return "CompactMachineRoomNode[" + + "pos=" + pos + ']'; } @Override - public int hashCode() { - return Objects.hash(pos); - } - - @Override - public Codec codec() { - return CODEC; + public IGraphNodeType getType() { + return CMGraphRegistration.ROOM_NODE.get(); } } diff --git a/src/main/java/dev/compactmods/machines/room/history/PlayerRoomHistoryItem.java b/src/main/java/dev/compactmods/machines/room/history/PlayerRoomHistoryItem.java index ecd44d5d..86141107 100644 --- a/src/main/java/dev/compactmods/machines/room/history/PlayerRoomHistoryItem.java +++ b/src/main/java/dev/compactmods/machines/room/history/PlayerRoomHistoryItem.java @@ -5,13 +5,14 @@ import dev.compactmods.machines.api.location.IDimensionalBlockPosition; import dev.compactmods.machines.api.location.IDimensionalPosition; import dev.compactmods.machines.api.room.history.IRoomHistoryItem; -import dev.compactmods.machines.core.LevelBlockPosition; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.location.PreciseDimensionalPosition; -public record PlayerRoomHistoryItem(LevelBlockPosition entry, int machine) implements IRoomHistoryItem { +public record PlayerRoomHistoryItem(PreciseDimensionalPosition entry, LevelBlockPosition machine) implements IRoomHistoryItem { public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( - LevelBlockPosition.CODEC.fieldOf("position").forGetter(PlayerRoomHistoryItem::entry), - Codec.INT.fieldOf("machine").forGetter(IRoomHistoryItem::getMachine) + PreciseDimensionalPosition.CODEC.fieldOf("position").forGetter(PlayerRoomHistoryItem::entry), + LevelBlockPosition.CODEC.fieldOf("machine").forGetter(PlayerRoomHistoryItem::machine) ).apply(i, PlayerRoomHistoryItem::new)); @Override @@ -20,7 +21,7 @@ public IDimensionalPosition getEntryLocation() { } @Override - public int getMachine() { + public IDimensionalBlockPosition getMachine() { return machine; } } diff --git a/src/main/java/dev/compactmods/machines/room/menu/MachineRoomMenu.java b/src/main/java/dev/compactmods/machines/room/menu/MachineRoomMenu.java new file mode 100644 index 00000000..44b1d152 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/menu/MachineRoomMenu.java @@ -0,0 +1,84 @@ +package dev.compactmods.machines.room.menu; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.core.UIRegistration; +import dev.compactmods.machines.room.Rooms; +import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MachineRoomMenu extends AbstractContainerMenu { + private final ChunkPos room; + private final LevelBlockPosition machine; + private StructureTemplate roomBlocks; + + public MachineRoomMenu(int win, ChunkPos room, LevelBlockPosition machine) { + super(UIRegistration.MACHINE_MENU.get(), win); + this.room = room; + this.roomBlocks = new StructureTemplate(); + this.machine = machine; + } + + public ChunkPos getRoom() { + return room; + } + + public LevelBlockPosition getMachine() { + return machine; + } + + public static MenuProvider makeProvider(MinecraftServer server, ChunkPos roomId, LevelBlockPosition machinePos) { + return new MenuProvider() { + @Override + public Component getDisplayName() { + return new TranslatableComponent(CompactMachines.MOD_ID + ".ui.room"); + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int winId, Inventory inv, Player player2) { + var menu = new MachineRoomMenu(winId, roomId, machinePos); + try { + menu.roomBlocks = Rooms.getInternalBlocks(server, roomId); + } catch (MissingDimensionException | NonexistentRoomException e) { + return null; + } + + return menu; + } + }; + } + + @Nonnull + @Override + public ItemStack quickMoveStack(@Nonnull Player player, int slotInd) { + return ItemStack.EMPTY; + } + + @Override + public boolean stillValid(Player player) { + return true; + } + + @Nullable + public StructureTemplate getBlocks() { + return roomBlocks; + } + + public void setBlocks(StructureTemplate blocks) { + this.roomBlocks = blocks; + } +} diff --git a/src/main/java/dev/compactmods/machines/room/network/ClientRoomNetworkHandler.java b/src/main/java/dev/compactmods/machines/room/network/ClientRoomNetworkHandler.java new file mode 100644 index 00000000..2dcc7532 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/network/ClientRoomNetworkHandler.java @@ -0,0 +1,15 @@ +package dev.compactmods.machines.room.network; + +import dev.compactmods.machines.room.client.MachineRoomScreen; +import dev.compactmods.machines.room.menu.MachineRoomMenu; +import net.minecraft.client.Minecraft; + +public class ClientRoomNetworkHandler { + public static void handleBlockData(InitialRoomBlockDataPacket blockData) { + final var mc = Minecraft.getInstance(); + if(mc.screen instanceof MachineRoomScreen mrs) { + mrs.getMenu().setBlocks(blockData.blocks()); + mrs.updateBlockRender(); + } + } +} diff --git a/src/main/java/dev/compactmods/machines/room/network/InitialRoomBlockDataPacket.java b/src/main/java/dev/compactmods/machines/room/network/InitialRoomBlockDataPacket.java new file mode 100644 index 00000000..51106c81 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/network/InitialRoomBlockDataPacket.java @@ -0,0 +1,29 @@ +package dev.compactmods.machines.room.network; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public record InitialRoomBlockDataPacket(StructureTemplate blocks) { + + public static InitialRoomBlockDataPacket fromNetwork(FriendlyByteBuf buf) { + final var nbt = buf.readNbt(); + final var struct = new StructureTemplate(); + struct.load(nbt); + + return new InitialRoomBlockDataPacket(struct); + } + + public void toNetwork(FriendlyByteBuf buf) { + final var tag = blocks.save(new CompoundTag()); + buf.writeNbt(tag); + } + + public boolean handle(Supplier ctx) { + ctx.get().enqueueWork(() -> ClientRoomNetworkHandler.handleBlockData(this)); + return true; + } +} diff --git a/src/main/java/dev/compactmods/machines/room/network/PlayerRequestedTeleportPacket.java b/src/main/java/dev/compactmods/machines/room/network/PlayerRequestedTeleportPacket.java new file mode 100644 index 00000000..a47d2fd5 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/network/PlayerRequestedTeleportPacket.java @@ -0,0 +1,36 @@ +package dev.compactmods.machines.room.network; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.util.PlayerUtil; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.ChunkPos; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public record PlayerRequestedTeleportPacket(LevelBlockPosition machine, ChunkPos room) { + + public PlayerRequestedTeleportPacket(FriendlyByteBuf buf) { + this(buf.readWithCodec(LevelBlockPosition.CODEC), buf.readChunkPos()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeWithCodec(LevelBlockPosition.CODEC, machine); + buf.writeChunkPos(room); + } + + public boolean handle(Supplier ctx) { + ctx.get().enqueueWork(() -> { + final var player = ctx.get().getSender(); + try { + PlayerUtil.teleportPlayerIntoMachine(player.level, player, machine.getBlockPosition()); + } catch (MissingDimensionException e) { + CompactMachines.LOGGER.error("Failed to teleport player into machine.", e); + } + }); + + return true; + } +} diff --git a/src/main/java/dev/compactmods/machines/room/network/PlayerStartedRoomTrackingPacket.java b/src/main/java/dev/compactmods/machines/room/network/PlayerStartedRoomTrackingPacket.java new file mode 100644 index 00000000..9a3bfeaa --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/network/PlayerStartedRoomTrackingPacket.java @@ -0,0 +1,37 @@ +package dev.compactmods.machines.room.network; + +import dev.compactmods.machines.core.MissingDimensionException; +import dev.compactmods.machines.room.Rooms; +import dev.compactmods.machines.room.exceptions.NonexistentRoomException; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.ChunkPos; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.PacketDistributor; + +import java.util.UUID; +import java.util.function.Supplier; + +public record PlayerStartedRoomTrackingPacket(ChunkPos room) { + + public PlayerStartedRoomTrackingPacket(FriendlyByteBuf buf) { + this(buf.readChunkPos()); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeChunkPos(room); + } + + public boolean handle(Supplier ctx) { + var sender = ctx.get().getSender(); + ctx.get().enqueueWork(() -> { + try { + var blocks = Rooms.getInternalBlocks(sender.server, room); + RoomNetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> sender), new InitialRoomBlockDataPacket(blocks)); + } catch (MissingDimensionException | NonexistentRoomException e) { + e.printStackTrace(); + } + }); + + return true; + } +} diff --git a/src/main/java/dev/compactmods/machines/room/network/RoomNetworkHandler.java b/src/main/java/dev/compactmods/machines/room/network/RoomNetworkHandler.java new file mode 100644 index 00000000..dc7fa5bc --- /dev/null +++ b/src/main/java/dev/compactmods/machines/room/network/RoomNetworkHandler.java @@ -0,0 +1,37 @@ +package dev.compactmods.machines.room.network; + +import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.tunnel.network.TunnelAddedPacket; +import dev.compactmods.machines.util.VersionUtil; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; + +public class RoomNetworkHandler { + + private static final ArtifactVersion ROOM_TRACK_VERSION = new DefaultArtifactVersion("1.0.0"); + + public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( + new ResourceLocation(CompactMachines.MOD_ID, "room_tracking"), + ROOM_TRACK_VERSION::toString, + clientVer -> VersionUtil.checkMajor(clientVer, ROOM_TRACK_VERSION), + serverVer -> VersionUtil.checkMajor(serverVer, ROOM_TRACK_VERSION) + ); + + public static void setupMessages() { + CHANNEL.messageBuilder(PlayerStartedRoomTrackingPacket.class, 1, NetworkDirection.PLAY_TO_SERVER) + .encoder(PlayerStartedRoomTrackingPacket::encode) + .decoder(PlayerStartedRoomTrackingPacket::new) + .consumer(PlayerStartedRoomTrackingPacket::handle) + .add(); + + CHANNEL.messageBuilder(InitialRoomBlockDataPacket.class, 2, NetworkDirection.PLAY_TO_CLIENT) + .encoder(InitialRoomBlockDataPacket::toNetwork) + .decoder(InitialRoomBlockDataPacket::fromNetwork) + .consumer(InitialRoomBlockDataPacket::handle) + .add(); + } +} diff --git a/src/main/java/dev/compactmods/machines/tunnel/BaseTunnelWallData.java b/src/main/java/dev/compactmods/machines/tunnel/BaseTunnelWallData.java new file mode 100644 index 00000000..9e4825d1 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/tunnel/BaseTunnelWallData.java @@ -0,0 +1,22 @@ +package dev.compactmods.machines.tunnel; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.compactmods.machines.api.tunnels.TunnelDefinition; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.core.Tunnels; +import net.minecraft.resources.ResourceLocation; + +public record BaseTunnelWallData(LevelBlockPosition connection, ResourceLocation tunnelType) { + public static final String KEY_CONNECTION = "connection"; + public static final String KEY_TUNNEL_TYPE = "tunnel_type"; + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + LevelBlockPosition.CODEC.fieldOf(KEY_CONNECTION).forGetter(BaseTunnelWallData::connection), + ResourceLocation.CODEC.fieldOf(KEY_TUNNEL_TYPE).forGetter(BaseTunnelWallData::tunnelType) + ).apply(i, BaseTunnelWallData::new)); + + public TunnelDefinition tunnel() { + return Tunnels.getDefinition(tunnelType); + } +} diff --git a/src/main/java/dev/compactmods/machines/tunnel/TunnelItem.java b/src/main/java/dev/compactmods/machines/tunnel/TunnelItem.java index 02ea1710..5c09d6ec 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/TunnelItem.java +++ b/src/main/java/dev/compactmods/machines/tunnel/TunnelItem.java @@ -6,16 +6,13 @@ import dev.compactmods.machines.api.location.IDimensionalPosition; import dev.compactmods.machines.api.tunnels.TunnelDefinition; import dev.compactmods.machines.api.tunnels.redstone.IRedstoneTunnel; -import dev.compactmods.machines.core.Capabilities; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Tunnels; +import dev.compactmods.machines.core.*; import dev.compactmods.machines.i18n.TranslationUtil; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.network.NetworkHandler; -import dev.compactmods.machines.network.TunnelAddedPacket; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; +import dev.compactmods.machines.tunnel.network.TunnelAddedPacket; import dev.compactmods.machines.api.room.IRoomHistory; import dev.compactmods.machines.api.room.history.IRoomHistoryItem; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; import dev.compactmods.machines.util.PlayerUtil; import dev.compactmods.machines.wall.SolidWallBlock; import net.minecraft.ChatFormatting; @@ -28,6 +25,7 @@ import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; @@ -41,6 +39,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.event.server.ServerLifecycleEvent; import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.RegistryManager; @@ -136,25 +135,27 @@ public InteractionResult useOn(UseOnContext context) { final BlockPos position = context.getClickedPos(); final BlockState state = level.getBlockState(position); - if (state.getBlock() instanceof SolidWallBlock && player != null) { - getDefinition(context.getItemInHand()).ifPresent(def -> { - try { - boolean success = setupTunnelWall(level, position, context.getClickedFace(), player, def); - if (success && !player.isCreative()) - context.getItemInHand().shrink(1); - } catch (Exception | MissingDimensionException e) { - CompactMachines.LOGGER.error(e); - } - }); - - return InteractionResult.CONSUME; + if(level instanceof ServerLevel sl && sl.dimension().equals(Registration.COMPACT_DIMENSION)) { + if (state.getBlock() instanceof SolidWallBlock && player != null) { + getDefinition(context.getItemInHand()).ifPresent(def -> { + try { + boolean success = setupTunnelWall(sl, position, context.getClickedFace(), player, def); + if (success && !player.isCreative()) + context.getItemInHand().shrink(1); + } catch (Exception | MissingDimensionException e) { + CompactMachines.LOGGER.error(e); + } + }); + + return InteractionResult.CONSUME; + } } return InteractionResult.FAIL; } public static Optional getMachineBindingInfo(Player player) { - final LazyOptional history = player.getCapability(Capabilities.ROOM_HISTORY); + final var history = player.getCapability(Capabilities.ROOM_HISTORY); var mapped = history.resolve().map(hist -> { if (!hist.hasHistory() && player instanceof ServerPlayer sp) { @@ -168,26 +169,22 @@ public static Optional getMachineBindingInfo(Player player) { return Optional.ofNullable(mapped); } - public static Optional getLastEnteredMachinePosition(Player player) { - var lastEnteredMachine = getMachineBindingInfo(player); - return lastEnteredMachine.flatMap(bound -> { - try { - CompactMachineData data = CompactMachineData.get(player.level.getServer()); - return data.getMachineLocation(bound.getMachine()).resolve(); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return Optional.empty(); - } - }); - } - - private static boolean setupTunnelWall(Level level, BlockPos position, Direction side, Player player, TunnelDefinition def) throws Exception, MissingDimensionException { + private static boolean setupTunnelWall(ServerLevel compactDim, BlockPos position, Direction innerFace, Player player, TunnelDefinition def) throws Exception, MissingDimensionException { boolean redstone = def instanceof IRedstoneTunnel; - final var roomTunnels = RoomTunnelData.get(level.getServer(), new ChunkPos(position)); - final var tunnelGraph = roomTunnels.getGraph(); + final var roomTunnels = TunnelConnectionGraph.forRoom(compactDim, player.chunkPosition()); - var placedSides = tunnelGraph.getTunnelSides(def).collect(Collectors.toSet()); + var lastEnteredMachine = getMachineBindingInfo(player); + if (lastEnteredMachine.isEmpty()) { + CompactMachines.LOGGER.warn("Player does not appear to have entered room via a machine;" + + " history is empty. If this is an error, report it."); + return false; + } + + var hist = lastEnteredMachine.get(); + var placedSides = roomTunnels + .getTunnelSides(def) + .collect(Collectors.toSet()); // all tunnels already placed for type if (placedSides.size() == 6) @@ -202,39 +199,32 @@ private static boolean setupTunnelWall(Level level, BlockPos position, Direction return false; } - var lastEnteredMachine = getMachineBindingInfo(player); - if (lastEnteredMachine.isEmpty()) { - CompactMachines.LOGGER.warn("Player does not appear to have entered room via a machine;" + - " history is empty. If this is an error, report it."); - return false; - } - Direction first = newlyPlacedSide.get(); var tunnelState = Tunnels.BLOCK_TUNNEL_WALL.get() .defaultBlockState() - .setValue(TunnelWallBlock.TUNNEL_SIDE, side) + .setValue(TunnelWallBlock.TUNNEL_SIDE, innerFace) .setValue(TunnelWallBlock.CONNECTED_SIDE, first) .setValue(TunnelWallBlock.REDSTONE, redstone); - - var hist = lastEnteredMachine.get(); - boolean connected = tunnelGraph.registerTunnel(position, def, hist.getMachine(), first); + boolean connected = roomTunnels.registerTunnel(position, def, hist.getMachine(), first); if (!connected) { player.displayClientMessage(TranslationUtil.message(Messages.NO_TUNNEL_SIDE), true); return false; } - level.setBlock(position, tunnelState, Block.UPDATE_ALL_IMMEDIATE); + final var oldState = compactDim.getBlockState(position); + compactDim.setBlock(position, tunnelState, Block.UPDATE_NEIGHBORS); - if (level.getBlockEntity(position) instanceof TunnelWallEntity twe) { + if (compactDim.getBlockEntity(position) instanceof TunnelWallEntity twe) { twe.setTunnelType(def); - twe.setConnectedTo(hist.getMachine()); + twe.setConnectedTo(hist.getMachine(), first); - NetworkHandler.MAIN_CHANNEL.send( - PacketDistributor.TRACKING_CHUNK.with(() -> level.getChunkAt(position)), + CompactMachinesNet.CHANNEL.send( + PacketDistributor.TRACKING_CHUNK.with(() -> compactDim.getChunkAt(position)), new TunnelAddedPacket(position, def)); } + compactDim.sendBlockUpdated(position, oldState, tunnelState, Block.UPDATE_ALL); return true; } } diff --git a/src/main/java/dev/compactmods/machines/tunnel/TunnelWallBlock.java b/src/main/java/dev/compactmods/machines/tunnel/TunnelWallBlock.java index 6e2599e9..d90e4f20 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/TunnelWallBlock.java +++ b/src/main/java/dev/compactmods/machines/tunnel/TunnelWallBlock.java @@ -9,7 +9,7 @@ import dev.compactmods.machines.core.Registration; import dev.compactmods.machines.core.Tunnels; import dev.compactmods.machines.i18n.TranslationUtil; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import dev.compactmods.machines.wall.ProtectedWallBlock; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; @@ -97,46 +97,39 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { @Override public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult) { - if (!(level instanceof ServerLevel serverLevel)) + if (level.isClientSide) return InteractionResult.SUCCESS; if (!(level.getBlockEntity(pos) instanceof TunnelWallEntity tunnel)) return InteractionResult.FAIL; - var def = tunnel.getTunnelType(); - final Direction tunnelWallSide = hitResult.getDirection(); + if(level.dimension().equals(Registration.COMPACT_DIMENSION) && level instanceof ServerLevel compactDim) { + var def = tunnel.getTunnelType(); + final Direction tunnelWallSide = hitResult.getDirection(); - if (player.isShiftKeyDown()) { - BlockState solidWall = Registration.BLOCK_SOLID_WALL.get().defaultBlockState(); + if (player.isShiftKeyDown()) { + BlockState solidWall = Registration.BLOCK_SOLID_WALL.get().defaultBlockState(); - level.setBlockAndUpdate(pos, solidWall); + level.setBlockAndUpdate(pos, solidWall); - ItemStack stack = new ItemStack(Tunnels.ITEM_TUNNEL.get(), 1); - CompoundTag defTag = stack.getOrCreateTagElement("definition"); - defTag.putString("id", def.getRegistryName().toString()); + ItemStack stack = new ItemStack(Tunnels.ITEM_TUNNEL.get(), 1); + CompoundTag defTag = stack.getOrCreateTagElement("definition"); + defTag.putString("id", def.getRegistryName().toString()); - ItemEntity ie = new ItemEntity(level, player.getX(), player.getY(), player.getZ(), stack); - level.addFreshEntity(ie); + ItemEntity ie = new ItemEntity(level, player.getX(), player.getY(), player.getZ(), stack); + level.addFreshEntity(ie); - if (def instanceof TunnelTeardownHandler teardown) { - teardown.onRemoved(new TunnelPosition(serverLevel, pos, tunnelWallSide), tunnel.getTunnel()); - } - - try { - final var tunnels = RoomTunnelData.get(serverLevel.getServer(), new ChunkPos(pos)); - final var tunnelGraph = tunnels.getGraph(); + if (def instanceof TunnelTeardownHandler teardown) { + teardown.onRemoved(new TunnelPosition(compactDim, pos, tunnelWallSide), tunnel.getTunnel()); + } - tunnelGraph.unregister(pos); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - } - } else { - // Rotate tunnel - Direction dir = state.getValue(CONNECTED_SIDE); + final var tunnels = TunnelConnectionGraph.forRoom(compactDim, new ChunkPos(pos)); + tunnels.unregister(pos); + } else { + // Rotate tunnel + Direction dir = state.getValue(CONNECTED_SIDE); - try { - final var tunnelData = RoomTunnelData.get(serverLevel.getServer(), new ChunkPos(pos)); - final var tunnelGraph = tunnelData.getGraph(); + final var tunnelGraph = TunnelConnectionGraph.forRoom(compactDim, new ChunkPos(pos)); final var existingDirs = tunnelGraph .getTunnelSides(def) .collect(Collectors.toSet()); @@ -154,14 +147,12 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player level.setBlockAndUpdate(pos, state.setValue(CONNECTED_SIDE, newSide)); if (def instanceof TunnelTeardownHandler teardown) { - teardown.onRotated(new TunnelPosition(serverLevel, pos, tunnelWallSide), tunnel.getTunnel(), dir, newSide); + teardown.onRotated(new TunnelPosition(compactDim, pos, tunnelWallSide), tunnel.getTunnel(), dir, newSide); } tunnelGraph.rotateTunnel(pos, newSide); - tunnelData.setDirty(); + tunnelGraph.setDirty(); }); - } catch (MissingDimensionException e) { - return InteractionResult.FAIL; } } diff --git a/src/main/java/dev/compactmods/machines/tunnel/TunnelWallEntity.java b/src/main/java/dev/compactmods/machines/tunnel/TunnelWallEntity.java index 2534000e..c67b5940 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/TunnelWallEntity.java +++ b/src/main/java/dev/compactmods/machines/tunnel/TunnelWallEntity.java @@ -1,7 +1,7 @@ package dev.compactmods.machines.tunnel; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.location.IDimensionalPosition; +import dev.compactmods.machines.api.location.IDimensionalBlockPosition; import dev.compactmods.machines.api.room.IRoomInformation; import dev.compactmods.machines.api.tunnels.TunnelDefinition; import dev.compactmods.machines.api.tunnels.TunnelPosition; @@ -9,30 +9,36 @@ import dev.compactmods.machines.api.tunnels.lifecycle.InstancedTunnel; import dev.compactmods.machines.api.tunnels.lifecycle.TunnelInstance; import dev.compactmods.machines.api.tunnels.lifecycle.TunnelTeardownHandler; -import dev.compactmods.machines.core.Capabilities; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.core.Tunnels; -import dev.compactmods.machines.machine.data.CompactMachineData; +import dev.compactmods.machines.core.*; +import dev.compactmods.machines.location.LevelBlockPosition; +import dev.compactmods.machines.machine.graph.legacy.LegacyMachineLocationsGraph; +import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.INBTSerializable; import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.event.server.ServerLifecycleEvent; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class TunnelWallEntity extends BlockEntity { - private int connectedMachine; + private static final String NBT_LEGACY_MACHINE_KEY = "machine"; + + @Deprecated(forRemoval = true) + private int legacyMachineId = -1; + + private LevelBlockPosition connectedMachine; private TunnelDefinition tunnelType; private LazyOptional ROOM = LazyOptional.empty(); @@ -50,40 +56,87 @@ public void load(@Nonnull CompoundTag nbt) { super.load(nbt); try { - if (nbt.contains("machine")) { - this.connectedMachine = nbt.getInt("machine"); + // TODO - Remove in 5.0 + if(nbt.contains(NBT_LEGACY_MACHINE_KEY)) { + // 4.2 and below + this.legacyMachineId = nbt.getInt(NBT_LEGACY_MACHINE_KEY); + this.tunnelType = Tunnels.getDefinition(new ResourceLocation(nbt.getString(BaseTunnelWallData.KEY_TUNNEL_TYPE))); + } else { + // 4.3 and above + final var baseData = BaseTunnelWallData.CODEC.parse(NbtOps.INSTANCE, nbt) + .getOrThrow(true, CompactMachines.LOGGER::fatal); + + this.connectedMachine = baseData.connection(); + this.tunnelType = baseData.tunnel(); } + } catch (Exception e) { + this.tunnelType = Tunnels.UNKNOWN.get(); + this.connectedMachine = null; + } + + try { + if (tunnelType instanceof InstancedTunnel it) + this.tunnel = it.newInstance(worldPosition, getTunnelSide()); + + if (tunnel instanceof INBTSerializable persist && nbt.contains("tunnel_data")) { + var data = nbt.get("tunnel_data"); + persist.deserializeNBT(data); + } + } catch (Exception ex) { + CompactMachines.LOGGER.error("Error loading tunnel persistent data at {}; this is likely a cross-mod issue!", worldPosition, ex); + } + } + + @Override + public void onLoad() { + super.onLoad(); - if (nbt.contains("tunnel_type")) { - ResourceLocation type = new ResourceLocation(nbt.getString("tunnel_type")); - this.tunnelType = Tunnels.getDefinition(type); + if (level instanceof ServerLevel sl) { + var chunk = level.getChunkAt(worldPosition); + ROOM = chunk.getCapability(Capabilities.ROOM); + if(legacyMachineId > -1) { try { - if (tunnelType instanceof InstancedTunnel it) - this.tunnel = it.newInstance(worldPosition, getTunnelSide()); - - if (tunnel instanceof INBTSerializable persist && nbt.contains("tunnel_data")) { - var data = nbt.get("tunnel_data"); - persist.deserializeNBT(data); - } - } catch (Exception ex) { - CompactMachines.LOGGER.error("Error loading tunnel persistent data at {}; this is likely a cross-mod issue!", worldPosition, ex); + this.upgradeLegacyData(); + } catch (MissingDimensionException e) { + CompactMachines.LOGGER.error(CompactMachines.CONN_MARKER, "Failed to load legacy location info for tunnel conversion at: {}; removing the tunnel instance.", worldPosition); + this.tunnelType = Tunnels.UNKNOWN.get(); } } - } catch (Exception e) { - this.tunnelType = Tunnels.UNKNOWN.get(); - this.connectedMachine = -1; + + // If tunnel type is unknown, remove the tunnel entirely + // Null tunnel types here mean it's being loaded into the world + if (this.tunnelType != null && tunnelType.equals(Tunnels.UNKNOWN.get())) { + CompactMachines.LOGGER.warn("Removing unknown tunnel type at {}", worldPosition.toShortString()); + sl.setBlock(worldPosition, Registration.BLOCK_SOLID_WALL.get().defaultBlockState(), Block.UPDATE_ALL); + } + } + } + + private void upgradeLegacyData() throws MissingDimensionException { + if(level != null && level.isClientSide) return; + if(this.legacyMachineId == -1) return; + + if(level instanceof ServerLevel sl) { + var leg = LegacyMachineLocationsGraph.get(sl.getServer()); + if(leg != null) + this.connectedMachine = leg.getLocation(this.legacyMachineId); + else { + CompactMachines.LOGGER.error(CompactMachines.CONN_MARKER, "Failed to load legacy location info for tunnel conversion at: {}; removing the tunnel instance.", worldPosition); + this.tunnelType = Tunnels.UNKNOWN.get(); + } } } @Override public void saveAdditional(@Nonnull CompoundTag compound) { if (tunnelType != null) - compound.putString("tunnel_type", tunnelType.getRegistryName().toString()); + compound.putString(BaseTunnelWallData.KEY_TUNNEL_TYPE, tunnelType.getRegistryName().toString()); else - compound.putString("tunnel_type", Tunnels.UNKNOWN.getId().toString()); + compound.putString(BaseTunnelWallData.KEY_TUNNEL_TYPE, Tunnels.UNKNOWN.getId().toString()); - compound.putInt("machine", connectedMachine); + if(connectedMachine != null) + compound.put(BaseTunnelWallData.KEY_CONNECTION, connectedMachine.serializeNBT()); if (tunnel instanceof INBTSerializable persist) { var data = persist.serializeNBT(); @@ -95,42 +148,26 @@ public void saveAdditional(@Nonnull CompoundTag compound) { @Nonnull public CompoundTag getUpdateTag() { CompoundTag nbt = super.getUpdateTag(); - nbt.putString("tunnel_type", tunnelType.getRegistryName().toString()); - nbt.putInt("machine", connectedMachine); + nbt.putString(BaseTunnelWallData.KEY_TUNNEL_TYPE, tunnelType.getRegistryName().toString()); + nbt.put(BaseTunnelWallData.KEY_CONNECTION, connectedMachine.serializeNBT()); return nbt; } @Override public void handleUpdateTag(CompoundTag tag) { super.handleUpdateTag(tag); - if (tag.contains("tunnel_type")) { - var id = new ResourceLocation(tag.getString("tunnel_type")); + if (tag.contains(BaseTunnelWallData.KEY_TUNNEL_TYPE)) { + var id = new ResourceLocation(tag.getString(BaseTunnelWallData.KEY_TUNNEL_TYPE)); this.tunnelType = Tunnels.getDefinition(id); } - if (tag.contains("machine")) { - this.connectedMachine = tag.getInt("machine"); + if (tag.contains(BaseTunnelWallData.KEY_CONNECTION)) { + this.connectedMachine = LevelBlockPosition.fromNBT(tag.getCompound(BaseTunnelWallData.KEY_CONNECTION)); } setChanged(); } - @Override - public void onLoad() { - super.onLoad(); - - if (level instanceof ServerLevel sl) { - var chunk = level.getChunkAt(worldPosition); - ROOM = chunk.getCapability(Capabilities.ROOM); - - // If tunnel type is unknown, remove the tunnel entirely - if (tunnelType != null && tunnelType.equals(Tunnels.UNKNOWN.get())) { - CompactMachines.LOGGER.warn("Removing unknown tunnel type at {}", worldPosition.toShortString()); - sl.setBlock(worldPosition, Registration.BLOCK_SOLID_WALL.get().defaultBlockState(), Block.UPDATE_ALL); - } - } - } - @Nonnull public LazyOptional getTunnelCapability(@Nonnull Capability cap, @Nullable Direction outerSide) { if (level == null || level.isClientSide) @@ -162,24 +199,11 @@ public LazyOptional getCapability(@Nonnull Capability cap, @Nullable D return super.getCapability(cap, side); } - public IDimensionalPosition getConnectedPosition() { - if (level == null || level.isClientSide) return null; - - final MinecraftServer server = level.getServer(); - if (server == null) + public IDimensionalBlockPosition getConnectedPosition() { + if(this.connectedMachine == null) return null; - try { - CompactMachineData machines = CompactMachineData.get(server); - - return machines.getMachineLocation(this.connectedMachine) - .map(dp -> dp.relative(getConnectedSide())) - .orElse(null); - - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return null; - } + return this.connectedMachine.relative(getConnectedSide()); } /** @@ -213,7 +237,7 @@ public void setTunnelType(TunnelDefinition type) { } this.tunnelType = type; - if(type instanceof InstancedTunnel it) + if (type instanceof InstancedTunnel it) this.tunnel = it.newInstance(p.pos(), p.side()); setChanged(); @@ -226,22 +250,16 @@ public TunnelDefinition getTunnelType() { /** * Server only. Changes where the tunnel is connected to. * - * @param machine Machine ID to connect tunnel to. + * @param machine Machine to connect tunnel to. */ - public void setConnectedTo(int machine) { + public void setConnectedTo(IDimensionalBlockPosition machine, Direction side) { if (level == null || level.isClientSide) return; + this.connectedMachine = new LevelBlockPosition(machine); - CompactMachineData data; - try { - data = CompactMachineData.get(level.getServer()); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return; + if(level instanceof ServerLevel sl) { + final var graph = TunnelConnectionGraph.forRoom(sl, new ChunkPos(worldPosition)); + graph.rebind(worldPosition, machine, side); } - - data.getMachineLocation(machine).ifPresent(p -> { - this.connectedMachine = machine; - }); } @Nullable @@ -254,27 +272,18 @@ public void setInstance(TunnelInstance newTunn) { setChanged(); } - public int getMachine() { - return connectedMachine; - } - public void disconnect() { - if (level == null || level.isClientSide) { - this.connectedMachine = -1; + this.connectedMachine = null; return; } - CompactMachineData data; - try { - data = CompactMachineData.get(level.getServer()); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.fatal(e); - return; - } + if(level instanceof ServerLevel compactDim && compactDim.dimension().equals(Registration.COMPACT_DIMENSION)) { + final var tunnelData = TunnelConnectionGraph.forRoom(compactDim, new ChunkPos(worldPosition)); + tunnelData.unregister(worldPosition); - data.remove(this.connectedMachine); - this.connectedMachine = -1; - this.setChanged(); + this.connectedMachine = null; + compactDim.setBlock(worldPosition, Registration.BLOCK_SOLID_WALL.get().defaultBlockState(), Block.UPDATE_ALL); + } } } diff --git a/src/main/java/dev/compactmods/machines/client/TunnelColors.java b/src/main/java/dev/compactmods/machines/tunnel/client/TunnelColors.java similarity index 96% rename from src/main/java/dev/compactmods/machines/client/TunnelColors.java rename to src/main/java/dev/compactmods/machines/tunnel/client/TunnelColors.java index 3ce6c9f6..2a5b2b03 100644 --- a/src/main/java/dev/compactmods/machines/client/TunnelColors.java +++ b/src/main/java/dev/compactmods/machines/tunnel/client/TunnelColors.java @@ -1,4 +1,4 @@ -package dev.compactmods.machines.client; +package dev.compactmods.machines.tunnel.client; import javax.annotation.Nullable; import dev.compactmods.machines.api.tunnels.TunnelDefinition; diff --git a/src/main/java/dev/compactmods/machines/client/TunnelItemColor.java b/src/main/java/dev/compactmods/machines/tunnel/client/TunnelItemColor.java similarity index 93% rename from src/main/java/dev/compactmods/machines/client/TunnelItemColor.java rename to src/main/java/dev/compactmods/machines/tunnel/client/TunnelItemColor.java index 53a55b14..b66b256e 100644 --- a/src/main/java/dev/compactmods/machines/client/TunnelItemColor.java +++ b/src/main/java/dev/compactmods/machines/tunnel/client/TunnelItemColor.java @@ -1,4 +1,4 @@ -package dev.compactmods.machines.client; +package dev.compactmods.machines.tunnel.client; import java.util.Optional; import dev.compactmods.machines.api.tunnels.TunnelDefinition; diff --git a/src/main/java/dev/compactmods/machines/tunnel/data/RoomTunnelData.java b/src/main/java/dev/compactmods/machines/tunnel/data/RoomTunnelData.java deleted file mode 100644 index f6be5dbf..00000000 --- a/src/main/java/dev/compactmods/machines/tunnel/data/RoomTunnelData.java +++ /dev/null @@ -1,68 +0,0 @@ -package dev.compactmods.machines.tunnel.data; - -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.saveddata.SavedData; - -import javax.annotation.Nonnull; -import java.io.File; - -public class RoomTunnelData extends SavedData { - - private final TunnelConnectionGraph graph; - - public RoomTunnelData() { - graph = new TunnelConnectionGraph(); - } - - public static String getDataFilename(ChunkPos room) { - return "tunnels_" + room.x + "_" + room.z; - } - - public static RoomTunnelData get(MinecraftServer server, ChunkPos room) throws MissingDimensionException { - final var level = server.getLevel(Registration.COMPACT_DIMENSION); - if (level == null) throw new MissingDimensionException(); - - final var storage = level.getDataStorage(); - return storage.computeIfAbsent(RoomTunnelData::fromDisk, RoomTunnelData::new, getDataFilename(room)); - } - - public static File getFile(MinecraftServer server, ChunkPos room) throws MissingDimensionException { - final var level = server.getLevel(Registration.COMPACT_DIMENSION); - if (level == null) throw new MissingDimensionException(); - - final var storage = level.getDataStorage(); - return storage.getDataFile(getDataFilename(room)); - } - - private static RoomTunnelData fromDisk(CompoundTag tag) { - var instance = new RoomTunnelData(); - - if(tag.contains("graph")) { - var g = tag.getCompound("graph"); - instance.graph.deserializeNBT(g); - } - - return instance; - } - - - - @Nonnull - @Override - public CompoundTag save(CompoundTag tag) { - var gData = graph.serializeNBT(); - tag.put("graph", gData); - - return tag; - } - - @Nonnull - public TunnelConnectionGraph getGraph() { - return this.graph; - } -} diff --git a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelConnectionGraph.java b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelConnectionGraph.java index d4899a10..1576f5f0 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelConnectionGraph.java +++ b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelConnectionGraph.java @@ -1,17 +1,18 @@ package dev.compactmods.machines.tunnel.graph; -import com.google.common.graph.*; -import com.mojang.serialization.Codec; +import com.google.common.graph.EndpointPair; +import com.google.common.graph.MutableValueGraph; +import com.google.common.graph.ValueGraphBuilder; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.api.codec.NbtListCollector; +import dev.compactmods.machines.api.location.IDimensionalBlockPosition; import dev.compactmods.machines.api.tunnels.TunnelDefinition; import dev.compactmods.machines.api.tunnels.capability.CapabilityTunnel; -import dev.compactmods.machines.api.codec.NbtListCollector; import dev.compactmods.machines.core.Tunnels; -import dev.compactmods.machines.graph.CompactGraphs; -import dev.compactmods.machines.graph.IGraphEdge; -import dev.compactmods.machines.graph.IGraphNode; +import dev.compactmods.machines.graph.*; +import dev.compactmods.machines.location.LevelBlockPosition; import dev.compactmods.machines.machine.graph.CompactMachineNode; -import dev.compactmods.machines.machine.graph.MachineLinkEdge; +import dev.compactmods.machines.machine.graph.MachineRoomEdge; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -19,6 +20,9 @@ import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.saveddata.SavedData; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.INBTSerializable; @@ -31,7 +35,7 @@ * Represents a room's tunnel connections in a graph-style format. * This should be accessed through the saved data for specific machine room chunks. */ -public class TunnelConnectionGraph implements INBTSerializable { +public class TunnelConnectionGraph extends SavedData implements INBTSerializable { /** * The full data graph. Contains tunnel nodes, machine ids, and tunnel type information. @@ -46,14 +50,14 @@ public class TunnelConnectionGraph implements INBTSerializable { /** * Quick access to machine information nodes. */ - private final Map machines; + private final Map machines; /** * Quick access to tunnel definition nodes. */ private final Map tunnelTypes; - public TunnelConnectionGraph() { + private TunnelConnectionGraph() { graph = ValueGraphBuilder .directed() .build(); @@ -63,13 +67,39 @@ public TunnelConnectionGraph() { tunnelTypes = new HashMap<>(); } + private TunnelConnectionGraph(CompoundTag nbt) { + this(); + this.deserializeNBT(nbt); + } + + public static TunnelConnectionGraph forRoom(ServerLevel compactDim, ChunkPos room) { + final var key = getDataFilename(room); + return compactDim.getDataStorage().computeIfAbsent( + TunnelConnectionGraph::new, + TunnelConnectionGraph::new, + key + ); + } + + @Nonnull + @Override + public CompoundTag save(CompoundTag tag) { + var gData = this.serializeNBT(); + tag.put("graph", gData); + return tag; + } + + public static String getDataFilename(ChunkPos room) { + return "tunnels_" + room.x + "_" + room.z; + } + /** * Finds which machine a tunnel is connected to. * * @param tunnel The tunnel to find a connection for. * @return The id of the connected machine. */ - public Optional connectedMachine(BlockPos tunnel) { + public Optional connectedMachine(BlockPos tunnel) { if (!tunnels.containsKey(tunnel)) return Optional.empty(); @@ -77,8 +107,9 @@ public Optional connectedMachine(BlockPos tunnel) { return graph.successors(tNode) .stream() .filter(CompactMachineNode.class::isInstance) + .map(CompactMachineNode.class::cast) .findFirst() - .map(mNode -> ((CompactMachineNode) mNode).machineId()); + .map(CompactMachineNode::dimpos); } /** @@ -87,55 +118,35 @@ public Optional connectedMachine(BlockPos tunnel) { * * @param tunnelPos The position of the tunnel inside the room. * @param type The type of tunnel being registered. - * @param machineId The machine the tunnel is to be connected to. + * @param machine The machine the tunnel is to be connected to. * @param side The side of the machine the tunnel is connecting to. * @return True if the connection could be established; false if the tunnel type is already registered for the given side. */ - public boolean registerTunnel(BlockPos tunnelPos, TunnelDefinition type, int machineId, Direction side) { + public boolean registerTunnel(BlockPos tunnelPos, TunnelDefinition type, IDimensionalBlockPosition machine, Direction side) { // First we need to get the machine the tunnel is trying to connect to - var machineNode = getOrCreateMachineNode(machineId); - - TunnelNode tunnelNode = getOrCreateTunnelNode(tunnelPos); + var machineNode = getOrCreateMachineNode(machine); + var tunnelNode = getOrCreateTunnelNode(tunnelPos); if (graph.hasEdgeConnecting(tunnelNode, machineNode)) { // connection already formed between the tunnel at pos and the machine - graph.edgeValue(tunnelNode, machineNode).ifPresent(edge -> { - CompactMachines.LOGGER.info("Tunnel already registered for machine {} at position {}.", - machineId, - tunnelPos); - }); + CompactMachines.LOGGER.info("Tunnel already registered for machine {} at position {}.", + machine, + tunnelPos); return false; } - // graph direction is (tunnel)-[connected_to]->(machine) - var tunnelsForSide = getTunnelsForSide(machineId, side).collect(Collectors.toSet()); - var tunnelTypeNode = getOrCreateTunnelTypeNode(type); - // If tunnels are registered for the requested side, make sure there isn't a type conflict - if (!tunnelsForSide.isEmpty()) { - for (var sidedTunnel : tunnelsForSide) { - // if we already have a tunnel with the same side and type, log the conflict and early exit - var existingConn = graph.edgeValue(sidedTunnel, tunnelTypeNode); - if (existingConn.isPresent()) { - CompactMachines.LOGGER.info("Tunnel type {} already registered for side {} at position {}.", type.getRegistryName(), - side.getSerializedName(), - sidedTunnel.position()); - - return false; - } - } - } - // no tunnels registered for side yet - free to make new tunnel node - createTunnelAndLink(tunnelNode, side, machineNode, tunnelTypeNode); + var newTM = graph.putEdgeValue(tunnelNode, machineNode, new TunnelMachineEdge(side)); + var newTT = graph.putEdgeValue(tunnelNode, tunnelTypeNode, new TunnelTypeEdge()); + + setDirty(); return true; } private void createTunnelAndLink(TunnelNode newTunnel, Direction side, CompactMachineNode machNode, TunnelTypeNode typeNode) { - var newEdge = new TunnelMachineEdge(side); - graph.putEdgeValue(newTunnel, machNode, newEdge); - graph.putEdgeValue(newTunnel, typeNode, new TunnelTypeEdge()); + } @Nonnull @@ -144,20 +155,23 @@ public TunnelNode getOrCreateTunnelNode(BlockPos tunnelPos) { return tunnels.get(tunnelPos); var newTunnel = new TunnelNode(tunnelPos); - graph.addNode(newTunnel); tunnels.put(tunnelPos, newTunnel); + graph.addNode(newTunnel); + setDirty(); + return newTunnel; } - public CompactMachineNode getOrCreateMachineNode(int machineId) { - var machineRegistered = machines.containsKey(machineId); + public CompactMachineNode getOrCreateMachineNode(IDimensionalBlockPosition machine) { + var machineRegistered = machines.containsKey(machine); CompactMachineNode node; if (!machineRegistered) { - node = new CompactMachineNode(machineId); - machines.put(machineId, node); + node = new CompactMachineNode(machine.dimensionKey(), machine.getBlockPosition()); + machines.put(machine, node); graph.addNode(node); + setDirty(); } else { - node = machines.get(machineId); + node = machines.get(machine); } return node; @@ -170,8 +184,9 @@ public TunnelTypeNode getOrCreateTunnelTypeNode(TunnelDefinition definition) { return tunnelTypes.get(id); TunnelTypeNode newType = new TunnelTypeNode(id); - graph.addNode(newType); tunnelTypes.put(id, newType); + graph.addNode(newType); + setDirty(); return newType; } @@ -187,9 +202,9 @@ public Stream getTunnelNodesByType(TunnelDefinition type) { if (defNode == null) return Stream.empty(); - return graph.predecessors(defNode) + return graph.adjacentNodes(defNode) .stream() - .filter(n -> n instanceof TunnelNode) + .filter(TunnelNode.class::isInstance) .map(TunnelNode.class::cast); } @@ -200,13 +215,24 @@ public Set getTunnelsByType(TunnelDefinition type) { .collect(Collectors.toSet()); } + public Optional getTunnelSide(TunnelNode node) { + return graph.adjacentNodes(node).stream() + .filter(CompactMachineNode.class::isInstance) + .map(mn -> graph.edgeValue(node, mn)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(TunnelMachineEdge.class::cast) + .map(TunnelMachineEdge::side) + .findFirst(); + } + public Optional getTunnelSide(BlockPos pos) { if (!tunnels.containsKey(pos)) return Optional.empty(); var node = tunnels.get(pos); - return graph.successors(node).stream() - .filter(outNode -> outNode instanceof CompactMachineNode) + return graph.adjacentNodes(node).stream() + .filter(CompactMachineNode.class::isInstance) .map(mn -> graph.edgeValue(node, mn)) .filter(Optional::isPresent) .map(Optional::get) @@ -215,6 +241,30 @@ public Optional getTunnelSide(BlockPos pos) { .findFirst(); } + public Optional getTunnelInfo(BlockPos tunnel) { + if (!tunnels.containsKey(tunnel)) + return Optional.empty(); + + var node = tunnels.get(tunnel); + var typeNode = graph.successors(node).stream() + .filter(TunnelTypeNode.class::isInstance) + .map(TunnelTypeNode.class::cast) + .findFirst() + .orElseThrow(); + + var mach = connectedMachine(tunnel).orElseThrow(); + var side = getTunnelSide(tunnel).orElseThrow(); + var type = typeNode.id(); + + return Optional.of(new TunnelMachineInfo(tunnel, type, new LevelBlockPosition(mach), side)); + } + + public Stream tunnels() { + return tunnels.keySet().stream() + .map(this::getTunnelInfo) + .filter(Optional::isPresent) + .map(Optional::get); + } public Stream nodes() { return graph.nodes().stream(); @@ -228,11 +278,14 @@ public CompoundTag serializeNBT() { HashMap nodeIds = new HashMap<>(); + final var nodeReg = CMGraphRegistration.NODE_TYPE_REG.get(); + final var nodeRegCodec = nodeReg.getCodec() + .dispatchStable(IGraphNode::getType, IGraphNodeType::codec); + var nodeList = nodes().map(node -> { CompoundTag nodeInfo = new CompoundTag(); - var codec = node.codec(); - var encoded = codec.encodeStart(NbtOps.INSTANCE, node); + var encoded = nodeRegCodec.encodeStart(NbtOps.INSTANCE, node); var nodeEncoded = encoded.getOrThrow(false, CompactMachines.LOGGER::error); var id = UUID.randomUUID(); @@ -250,7 +303,7 @@ public CompoundTag serializeNBT() { //noinspection OptionalGetWithoutIsPresent var realEdge = graph.edgeValue(edge).get(); - var codec = realEdge.codec(); + var codec = realEdge.getEdgeType().codec(); var encoded = codec.encodeStart(NbtOps.INSTANCE, realEdge); var edgeEnc = encoded.getOrThrow(false, CompactMachines.LOGGER::error); @@ -269,105 +322,104 @@ public CompoundTag serializeNBT() { @Override public void deserializeNBT(CompoundTag tag) { - // Early exit if there are no nodes - no data to load - if (!tag.contains("nodes")) - return; + if (!tag.contains("graph")) return; + + final var g = tag.getCompound("graph"); + + final var nodeReg = CMGraphRegistration.NODE_TYPE_REG.get(); + final var nodeRegCodec = nodeReg.getCodec() + .dispatchStable(IGraphNode::getType, IGraphNodeType::codec); - final var nodes = tag.getList("nodes", Tag.TAG_COMPOUND); + final var edgeRegCodec = CMGraphRegistration.EDGE_TYPE_REG.get().getCodec() + .dispatchStable(IGraphEdge::getEdgeType, IGraphEdgeType::codec); + + final var nodes = g.getList("nodes", Tag.TAG_COMPOUND); HashMap nodeMap = new HashMap<>(nodes.size()); - for (var nodeNbt : nodes) { - if (!(nodeNbt instanceof CompoundTag nt)) - continue; + if (tag.contains("nodes", Tag.TAG_LIST)) { + for (var nodeNbt : nodes) { + if (!(nodeNbt instanceof CompoundTag nt)) + continue; - if (!nt.contains("data") || !nt.hasUUID("id")) - continue; + if (!nt.contains("data") || !nt.hasUUID("id")) + continue; - UUID nodeId = nt.getUUID("id"); - CompoundTag nodeData = nt.getCompound("data"); + UUID nodeId = nt.getUUID("id"); + CompoundTag nodeData = nt.getCompound("data"); - ResourceLocation nodeType = new ResourceLocation(nodeData.getString("type")); - Codec codec = CompactGraphs.getCodecForNode(nodeType); - if (codec == null) continue; + var result = nodeRegCodec.parse(NbtOps.INSTANCE, nodeData) + .getOrThrow(false, CompactMachines.LOGGER::error); - var res = codec.parse(NbtOps.INSTANCE, nodeData); + if (result == null) continue; - try { - final IGraphNode node = res.getOrThrow(false, CompactMachines.LOGGER::error); - if (node instanceof CompactMachineNode m) { - final var mn = getOrCreateMachineNode(m.machineId()); - nodeMap.putIfAbsent(nodeId, mn); - } + try { + if (result instanceof CompactMachineNode m) { + nodeMap.putIfAbsent(nodeId, m); + } - if (node instanceof TunnelNode t) { - final var tn = getOrCreateTunnelNode(t.position()); - nodeMap.putIfAbsent(nodeId, tn); - } + if (result instanceof TunnelNode t) { + final var tn = getOrCreateTunnelNode(t.position()); + nodeMap.putIfAbsent(nodeId, tn); + } - if (node instanceof TunnelTypeNode tt) { - final var ttn = getOrCreateTunnelTypeNode(Tunnels.getDefinition(tt.id())); - nodeMap.putIfAbsent(nodeId, ttn); - } + if (result instanceof TunnelTypeNode tt) { + final var ttn = getOrCreateTunnelTypeNode(Tunnels.getDefinition(tt.id())); + nodeMap.putIfAbsent(nodeId, ttn); + } - } catch (RuntimeException ignored) { + } catch (RuntimeException ignored) { + } } } // No edges - skip rest of processing - if (!tag.contains("edges")) - return; - - final var edgeTags = tag.getList("edges", Tag.TAG_COMPOUND); - for (var edgeTag : edgeTags) { - if (!(edgeTag instanceof CompoundTag edge)) - continue; - - // invalid edge data - if (!edge.contains("data") || !edge.hasUUID("from") || !edge.hasUUID("to")) - continue; + if (g.contains("edges", Tag.TAG_LIST)) { + final var edgeTags = g.getList("edges", Tag.TAG_COMPOUND); + for (var edgeTag : edgeTags) { + if (!(edgeTag instanceof CompoundTag edge)) + continue; - var nodeFrom = nodeMap.get(edge.getUUID("from")); - var nodeTo = nodeMap.get(edge.getUUID("to")); + // invalid edge data + if (!edge.contains("data") || !edge.hasUUID("from") || !edge.hasUUID("to")) + continue; - if (nodeFrom == null || nodeTo == null) - continue; + var nodeFrom = nodeMap.get(edge.getUUID("from")); + var nodeTo = nodeMap.get(edge.getUUID("to")); - var edgeCodec = CompactGraphs.getCodecForEdge(new ResourceLocation(edge.getCompound("data").getString("type"))); - if (edgeCodec == null) - continue; + if (nodeFrom == null || nodeTo == null) + continue; - var edgeData = edgeCodec.parse(NbtOps.INSTANCE, edge.getCompound("data")) - .getOrThrow(false, CompactMachines.LOGGER::error); + var edgeData = edgeRegCodec.parse(NbtOps.INSTANCE, edge.getCompound("data")) + .getOrThrow(false, CompactMachines.LOGGER::error); - graph.putEdgeValue(nodeFrom, nodeTo, edgeData); + graph.putEdgeValue(nodeFrom, nodeTo, edgeData); + } } } - public boolean hasTunnel(BlockPos zero) { - return tunnels.containsKey(zero); + public boolean hasTunnel(BlockPos location) { + return tunnels.containsKey(location); } - public Stream getTunnelsSupporting(int machine, Direction side, Capability capability) { - final IGraphNode node = machines.get(machine); + public Stream getTunnelsSupporting(LevelBlockPosition machine, Direction side, Capability capability) { + final var node = machines.get(machine); if (node == null) return Stream.empty(); return getTunnelsForSide(machine, side) - .filter(sided -> { - return graph.successors(sided).stream() - .filter(TunnelTypeNode.class::isInstance) - .map(TunnelTypeNode.class::cast) - .anyMatch(ttn -> { - var def = Tunnels.getDefinition(ttn.id()); - if (!(def instanceof CapabilityTunnel tcp)) - return false; - - return tcp.getSupportedCapabilities().contains(capability); - }); - }).map(TunnelNode::position); - } - - public Stream getTypesForSide(int machine, Direction side) { - final IGraphNode node = machines.get(machine); + .filter(sided -> graph.successors(sided).stream() + .filter(TunnelTypeNode.class::isInstance) + .map(TunnelTypeNode.class::cast) + .anyMatch(ttn -> { + var def = Tunnels.getDefinition(ttn.id()); + if (!(def instanceof CapabilityTunnel tcp)) + return false; + + return tcp.getSupportedCapabilities().contains(capability); + })).map(TunnelNode::position); + } + + public Stream getTypesForSide(LevelBlockPosition machine, Direction side) { + final var node = machines.get(machine); if (node == null) return Stream.empty(); return getTunnelsForSide(machine, side) @@ -378,7 +430,7 @@ public Stream getTypesForSide(int machine, Direction side) { .distinct(); } - public Stream getTunnelsForSide(int machine, Direction side) { + public Stream getTunnelsForSide(IDimensionalBlockPosition machine, Direction side) { final var node = machines.get(machine); if (node == null) return Stream.empty(); @@ -396,31 +448,12 @@ public Stream getTunnelSides(TunnelDefinition type) { if (!tunnelTypes.containsKey(type.getRegistryName())) return Stream.empty(); - return getTunnelsByType(type).stream() + return getTunnelNodesByType(type) .map(this::getTunnelSide) .filter(Optional::isPresent) .map(Optional::get); } - public void deleteMachine(int machine) { - if (!machines.containsKey(machine)) return; - - final var node = machines.get(machine); - - // Remove all connected tunnels - graph.predecessors(node).stream() - .filter(TunnelNode.class::isInstance) - .map(TunnelNode.class::cast) - .forEach(node1 -> { - var p = node1.position(); - tunnels.remove(p); - graph.removeNode(node1); - }); - - graph.removeNode(node); - machines.remove(machine); - } - public void clear() { for (var machine : machines.values()) graph.removeNode(machine); @@ -435,24 +468,32 @@ public void clear() { tunnels.clear(); } - - public Stream getMachineTunnels(int machine, TunnelDefinition type) { + public Stream getMachineTunnels(IDimensionalBlockPosition machine, TunnelDefinition type) { return getTunnelNodesByType(type) .map(TunnelNode::position) - .filter(position -> connectedMachine(position).map(m -> m == machine).orElse(false)) + .filter(position -> connectedMachine(position).map(machine::equals).orElse(false)) .map(BlockPos::immutable); } + /** + * Unlinks a tunnel at a specified point inside the machine room. + * + * @param pos Tunnel position inside the room. + */ public void unregister(BlockPos pos) { if (!hasTunnel(pos)) return; + CompactMachines.LOGGER.debug("Unregistering tunnel at {}", pos); + final var existing = tunnels.get(pos); graph.removeNode(existing); tunnels.remove(pos); cleanupOrphanedTypes(); cleanupOrphanedMachines(); + + setDirty(); } private void cleanupOrphans() { @@ -470,7 +511,12 @@ private void cleanupOrphanedTypes() { } }); - removedTypes.forEach(tunnelTypes::remove); + if (!removedTypes.isEmpty()) { + CompactMachines.LOGGER.debug("Removed {} tunnel type nodes during cleanup.", removedTypes.size()); + removedTypes.forEach(tunnelTypes::remove); + setDirty(); + } + } private void cleanupOrphanedTunnels() { @@ -482,11 +528,15 @@ private void cleanupOrphanedTunnels() { } }); - removed.forEach(tunnels::remove); + if (!removed.isEmpty()) { + CompactMachines.LOGGER.debug("Removed {} tunnel nodes during cleanup.", removed.size()); + removed.forEach(tunnels::remove); + setDirty(); + } } private void cleanupOrphanedMachines() { - HashSet removed = new HashSet<>(); + HashSet removed = new HashSet<>(); machines.forEach((machine, node) -> { if (graph.degree(node) == 0) { graph.removeNode(node); @@ -494,7 +544,11 @@ private void cleanupOrphanedMachines() { } }); - removed.forEach(machines::remove); + if (!removed.isEmpty()) { + CompactMachines.LOGGER.debug("Removed {} machine nodes during cleanup.", removed.size()); + removed.forEach(machines::remove); + setDirty(); + } } public void rotateTunnel(BlockPos tunnel, Direction newSide) { @@ -510,20 +564,22 @@ public void rotateTunnel(BlockPos tunnel, Direction newSide) { final var m = machines.get(machine); graph.removeEdge(t, m); graph.putEdgeValue(t, m, new TunnelMachineEdge(newSide)); + + setDirty(); }); } - public Stream getMachines() { + public Stream getMachines() { return this.machines.keySet().stream(); } - public Stream getConnections(int machineId) { - if (!machines.containsKey(machineId)) + public Stream getConnections(IDimensionalBlockPosition machine) { + if (!machines.containsKey(machine)) return Stream.empty(); - final var mNode = machines.get(machineId); + final var mNode = machines.get(machine); return graph.incidentEdges(mNode).stream() - .filter(e -> graph.edgeValue(e).orElseThrow() instanceof MachineLinkEdge) + .filter(e -> graph.edgeValue(e).orElseThrow() instanceof MachineRoomEdge) .map(edge -> { if (edge.nodeU() instanceof TunnelNode cmn) return cmn; if (edge.nodeV() instanceof TunnelNode cmn2) return cmn2; @@ -532,7 +588,16 @@ public Stream getConnections(int machineId) { } - public boolean hasAnyConnectedTo(int machineId) { - return getConnections(machineId).findAny().isPresent(); + public boolean hasAnyConnectedTo(IDimensionalBlockPosition machine) { + return getConnections(machine).findAny().isPresent(); + } + + public void rebind(BlockPos tunnel, IDimensionalBlockPosition newMachine, Direction side) { + CompactMachines.LOGGER.debug("Rebinding tunnel at {} to machine {}", tunnel, newMachine); + + final var tunnelNode = getOrCreateTunnelNode(tunnel); + final var newMachineNode = getOrCreateMachineNode(newMachine); + graph.putEdgeValue(tunnelNode, newMachineNode, new TunnelMachineEdge(side)); + setDirty(); } } diff --git a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineEdge.java b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineEdge.java index 933967d0..f083cb28 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineEdge.java +++ b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineEdge.java @@ -3,10 +3,18 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.graph.CMGraphRegistration; import dev.compactmods.machines.graph.IGraphEdge; +import dev.compactmods.machines.graph.IGraphEdgeType; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.registries.ForgeRegistryEntry; +import java.util.Objects; + +/** + * Bridges connection between a tunnel and a given machine side. + */ public record TunnelMachineEdge(Direction side) implements IGraphEdge { private static final ResourceLocation TYPE = new ResourceLocation(CompactMachines.MOD_ID, "tunnel_machine"); @@ -16,7 +24,13 @@ public record TunnelMachineEdge(Direction side) implements IGraphEdge { ).apply(i, (side, t) -> new TunnelMachineEdge(side))); @Override - public Codec codec() { - return CODEC; + public String toString() { + return "TunnelMachineEdge[" + + "side=" + side + ']'; + } + + @Override + public IGraphEdgeType getEdgeType() { + return CMGraphRegistration.TUNNEL_MACHINE_LINK.get(); } } diff --git a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineInfo.java b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineInfo.java new file mode 100644 index 00000000..636d111c --- /dev/null +++ b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelMachineInfo.java @@ -0,0 +1,9 @@ +package dev.compactmods.machines.tunnel.graph; + +import dev.compactmods.machines.location.LevelBlockPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; + +public record TunnelMachineInfo(BlockPos location, ResourceLocation type, LevelBlockPosition machine, Direction side) { +} diff --git a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelNode.java b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelNode.java index 1100f445..73904032 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelNode.java +++ b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelNode.java @@ -3,20 +3,61 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.graph.IGraphNode; +import dev.compactmods.machines.graph.CMGraphRegistration; +import dev.compactmods.machines.graph.GraphNodeBase; +import dev.compactmods.machines.graph.IGraphNodeType; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; -public record TunnelNode(BlockPos position) implements IGraphNode { +import java.util.Objects; + +public final class TunnelNode extends GraphNodeBase implements IGraphNodeType { private static final ResourceLocation TYPE = new ResourceLocation(CompactMachines.MOD_ID, "tunnel"); public static final Codec CODEC = RecordCodecBuilder.create((i) -> i.group( BlockPos.CODEC.fieldOf("pos").forGetter(TunnelNode::position), ResourceLocation.CODEC.fieldOf("type").forGetter(x -> TYPE) ).apply(i, (bpos, type) -> new TunnelNode(bpos))); + private final BlockPos position; + + public TunnelNode() { + this.position = BlockPos.ZERO; + } + + public TunnelNode(BlockPos position) { + this.position = position; + } @Override public Codec codec() { return CODEC; } + + public BlockPos position() { + return position; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (TunnelNode) obj; + return Objects.equals(this.position, that.position); + } + + @Override + public int hashCode() { + return Objects.hash(position); + } + + @Override + public String toString() { + return "TunnelNode[" + + "position=" + position + ']'; + } + + @Override + public IGraphNodeType getType() { + return CMGraphRegistration.TUNNEL_NODE.get(); + } } diff --git a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeEdge.java b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeEdge.java index f7ed6b80..549f02d7 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeEdge.java +++ b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeEdge.java @@ -3,18 +3,39 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; +import dev.compactmods.machines.graph.CMGraphRegistration; import dev.compactmods.machines.graph.IGraphEdge; +import dev.compactmods.machines.graph.IGraphEdgeType; import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; -public record TunnelTypeEdge() implements IGraphEdge { +public final class TunnelTypeEdge implements IGraphEdge { private static final ResourceLocation TYPE = new ResourceLocation(CompactMachines.MOD_ID, "tunnel_type"); public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( ResourceLocation.CODEC.fieldOf("type").forGetter(x -> TYPE) ).apply(i, (t) -> new TunnelTypeEdge())); + public TunnelTypeEdge() { + } + + @Override + public boolean equals(Object obj) { + return obj == this || obj != null && obj.getClass() == this.getClass(); + } + + @Override + public int hashCode() { + return 1; + } + + @Override + public String toString() { + return "TunnelTypeEdge[]"; + } + @Override - public Codec codec() { - return CODEC; + public @NotNull IGraphEdgeType getEdgeType() { + return CMGraphRegistration.TUNNEL_TYPE.get(); } } diff --git a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeNode.java b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeNode.java index 04482d2c..5fa6b7dd 100644 --- a/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeNode.java +++ b/src/main/java/dev/compactmods/machines/tunnel/graph/TunnelTypeNode.java @@ -3,19 +3,60 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.graph.IGraphNode; +import dev.compactmods.machines.graph.CMGraphRegistration; +import dev.compactmods.machines.graph.GraphNodeBase; +import dev.compactmods.machines.graph.IGraphNodeType; import net.minecraft.resources.ResourceLocation; -public record TunnelTypeNode(ResourceLocation id) implements IGraphNode { +import java.util.Objects; + +public final class TunnelTypeNode extends GraphNodeBase implements IGraphNodeType { private static final ResourceLocation TYPE = new ResourceLocation(CompactMachines.MOD_ID, "tunnel_type"); public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( ResourceLocation.CODEC.fieldOf("tunnel_type").forGetter(TunnelTypeNode::id), ResourceLocation.CODEC.fieldOf("type").forGetter(x -> TYPE) ).apply(i, (tunn, type) -> new TunnelTypeNode(tunn))); + private final ResourceLocation id; + + public TunnelTypeNode() { + this.id = null; + } + + public TunnelTypeNode(ResourceLocation id) { + this.id = id; + } @Override public Codec codec() { return CODEC; } + + public ResourceLocation id() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (TunnelTypeNode) obj; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "TunnelTypeNode[" + + "id=" + id + ']'; + } + + @Override + public IGraphNodeType getType() { + return CMGraphRegistration.TUNNEL_TYPE_NODE.get(); + } } diff --git a/src/main/java/dev/compactmods/machines/network/TunnelAddedPacket.java b/src/main/java/dev/compactmods/machines/tunnel/network/TunnelAddedPacket.java similarity index 96% rename from src/main/java/dev/compactmods/machines/network/TunnelAddedPacket.java rename to src/main/java/dev/compactmods/machines/tunnel/network/TunnelAddedPacket.java index eb849e10..fb769ad4 100644 --- a/src/main/java/dev/compactmods/machines/network/TunnelAddedPacket.java +++ b/src/main/java/dev/compactmods/machines/tunnel/network/TunnelAddedPacket.java @@ -1,4 +1,4 @@ -package dev.compactmods.machines.network; +package dev.compactmods.machines.tunnel.network; import dev.compactmods.machines.api.tunnels.TunnelDefinition; import dev.compactmods.machines.core.Tunnels; diff --git a/src/main/java/dev/compactmods/machines/util/DimensionUtil.java b/src/main/java/dev/compactmods/machines/util/DimensionUtil.java index 4cb9652e..384a28c9 100644 --- a/src/main/java/dev/compactmods/machines/util/DimensionUtil.java +++ b/src/main/java/dev/compactmods/machines/util/DimensionUtil.java @@ -37,15 +37,15 @@ public class DimensionUtil { public static void createAndRegisterWorldAndDimension(final MinecraftServer server) { final var map = server.forgeGetWorldMap(); - // get everything we need to create the dimension and the level + // get everything we need to create the dimension and the dimension final ServerLevel overworld = server.getLevel(Level.OVERWORLD); - // dimension keys have a 1:1 relationship with level keys, they have the same IDs as well + // dimension keys have a 1:1 relationship with dimension keys, they have the same IDs as well final ResourceKey dimensionKey = ResourceKey.create(Registry.LEVEL_STEM_REGISTRY, Registration.COMPACT_DIMENSION.location()); final var serverResources = server.getResourceManager(); - // only back up level.dat in production + // only back up dimension.dat in production if (FMLEnvironment.production && !doLevelFileBackup(server)) return; var reg = server.registryAccess(); @@ -76,10 +76,10 @@ public static void createAndRegisterWorldAndDimension(final MinecraftServer serv final WorldGenSettings worldGenSettings = worldData.worldGenSettings(); final DerivedLevelData derivedLevelData = new DerivedLevelData(worldData, worldData.overworldData()); - // now we have everything we need to create the dimension and the level + // now we have everything we need to create the dimension and the dimension // this is the same order server init creates levels: // the dimensions are already registered when levels are created, we'll do that first - // then instantiate level, add border listener, add to map, fire world load event + // then instantiate dimension, add border listener, add to map, fire world load event // register the actual dimension if(worldGenSettings.dimensions() instanceof MappedRegistry stems) { @@ -115,10 +115,10 @@ public static void createAndRegisterWorldAndDimension(final MinecraftServer serv */ overworld.getWorldBorder().addListener(new BorderChangeListener.DelegateBorderChangeListener(newWorld.getWorldBorder())); - // register level + // register dimension map.put(Registration.COMPACT_DIMENSION, newWorld); - // update forge's world cache so the new level can be ticked + // update forge's world cache so the new dimension can be ticked server.markWorldsDirty(); // fire world load event @@ -130,12 +130,12 @@ public static boolean doLevelFileBackup(MinecraftServer server) { var levelRoot = server.getWorldPath(LevelResource.ROOT); var levelFile = server.getWorldPath(LevelResource.LEVEL_DATA_FILE); - var formatter = DateTimeFormatter.ofPattern("'cm4-level-'yyyyMMdd-HHmmss'.dat'"); + var formatter = DateTimeFormatter.ofPattern("'cm4-dimension-'yyyyMMdd-HHmmss'.dat'"); var timestamp = formatter.format(ZonedDateTime.now()); try { Files.copy(levelFile, levelRoot.resolve(timestamp)); } catch (IOException e) { - CompactMachines.LOGGER.error("Failed to backup level.dat file before modification; canceling register dim attempt."); + CompactMachines.LOGGER.error("Failed to backup dimension.dat file before modification; canceling register dim attempt."); return false; } diff --git a/src/main/java/dev/compactmods/machines/util/PlayerUtil.java b/src/main/java/dev/compactmods/machines/util/PlayerUtil.java index 8b6061d4..daef4372 100644 --- a/src/main/java/dev/compactmods/machines/util/PlayerUtil.java +++ b/src/main/java/dev/compactmods/machines/util/PlayerUtil.java @@ -5,16 +5,16 @@ import dev.compactmods.machines.advancement.AdvancementTriggers; import dev.compactmods.machines.api.core.Messages; import dev.compactmods.machines.core.Capabilities; -import dev.compactmods.machines.core.LevelBlockPosition; +import dev.compactmods.machines.location.LevelBlockPosition; import dev.compactmods.machines.core.MissingDimensionException; import dev.compactmods.machines.core.Registration; import dev.compactmods.machines.i18n.TranslationUtil; +import dev.compactmods.machines.location.PreciseDimensionalPosition; import dev.compactmods.machines.machine.CompactMachineBlockEntity; -import dev.compactmods.machines.machine.Machines; -import dev.compactmods.machines.room.RoomSize; import dev.compactmods.machines.room.Rooms; import dev.compactmods.machines.api.room.IRoomHistory; import dev.compactmods.machines.api.room.history.IRoomHistoryItem; +import dev.compactmods.machines.room.exceptions.NonexistentRoomException; import dev.compactmods.machines.room.history.PlayerRoomHistoryItem; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; @@ -42,51 +42,59 @@ public static Optional getProfileByUUID(LevelAccessor world, UUID u return Optional.of(profile); } - public static void teleportPlayerIntoMachine(Level level, Player player, BlockPos machinePos, RoomSize size) { - MinecraftServer serv = level.getServer(); + public static void teleportPlayerIntoMachine(Level machineLevel, Player player, BlockPos machinePos) throws MissingDimensionException { + MinecraftServer serv = machineLevel.getServer(); ServerLevel compactWorld = serv.getLevel(Registration.COMPACT_DIMENSION); if (compactWorld == null) { - CompactMachines.LOGGER.warn("Compact dimension not found; player attempted to enter machine."); - return; + throw new MissingDimensionException("Compact dimension not found; player attempted to enter machine."); } - CompactMachineBlockEntity tile = (CompactMachineBlockEntity) level.getBlockEntity(machinePos); - if (tile == null) - return; + if(machineLevel.getBlockEntity(machinePos) instanceof CompactMachineBlockEntity tile) { + final var targetRoom = tile.getConnectedRoom(); + boolean grantAdvancement = targetRoom.isEmpty(); - final boolean grantAdvancement = !tile.mapped(); - if (!tile.mapped()) { - ChunkPos newRoomPos; - try { - newRoomPos = Rooms.createNew(serv, size, player.getUUID()); - Machines.createAndLink(serv, level, machinePos, tile, newRoomPos); - } catch (MissingDimensionException e) { - CompactMachines.LOGGER.error("Error occurred while generating new room and machine info for first player entry.", e); - return; - } + targetRoom.ifPresent(room -> { + if (player.level.dimension().equals(Registration.COMPACT_DIMENSION) && player.chunkPosition().equals(room)) { + if (player instanceof ServerPlayer sp) { + AdvancementTriggers.RECURSIVE_ROOMS.trigger(sp); + } + + return; + } + + try { + teleportPlayerIntoRoom(serv, player, room, grantAdvancement); + + // Mark the player as inside the machine, set external spawn, and yeet + player.getCapability(Capabilities.ROOM_HISTORY).ifPresent(hist -> { + var entry = PreciseDimensionalPosition.fromPlayer(player); + hist.addHistory(new PlayerRoomHistoryItem(entry, tile.getLevelPosition())); + }); + } catch (MissingDimensionException | NonexistentRoomException e) { + CompactMachines.LOGGER.fatal("Critical error; could not enter a freshly-created room instance.", e); + } + }); } + } - serv.submitAsync(() -> { - LevelBlockPosition spawn = tile.getSpawn().orElse(null); - if (spawn == null) { - CompactMachines.LOGGER.error("Machine " + tile.machineId + " could not load spawn info."); - return; - } + public static void teleportPlayerIntoRoom(MinecraftServer serv, Player player, ChunkPos room, boolean grantAdvancement) throws MissingDimensionException, NonexistentRoomException { + final var compactDim = serv.getLevel(Registration.COMPACT_DIMENSION); + final var spawn = Rooms.getSpawn(serv, room); + final var roomSize = Rooms.sizeOf(serv, room); - try { - // Mark the player as inside the machine, set external spawn, and yeet - addPlayerToMachine(player, machinePos); - } catch (Exception ex) { - CompactMachines.LOGGER.error(ex); - } + if (spawn == null) { + CompactMachines.LOGGER.error("Room %s could not load spawn info.".formatted(room)); + return; + } + serv.submitAsync(() -> { Vec3 sp = spawn.getExactPosition(); Vec3 sr = spawn.getRotation().orElse(new Vec3(player.xRotO, player.yRotO, 0)); if (player instanceof ServerPlayer servPlayer) { servPlayer.teleportTo( - compactWorld, + compactDim, sp.x, sp.y, sp.z, @@ -94,7 +102,7 @@ public static void teleportPlayerIntoMachine(Level level, Player player, BlockPo (float) sr.x); if (grantAdvancement) - AdvancementTriggers.getTriggerForMachineClaim(size).trigger(servPlayer); + AdvancementTriggers.getTriggerForMachineClaim(roomSize).trigger(servPlayer); } }); } @@ -157,28 +165,4 @@ public static void teleportPlayerToRespawnOrOverworld(MinecraftServer serv, @Non player.teleportTo(level, worldPos.x(), worldPos.y(), worldPos.z(), 0, player.getRespawnAngle()); } - - public static void addPlayerToMachine(Player player, BlockPos machinePos) { - MinecraftServer serv = player.getServer(); - if (serv == null) - return; - - CompactMachineBlockEntity tile = (CompactMachineBlockEntity) player.getLevel().getBlockEntity(machinePos); - if (tile == null) - return; - - tile.getInternalChunkPos().ifPresent(mChunk -> { - final LevelChunk chunk = serv.getLevel(Registration.COMPACT_DIMENSION) - .getChunk(mChunk.x, mChunk.z); - - player.getCapability(Capabilities.ROOM_HISTORY) - .ifPresent(hist -> { - LevelBlockPosition pos = LevelBlockPosition.fromEntity(player); - hist.addHistory(new PlayerRoomHistoryItem(pos, tile.machineId)); - }); - - // TODO - player tracking packet - }); - } - } diff --git a/src/main/java/dev/compactmods/machines/util/VersionUtil.java b/src/main/java/dev/compactmods/machines/util/VersionUtil.java new file mode 100644 index 00000000..75b83266 --- /dev/null +++ b/src/main/java/dev/compactmods/machines/util/VersionUtil.java @@ -0,0 +1,17 @@ +package dev.compactmods.machines.util; + +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; + +public class VersionUtil { + public static boolean checkMajor(String version, ArtifactVersion art) { + try { + final var v = new DefaultArtifactVersion(version); + return v.getMajorVersion() >= art.getMajorVersion(); + } + + catch(Exception e) { + return false; + } + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 91b53014..857c1537 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -14,4 +14,8 @@ public net.minecraft.server.MinecraftServer f_129756_ # progressListenerFactory public net.minecraft.world.level.border.WorldBorder f_61905_ # listeners public-f net.minecraft.world.level.levelgen.WorldGenSettings f_64605_ # dimensions public net.minecraft.world.level.border.BorderChangeListener$DelegateBorderChangeListener f_61864_ # worldBorder -public-f net.minecraft.client.renderer.DimensionSpecialEffects f_108857_ # EFFECTS \ No newline at end of file +public-f net.minecraft.client.renderer.DimensionSpecialEffects f_108857_ # EFFECTS + +# Rendering Stuff +public net.minecraft.world.level.dimension.DimensionType f_63848_ # DEFAULT_OVERWORLD +public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate f_74482_ # palettes \ No newline at end of file diff --git a/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.fsh b/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.fsh new file mode 100644 index 00000000..1c53a3e2 --- /dev/null +++ b/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.fsh @@ -0,0 +1,27 @@ +#version 150 + +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec2 texCoord0; +in vec4 normal; +in vec4 overlayColor; + +out vec4 fragColor; + +void main() { + vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; + if (color.a < 0.1) { + discard; + } + color.rgb = mix(overlayColor.rgb, color.rgb, overlayColor.a); + fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); +} \ No newline at end of file diff --git a/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.json b/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.json new file mode 100644 index 00000000..505bddc0 --- /dev/null +++ b/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.json @@ -0,0 +1,120 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "compactmachines:block_fullbright", + "fragment": "compactmachines:block_fullbright", + "attributes": [ + "Position", + "Color", + "UV0", + "UV1", + "Normal" + ], + "samplers": [ + { + "name": "Sampler0" + }, + { + "name": "Sampler1" + } + ], + "uniforms": [ + { + "name": "ModelViewMat", + "type": "matrix4x4", + "count": 16, + "values": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "name": "ProjMat", + "type": "matrix4x4", + "count": 16, + "values": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "name": "ChunkOffset", + "type": "float", + "count": 3, + "values": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "ColorModulator", + "type": "float", + "count": 4, + "values": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "FogStart", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "FogEnd", + "type": "float", + "count": 1, + "values": [ + 1.0 + ] + }, + { + "name": "FogColor", + "type": "float", + "count": 4, + "values": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + } + ] +} diff --git a/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.vsh b/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.vsh new file mode 100644 index 00000000..9da20e32 --- /dev/null +++ b/src/main/resources/assets/compactmachines/shaders/core/block_fullbright.vsh @@ -0,0 +1,28 @@ +#version 150 + +in vec3 Position; +in vec4 Color; +in vec2 UV0; +in ivec2 UV1; +in vec3 Normal; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform vec3 ChunkOffset; +uniform sampler2D Sampler1; + +out float vertexDistance; +out vec4 vertexColor; +out vec2 texCoord0; +out vec4 normal; +out vec4 overlayColor; + +void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position + ChunkOffset, 1.0); + + vertexDistance = length((ModelViewMat * vec4(Position + ChunkOffset, 1.0)).xyz); + vertexColor = Color; + texCoord0 = UV0; + normal = ProjMat * ModelViewMat * vec4(Normal, 0.0); + overlayColor = texelFetch(Sampler1, UV1, 0); +} \ No newline at end of file diff --git a/src/main/resources/assets/compactmachines/shaders/core/wall.fsh b/src/main/resources/assets/compactmachines/shaders/core/wall.fsh new file mode 100644 index 00000000..52013e07 --- /dev/null +++ b/src/main/resources/assets/compactmachines/shaders/core/wall.fsh @@ -0,0 +1,27 @@ +#version 150 + +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec2 texCoord0; +in vec4 normal; +in vec4 overlayColor; + +out vec4 fragColor; + +void main() { + vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; + if (color.a < 0.1) { + discard; + } + color.rgb = mix(overlayColor.rgb, color.rgb, 0.1); + fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); +} \ No newline at end of file diff --git a/src/main/resources/assets/compactmachines/shaders/core/wall.json b/src/main/resources/assets/compactmachines/shaders/core/wall.json new file mode 100644 index 00000000..149c2448 --- /dev/null +++ b/src/main/resources/assets/compactmachines/shaders/core/wall.json @@ -0,0 +1,120 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "compactmachines:block_fullbright", + "fragment": "compactmachines:wall", + "attributes": [ + "Position", + "Color", + "UV0", + "UV1", + "Normal" + ], + "samplers": [ + { + "name": "Sampler0" + }, + { + "name": "Sampler1" + } + ], + "uniforms": [ + { + "name": "ModelViewMat", + "type": "matrix4x4", + "count": 16, + "values": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "name": "ProjMat", + "type": "matrix4x4", + "count": 16, + "values": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "name": "ChunkOffset", + "type": "float", + "count": 3, + "values": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "ColorModulator", + "type": "float", + "count": 4, + "values": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "FogStart", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "FogEnd", + "type": "float", + "count": 1, + "values": [ + 1.0 + ] + }, + { + "name": "FogColor", + "type": "float", + "count": 4, + "values": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + } + ] +} diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_giant.png b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_giant.png new file mode 100644 index 00000000..f9b9f799 Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_giant.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_large.png b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_large.png new file mode 100644 index 00000000..d80b50f0 Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_large.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_maximum.png b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_maximum.png new file mode 100644 index 00000000..d4103bc0 Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_maximum.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_normal.png b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_normal.png new file mode 100644 index 00000000..c976efff Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_normal.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_small.png b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_small.png new file mode 100644 index 00000000..66313d61 Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_small.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_tiny.png b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_tiny.png new file mode 100644 index 00000000..ba22215b Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/block/machine/e x/machine_tiny.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/machine_giant.png b/src/main/resources/assets/compactmachines/textures/block/machine/machine_giant.png index f9b9f799..ca7a1c72 100644 Binary files a/src/main/resources/assets/compactmachines/textures/block/machine/machine_giant.png and b/src/main/resources/assets/compactmachines/textures/block/machine/machine_giant.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/machine_large.png b/src/main/resources/assets/compactmachines/textures/block/machine/machine_large.png index d80b50f0..b316aba8 100644 Binary files a/src/main/resources/assets/compactmachines/textures/block/machine/machine_large.png and b/src/main/resources/assets/compactmachines/textures/block/machine/machine_large.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/machine_maximum.png b/src/main/resources/assets/compactmachines/textures/block/machine/machine_maximum.png index d4103bc0..102304a8 100644 Binary files a/src/main/resources/assets/compactmachines/textures/block/machine/machine_maximum.png and b/src/main/resources/assets/compactmachines/textures/block/machine/machine_maximum.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/machine_normal.png b/src/main/resources/assets/compactmachines/textures/block/machine/machine_normal.png index c976efff..a2314ede 100644 Binary files a/src/main/resources/assets/compactmachines/textures/block/machine/machine_normal.png and b/src/main/resources/assets/compactmachines/textures/block/machine/machine_normal.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/machine_small.png b/src/main/resources/assets/compactmachines/textures/block/machine/machine_small.png index 66313d61..03cf5ecc 100644 Binary files a/src/main/resources/assets/compactmachines/textures/block/machine/machine_small.png and b/src/main/resources/assets/compactmachines/textures/block/machine/machine_small.png differ diff --git a/src/main/resources/assets/compactmachines/textures/block/machine/machine_tiny.png b/src/main/resources/assets/compactmachines/textures/block/machine/machine_tiny.png index ba22215b..4debc3d8 100644 Binary files a/src/main/resources/assets/compactmachines/textures/block/machine/machine_tiny.png and b/src/main/resources/assets/compactmachines/textures/block/machine/machine_tiny.png differ diff --git a/src/main/resources/assets/compactmachines/textures/gui/room_menu.png b/src/main/resources/assets/compactmachines/textures/gui/room_menu.png new file mode 100644 index 00000000..2f5fcdf8 Binary files /dev/null and b/src/main/resources/assets/compactmachines/textures/gui/room_menu.png differ diff --git a/src/main/resources/compactmachines.mixin.json b/src/main/resources/compactmachines.mixin.json new file mode 100644 index 00000000..5b5cc26c --- /dev/null +++ b/src/main/resources/compactmachines.mixin.json @@ -0,0 +1,15 @@ +{ + "required": true, + "priority": 1100, + "package": "dev.compactmods.machines.mixin", + "compatibilityLevel": "JAVA_16", + "refmap": "compactmachines.refmap.json", + "mixins": [], + "client": [ + "ModelDataRefreshMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "minVersion": "0.8" +} \ No newline at end of file diff --git a/src/test/java/dev/compactmods/machines/test/TestBatches.java b/src/test/java/dev/compactmods/machines/test/TestBatches.java index 25cfa8da..db6923de 100644 --- a/src/test/java/dev/compactmods/machines/test/TestBatches.java +++ b/src/test/java/dev/compactmods/machines/test/TestBatches.java @@ -1,6 +1,6 @@ package dev.compactmods.machines.test; -public class TestBatches { +public final class TestBatches { public static final String TUNNELS = "tunnels"; public static final String TUNNEL_DATA = "tunnel_data"; diff --git a/src/test/java/dev/compactmods/machines/test/machine/MachineDataTests.java b/src/test/java/dev/compactmods/machines/test/machine/MachineDataTests.java deleted file mode 100644 index dc79f2f8..00000000 --- a/src/test/java/dev/compactmods/machines/test/machine/MachineDataTests.java +++ /dev/null @@ -1,76 +0,0 @@ -package dev.compactmods.machines.test.machine; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Optional; -import com.mojang.serialization.Codec; -import com.mojang.serialization.DataResult; -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.machine.data.CompactMachineData; -import dev.compactmods.machines.core.LevelBlockPosition; -import dev.compactmods.machines.test.TestBatches; -import dev.compactmods.machines.test.util.FileHelper; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Registry; -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.gametest.framework.GameTestHelper; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; - -@PrefixGameTestTemplate(false) -@GameTestHolder(CompactMachines.MOD_ID) -public class MachineDataTests { - final static Path EXTERNAL = Paths.get("scenario", "single-machine-player-inside", "machines_external.nbt"); - - final static Codec> c = CompactMachineData.MachineData.CODEC.listOf() - .fieldOf("locations") - .stable() - .codec(); - - @GameTest(template = "empty_5x5", timeoutTicks = 240, batch = TestBatches.MACHINE_DATA) - public static void canLoadSingleMachineData(final GameTestHelper game) throws IOException { - // The external point is overworld @ 8x4x8 (it was made in a default void superflat) - LevelBlockPosition OUTSIDE = new LevelBlockPosition( - ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("overworld")), - new BlockPos(8, 4, 8) - ); - - CompoundTag nbt = FileHelper.getNbtFromFile(EXTERNAL.toString()); - CompoundTag data = nbt.getCompound("data"); - DataResult> result = c.parse(NbtOps.INSTANCE, data); - - if (data.isEmpty()) { - game.fail("Expected data to be read from external data file; it was empty."); - return; - } - - Optional> res = result.result(); - if (res.isEmpty()) { - game.fail("Expected machine info; got nothing."); - return; - } - - res.ifPresent(list -> { - if (list.size() != 1) { - game.fail("Expected exactly one connection; got " + list.size()); - return; - } - - CompactMachines.LOGGER.debug("hi - " + game.getTick()); - - CompactMachineData.MachineData extern = list.get(0); - if(!extern.location.equals(OUTSIDE)) { - game.fail("Connected position is not correct."); - return; - } - - game.succeed(); - }); - } -} diff --git a/src/test/java/dev/compactmods/machines/test/machine/MachineGraphTests.java b/src/test/java/dev/compactmods/machines/test/machine/MachineGraphTests.java deleted file mode 100644 index 611f57b9..00000000 --- a/src/test/java/dev/compactmods/machines/test/machine/MachineGraphTests.java +++ /dev/null @@ -1,221 +0,0 @@ -package dev.compactmods.machines.test.machine; - -import com.mojang.serialization.DataResult; -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.api.codec.CodecExtensions; -import dev.compactmods.machines.graph.CompactMachineConnectionGraph; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import dev.compactmods.machines.test.TestBatches; -import dev.compactmods.machines.util.MathUtil; -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.gametest.framework.GameTestHelper; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.nbt.Tag; -import net.minecraft.world.level.ChunkPos; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; - -import java.util.Collection; -import java.util.Objects; -import java.util.Optional; - -@PrefixGameTestTemplate(false) -@GameTestHolder(CompactMachines.MOD_ID) -public class MachineGraphTests { - - private static CompactMachineConnectionGraph generateGraphWithSingleRoom() { - CompactMachineConnectionGraph g = new CompactMachineConnectionGraph(); - - g.addMachine(0); - g.addRoom(new ChunkPos(0, 0)); - - g.connectMachineToRoom(0, new ChunkPos(0, 0)); - return g; - } - - private static CompactMachineConnectionGraph generateGraphWithMultipleRooms(int numRooms) { - CompactMachineConnectionGraph g = new CompactMachineConnectionGraph(); - - for (int i = 0; i < numRooms; i++) { - g.addMachine(i); - ChunkPos chunk = MathUtil.getChunkForRoomIndex(i); - g.addRoom(chunk); - g.connectMachineToRoom(i, chunk); - } - - return g; - } - - private static void verifySingleRoomValid(GameTestHelper test, CompactMachineConnectionGraph graph, int machine, ChunkPos room) { - Optional connectedRoom = graph.getConnectedRoom(machine); - if (connectedRoom.isEmpty()) test.fail("Connected room not found."); - - connectedRoom.ifPresent(cRoom -> { - if (!room.equals(cRoom)) test.fail("Room not equal."); - }); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.MACHINE_GRAPH) - public static void basicGraph(final GameTestHelper test) { - - CompactMachineConnectionGraph g = new CompactMachineConnectionGraph(); - - if (g.getMachines().findAny().isPresent()) test.fail("Graph should have been empty after construction."); - - // At construction, no machines or rooms are registered - - // Make sure that there's no linked machines here - try { - g.getMachinesFor(ChunkPos.ZERO); - test.fail("getMachinesFor should throw an error, but it did not"); - } catch (NonexistentRoomException e) { - // desired behavior, the room doesn't exist yet - } - - // Make sure there's no linked rooms - Optional connectedRoom = g.getConnectedRoom(0); - Objects.requireNonNull(connectedRoom); - if (connectedRoom.isPresent()) test.fail("No room connections should be present."); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.MACHINE_GRAPH) - public static void canCreateGraphWithLinkedMachine(final GameTestHelper test) { - int machine = 0; - ChunkPos room = new ChunkPos(0, 0); - - CompactMachineConnectionGraph g = generateGraphWithSingleRoom(); - - verifySingleRoomValid(test, g, machine, room); - - try { - Collection linkedMachines = g.getMachinesFor(room); - if (1 != linkedMachines.size()) - test.fail("Expected exactly one linked machine; got " + linkedMachines.size()); - - if (!linkedMachines.contains(machine)) - test.fail("Expected machine 0 to be linked; did not exist in linked machine collection"); - - test.succeed(); - } catch (NonexistentRoomException e) { - test.fail(e.toString()); - } - - } - - - @GameTest(template = "empty_1x1", batch = TestBatches.MACHINE_GRAPH) - public static void canCreateMultipleRoomsWithSingleLinkedMachine(final GameTestHelper test) { - CompactMachineConnectionGraph graph = generateGraphWithMultipleRooms(10); - - for (int roomIndex = 0; roomIndex < 10; roomIndex++) { - final ChunkPos EXPECTED_CHUNK = MathUtil.getChunkForRoomIndex(roomIndex); - - verifySingleRoomValid(test, graph, roomIndex, EXPECTED_CHUNK); - } - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.MACHINE_GRAPH) - public static void canCreateRoomWithMultipleLinkedMachines(final GameTestHelper test) { - int MACHINE_1 = 0; - int MACHINE_2 = 1; - ChunkPos EXPECTED_ROOM = new ChunkPos(0, 0); - - CompactMachineConnectionGraph g = new CompactMachineConnectionGraph(); - - g.addMachine(0); - g.addMachine(1); - - ChunkPos roomChunk = new ChunkPos(0, 0); - g.addRoom(roomChunk); - g.connectMachineToRoom(0, roomChunk); - g.connectMachineToRoom(1, roomChunk); - - verifySingleRoomValid(test, g, 0, EXPECTED_ROOM); - verifySingleRoomValid(test, g, 1, EXPECTED_ROOM); - - try { - Collection linkedMachines = g.getMachinesFor(EXPECTED_ROOM); - if (2 != linkedMachines.size()) test.fail("Linked machine count was not correct"); - - if (!linkedMachines.contains(MACHINE_1)) test.fail("1st machine not found in linked machine set"); - - if (!linkedMachines.contains(MACHINE_2)) test.fail("2nd machine not found in linked machine set"); - - test.succeed(); - } - - catch(NonexistentRoomException nre) { - test.fail(nre.toString()); - } - } - - @GameTest(template = "empty_1x1", batch = TestBatches.MACHINE_GRAPH) - public static void canSerialize(final GameTestHelper test) { - CompactMachineConnectionGraph graph = generateGraphWithSingleRoom(); - - DataResult nbtResult = CompactMachineConnectionGraph.CODEC.encodeStart(NbtOps.INSTANCE, graph); - - nbtResult.resultOrPartial(test::fail).ifPresent(nbt -> { - if (!(nbt instanceof CompoundTag graphData)) { - test.fail("Encoded graph not expected tag type"); - return; - } - - if (graphData.isEmpty()) test.fail("Encoded tag does not have any data."); - - if (!graphData.contains("connections")) test.fail("Connection info not found."); - - ListTag connections = graphData.getList("connections", Tag.TAG_COMPOUND); - if (1 != connections.size()) test.fail("Expected one connection from a machine to a single room."); - - CompoundTag room1 = connections.getCompound(0); - - if (!room1.contains("machine")) test.fail("Machine info in connection not found."); - - if (!room1.contains("connections")) test.fail("Machine connection info not found."); - - Tag machineChunk = room1.get("machine"); - DataResult chunkRes = CodecExtensions.CHUNKPOS.parse(NbtOps.INSTANCE, machineChunk); - chunkRes.resultOrPartial(test::fail).ifPresent(chunk -> { - if (!new ChunkPos(0, 0).equals(chunk)) test.fail("Room chunk location is not correct."); - }); - - ListTag roomMachineConnections = room1.getList("connections", Tag.TAG_INT); - if (1 != roomMachineConnections.size()) - test.fail("Expected exactly 1 machine to be connected to the room."); - - if (0 != roomMachineConnections.getInt(0)) test.fail("Expected the connected machine ID to be 0."); - }); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.MACHINE_GRAPH) - public static void simpleNestedMachines(final GameTestHelper test) { - /* - Overworld - Contains Machine 0, linked to Room 0 - CompactWorld - Contains Machine 1, linked to Room 1 (Inside Room 0) - */ - CompactMachineConnectionGraph graph = new CompactMachineConnectionGraph(); - - graph.addMachine(0); - graph.addMachine(1); - - // Add two rooms - ChunkPos room0 = MathUtil.getChunkForRoomIndex(0); - ChunkPos room1 = MathUtil.getChunkForRoomIndex(1); - graph.addRoom(room0); - graph.addRoom(room1); - - graph.connectMachineToRoom(0, room0); - graph.connectMachineToRoom(1, room1); - - test.succeed(); - } -} diff --git a/src/test/java/dev/compactmods/machines/test/tunnel/TunnelGraphTests.java b/src/test/java/dev/compactmods/machines/test/tunnel/TunnelGraphTests.java deleted file mode 100644 index 198d83df..00000000 --- a/src/test/java/dev/compactmods/machines/test/tunnel/TunnelGraphTests.java +++ /dev/null @@ -1,260 +0,0 @@ -package dev.compactmods.machines.test.tunnel; - -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.core.Tunnels; -import dev.compactmods.machines.test.TestBatches; -import dev.compactmods.machines.tunnel.graph.TunnelConnectionGraph; -import dev.compactmods.machines.tunnel.graph.TunnelNode; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.gametest.framework.GameTestGenerator; -import net.minecraft.gametest.framework.GameTestHelper; -import net.minecraft.gametest.framework.TestFunction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; -import net.minecraftforge.items.CapabilityItemHandler; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.HashSet; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -@PrefixGameTestTemplate(false) -@GameTestHolder(CompactMachines.MOD_ID) -public class TunnelGraphTests { - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canCreateGraph(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - - int size = graph.size(); - if (size > 0) - test.fail("Graph should begin empty."); - - test.succeed(); - } - - @GameTestGenerator - public static Collection usingTunnelItemOnWall() { - HashSet tests = new HashSet<>(); - - // TestFunction(String batch, String testName, String structure, int testTime, long setupTime, boolean isRequired, Consumer tester) - for (var dir : Direction.values()) { - var template = new ResourceLocation(CompactMachines.MOD_ID, "empty_1x1"); - Consumer test = (t) -> sidedTunnelTest(dir, t); - - var testf = new TestFunction(TestBatches.TUNNEL_DATA, "tunnel_register_" + dir.getSerializedName(), template.toString(), 100, 0, true, test); - tests.add(testf); - } - - return tests; - } - - private static void sidedTunnelTest(Direction dir, GameTestHelper test) { - var graph = new TunnelConnectionGraph(); - - if (!graph.registerTunnel(BlockPos.ZERO, Tunnels.ITEM_TUNNEL_DEF.get(), 1, dir)) - test.fail("Failed to register for direction: " + dir); - - graph.getTunnelSide(BlockPos.ZERO).ifPresentOrElse(side -> { - if (side != dir) - test.fail("Tunnel not registered on side."); - }, () -> test.fail("Tunnel not found after registration; getTunnelSide returned empty")); - - final var sides = graph.getTunnelsForSide(1, dir) - .map(TunnelNode::position) - .collect(Collectors.toSet()); - - if (!sides.contains(BlockPos.ZERO)) - test.fail("Sided tunnel list for machine did not return position"); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canRegisterTunnel(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - - try { - var registered = graph.registerTunnel(BlockPos.ZERO, Tunnels.UNKNOWN.get(), 1, Direction.NORTH); - if (!registered) - test.fail("Expected tunnel to be registered without error"); - - var typed = graph.getTunnelsByType(Tunnels.UNKNOWN.get()); - if (typed.size() != 1) - test.fail("Tunnel not found when searching by type."); - - if (!typed.contains(BlockPos.ZERO)) - test.fail("Tunnel position not found when searching by type."); - - var side = graph.getTunnelSide(BlockPos.ZERO); - if (side.isEmpty()) - test.fail("Tunnel to machine edge not found."); - - side.ifPresent(dir -> { - if (dir != Direction.NORTH) - test.fail(String.format("Tunnel connection side is not correct; expected %s but got %s.", Direction.NORTH, dir)); - }); - } catch (Exception e) { - test.fail(e.getMessage()); - } - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canRegisterMultipleTunnelSides(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - final var td = Tunnels.ITEM_TUNNEL_DEF.get(); - graph.registerTunnel(BlockPos.ZERO, td, 1, Direction.NORTH); - graph.registerTunnel(BlockPos.ZERO.above(), td, 1, Direction.SOUTH); - graph.registerTunnel(BlockPos.ZERO.below(), td, 1, Direction.WEST); - graph.registerTunnel(BlockPos.ZERO.east(), td, 1, Direction.EAST); - - final var positions = graph.getTunnelSides(td).collect(Collectors.toSet()); - if(positions.size() != 4) - test.fail("Expected four registered tunnels."); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canRegisterTunnelsComplex(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - final var td = Tunnels.ITEM_TUNNEL_DEF.get(); - graph.registerTunnel(BlockPos.ZERO, td, 1, Direction.NORTH); - graph.registerTunnel(BlockPos.ZERO.above(), td, 2, Direction.SOUTH); - graph.registerTunnel(BlockPos.ZERO.below(), td, 3, Direction.WEST); - graph.registerTunnel(BlockPos.ZERO.east(), td, 4, Direction.EAST); - - final var positions = graph.getTunnelSides(td).collect(Collectors.toSet()); - if(positions.size() != 4) - test.fail("Expected four registered tunnels."); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canFetchByMachine(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - final var td = Tunnels.ITEM_TUNNEL_DEF.get(); - graph.registerTunnel(BlockPos.ZERO, td, 1, Direction.NORTH); - graph.registerTunnel(BlockPos.ZERO.above(), td, 1, Direction.SOUTH); - graph.registerTunnel(BlockPos.ZERO.below(), td, 1, Direction.WEST); - graph.registerTunnel(BlockPos.ZERO.east(), td, 1, Direction.EAST); - - final var positions = graph.getMachineTunnels(1, td).collect(Collectors.toSet()); - if(positions.size() != 4) - test.fail("Expected four registered tunnels."); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canFetchSupportingTunnelsOnSide(final GameTestHelper test) { - var graph = new TunnelConnectionGraph(); - - graph.registerTunnel(BlockPos.ZERO, Tunnels.ITEM_TUNNEL_DEF.get(), 1, Direction.NORTH); - graph.registerTunnel(BlockPos.ZERO.above(), Tunnels.ITEM_TUNNEL_DEF.get(), 1, Direction.SOUTH); - graph.registerTunnel(BlockPos.ZERO.below(), Tunnels.UNKNOWN.get(), 1, Direction.NORTH); - - final var positions = graph.getTunnelsSupporting(1, Direction.NORTH, CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) - .collect(Collectors.toSet()); - - if (positions.isEmpty()) - test.fail("Items are supported on the item tunnel type."); - - if (positions.size() != 1) - test.fail("Should only have one position found."); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canFetchSidedTypes(final GameTestHelper test) { - var graph = new TunnelConnectionGraph(); - - // Register three tunnels - two types on the north side (different positions), one on south - graph.registerTunnel(BlockPos.ZERO, Tunnels.ITEM_TUNNEL_DEF.get(), 1, Direction.NORTH); - graph.registerTunnel(BlockPos.ZERO.above(), Tunnels.ITEM_TUNNEL_DEF.get(), 1, Direction.SOUTH); - graph.registerTunnel(BlockPos.ZERO.below(), Tunnels.UNKNOWN.get(), 1, Direction.NORTH); - - final var positions = graph.getTypesForSide(1, Direction.NORTH) - .collect(Collectors.toSet()); - - if (positions.size() != 2) - test.fail("Should have two tunnel types for the machine side."); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canSerializeData(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - - var registered = graph.registerTunnel(BlockPos.ZERO, Tunnels.UNKNOWN.get(), 1, Direction.NORTH); - if (!registered) - test.fail("Expected tunnel to be registered without error"); - - var savedData = graph.serializeNBT(); - - if (!savedData.contains("nodes")) - test.fail("Did not serialize node information."); - - var d = new TunnelConnectionGraph(); - d.deserializeNBT(savedData); - - test.succeed(); - } - - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNEL_DATA) - public static void canDeserializeTunnelData(final GameTestHelper test) { - - var graph = new TunnelConnectionGraph(); - - var data = new CompoundTag(); - - ListTag nodeList = new ListTag(); - nodeList.add(makeTunnelTag(test, BlockPos.ZERO)); - nodeList.add(makeTunnelTag(test, BlockPos.ZERO.north())); - nodeList.add(makeTunnelTag(test, BlockPos.ZERO.south())); - - data.put("nodes", nodeList); - data.put("edges", new ListTag()); - - graph.deserializeNBT(data); - - if (graph.size() != 3) - test.fail("Did not get expected number of nodes."); - - if (!graph.hasTunnel(BlockPos.ZERO)) - test.fail("Did not serialize node information."); - - test.succeed(); - } - - @Nonnull - private static CompoundTag makeTunnelTag(GameTestHelper test, BlockPos location) { - CompoundTag tunnTag = new CompoundTag(); - TunnelNode tunn = new TunnelNode(location); - var tunnNbt = TunnelNode.CODEC.encodeStart(NbtOps.INSTANCE, tunn) - .getOrThrow(false, test::fail); - - tunnTag.putUUID("id", UUID.randomUUID()); - tunnTag.put("data", tunnNbt); - return tunnTag; - } -} diff --git a/src/test/java/dev/compactmods/machines/test/tunnel/TunnelTests.java b/src/test/java/dev/compactmods/machines/test/tunnel/TunnelTests.java deleted file mode 100644 index 9765ab3b..00000000 --- a/src/test/java/dev/compactmods/machines/test/tunnel/TunnelTests.java +++ /dev/null @@ -1,201 +0,0 @@ -package dev.compactmods.machines.test.tunnel; - -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.core.*; -import dev.compactmods.machines.machine.Machines; -import dev.compactmods.machines.room.RoomSize; -import dev.compactmods.machines.room.Rooms; -import dev.compactmods.machines.room.data.CompactRoomData; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import dev.compactmods.machines.room.history.PlayerRoomHistoryItem; -import dev.compactmods.machines.test.TestBatches; -import dev.compactmods.machines.test.util.TestUtil; -import dev.compactmods.machines.tunnel.TunnelItem; -import dev.compactmods.machines.tunnel.TunnelWallBlock; -import dev.compactmods.machines.tunnel.data.RoomTunnelData; -import dev.compactmods.machines.tunnel.graph.TunnelNode; -import dev.compactmods.machines.util.CompactStructureGenerator; -import dev.compactmods.machines.util.DimensionUtil; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.gametest.framework.AfterBatch; -import net.minecraft.gametest.framework.BeforeBatch; -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.gametest.framework.GameTestHelper; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.AABB; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; -import org.apache.commons.lang3.RandomUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@GameTestHolder(CompactMachines.MOD_ID) -public class TunnelTests { - - private static AABB ROOM_BOUNDS; - private static ChunkPos TESTING_ROOM; - - private static Logger LOG = LogManager.getLogger(); - - @BeforeBatch(batch = TestBatches.TUNNELS) - public static void beforeTunnelTests(final ServerLevel level) { - final var server = level.getServer(); - - try { - var compactLevel = server.getLevel(Registration.COMPACT_DIMENSION); - if (compactLevel == null) { - CompactMachines.LOGGER.warn("Compact dimension not found; recreating it."); - DimensionUtil.createAndRegisterWorldAndDimension(server); - } - - LOG.info("Starting tunnel tests; creating a new room..."); - - TESTING_ROOM = Rooms.createNew(server, RoomSize.NORMAL, UUID.randomUUID()); - - LOG.info("New room generated at chunk: {}", TESTING_ROOM); - - ROOM_BOUNDS = CompactRoomData.get(server).getBounds(TESTING_ROOM); - } catch (MissingDimensionException | NonexistentRoomException e) { - e.printStackTrace(); - } - } - - @AfterBatch(batch = TestBatches.TUNNELS) - public static void afterTunnelTests(final ServerLevel level) { - final var server = level.getServer(); - - try { - LOG.info("Starting test room destruction."); - Stream connected = Rooms.getConnectedMachines(server, TESTING_ROOM); - connected.forEach(mach -> Machines.destroy(server, mach)); - - Rooms.destroy(server, TESTING_ROOM); - LOG.info("Finished destruction of test room."); - } catch (MissingDimensionException | NonexistentRoomException e) { - e.printStackTrace(); - } - } - - @PrefixGameTestTemplate(false) - @GameTest(template = "empty_1x1", batch = TestBatches.TUNNELS, timeoutTicks = 150) - public static void tunnel_item_transforms_wall(final GameTestHelper test) { - var serv = test.getLevel().getServer(); - var comLev = serv.getLevel(Registration.COMPACT_DIMENSION); - - final var seq = test.startSequence(); - int offset = 0; - for (var dir : Direction.values()) { - seq.thenExecuteAfter(offset, () -> { - try { - useTunnelItem(test, comLev, dir); - } catch (MissingDimensionException e) { - e.printStackTrace(); - } - }); - - offset += 5; - } - - seq.thenIdle(60) - .thenExecute(() -> checkTunnelWalls(test, comLev)) - .thenSucceed(); - } - - private static void checkTunnelWalls(GameTestHelper test, ServerLevel comLev) { - RoomTunnelData roomTunnels = null; - try { - roomTunnels = RoomTunnelData.get(comLev.getServer(), TESTING_ROOM); - } catch (MissingDimensionException e) { - test.fail(e.getMessage()); - } - - final var graph = roomTunnels.getGraph(); - - final long countedTunnels = comLev.getBlockStates(ROOM_BOUNDS.inflate(1)) - .filter(s -> s.getBlock() instanceof TunnelWallBlock) - .count(); - - final var positions = graph.getTunnelNodesByType(Tunnels.ITEM_TUNNEL_DEF.get()) - .map(TunnelNode::position) - .map(BlockPos::immutable) - .collect(Collectors.toSet()); - - if (countedTunnels != 6) - test.fail("Expected 6 tunnel walls inside the room boundaries. Got " + countedTunnels); - - if (positions.size() != 6) - test.fail("Expected a tunnel to be placed on all 6 sides. Got " + positions.size()); - - for (var tunnelLocation : positions) { - - final var machine = graph.connectedMachine(tunnelLocation).orElse(-1); - if (machine == -1) - test.fail("Tunnel not connected to a machine: " + tunnelLocation); - - var side = graph.getTunnelSide(tunnelLocation); - if (side.isEmpty()) { - test.fail("Did not find side for tunnel: " + tunnelLocation); - return; - } - - final var dir = side.get(); - - final var wallState = comLev.getBlockState(tunnelLocation); - if (!(wallState.getBlock() instanceof TunnelWallBlock)) - test.fail("Bad wall block data", tunnelLocation); - - graph.connectedMachine(tunnelLocation).ifPresentOrElse(m -> { - if (!Objects.equals(m, machine)) - test.fail("Tunnel " + tunnelLocation + " (" + dir + ") is not linked to the correct machine. Got: " + m + "; Expected: " + machine); - }, () -> test.fail("Tunnel " + tunnelLocation + " (" + dir + ") is not linked to a machine.")); - - var placed = graph.getMachineTunnels(machine, Tunnels.ITEM_TUNNEL_DEF.get()) - .collect(Collectors.toSet()); - - if (placed.size() != 1) - test.fail("Should have had one tunnel placed; got " + placed); - } - } - - private static void useTunnelItem(GameTestHelper test, ServerLevel comLev, Direction dir) throws MissingDimensionException { - // room should be generated - find a random wall position - var playerInRoom = test.makeMockPlayer(); - playerInRoom.level = comLev; - playerInRoom.setPos(ROOM_BOUNDS.getCenter()); - - ItemStack tunnelStack = new ItemStack(Tunnels.ITEM_TUNNEL.get(), 1); - TunnelItem.setTunnelType(tunnelStack, Tunnels.ITEM_TUNNEL_DEF.get()); - - playerInRoom.setItemInHand(InteractionHand.MAIN_HAND, tunnelStack); - - int machine = Machines.createNew(comLev.getServer(), test.getLevel(), new BlockPos(2, 2, 2).relative(dir, 2)); - - LOG.info("Created new machine " + machine + "; using it for side: " + dir); - - BlockPos machineBlock = new BlockPos(2, 2, 2) - .relative(dir, 2); - - playerInRoom.getCapability(Capabilities.ROOM_HISTORY).ifPresent(rooms -> { - var d = new LevelBlockPosition(Level.OVERWORLD, test.absolutePos(machineBlock)); - rooms.addHistory(new PlayerRoomHistoryItem(d, machine)); - }); - - final var wallBounds = CompactStructureGenerator.getWallBounds(RoomSize.NORMAL, new BlockPos(ROOM_BOUNDS.getCenter()), dir); - final var wallPositions = BlockPos.betweenClosedStream(wallBounds).map(BlockPos::immutable).toList(); - final var randomWall = wallPositions.get(RandomUtils.nextInt(0, wallPositions.size())); - - LOG.info("Using tunnel item on wall at {} (machine {}, chunk {})", randomWall, machine, new ChunkPos(randomWall)); - - TestUtil.useHeldItemOnBlockAt(comLev, playerInRoom, InteractionHand.MAIN_HAND, randomWall, dir.getOpposite()); - } -} diff --git a/src/test/java/dev/compactmods/machines/test/util/FileHelper.java b/src/test/java/dev/compactmods/machines/test/util/FileHelper.java index dd63eb18..ddcdec78 100644 --- a/src/test/java/dev/compactmods/machines/test/util/FileHelper.java +++ b/src/test/java/dev/compactmods/machines/test/util/FileHelper.java @@ -11,7 +11,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -public class FileHelper { +public final class FileHelper { public static final FileHelper INSTANCE = new FileHelper(); public static Path RESOURCES_DIR = Paths.get("src","test","resources"); diff --git a/src/test/java/dev/compactmods/machines/test/util/TestUtil.java b/src/test/java/dev/compactmods/machines/test/util/TestUtil.java index b198bfad..130bf3e6 100644 --- a/src/test/java/dev/compactmods/machines/test/util/TestUtil.java +++ b/src/test/java/dev/compactmods/machines/test/util/TestUtil.java @@ -14,7 +14,7 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; -public class TestUtil { +public final class TestUtil { public static void loadStructureIntoTestArea(GameTestHelper test, ResourceLocation structure, BlockPos relLocation) { final var structures = test.getLevel().getStructureManager(); diff --git a/src/test/java/dev/compactmods/machines/test/worldgen/MachineRoomGenerationTests.java b/src/test/java/dev/compactmods/machines/test/worldgen/MachineRoomGenerationTests.java deleted file mode 100644 index 2b31019a..00000000 --- a/src/test/java/dev/compactmods/machines/test/worldgen/MachineRoomGenerationTests.java +++ /dev/null @@ -1,115 +0,0 @@ -package dev.compactmods.machines.test.worldgen; - -import dev.compactmods.machines.CompactMachines; -import dev.compactmods.machines.core.MissingDimensionException; -import dev.compactmods.machines.core.Registration; -import dev.compactmods.machines.machine.CompactMachineBlockEntity; -import dev.compactmods.machines.room.RoomSize; -import dev.compactmods.machines.room.Rooms; -import dev.compactmods.machines.room.data.CompactRoomData; -import dev.compactmods.machines.room.exceptions.NonexistentRoomException; -import dev.compactmods.machines.test.TestBatches; -import dev.compactmods.machines.test.util.TestUtil; -import dev.compactmods.machines.util.CompactStructureGenerator; -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.TestFunction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.ChunkPos; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; - -import java.util.Collection; -import java.util.HashSet; -import java.util.function.Consumer; - -@PrefixGameTestTemplate(false) -@GameTestHolder(CompactMachines.MOD_ID) -public class MachineRoomGenerationTests { - - @GameTestGenerator - public static Collection generatesMachinesCorrectly() { - HashSet tests = new HashSet<>(); - - // TestFunction(String batch, String testName, String structure, int testTime, long setupTime, boolean isRequired, Consumer tester) - for (var size : RoomSize.values()) { - var template = new ResourceLocation(CompactMachines.MOD_ID, size.getSerializedName()); - Consumer test = (t) -> { - var lev = t.getLevel(); - TestUtil.loadStructureIntoTestArea(t, template, new BlockPos(0, 17, 0)); - - var centerSpawn = t.absolutePos(new BlockPos(7, 8, 7)); - CompactStructureGenerator.generateCompactStructure(lev, size, centerSpawn); - - BlockPos.betweenClosedStream(BlockPos.ZERO.above(), new BlockPos(15, 16, 15)) - .forEach(pos -> { - var compare = pos.above(16); - t.assertSameBlockState(pos, compare); - }); - - t.succeed(); - }; - - var testf = new TestFunction(TestBatches.ROOM_GENERATION, "room_" + size.getSerializedName(), - "compactmachines:empty_15x31", 50, 0, true, test); - tests.add(testf); - } - - return tests; - } - - @GameTest(template = "empty_5x5", batch = TestBatches.DIMENSION) - public static void usingPsdCreatesRoom(final GameTestHelper test) { - var player = test.makeMockPlayer(); - var serv = test.getLevel().getServer(); - - var machLoc = new BlockPos(3, 0, 3); - - test.setBlock(machLoc, Registration.MACHINE_BLOCK_NORMAL.get()); - - player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Registration.PERSONAL_SHRINKING_DEVICE.get())); - - test.startSequence() - .thenExecute(() -> TestUtil.useItemOnBlockAt(test, player, machLoc)) - .thenExecuteAfter(2, () -> { - if (test.getBlockEntity(machLoc) instanceof CompactMachineBlockEntity mach) { - - if (!mach.mapped()) - test.fail("Machine was not mapped to a room."); - - if (mach.machineId == -1) - test.fail("Machine ID not set after PSD usage."); - - var roomid = mach.getInternalChunkPos(); - if (roomid.isEmpty()) { - test.fail("Room was not registered."); - return; - } - - ChunkPos room = roomid.get(); - try { - boolean destroyed = Rooms.destroy(serv, room); - if (!destroyed) - test.fail("Room was not destroyed after test."); - - final var rooms = CompactRoomData.get(serv); - if (rooms.isRegistered(room)) - test.fail("Room was not unregistered after test."); - - } catch (MissingDimensionException | NonexistentRoomException e) { - test.fail(e.toString()); - } - - test.succeed(); - } else { - test.fail("Expected machine block to have a block entity."); - } - }); - } - - -}