From 1186043bf3e3aa504ecb1d0f0ee7e981a6263e17 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 26 Aug 2020 17:18:54 +0200 Subject: [PATCH 01/78] placement dir improvements --- .../java/thetadev/constructionwand/job/WandJob.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index eb24b95..14eb98a 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -6,6 +6,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; import net.minecraft.state.Property; import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.state.properties.SlabType; import net.minecraft.stats.Stats; import net.minecraft.util.*; import net.minecraft.util.math.AxisAlignedBB; @@ -197,7 +198,7 @@ public abstract class WandJob if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return false; // No entities in area? - AxisAlignedBB blockBB = new AxisAlignedBB(pos); + AxisAlignedBB blockBB = blockState.getCollisionShape(world, pos).getBoundingBox().offset(pos); return world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty(); } @@ -215,7 +216,7 @@ public abstract class WandJob BlockState supportingBlock = placeSnapshot.supportingBlock; - if(targetDirection && placeBlock.getBlock() == supportingBlock.getBlock()) { + if(targetDirection) { // Block properties to be copied (alignment/rotation properties) for(Property property : new Property[] { BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, @@ -225,6 +226,12 @@ public abstract class WandJob placeBlock = placeBlock.with(property, supportingBlock.get(property)); } } + + // Dont dupe double slabs + if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE)) { + SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); + if(slabType != SlabType.DOUBLE) placeBlock = placeBlock.with(BlockStateProperties.SLAB_TYPE, slabType); + } } // Place the block From d87c7f7d1ba3a7d30e10c6e44717dbe6b84b149b Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 27 Aug 2020 00:10:38 +0200 Subject: [PATCH 02/78] fix exception with no-collision blocks --- build.gradle | 4 ++-- src/main/java/thetadev/constructionwand/job/WandJob.java | 2 +- src/main/resources/META-INF/mods.toml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 2160d5d..3758453 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ apply plugin: 'net.minecraftforge.gradle' apply plugin: 'eclipse' apply plugin: 'maven-publish' -version = '1.16.2-1.2' +version = '1.16-1.2' group = 'thetadev.constructionwand' // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = 'constructionwand' @@ -92,7 +92,7 @@ repositories { } dependencies { - minecraft 'net.minecraftforge:forge:1.16.2-33.0.21' + minecraft 'net.minecraftforge:forge:1.16.1-32.0.108' //runtimeOnly fg.deobf("vazkii.patchouli:Patchouli:1.16-39") //runtimeOnly fg.deobf("top.theillusivec4.curios:curios:FORGE-1.16.1-3.0") diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 97d2c08..9b13530 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -244,7 +244,7 @@ public abstract class WandJob } // Remove block if placeEvent is canceled - BlockSnapshot snapshot = BlockSnapshot.create(world.func_234923_W_(), world, blockPos); + BlockSnapshot snapshot = BlockSnapshot.create(world, blockPos); BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, placeBlock, player); MinecraftForge.EVENT_BUS.post(placeEvent); if(placeEvent.isCanceled()) { diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index c709f6f..5782400 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,5 +1,5 @@ modLoader="javafml" -loaderVersion="[33,)" +loaderVersion="[32,)" license="MIT License" [[mods]] modId="constructionwand" @@ -20,12 +20,12 @@ This is my first minecraft mod. May the odds be ever in your favor. [[dependencies.constructionwand]] modId="forge" mandatory=true - versionRange="[33,)" + versionRange="[32,)" ordering="NONE" side="BOTH" [[dependencies.constructionwand]] modId="minecraft" mandatory=true - versionRange="[1.16.2]" + versionRange="[1.16.1]" ordering="NONE" side="BOTH" From c940aaac9fd6e189327eaf015d54041d8080e584 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 16 Sep 2020 16:25:43 +0200 Subject: [PATCH 03/78] Added block randomization, ReplacementRegistry --- build.gradle | 40 ++-- gradle.properties | 16 +- .../basics/ConfigHandler.java | 19 ++ .../basics/ReplacementRegistry.java | 63 ++++++ .../constructionwand/basics/WandUtil.java | 36 ++++ .../client/RenderBlockPreview.java | 8 +- .../constructionwand/data/ModData.java | 2 - .../constructionwand/job/AngelJob.java | 6 +- .../constructionwand/job/ConstructionJob.java | 8 +- .../constructionwand/job/JobHistory.java | 10 +- .../constructionwand/job/PlaceSnapshot.java | 14 +- .../constructionwand/job/TransductionJob.java | 7 +- .../job/WandItemUseContext.java | 5 +- .../constructionwand/job/WandJob.java | 187 +++++++++++------- .../network/PacketUndoBlocks.java | 11 +- .../data/constructionwand/tags/trowels.json | 6 + 16 files changed, 317 insertions(+), 121 deletions(-) create mode 100644 src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java create mode 100644 src/main/resources/data/constructionwand/tags/trowels.json diff --git a/build.gradle b/build.gradle index 2160d5d..70ba5b0 100644 --- a/build.gradle +++ b/build.gradle @@ -13,14 +13,14 @@ apply plugin: 'net.minecraftforge.gradle' apply plugin: 'eclipse' apply plugin: 'maven-publish' -version = '1.16.2-1.2' -group = 'thetadev.constructionwand' // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = 'constructionwand' +version = "${mcversion}-${version_major}.${version_minor}" +group = "${author}.${modid}" +archivesBaseName = "${modid}" sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. minecraft { - mappings channel: 'snapshot', version: '20200723-1.16.1' + mappings channel: 'snapshot', version: project.mcp_mappings runs { client { @@ -89,26 +89,44 @@ repositories { maven { url = "https://maven.theillusivec4.top/" } + maven { + url = "https://www.cursemaven.com" + } } dependencies { - minecraft 'net.minecraftforge:forge:1.16.2-33.0.21' - //runtimeOnly fg.deobf("vazkii.patchouli:Patchouli:1.16-39") - //runtimeOnly fg.deobf("top.theillusivec4.curios:curios:FORGE-1.16.1-3.0") + minecraft([ + group : "net.minecraftforge", + name : "forge", + version: "${project.mcversion}-${project.forgeversion}" + ]) - compileOnly fg.deobf("vazkii.botania:Botania:1.16-398:api") - //runtimeOnly fg.deobf("vazkii.botania:Botania:1.16-398") + compileOnly fg.deobf([ + group: "vazkii.botania", + name: "Botania", + version: "${project.botania}", + classifier: "api" + ]) + /* + runtimeOnly fg.deobf("vazkii.patchouli:Patchouli:1.16-41") + runtimeOnly fg.deobf("curse.maven:Curios:3042479") + runtimeOnly fg.deobf([ + group: "vazkii.botania", + name: "Botania", + version: "${project.botania}", + classifier: "" + ])*/ } jar { manifest { attributes([ "Specification-Title": archivesBaseName, - "Specification-Vendor": "thetadev", + "Specification-Vendor": "${author}", "Specification-Version": "1", // We are version 1 of ourselves "Implementation-Title": archivesBaseName, "Implementation-Version": "${version}", - "Implementation-Vendor" : "thetadev", + "Implementation-Vendor" :"${author}", "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") ]) } diff --git a/gradle.properties b/gradle.properties index 878bf1f..54cf821 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,14 @@ -# Sets default memory used for gradle commands. Can be overridden by user or command line properties. -# This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx3G -org.gradle.daemon=false \ No newline at end of file +org.gradle.daemon=false + +author=thetadev +modid=constructionwand + +mcversion=1.16.2 +forgeversion=33.0.60 +mcp_mappings=20200723-1.16.1 + +botania=1.16.2-405 + +version_major=1 +version_minor=3 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java b/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java index 91c05fa..e28a815 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java @@ -4,6 +4,7 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemTier; import net.minecraft.item.Items; import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.ModList; import java.util.Arrays; import java.util.List; @@ -30,6 +31,7 @@ public class ConfigHandler public static final ForgeConfigSpec.IntValue UNDO_HISTORY; public static final ForgeConfigSpec.BooleanValue ANGEL_FALLING; + public static final ForgeConfigSpec.EnumValue SHOVEL_AS_TROWEL; static { BUILDER.comment("Wand durability"); @@ -62,8 +64,25 @@ public class ConfigHandler UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); ANGEL_FALLING = BUILDER.define("AngelFalling", false); + BUILDER.comment("Use shovels like trowels (Holding in offhand will make wand use random blocks from your hotbar). Default: Only when Quark is not installed."); + SHOVEL_AS_TROWEL = BUILDER.defineEnum("ShovelAsTrowel", EnumShovelTrowel.NOQUARK); BUILDER.pop(); } public static final ForgeConfigSpec SPEC = BUILDER.build(); + + public enum EnumShovelTrowel { + TRUE, + FALSE, + NOQUARK; + + public boolean isEn() { + switch(this) { + case TRUE: return true; + case FALSE: return false; + case NOQUARK: return !ModList.get().isLoaded("quark"); + } + return false; + } + } } diff --git a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java new file mode 100644 index 0000000..25fd66e --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java @@ -0,0 +1,63 @@ +package thetadev.constructionwand.basics; + +import net.minecraft.block.Block; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.Items; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class ReplacementRegistry +{ + private static final HashMap> replacements = new HashMap<>(); + + static { + add("dirt", Items.DIRT); + add("dirt", Items.GRASS_BLOCK); + add("dirt", Items.COARSE_DIRT); + add("dirt", Items.PODZOL); + add("dirt", Items.MYCELIUM); + } + + private static void init() { + ArrayList configList = new ArrayList<>(); + configList.add("minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt"); + + for(String key : configList) { + String[] itemIDs = key.split(";"); + + } + } + + private static void add(String name, Item item) { + HashSet set = replacements.get(name); + if(set == null) { + set = new HashSet<>(); + set.add(item); + replacements.put(name, set); + } + else set.add(item); + } + + public static Set getMatchingSet(Item item) { + HashSet res = new HashSet<>(); + + for(HashSet set : replacements.values()) { + if(set.contains(item)) res.addAll(set); + } + res.remove(item); + return res; + } + + public static boolean matchBlocks(Block b1, Block b2) { + if(b1 == b2) return true; + + for(HashSet set : replacements.values()) { + if(set.contains(b1.asItem()) && set.contains(b2.asItem())) return true; + } + return false; + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 29a2f57..20f378f 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -4,13 +4,21 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.ShovelItem; import net.minecraft.util.Hand; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; +import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.items.ItemWand; +import java.util.ArrayList; +import java.util.List; + public class WandUtil { + public static ResourceLocation TAG_TROWELS = new ResourceLocation(ConstructionWand.MODID, "trowels"); + public static boolean stackEquals(ItemStack stackA, ItemStack stackB) { return ItemStack.areItemsEqual(stackA, stackB) && ItemStack.areItemStackTagsEqual(stackA, stackB); } @@ -30,6 +38,14 @@ public class WandUtil return null; } + public static boolean isTrowel(ItemStack stack) { + if(stack.isEmpty()) return false; + + if(stack.getItem().getTags().contains(TAG_TROWELS)) return true; + if(ConfigHandler.SHOVEL_AS_TROWEL.get().isEn() && stack.getItem() instanceof ShovelItem) return true; + return false; + } + public static BlockPos playerPos(PlayerEntity player) { return new BlockPos(player.getPositionVec()); } @@ -41,4 +57,24 @@ public class WandUtil public static Vector3d blockPosVec(BlockPos pos) { return new Vector3d(pos.getX(), pos.getY(), pos.getZ()); } + + public static List getHotbar(PlayerEntity player) { + return player.inventory.mainInventory.subList(0, 9); + } + + public static List getHotbarWithOffhand(PlayerEntity player) { + ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); + inventory.addAll(player.inventory.mainInventory.subList(0, 9)); + return inventory; + } + + public static List getMainInv(PlayerEntity player) { + return player.inventory.mainInventory.subList(9, player.inventory.mainInventory.size()); + } + + public static List getFullInv(PlayerEntity player) { + ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); + inventory.addAll(player.inventory.mainInventory); + return inventory; + } } diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index e45fb2d..7932815 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -18,12 +18,12 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.job.WandJob; -import java.util.LinkedList; +import java.util.Set; public class RenderBlockPreview { public WandJob wandJob; - public LinkedList undoBlocks; + public Set undoBlocks; @SubscribeEvent public void renderBlockHighlight(DrawHighlightEvent event) @@ -34,7 +34,7 @@ public class RenderBlockPreview Entity entity = event.getInfo().getRenderViewEntity(); if(!(entity instanceof PlayerEntity)) return; PlayerEntity player = (PlayerEntity) entity; - LinkedList blocks; + Set blocks; float colorR=0, colorG=0, colorB=0; ItemStack wand = WandUtil.holdingWand(player); @@ -59,7 +59,7 @@ public class RenderBlockPreview event.setCanceled(true); } - private void renderBlockList(LinkedList blocks, MatrixStack ms, IRenderTypeBuffer buffer, float red, float green, float blue) { + private void renderBlockList(Set blocks, MatrixStack ms, IRenderTypeBuffer buffer, float red, float green, float blue) { double renderPosX = Minecraft.getInstance().getRenderManager().info.getProjectedView().getX(); double renderPosY = Minecraft.getInstance().getRenderManager().info.getProjectedView().getY(); double renderPosZ = Minecraft.getInstance().getRenderManager().info.getProjectedView().getZ(); diff --git a/src/main/java/thetadev/constructionwand/data/ModData.java b/src/main/java/thetadev/constructionwand/data/ModData.java index e8819df..a6d94ad 100644 --- a/src/main/java/thetadev/constructionwand/data/ModData.java +++ b/src/main/java/thetadev/constructionwand/data/ModData.java @@ -1,7 +1,6 @@ package thetadev.constructionwand.data; import net.minecraft.data.DataGenerator; -import net.minecraftforge.client.model.generators.ExistingFileHelper; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; @@ -12,7 +11,6 @@ public class ModData @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); - ExistingFileHelper fileHelper = event.getExistingFileHelper(); if(event.includeServer()) { generator.addProvider(new RecipeGenerator(generator)); diff --git a/src/main/java/thetadev/constructionwand/job/AngelJob.java b/src/main/java/thetadev/constructionwand/job/AngelJob.java index 98f0e31..1de6776 100644 --- a/src/main/java/thetadev/constructionwand/job/AngelJob.java +++ b/src/main/java/thetadev/constructionwand/job/AngelJob.java @@ -1,5 +1,6 @@ package thetadev.constructionwand.job; +import net.minecraft.block.Blocks; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; @@ -33,8 +34,9 @@ public class AngelJob extends WandJob BlockPos currentPos = new BlockPos(placeVec); - if(canPlace(currentPos)) { - placeSnapshots.add(new PlaceSnapshot(currentPos, placeItem.getBlock().getDefaultState())); + PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, Blocks.AIR.getDefaultState()); + if(snapshot != null) { + placeSnapshots.add(snapshot); } } } diff --git a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java index a760288..815e9cb 100644 --- a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java +++ b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java @@ -7,7 +7,7 @@ import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; -import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.ReplacementRegistry; import thetadev.constructionwand.basics.options.EnumLock; import java.util.HashSet; @@ -43,8 +43,10 @@ public class ConstructionJob extends WandJob BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); BlockState candidateSupportingBlock = world.getBlockState(supportingPoint); - if (targetBlock.getBlock().equals(candidateSupportingBlock.getBlock()) && canPlace(currentCandidate) && allCandidates.add(currentCandidate)) { - placeSnapshots.add(new PlaceSnapshot(currentCandidate, candidateSupportingBlock)); + if(ReplacementRegistry.matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { + PlaceSnapshot snapshot = getPlaceSnapshot(currentCandidate, candidateSupportingBlock); + if(snapshot == null) continue; + placeSnapshots.add(snapshot); switch(placeDirection) { case DOWN: diff --git a/src/main/java/thetadev/constructionwand/job/JobHistory.java b/src/main/java/thetadev/constructionwand/job/JobHistory.java index 2b64eee..9fc43d9 100644 --- a/src/main/java/thetadev/constructionwand/job/JobHistory.java +++ b/src/main/java/thetadev/constructionwand/job/JobHistory.java @@ -9,9 +9,7 @@ import thetadev.constructionwand.basics.ConfigHandler; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.network.PacketUndoBlocks; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.UUID; +import java.util.*; public class JobHistory { @@ -48,13 +46,13 @@ public class JobHistory entry.undoActive = ctrlDown; LinkedList jobs = entry.jobs; - LinkedList positions; + Set positions; // Send block positions of most recent job to client - if(jobs.isEmpty()) positions = new LinkedList<>(); + if(jobs.isEmpty()) positions = Collections.emptySet(); else { WandJob job = jobs.getLast(); - if(job == null || !job.getWorld().equals(world)) positions = new LinkedList<>(); + if(job == null || !job.getWorld().equals(world)) positions = Collections.emptySet(); else positions = job.getBlockPositions(); } diff --git a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java index 590acad..de64757 100644 --- a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java @@ -1,19 +1,19 @@ package thetadev.constructionwand.job; import net.minecraft.block.BlockState; -import net.minecraft.item.Item; +import net.minecraft.item.BlockItem; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; public class PlaceSnapshot { - public BlockState block; - public BlockState supportingBlock; - public BlockPos pos; + public final BlockState block; + public final BlockPos pos; + public final BlockItem item; - public PlaceSnapshot(BlockPos pos, BlockState supportingBlock) + public PlaceSnapshot(BlockPos pos, BlockState block, BlockItem item) { this.pos = pos; - this.supportingBlock = supportingBlock; + this.block = block; + this.item = item; } } diff --git a/src/main/java/thetadev/constructionwand/job/TransductionJob.java b/src/main/java/thetadev/constructionwand/job/TransductionJob.java index 0418e16..ecf9bf4 100644 --- a/src/main/java/thetadev/constructionwand/job/TransductionJob.java +++ b/src/main/java/thetadev/constructionwand/job/TransductionJob.java @@ -7,7 +7,6 @@ import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; -import thetadev.constructionwand.items.ItemWand; public class TransductionJob extends WandJob { @@ -23,8 +22,10 @@ public class TransductionJob extends WandJob for(int i=0; i itemCounts; + private boolean randomizeItems = false; + protected LinkedList placeSnapshots; + public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { this.player = player; @@ -59,23 +64,12 @@ public abstract class WandJob doReplace = options.getOption(EnumReplace.YES) == EnumReplace.YES; targetDirection = options.getOption(EnumDirection.TARGET) == EnumDirection.TARGET; - BlockPos targetPos = rayTraceResult.getPos(); - BlockState targetState = world.getBlockState(targetPos); - Block targetBlock = targetState.getBlock(); - ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); - // Get place item - Item item; - if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) item = offhandStack.getItem(); - else { - //item = targetBlock.getBlock().getItem(world, targetPos, targetState).getItem(); - item = targetBlock.getPickBlock(targetState, rayTraceResult, world, targetPos, player).getItem(); - } - if(!(item instanceof BlockItem)) return; - placeItem = (BlockItem) item; + addBlockItems(); + if(itemCounts.isEmpty()) return; // Get inventory supply - maxBlocks = Math.min(countItems(), wandItem.getLimit(player, wand)); + maxBlocks = Math.min(itemCounts.values().stream().reduce(0, Integer::sum), wandItem.getLimit(player, wand)); if(maxBlocks == 0) return; getBlockPositionList(); @@ -88,13 +82,8 @@ public abstract class WandJob else return new ConstructionJob(player, world, rayTraceResult, itemStack); } - public LinkedList getBlockPositions() { - LinkedList res = new LinkedList<>(); - - for(PlaceSnapshot snapshot : placeSnapshots) { - res.add(snapshot.pos); - } - return res; + public Set getBlockPositions() { + return placeSnapshots.stream().map(snapshot -> snapshot.pos).collect(Collectors.toSet()); } public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; } @@ -111,24 +100,64 @@ public abstract class WandJob public ItemStack getWand() { return wand; } - protected int countItems() - { + private void addBlockItem(BlockItem item) { + int count = countItem(item); + if(count > 0) itemCounts.put(item, count); + } + + private void addBlockItems() { + itemCounts = new LinkedHashMap<>(); + BlockPos targetPos = rayTraceResult.getPos(); + BlockState targetState = world.getBlockState(targetPos); + Block targetBlock = targetState.getBlock(); + ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); + + if(!offhandStack.isEmpty()) { + Item offhandItem = offhandStack.getItem(); + // Trovel in offhand -> randomize hotbar blocks + if(WandUtil.isTrowel(offhandStack)) { + randomizeItems = true; + + for(ItemStack stack : WandUtil.getHotbar(player)) { + if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); + } + return; + } + // Block in offhand -> override + else if(offhandItem instanceof BlockItem) { + addBlockItem((BlockItem) offhandItem); + return; + } + } + + // Otherwise use target block + Item item = targetBlock.getPickBlock(targetState, rayTraceResult, world, targetPos, player).getItem(); + if(item instanceof BlockItem) { + addBlockItem((BlockItem) item); + + // Add replacement items + for(Item it : ReplacementRegistry.getMatchingSet(item)) { + if(it instanceof BlockItem) addBlockItem((BlockItem) it); + } + } + } + + private int countItem(Item item) { if(player.inventory == null || player.inventory.mainInventory == null) return 0; if(player.isCreative()) return Integer.MAX_VALUE; int total = 0; ContainerManager containerManager = ConstructionWand.instance.containerManager; - LinkedList inventory = new LinkedList<>(player.inventory.offHandInventory); - inventory.addAll(player.inventory.mainInventory); + List inventory = WandUtil.getFullInv(player); for(ItemStack stack : inventory) { if(stack == null) continue; - if(WandUtil.stackEquals(stack, placeItem)) { - total += Math.max(0, stack.getCount()); + if(WandUtil.stackEquals(stack, item)) { + total += stack.getCount(); } else { - int amount = containerManager.countItems(player, new ItemStack(placeItem), stack); + int amount = containerManager.countItems(player, new ItemStack(item), stack); if(amount == Integer.MAX_VALUE) return Integer.MAX_VALUE; total += amount; } @@ -137,38 +166,37 @@ public abstract class WandJob } // Attempts to take specified number of items, returns number of missing items - protected int takeItems(int count) + private int takeItems(Item item, int count) { if(player.inventory == null || player.inventory.mainInventory == null) return count; if(player.isCreative()) return 0; - LinkedList hotbar = new LinkedList<>(player.inventory.mainInventory.subList(0, 9)); - hotbar.addAll(player.inventory.offHandInventory); - LinkedList mainInv = new LinkedList<>(player.inventory.mainInventory.subList(9, player.inventory.mainInventory.size())); + List hotbar = WandUtil.getHotbarWithOffhand(player); + List mainInv = WandUtil.getMainInv(player); // Take items from main inv, loose items first - count = takeItemsInvList(count, mainInv, false); - count = takeItemsInvList(count, mainInv, true); + count = takeItemsInvList(count, item, mainInv, false); + count = takeItemsInvList(count, item, mainInv, true); // Take items from hotbar, containers first - count = takeItemsInvList(count, hotbar, true); - count = takeItemsInvList(count, hotbar, false); + count = takeItemsInvList(count, item, hotbar, true); + count = takeItemsInvList(count, item, hotbar, false); return count; } - private int takeItemsInvList(int count, LinkedList inv, boolean container) { + private int takeItemsInvList(int count, Item item, List inv, boolean container) { ContainerManager containerManager = ConstructionWand.instance.containerManager; for(ItemStack stack : inv) { if(count == 0) break; if(container) { - int nCount = containerManager.useItems(player, new ItemStack(placeItem), stack, count); + int nCount = containerManager.useItems(player, new ItemStack(item), stack, count); count = nCount; } - if(!container && WandUtil.stackEquals(stack, placeItem)) { + if(!container && WandUtil.stackEquals(stack, item)) { int toTake = Math.min(count, stack.getCount()); stack.shrink(toTake); count -= toTake; @@ -180,46 +208,32 @@ public abstract class WandJob protected abstract void getBlockPositionList(); - protected boolean canPlace(BlockPos pos) { + @Nullable + private BlockState getPlaceBlockstate(BlockPos pos, BlockItem item, BlockState supportingBlock) { // Is position out of world? - if(!world.isBlockPresent(pos)) return false; + if(!world.isBlockPresent(pos)) return null; // Is block at pos replaceable? - //BlockItemUseContext ctx = new WandItemUseContext(world, player, new ItemStack(placeItem), new BlockRayTraceResult(rayTraceResult.getHitVec(), rayTraceResult.getFace(), pos, false)); - BlockItemUseContext ctx = new WandItemUseContext(this, pos); - if(!ctx.canPlace()) return false; + BlockItemUseContext ctx = new WandItemUseContext(this, pos, item); + if(!ctx.canPlace()) return null; // If replace mode is off, target has to be air - if(!doReplace && !world.isAirBlock(pos)) return false; + if(!doReplace && !world.isAirBlock(pos)) return null; // Can block be placed? - BlockState blockState = placeItem.getBlock().getStateForPlacement(ctx); - if(blockState == null) return false; - blockState = Block.getValidBlockForPosition(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return false; + BlockState placeBlock = Block.getBlockFromItem(item).getStateForPlacement(ctx); + if(placeBlock == null) return null; + placeBlock = Block.getValidBlockForPosition(placeBlock, world, pos); + if(placeBlock.getBlock() == Blocks.AIR) return null; // No entities colliding? - VoxelShape shape = blockState.getCollisionShape(world, pos); - if(shape.isEmpty()) return true; + VoxelShape shape = placeBlock.getCollisionShape(world, pos); + if(shape.isEmpty()) return null; AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - return world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty(); - } + if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) return null; - private boolean placeBlock(PlaceSnapshot placeSnapshot) { - BlockPos blockPos = placeSnapshot.pos; - - //BlockItemUseContext ctx = new WandItemUseContext(world, player, new ItemStack(placeItem), new BlockRayTraceResult(rayTraceResult.getHitVec(), rayTraceResult.getFace(), blockPos, false)); - BlockItemUseContext ctx = new WandItemUseContext(this, blockPos); - if(!ctx.canPlace()) return false; - - BlockState placeBlock = Block.getBlockFromItem(placeItem).getStateForPlacement(ctx); - if(placeBlock == null) return false; - placeBlock = Block.getValidBlockForPosition(placeBlock, world, blockPos); - if(placeBlock.getBlock() == Blocks.AIR) return false; - - BlockState supportingBlock = placeSnapshot.supportingBlock; - - if(targetDirection) { + // Copy certain properties of supporting block (save the effort when running preview on client) + if(targetDirection && !world.isRemote) { // Block properties to be copied (alignment/rotation properties) for(Property property : new Property[] { BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, @@ -236,6 +250,29 @@ public abstract class WandJob if(slabType != SlabType.DOUBLE) placeBlock = placeBlock.with(BlockStateProperties.SLAB_TYPE, slabType); } } + return placeBlock; + } + + @Nullable + protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { + ArrayList items = new ArrayList<>(itemCounts.keySet()); + if(randomizeItems) Collections.shuffle(items, player.getRNG()); + + for(BlockItem item : items) { + if(itemCounts.get(item) == 0) continue; + + BlockState placeBlock = getPlaceBlockstate(pos, item, supportingBlock); + if(placeBlock == null) continue; + + itemCounts.merge(item, -1, Integer::sum); + return new PlaceSnapshot(pos, placeBlock, item); + } + return null; + } + + private boolean placeBlock(PlaceSnapshot placeSnapshot) { + BlockPos blockPos = placeSnapshot.pos; + BlockState placeBlock = placeSnapshot.block; // Place the block if(!world.setBlockState(blockPos, placeBlock)) { @@ -256,10 +293,9 @@ public abstract class WandJob world.notifyNeighborsOfStateChange(blockPos, placeBlock.getBlock()); // Update stats - player.addStat(Stats.ITEM_USED.get(placeItem)); + player.addStat(Stats.ITEM_USED.get(placeSnapshot.item)); player.addStat(ModStats.USE_WAND); - placeSnapshot.block = placeBlock; return true; } @@ -270,12 +306,13 @@ public abstract class WandJob if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; BlockPos pos = snapshot.pos; + BlockItem placeItem = snapshot.item; if(placeBlock(snapshot)) { wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); // If the item cant be taken, undo the placement - if(takeItems(1) == 0) placed.add(snapshot); + if(takeItems(placeItem, 1) == 0) placed.add(snapshot); else { ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+placeItem.toString()); world.removeBlock(pos, false); @@ -303,7 +340,7 @@ public abstract class WandJob // If placed block is still present and can be broken, break it and return item if(world.isBlockModifiable(player, snapshot.pos) && (player.isCreative() || - (currentBlock.getBlockHardness(world, snapshot.pos) > -1 && world.getTileEntity(snapshot.pos) == null && currentBlock.getBlock() == snapshot.block.getBlock()))) + (currentBlock.getBlockHardness(world, snapshot.pos) > -1 && world.getTileEntity(snapshot.pos) == null && ReplacementRegistry.matchBlocks(currentBlock.getBlock(), snapshot.block.getBlock())))) { BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, snapshot.pos, currentBlock, player); MinecraftForge.EVENT_BUS.post(breakEvent); @@ -312,7 +349,7 @@ public abstract class WandJob world.removeBlock(snapshot.pos, false); if(!player.isCreative()) { - ItemStack stack = new ItemStack(placeItem); + ItemStack stack = new ItemStack(snapshot.item); if(!player.inventory.addItemStackToInventory(stack)) { player.dropItem(stack, false); } diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index 78f8ed4..cab030e 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -5,14 +5,19 @@ import net.minecraft.util.math.BlockPos; import net.minecraftforge.fml.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; +import java.util.HashSet; import java.util.LinkedList; +import java.util.Set; import java.util.function.Supplier; public class PacketUndoBlocks { - public LinkedList undoBlocks; + public HashSet undoBlocks; - public PacketUndoBlocks(LinkedList undoBlocks) { + public PacketUndoBlocks(Set undoBlocks) { + this.undoBlocks = new HashSet<>(undoBlocks); + } + private PacketUndoBlocks(HashSet undoBlocks) { this.undoBlocks = undoBlocks; } @@ -23,7 +28,7 @@ public class PacketUndoBlocks } public static PacketUndoBlocks decode(PacketBuffer buffer) { - LinkedList undoBlocks = new LinkedList<>(); + HashSet undoBlocks = new HashSet<>(); while(buffer.isReadable()) { undoBlocks.add(buffer.readBlockPos()); diff --git a/src/main/resources/data/constructionwand/tags/trowels.json b/src/main/resources/data/constructionwand/tags/trowels.json new file mode 100644 index 0000000..6b646c8 --- /dev/null +++ b/src/main/resources/data/constructionwand/tags/trowels.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "quark:trowel" + ] +} \ No newline at end of file From fa7979f40a259d2302190fbdac129f0cb48bbb16 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 17 Sep 2020 16:17:38 +0200 Subject: [PATCH 04/78] Better config, more wand options, removed keys, added GUI --- .../constructionwand/ConstructionWand.java | 16 +++- .../constructionwand/basics/ConfigClient.java | 19 ++++ .../basics/ConfigHandler.java | 88 ----------------- .../constructionwand/basics/ConfigServer.java | 93 ++++++++++++++++++ .../basics/ReplacementRegistry.java | 50 ++++------ .../constructionwand/basics/WandUtil.java | 9 -- .../basics/options/EnumMatch.java | 35 +++++++ .../basics/options/EnumRandom.java | 34 +++++++ .../basics/options/WandOptions.java | 14 ++- .../constructionwand/client/ClientEvents.java | 79 +++++++++++++++ .../constructionwand/client/KeyEvents.java | 84 ---------------- .../constructionwand/client/ScreenWand.java | 92 ++++++++++++++++++ .../constructionwand/items/ItemWand.java | 40 +++----- .../constructionwand/items/ItemWandBasic.java | 12 ++- .../items/ItemWandInfinity.java | 11 +-- .../constructionwand/items/ModItems.java | 28 ++---- .../constructionwand/job/AngelJob.java | 6 +- .../constructionwand/job/ConstructionJob.java | 2 +- .../constructionwand/job/JobHistory.java | 6 +- .../constructionwand/job/TransductionJob.java | 3 +- .../constructionwand/job/WandJob.java | 96 +++++++++++++------ .../network/PacketWandOption.java | 48 ++++------ .../assets/constructionwand/lang/de_de.json | 14 +++ .../assets/constructionwand/lang/en_us.json | 14 +++ .../data/constructionwand/tags/trowels.json | 6 -- 25 files changed, 547 insertions(+), 352 deletions(-) create mode 100644 src/main/java/thetadev/constructionwand/basics/ConfigClient.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/ConfigHandler.java create mode 100644 src/main/java/thetadev/constructionwand/basics/ConfigServer.java create mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java create mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java create mode 100644 src/main/java/thetadev/constructionwand/client/ClientEvents.java delete mode 100644 src/main/java/thetadev/constructionwand/client/KeyEvents.java create mode 100644 src/main/java/thetadev/constructionwand/client/ScreenWand.java delete mode 100644 src/main/resources/data/constructionwand/tags/trowels.json diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index c9275c2..fae0393 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -12,9 +12,10 @@ import net.minecraftforge.fml.network.NetworkRegistry; import net.minecraftforge.fml.network.simple.SimpleChannel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import thetadev.constructionwand.basics.ConfigHandler; +import thetadev.constructionwand.basics.ConfigClient; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; -import thetadev.constructionwand.client.KeyEvents; +import thetadev.constructionwand.basics.ReplacementRegistry; import thetadev.constructionwand.client.RenderBlockPreview; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.containers.ContainerRegistrar; @@ -50,7 +51,8 @@ public class ConstructionWand MinecraftForge.EVENT_BUS.register(this); // Config setup - ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ConfigHandler.SPEC, MODID + ".toml"); + ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ConfigServer.SPEC); + ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ConfigClient.SPEC); } private void commonSetup(final FMLCommonSetupEvent event) @@ -67,6 +69,9 @@ public class ConstructionWand // Container registry ContainerRegistrar.register(); + //Replacement registry + ReplacementRegistry.init(); + // Stats ModStats.register(); } @@ -75,7 +80,10 @@ public class ConstructionWand { renderBlockPreview = new RenderBlockPreview(); MinecraftForge.EVENT_BUS.register(renderBlockPreview); - MinecraftForge.EVENT_BUS.register(new KeyEvents()); ModItems.registerModelProperties(); } + + public static ResourceLocation loc(String name) { + return new ResourceLocation(MODID, name); + } } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java new file mode 100644 index 0000000..e9dc900 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -0,0 +1,19 @@ +package thetadev.constructionwand.basics; + +import net.minecraftforge.common.ForgeConfigSpec; + +public class ConfigClient +{ + private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + + public static final ForgeConfigSpec.BooleanValue SHIFTCTRL; + + static { + BUILDER.push("keys"); + BUILDER.comment("Press SHIFT+CTRL to show wand options / scroll to change direction lock"); + SHIFTCTRL = BUILDER.define("ShiftCtrl", false); + BUILDER.pop(); + } + + public static final ForgeConfigSpec SPEC = BUILDER.build(); +} diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java b/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java deleted file mode 100644 index e28a815..0000000 --- a/src/main/java/thetadev/constructionwand/basics/ConfigHandler.java +++ /dev/null @@ -1,88 +0,0 @@ -package thetadev.constructionwand.basics; - -import net.minecraft.item.Item; -import net.minecraft.item.ItemTier; -import net.minecraft.item.Items; -import net.minecraftforge.common.ForgeConfigSpec; -import net.minecraftforge.fml.ModList; - -import java.util.Arrays; -import java.util.List; - -public class ConfigHandler -{ - private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - - // Durability - public static final ForgeConfigSpec.IntValue DURABILITY_STONE; - public static final ForgeConfigSpec.IntValue DURABILITY_IRON; - public static final ForgeConfigSpec.IntValue DURABILITY_DIAMOND; - - public static final ForgeConfigSpec.IntValue LIMIT_STONE; - public static final ForgeConfigSpec.IntValue LIMIT_IRON; - public static final ForgeConfigSpec.IntValue LIMIT_DIAMOND; - public static final ForgeConfigSpec.IntValue LIMIT_INFINITY; - public static final ForgeConfigSpec.IntValue LIMIT_CREATIVE; - - public static final ForgeConfigSpec.IntValue ANGEL_STONE; - public static final ForgeConfigSpec.IntValue ANGEL_IRON; - public static final ForgeConfigSpec.IntValue ANGEL_DIAMOND; - public static final ForgeConfigSpec.IntValue ANGEL_INFINITY; - - public static final ForgeConfigSpec.IntValue UNDO_HISTORY; - public static final ForgeConfigSpec.BooleanValue ANGEL_FALLING; - public static final ForgeConfigSpec.EnumValue SHOVEL_AS_TROWEL; - - static { - BUILDER.comment("Wand durability"); - BUILDER.push("durability"); - DURABILITY_STONE = BUILDER.defineInRange("StoneWand", ItemTier.STONE.getMaxUses(), 1, Integer.MAX_VALUE); - DURABILITY_IRON = BUILDER.defineInRange("IronWand", ItemTier.IRON.getMaxUses(), 1, Integer.MAX_VALUE); - DURABILITY_DIAMOND = BUILDER.defineInRange("DiamondWand", ItemTier.DIAMOND.getMaxUses(), 1, Integer.MAX_VALUE); - BUILDER.pop(); - - BUILDER.comment("Wand block limit"); - BUILDER.push("block_limit"); - LIMIT_STONE = BUILDER.defineInRange("StoneWand", 9, 1, Integer.MAX_VALUE); - LIMIT_IRON = BUILDER.defineInRange("IronWand", 27, 1, Integer.MAX_VALUE); - LIMIT_DIAMOND = BUILDER.defineInRange("DiamondWand", 128, 1, Integer.MAX_VALUE); - LIMIT_INFINITY = BUILDER.defineInRange("InfinityWand", 1024, 1, Integer.MAX_VALUE); - BUILDER.comment("Infinity Wand used in creative mode"); - LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); - BUILDER.pop(); - - BUILDER.comment("Max placement distance with angel mode (0 to disable angel mode)"); - BUILDER.push("angel_distance"); - ANGEL_STONE = BUILDER.defineInRange("StoneWand", 0, 0, Integer.MAX_VALUE); - ANGEL_IRON = BUILDER.defineInRange("IronWand", 1, 0, Integer.MAX_VALUE); - ANGEL_DIAMOND = BUILDER.defineInRange("DiamondWand", 4, 0, Integer.MAX_VALUE); - ANGEL_INFINITY = BUILDER.defineInRange("InfinityWand", 8, 0, Integer.MAX_VALUE); - BUILDER.pop(); - - BUILDER.push("misc"); - BUILDER.comment("Number of operations that can be undone"); - UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); - BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); - ANGEL_FALLING = BUILDER.define("AngelFalling", false); - BUILDER.comment("Use shovels like trowels (Holding in offhand will make wand use random blocks from your hotbar). Default: Only when Quark is not installed."); - SHOVEL_AS_TROWEL = BUILDER.defineEnum("ShovelAsTrowel", EnumShovelTrowel.NOQUARK); - BUILDER.pop(); - } - - public static final ForgeConfigSpec SPEC = BUILDER.build(); - - public enum EnumShovelTrowel { - TRUE, - FALSE, - NOQUARK; - - public boolean isEn() { - switch(this) { - case TRUE: return true; - case FALSE: return false; - case NOQUARK: return !ModList.get().isLoaded("quark"); - } - return false; - } - } -} diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java new file mode 100644 index 0000000..5530fd4 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -0,0 +1,93 @@ +package thetadev.constructionwand.basics; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemTier; +import net.minecraftforge.common.ForgeConfigSpec; +import thetadev.constructionwand.items.ItemWand; +import thetadev.constructionwand.items.ModItems; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class ConfigServer +{ + private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + + public static final ForgeConfigSpec.IntValue LIMIT_CREATIVE; + public static final ForgeConfigSpec.IntValue UNDO_HISTORY; + public static final ForgeConfigSpec.BooleanValue ANGEL_FALLING; + + public static final ForgeConfigSpec.ConfigValue> SIMILAR_BLOCKS; + private static final String[] SIMILAR_BLOCKS_DEFAULT = { + "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium" + }; + + private static final HashMap wandProperties = new HashMap<>(); + + public static WandProperties getWandProperties(Item wand) { + return wandProperties.getOrDefault(wand, WandProperties.DEFAULT); + } + + public static class WandProperties + { + public static final WandProperties DEFAULT = new WandProperties(null, null, null); + + private final ForgeConfigSpec.IntValue durability; + private final ForgeConfigSpec.IntValue limit; + private final ForgeConfigSpec.IntValue angel; + + private WandProperties(ForgeConfigSpec.IntValue durability, ForgeConfigSpec.IntValue limit, ForgeConfigSpec.IntValue angel) { + this.durability = durability; + this.limit = limit; + this.angel = angel; + } + + public WandProperties(ForgeConfigSpec.Builder builder, Item wand, int defDurability, int defLimit, int defAngel) { + builder.push(wand.getRegistryName().getPath()); + + if(defDurability > 0) { + builder.comment("Wand durability"); + durability = builder.defineInRange("durability", defDurability, 1, Integer.MAX_VALUE); + } + else durability = null; + builder.comment("Wand block limit"); + limit = builder.defineInRange("limit", defLimit, 1, Integer.MAX_VALUE); + builder.comment("Max placement distance with angel mode (0 to disable angel mode)"); + angel = builder.defineInRange("angel", defAngel, 0, Integer.MAX_VALUE); + builder.pop(); + + wandProperties.put(wand, this); + } + + public int getDurability() { + return durability == null ? -1 : durability.get(); + } + public int getLimit() { + return limit == null ? 0 : limit.get(); + } + public int getAngel() { + return angel == null ? 0 : angel.get(); + } + } + + static { + new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0); + new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 1); + new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 4); + new WandProperties(BUILDER, ModItems.WAND_INFINITY, 0, 1024, 8); + + BUILDER.push("misc"); + BUILDER.comment("Block limit for Infinity Wand used in creative mode"); + LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); + BUILDER.comment("Number of operations that can be undone"); + UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); + BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); + ANGEL_FALLING = BUILDER.define("AngelFalling", false); + BUILDER.comment("Blocks to treat equally when in Similar mode. Enter block IDs seperated by ;"); + SIMILAR_BLOCKS = BUILDER.defineList("SimilarBlocks", Arrays.asList(SIMILAR_BLOCKS_DEFAULT), obj -> true); + BUILDER.pop(); + } + + public static final ForgeConfigSpec SPEC = BUILDER.build(); +} diff --git a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java index 25fd66e..ebc6d06 100644 --- a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java +++ b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java @@ -1,51 +1,39 @@ package thetadev.constructionwand.basics; import net.minecraft.block.Block; -import net.minecraft.item.BlockItem; import net.minecraft.item.Item; -import net.minecraft.item.Items; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.ForgeRegistries; +import thetadev.constructionwand.ConstructionWand; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.Set; public class ReplacementRegistry { - private static final HashMap> replacements = new HashMap<>(); + private static final HashSet> replacements = new HashSet<>(); - static { - add("dirt", Items.DIRT); - add("dirt", Items.GRASS_BLOCK); - add("dirt", Items.COARSE_DIRT); - add("dirt", Items.PODZOL); - add("dirt", Items.MYCELIUM); - } + public static void init() { + for(Object key : ConfigServer.SIMILAR_BLOCKS.get()) { + if(!(key instanceof String)) continue; + HashSet set = new HashSet<>(); - private static void init() { - ArrayList configList = new ArrayList<>(); - configList.add("minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt"); - - for(String key : configList) { - String[] itemIDs = key.split(";"); - + for(String id : ((String)key).split(";")) { + Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(id)); + if(item == null) { + ConstructionWand.LOGGER.warn("Replacement Registry: Could not find item "+id); + continue; + } + set.add(item); + } + if(!set.isEmpty()) replacements.add(set); } } - private static void add(String name, Item item) { - HashSet set = replacements.get(name); - if(set == null) { - set = new HashSet<>(); - set.add(item); - replacements.put(name, set); - } - else set.add(item); - } - public static Set getMatchingSet(Item item) { HashSet res = new HashSet<>(); - for(HashSet set : replacements.values()) { + for(HashSet set : replacements) { if(set.contains(item)) res.addAll(set); } res.remove(item); @@ -55,7 +43,7 @@ public class ReplacementRegistry public static boolean matchBlocks(Block b1, Block b2) { if(b1 == b2) return true; - for(HashSet set : replacements.values()) { + for(HashSet set : replacements) { if(set.contains(b1.asItem()) && set.contains(b2.asItem())) return true; } return false; diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 20f378f..1b824fe 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -4,7 +4,6 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.ShovelItem; import net.minecraft.util.Hand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; @@ -38,14 +37,6 @@ public class WandUtil return null; } - public static boolean isTrowel(ItemStack stack) { - if(stack.isEmpty()) return false; - - if(stack.getItem().getTags().contains(TAG_TROWELS)) return true; - if(ConfigHandler.SHOVEL_AS_TROWEL.get().isEn() && stack.getItem() instanceof ShovelItem) return true; - return false; - } - public static BlockPos playerPos(PlayerEntity player) { return new BlockPos(player.getPositionVec()); } diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java b/src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java new file mode 100644 index 0000000..31e5d01 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java @@ -0,0 +1,35 @@ +package thetadev.constructionwand.basics.options; + +import com.google.common.base.Enums; + +public enum EnumMatch implements IEnumOption +{ + EXACT, + SIMILAR, + ANY; + + private static EnumMatch[] vals = values(); + + public IEnumOption fromName(String name) { + return Enums.getIfPresent(EnumMatch.class, name.toUpperCase()).or(this); + } + + public EnumMatch next(boolean dir) { + int i = this.ordinal() + (dir ? 1:-1); + if(i < 0) i += vals.length; + + return vals[i % vals.length]; + } + + public int getOrdinal() { + return ordinal(); + } + + public String getOptionKey() { + return "match"; + } + + public String getValue() { + return this.name().toLowerCase(); + } +} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java b/src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java new file mode 100644 index 0000000..e19ceed --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java @@ -0,0 +1,34 @@ +package thetadev.constructionwand.basics.options; + +import com.google.common.base.Enums; + +public enum EnumRandom implements IEnumOption +{ + YES, + NO; + + private static EnumRandom[] vals = values(); + + public IEnumOption fromName(String name) { + return Enums.getIfPresent(EnumRandom.class, name.toUpperCase()).or(this); + } + + public EnumRandom next(boolean dir) { + int i = this.ordinal() + (dir ? 1:-1); + if(i < 0) i += vals.length; + + return vals[i % vals.length]; + } + + public int getOrdinal() { + return ordinal(); + } + + public String getOptionKey() { + return "random"; + } + + public String getValue() { + return this.name().toLowerCase(); + } +} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java index 2001eac..5bfbe75 100644 --- a/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java @@ -2,6 +2,7 @@ package thetadev.constructionwand.basics.options; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.items.ItemWand; public class WandOptions @@ -15,7 +16,9 @@ public class WandOptions EnumMode.DEFAULT, EnumLock.NOLOCK, EnumDirection.TARGET, - EnumReplace.YES + EnumReplace.YES, + EnumMatch.SIMILAR, + EnumRandom.NO }; public WandOptions(ItemStack stack) { @@ -33,7 +36,7 @@ public class WandOptions public IEnumOption nextOption(IEnumOption option, boolean dir) { IEnumOption nextOption = getOption(option).next(dir); - if(nextOption == EnumMode.ANGEL && item.angelDistance == 0) nextOption = EnumMode.DEFAULT; + if(nextOption == EnumMode.ANGEL && ConfigServer.getWandProperties(item).getAngel() == 0) nextOption = EnumMode.DEFAULT; setOption(nextOption); return nextOption; } @@ -41,4 +44,11 @@ public class WandOptions public IEnumOption nextOption(IEnumOption option) { return nextOption(option, true); } + + public static IEnumOption fromKey(String key) { + for(IEnumOption option : options) { + if(option.getOptionKey().equals(key)) return option; + } + return EnumMode.DEFAULT; + } } diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java new file mode 100644 index 0000000..90246e9 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -0,0 +1,79 @@ +package thetadev.constructionwand.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.InputEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.*; +import thetadev.constructionwand.basics.options.*; +import thetadev.constructionwand.items.ItemWand; +import thetadev.constructionwand.network.PacketQueryUndo; +import thetadev.constructionwand.network.PacketWandOption; + +@Mod.EventBusSubscriber(value = Dist.CLIENT) +public class ClientEvents +{ + private static boolean ctrlPressed = false; + + @SubscribeEvent + public static void KeyEvent(InputEvent.KeyInputEvent event) { + PlayerEntity player = Minecraft.getInstance().player; + if(player == null) return; + if(WandUtil.holdingWand(player) == null) return; + + boolean ctrlState = Screen.hasControlDown(); + if(ctrlPressed != ctrlState) { + ctrlPressed = ctrlState; + PacketQueryUndo packet = new PacketQueryUndo(ctrlPressed); + ConstructionWand.instance.HANDLER.sendToServer(packet); + //ConstructionWand.LOGGER.debug("CTRL key update: "+ctrlPressed); + } + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public static void MouseScrollEvent(InputEvent.MouseScrollEvent event) { + PlayerEntity player = Minecraft.getInstance().player; + double scroll = event.getScrollDelta(); + + if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL.get()) || scroll == 0) return; + + ItemStack wand = WandUtil.holdingWand(player); + if(wand == null) return; + + WandOptions wandOptions = new WandOptions(wand); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.nextOption(EnumLock.NOLOCK, scroll<0), true)); + event.setCanceled(true); + } + + @SubscribeEvent + public static void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { + PlayerEntity player = event.getPlayer(); + + if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL.get())) return; + + ItemStack wand = event.getItemStack(); + if(!(wand.getItem() instanceof ItemWand)) return; + + WandOptions wandOptions = new WandOptions(wand); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.nextOption(EnumMode.DEFAULT), true)); + } + + @SubscribeEvent + public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) { + PlayerEntity player = event.getPlayer(); + if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL.get())) return; + + ItemStack wand = event.getItemStack(); + if(!(wand.getItem() instanceof ItemWand)) return; + + Minecraft.getInstance().displayGuiScreen(new ScreenWand(wand)); + event.setCanceled(true); + } +} diff --git a/src/main/java/thetadev/constructionwand/client/KeyEvents.java b/src/main/java/thetadev/constructionwand/client/KeyEvents.java deleted file mode 100644 index b8754fa..0000000 --- a/src/main/java/thetadev/constructionwand/client/KeyEvents.java +++ /dev/null @@ -1,84 +0,0 @@ -package thetadev.constructionwand.client; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.settings.KeyBinding; -import net.minecraft.client.util.InputMappings; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraftforge.client.event.InputEvent; -import net.minecraftforge.client.event.InputUpdateEvent; -import net.minecraftforge.client.settings.KeyConflictContext; -import net.minecraftforge.client.settings.KeyModifier; -import net.minecraftforge.event.entity.player.PlayerInteractEvent; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.client.registry.ClientRegistry; -import org.lwjgl.glfw.GLFW; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.*; -import thetadev.constructionwand.basics.options.EnumDirection; -import thetadev.constructionwand.basics.options.EnumReplace; -import thetadev.constructionwand.basics.options.EnumLock; -import thetadev.constructionwand.basics.options.IEnumOption; -import thetadev.constructionwand.network.PacketQueryUndo; -import thetadev.constructionwand.network.PacketWandOption; - - -public class KeyEvents -{ - private final String langPrefix = ConstructionWand.MODID + ".key."; - private final String langCategory = langPrefix + "category"; - - public final KeyBinding[] keys = { - new KeyBinding(langPrefix+"direction", KeyConflictContext.IN_GAME, InputMappings.getInputByCode(GLFW.GLFW_KEY_N, 0), langCategory), - new KeyBinding(langPrefix+"replace", KeyConflictContext.IN_GAME, KeyModifier.SHIFT, InputMappings.getInputByCode(GLFW.GLFW_KEY_N, 0), langCategory) - }; - - public static final IEnumOption[] keyOptions = { - EnumDirection.TARGET, - EnumReplace.YES - }; - - private boolean ctrlPressed; - - public KeyEvents() { - for(KeyBinding key : keys) ClientRegistry.registerKeyBinding(key); - ctrlPressed = false; - } - - @SubscribeEvent - public void KeyEvent(InputEvent.KeyInputEvent e) { - PlayerEntity player = Minecraft.getInstance().player; - if(player == null) return; - if(WandUtil.holdingWand(player) == null) return; - - for(int i=0; i 0); + createButton(0, 1, EnumLock.NOLOCK, true); + createButton(0, 2, EnumDirection.TARGET, true); + createButton(1, 0, EnumReplace.YES, true); + createButton(1, 1, EnumMatch.SIMILAR, true); + createButton(1, 2, EnumRandom.NO, true); + } + + @Override + public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { + renderBackground(matrixStack); + super.render(matrixStack, mouseX, mouseY, partialTicks); + drawCenteredString(matrixStack, font, wand.getDisplayName(), width/2, height/2 - FIELD_HEIGHT/2 - SPACING_HEIGHT, 16777215); + } + + @Override + public boolean charTyped(char character, int code) { + if(character == 'e') closeScreen(); + return super.charTyped(character, code); + } + + private void createButton(int cx, int cy, IEnumOption option, boolean en) { + Button button = new Button(getX(cx), getY(cy), BUTTON_WIDTH, BUTTON_HEIGHT, getButtonLabel(option), bt -> clickButton(bt, option), (bt, ms, x, y) -> drawTooltip(ms, x, y, option)); + button.active = en; + addButton(button); + } + + private void clickButton(Button button, IEnumOption option) { + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.nextOption(option), false)); + button.setMessage(getButtonLabel(option)); + } + + private void drawTooltip(MatrixStack matrixStack, int mouseX, int mouseY, IEnumOption option) { + if(isMouseOver(mouseX, mouseY)) { + renderTooltip(matrixStack, new TranslationTextComponent(LANG_PREFIX + wandOptions.getOption(option).getTranslationKey() + ".desc"), mouseX, mouseY); + } + } + + private int getX(int n) { + return width/2 - FIELD_WIDTH/2 + n*(BUTTON_WIDTH+SPACING_WIDTH); + } + + private int getY(int n) { + return height/2 - FIELD_HEIGHT/2 + n*(BUTTON_HEIGHT+SPACING_HEIGHT); + } + + private ITextComponent getButtonLabel(IEnumOption option) { + IEnumOption opt = wandOptions.getOption(option); + return new TranslationTextComponent(LANG_PREFIX+opt.getOptionKey()).append(new TranslationTextComponent(LANG_PREFIX+opt.getTranslationKey())); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/ItemWand.java b/src/main/java/thetadev/constructionwand/items/ItemWand.java index e0c86db..08c7d6f 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWand.java @@ -9,11 +9,9 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUseContext; -import net.minecraft.item.crafting.Ingredient; import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResultType; import net.minecraft.util.Hand; -import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; @@ -23,26 +21,20 @@ import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.*; -import thetadev.constructionwand.basics.options.EnumLock; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.options.EnumMode; import thetadev.constructionwand.basics.options.IEnumOption; import thetadev.constructionwand.basics.options.WandOptions; import thetadev.constructionwand.job.AngelJob; -import thetadev.constructionwand.job.JobHistory; import thetadev.constructionwand.job.WandJob; import java.util.List; public abstract class ItemWand extends Item { - public final int maxBlocks; - public final int angelDistance; - - public ItemWand(Item.Properties properties, int maxBlocks, int angelDistance) { + public ItemWand(String name, Item.Properties properties) { super(properties.group(ItemGroup.TOOLS)); - this.maxBlocks = maxBlocks; - this.angelDistance = angelDistance; + setRegistryName(ConstructionWand.loc(name)); } @Override @@ -72,27 +64,15 @@ public abstract class ItemWand extends Item public ActionResult onItemRightClick(World world, PlayerEntity player, Hand hand) { ItemStack stack = player.getHeldItem(hand); - if(world.isRemote) return ActionResult.resultFail(stack); + if(!player.isSneaking()) { + if(world.isRemote) return ActionResult.resultFail(stack); - if(player.isSneaking()) { - // SHIFT + Right click: Change wand mode - WandOptions options = new WandOptions(stack); - IEnumOption opt = EnumMode.DEFAULT; - opt = options.nextOption(opt); - - //ConstructionWand.LOGGER.debug("Wand mode: " + options.getOption(EnumLock.NOLOCK)); - - optionMessage(player, opt); - - player.inventory.markDirty(); - return ActionResult.resultSuccess(stack); - } - else { // Right click: Place angel block //ConstructionWand.LOGGER.debug("Place angel block"); WandJob job = new AngelJob(player, world, stack); return job.doIt() ? ActionResult.resultSuccess(stack) : ActionResult.resultFail(stack); } + return ActionResult.resultFail(stack); } @Override @@ -106,7 +86,11 @@ public abstract class ItemWand extends Item } public int getLimit(PlayerEntity player, ItemStack stack) { - return maxBlocks; + return getLimit(); + } + + protected int getLimit() { + return ConfigServer.getWandProperties(this).getLimit(); } public static int getWandMode(ItemStack stack) { @@ -132,7 +116,7 @@ public abstract class ItemWand extends Item } else { IEnumOption opt = WandOptions.options[0]; - lines.add(new TranslationTextComponent(langTooltip + "blocks", wand.maxBlocks).mergeStyle(TextFormatting.GRAY)); + lines.add(new TranslationTextComponent(langTooltip + "blocks", getLimit()).mergeStyle(TextFormatting.GRAY)); lines.add(new TranslationTextComponent(langPrefix+opt.getOptionKey()).mergeStyle(TextFormatting.AQUA) .append(new TranslationTextComponent(langPrefix+options.getOption(opt).getTranslationKey()).mergeStyle(TextFormatting.WHITE))); lines.add(new TranslationTextComponent(langTooltip + "shift").mergeStyle(TextFormatting.AQUA)); diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java index f92652b..3186834 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java @@ -8,19 +8,25 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.Ingredient; import net.minecraft.tags.ItemTags; import net.minecraft.util.ResourceLocation; +import thetadev.constructionwand.basics.ConfigServer; public class ItemWandBasic extends ItemWand { private final IItemTier tier; - public ItemWandBasic(IItemTier tier, int durability, int maxBlocks, int angelDistance) { - super(new Properties().maxDamage(durability), maxBlocks, angelDistance); + public ItemWandBasic(String name, IItemTier tier) { + super(name, new Properties().maxDamage(tier.getMaxUses())); this.tier = tier; } + @Override + public int getMaxDamage(ItemStack stack) { + return ConfigServer.getWandProperties(this).getDurability(); + } + @Override public int getLimit(PlayerEntity player, ItemStack stack) { - return Math.min(stack.getMaxDamage() - stack.getDamage(), maxBlocks); + return Math.min(stack.getMaxDamage() - stack.getDamage(), getLimit()); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java index 5686a9c..ae9e2aa 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java @@ -3,20 +3,17 @@ package thetadev.constructionwand.items; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.Ingredient; -import net.minecraft.util.ResourceLocation; -import thetadev.constructionwand.basics.ConfigHandler; +import thetadev.constructionwand.basics.ConfigServer; public class ItemWandInfinity extends ItemWand { - public ItemWandInfinity(int maxBlocks, int angelDistance) + public ItemWandInfinity(String name) { - //func_234689_a_(): Dont burn like Netherite - super(new Item.Properties().maxStackSize(1).isBurnable(), maxBlocks, angelDistance); + super(name, new Properties().maxStackSize(1).isBurnable()); } @Override public int getLimit(PlayerEntity player, ItemStack stack) { - return player.isCreative() ? ConfigHandler.LIMIT_CREATIVE.get() : maxBlocks; + return player.isCreative() ? ConfigServer.LIMIT_CREATIVE.get() : getLimit(); } } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index bbd7133..f043b7a 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -8,37 +8,23 @@ import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.registries.IForgeRegistryEntry; -import thetadev.constructionwand.basics.ConfigHandler; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.ConstructionWand; @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModItems { - public static final Item WAND_STONE = new ItemWandBasic(ItemTier.STONE, ConfigHandler.DURABILITY_STONE.get(), ConfigHandler.LIMIT_STONE.get(), ConfigHandler.ANGEL_STONE.get()); - public static final Item WAND_IRON = new ItemWandBasic(ItemTier.IRON, ConfigHandler.DURABILITY_IRON.get(), ConfigHandler.LIMIT_IRON.get(), ConfigHandler.ANGEL_IRON.get()); - public static final Item WAND_DIAMOND = new ItemWandBasic(ItemTier.DIAMOND, ConfigHandler.DURABILITY_DIAMOND.get(), ConfigHandler.LIMIT_DIAMOND.get(), ConfigHandler.ANGEL_DIAMOND.get()); - public static final Item WAND_INFINITY = new ItemWandInfinity(ConfigHandler.LIMIT_INFINITY.get(), ConfigHandler.ANGEL_INFINITY.get()); + public static final Item WAND_STONE = new ItemWandBasic("stone_wand", ItemTier.STONE); + public static final Item WAND_IRON = new ItemWandBasic("iron_wand", ItemTier.IRON); + public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", ItemTier.DIAMOND); + public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand"); - public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; + public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; @SubscribeEvent public static void onRegisterItems(RegistryEvent.Register event) { - event.getRegistry().registerAll( - register(WAND_STONE, "stone_wand"), - register(WAND_IRON, "iron_wand"), - register(WAND_DIAMOND, "diamond_wand"), - register(WAND_INFINITY, "infinity_wand") - ); - } - - public static > T register(final T entry, final String name) { - return register(entry, new ResourceLocation(ConstructionWand.MODID, name)); - } - - public static > T register(final T entry, final ResourceLocation registryName) { - entry.setRegistryName(registryName); - return entry; + event.getRegistry().registerAll(WANDS); } public static void registerModelProperties() { diff --git a/src/main/java/thetadev/constructionwand/job/AngelJob.java b/src/main/java/thetadev/constructionwand/job/AngelJob.java index 1de6776..2547dea 100644 --- a/src/main/java/thetadev/constructionwand/job/AngelJob.java +++ b/src/main/java/thetadev/constructionwand/job/AngelJob.java @@ -8,7 +8,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; -import thetadev.constructionwand.basics.ConfigHandler; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.options.EnumMode; @@ -24,9 +24,9 @@ public class AngelJob extends WandJob @Override protected void getBlockPositionList() { - if(options.getOption(EnumMode.DEFAULT) != EnumMode.ANGEL || wandItem.angelDistance == 0) return; + if(options.getOption(EnumMode.DEFAULT) != EnumMode.ANGEL || ConfigServer.getWandProperties(wandItem).getAngel() == 0) return; - if(!player.isCreative() && !ConfigHandler.ANGEL_FALLING.get() && player.fallDistance > 10) return; + if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return; Vector3d playerVec = WandUtil.entityPositionVec(player); Vector3d lookVec = player.getLookVec().mul(2, 2, 2); diff --git a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java index 815e9cb..aab3705 100644 --- a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java +++ b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java @@ -43,7 +43,7 @@ public class ConstructionJob extends WandJob BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); BlockState candidateSupportingBlock = world.getBlockState(supportingPoint); - if(ReplacementRegistry.matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { + if(matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { PlaceSnapshot snapshot = getPlaceSnapshot(currentCandidate, candidateSupportingBlock); if(snapshot == null) continue; placeSnapshots.add(snapshot); diff --git a/src/main/java/thetadev/constructionwand/job/JobHistory.java b/src/main/java/thetadev/constructionwand/job/JobHistory.java index 9fc43d9..61b5650 100644 --- a/src/main/java/thetadev/constructionwand/job/JobHistory.java +++ b/src/main/java/thetadev/constructionwand/job/JobHistory.java @@ -5,8 +5,8 @@ import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.network.PacketDistributor; -import thetadev.constructionwand.basics.ConfigHandler; import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.network.PacketUndoBlocks; import java.util.*; @@ -30,7 +30,7 @@ public class JobHistory public void add(WandJob job) { LinkedList list = getJobsFromPlayer(job.getPlayer()); list.add(job); - while(list.size() > ConfigHandler.UNDO_HISTORY.get()) list.removeFirst(); + while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); } public void removePlayer(PlayerEntity player) { @@ -75,7 +75,7 @@ public class JobHistory WandJob job = jobs.getLast(); if(job.getWorld().equals(world) && job.getBlockPositions().contains(pos)) { - // Update job player entity, they could have changed by rejoin/respawn + // Update job player entity, it could have changed by rejoin/respawn job.setPlayer(player); // Remove undo job, sent update to client and return it diff --git a/src/main/java/thetadev/constructionwand/job/TransductionJob.java b/src/main/java/thetadev/constructionwand/job/TransductionJob.java index ecf9bf4..10b9d90 100644 --- a/src/main/java/thetadev/constructionwand/job/TransductionJob.java +++ b/src/main/java/thetadev/constructionwand/job/TransductionJob.java @@ -7,6 +7,7 @@ import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; +import thetadev.constructionwand.basics.ConfigServer; public class TransductionJob extends WandJob { @@ -20,7 +21,7 @@ public class TransductionJob extends WandJob BlockPos currentPos = rayTraceResult.getPos(); BlockState supportingBlock = world.getBlockState(currentPos); - for(int i=0; i itemCounts; - private boolean randomizeItems = false; + protected HashMap itemWeights; protected LinkedList placeSnapshots; @@ -63,13 +66,25 @@ public abstract class WandJob options = new WandOptions(wand); doReplace = options.getOption(EnumReplace.YES) == EnumReplace.YES; targetDirection = options.getOption(EnumDirection.TARGET) == EnumDirection.TARGET; + randomMode = options.getOption(EnumRandom.NO) == EnumRandom.YES; + matchMode = (EnumMatch) options.getOption(EnumMatch.SIMILAR); // Get place item addBlockItems(); if(itemCounts.isEmpty()) return; // Get inventory supply - maxBlocks = Math.min(itemCounts.values().stream().reduce(0, Integer::sum), wandItem.getLimit(player, wand)); + for(int v : itemCounts.values()) { + try { + maxBlocks = Math.addExact(maxBlocks, v); + } + catch(ArithmeticException e) { + maxBlocks = Integer.MAX_VALUE; + break; + } + } + + maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand)); if(maxBlocks == 0) return; getBlockPositionList(); @@ -107,38 +122,41 @@ public abstract class WandJob private void addBlockItems() { itemCounts = new LinkedHashMap<>(); + itemWeights = new HashMap<>(); + BlockPos targetPos = rayTraceResult.getPos(); BlockState targetState = world.getBlockState(targetPos); Block targetBlock = targetState.getBlock(); ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); - if(!offhandStack.isEmpty()) { - Item offhandItem = offhandStack.getItem(); - // Trovel in offhand -> randomize hotbar blocks - if(WandUtil.isTrowel(offhandStack)) { - randomizeItems = true; - - for(ItemStack stack : WandUtil.getHotbar(player)) { - if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); + if(randomMode) { + for(ItemStack stack : WandUtil.getHotbar(player)) { + if(stack.getItem() instanceof BlockItem) { + BlockItem item = (BlockItem) stack.getItem(); + addBlockItem(item); + itemWeights.compute(item, (k, v) -> (v == null) ? 1 : v+1); } - return; } + } + else if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { // Block in offhand -> override - else if(offhandItem instanceof BlockItem) { - addBlockItem((BlockItem) offhandItem); - return; - } + addBlockItem((BlockItem) offhandStack.getItem()); } // Otherwise use target block - Item item = targetBlock.getPickBlock(targetState, rayTraceResult, world, targetPos, player).getItem(); - if(item instanceof BlockItem) { - addBlockItem((BlockItem) item); + if(itemCounts.isEmpty()) { + Item item = targetBlock.getPickBlock(targetState, rayTraceResult, world, targetPos, player).getItem(); + if(item instanceof BlockItem) { + addBlockItem((BlockItem) item); - // Add replacement items - for(Item it : ReplacementRegistry.getMatchingSet(item)) { - if(it instanceof BlockItem) addBlockItem((BlockItem) it); + // Add replacement items + if(matchMode != EnumMatch.EXACT) { + for(Item it : ReplacementRegistry.getMatchingSet(item)) { + if(it instanceof BlockItem) addBlockItem((BlockItem) it); + } + } } + randomMode = false; } } @@ -224,13 +242,14 @@ public abstract class WandJob BlockState placeBlock = Block.getBlockFromItem(item).getStateForPlacement(ctx); if(placeBlock == null) return null; placeBlock = Block.getValidBlockForPosition(placeBlock, world, pos); - if(placeBlock.getBlock() == Blocks.AIR) return null; + if(placeBlock.getBlock() == Blocks.AIR || !placeBlock.isValidPosition(world, pos)) return null; // No entities colliding? VoxelShape shape = placeBlock.getCollisionShape(world, pos); - if(shape.isEmpty()) return null; - AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) return null; + if(!shape.isEmpty()) { + AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); + if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) return null; + } // Copy certain properties of supporting block (save the effort when running preview on client) if(targetDirection && !world.isRemote) { @@ -239,13 +258,13 @@ public abstract class WandJob BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) { - if(supportingBlock.hasProperty(property)) { + if(supportingBlock.hasProperty(property) && placeBlock.hasProperty(property)) { placeBlock = placeBlock.with(property, supportingBlock.get(property)); } } // Dont dupe double slabs - if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE)) { + if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && placeBlock.hasProperty(BlockStateProperties.SLAB_TYPE)) { SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); if(slabType != SlabType.DOUBLE) placeBlock = placeBlock.with(BlockStateProperties.SLAB_TYPE, slabType); } @@ -256,15 +275,23 @@ public abstract class WandJob @Nullable protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { ArrayList items = new ArrayList<>(itemCounts.keySet()); - if(randomizeItems) Collections.shuffle(items, player.getRNG()); + if(randomMode) { + for(BlockItem item : itemWeights.keySet()) { + int weight = itemWeights.get(item); + for(int i=0; i placed = new LinkedList<>(); diff --git a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java index c4b4f29..5006b95 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java +++ b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java @@ -3,43 +3,37 @@ package thetadev.constructionwand.network; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.network.PacketBuffer; -import net.minecraft.util.text.StringTextComponent; -import net.minecraft.util.text.TextFormatting; -import net.minecraft.util.text.TranslationTextComponent; import net.minecraftforge.fml.network.NetworkEvent; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.*; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.options.IEnumOption; import thetadev.constructionwand.basics.options.WandOptions; import thetadev.constructionwand.items.ItemWand; -import java.util.Arrays; import java.util.function.Supplier; public class PacketWandOption { - public byte[] options; + public final IEnumOption option; + public final boolean notify; - public PacketWandOption(byte[] options) { - this.options = options; - } - - public PacketWandOption(IEnumOption option, boolean dir) { - options = new byte[WandOptions.options.length]; - - for(int i=0; i1); - ItemWand.optionMessage(player, opt); - } + if(msg.notify) ItemWand.optionMessage(player, msg.option); player.inventory.markDirty(); - - //ConstructionWand.LOGGER.debug("Keys: "+ Arrays.toString(msg.options)); } } } diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index e2f8adc..36b71bf 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -41,5 +41,19 @@ "constructionwand.option.replace.no": "§cNein", "constructionwand.option.replace.no.desc": "Ersetzt keine Blöcke", + "constructionwand.option.match": "Vergleich: ", + "constructionwand.option.match.exact": "§aExakt", + "constructionwand.option.match.exact.desc": "Erweitert nur Blöcke, die gleich dem Startblock sind", + "constructionwand.option.match.similar": "§6Ähnlich", + "constructionwand.option.match.similar.desc": "Behandle ähnliche Blöcke (Erde/Gras) gleich", + "constructionwand.option.match.any": "§cAlle", + "constructionwand.option.match.any.desc": "Erweitert alle Blöcke", + + "constructionwand.option.random": "Zufall: ", + "constructionwand.option.random.yes": "§aJa", + "constructionwand.option.random.yes.desc": "Platziere zufällige Blöcke aus der Hotbar", + "constructionwand.option.random.no": "§cNein", + "constructionwand.option.random.no.desc": "Platziere Blöcke nicht zufällig", + "stat.constructionwand.use_wand": "Blöcke mithilfe des Stabs platziert" } \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 6c2d948..6a000f5 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -41,5 +41,19 @@ "constructionwand.option.replace.no": "§cNo", "constructionwand.option.replace.no.desc": "Don't replace blocks", + "constructionwand.option.match": "Matching: ", + "constructionwand.option.match.exact": "§aExact", + "constructionwand.option.match.exact.desc": "Only extend blocks that are exactly the same", + "constructionwand.option.match.similar": "§6Similar", + "constructionwand.option.match.similar.desc": "Treat similar blocks (dirt/grass types) equally", + "constructionwand.option.match.any": "§cAny", + "constructionwand.option.match.any.desc": "Extend any block", + + "constructionwand.option.random": "Random: ", + "constructionwand.option.random.yes": "§aYes", + "constructionwand.option.random.yes.desc": "Place random blocks present in your hotbar", + "constructionwand.option.random.no": "§cNo", + "constructionwand.option.random.no.desc": "Don't randomize placed blocks", + "stat.constructionwand.use_wand": "Blocks placed using Wand" } \ No newline at end of file diff --git a/src/main/resources/data/constructionwand/tags/trowels.json b/src/main/resources/data/constructionwand/tags/trowels.json deleted file mode 100644 index 6b646c8..0000000 --- a/src/main/resources/data/constructionwand/tags/trowels.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "replace": false, - "values": [ - "quark:trowel" - ] -} \ No newline at end of file From 5ba260d5445fbf7e1b8291d5c5ed2635a0a0db1f Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 17 Sep 2020 22:00:51 +0200 Subject: [PATCH 05/78] Rewrite of WandOptions, fixed server crash when placing banners --- .../constructionwand/basics/ConfigClient.java | 2 +- .../constructionwand/basics/ConfigServer.java | 2 +- .../basics/option/IOption.java | 28 +++++++ .../basics/option/OptionBoolean.java | 62 +++++++++++++++ .../basics/option/OptionEnum.java | 69 +++++++++++++++++ .../basics/option/WandOptions.java | 76 +++++++++++++++++++ .../basics/options/EnumDirection.java | 34 --------- .../basics/options/EnumLock.java | 42 ---------- .../basics/options/EnumMatch.java | 35 --------- .../basics/options/EnumMode.java | 34 --------- .../basics/options/EnumRandom.java | 34 --------- .../basics/options/EnumReplace.java | 34 --------- .../basics/options/IEnumOption.java | 13 ---- .../basics/options/WandOptions.java | 54 ------------- .../constructionwand/client/ClientEvents.java | 13 +++- .../constructionwand/client/ScreenWand.java | 35 +++++---- .../constructionwand/items/ItemWand.java | 32 ++++---- .../constructionwand/job/AngelJob.java | 4 +- .../constructionwand/job/ConstructionJob.java | 27 +++---- .../constructionwand/job/WandJob.java | 38 ++++------ .../network/PacketWandOption.java | 33 ++++---- .../assets/constructionwand/lang/de_de.json | 4 - .../assets/constructionwand/lang/en_us.json | 4 - 23 files changed, 325 insertions(+), 384 deletions(-) create mode 100644 src/main/java/thetadev/constructionwand/basics/option/IOption.java create mode 100644 src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java create mode 100644 src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java create mode 100644 src/main/java/thetadev/constructionwand/basics/option/WandOptions.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumDirection.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumLock.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumMode.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/EnumReplace.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/IEnumOption.java delete mode 100644 src/main/java/thetadev/constructionwand/basics/options/WandOptions.java diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java index e9dc900..4767952 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -10,7 +10,7 @@ public class ConfigClient static { BUILDER.push("keys"); - BUILDER.comment("Press SHIFT+CTRL to show wand options / scroll to change direction lock"); + BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for changing wand mode/direction lock"); SHIFTCTRL = BUILDER.define("ShiftCtrl", false); BUILDER.pop(); } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 5530fd4..050db68 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -20,7 +20,7 @@ public class ConfigServer public static final ForgeConfigSpec.ConfigValue> SIMILAR_BLOCKS; private static final String[] SIMILAR_BLOCKS_DEFAULT = { - "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium" + "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:grass_path" }; private static final HashMap wandProperties = new HashMap<>(); diff --git a/src/main/java/thetadev/constructionwand/basics/option/IOption.java b/src/main/java/thetadev/constructionwand/basics/option/IOption.java new file mode 100644 index 0000000..3365fed --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/option/IOption.java @@ -0,0 +1,28 @@ +package thetadev.constructionwand.basics.option; + +import thetadev.constructionwand.ConstructionWand; + +public interface IOption +{ + String getKey(); + String getValueString(); + void setValueString(String val); + + default String getKeyTranslation() { + return ConstructionWand.MODID + ".option." + getKey(); + } + default String getValueTranslation() { + return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString(); + } + default String getDescTranslation() { + return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString() + ".desc"; + } + + boolean isEnabled(); + void set(T val); + T get(); + T next(boolean dir); + default T next() { + return next(true); + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java b/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java new file mode 100644 index 0000000..4d4190d --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java @@ -0,0 +1,62 @@ +package thetadev.constructionwand.basics.option; + +import net.minecraft.nbt.CompoundNBT; + +public class OptionBoolean implements IOption +{ + private final CompoundNBT tag; + private final String key; + private final boolean enabled; + private boolean value; + + public OptionBoolean(CompoundNBT tag, String key, boolean dval, boolean enabled) { + this.tag = tag; + this.key = key; + this.enabled = enabled; + + if(tag.contains(key)) value = tag.getBoolean(key); + else value = dval; + } + + public OptionBoolean(CompoundNBT tag, String key, boolean dval) { + this(tag, key, dval, true); + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValueString() { + return value ? "yes" : "no"; + } + + @Override + public void setValueString(String val) { + set(val.equals("yes")); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void set(Boolean val) { + if(!enabled) return; + value = val; + tag.putBoolean(key, value); + } + + @Override + public Boolean get() { + return value; + } + + @Override + public Boolean next(boolean dir) { + set(!value); + return value; + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java new file mode 100644 index 0000000..f1d1865 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java @@ -0,0 +1,69 @@ +package thetadev.constructionwand.basics.option; + +import com.google.common.base.Enums; +import net.minecraft.nbt.CompoundNBT; + +public class OptionEnum> implements IOption +{ + private final CompoundNBT tag; + private final String key; + private final Class enumClass; + private final boolean enabled; + private final E dval; + private E value; + + public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval, boolean enabled) { + this.tag = tag; + this.key = key; + this.enumClass = enumClass; + this.enabled = enabled; + this.dval = dval; + + value = Enums.getIfPresent(enumClass, tag.getString(key).toUpperCase()).or(dval); + } + + public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval) { + this(tag, key, enumClass, dval, true); + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValueString() { + return value.name().toLowerCase(); + } + + @Override + public void setValueString(String val) { + set(Enums.getIfPresent(enumClass, val.toUpperCase()).or(dval)); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void set(E val) { + if(!enabled) return; + value = val; + tag.putString(key, getValueString()); + } + + @Override + public E get() { + return value; + } + + @Override + public E next(boolean dir) { + E[] enumValues = enumClass.getEnumConstants(); + int i = value.ordinal() + (dir ? 1:-1); + if(i < 0) i += enumValues.length; + set(enumValues[i % enumValues.length]); + return value; + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java new file mode 100644 index 0000000..6edb29d --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -0,0 +1,76 @@ +package thetadev.constructionwand.basics.option; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.items.ItemWand; + +import javax.annotation.Nullable; + +public class WandOptions +{ + public final CompoundNBT tag; + + private static final String TAG_ROOT = "wand_options"; + + public enum MODE + { + DEFAULT, + ANGEL; + } + public enum LOCK + { + HORIZONTAL, + VERTICAL, + NORTHSOUTH, + EASTWEST, + NOLOCK; + } + public enum DIRECTION + { + TARGET, + PLAYER; + } + public enum MATCH + { + EXACT, + SIMILAR, + ANY; + } + + public final OptionEnum mode; + public final OptionEnum lock; + public final OptionEnum direction; + public final OptionBoolean replace; + public final OptionEnum match; + public final OptionBoolean random; + + public final IOption[] allOptions; + + public WandOptions(ItemStack wandStack) { + ItemWand wand = (ItemWand) wandStack.getItem(); + tag = wandStack.getOrCreateChildTag(TAG_ROOT); + + mode = new OptionEnum<>(tag, "mode", MODE.class, MODE.DEFAULT, ConfigServer.getWandProperties(wand).getAngel() > 0); + lock = new OptionEnum<>(tag, "lock", LOCK.class, LOCK.NOLOCK); + direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.PLAYER); + replace = new OptionBoolean(tag, "replace", true); + match = new OptionEnum<>(tag, "match", MATCH.class, MATCH.SIMILAR); + random = new OptionBoolean(tag, "random", false); + + allOptions = new IOption[]{mode, lock, direction, replace, match, random}; + } + + @Nullable + public IOption get(String key){ + for(IOption option : allOptions) { + if(option.getKey().equals(key)) return option; + } + return null; + } + + public boolean testLock(LOCK l) { + if(lock.get() == LOCK.NOLOCK) return true; + return lock.get() == l; + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumDirection.java b/src/main/java/thetadev/constructionwand/basics/options/EnumDirection.java deleted file mode 100644 index f06d2ac..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/EnumDirection.java +++ /dev/null @@ -1,34 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import com.google.common.base.Enums; - -public enum EnumDirection implements IEnumOption -{ - TARGET, - PLAYER; - - private static EnumDirection[] vals = values(); - - public IEnumOption fromName(String name) { - return Enums.getIfPresent(EnumDirection.class, name.toUpperCase()).or(this); - } - - public EnumDirection next(boolean dir) { - int i = this.ordinal() + (dir ? 1:-1); - if(i < 0) i += vals.length; - - return vals[i % vals.length]; - } - - public int getOrdinal() { - return ordinal(); - } - - public String getOptionKey() { - return "direction"; - } - - public String getValue() { - return this.name().toLowerCase(); - } -} diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumLock.java b/src/main/java/thetadev/constructionwand/basics/options/EnumLock.java deleted file mode 100644 index 3bb7e83..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/EnumLock.java +++ /dev/null @@ -1,42 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import com.google.common.base.Enums; - -public enum EnumLock implements IEnumOption -{ - HORIZONTAL, - VERTICAL, - NORTHSOUTH, - EASTWEST, - NOLOCK; - - private static EnumLock[] vals = values(); - - public IEnumOption fromName(String name) { - return Enums.getIfPresent(EnumLock.class, name.toUpperCase()).or(this); - } - - public EnumLock next(boolean dir) { - int i = this.ordinal() + (dir ? 1:-1); - if(i < 0) i += vals.length; - - return vals[i % vals.length]; - } - - public int getOrdinal() { - return ordinal(); - } - - public String getOptionKey() { - return "lock"; - } - - public String getValue() { - return this.name().toLowerCase(); - } - - public boolean test(EnumLock lock) { - if(this == NOLOCK) return true; - return this == lock; - } -} diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java b/src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java deleted file mode 100644 index 31e5d01..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/EnumMatch.java +++ /dev/null @@ -1,35 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import com.google.common.base.Enums; - -public enum EnumMatch implements IEnumOption -{ - EXACT, - SIMILAR, - ANY; - - private static EnumMatch[] vals = values(); - - public IEnumOption fromName(String name) { - return Enums.getIfPresent(EnumMatch.class, name.toUpperCase()).or(this); - } - - public EnumMatch next(boolean dir) { - int i = this.ordinal() + (dir ? 1:-1); - if(i < 0) i += vals.length; - - return vals[i % vals.length]; - } - - public int getOrdinal() { - return ordinal(); - } - - public String getOptionKey() { - return "match"; - } - - public String getValue() { - return this.name().toLowerCase(); - } -} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumMode.java b/src/main/java/thetadev/constructionwand/basics/options/EnumMode.java deleted file mode 100644 index aa6f072..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/EnumMode.java +++ /dev/null @@ -1,34 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import com.google.common.base.Enums; - -public enum EnumMode implements IEnumOption -{ - DEFAULT, - ANGEL; - - private static EnumMode[] vals = values(); - - public IEnumOption fromName(String name) { - return Enums.getIfPresent(EnumMode.class, name.toUpperCase()).or(this); - } - - public EnumMode next(boolean dir) { - int i = this.ordinal() + (dir ? 1:-1); - if(i < 0) i += vals.length; - - return vals[i % vals.length]; - } - - public int getOrdinal() { - return ordinal(); - } - - public String getOptionKey() { - return "mode"; - } - - public String getValue() { - return this.name().toLowerCase(); - } -} diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java b/src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java deleted file mode 100644 index e19ceed..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/EnumRandom.java +++ /dev/null @@ -1,34 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import com.google.common.base.Enums; - -public enum EnumRandom implements IEnumOption -{ - YES, - NO; - - private static EnumRandom[] vals = values(); - - public IEnumOption fromName(String name) { - return Enums.getIfPresent(EnumRandom.class, name.toUpperCase()).or(this); - } - - public EnumRandom next(boolean dir) { - int i = this.ordinal() + (dir ? 1:-1); - if(i < 0) i += vals.length; - - return vals[i % vals.length]; - } - - public int getOrdinal() { - return ordinal(); - } - - public String getOptionKey() { - return "random"; - } - - public String getValue() { - return this.name().toLowerCase(); - } -} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/options/EnumReplace.java b/src/main/java/thetadev/constructionwand/basics/options/EnumReplace.java deleted file mode 100644 index b9e9a10..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/EnumReplace.java +++ /dev/null @@ -1,34 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import com.google.common.base.Enums; - -public enum EnumReplace implements IEnumOption -{ - YES, - NO; - - private static EnumReplace[] vals = values(); - - public IEnumOption fromName(String name) { - return Enums.getIfPresent(EnumReplace.class, name.toUpperCase()).or(this); - } - - public EnumReplace next(boolean dir) { - int i = this.ordinal() + (dir ? 1:-1); - if(i < 0) i += vals.length; - - return vals[i % vals.length]; - } - - public int getOrdinal() { - return ordinal(); - } - - public String getOptionKey() { - return "replace"; - } - - public String getValue() { - return this.name().toLowerCase(); - } -} diff --git a/src/main/java/thetadev/constructionwand/basics/options/IEnumOption.java b/src/main/java/thetadev/constructionwand/basics/options/IEnumOption.java deleted file mode 100644 index 8a39b20..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/IEnumOption.java +++ /dev/null @@ -1,13 +0,0 @@ -package thetadev.constructionwand.basics.options; - -public interface IEnumOption -{ - public int getOrdinal(); - public String getOptionKey(); - public String getValue(); - default String getTranslationKey() { - return getOptionKey() + "." + getValue(); - } - public IEnumOption next(boolean dir); - public IEnumOption fromName(String name); -} diff --git a/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java deleted file mode 100644 index 5bfbe75..0000000 --- a/src/main/java/thetadev/constructionwand/basics/options/WandOptions.java +++ /dev/null @@ -1,54 +0,0 @@ -package thetadev.constructionwand.basics.options; - -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; -import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.items.ItemWand; - -public class WandOptions -{ - private ItemWand item; - private CompoundNBT tag; - - private final String TAG_ROOT = "wand_options"; - - public static final IEnumOption[] options = { - EnumMode.DEFAULT, - EnumLock.NOLOCK, - EnumDirection.TARGET, - EnumReplace.YES, - EnumMatch.SIMILAR, - EnumRandom.NO - }; - - public WandOptions(ItemStack stack) { - this.item = (ItemWand) stack.getItem(); - this.tag = stack.getOrCreateChildTag(TAG_ROOT); - } - - public IEnumOption getOption(IEnumOption option) { - return option.fromName(tag.getString(option.getOptionKey())); - } - - public void setOption(IEnumOption option) { - tag.putString(option.getOptionKey(), option.getValue()); - } - - public IEnumOption nextOption(IEnumOption option, boolean dir) { - IEnumOption nextOption = getOption(option).next(dir); - if(nextOption == EnumMode.ANGEL && ConfigServer.getWandProperties(item).getAngel() == 0) nextOption = EnumMode.DEFAULT; - setOption(nextOption); - return nextOption; - } - - public IEnumOption nextOption(IEnumOption option) { - return nextOption(option, true); - } - - public static IEnumOption fromKey(String key) { - for(IEnumOption option : options) { - if(option.getOptionKey().equals(key)) return option; - } - return EnumMode.DEFAULT; - } -} diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 90246e9..4a4ec38 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -12,7 +12,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.*; -import thetadev.constructionwand.basics.options.*; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.items.ItemWand; import thetadev.constructionwand.network.PacketQueryUndo; import thetadev.constructionwand.network.PacketWandOption; @@ -37,6 +37,7 @@ public class ClientEvents } } + // SHIFT+(CTRL)+Scroll to change direction lock @SubscribeEvent(priority = EventPriority.HIGHEST) public static void MouseScrollEvent(InputEvent.MouseScrollEvent event) { PlayerEntity player = Minecraft.getInstance().player; @@ -48,10 +49,12 @@ public class ClientEvents if(wand == null) return; WandOptions wandOptions = new WandOptions(wand); - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.nextOption(EnumLock.NOLOCK, scroll<0), true)); + wandOptions.lock.next(scroll<0); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.lock, true)); event.setCanceled(true); } + // SHIFT+(CTRL)+Left click wand to change mode @SubscribeEvent public static void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { PlayerEntity player = event.getPlayer(); @@ -62,13 +65,15 @@ public class ClientEvents if(!(wand.getItem() instanceof ItemWand)) return; WandOptions wandOptions = new WandOptions(wand); - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.nextOption(EnumMode.DEFAULT), true)); + wandOptions.mode.next(); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.mode, true)); } + // SHIFT+Right click wand to open GUI @SubscribeEvent public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) { PlayerEntity player = event.getPlayer(); - if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL.get())) return; + if(player == null || !player.isSneaking()) return; ItemStack wand = event.getItemStack(); if(!(wand.getItem() instanceof ItemWand)) return; diff --git a/src/main/java/thetadev/constructionwand/client/ScreenWand.java b/src/main/java/thetadev/constructionwand/client/ScreenWand.java index 9125243..bc312f1 100644 --- a/src/main/java/thetadev/constructionwand/client/ScreenWand.java +++ b/src/main/java/thetadev/constructionwand/client/ScreenWand.java @@ -9,8 +9,8 @@ import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TranslationTextComponent; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.options.*; +import thetadev.constructionwand.basics.option.IOption; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.network.PacketWandOption; public class ScreenWand extends Screen @@ -27,7 +27,6 @@ public class ScreenWand extends Screen private static final int FIELD_WIDTH = N_COLS * (BUTTON_WIDTH+SPACING_WIDTH) - SPACING_WIDTH; private static final int FIELD_HEIGHT = N_ROWS * (BUTTON_HEIGHT+SPACING_HEIGHT) - SPACING_HEIGHT; - private static final String LANG_PREFIX = ConstructionWand.MODID + ".option."; public ScreenWand(ItemStack wand) { super(new StringTextComponent("ScreenWand")); @@ -39,12 +38,12 @@ public class ScreenWand extends Screen public void init(Minecraft minecraft, int width, int height) { super.init(minecraft, width, height); - createButton(0, 0, EnumMode.DEFAULT, ConfigServer.getWandProperties(wand.getItem()).getAngel() > 0); - createButton(0, 1, EnumLock.NOLOCK, true); - createButton(0, 2, EnumDirection.TARGET, true); - createButton(1, 0, EnumReplace.YES, true); - createButton(1, 1, EnumMatch.SIMILAR, true); - createButton(1, 2, EnumRandom.NO, true); + createButton(0, 0, wandOptions.mode); + createButton(0, 1, wandOptions.lock); + createButton(0, 2, wandOptions.direction); + createButton(1, 0, wandOptions.replace); + createButton(1, 1, wandOptions.match); + createButton(1, 2, wandOptions.random); } @Override @@ -60,20 +59,21 @@ public class ScreenWand extends Screen return super.charTyped(character, code); } - private void createButton(int cx, int cy, IEnumOption option, boolean en) { + private void createButton(int cx, int cy, IOption option) { Button button = new Button(getX(cx), getY(cy), BUTTON_WIDTH, BUTTON_HEIGHT, getButtonLabel(option), bt -> clickButton(bt, option), (bt, ms, x, y) -> drawTooltip(ms, x, y, option)); - button.active = en; + button.active = option.isEnabled(); addButton(button); } - private void clickButton(Button button, IEnumOption option) { - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.nextOption(option), false)); + private void clickButton(Button button, IOption option) { + option.next(); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(option, false)); button.setMessage(getButtonLabel(option)); } - private void drawTooltip(MatrixStack matrixStack, int mouseX, int mouseY, IEnumOption option) { + private void drawTooltip(MatrixStack matrixStack, int mouseX, int mouseY, IOption option) { if(isMouseOver(mouseX, mouseY)) { - renderTooltip(matrixStack, new TranslationTextComponent(LANG_PREFIX + wandOptions.getOption(option).getTranslationKey() + ".desc"), mouseX, mouseY); + renderTooltip(matrixStack, new TranslationTextComponent(option.getDescTranslation()), mouseX, mouseY); } } @@ -85,8 +85,7 @@ public class ScreenWand extends Screen return height/2 - FIELD_HEIGHT/2 + n*(BUTTON_HEIGHT+SPACING_HEIGHT); } - private ITextComponent getButtonLabel(IEnumOption option) { - IEnumOption opt = wandOptions.getOption(option); - return new TranslationTextComponent(LANG_PREFIX+opt.getOptionKey()).append(new TranslationTextComponent(LANG_PREFIX+opt.getTranslationKey())); + private ITextComponent getButtonLabel(IOption option) { + return new TranslationTextComponent(option.getKeyTranslation()).append(new TranslationTextComponent(option.getValueTranslation())); } } diff --git a/src/main/java/thetadev/constructionwand/items/ItemWand.java b/src/main/java/thetadev/constructionwand/items/ItemWand.java index 08c7d6f..6e1001e 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWand.java @@ -22,9 +22,8 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.options.EnumMode; -import thetadev.constructionwand.basics.options.IEnumOption; -import thetadev.constructionwand.basics.options.WandOptions; +import thetadev.constructionwand.basics.option.IOption; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.job.AngelJob; import thetadev.constructionwand.job.WandJob; @@ -95,7 +94,7 @@ public abstract class ItemWand extends Item public static int getWandMode(ItemStack stack) { WandOptions options = new WandOptions(stack); - return options.getOption(EnumMode.DEFAULT).getOrdinal(); + return options.mode.get().ordinal(); } @OnlyIn(Dist.CLIENT) @@ -103,34 +102,31 @@ public abstract class ItemWand extends Item ItemWand wand = (ItemWand) itemstack.getItem(); WandOptions options = new WandOptions(itemstack); - String langPrefix = ConstructionWand.MODID + ".option."; String langTooltip = ConstructionWand.MODID + ".tooltip."; if(Screen.hasShiftDown()) { - for(int i=1; i opt = options.allOptions[i]; + lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) + .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.GRAY)) ); } } else { - IEnumOption opt = WandOptions.options[0]; + IOption opt = options.allOptions[0]; lines.add(new TranslationTextComponent(langTooltip + "blocks", getLimit()).mergeStyle(TextFormatting.GRAY)); - lines.add(new TranslationTextComponent(langPrefix+opt.getOptionKey()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(langPrefix+options.getOption(opt).getTranslationKey()).mergeStyle(TextFormatting.WHITE))); + lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) + .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.WHITE))); lines.add(new TranslationTextComponent(langTooltip + "shift").mergeStyle(TextFormatting.AQUA)); } } - public static void optionMessage(PlayerEntity player, IEnumOption option) { - String langPrefix = ConstructionWand.MODID + ".option."; - + public static void optionMessage(PlayerEntity player, IOption option) { player.sendStatusMessage( - new TranslationTextComponent(langPrefix+option.getOptionKey()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(langPrefix+option.getTranslationKey()).mergeStyle(TextFormatting.WHITE)) + new TranslationTextComponent(option.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) + .append(new TranslationTextComponent(option.getValueTranslation()).mergeStyle(TextFormatting.WHITE)) .append(new StringTextComponent(" - ").mergeStyle(TextFormatting.GRAY)) - .append(new TranslationTextComponent(langPrefix+option.getTranslationKey()+".desc").mergeStyle(TextFormatting.WHITE)) + .append(new TranslationTextComponent(option.getDescTranslation()).mergeStyle(TextFormatting.WHITE)) , true); } } diff --git a/src/main/java/thetadev/constructionwand/job/AngelJob.java b/src/main/java/thetadev/constructionwand/job/AngelJob.java index 2547dea..59d11f3 100644 --- a/src/main/java/thetadev/constructionwand/job/AngelJob.java +++ b/src/main/java/thetadev/constructionwand/job/AngelJob.java @@ -10,7 +10,7 @@ import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.basics.options.EnumMode; +import thetadev.constructionwand.basics.option.WandOptions; public class AngelJob extends WandJob { @@ -24,7 +24,7 @@ public class AngelJob extends WandJob @Override protected void getBlockPositionList() { - if(options.getOption(EnumMode.DEFAULT) != EnumMode.ANGEL || ConfigServer.getWandProperties(wandItem).getAngel() == 0) return; + if(options.mode.get() != WandOptions.MODE.ANGEL || ConfigServer.getWandProperties(wandItem).getAngel() == 0) return; if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return; diff --git a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java index aab3705..b3b48fd 100644 --- a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java +++ b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java @@ -7,8 +7,7 @@ import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; -import thetadev.constructionwand.basics.ReplacementRegistry; -import thetadev.constructionwand.basics.options.EnumLock; +import thetadev.constructionwand.basics.option.WandOptions; import java.util.HashSet; import java.util.LinkedList; @@ -21,8 +20,6 @@ public class ConstructionJob extends WandJob @Override protected void getBlockPositionList() { - EnumLock lock = (EnumLock) options.getOption(EnumLock.NOLOCK); - LinkedList candidates = new LinkedList<>(); HashSet allCandidates = new HashSet<>(); @@ -32,9 +29,9 @@ public class ConstructionJob extends WandJob // Is place direction allowed by lock? if(placeDirection == Direction.UP || placeDirection == Direction.DOWN) { - if(lock.test(EnumLock.NORTHSOUTH) || lock.test(EnumLock.EASTWEST)) candidates.add(startingPoint); + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) candidates.add(startingPoint); } - else if(lock.test(EnumLock.HORIZONTAL) || lock.test(EnumLock.VERTICAL)) candidates.add(startingPoint); + else if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) candidates.add(startingPoint); while(!candidates.isEmpty() && placeSnapshots.size() < maxBlocks) { @@ -51,15 +48,15 @@ public class ConstructionJob extends WandJob switch(placeDirection) { case DOWN: case UP: - if(lock.test(EnumLock.NORTHSOUTH)) { + if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { candidates.add(currentCandidate.offset(Direction.NORTH)); candidates.add(currentCandidate.offset(Direction.SOUTH)); } - if(lock.test(EnumLock.EASTWEST)) { + if(options.testLock(WandOptions.LOCK.EASTWEST)) { candidates.add(currentCandidate.offset(Direction.EAST)); candidates.add(currentCandidate.offset(Direction.WEST)); } - if(lock.test(EnumLock.NORTHSOUTH) && lock.test(EnumLock.EASTWEST)) { + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) && options.testLock(WandOptions.LOCK.EASTWEST)) { candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.EAST)); candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.WEST)); candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.EAST)); @@ -68,15 +65,15 @@ public class ConstructionJob extends WandJob break; case NORTH: case SOUTH: - if(lock.test(EnumLock.HORIZONTAL)) { + if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { candidates.add(currentCandidate.offset(Direction.EAST)); candidates.add(currentCandidate.offset(Direction.WEST)); } - if(lock.test(EnumLock.VERTICAL)) { + if(options.testLock(WandOptions.LOCK.VERTICAL)) { candidates.add(currentCandidate.offset(Direction.UP)); candidates.add(currentCandidate.offset(Direction.DOWN)); } - if(lock.test(EnumLock.HORIZONTAL) && lock.test(EnumLock.VERTICAL)) { + if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.EAST)); candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.WEST)); candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.EAST)); @@ -85,15 +82,15 @@ public class ConstructionJob extends WandJob break; case EAST: case WEST: - if(lock.test(EnumLock.HORIZONTAL)) { + if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { candidates.add(currentCandidate.offset(Direction.NORTH)); candidates.add(currentCandidate.offset(Direction.SOUTH)); } - if(lock.test(EnumLock.VERTICAL)) { + if(options.testLock(WandOptions.LOCK.VERTICAL)) { candidates.add(currentCandidate.offset(Direction.UP)); candidates.add(currentCandidate.offset(Direction.DOWN)); } - if(lock.test(EnumLock.HORIZONTAL) && lock.test(EnumLock.VERTICAL)) { + if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.NORTH)); candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.SOUTH)); candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.NORTH)); diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index eae78ef..df8cf05 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -19,7 +19,7 @@ import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.*; -import thetadev.constructionwand.basics.options.*; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.ItemWand; @@ -34,14 +34,11 @@ public abstract class WandJob protected BlockRayTraceResult rayTraceResult; protected ItemStack wand; protected ItemWand wandItem; - protected WandOptions options; // Wand options + protected WandOptions options; protected int maxBlocks; - protected boolean doReplace; - protected boolean targetDirection; - protected boolean randomMode; - protected EnumMatch matchMode; + protected boolean doRandomize; protected LinkedHashMap itemCounts; protected HashMap itemWeights; @@ -64,10 +61,7 @@ public abstract class WandJob // Get options options = new WandOptions(wand); - doReplace = options.getOption(EnumReplace.YES) == EnumReplace.YES; - targetDirection = options.getOption(EnumDirection.TARGET) == EnumDirection.TARGET; - randomMode = options.getOption(EnumRandom.NO) == EnumRandom.YES; - matchMode = (EnumMatch) options.getOption(EnumMatch.SIMILAR); + doRandomize = options.random.get(); // Get place item addBlockItems(); @@ -91,10 +85,10 @@ public abstract class WandJob } public static WandJob getJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { - IEnumOption mode = new WandOptions(itemStack).getOption(EnumMode.DEFAULT); + WandOptions options = new WandOptions(itemStack); - if(mode == EnumMode.ANGEL) return new TransductionJob(player, world, rayTraceResult, itemStack); - else return new ConstructionJob(player, world, rayTraceResult, itemStack); + if(options.mode.get() == WandOptions.MODE.ANGEL) return new TransductionJob(player, world, rayTraceResult, itemStack); + return new ConstructionJob(player, world, rayTraceResult, itemStack); } public Set getBlockPositions() { @@ -129,8 +123,8 @@ public abstract class WandJob Block targetBlock = targetState.getBlock(); ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); - if(randomMode) { - for(ItemStack stack : WandUtil.getHotbar(player)) { + if(doRandomize) { + for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { if(stack.getItem() instanceof BlockItem) { BlockItem item = (BlockItem) stack.getItem(); addBlockItem(item); @@ -145,18 +139,18 @@ public abstract class WandJob // Otherwise use target block if(itemCounts.isEmpty()) { - Item item = targetBlock.getPickBlock(targetState, rayTraceResult, world, targetPos, player).getItem(); + Item item = targetBlock.asItem(); if(item instanceof BlockItem) { addBlockItem((BlockItem) item); // Add replacement items - if(matchMode != EnumMatch.EXACT) { + if(options.match.get() != WandOptions.MATCH.EXACT) { for(Item it : ReplacementRegistry.getMatchingSet(item)) { if(it instanceof BlockItem) addBlockItem((BlockItem) it); } } } - randomMode = false; + doRandomize = false; } } @@ -236,7 +230,7 @@ public abstract class WandJob if(!ctx.canPlace()) return null; // If replace mode is off, target has to be air - if(!doReplace && !world.isAirBlock(pos)) return null; + if(!options.replace.get() && !world.isAirBlock(pos)) return null; // Can block be placed? BlockState placeBlock = Block.getBlockFromItem(item).getStateForPlacement(ctx); @@ -252,7 +246,7 @@ public abstract class WandJob } // Copy certain properties of supporting block (save the effort when running preview on client) - if(targetDirection && !world.isRemote) { + if(options.direction.get() == WandOptions.DIRECTION.TARGET && !world.isRemote) { // Block properties to be copied (alignment/rotation properties) for(Property property : new Property[] { BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, @@ -275,7 +269,7 @@ public abstract class WandJob @Nullable protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { ArrayList items = new ArrayList<>(itemCounts.keySet()); - if(randomMode) { + if(doRandomize) { for(BlockItem item : itemWeights.keySet()) { int weight = itemWeights.get(item); for(int i=0; i option, boolean notify) { + this(option.getKey(), option.getValueString(), notify); + } + + private PacketWandOption(String key, String value, boolean notify) { + this.key = key; + this.value = value; this.notify = notify; } public static void encode(PacketWandOption msg, PacketBuffer buffer) { - buffer.writeString(msg.option.getOptionKey()); - buffer.writeString(msg.option.getValue()); + buffer.writeString(msg.key); + buffer.writeString(msg.value); buffer.writeBoolean(msg.notify); } public static PacketWandOption decode(PacketBuffer buffer) { - String key = buffer.readString(100); - String val = buffer.readString(100); - - boolean notify = buffer.readBoolean(); - - return new PacketWandOption(WandOptions.fromKey(key).fromName(val), notify); + return new PacketWandOption(buffer.readString(100), buffer.readString(100), buffer.readBoolean()); } public static class Handler @@ -47,10 +48,12 @@ public class PacketWandOption ItemStack wand = WandUtil.holdingWand(player); if(wand == null) return; WandOptions options = new WandOptions(wand); - options.setOption(msg.option); - if(msg.notify) ItemWand.optionMessage(player, msg.option); + IOption option = options.get(msg.key); + if(option == null) return; + option.setValueString(msg.value); + if(msg.notify) ItemWand.optionMessage(player, option); player.inventory.markDirty(); } } diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index 36b71bf..6743912 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -4,10 +4,6 @@ "item.constructionwand.diamond_wand": "Diamantener Stab", "item.constructionwand.infinity_wand": "Stab der Unendlichkeit", - "constructionwand.key.category": "Construction Wand", - "constructionwand.key.direction": "Ausrichtung", - "constructionwand.key.replace": "Ersetzungsmodus", - "constructionwand.tooltip.blocks": "Max. %d Blöcke", "constructionwand.tooltip.shift": "Drücke [SHIFT]", diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 6a000f5..5415b4e 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -4,10 +4,6 @@ "item.constructionwand.diamond_wand": "Diamond Wand", "item.constructionwand.infinity_wand": "Infinity Wand", - "constructionwand.key.category": "Construction Wand", - "constructionwand.key.direction": "Place direction", - "constructionwand.key.replace": "Replacement mode", - "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", From 733f3bf597bdd242f67eb62abb5b86718aaf78ee Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 17 Sep 2020 22:35:04 +0200 Subject: [PATCH 06/78] Cleaned up gradle --- build.gradle | 9 --------- 1 file changed, 9 deletions(-) diff --git a/build.gradle b/build.gradle index 70ba5b0..bfb6d65 100644 --- a/build.gradle +++ b/build.gradle @@ -107,15 +107,6 @@ dependencies { version: "${project.botania}", classifier: "api" ]) - /* - runtimeOnly fg.deobf("vazkii.patchouli:Patchouli:1.16-41") - runtimeOnly fg.deobf("curse.maven:Curios:3042479") - runtimeOnly fg.deobf([ - group: "vazkii.botania", - name: "Botania", - version: "${project.botania}", - classifier: "" - ])*/ } jar { From ac21bb14c69f14a33c44b6f0d8684971f520a553 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 17 Sep 2020 22:55:08 +0200 Subject: [PATCH 07/78] Added 1.16.3 compat --- build.gradle | 3 --- src/main/resources/META-INF/mods.toml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index bfb6d65..f075063 100644 --- a/build.gradle +++ b/build.gradle @@ -89,9 +89,6 @@ repositories { maven { url = "https://maven.theillusivec4.top/" } - maven { - url = "https://www.cursemaven.com" - } } dependencies { diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index c709f6f..cfdd15e 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -26,6 +26,6 @@ This is my first minecraft mod. May the odds be ever in your favor. [[dependencies.constructionwand]] modId="minecraft" mandatory=true - versionRange="[1.16.2]" + versionRange="[1.16.2, 1.16.3]" ordering="NONE" side="BOTH" From eacb3138e900274627db3f2813550d0f98d6c82f Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 17 Sep 2020 23:18:26 +0200 Subject: [PATCH 08/78] Set direction default to TARGET --- .../thetadev/constructionwand/basics/option/WandOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java index 6edb29d..575d9ca 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -53,7 +53,7 @@ public class WandOptions mode = new OptionEnum<>(tag, "mode", MODE.class, MODE.DEFAULT, ConfigServer.getWandProperties(wand).getAngel() > 0); lock = new OptionEnum<>(tag, "lock", LOCK.class, LOCK.NOLOCK); - direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.PLAYER); + direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.TARGET); replace = new OptionBoolean(tag, "replace", true); match = new OptionEnum<>(tag, "match", MATCH.class, MATCH.SIMILAR); random = new OptionBoolean(tag, "random", false); From d8b5d0cf2a5cee81bb00774d168eae7bf309e16b Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 18 Sep 2020 13:04:01 +0200 Subject: [PATCH 09/78] Changed readme --- README.md | 23 ++++++++++++++++------- images/options.png | Bin 0 -> 307569 bytes 2 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 images/options.png diff --git a/README.md b/README.md index 2ac223f..bc211d2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ If you concentrate enough, you can even conjure a block in mid air! ## Wands There are basic wands made from stone, iron and diamond and the Infinity wand. +Wand properties can be changed in the config. | Wand | Durability | Max. Blocks | Angel distance | |----------|-------------|-------------|----------------| @@ -22,22 +23,30 @@ There are basic wands made from stone, iron and diamond and the Infinity wand. ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/crafting4.png) ## Modes -There are four wand tiers: Stone, Iron, Diamond and Infinity. - **Default mode:** Extends your build on the side facing you. Maximum number of blocks depends on wand tier. SHIFT+scroll to change the placement mode (Horizontal, Vertical, North/South, East/West, No lock). **Angel mode:** Places a block on the opposite side of the block (or row of blocks) you are facing. Maximum distance depends on wand tier. Right click empty space to place a block in midair (similar to angel blocks, hence the name). To do that, you'll need to have the block you want to place in your offhand. You can't place a block in midair if you've fallen more than 10 blocks deep (no easy rescue tool from falling into the void). +You can change the wand mode using the option screen or by SHIFT+Left clicking empty space. + ## Options -**Direction lock:** To change the direction lock, hold down SHIFT and scroll. With active direction lock the wand will only place blocks in one row or column (choose between North/South, East/West on a horizontal plane and Horizontal, Vertical on a vertical plane). If the direction lock is switched off, the wand will extend the entire face of the building it's pointed at. This option has no effect in Angel mode. +SHIFT+Right clicking empty space opens the option screen of your wand. -Every wand has two additional options that can be changed using keys. (Standard: N / SHIFT+N) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/options.png) -**Placement direction:** If set to "player" the wand places blocks in the same direction as if they were placed by yourself. Target mode places the blocks in the same direction as their supporting block. See the picture below: +**Restriction:** If restriction is enabled the wand will only place blocks in one row or column (choose between North/South, East/West on a horizontal plane and Horizontal, Vertical on a vertical plane). If the direction lock is switched off, the wand will extend the entire face of the building it's pointed at. This option has no effect in Angel mode. + +**Direction:** If set to "Player" the wand places blocks in the same direction as if they were placed by yourself. Target mode places the blocks in the same direction as their supporting block. See the picture below: ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/placedir.png) -**Fluid lock:** Enables/disables the replacement of fluid blocks (both source + flowing). +**Replacement:** Enables/disables the replacement of replaceable blocks (Fluids, snow, tallgrass). + +**Matching:** Select which blocks are extended by the wand. If set to "EXACT" it will only extend blocks that are exactly the same as the selected block. +"SIMILAR" will treat similar blocks equally (e.g. extend dirt and grass blocks). +"ANY" will extend any block on the face of the building you're looking at. + +**Random:** If random mode is enabled, the wand places random blocks from your hotbar. ~~Shamelessly stolen~~ Inspired by the Trowel from Quark. ## Additional features - If you have shulker boxes in your inventory filled with blocks, the wand can pull them out and place them @@ -50,4 +59,4 @@ Every wand has two additional options that can be changed using keys. (Standard: - Look at your statisics to see how many blocks you have placed using your wand -- **1.16 only:** The Infinity Wand won't burn in lava just like netherite gear. \ No newline at end of file +- **1.16+ only:** The Infinity Wand won't burn in lava just like netherite gear. \ No newline at end of file diff --git a/images/options.png b/images/options.png new file mode 100644 index 0000000000000000000000000000000000000000..9adb38b4b326fcae0d9090abef4084730af281c4 GIT binary patch literal 307569 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%0AP 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�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 FV1fku*6RQO literal 0 HcmV?d00001 From b569e274dee2a7194c60ceda61bb0d32e534b0d6 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 18 Sep 2020 21:32:42 +0200 Subject: [PATCH 10/78] Fixed stack overflow when placing many connecting blocks (1.16) --- gradle.properties | 2 +- .../constructionwand/job/PlaceSnapshot.java | 7 +- .../constructionwand/job/WandJob.java | 102 +++++++++--------- 3 files changed, 55 insertions(+), 56 deletions(-) diff --git a/gradle.properties b/gradle.properties index 54cf821..1e9da9a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=1 -version_minor=3 \ No newline at end of file +version_minor=4 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java index de64757..e04b6ef 100644 --- a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java @@ -6,14 +6,15 @@ import net.minecraft.util.math.BlockPos; public class PlaceSnapshot { - public final BlockState block; + public BlockState block; + public final BlockState supportingBlock; public final BlockPos pos; public final BlockItem item; - public PlaceSnapshot(BlockPos pos, BlockState block, BlockItem item) + public PlaceSnapshot(BlockPos pos, BlockState supportingBlock, BlockItem item) { this.pos = pos; - this.block = block; + this.supportingBlock = supportingBlock; this.item = item; } } diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index df8cf05..3ff8236 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -220,33 +220,65 @@ public abstract class WandJob protected abstract void getBlockPositionList(); + // Get PlaceSnapshot, or null if no block can be placed @Nullable - private BlockState getPlaceBlockstate(BlockPos pos, BlockItem item, BlockState supportingBlock) { + protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { // Is position out of world? if(!world.isBlockPresent(pos)) return null; - // Is block at pos replaceable? - BlockItemUseContext ctx = new WandItemUseContext(this, pos, item); - if(!ctx.canPlace()) return null; - // If replace mode is off, target has to be air if(!options.replace.get() && !world.isAirBlock(pos)) return null; - // Can block be placed? - BlockState placeBlock = Block.getBlockFromItem(item).getStateForPlacement(ctx); - if(placeBlock == null) return null; - placeBlock = Block.getValidBlockForPosition(placeBlock, world, pos); - if(placeBlock.getBlock() == Blocks.AIR || !placeBlock.isValidPosition(world, pos)) return null; + ArrayList items = new ArrayList<>(itemCounts.keySet()); + if(doRandomize) { + for(BlockItem item : itemWeights.keySet()) { + int weight = itemWeights.get(item); + for(int i=0; i items = new ArrayList<>(itemCounts.keySet()); - if(doRandomize) { - for(BlockItem item : itemWeights.keySet()) { - int weight = itemWeights.get(item); - for(int i=0; i Date: Mon, 28 Sep 2020 00:57:00 +0200 Subject: [PATCH 11/78] Added Pools, CTRL+Shift to open GUI, refactored undo --- gradle.properties | 2 +- .../constructionwand/ConstructionWand.java | 6 +- .../constructionwand/basics/CommonEvents.java | 2 +- .../constructionwand/basics/ConfigClient.java | 7 +- .../constructionwand/basics/pool/IPool.java | 11 ++ .../basics/pool/OrderedPool.java | 34 ++++ .../basics/pool/RandomPool.java | 54 +++++++ .../constructionwand/client/ClientEvents.java | 6 +- .../constructionwand/items/ItemWand.java | 8 +- .../constructionwand/job/JobHistory.java | 98 ------------ .../constructionwand/job/UndoHistory.java | 148 ++++++++++++++++++ .../constructionwand/job/WandJob.java | 121 +++++--------- .../network/PacketQueryUndo.java | 2 +- 13 files changed, 301 insertions(+), 198 deletions(-) create mode 100644 src/main/java/thetadev/constructionwand/basics/pool/IPool.java create mode 100644 src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java create mode 100644 src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java delete mode 100644 src/main/java/thetadev/constructionwand/job/JobHistory.java create mode 100644 src/main/java/thetadev/constructionwand/job/UndoHistory.java diff --git a/gradle.properties b/gradle.properties index 1e9da9a..eec0280 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=1 -version_minor=4 \ No newline at end of file +version_minor=5 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index fae0393..e8db1fe 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -20,7 +20,7 @@ import thetadev.constructionwand.client.RenderBlockPreview; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.containers.ContainerRegistrar; import thetadev.constructionwand.items.ModItems; -import thetadev.constructionwand.job.JobHistory; +import thetadev.constructionwand.job.UndoHistory; import thetadev.constructionwand.network.PacketQueryUndo; import thetadev.constructionwand.network.PacketUndoBlocks; import thetadev.constructionwand.network.PacketWandOption; @@ -36,14 +36,14 @@ public class ConstructionWand public SimpleChannel HANDLER; public ContainerManager containerManager; - public JobHistory jobHistory; + public UndoHistory undoHistory; public RenderBlockPreview renderBlockPreview; public ConstructionWand() { instance = this; containerManager = new ContainerManager(); - jobHistory = new JobHistory(); + undoHistory = new UndoHistory(); // Register setup methods for modloading FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); diff --git a/src/main/java/thetadev/constructionwand/basics/CommonEvents.java b/src/main/java/thetadev/constructionwand/basics/CommonEvents.java index f248eee..013ff08 100644 --- a/src/main/java/thetadev/constructionwand/basics/CommonEvents.java +++ b/src/main/java/thetadev/constructionwand/basics/CommonEvents.java @@ -13,6 +13,6 @@ public class CommonEvents public static void logOut(PlayerEvent.PlayerLoggedOutEvent e) { PlayerEntity player = e.getPlayer(); if(player.getEntityWorld().isRemote) return; - ConstructionWand.instance.jobHistory.removePlayer(player); + ConstructionWand.instance.undoHistory.removePlayer(player); } } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java index 4767952..60fd305 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -6,12 +6,15 @@ public class ConfigClient { private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - public static final ForgeConfigSpec.BooleanValue SHIFTCTRL; + public static final ForgeConfigSpec.BooleanValue SHIFTCTRL_MODE; + public static final ForgeConfigSpec.BooleanValue SHIFTCTRL_GUI; static { BUILDER.push("keys"); BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for changing wand mode/direction lock"); - SHIFTCTRL = BUILDER.define("ShiftCtrl", false); + SHIFTCTRL_MODE = BUILDER.define("ShiftCtrl", false); + BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for opening wand GUI"); + SHIFTCTRL_GUI = BUILDER.define("ShiftCtrlGUI", false); BUILDER.pop(); } diff --git a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java new file mode 100644 index 0000000..2222694 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java @@ -0,0 +1,11 @@ +package thetadev.constructionwand.basics.pool; + +import javax.annotation.Nullable; + +public interface IPool +{ + void add(T element); + @Nullable + T draw(); + void reset(); +} diff --git a/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java b/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java new file mode 100644 index 0000000..f3f5d08 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java @@ -0,0 +1,34 @@ +package thetadev.constructionwand.basics.pool; + +import javax.annotation.Nullable; +import java.util.ArrayList; + +public class OrderedPool implements IPool +{ + private final ArrayList elements; + private int index; + + public OrderedPool() { + elements = new ArrayList<>(); + reset(); + } + + @Override + public void add(T element) { + elements.add(element); + } + + @Nullable + @Override + public T draw() { + if(index >= elements.size()) return null; + T e = elements.get(index); + index++; + return e; + } + + @Override + public void reset() { + index = 0; + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java b/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java new file mode 100644 index 0000000..4f63320 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java @@ -0,0 +1,54 @@ +package thetadev.constructionwand.basics.pool; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Random; + +public class RandomPool implements IPool +{ + private final Random rng; + private final HashMap elements; + private HashSet pool; + + public RandomPool(Random rng) { + this.rng = rng; + elements = new HashMap<>(); + reset(); + } + + @Override + public void add(T element) { + addWithWeight(element, 1); + } + + public void addWithWeight(T element, int weight) { + if(weight < 1) return; + elements.merge(element, weight, Integer::sum); + pool.add(element); + } + + @Nullable + @Override + public T draw() { + int allWeights = pool.stream().reduce(0, (partialRes, e) -> partialRes + elements.get(e), Integer::sum); + if(allWeights < 1) return null; + + int random = rng.nextInt(allWeights); + int accWeight = 0; + + for(T e : pool) { + accWeight += elements.get(e); + if(random < accWeight) { + pool.remove(e); + return e; + } + } + return null; + } + + @Override + public void reset() { + pool = new HashSet<>(elements.keySet()); + } +} diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 4a4ec38..5e125aa 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -43,7 +43,7 @@ public class ClientEvents PlayerEntity player = Minecraft.getInstance().player; double scroll = event.getScrollDelta(); - if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL.get()) || scroll == 0) return; + if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL_MODE.get()) || scroll == 0) return; ItemStack wand = WandUtil.holdingWand(player); if(wand == null) return; @@ -59,7 +59,7 @@ public class ClientEvents public static void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { PlayerEntity player = event.getPlayer(); - if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL.get())) return; + if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL_MODE.get())) return; ItemStack wand = event.getItemStack(); if(!(wand.getItem() instanceof ItemWand)) return; @@ -73,7 +73,7 @@ public class ClientEvents @SubscribeEvent public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) { PlayerEntity player = event.getPlayer(); - if(player == null || !player.isSneaking()) return; + if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL_GUI.get())) return; ItemStack wand = event.getItemStack(); if(!(wand.getItem() instanceof ItemWand)) return; diff --git a/src/main/java/thetadev/constructionwand/items/ItemWand.java b/src/main/java/thetadev/constructionwand/items/ItemWand.java index 6e1001e..2b535a2 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWand.java @@ -1,7 +1,6 @@ package thetadev.constructionwand.items; import net.minecraft.block.BlockState; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.PlayerEntity; @@ -47,11 +46,8 @@ public abstract class ItemWand extends Item ItemStack stack = player.getHeldItem(hand); - if(player.isSneaking() && ConstructionWand.instance.jobHistory.isUndoActive(player)) { - WandJob job = ConstructionWand.instance.jobHistory.getForUndo(player, world, context.getPos()); - if(job == null) return ActionResultType.FAIL; - //ConstructionWand.LOGGER.debug("Starting Undo"); - return job.undo() ? ActionResultType.SUCCESS : ActionResultType.FAIL; + if(player.isSneaking() && ConstructionWand.instance.undoHistory.isUndoActive(player)) { + return ConstructionWand.instance.undoHistory.undo(player, world, context.getPos()) ? ActionResultType.SUCCESS : ActionResultType.FAIL; } else { WandJob job = WandJob.getJob(player, world, new BlockRayTraceResult(context.getHitVec(), context.getFace(), context.getPos(), false), stack); diff --git a/src/main/java/thetadev/constructionwand/job/JobHistory.java b/src/main/java/thetadev/constructionwand/job/JobHistory.java deleted file mode 100644 index 61b5650..0000000 --- a/src/main/java/thetadev/constructionwand/job/JobHistory.java +++ /dev/null @@ -1,98 +0,0 @@ -package thetadev.constructionwand.job; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.player.ServerPlayerEntity; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import net.minecraftforge.fml.network.PacketDistributor; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.network.PacketUndoBlocks; - -import java.util.*; - -public class JobHistory -{ - private final HashMap history; - - public JobHistory() { - history = new HashMap<>(); - } - - private HistoryEntry getEntryFromPlayer(PlayerEntity player) { - return history.computeIfAbsent(player.getUniqueID(), k -> new HistoryEntry()); - } - - private LinkedList getJobsFromPlayer(PlayerEntity player) { - return getEntryFromPlayer(player).jobs; - } - - public void add(WandJob job) { - LinkedList list = getJobsFromPlayer(job.getPlayer()); - list.add(job); - while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); - } - - public void removePlayer(PlayerEntity player) { - history.remove(player.getUniqueID()); - } - - public void updateClient(PlayerEntity player, boolean ctrlDown) { - World world = player.getEntityWorld(); - if(world.isRemote) return; - - // Set state of CTRL key - HistoryEntry entry = getEntryFromPlayer(player); - entry.undoActive = ctrlDown; - - LinkedList jobs = entry.jobs; - Set positions; - - // Send block positions of most recent job to client - if(jobs.isEmpty()) positions = Collections.emptySet(); - else { - WandJob job = jobs.getLast(); - if(job == null || !job.getWorld().equals(world)) positions = Collections.emptySet(); - else positions = job.getBlockPositions(); - } - - PacketUndoBlocks packet = new PacketUndoBlocks(positions); - ConstructionWand.instance.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), packet); - } - - public boolean isUndoActive(PlayerEntity player) { - return getEntryFromPlayer(player).undoActive; - } - - public WandJob getForUndo(PlayerEntity player, World world, BlockPos pos) { - // If CTRL key is not pressed, return - HistoryEntry entry = getEntryFromPlayer(player); - if(!entry.undoActive) return null; - - // Get the most recent job for undo - LinkedList jobs = entry.jobs; - if(jobs.isEmpty()) return null; - WandJob job = jobs.getLast(); - - if(job.getWorld().equals(world) && job.getBlockPositions().contains(pos)) { - // Update job player entity, it could have changed by rejoin/respawn - job.setPlayer(player); - - // Remove undo job, sent update to client and return it - jobs.remove(job); - updateClient(player, true); - return job; - } - return null; - } - - private static class HistoryEntry { - public LinkedList jobs; - public boolean undoActive; - - public HistoryEntry() { - jobs = new LinkedList<>(); - undoActive = false; - } - } -} diff --git a/src/main/java/thetadev/constructionwand/job/UndoHistory.java b/src/main/java/thetadev/constructionwand/job/UndoHistory.java new file mode 100644 index 0000000..dd226ad --- /dev/null +++ b/src/main/java/thetadev/constructionwand/job/UndoHistory.java @@ -0,0 +1,148 @@ +package thetadev.constructionwand.job; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.network.PacketDistributor; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.ReplacementRegistry; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.network.PacketUndoBlocks; + +import java.util.*; +import java.util.stream.Collectors; + +public class UndoHistory +{ + private final HashMap history; + + public UndoHistory() { + history = new HashMap<>(); + } + + private PlayerEntry getEntryFromPlayer(PlayerEntity player) { + return history.computeIfAbsent(player.getUniqueID(), k -> new PlayerEntry()); + } + + public void add(PlayerEntity player, World world, LinkedList placeSnapshots) { + LinkedList list = getEntryFromPlayer(player).entries; + list.add(new HistoryEntry(placeSnapshots, world)); + while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); + } + + public void removePlayer(PlayerEntity player) { + history.remove(player.getUniqueID()); + } + + public void updateClient(PlayerEntity player, boolean ctrlDown) { + World world = player.getEntityWorld(); + if(world.isRemote) return; + + // Set state of CTRL key + PlayerEntry playerEntry = getEntryFromPlayer(player); + playerEntry.undoActive = ctrlDown; + + LinkedList historyEntries = playerEntry.entries; + Set positions; + + // Send block positions of most recent entry to client + if(historyEntries.isEmpty()) positions = Collections.emptySet(); + else { + HistoryEntry entry = historyEntries.getLast(); + + if(entry == null || !entry.world.equals(world)) positions = Collections.emptySet(); + else positions = entry.getBlockPositions(); + } + + PacketUndoBlocks packet = new PacketUndoBlocks(positions); + ConstructionWand.instance.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), packet); + } + + public boolean isUndoActive(PlayerEntity player) { + return getEntryFromPlayer(player).undoActive; + } + + public boolean undo(PlayerEntity player, World world, BlockPos pos) { + // If CTRL key is not pressed, return + PlayerEntry playerEntry = getEntryFromPlayer(player); + if(!playerEntry.undoActive) return false; + + // Get the most recent entry for undo + LinkedList historyEntries = playerEntry.entries; + if(historyEntries.isEmpty()) return false; + HistoryEntry entry = historyEntries.getLast(); + + if(entry.world.equals(world) && entry.getBlockPositions().contains(pos)) { + // Remove history entry, sent update to client and undo it + historyEntries.remove(entry); + updateClient(player, true); + return entry.undo(player); + } + return false; + } + + private static class PlayerEntry { + public final LinkedList entries; + public boolean undoActive; + + public PlayerEntry() { + entries = new LinkedList<>(); + undoActive = false; + } + } + + private static class HistoryEntry { + public final LinkedList placeSnapshots; + public final World world; + + public HistoryEntry(LinkedList placeSnapshots, World world) { + this.placeSnapshots = placeSnapshots; + this.world = world; + } + + public Set getBlockPositions() { + return placeSnapshots.stream().map(snapshot -> snapshot.pos).collect(Collectors.toSet()); + } + + public boolean undo(PlayerEntity player) { + for(PlaceSnapshot snapshot : placeSnapshots) { + BlockState currentBlock = world.getBlockState(snapshot.pos); + + // If placed block is still present and can be broken, break it and return item + if(world.isBlockModifiable(player, snapshot.pos) && + (player.isCreative() || + (currentBlock.getBlockHardness(world, snapshot.pos) > -1 && world.getTileEntity(snapshot.pos) == null && ReplacementRegistry.matchBlocks(currentBlock.getBlock(), snapshot.block.getBlock())))) + { + BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, snapshot.pos, currentBlock, player); + MinecraftForge.EVENT_BUS.post(breakEvent); + if(breakEvent.isCanceled()) continue; + + world.removeBlock(snapshot.pos, false); + + if(!player.isCreative()) { + ItemStack stack = new ItemStack(snapshot.item); + if(!player.inventory.addItemStackToInventory(stack)) { + player.dropItem(stack, false); + } + } + } + } + player.inventory.markDirty(); + + // Play teleport sound + SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; + world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); + + return true; + } + } +} diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 3ff8236..08e7e06 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -20,6 +20,7 @@ import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.*; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.basics.pool.*; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.ItemWand; @@ -29,24 +30,23 @@ import java.util.stream.Collectors; public abstract class WandJob { - protected PlayerEntity player; - protected World world; - protected BlockRayTraceResult rayTraceResult; + protected final PlayerEntity player; + protected final World world; + protected final BlockRayTraceResult rayTraceResult; protected ItemStack wand; protected ItemWand wandItem; // Wand options protected WandOptions options; protected int maxBlocks; - protected boolean doRandomize; - protected LinkedHashMap itemCounts; - protected HashMap itemWeights; + protected HashMap itemCounts; + protected IPool itemPool; protected LinkedList placeSnapshots; - public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) + protected WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { this.player = player; this.world = world; @@ -61,7 +61,6 @@ public abstract class WandJob // Get options options = new WandOptions(wand); - doRandomize = options.random.get(); // Get place item addBlockItems(); @@ -97,60 +96,53 @@ public abstract class WandJob public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; } - public BlockPos getTargetPos() { return rayTraceResult.getPos(); } - - public PlayerEntity getPlayer() { return player; } - - public void setPlayer(PlayerEntity player) { this.player = player; } - - public World getWorld() { return world; } - - public void setWorld(World world) { this.world = world; } - public ItemStack getWand() { return wand; } private void addBlockItem(BlockItem item) { int count = countItem(item); - if(count > 0) itemCounts.put(item, count); + if(count > 0) { + itemCounts.put(item, count); + itemPool.add(item); + } } private void addBlockItems() { itemCounts = new LinkedHashMap<>(); - itemWeights = new HashMap<>(); BlockPos targetPos = rayTraceResult.getPos(); BlockState targetState = world.getBlockState(targetPos); Block targetBlock = targetState.getBlock(); ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); - if(doRandomize) { + // Random mode -> add all items from hotbar + if(options.random.get()) { + itemPool = new RandomPool<>(player.getRNG()); + for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { - if(stack.getItem() instanceof BlockItem) { - BlockItem item = (BlockItem) stack.getItem(); - addBlockItem(item); - itemWeights.compute(item, (k, v) -> (v == null) ? 1 : v+1); - } + if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); } } - else if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { + else { + itemPool = new OrderedPool<>(); + // Block in offhand -> override - addBlockItem((BlockItem) offhandStack.getItem()); - } + if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { + addBlockItem((BlockItem) offhandStack.getItem()); + } + // Otherwise use target block + else { + Item item = targetBlock.asItem(); + if(item instanceof BlockItem) { + addBlockItem((BlockItem) item); - // Otherwise use target block - if(itemCounts.isEmpty()) { - Item item = targetBlock.asItem(); - if(item instanceof BlockItem) { - addBlockItem((BlockItem) item); - - // Add replacement items - if(options.match.get() != WandOptions.MATCH.EXACT) { - for(Item it : ReplacementRegistry.getMatchingSet(item)) { - if(it instanceof BlockItem) addBlockItem((BlockItem) it); + // Add replacement items + if(options.match.get() != WandOptions.MATCH.EXACT) { + for(Item it : ReplacementRegistry.getMatchingSet(item)) { + if(it instanceof BlockItem) addBlockItem((BlockItem) it); + } } } } - doRandomize = false; } } @@ -229,17 +221,13 @@ public abstract class WandJob // If replace mode is off, target has to be air if(!options.replace.get() && !world.isAirBlock(pos)) return null; - ArrayList items = new ArrayList<>(itemCounts.keySet()); - if(doRandomize) { - for(BlockItem item : itemWeights.keySet()) { - int weight = itemWeights.get(item); - for(int i=0; i 1) ConstructionWand.instance.jobHistory.add(this); + if(placeSnapshots.size() > 1) ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); return !placeSnapshots.isEmpty(); } - - public boolean undo() { - for(PlaceSnapshot snapshot : placeSnapshots) { - BlockState currentBlock = world.getBlockState(snapshot.pos); - - // If placed block is still present and can be broken, break it and return item - if(world.isBlockModifiable(player, snapshot.pos) && - (player.isCreative() || - (currentBlock.getBlockHardness(world, snapshot.pos) > -1 && world.getTileEntity(snapshot.pos) == null && ReplacementRegistry.matchBlocks(currentBlock.getBlock(), snapshot.block.getBlock())))) - { - BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, snapshot.pos, currentBlock, player); - MinecraftForge.EVENT_BUS.post(breakEvent); - if(breakEvent.isCanceled()) continue; - - world.removeBlock(snapshot.pos, false); - - if(!player.isCreative()) { - ItemStack stack = new ItemStack(snapshot.item); - if(!player.inventory.addItemStackToInventory(stack)) { - player.dropItem(stack, false); - } - } - } - } - player.inventory.markDirty(); - - // Play teleport sound - SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; - world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); - - return true; - } } diff --git a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java index 911c4e0..b0b888c 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java +++ b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java @@ -31,7 +31,7 @@ public class PacketQueryUndo ServerPlayerEntity player = ctx.get().getSender(); if(player == null) return; - ConstructionWand.instance.jobHistory.updateClient(player, msg.undoPressed); + ConstructionWand.instance.undoHistory.updateClient(player, msg.undoPressed); //ConstructionWand.LOGGER.debug("Undo queried"); } From 8a558da7ba1f5f12be88b5a7a400545aff0d72e8 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Tue, 29 Sep 2020 22:29:07 +0200 Subject: [PATCH 12/78] Added placement range limit --- .../java/thetadev/constructionwand/basics/ConfigServer.java | 3 +++ .../java/thetadev/constructionwand/basics/WandUtil.java | 4 ++++ src/main/java/thetadev/constructionwand/job/WandJob.java | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 050db68..0e66e0f 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -15,6 +15,7 @@ public class ConfigServer private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); public static final ForgeConfigSpec.IntValue LIMIT_CREATIVE; + public static final ForgeConfigSpec.IntValue MAX_RANGE; public static final ForgeConfigSpec.IntValue UNDO_HISTORY; public static final ForgeConfigSpec.BooleanValue ANGEL_FALLING; @@ -80,6 +81,8 @@ public class ConfigServer BUILDER.push("misc"); BUILDER.comment("Block limit for Infinity Wand used in creative mode"); LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); + BUILDER.comment("Maximum placement range (0: unlimited)"); + MAX_RANGE = BUILDER.defineInRange("MaxRange", 256, 0, Integer.MAX_VALUE); BUILDER.comment("Number of operations that can be undone"); UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 1b824fe..38b0687 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -68,4 +68,8 @@ public class WandUtil inventory.addAll(player.inventory.mainInventory); return inventory; } + + public static int maxRange(BlockPos p1, BlockPos p2) { + return Math.max(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getZ() - p2.getZ())); + } } diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 08e7e06..5053465 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -218,9 +218,15 @@ public abstract class WandJob // Is position out of world? if(!world.isBlockPresent(pos)) return null; + // Is block modifiable? + if(!world.isBlockModifiable(player, pos)) return null; + // If replace mode is off, target has to be air if(!options.replace.get() && !world.isAirBlock(pos)) return null; + // Limit placement range + if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) return null; + itemPool.reset(); while(true) { From 23eb77e18087e57627d4d72a771fc85e179043e2 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Tue, 29 Sep 2020 22:39:27 +0200 Subject: [PATCH 13/78] Changed config desc --- .../java/thetadev/constructionwand/basics/ConfigServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 0e66e0f..eb22814 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -81,7 +81,7 @@ public class ConfigServer BUILDER.push("misc"); BUILDER.comment("Block limit for Infinity Wand used in creative mode"); LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); - BUILDER.comment("Maximum placement range (0: unlimited)"); + BUILDER.comment("Maximum placement range (0: unlimited). Affects all wands and is meant for lag prevention, not game balancing."); MAX_RANGE = BUILDER.defineInRange("MaxRange", 256, 0, Integer.MAX_VALUE); BUILDER.comment("Number of operations that can be undone"); UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); From 94aa1a89e8270f2314ae808fce8a6ff4e0eac1c5 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 4 Oct 2020 23:18:09 +0200 Subject: [PATCH 14/78] Added Hacktoberfest info to readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc211d2..d2ce684 100644 --- a/README.md +++ b/README.md @@ -59,4 +59,9 @@ SHIFT+Right clicking empty space opens the option screen of your wand. - Look at your statisics to see how many blocks you have placed using your wand -- **1.16+ only:** The Infinity Wand won't burn in lava just like netherite gear. \ No newline at end of file +- **1.16+ only:** The Infinity Wand won't burn in lava just like netherite gear. + +## Contributions and #Hacktoberfest +As #Hacktoberfest now requires repo owners to opt in, I added the tag to this repository. + +I'd really appreciate translations. Currently ConstructionWand only has English and German, so if you speak any other language you can help translate the mod and add a new language file under `src/main/resources/assets/constructionwand/lang/`. From 5714f77092930ca66668aa8a48736f39612990cb Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 5 Nov 2020 19:04:36 +0100 Subject: [PATCH 15/78] Updated mod to V1.6 Added MC 1.16.4 support Better direction lock descriptions Removed stray English words in German translation --- .gitignore | 3 ++- gradle.properties | 6 ++--- src/main/resources/META-INF/mods.toml | 8 +++---- .../assets/constructionwand/lang/de_de.json | 22 +++++++++---------- .../assets/constructionwand/lang/en_us.json | 12 +++++----- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index aa70068..faba627 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ run # Files from Forge MDK forge*changelog.txt -.cache \ No newline at end of file +.cache +logs \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index eec0280..d87fa2d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,11 +4,11 @@ org.gradle.daemon=false author=thetadev modid=constructionwand -mcversion=1.16.2 -forgeversion=33.0.60 +mcversion=1.16.4 +forgeversion=35.0.2 mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=1 -version_minor=5 \ No newline at end of file +version_minor=6 \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index cfdd15e..ed75645 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -5,9 +5,9 @@ license="MIT License" modId="constructionwand" version="${file.jarVersion}" displayName="Construction Wand" -displayURL="https://github.com/Theta-Dev/ConstructionWand" #optional -logoFile="logo.png" #optional -authors="ThetaDev" #optional +displayURL="https://github.com/Theta-Dev/ConstructionWand" +logoFile="logo.png" +authors="ThetaDev" description=''' Construction Wands make building easier! @@ -26,6 +26,6 @@ This is my first minecraft mod. May the odds be ever in your favor. [[dependencies.constructionwand]] modId="minecraft" mandatory=true - versionRange="[1.16.2, 1.16.3]" + versionRange="[1.16.2, 1.16.4]" ordering="NONE" side="BOTH" diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index 6743912..cf3f7d2 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -15,13 +15,13 @@ "constructionwand.option.lock": "Beschränkung: ", "constructionwand.option.lock.horizontal": "§aHorizontal", - "constructionwand.option.lock.horizontal.desc": "Erweitert nach §arechts/links§f. Baut nicht auf der Ober/Unterseite", + "constructionwand.option.lock.horizontal.desc": "Baut eine horizontale Säule vor dem Originalblock", "constructionwand.option.lock.vertical": "§aVertikal", - "constructionwand.option.lock.vertical.desc": "Erweitert nach §aoben/unten§f. Baut nicht auf der Ober/Unterseite", - "constructionwand.option.lock.northsouth": "§6North/South", - "constructionwand.option.lock.northsouth.desc": "Erweitert nach §aNorden/Süden§f. Baut nicht an den Seitenflächen", - "constructionwand.option.lock.eastwest": "§6East/West", - "constructionwand.option.lock.eastwest.desc": "Erweitert nach §aOsten/Westen§f. Baut nicht an den Seitenflächen", + "constructionwand.option.lock.vertical.desc": "Baut eine vertikale Säule vor dem Originalblock", + "constructionwand.option.lock.northsouth": "§6Nord/Süd", + "constructionwand.option.lock.northsouth.desc": "Baut eine Reihe in NS-Richtung auf dem Originalblock", + "constructionwand.option.lock.eastwest": "§6Ost/West", + "constructionwand.option.lock.eastwest.desc": "Baut eine Reihe in OW-Richtung auf dem Originalblock", "constructionwand.option.lock.nolock": "§cKeine", "constructionwand.option.lock.nolock.desc": "Erweitert in jede Richtung", @@ -29,7 +29,7 @@ "constructionwand.option.direction.target": "§6Zielblock", "constructionwand.option.direction.target.desc": "Platziert Blöcke mit der selben Ausrichtung wie der Zielblock", "constructionwand.option.direction.player": "§aSpieler", - "constructionwand.option.direction.player.desc": "Place blocks facing the player", + "constructionwand.option.direction.player.desc": "Platziert Blöcke in der Richtung, auf die der Spieler zeigt", "constructionwand.option.replace": "Ersetzen: ", "constructionwand.option.replace.yes": "§aJa", @@ -45,11 +45,11 @@ "constructionwand.option.match.any": "§cAlle", "constructionwand.option.match.any.desc": "Erweitert alle Blöcke", - "constructionwand.option.random": "Zufall: ", - "constructionwand.option.random.yes": "§aJa", + "constructionwand.option.random": "Zufallsmodus: ", + "constructionwand.option.random.yes": "§aEin", "constructionwand.option.random.yes.desc": "Platziere zufällige Blöcke aus der Hotbar", - "constructionwand.option.random.no": "§cNein", - "constructionwand.option.random.no.desc": "Platziere Blöcke nicht zufällig", + "constructionwand.option.random.no": "§cAus", + "constructionwand.option.random.no.desc": "Platziere Blöcke normal", "stat.constructionwand.use_wand": "Blöcke mithilfe des Stabs platziert" } \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 5415b4e..b169c1b 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -14,14 +14,14 @@ "constructionwand.option.mode.angel.desc": "Place behind blocks and in mid air", "constructionwand.option.lock": "Restriction: ", - "constructionwand.option.lock.horizontal": "§aHorizontal", - "constructionwand.option.lock.horizontal.desc": "Extend §aleft/right§f from the original block. No building on top/bottom face", - "constructionwand.option.lock.vertical": "§aVertical", - "constructionwand.option.lock.vertical.desc": "Extend §aup/down§f from the original block. No building on top/bottom face", + "constructionwand.option.lock.horizontal": "§aLeft/Right", + "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in fromt of the original block", + "constructionwand.option.lock.vertical": "§aUp/Down", + "constructionwand.option.lock.vertical.desc": "Build a vertical column in front of the original block", "constructionwand.option.lock.northsouth": "§6North/South", - "constructionwand.option.lock.northsouth.desc": "Extend §anorth/south§f on top of the original block. No building on side faces", + "constructionwand.option.lock.northsouth.desc": "Build a row in N/S direction on top of the original block", "constructionwand.option.lock.eastwest": "§6East/West", - "constructionwand.option.lock.eastwest.desc": "Extend §aeast/west§f on top of the original block. No building on side faces", + "constructionwand.option.lock.eastwest.desc": "Build a row in E/W direction on top of the original block", "constructionwand.option.lock.nolock": "§cNone", "constructionwand.option.lock.nolock.desc": "Extend from any side of the original block", From 224c0350b39b4882bf1ee8a97156d2802608808e Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 6 Nov 2020 15:59:26 +0100 Subject: [PATCH 16/78] Fixed typo --- src/main/resources/assets/constructionwand/lang/en_us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index b169c1b..ff6ce4e 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -15,7 +15,7 @@ "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", - "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in fromt of the original block", + "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in front of the original block", "constructionwand.option.lock.vertical": "§aUp/Down", "constructionwand.option.lock.vertical.desc": "Build a vertical column in front of the original block", "constructionwand.option.lock.northsouth": "§6North/South", From ea20d44d614d72a4394f746c1b729639e85f5dc7 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 6 Nov 2020 17:01:12 +0100 Subject: [PATCH 17/78] Set MC version to 1.16.2 --- gradle.properties | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index d87fa2d..3d54ea8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,10 @@ org.gradle.daemon=false author=thetadev modid=constructionwand -mcversion=1.16.4 -forgeversion=35.0.2 +#mcversion=1.16.4 +#forgeversion=35.0.2 +mcversion=1.16.2 +forgeversion=33.0.60 mcp_mappings=20200723-1.16.1 botania=1.16.2-405 From a7dbdcec0d2cca9de13a04f1c65e3a4f6d514772 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 6 Dec 2020 17:42:39 +0100 Subject: [PATCH 18/78] Added TE White/Blacklist Now triggers onBlockPlacedBy() Fixed SecurityCraft bug --- gradle.properties | 2 +- .../constructionwand/basics/ConfigClient.java | 7 +++++++ .../constructionwand/basics/ConfigServer.java | 20 ++++++++++++++++++- .../constructionwand/basics/WandUtil.java | 16 +++++++++++++++ .../constructionwand/client/ClientEvents.java | 3 ++- .../constructionwand/items/ItemWandBasic.java | 5 ----- .../items/ItemWandInfinity.java | 1 - .../constructionwand/items/ModItems.java | 2 -- .../constructionwand/job/WandJob.java | 6 ++++++ .../network/PacketUndoBlocks.java | 1 - 10 files changed, 51 insertions(+), 12 deletions(-) diff --git a/gradle.properties b/gradle.properties index 3d54ea8..a9479c6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,4 +13,4 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=1 -version_minor=6 \ No newline at end of file +version_minor=7 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java index 60fd305..621f042 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -10,6 +10,13 @@ public class ConfigClient public static final ForgeConfigSpec.BooleanValue SHIFTCTRL_GUI; static { + BUILDER.comment("This is the Client config for ConstructionWand.", + "If you're not familiar with Forge's new split client/server config, let me explain:", + "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", + "Mod behavior is configured in the Server config, which is world-specific and thus located", + "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", + "new worlds, copy the config files in the /defaultconfigs folder."); + BUILDER.push("keys"); BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for changing wand mode/direction lock"); SHIFTCTRL_MODE = BUILDER.define("ShiftCtrl", false); diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index eb22814..003a265 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -3,7 +3,6 @@ package thetadev.constructionwand.basics; import net.minecraft.item.Item; import net.minecraft.item.ItemTier; import net.minecraftforge.common.ForgeConfigSpec; -import thetadev.constructionwand.items.ItemWand; import thetadev.constructionwand.items.ModItems; import java.util.Arrays; @@ -24,6 +23,10 @@ public class ConfigServer "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:grass_path" }; + public static final ForgeConfigSpec.BooleanValue TE_WHITELIST; + public static final ForgeConfigSpec.ConfigValue> TE_LIST; + private static final String[] TE_LIST_DEFAULT = {"chiselsandbits"}; + private static final HashMap wandProperties = new HashMap<>(); public static WandProperties getWandProperties(Item wand) { @@ -73,6 +76,13 @@ public class ConfigServer } static { + BUILDER.comment("This is the Server config for ConstructionWand.", + "If you're not familiar with Forge's new split client/server config, let me explain:", + "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", + "Mod behavior is configured in the Server config, which is world-specific and thus located", + "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", + "new worlds, copy the config files in the /defaultconfigs folder."); + new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0); new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 1); new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 4); @@ -90,6 +100,14 @@ public class ConfigServer BUILDER.comment("Blocks to treat equally when in Similar mode. Enter block IDs seperated by ;"); SIMILAR_BLOCKS = BUILDER.defineList("SimilarBlocks", Arrays.asList(SIMILAR_BLOCKS_DEFAULT), obj -> true); BUILDER.pop(); + + BUILDER.push("tileentity"); + BUILDER.comment("White/Blacklist for Tile Entities. Allow/Prevent blocks with TEs from being placed by wand.", + "You can either add block ids like minecraft:chest or mod ids like minecraft"); + TE_LIST = BUILDER.defineList("TEList", Arrays.asList(TE_LIST_DEFAULT), obj -> true); + BUILDER.comment("If set to TRUE, treat TEList as a whitelist, otherwise blacklist"); + TE_WHITELIST = BUILDER.define("TEWhitelist", false); + BUILDER.pop(); } public static final ForgeConfigSpec SPEC = BUILDER.build(); diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 38b0687..f8a435f 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -1,5 +1,6 @@ package thetadev.constructionwand.basics; +import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; @@ -72,4 +73,19 @@ public class WandUtil public static int maxRange(BlockPos p1, BlockPos p2) { return Math.max(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getZ() - p2.getZ())); } + + public static boolean isTEAllowed(BlockState state) { + if(!state.hasTileEntity()) return true; + + ResourceLocation name = state.getBlock().getRegistryName(); + if(name == null) return false; + + String fullId = name.toString(); + String modId = name.getNamespace(); + + boolean inList = ConfigServer.TE_LIST.get().contains(fullId) || ConfigServer.TE_LIST.get().contains(modId); + boolean isWhitelist = ConfigServer.TE_WHITELIST.get(); + + return isWhitelist == inList; + } } diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 5e125aa..2d1facf 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -11,7 +11,8 @@ import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.*; +import thetadev.constructionwand.basics.ConfigClient; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.items.ItemWand; import thetadev.constructionwand.network.PacketQueryUndo; diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java index 3186834..ff5d3dd 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java @@ -2,12 +2,7 @@ package thetadev.constructionwand.items; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.IItemTier; -import net.minecraft.item.Item; -import net.minecraft.item.Item.Properties; import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.Ingredient; -import net.minecraft.tags.ItemTags; -import net.minecraft.util.ResourceLocation; import thetadev.constructionwand.basics.ConfigServer; public class ItemWandBasic extends ItemWand diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java index ae9e2aa..2af6ec9 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java @@ -1,7 +1,6 @@ package thetadev.constructionwand.items; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import thetadev.constructionwand.basics.ConfigServer; diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index f043b7a..0f85359 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -7,8 +7,6 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.registries.IForgeRegistryEntry; -import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.ConstructionWand; @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 5053465..3686435 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -247,6 +247,9 @@ public abstract class WandJob blockState = Block.getValidBlockForPosition(blockState, world, pos); if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; + // Forbidden Tile Entity? + if(!WandUtil.isTEAllowed(blockState)) continue; + // No entities colliding? VoxelShape shape = blockState.getCollisionShape(world, pos); if(!shape.isEmpty()) { @@ -303,6 +306,9 @@ public abstract class WandJob return false; } + // Call OnBlockPlaced method + placeBlock.getBlock().onBlockPlacedBy(world, blockPos, placeBlock, player, new ItemStack(placeSnapshot.item)); + // Update stats player.addStat(Stats.ITEM_USED.get(placeSnapshot.item)); player.addStat(ModStats.USE_WAND); diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index cab030e..5d535f0 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -6,7 +6,6 @@ import net.minecraftforge.fml.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; import java.util.HashSet; -import java.util.LinkedList; import java.util.Set; import java.util.function.Supplier; From 933fbea3821f428e1fd7021dd6733eb88c03f2c8 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 6 Dec 2020 17:42:39 +0100 Subject: [PATCH 19/78] Added TE White/Blacklist Now triggers onBlockPlacedBy() Fixed SecurityCraft bug --- README.md | 11 ++++++++++ gradle.properties | 2 +- .../constructionwand/basics/ConfigClient.java | 7 +++++++ .../constructionwand/basics/ConfigServer.java | 20 ++++++++++++++++++- .../constructionwand/basics/WandUtil.java | 16 +++++++++++++++ .../constructionwand/client/ClientEvents.java | 3 ++- .../constructionwand/items/ItemWandBasic.java | 5 ----- .../items/ItemWandInfinity.java | 1 - .../constructionwand/items/ModItems.java | 2 -- .../constructionwand/job/WandJob.java | 6 ++++++ .../network/PacketUndoBlocks.java | 1 - 11 files changed, 62 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d2ce684..891c507 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,14 @@ SHIFT+Right clicking empty space opens the option screen of your wand. As #Hacktoberfest now requires repo owners to opt in, I added the tag to this repository. I'd really appreciate translations. Currently ConstructionWand only has English and German, so if you speak any other language you can help translate the mod and add a new language file under `src/main/resources/assets/constructionwand/lang/`. + +## TileEntity Blacklist +Some modded TileEntitys can cause issues when placed using a wand. They may turn into invisible and unremovable ghost blocks, +become unbreakable or cause other unwanted effects. + +That's why I've included a Black/Whitelist system +for TileEntities in CW Version 1.7. Chisels&Bits blocks are blacklisted by default. There are probably a few other tile entities +from other mods out there which may cause issues as well. If you find some of them you can tell me by creating +an issue, commenting on Curse or editing the default blacklist yourself +(it is located at https://github.com/Theta-Dev/ConstructionWand/blob/1.16.2/src/main/java/thetadev/constructionwand/basics/ConfigServer.java#L28) +and making a PR. diff --git a/gradle.properties b/gradle.properties index 3d54ea8..a9479c6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,4 +13,4 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=1 -version_minor=6 \ No newline at end of file +version_minor=7 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java index 60fd305..621f042 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -10,6 +10,13 @@ public class ConfigClient public static final ForgeConfigSpec.BooleanValue SHIFTCTRL_GUI; static { + BUILDER.comment("This is the Client config for ConstructionWand.", + "If you're not familiar with Forge's new split client/server config, let me explain:", + "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", + "Mod behavior is configured in the Server config, which is world-specific and thus located", + "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", + "new worlds, copy the config files in the /defaultconfigs folder."); + BUILDER.push("keys"); BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for changing wand mode/direction lock"); SHIFTCTRL_MODE = BUILDER.define("ShiftCtrl", false); diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index eb22814..003a265 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -3,7 +3,6 @@ package thetadev.constructionwand.basics; import net.minecraft.item.Item; import net.minecraft.item.ItemTier; import net.minecraftforge.common.ForgeConfigSpec; -import thetadev.constructionwand.items.ItemWand; import thetadev.constructionwand.items.ModItems; import java.util.Arrays; @@ -24,6 +23,10 @@ public class ConfigServer "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:grass_path" }; + public static final ForgeConfigSpec.BooleanValue TE_WHITELIST; + public static final ForgeConfigSpec.ConfigValue> TE_LIST; + private static final String[] TE_LIST_DEFAULT = {"chiselsandbits"}; + private static final HashMap wandProperties = new HashMap<>(); public static WandProperties getWandProperties(Item wand) { @@ -73,6 +76,13 @@ public class ConfigServer } static { + BUILDER.comment("This is the Server config for ConstructionWand.", + "If you're not familiar with Forge's new split client/server config, let me explain:", + "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", + "Mod behavior is configured in the Server config, which is world-specific and thus located", + "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", + "new worlds, copy the config files in the /defaultconfigs folder."); + new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0); new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 1); new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 4); @@ -90,6 +100,14 @@ public class ConfigServer BUILDER.comment("Blocks to treat equally when in Similar mode. Enter block IDs seperated by ;"); SIMILAR_BLOCKS = BUILDER.defineList("SimilarBlocks", Arrays.asList(SIMILAR_BLOCKS_DEFAULT), obj -> true); BUILDER.pop(); + + BUILDER.push("tileentity"); + BUILDER.comment("White/Blacklist for Tile Entities. Allow/Prevent blocks with TEs from being placed by wand.", + "You can either add block ids like minecraft:chest or mod ids like minecraft"); + TE_LIST = BUILDER.defineList("TEList", Arrays.asList(TE_LIST_DEFAULT), obj -> true); + BUILDER.comment("If set to TRUE, treat TEList as a whitelist, otherwise blacklist"); + TE_WHITELIST = BUILDER.define("TEWhitelist", false); + BUILDER.pop(); } public static final ForgeConfigSpec SPEC = BUILDER.build(); diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 38b0687..f8a435f 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -1,5 +1,6 @@ package thetadev.constructionwand.basics; +import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; @@ -72,4 +73,19 @@ public class WandUtil public static int maxRange(BlockPos p1, BlockPos p2) { return Math.max(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getZ() - p2.getZ())); } + + public static boolean isTEAllowed(BlockState state) { + if(!state.hasTileEntity()) return true; + + ResourceLocation name = state.getBlock().getRegistryName(); + if(name == null) return false; + + String fullId = name.toString(); + String modId = name.getNamespace(); + + boolean inList = ConfigServer.TE_LIST.get().contains(fullId) || ConfigServer.TE_LIST.get().contains(modId); + boolean isWhitelist = ConfigServer.TE_WHITELIST.get(); + + return isWhitelist == inList; + } } diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 5e125aa..2d1facf 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -11,7 +11,8 @@ import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.*; +import thetadev.constructionwand.basics.ConfigClient; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.items.ItemWand; import thetadev.constructionwand.network.PacketQueryUndo; diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java index 3186834..ff5d3dd 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java @@ -2,12 +2,7 @@ package thetadev.constructionwand.items; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.IItemTier; -import net.minecraft.item.Item; -import net.minecraft.item.Item.Properties; import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.Ingredient; -import net.minecraft.tags.ItemTags; -import net.minecraft.util.ResourceLocation; import thetadev.constructionwand.basics.ConfigServer; public class ItemWandBasic extends ItemWand diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java index ae9e2aa..2af6ec9 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java @@ -1,7 +1,6 @@ package thetadev.constructionwand.items; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import thetadev.constructionwand.basics.ConfigServer; diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index f043b7a..0f85359 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -7,8 +7,6 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.registries.IForgeRegistryEntry; -import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.ConstructionWand; @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 5053465..3686435 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -247,6 +247,9 @@ public abstract class WandJob blockState = Block.getValidBlockForPosition(blockState, world, pos); if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; + // Forbidden Tile Entity? + if(!WandUtil.isTEAllowed(blockState)) continue; + // No entities colliding? VoxelShape shape = blockState.getCollisionShape(world, pos); if(!shape.isEmpty()) { @@ -303,6 +306,9 @@ public abstract class WandJob return false; } + // Call OnBlockPlaced method + placeBlock.getBlock().onBlockPlacedBy(world, blockPos, placeBlock, player, new ItemStack(placeSnapshot.item)); + // Update stats player.addStat(Stats.ITEM_USED.get(placeSnapshot.item)); player.addStat(ModStats.USE_WAND); diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index cab030e..5d535f0 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -6,7 +6,6 @@ import net.minecraftforge.fml.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; import java.util.HashSet; -import java.util.LinkedList; import java.util.Set; import java.util.function.Supplier; From a90ced6395124cfa9a0f5dc3c9bb47167ae0b0e7 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 5 Feb 2021 16:41:13 +0100 Subject: [PATCH 20/78] OptionKey can be changed in config --- LICENSE | 21 +++ README.md | 32 ++++- gradle.properties | 10 +- .../constructionwand/ConstructionWand.java | 2 + .../constructionwand/basics/ConfigClient.java | 15 ++- .../constructionwand/client/ClientEvents.java | 122 ++++++++++-------- .../client/RenderBlockPreview.java | 2 +- src/main/resources/META-INF/mods.toml | 2 +- 8 files changed, 139 insertions(+), 67 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b33b4c3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2021 ThetaDev +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 891c507..ff04050 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,40 @@ Wand properties can be changed in the config. ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/crafting4.png) ## Modes -**Default mode:** Extends your build on the side facing you. Maximum number of blocks depends on wand tier. SHIFT+scroll to change the placement mode (Horizontal, Vertical, North/South, East/West, No lock). +**Default mode:** Extends your build on the side facing you. Maximum number of blocks depends on wand tier. +SHIFT+scroll to change the placement mode (Horizontal, Vertical, North/South, East/West, No lock). -**Angel mode:** Places a block on the opposite side of the block (or row of blocks) you are facing. Maximum distance depends on wand tier. Right click empty space to place a block in midair (similar to angel blocks, hence the name). To do that, you'll need to have the block you want to place in your offhand. You can't place a block in midair if you've fallen more than 10 blocks deep (no easy rescue tool from falling into the void). +**Angel mode:** Places a block on the opposite side of the block (or row of blocks) you are facing. +Maximum distance depends on wand tier. Right click empty space to place a block in midair (similar to angel blocks, +hence the name). To do that, you'll need to have the block you want to place in your offhand. +You can't place a block in midair if you've fallen more than 10 blocks deep +(no easy rescue tool from falling into the void). You can change the wand mode using the option screen or by SHIFT+Left clicking empty space. +## Wand cores +Make your wand even better by putting aditional wand cores into it. + +- New modes: + - **Angel core** + + Places a block on the opposite side of the block (or row of blocks) you are facing. Maximum distance depends + on wand tier. Right click empty space to place a block in midair (similar to angel blocks, hence the name). + To do that, you'll need to have the block you want to place in your offhand. You can't place a block in midair + if you've fallen more than 10 blocks deep (no easy rescue tool from falling into the void). + + - **Destruction core** + + Point the wand at some blocks to destroy them. + +- Upgrades + - **Conjuration core** + + If the wand runs out of blocks, it conjures them from thin air. Conjured blocks are nearly transparent and + are very easy to break. Right click them with any block to replace them. + You can force the placement of conjured blocks by holding a feather in your offhand. + + ## Options SHIFT+Right clicking empty space opens the option screen of your wand. diff --git a/gradle.properties b/gradle.properties index a9479c6..568f50b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,13 +4,13 @@ org.gradle.daemon=false author=thetadev modid=constructionwand -#mcversion=1.16.4 -#forgeversion=35.0.2 -mcversion=1.16.2 -forgeversion=33.0.60 +mcversion=1.16.5 +forgeversion=36.0.13 +#mcversion=1.16.2 +#forgeversion=33.0.60 mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=1 -version_minor=7 \ No newline at end of file +version_minor=8 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index e8db1fe..dd3b326 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -16,6 +16,7 @@ import thetadev.constructionwand.basics.ConfigClient; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.ReplacementRegistry; +import thetadev.constructionwand.client.ClientEvents; import thetadev.constructionwand.client.RenderBlockPreview; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.containers.ContainerRegistrar; @@ -80,6 +81,7 @@ public class ConstructionWand { renderBlockPreview = new RenderBlockPreview(); MinecraftForge.EVENT_BUS.register(renderBlockPreview); + MinecraftForge.EVENT_BUS.register(new ClientEvents()); ModItems.registerModelProperties(); } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java index 621f042..7c18176 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -6,8 +6,9 @@ public class ConfigClient { private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - public static final ForgeConfigSpec.BooleanValue SHIFTCTRL_MODE; - public static final ForgeConfigSpec.BooleanValue SHIFTCTRL_GUI; + public static final ForgeConfigSpec.IntValue OPT_KEY; + public static final ForgeConfigSpec.BooleanValue SHIFTOPT_MODE; + public static final ForgeConfigSpec.BooleanValue SHIFTOPT_GUI; static { BUILDER.comment("This is the Client config for ConstructionWand.", @@ -18,10 +19,12 @@ public class ConfigClient "new worlds, copy the config files in the /defaultconfigs folder."); BUILDER.push("keys"); - BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for changing wand mode/direction lock"); - SHIFTCTRL_MODE = BUILDER.define("ShiftCtrl", false); - BUILDER.comment("Press SHIFT+CTRL instead of SHIFT for opening wand GUI"); - SHIFTCTRL_GUI = BUILDER.define("ShiftCtrlGUI", false); + BUILDER.comment("Key code of OPTKEY (Default: Left Control). Look up key codes under https://www.glfw.org/docs/3.3/group__keys.html"); + OPT_KEY = BUILDER.defineInRange("OptKey", 341, 0, 350); + BUILDER.comment("Press SNEAK+OPTKEY instead of SNEAK for changing wand mode/direction lock"); + SHIFTOPT_MODE = BUILDER.define("ShiftOpt", false); + BUILDER.comment("Press SNEAK+OPTKEY instead of SNEAK for opening wand GUI"); + SHIFTOPT_GUI = BUILDER.define("ShiftOptGUI", true); BUILDER.pop(); } diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 2d1facf..e933d8b 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -1,15 +1,13 @@ package thetadev.constructionwand.client; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.InputMappings; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; -import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.InputEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigClient; import thetadev.constructionwand.basics.WandUtil; @@ -18,68 +16,88 @@ import thetadev.constructionwand.items.ItemWand; import thetadev.constructionwand.network.PacketQueryUndo; import thetadev.constructionwand.network.PacketWandOption; -@Mod.EventBusSubscriber(value = Dist.CLIENT) public class ClientEvents { - private static boolean ctrlPressed = false; + private boolean optPressed; - @SubscribeEvent - public static void KeyEvent(InputEvent.KeyInputEvent event) { - PlayerEntity player = Minecraft.getInstance().player; - if(player == null) return; - if(WandUtil.holdingWand(player) == null) return; + public ClientEvents() { + optPressed = false; + } - boolean ctrlState = Screen.hasControlDown(); - if(ctrlPressed != ctrlState) { - ctrlPressed = ctrlState; - PacketQueryUndo packet = new PacketQueryUndo(ctrlPressed); - ConstructionWand.instance.HANDLER.sendToServer(packet); - //ConstructionWand.LOGGER.debug("CTRL key update: "+ctrlPressed); - } - } + // Send state of OPT key to server + @SubscribeEvent + public void KeyEvent(InputEvent.KeyInputEvent event) { + PlayerEntity player = Minecraft.getInstance().player; + if(player == null) return; + if(WandUtil.holdingWand(player) == null) return; - // SHIFT+(CTRL)+Scroll to change direction lock - @SubscribeEvent(priority = EventPriority.HIGHEST) - public static void MouseScrollEvent(InputEvent.MouseScrollEvent event) { - PlayerEntity player = Minecraft.getInstance().player; - double scroll = event.getScrollDelta(); + boolean optState = isOptKeyDown(); + if(optPressed != optState) { + optPressed = optState; + PacketQueryUndo packet = new PacketQueryUndo(optPressed); + ConstructionWand.instance.HANDLER.sendToServer(packet); + ConstructionWand.LOGGER.debug("OPT key update: " + optPressed); + } + } - if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL_MODE.get()) || scroll == 0) return; + // Sneak+(OPT)+Scroll to change direction lock + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void MouseScrollEvent(InputEvent.MouseScrollEvent event) { + PlayerEntity player = Minecraft.getInstance().player; + double scroll = event.getScrollDelta(); - ItemStack wand = WandUtil.holdingWand(player); - if(wand == null) return; + if(player == null || !modeKeyCombDown(player) || scroll == 0) return; - WandOptions wandOptions = new WandOptions(wand); - wandOptions.lock.next(scroll<0); - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.lock, true)); - event.setCanceled(true); - } + ItemStack wand = WandUtil.holdingWand(player); + if(wand == null) return; - // SHIFT+(CTRL)+Left click wand to change mode - @SubscribeEvent - public static void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { - PlayerEntity player = event.getPlayer(); + WandOptions wandOptions = new WandOptions(wand); + wandOptions.lock.next(scroll < 0); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.lock, true)); + event.setCanceled(true); + } - if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL_MODE.get())) return; + // Sneak+(OPT)+Left click wand to change mode + @SubscribeEvent + public void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { + PlayerEntity player = event.getPlayer(); - ItemStack wand = event.getItemStack(); - if(!(wand.getItem() instanceof ItemWand)) return; + if(player == null || !modeKeyCombDown(player)) return; - WandOptions wandOptions = new WandOptions(wand); - wandOptions.mode.next(); - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.mode, true)); - } + ItemStack wand = event.getItemStack(); + if(!(wand.getItem() instanceof ItemWand)) return; - // SHIFT+Right click wand to open GUI - @SubscribeEvent - public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) { - PlayerEntity player = event.getPlayer(); - if(player == null || !player.isSneaking() || (!Screen.hasControlDown() && ConfigClient.SHIFTCTRL_GUI.get())) return; + WandOptions wandOptions = new WandOptions(wand); + wandOptions.mode.next(); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.mode, true)); + } - ItemStack wand = event.getItemStack(); - if(!(wand.getItem() instanceof ItemWand)) return; + // Sneak+(OPT)+Right click wand to open GUI + @SubscribeEvent + public void onRightClickItem(PlayerInteractEvent.RightClickItem event) { + PlayerEntity player = event.getPlayer(); + if(player == null || !guiKeyCombDown(player)) return; - Minecraft.getInstance().displayGuiScreen(new ScreenWand(wand)); - event.setCanceled(true); - } + ItemStack wand = event.getItemStack(); + if(!(wand.getItem() instanceof ItemWand)) return; + + Minecraft.getInstance().displayGuiScreen(new ScreenWand(wand)); + event.setCanceled(true); + } + + private static boolean isKeyDown(int id) { + return InputMappings.isKeyDown(Minecraft.getInstance().getMainWindow().getHandle(), id); + } + + public static boolean isOptKeyDown() { + return isKeyDown(ConfigClient.OPT_KEY.get()); + } + + public static boolean modeKeyCombDown(PlayerEntity player) { + return player.isSneaking() && (isOptKeyDown() || !ConfigClient.SHIFTOPT_MODE.get()); + } + + public static boolean guiKeyCombDown(PlayerEntity player) { + return player.isSneaking() && (isOptKeyDown() || !ConfigClient.SHIFTOPT_GUI.get()); + } } diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index 7932815..22aa689 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -40,7 +40,7 @@ public class RenderBlockPreview ItemStack wand = WandUtil.holdingWand(player); if(wand == null) return; - if(!(player.isSneaking() && Screen.hasControlDown())) { + if(!(player.isSneaking() && ClientEvents.isOptKeyDown())) { if(wandJob == null || !(wandJob.getRayTraceResult().equals(rtr)) || !(wandJob.getWand().equals(wand))) { wandJob = WandJob.getJob(player, player.getEntityWorld(), rtr, wand); } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index ed75645..e3238ee 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -26,6 +26,6 @@ This is my first minecraft mod. May the odds be ever in your favor. [[dependencies.constructionwand]] modId="minecraft" mandatory=true - versionRange="[1.16.2, 1.16.4]" + versionRange="[1.16.2, 1.16.5]" ordering="NONE" side="BOTH" From b914deb7cf6f450a4c8f3f6d41b182cb371acef6 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 6 Feb 2021 00:37:03 +0100 Subject: [PATCH 21/78] Reworked snapshot/undo system. Added DestroySnapshot --- images/textures/core_overlay.png | Bin 0 -> 613 bytes images/textures/diamond_wand.xcf | Bin 4343 -> 4654 bytes .../constructionwand/ConstructionWand.java | 1 + .../constructionwand/basics/WandUtil.java | 52 ++++++++ .../constructionwand/items/ModItems.java | 17 ++- .../constructionwand/job/DestroySnapshot.java | 49 ++++++++ .../constructionwand/job/ISnapshot.java | 18 +++ .../constructionwand/job/PlaceSnapshot.java | 54 +++++++-- .../constructionwand/job/UndoHistory.java | 31 ++--- .../constructionwand/job/WandJob.java | 113 ++++++------------ .../models/item/diamond_wand.json | 4 +- ...wand_angel.json => diamond_wand_core.json} | 2 +- .../models/item/infinity_wand.json | 4 +- ...and_angel.json => infinity_wand_core.json} | 2 +- .../models/item/iron_wand.json | 4 +- ...on_wand_angel.json => iron_wand_core.json} | 2 +- .../models/item/stone_wand.json | 4 +- ...e_wand_angel.json => stone_wand_core.json} | 2 +- .../textures/items/overlay_core.png | Bin 0 -> 613 bytes 19 files changed, 238 insertions(+), 121 deletions(-) create mode 100644 images/textures/core_overlay.png create mode 100644 src/main/java/thetadev/constructionwand/job/DestroySnapshot.java create mode 100644 src/main/java/thetadev/constructionwand/job/ISnapshot.java rename src/main/resources/assets/constructionwand/models/item/{diamond_wand_angel.json => diamond_wand_core.json} (67%) rename src/main/resources/assets/constructionwand/models/item/{infinity_wand_angel.json => infinity_wand_core.json} (66%) rename src/main/resources/assets/constructionwand/models/item/{iron_wand_angel.json => iron_wand_core.json} (66%) rename src/main/resources/assets/constructionwand/models/item/{stone_wand_angel.json => stone_wand_core.json} (66%) create mode 100644 src/main/resources/assets/constructionwand/textures/items/overlay_core.png diff --git a/images/textures/core_overlay.png b/images/textures/core_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..b70e74586d12f7217065bea419fd06b4220ba7a9 GIT binary patch literal 613 zcmV-r0-F7aP)EX>4Tx04R}tkv&MmKpe$iQ;Q-MK|828WT;LSL`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT3N2zhIPS;0dyl(!fKV?p&FUBjG~G5+ ziMW`_u8N(n5D-EHeHfIPWz0!Z629Z>9s$1I#dwzgxj#pbnzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)O(glehxvqHp#<}3Kz%wIeIyFxmAr=d5th6yJni}yGaa7fG$`>*o ztDLtuYvn3y-jlyDoYPm9xlVH!2`pj>5=1DdqJ%PR#Aww?v5=zuxQ~Cx^-JVZ$W;O( z#{w$QAiI9>Klt6Pm7kpOlEQHy^x`-lBS7aa(5N}i_p#$NPJrMuaHY5Wl{zr8+LMpN$mid00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{002EnL_t(I%VS^|S>Wi=qyPW^|NkFZjEPvyYuB#*&&S8d zaO~JI2C@wJ^yw4B-Me=gtgWpX8Aib<7<2#tk`WT{QFy0}00000NkvXXu0mjfhSUfq literal 0 HcmV?d00001 diff --git a/images/textures/diamond_wand.xcf b/images/textures/diamond_wand.xcf index 0281a91bff28a463c972ee463b196031db2b84cf..297d55e0a08055bab599a89e35971f43920dbe58 100644 GIT binary patch delta 651 zcmeyaxK3q)lc+fZ1A`b4vj8z85Q_rw=ZWF2Oblx#&QozzFaSbBO-(M1r1a$coctmM z4KSr!l%ABRU5Afl#N^DpbOl2LAg>b6o9xM`%ncIBPfIIKEm4@<%BZ0^31|rr zFv~+|mZcDyO$9=8+e2splgZ~81^NUg^e`b833vw(HV49D_5dgA57~~NkVA?&2 zS@ zl7k7SXNotWoZesCdL4M!K(M&v(b2**FxLzV@1VtkdaI=Cma?5VM%PG$&$72T( z<(CA~XjbqdX zW`hXxJ_OP*rBExlmB325tu|-z>|)~OP0BAyL9%0(7~fPRO;GdsHi9+ro!tC`-++;c d|Mz4qK`AK#afmfwk`qXRFc1jnPmUA3001y4XNUj* diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index dd3b326..ae183f8 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -83,6 +83,7 @@ public class ConstructionWand MinecraftForge.EVENT_BUS.register(renderBlockPreview); MinecraftForge.EVENT_BUS.register(new ClientEvents()); ModItems.registerModelProperties(); + ModItems.registerItemColors(); } public static ResourceLocation loc(String name) { diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index f8a435f..4a538ff 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -3,12 +3,18 @@ package thetadev.constructionwand.basics; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.stats.Stats; import net.minecraft.util.Hand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.BlockSnapshot; +import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.items.ItemWand; @@ -88,4 +94,50 @@ public class WandUtil return isWhitelist == inList; } + + public static boolean placeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos, BlockItem item) { + if(!world.setBlockState(pos, block)) { + ConstructionWand.LOGGER.info("Block could not be placed"); + return false; + } + + // Remove block if placeEvent is canceled + BlockSnapshot snapshot = BlockSnapshot.create(world.func_234923_W_(), world, pos); + BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, block, player); + MinecraftForge.EVENT_BUS.post(placeEvent); + if(placeEvent.isCanceled()) { + world.removeBlock(pos, false); + return false; + } + + ItemStack stack; + if(item == null) stack = new ItemStack(block.getBlock().asItem()); + else { + stack = new ItemStack(item); + player.addStat(Stats.ITEM_USED.get(item)); + } + + // Call OnBlockPlaced method + block.getBlock().onBlockPlacedBy(world, pos, block, player, stack); + + return true; + } + + public static boolean removeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos) { + BlockState currentBlock = world.getBlockState(pos); + + if(world.isBlockModifiable(player, pos) && + (player.isCreative() || + (currentBlock.getBlockHardness(world, pos) > -1 && world.getTileEntity(pos) == null && + ReplacementRegistry.matchBlocks(currentBlock.getBlock(), block.getBlock())))) { + + BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, pos, currentBlock, player); + MinecraftForge.EVENT_BUS.post(breakEvent); + if(breakEvent.isCanceled()) return false; + + world.removeBlock(pos, false); + return true; + } + return false; + } } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 0f85359..2f92241 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -1,9 +1,12 @@ package thetadev.constructionwand.items; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.color.ItemColors; import net.minecraft.item.Item; import net.minecraft.item.ItemModelsProperties; import net.minecraft.item.ItemTier; -import net.minecraft.util.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -25,12 +28,22 @@ public class ModItems event.getRegistry().registerAll(WANDS); } + @OnlyIn(Dist.CLIENT) public static void registerModelProperties() { for(Item item : WANDS) { ItemModelsProperties.func_239418_a_( - item, new ResourceLocation(ConstructionWand.MODID, "wand_mode"), + item, ConstructionWand.loc("using_core"), (stack, world, entity) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : ItemWand.getWandMode(stack) ); } } + + @OnlyIn(Dist.CLIENT) + public static void registerItemColors() { + ItemColors colors = Minecraft.getInstance().getItemColors(); + + for(Item item : WANDS) { + colors.register((stack, layer) -> layer == 1 ? 0xFF0000 : -1, item); + } + } } diff --git a/src/main/java/thetadev/constructionwand/job/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/job/DestroySnapshot.java new file mode 100644 index 0000000..aa27b2b --- /dev/null +++ b/src/main/java/thetadev/constructionwand/job/DestroySnapshot.java @@ -0,0 +1,49 @@ +package thetadev.constructionwand.job; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import thetadev.constructionwand.basics.WandUtil; + +public class DestroySnapshot implements ISnapshot +{ + public final BlockState block; + public final BlockPos pos; + + public DestroySnapshot(BlockState block, BlockPos pos) { + this.pos = pos; + this.block = block; + } + + @Override + public BlockPos getPos() { + return pos; + } + + @Override + public BlockState getBlockState() { + return block; + } + + @Override + public ItemStack getRequiredItems() { + return ItemStack.EMPTY; + } + + @Override + public boolean execute(World world, PlayerEntity player) { + return WandUtil.removeBlock(world, player, block, pos); + } + + @Override + public boolean restore(World world, PlayerEntity player) { + return WandUtil.placeBlock(world, player, block, pos, null); + } + + @Override + public void forceRestore(World world) { + world.setBlockState(pos, block); + } +} diff --git a/src/main/java/thetadev/constructionwand/job/ISnapshot.java b/src/main/java/thetadev/constructionwand/job/ISnapshot.java new file mode 100644 index 0000000..8e84145 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/job/ISnapshot.java @@ -0,0 +1,18 @@ +package thetadev.constructionwand.job; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public interface ISnapshot +{ + BlockPos getPos(); + BlockState getBlockState(); + ItemStack getRequiredItems(); + + boolean execute(World world, PlayerEntity player); + boolean restore(World world, PlayerEntity player); + void forceRestore(World world); +} diff --git a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java index e04b6ef..3df8ab3 100644 --- a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java @@ -1,20 +1,52 @@ package thetadev.constructionwand.job; import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import thetadev.constructionwand.basics.WandUtil; -public class PlaceSnapshot +public class PlaceSnapshot implements ISnapshot { - public BlockState block; - public final BlockState supportingBlock; - public final BlockPos pos; - public final BlockItem item; + public final BlockState block; + public final BlockPos pos; + public final BlockItem item; - public PlaceSnapshot(BlockPos pos, BlockState supportingBlock, BlockItem item) - { - this.pos = pos; - this.supportingBlock = supportingBlock; - this.item = item; - } + public PlaceSnapshot(BlockState block, BlockPos pos, BlockItem item) { + this.block = block; + this.pos = pos; + this.item = item; + } + + @Override + public BlockPos getPos() { + return pos; + } + + @Override + public BlockState getBlockState() { + return block; + } + + @Override + public ItemStack getRequiredItems() { + return new ItemStack(item); + } + + @Override + public boolean execute(World world, PlayerEntity player) { + return WandUtil.placeBlock(world, player, block, pos, item); + } + + @Override + public boolean restore(World world, PlayerEntity player) { + return WandUtil.removeBlock(world, player, block, pos); + } + + @Override + public void forceRestore(World world) { + world.removeBlock(pos, false); + } } diff --git a/src/main/java/thetadev/constructionwand/job/UndoHistory.java b/src/main/java/thetadev/constructionwand/job/UndoHistory.java index dd226ad..5aeb2ab 100644 --- a/src/main/java/thetadev/constructionwand/job/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/job/UndoHistory.java @@ -33,7 +33,7 @@ public class UndoHistory return history.computeIfAbsent(player.getUniqueID(), k -> new PlayerEntry()); } - public void add(PlayerEntity player, World world, LinkedList placeSnapshots) { + public void add(PlayerEntity player, World world, LinkedList placeSnapshots) { LinkedList list = getEntryFromPlayer(player).entries; list.add(new HistoryEntry(placeSnapshots, world)); while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); @@ -101,38 +101,25 @@ public class UndoHistory } private static class HistoryEntry { - public final LinkedList placeSnapshots; + public final LinkedList placeSnapshots; public final World world; - public HistoryEntry(LinkedList placeSnapshots, World world) { + public HistoryEntry(LinkedList placeSnapshots, World world) { this.placeSnapshots = placeSnapshots; this.world = world; } public Set getBlockPositions() { - return placeSnapshots.stream().map(snapshot -> snapshot.pos).collect(Collectors.toSet()); + return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); } public boolean undo(PlayerEntity player) { - for(PlaceSnapshot snapshot : placeSnapshots) { - BlockState currentBlock = world.getBlockState(snapshot.pos); + for(ISnapshot snapshot : placeSnapshots) { + if(snapshot.restore(world, player) && !player.isCreative()) { + ItemStack stack = snapshot.getRequiredItems(); - // If placed block is still present and can be broken, break it and return item - if(world.isBlockModifiable(player, snapshot.pos) && - (player.isCreative() || - (currentBlock.getBlockHardness(world, snapshot.pos) > -1 && world.getTileEntity(snapshot.pos) == null && ReplacementRegistry.matchBlocks(currentBlock.getBlock(), snapshot.block.getBlock())))) - { - BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, snapshot.pos, currentBlock, player); - MinecraftForge.EVENT_BUS.post(breakEvent); - if(breakEvent.isCanceled()) continue; - - world.removeBlock(snapshot.pos, false); - - if(!player.isCreative()) { - ItemStack stack = new ItemStack(snapshot.item); - if(!player.inventory.addItemStackToInventory(stack)) { - player.dropItem(stack, false); - } + if(!player.inventory.addItemStackToInventory(stack)) { + player.dropItem(stack, false); } } } diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 3686435..4a7e288 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -7,16 +7,12 @@ import net.minecraft.item.*; import net.minecraft.state.Property; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.SlabType; -import net.minecraft.stats.Stats; import net.minecraft.util.*; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.world.World; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.common.util.BlockSnapshot; -import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.*; import thetadev.constructionwand.basics.option.WandOptions; @@ -43,7 +39,7 @@ public abstract class WandJob protected HashMap itemCounts; protected IPool itemPool; - protected LinkedList placeSnapshots; + protected LinkedList placeSnapshots; protected WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) @@ -91,7 +87,7 @@ public abstract class WandJob } public Set getBlockPositions() { - return placeSnapshots.stream().map(snapshot -> snapshot.pos).collect(Collectors.toSet()); + return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); } public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; } @@ -170,8 +166,11 @@ public abstract class WandJob } // Attempts to take specified number of items, returns number of missing items - private int takeItems(Item item, int count) + private int takeItemStack(ItemStack stack) { + int count = stack.getCount(); + Item item = stack.getItem(); + if(player.inventory == null || player.inventory.mainInventory == null) return count; if(player.isCreative()) return 0; @@ -244,8 +243,6 @@ public abstract class WandJob // Can block be placed? BlockState blockState = item.getBlock().getStateForPlacement(ctx); if(blockState == null) continue; - blockState = Block.getValidBlockForPosition(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; // Forbidden Tile Entity? if(!WandUtil.isTEAllowed(blockState)) continue; @@ -257,64 +254,33 @@ public abstract class WandJob if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) continue; } - // Reduce item count - if(count < Integer.MAX_VALUE) itemCounts.merge(item, -1, Integer::sum); - return new PlaceSnapshot(pos, supportingBlock, item); - } - } + // Adjust blockstate to neighbors + blockState = Block.getValidBlockForPosition(blockState, world, pos); + if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; - private boolean placeBlock(PlaceSnapshot placeSnapshot) { - BlockPos blockPos = placeSnapshot.pos; + // Copy block properties from supporting block + if(options.direction.get() == WandOptions.DIRECTION.TARGET) { + // Block properties to be copied (alignment/rotation properties) + for(Property property : new Property[] { + BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, + BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) + { + if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { + blockState = blockState.with(property, supportingBlock.get(property)); + } + } - BlockItemUseContext ctx = new WandItemUseContext(this, blockPos, placeSnapshot.item); - if(!ctx.canPlace()) return false; - - BlockState placeBlock = Block.getBlockFromItem(placeSnapshot.item).getStateForPlacement(ctx); - if(placeBlock == null) return false; - - BlockState supportingBlock = placeSnapshot.supportingBlock; - - if(options.direction.get() == WandOptions.DIRECTION.TARGET) { - // Block properties to be copied (alignment/rotation properties) - for(Property property : new Property[] { - BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, - BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) - { - if(supportingBlock.hasProperty(property) && placeBlock.hasProperty(property)) { - placeBlock = placeBlock.with(property, supportingBlock.get(property)); + // Dont dupe double slabs + if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { + SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); + if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); } } - // Dont dupe double slabs - if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && placeBlock.hasProperty(BlockStateProperties.SLAB_TYPE)) { - SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); - if(slabType != SlabType.DOUBLE) placeBlock = placeBlock.with(BlockStateProperties.SLAB_TYPE, slabType); - } + // Reduce item count + if(count < Integer.MAX_VALUE) itemCounts.merge(item, -1, Integer::sum); + return new PlaceSnapshot(blockState, pos, item); } - // Place the block - if(!world.setBlockState(blockPos, placeBlock)) { - ConstructionWand.LOGGER.info("Block could not be placed"); - return false; - } - - // Remove block if placeEvent is canceled - BlockSnapshot snapshot = BlockSnapshot.create(world.func_234923_W_(), world, blockPos); - BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, placeBlock, player); - MinecraftForge.EVENT_BUS.post(placeEvent); - if(placeEvent.isCanceled()) { - world.removeBlock(blockPos, false); - return false; - } - - // Call OnBlockPlaced method - placeBlock.getBlock().onBlockPlacedBy(world, blockPos, placeBlock, player, new ItemStack(placeSnapshot.item)); - - // Update stats - player.addStat(Stats.ITEM_USED.get(placeSnapshot.item)); - player.addStat(ModStats.USE_WAND); - - placeSnapshot.block = placeBlock; - return true; } protected boolean matchBlocks(Block b1, Block b2) { @@ -327,30 +293,29 @@ public abstract class WandJob } public boolean doIt() { - LinkedList placed = new LinkedList<>(); + LinkedList executed = new LinkedList<>(); - for(PlaceSnapshot snapshot : placeSnapshots) { + for(ISnapshot snapshot : placeSnapshots) { if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; - BlockPos pos = snapshot.pos; - BlockItem placeItem = snapshot.item; - - if(placeBlock(snapshot)) { - wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); - + if(snapshot.execute(world, player)) { // If the item cant be taken, undo the placement - if(takeItems(placeItem, 1) == 0) placed.add(snapshot); + if(takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); else { - ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+placeItem.toString()); - world.removeBlock(pos, false); + ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+ + snapshot.getBlockState().getBlock().toString()); + snapshot.forceRestore(world); } + + wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); + player.addStat(ModStats.USE_WAND); } } - placeSnapshots = placed; + placeSnapshots = executed; // Play place sound if(!placeSnapshots.isEmpty()) { - SoundType sound = placeSnapshots.getFirst().block.getSoundType(); + SoundType sound = placeSnapshots.getFirst().getBlockState().getSoundType(); world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); } diff --git a/src/main/resources/assets/constructionwand/models/item/diamond_wand.json b/src/main/resources/assets/constructionwand/models/item/diamond_wand.json index e60a69b..01aff39 100644 --- a/src/main/resources/assets/constructionwand/models/item/diamond_wand.json +++ b/src/main/resources/assets/constructionwand/models/item/diamond_wand.json @@ -5,8 +5,8 @@ }, "overrides": [{ "predicate": { - "constructionwand:wand_mode": 1 + "constructionwand:using_core": 1 }, - "model": "constructionwand:item/diamond_wand_angel" + "model": "constructionwand:item/diamond_wand_core" }] } diff --git a/src/main/resources/assets/constructionwand/models/item/diamond_wand_angel.json b/src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json similarity index 67% rename from src/main/resources/assets/constructionwand/models/item/diamond_wand_angel.json rename to src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json index fc6c59b..fd16367 100644 --- a/src/main/resources/assets/constructionwand/models/item/diamond_wand_angel.json +++ b/src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json @@ -2,6 +2,6 @@ "parent": "item/handheld", "textures": { "layer0": "constructionwand:items/diamond_wand", - "layer1": "constructionwand:items/overlay_angel" + "layer1": "constructionwand:items/overlay_core" } } diff --git a/src/main/resources/assets/constructionwand/models/item/infinity_wand.json b/src/main/resources/assets/constructionwand/models/item/infinity_wand.json index f095f9a..403cdb5 100644 --- a/src/main/resources/assets/constructionwand/models/item/infinity_wand.json +++ b/src/main/resources/assets/constructionwand/models/item/infinity_wand.json @@ -5,8 +5,8 @@ }, "overrides": [{ "predicate": { - "constructionwand:wand_mode": 1 + "constructionwand:using_core": 1 }, - "model": "constructionwand:item/infinity_wand_angel" + "model": "constructionwand:item/infinity_wand_core" }] } diff --git a/src/main/resources/assets/constructionwand/models/item/infinity_wand_angel.json b/src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json similarity index 66% rename from src/main/resources/assets/constructionwand/models/item/infinity_wand_angel.json rename to src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json index 8f65fec..1544e5f 100644 --- a/src/main/resources/assets/constructionwand/models/item/infinity_wand_angel.json +++ b/src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json @@ -2,6 +2,6 @@ "parent": "item/handheld", "textures": { "layer0": "constructionwand:items/infinity_wand", - "layer1": "constructionwand:items/overlay_angel" + "layer1": "constructionwand:items/overlay_core" } } diff --git a/src/main/resources/assets/constructionwand/models/item/iron_wand.json b/src/main/resources/assets/constructionwand/models/item/iron_wand.json index eaeb67a..53e60d3 100644 --- a/src/main/resources/assets/constructionwand/models/item/iron_wand.json +++ b/src/main/resources/assets/constructionwand/models/item/iron_wand.json @@ -5,8 +5,8 @@ }, "overrides": [{ "predicate": { - "constructionwand:wand_mode": 1 + "constructionwand:using_core": 1 }, - "model": "constructionwand:item/iron_wand_angel" + "model": "constructionwand:item/iron_wand_core" }] } diff --git a/src/main/resources/assets/constructionwand/models/item/iron_wand_angel.json b/src/main/resources/assets/constructionwand/models/item/iron_wand_core.json similarity index 66% rename from src/main/resources/assets/constructionwand/models/item/iron_wand_angel.json rename to src/main/resources/assets/constructionwand/models/item/iron_wand_core.json index c9eba70..687e0c5 100644 --- a/src/main/resources/assets/constructionwand/models/item/iron_wand_angel.json +++ b/src/main/resources/assets/constructionwand/models/item/iron_wand_core.json @@ -2,6 +2,6 @@ "parent": "item/handheld", "textures": { "layer0": "constructionwand:items/iron_wand", - "layer1": "constructionwand:items/overlay_angel" + "layer1": "constructionwand:items/overlay_core" } } diff --git a/src/main/resources/assets/constructionwand/models/item/stone_wand.json b/src/main/resources/assets/constructionwand/models/item/stone_wand.json index 7ea2a01..d628f8f 100644 --- a/src/main/resources/assets/constructionwand/models/item/stone_wand.json +++ b/src/main/resources/assets/constructionwand/models/item/stone_wand.json @@ -5,8 +5,8 @@ }, "overrides": [{ "predicate": { - "constructionwand:wand_mode": 1 + "constructionwand:using_core": 1 }, - "model": "constructionwand:item/stone_wand_angel" + "model": "constructionwand:item/stone_wand_core" }] } diff --git a/src/main/resources/assets/constructionwand/models/item/stone_wand_angel.json b/src/main/resources/assets/constructionwand/models/item/stone_wand_core.json similarity index 66% rename from src/main/resources/assets/constructionwand/models/item/stone_wand_angel.json rename to src/main/resources/assets/constructionwand/models/item/stone_wand_core.json index c5d6367..b78fc5f 100644 --- a/src/main/resources/assets/constructionwand/models/item/stone_wand_angel.json +++ b/src/main/resources/assets/constructionwand/models/item/stone_wand_core.json @@ -2,6 +2,6 @@ "parent": "item/handheld", "textures": { "layer0": "constructionwand:items/stone_wand", - "layer1": "constructionwand:items/overlay_angel" + "layer1": "constructionwand:items/overlay_core" } } diff --git a/src/main/resources/assets/constructionwand/textures/items/overlay_core.png b/src/main/resources/assets/constructionwand/textures/items/overlay_core.png new file mode 100644 index 0000000000000000000000000000000000000000..b70e74586d12f7217065bea419fd06b4220ba7a9 GIT binary patch literal 613 zcmV-r0-F7aP)EX>4Tx04R}tkv&MmKpe$iQ;Q-MK|828WT;LSL`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT3N2zhIPS;0dyl(!fKV?p&FUBjG~G5+ ziMW`_u8N(n5D-EHeHfIPWz0!Z629Z>9s$1I#dwzgxj#pbnzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)O(glehxvqHp#<}3Kz%wIeIyFxmAr=d5th6yJni}yGaa7fG$`>*o ztDLtuYvn3y-jlyDoYPm9xlVH!2`pj>5=1DdqJ%PR#Aww?v5=zuxQ~Cx^-JVZ$W;O( z#{w$QAiI9>Klt6Pm7kpOlEQHy^x`-lBS7aa(5N}i_p#$NPJrMuaHY5Wl{zr8+LMpN$mid00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{002EnL_t(I%VS^|S>Wi=qyPW^|NkFZjEPvyYuB#*&&S8d zaO~JI2C@wJ^yw4B-Me=gtgWpX8Aib<7<2#tk`WT{QFy0}00000NkvXXu0mjfhSUfq literal 0 HcmV?d00001 From 39dc7204cda3f825583f850a32bb1207cd823233 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 6 Feb 2021 15:02:44 +0100 Subject: [PATCH 22/78] Reformat & cleanup --- .../constructionwand/basics/CommonEvents.java | 12 +- .../constructionwand/basics/ConfigClient.java | 42 +- .../constructionwand/basics/ConfigServer.java | 166 +++--- .../constructionwand/basics/ModStats.java | 18 +- .../basics/ReplacementRegistry.java | 62 +-- .../constructionwand/basics/WandUtil.java | 184 ++++--- .../basics/option/IOption.java | 38 +- .../basics/option/OptionBoolean.java | 90 ++-- .../basics/option/OptionEnum.java | 102 ++-- .../basics/option/WandOptions.java | 122 +++-- .../constructionwand/basics/pool/IPool.java | 8 +- .../basics/pool/OrderedPool.java | 44 +- .../basics/pool/RandomPool.java | 74 +-- .../client/RenderBlockPreview.java | 83 ++- .../constructionwand/client/RenderTypes.java | 40 +- .../constructionwand/client/ScreenWand.java | 120 ++--- .../containers/ContainerManager.java | 46 +- .../containers/ContainerRegistrar.java | 20 +- .../handlers/HandlerShulkerbox.java | 106 ++-- .../thetadev/constructionwand/data/Inp.java | 28 +- .../constructionwand/data/ModData.java | 14 +- .../data/RecipeGenerator.java | 48 +- .../constructionwand/items/ItemWand.java | 158 +++--- .../constructionwand/items/ItemWandBasic.java | 34 +- .../items/ItemWandInfinity.java | 16 +- .../constructionwand/items/ModItems.java | 52 +- .../constructionwand/job/AngelJob.java | 38 +- .../constructionwand/job/ConstructionJob.java | 172 +++---- .../constructionwand/job/TransductionJob.java | 34 +- .../constructionwand/job/UndoHistory.java | 176 ++++--- .../job/WandItemUseContext.java | 24 +- .../constructionwand/job/WandJob.java | 475 +++++++++--------- .../network/PacketQueryUndo.java | 40 +- .../network/PacketUndoBlocks.java | 54 +- .../network/PacketWandOption.java | 70 +-- 35 files changed, 1415 insertions(+), 1395 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/basics/CommonEvents.java b/src/main/java/thetadev/constructionwand/basics/CommonEvents.java index 013ff08..e698c27 100644 --- a/src/main/java/thetadev/constructionwand/basics/CommonEvents.java +++ b/src/main/java/thetadev/constructionwand/basics/CommonEvents.java @@ -9,10 +9,10 @@ import thetadev.constructionwand.ConstructionWand; @Mod.EventBusSubscriber(modid = ConstructionWand.MODID) public class CommonEvents { - @SubscribeEvent - public static void logOut(PlayerEvent.PlayerLoggedOutEvent e) { - PlayerEntity player = e.getPlayer(); - if(player.getEntityWorld().isRemote) return; - ConstructionWand.instance.undoHistory.removePlayer(player); - } + @SubscribeEvent + public static void logOut(PlayerEvent.PlayerLoggedOutEvent e) { + PlayerEntity player = e.getPlayer(); + if(player.getEntityWorld().isRemote) return; + ConstructionWand.instance.undoHistory.removePlayer(player); + } } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java index 7c18176..d623360 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigClient.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigClient.java @@ -4,29 +4,29 @@ import net.minecraftforge.common.ForgeConfigSpec; public class ConfigClient { - private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - public static final ForgeConfigSpec.IntValue OPT_KEY; - public static final ForgeConfigSpec.BooleanValue SHIFTOPT_MODE; - public static final ForgeConfigSpec.BooleanValue SHIFTOPT_GUI; + public static final ForgeConfigSpec.IntValue OPT_KEY; + public static final ForgeConfigSpec.BooleanValue SHIFTOPT_MODE; + public static final ForgeConfigSpec.BooleanValue SHIFTOPT_GUI; - static { - BUILDER.comment("This is the Client config for ConstructionWand.", - "If you're not familiar with Forge's new split client/server config, let me explain:", - "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", - "Mod behavior is configured in the Server config, which is world-specific and thus located", - "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", - "new worlds, copy the config files in the /defaultconfigs folder."); + static { + BUILDER.comment("This is the Client config for ConstructionWand.", + "If you're not familiar with Forge's new split client/server config, let me explain:", + "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", + "Mod behavior is configured in the Server config, which is world-specific and thus located", + "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", + "new worlds, copy the config files in the /defaultconfigs folder."); - BUILDER.push("keys"); - BUILDER.comment("Key code of OPTKEY (Default: Left Control). Look up key codes under https://www.glfw.org/docs/3.3/group__keys.html"); - OPT_KEY = BUILDER.defineInRange("OptKey", 341, 0, 350); - BUILDER.comment("Press SNEAK+OPTKEY instead of SNEAK for changing wand mode/direction lock"); - SHIFTOPT_MODE = BUILDER.define("ShiftOpt", false); - BUILDER.comment("Press SNEAK+OPTKEY instead of SNEAK for opening wand GUI"); - SHIFTOPT_GUI = BUILDER.define("ShiftOptGUI", true); - BUILDER.pop(); - } + BUILDER.push("keys"); + BUILDER.comment("Key code of OPTKEY (Default: Left Control). Look up key codes under https://www.glfw.org/docs/3.3/group__keys.html"); + OPT_KEY = BUILDER.defineInRange("OptKey", 341, 0, 350); + BUILDER.comment("Press SNEAK+OPTKEY instead of SNEAK for changing wand mode/direction lock"); + SHIFTOPT_MODE = BUILDER.define("ShiftOpt", false); + BUILDER.comment("Press SNEAK+OPTKEY instead of SNEAK for opening wand GUI"); + SHIFTOPT_GUI = BUILDER.define("ShiftOptGUI", true); + BUILDER.pop(); + } - public static final ForgeConfigSpec SPEC = BUILDER.build(); + public static final ForgeConfigSpec SPEC = BUILDER.build(); } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 003a265..d1bed30 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -11,104 +11,104 @@ import java.util.List; public class ConfigServer { - private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - public static final ForgeConfigSpec.IntValue LIMIT_CREATIVE; - public static final ForgeConfigSpec.IntValue MAX_RANGE; - public static final ForgeConfigSpec.IntValue UNDO_HISTORY; - public static final ForgeConfigSpec.BooleanValue ANGEL_FALLING; + public static final ForgeConfigSpec.IntValue LIMIT_CREATIVE; + public static final ForgeConfigSpec.IntValue MAX_RANGE; + public static final ForgeConfigSpec.IntValue UNDO_HISTORY; + public static final ForgeConfigSpec.BooleanValue ANGEL_FALLING; - public static final ForgeConfigSpec.ConfigValue> SIMILAR_BLOCKS; - private static final String[] SIMILAR_BLOCKS_DEFAULT = { - "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:grass_path" - }; + public static final ForgeConfigSpec.ConfigValue> SIMILAR_BLOCKS; + private static final String[] SIMILAR_BLOCKS_DEFAULT = { + "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:grass_path" + }; - public static final ForgeConfigSpec.BooleanValue TE_WHITELIST; - public static final ForgeConfigSpec.ConfigValue> TE_LIST; - private static final String[] TE_LIST_DEFAULT = {"chiselsandbits"}; + public static final ForgeConfigSpec.BooleanValue TE_WHITELIST; + public static final ForgeConfigSpec.ConfigValue> TE_LIST; + private static final String[] TE_LIST_DEFAULT = {"chiselsandbits"}; - private static final HashMap wandProperties = new HashMap<>(); + private static final HashMap wandProperties = new HashMap<>(); - public static WandProperties getWandProperties(Item wand) { - return wandProperties.getOrDefault(wand, WandProperties.DEFAULT); - } + public static WandProperties getWandProperties(Item wand) { + return wandProperties.getOrDefault(wand, WandProperties.DEFAULT); + } - public static class WandProperties - { - public static final WandProperties DEFAULT = new WandProperties(null, null, null); + public static class WandProperties + { + public static final WandProperties DEFAULT = new WandProperties(null, null, null); - private final ForgeConfigSpec.IntValue durability; - private final ForgeConfigSpec.IntValue limit; - private final ForgeConfigSpec.IntValue angel; + private final ForgeConfigSpec.IntValue durability; + private final ForgeConfigSpec.IntValue limit; + private final ForgeConfigSpec.IntValue angel; - private WandProperties(ForgeConfigSpec.IntValue durability, ForgeConfigSpec.IntValue limit, ForgeConfigSpec.IntValue angel) { - this.durability = durability; - this.limit = limit; - this.angel = angel; - } + private WandProperties(ForgeConfigSpec.IntValue durability, ForgeConfigSpec.IntValue limit, ForgeConfigSpec.IntValue angel) { + this.durability = durability; + this.limit = limit; + this.angel = angel; + } - public WandProperties(ForgeConfigSpec.Builder builder, Item wand, int defDurability, int defLimit, int defAngel) { - builder.push(wand.getRegistryName().getPath()); + public WandProperties(ForgeConfigSpec.Builder builder, Item wand, int defDurability, int defLimit, int defAngel) { + builder.push(wand.getRegistryName().getPath()); - if(defDurability > 0) { - builder.comment("Wand durability"); - durability = builder.defineInRange("durability", defDurability, 1, Integer.MAX_VALUE); - } - else durability = null; - builder.comment("Wand block limit"); - limit = builder.defineInRange("limit", defLimit, 1, Integer.MAX_VALUE); - builder.comment("Max placement distance with angel mode (0 to disable angel mode)"); - angel = builder.defineInRange("angel", defAngel, 0, Integer.MAX_VALUE); - builder.pop(); + if(defDurability > 0) { + builder.comment("Wand durability"); + durability = builder.defineInRange("durability", defDurability, 1, Integer.MAX_VALUE); + } + else durability = null; + builder.comment("Wand block limit"); + limit = builder.defineInRange("limit", defLimit, 1, Integer.MAX_VALUE); + builder.comment("Max placement distance with angel mode (0 to disable angel mode)"); + angel = builder.defineInRange("angel", defAngel, 0, Integer.MAX_VALUE); + builder.pop(); - wandProperties.put(wand, this); - } + wandProperties.put(wand, this); + } - public int getDurability() { - return durability == null ? -1 : durability.get(); - } - public int getLimit() { - return limit == null ? 0 : limit.get(); - } - public int getAngel() { - return angel == null ? 0 : angel.get(); - } - } + public int getDurability() { + return durability == null ? -1 : durability.get(); + } + public int getLimit() { + return limit == null ? 0 : limit.get(); + } + public int getAngel() { + return angel == null ? 0 : angel.get(); + } + } - static { - BUILDER.comment("This is the Server config for ConstructionWand.", - "If you're not familiar with Forge's new split client/server config, let me explain:", - "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", - "Mod behavior is configured in the Server config, which is world-specific and thus located", - "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", - "new worlds, copy the config files in the /defaultconfigs folder."); + static { + BUILDER.comment("This is the Server config for ConstructionWand.", + "If you're not familiar with Forge's new split client/server config, let me explain:", + "Client config is stored in the /config folder and only contains client specific settings like graphics and keybinds.", + "Mod behavior is configured in the Server config, which is world-specific and thus located", + "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", + "new worlds, copy the config files in the /defaultconfigs folder."); - new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0); - new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 1); - new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 4); - new WandProperties(BUILDER, ModItems.WAND_INFINITY, 0, 1024, 8); + new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0); + new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 1); + new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 4); + new WandProperties(BUILDER, ModItems.WAND_INFINITY, 0, 1024, 8); - BUILDER.push("misc"); - BUILDER.comment("Block limit for Infinity Wand used in creative mode"); - LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); - BUILDER.comment("Maximum placement range (0: unlimited). Affects all wands and is meant for lag prevention, not game balancing."); - MAX_RANGE = BUILDER.defineInRange("MaxRange", 256, 0, Integer.MAX_VALUE); - BUILDER.comment("Number of operations that can be undone"); - UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); - BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); - ANGEL_FALLING = BUILDER.define("AngelFalling", false); - BUILDER.comment("Blocks to treat equally when in Similar mode. Enter block IDs seperated by ;"); - SIMILAR_BLOCKS = BUILDER.defineList("SimilarBlocks", Arrays.asList(SIMILAR_BLOCKS_DEFAULT), obj -> true); - BUILDER.pop(); + BUILDER.push("misc"); + BUILDER.comment("Block limit for Infinity Wand used in creative mode"); + LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); + BUILDER.comment("Maximum placement range (0: unlimited). Affects all wands and is meant for lag prevention, not game balancing."); + MAX_RANGE = BUILDER.defineInRange("MaxRange", 256, 0, Integer.MAX_VALUE); + BUILDER.comment("Number of operations that can be undone"); + UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); + BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); + ANGEL_FALLING = BUILDER.define("AngelFalling", false); + BUILDER.comment("Blocks to treat equally when in Similar mode. Enter block IDs seperated by ;"); + SIMILAR_BLOCKS = BUILDER.defineList("SimilarBlocks", Arrays.asList(SIMILAR_BLOCKS_DEFAULT), obj -> true); + BUILDER.pop(); - BUILDER.push("tileentity"); - BUILDER.comment("White/Blacklist for Tile Entities. Allow/Prevent blocks with TEs from being placed by wand.", - "You can either add block ids like minecraft:chest or mod ids like minecraft"); - TE_LIST = BUILDER.defineList("TEList", Arrays.asList(TE_LIST_DEFAULT), obj -> true); - BUILDER.comment("If set to TRUE, treat TEList as a whitelist, otherwise blacklist"); - TE_WHITELIST = BUILDER.define("TEWhitelist", false); - BUILDER.pop(); - } + BUILDER.push("tileentity"); + BUILDER.comment("White/Blacklist for Tile Entities. Allow/Prevent blocks with TEs from being placed by wand.", + "You can either add block ids like minecraft:chest or mod ids like minecraft"); + TE_LIST = BUILDER.defineList("TEList", Arrays.asList(TE_LIST_DEFAULT), obj -> true); + BUILDER.comment("If set to TRUE, treat TEList as a whitelist, otherwise blacklist"); + TE_WHITELIST = BUILDER.define("TEWhitelist", false); + BUILDER.pop(); + } - public static final ForgeConfigSpec SPEC = BUILDER.build(); + public static final ForgeConfigSpec SPEC = BUILDER.build(); } diff --git a/src/main/java/thetadev/constructionwand/basics/ModStats.java b/src/main/java/thetadev/constructionwand/basics/ModStats.java index e37e587..c41fbd7 100644 --- a/src/main/java/thetadev/constructionwand/basics/ModStats.java +++ b/src/main/java/thetadev/constructionwand/basics/ModStats.java @@ -8,15 +8,15 @@ import thetadev.constructionwand.ConstructionWand; public class ModStats { - public static final ResourceLocation USE_WAND = new ResourceLocation(ConstructionWand.MODID, "use_wand"); + public static final ResourceLocation USE_WAND = new ResourceLocation(ConstructionWand.MODID, "use_wand"); - public static void register() { - registerStat(USE_WAND); - } + public static void register() { + registerStat(USE_WAND); + } - private static void registerStat(ResourceLocation registryName) { - // Compare with net.minecraft.stats.Stats#registerCustom - Registry.register(Registry.CUSTOM_STAT, registryName.getPath(), registryName); - Stats.CUSTOM.get(registryName, IStatFormatter.DEFAULT); - } + private static void registerStat(ResourceLocation registryName) { + // Compare with net.minecraft.stats.Stats#registerCustom + Registry.register(Registry.CUSTOM_STAT, registryName.getPath(), registryName); + Stats.CUSTOM.get(registryName, IStatFormatter.DEFAULT); + } } diff --git a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java index ebc6d06..d6c2f29 100644 --- a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java +++ b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java @@ -11,41 +11,41 @@ import java.util.Set; public class ReplacementRegistry { - private static final HashSet> replacements = new HashSet<>(); + private static final HashSet> replacements = new HashSet<>(); - public static void init() { - for(Object key : ConfigServer.SIMILAR_BLOCKS.get()) { - if(!(key instanceof String)) continue; - HashSet set = new HashSet<>(); + public static void init() { + for(Object key : ConfigServer.SIMILAR_BLOCKS.get()) { + if(!(key instanceof String)) continue; + HashSet set = new HashSet<>(); - for(String id : ((String)key).split(";")) { - Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(id)); - if(item == null) { - ConstructionWand.LOGGER.warn("Replacement Registry: Could not find item "+id); - continue; - } - set.add(item); - } - if(!set.isEmpty()) replacements.add(set); - } - } + for(String id : ((String)key).split(";")) { + Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(id)); + if(item == null) { + ConstructionWand.LOGGER.warn("Replacement Registry: Could not find item "+id); + continue; + } + set.add(item); + } + if(!set.isEmpty()) replacements.add(set); + } + } - public static Set getMatchingSet(Item item) { - HashSet res = new HashSet<>(); + public static Set getMatchingSet(Item item) { + HashSet res = new HashSet<>(); - for(HashSet set : replacements) { - if(set.contains(item)) res.addAll(set); - } - res.remove(item); - return res; - } + for(HashSet set : replacements) { + if(set.contains(item)) res.addAll(set); + } + res.remove(item); + return res; + } - public static boolean matchBlocks(Block b1, Block b2) { - if(b1 == b2) return true; + public static boolean matchBlocks(Block b1, Block b2) { + if(b1 == b2) return true; - for(HashSet set : replacements) { - if(set.contains(b1.asItem()) && set.contains(b2.asItem())) return true; - } - return false; - } + for(HashSet set : replacements) { + if(set.contains(b1.asItem()) && set.contains(b2.asItem())) return true; + } + return false; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 4a538ff..67a81d8 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -23,121 +23,119 @@ import java.util.List; public class WandUtil { - public static ResourceLocation TAG_TROWELS = new ResourceLocation(ConstructionWand.MODID, "trowels"); + public static boolean stackEquals(ItemStack stackA, ItemStack stackB) { + return ItemStack.areItemsEqual(stackA, stackB) && ItemStack.areItemStackTagsEqual(stackA, stackB); + } - public static boolean stackEquals(ItemStack stackA, ItemStack stackB) { - return ItemStack.areItemsEqual(stackA, stackB) && ItemStack.areItemStackTagsEqual(stackA, stackB); - } + public static boolean stackEquals(ItemStack stackA, Item item) { + ItemStack stackB = new ItemStack(item); + return stackEquals(stackA, stackB); + } - public static boolean stackEquals(ItemStack stackA, Item item) { - ItemStack stackB = new ItemStack(item); - return stackEquals(stackA, stackB); - } + public static ItemStack holdingWand(PlayerEntity player) { + if(player.getHeldItem(Hand.MAIN_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.MAIN_HAND).getItem() instanceof ItemWand) { + return player.getHeldItem(Hand.MAIN_HAND); + } + else if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { + return player.getHeldItem(Hand.OFF_HAND); + } + return null; + } - public static ItemStack holdingWand(PlayerEntity player) { - if(player.getHeldItem(Hand.MAIN_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.MAIN_HAND).getItem() instanceof ItemWand) { - return player.getHeldItem(Hand.MAIN_HAND); - } - else if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { - return player.getHeldItem(Hand.OFF_HAND); - } - return null; - } + public static BlockPos playerPos(PlayerEntity player) { + return new BlockPos(player.getPositionVec()); + } - public static BlockPos playerPos(PlayerEntity player) { - return new BlockPos(player.getPositionVec()); - } - - public static Vector3d entityPositionVec(Entity entity) { - return new Vector3d(entity.getPosX(), entity.getPosY() - entity.getYOffset() + entity.getHeight()/2, entity.getPosZ()); - } + public static Vector3d entityPositionVec(Entity entity) { + return new Vector3d(entity.getPosX(), entity.getPosY() - entity.getYOffset() + entity.getHeight()/2, entity.getPosZ()); + } - public static Vector3d blockPosVec(BlockPos pos) { - return new Vector3d(pos.getX(), pos.getY(), pos.getZ()); - } + public static Vector3d blockPosVec(BlockPos pos) { + return new Vector3d(pos.getX(), pos.getY(), pos.getZ()); + } - public static List getHotbar(PlayerEntity player) { - return player.inventory.mainInventory.subList(0, 9); - } + public static List getHotbar(PlayerEntity player) { + return player.inventory.mainInventory.subList(0, 9); + } - public static List getHotbarWithOffhand(PlayerEntity player) { - ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); - inventory.addAll(player.inventory.mainInventory.subList(0, 9)); - return inventory; - } + public static List getHotbarWithOffhand(PlayerEntity player) { + ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); + inventory.addAll(player.inventory.mainInventory.subList(0, 9)); + return inventory; + } - public static List getMainInv(PlayerEntity player) { - return player.inventory.mainInventory.subList(9, player.inventory.mainInventory.size()); - } + public static List getMainInv(PlayerEntity player) { + return player.inventory.mainInventory.subList(9, player.inventory.mainInventory.size()); + } - public static List getFullInv(PlayerEntity player) { - ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); - inventory.addAll(player.inventory.mainInventory); - return inventory; - } + public static List getFullInv(PlayerEntity player) { + ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); + inventory.addAll(player.inventory.mainInventory); + return inventory; + } - public static int maxRange(BlockPos p1, BlockPos p2) { - return Math.max(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getZ() - p2.getZ())); - } + public static int maxRange(BlockPos p1, BlockPos p2) { + return Math.max(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getZ() - p2.getZ())); + } - public static boolean isTEAllowed(BlockState state) { - if(!state.hasTileEntity()) return true; + public static boolean isTEAllowed(BlockState state) { + if(!state.hasTileEntity()) return true; - ResourceLocation name = state.getBlock().getRegistryName(); - if(name == null) return false; + ResourceLocation name = state.getBlock().getRegistryName(); + if(name == null) return false; - String fullId = name.toString(); - String modId = name.getNamespace(); + String fullId = name.toString(); + String modId = name.getNamespace(); - boolean inList = ConfigServer.TE_LIST.get().contains(fullId) || ConfigServer.TE_LIST.get().contains(modId); - boolean isWhitelist = ConfigServer.TE_WHITELIST.get(); + boolean inList = ConfigServer.TE_LIST.get().contains(fullId) || ConfigServer.TE_LIST.get().contains(modId); + boolean isWhitelist = ConfigServer.TE_WHITELIST.get(); - return isWhitelist == inList; - } + return isWhitelist == inList; + } - public static boolean placeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos, BlockItem item) { - if(!world.setBlockState(pos, block)) { - ConstructionWand.LOGGER.info("Block could not be placed"); - return false; - } + public static boolean placeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos, BlockItem item) { + if(!world.setBlockState(pos, block)) { + ConstructionWand.LOGGER.info("Block could not be placed"); + return false; + } - // Remove block if placeEvent is canceled - BlockSnapshot snapshot = BlockSnapshot.create(world.func_234923_W_(), world, pos); - BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, block, player); - MinecraftForge.EVENT_BUS.post(placeEvent); - if(placeEvent.isCanceled()) { - world.removeBlock(pos, false); - return false; - } + // Remove block if placeEvent is canceled + BlockSnapshot snapshot = BlockSnapshot.create(world.func_234923_W_(), world, pos); + BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, block, player); + MinecraftForge.EVENT_BUS.post(placeEvent); + if(placeEvent.isCanceled()) { + world.removeBlock(pos, false); + return false; + } - ItemStack stack; - if(item == null) stack = new ItemStack(block.getBlock().asItem()); - else { - stack = new ItemStack(item); - player.addStat(Stats.ITEM_USED.get(item)); - } + ItemStack stack; + if(item == null) stack = new ItemStack(block.getBlock().asItem()); + else { + stack = new ItemStack(item); + player.addStat(Stats.ITEM_USED.get(item)); + } - // Call OnBlockPlaced method - block.getBlock().onBlockPlacedBy(world, pos, block, player, stack); + // Call OnBlockPlaced method + block.getBlock().onBlockPlacedBy(world, pos, block, player, stack); - return true; - } + return true; + } - public static boolean removeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos) { - BlockState currentBlock = world.getBlockState(pos); + public static boolean removeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos) { + BlockState currentBlock = world.getBlockState(pos); - if(world.isBlockModifiable(player, pos) && - (player.isCreative() || - (currentBlock.getBlockHardness(world, pos) > -1 && world.getTileEntity(pos) == null && - ReplacementRegistry.matchBlocks(currentBlock.getBlock(), block.getBlock())))) { + if(world.isBlockModifiable(player, pos) && + (player.isCreative() || + (currentBlock.getBlockHardness(world, pos) > -1 && world.getTileEntity(pos) == null && + ReplacementRegistry.matchBlocks(currentBlock.getBlock(), block.getBlock())))) { - BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, pos, currentBlock, player); - MinecraftForge.EVENT_BUS.post(breakEvent); - if(breakEvent.isCanceled()) return false; + BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, pos, currentBlock, player); + MinecraftForge.EVENT_BUS.post(breakEvent); + if(breakEvent.isCanceled()) return false; - world.removeBlock(pos, false); - return true; - } - return false; - } + world.removeBlock(pos, false); + return true; + } + return false; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/IOption.java b/src/main/java/thetadev/constructionwand/basics/option/IOption.java index 3365fed..4d921bc 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/IOption.java +++ b/src/main/java/thetadev/constructionwand/basics/option/IOption.java @@ -4,25 +4,25 @@ import thetadev.constructionwand.ConstructionWand; public interface IOption { - String getKey(); - String getValueString(); - void setValueString(String val); + String getKey(); + String getValueString(); + void setValueString(String val); - default String getKeyTranslation() { - return ConstructionWand.MODID + ".option." + getKey(); - } - default String getValueTranslation() { - return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString(); - } - default String getDescTranslation() { - return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString() + ".desc"; - } + default String getKeyTranslation() { + return ConstructionWand.MODID + ".option." + getKey(); + } + default String getValueTranslation() { + return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString(); + } + default String getDescTranslation() { + return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString() + ".desc"; + } - boolean isEnabled(); - void set(T val); - T get(); - T next(boolean dir); - default T next() { - return next(true); - } + boolean isEnabled(); + void set(T val); + T get(); + T next(boolean dir); + default T next() { + return next(true); + } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java b/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java index 4d4190d..dfb5d00 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java @@ -4,59 +4,59 @@ import net.minecraft.nbt.CompoundNBT; public class OptionBoolean implements IOption { - private final CompoundNBT tag; - private final String key; - private final boolean enabled; - private boolean value; + private final CompoundNBT tag; + private final String key; + private final boolean enabled; + private boolean value; - public OptionBoolean(CompoundNBT tag, String key, boolean dval, boolean enabled) { - this.tag = tag; - this.key = key; - this.enabled = enabled; + public OptionBoolean(CompoundNBT tag, String key, boolean dval, boolean enabled) { + this.tag = tag; + this.key = key; + this.enabled = enabled; - if(tag.contains(key)) value = tag.getBoolean(key); - else value = dval; - } + if(tag.contains(key)) value = tag.getBoolean(key); + else value = dval; + } - public OptionBoolean(CompoundNBT tag, String key, boolean dval) { - this(tag, key, dval, true); - } + public OptionBoolean(CompoundNBT tag, String key, boolean dval) { + this(tag, key, dval, true); + } - @Override - public String getKey() { - return key; - } + @Override + public String getKey() { + return key; + } - @Override - public String getValueString() { - return value ? "yes" : "no"; - } + @Override + public String getValueString() { + return value ? "yes" : "no"; + } - @Override - public void setValueString(String val) { - set(val.equals("yes")); - } + @Override + public void setValueString(String val) { + set(val.equals("yes")); + } - @Override - public boolean isEnabled() { - return enabled; - } + @Override + public boolean isEnabled() { + return enabled; + } - @Override - public void set(Boolean val) { - if(!enabled) return; - value = val; - tag.putBoolean(key, value); - } + @Override + public void set(Boolean val) { + if(!enabled) return; + value = val; + tag.putBoolean(key, value); + } - @Override - public Boolean get() { - return value; - } + @Override + public Boolean get() { + return value; + } - @Override - public Boolean next(boolean dir) { - set(!value); - return value; - } + @Override + public Boolean next(boolean dir) { + set(!value); + return value; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java index f1d1865..e69fdb3 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java @@ -5,65 +5,65 @@ import net.minecraft.nbt.CompoundNBT; public class OptionEnum> implements IOption { - private final CompoundNBT tag; - private final String key; - private final Class enumClass; - private final boolean enabled; - private final E dval; - private E value; + private final CompoundNBT tag; + private final String key; + private final Class enumClass; + private final boolean enabled; + private final E dval; + private E value; - public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval, boolean enabled) { - this.tag = tag; - this.key = key; - this.enumClass = enumClass; - this.enabled = enabled; - this.dval = dval; + public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval, boolean enabled) { + this.tag = tag; + this.key = key; + this.enumClass = enumClass; + this.enabled = enabled; + this.dval = dval; - value = Enums.getIfPresent(enumClass, tag.getString(key).toUpperCase()).or(dval); - } + value = Enums.getIfPresent(enumClass, tag.getString(key).toUpperCase()).or(dval); + } - public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval) { - this(tag, key, enumClass, dval, true); - } + public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval) { + this(tag, key, enumClass, dval, true); + } - @Override - public String getKey() { - return key; - } + @Override + public String getKey() { + return key; + } - @Override - public String getValueString() { - return value.name().toLowerCase(); - } + @Override + public String getValueString() { + return value.name().toLowerCase(); + } - @Override - public void setValueString(String val) { - set(Enums.getIfPresent(enumClass, val.toUpperCase()).or(dval)); - } + @Override + public void setValueString(String val) { + set(Enums.getIfPresent(enumClass, val.toUpperCase()).or(dval)); + } - @Override - public boolean isEnabled() { - return enabled; - } + @Override + public boolean isEnabled() { + return enabled; + } - @Override - public void set(E val) { - if(!enabled) return; - value = val; - tag.putString(key, getValueString()); - } + @Override + public void set(E val) { + if(!enabled) return; + value = val; + tag.putString(key, getValueString()); + } - @Override - public E get() { - return value; - } + @Override + public E get() { + return value; + } - @Override - public E next(boolean dir) { - E[] enumValues = enumClass.getEnumConstants(); - int i = value.ordinal() + (dir ? 1:-1); - if(i < 0) i += enumValues.length; - set(enumValues[i % enumValues.length]); - return value; - } + @Override + public E next(boolean dir) { + E[] enumValues = enumClass.getEnumConstants(); + int i = value.ordinal() + (dir ? 1:-1); + if(i < 0) i += enumValues.length; + set(enumValues[i % enumValues.length]); + return value; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java index 575d9ca..0982f04 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -1,76 +1,88 @@ package thetadev.constructionwand.basics.option; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.ReplacementRegistry; import thetadev.constructionwand.items.ItemWand; import javax.annotation.Nullable; public class WandOptions { - public final CompoundNBT tag; + public final CompoundNBT tag; - private static final String TAG_ROOT = "wand_options"; + private static final String TAG_ROOT = "wand_options"; - public enum MODE - { - DEFAULT, - ANGEL; - } - public enum LOCK - { - HORIZONTAL, - VERTICAL, - NORTHSOUTH, - EASTWEST, - NOLOCK; - } - public enum DIRECTION - { - TARGET, - PLAYER; - } - public enum MATCH - { - EXACT, - SIMILAR, - ANY; - } + public enum MODE + { + DEFAULT, + ANGEL + } + public enum LOCK + { + HORIZONTAL, + VERTICAL, + NORTHSOUTH, + EASTWEST, + NOLOCK + } + public enum DIRECTION + { + TARGET, + PLAYER + } + public enum MATCH + { + EXACT, + SIMILAR, + ANY + } - public final OptionEnum mode; - public final OptionEnum lock; - public final OptionEnum direction; - public final OptionBoolean replace; - public final OptionEnum match; - public final OptionBoolean random; + public final OptionEnum mode; + public final OptionEnum lock; + public final OptionEnum direction; + public final OptionBoolean replace; + public final OptionEnum match; + public final OptionBoolean random; - public final IOption[] allOptions; + public final IOption[] allOptions; - public WandOptions(ItemStack wandStack) { - ItemWand wand = (ItemWand) wandStack.getItem(); - tag = wandStack.getOrCreateChildTag(TAG_ROOT); + public WandOptions(ItemStack wandStack) { + ItemWand wand = (ItemWand) wandStack.getItem(); + tag = wandStack.getOrCreateChildTag(TAG_ROOT); - mode = new OptionEnum<>(tag, "mode", MODE.class, MODE.DEFAULT, ConfigServer.getWandProperties(wand).getAngel() > 0); - lock = new OptionEnum<>(tag, "lock", LOCK.class, LOCK.NOLOCK); - direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.TARGET); - replace = new OptionBoolean(tag, "replace", true); - match = new OptionEnum<>(tag, "match", MATCH.class, MATCH.SIMILAR); - random = new OptionBoolean(tag, "random", false); + mode = new OptionEnum<>(tag, "mode", MODE.class, MODE.DEFAULT, ConfigServer.getWandProperties(wand).getAngel() > 0); + lock = new OptionEnum<>(tag, "lock", LOCK.class, LOCK.NOLOCK); + direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.TARGET); + replace = new OptionBoolean(tag, "replace", true); + match = new OptionEnum<>(tag, "match", MATCH.class, MATCH.SIMILAR); + random = new OptionBoolean(tag, "random", false); - allOptions = new IOption[]{mode, lock, direction, replace, match, random}; - } + allOptions = new IOption[]{mode, lock, direction, replace, match, random}; + } - @Nullable - public IOption get(String key){ - for(IOption option : allOptions) { - if(option.getKey().equals(key)) return option; - } - return null; - } + @Nullable + public IOption get(String key){ + for(IOption option : allOptions) { + if(option.getKey().equals(key)) return option; + } + return null; + } - public boolean testLock(LOCK l) { - if(lock.get() == LOCK.NOLOCK) return true; - return lock.get() == l; - } + public boolean testLock(LOCK l) { + if(lock.get() == LOCK.NOLOCK) return true; + return lock.get() == l; + } + + public boolean matchBlocks(Block b1, Block b2) { + switch(match.get()) { + case EXACT: return b1 == b2; + case SIMILAR: return ReplacementRegistry.matchBlocks(b1, b2); + case ANY: return b1 != Blocks.AIR && b2 != Blocks.AIR; + } + return false; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java index 2222694..2e89e58 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java @@ -4,8 +4,8 @@ import javax.annotation.Nullable; public interface IPool { - void add(T element); - @Nullable - T draw(); - void reset(); + void add(T element); + @Nullable + T draw(); + void reset(); } diff --git a/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java b/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java index f3f5d08..a5a8449 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java @@ -5,30 +5,30 @@ import java.util.ArrayList; public class OrderedPool implements IPool { - private final ArrayList elements; - private int index; + private final ArrayList elements; + private int index; - public OrderedPool() { - elements = new ArrayList<>(); - reset(); - } + public OrderedPool() { + elements = new ArrayList<>(); + reset(); + } - @Override - public void add(T element) { - elements.add(element); - } + @Override + public void add(T element) { + elements.add(element); + } - @Nullable - @Override - public T draw() { - if(index >= elements.size()) return null; - T e = elements.get(index); - index++; - return e; - } + @Nullable + @Override + public T draw() { + if(index >= elements.size()) return null; + T e = elements.get(index); + index++; + return e; + } - @Override - public void reset() { - index = 0; - } + @Override + public void reset() { + index = 0; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java b/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java index 4f63320..2b5f2dd 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java @@ -7,48 +7,48 @@ import java.util.Random; public class RandomPool implements IPool { - private final Random rng; - private final HashMap elements; - private HashSet pool; + private final Random rng; + private final HashMap elements; + private HashSet pool; - public RandomPool(Random rng) { - this.rng = rng; - elements = new HashMap<>(); - reset(); - } + public RandomPool(Random rng) { + this.rng = rng; + elements = new HashMap<>(); + reset(); + } - @Override - public void add(T element) { - addWithWeight(element, 1); - } + @Override + public void add(T element) { + addWithWeight(element, 1); + } - public void addWithWeight(T element, int weight) { - if(weight < 1) return; - elements.merge(element, weight, Integer::sum); - pool.add(element); - } + public void addWithWeight(T element, int weight) { + if(weight < 1) return; + elements.merge(element, weight, Integer::sum); + pool.add(element); + } - @Nullable - @Override - public T draw() { - int allWeights = pool.stream().reduce(0, (partialRes, e) -> partialRes + elements.get(e), Integer::sum); - if(allWeights < 1) return null; + @Nullable + @Override + public T draw() { + int allWeights = pool.stream().reduce(0, (partialRes, e) -> partialRes + elements.get(e), Integer::sum); + if(allWeights < 1) return null; - int random = rng.nextInt(allWeights); - int accWeight = 0; + int random = rng.nextInt(allWeights); + int accWeight = 0; - for(T e : pool) { - accWeight += elements.get(e); - if(random < accWeight) { - pool.remove(e); - return e; - } - } - return null; - } + for(T e : pool) { + accWeight += elements.get(e); + if(random < accWeight) { + pool.remove(e); + return e; + } + } + return null; + } - @Override - public void reset() { - pool = new HashSet<>(elements.keySet()); - } + @Override + public void reset() { + pool = new HashSet<>(elements.keySet()); + } } diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index 22aa689..9335f3d 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -3,7 +3,6 @@ package thetadev.constructionwand.client; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.vertex.IVertexBuilder; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.renderer.IRenderTypeBuffer; import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.entity.Entity; @@ -22,56 +21,56 @@ import java.util.Set; public class RenderBlockPreview { - public WandJob wandJob; - public Set undoBlocks; + public WandJob wandJob; + public Set undoBlocks; - @SubscribeEvent - public void renderBlockHighlight(DrawHighlightEvent event) - { - if(event.getTarget().getType() != RayTraceResult.Type.BLOCK) return; + @SubscribeEvent + public void renderBlockHighlight(DrawHighlightEvent event) + { + if(event.getTarget().getType() != RayTraceResult.Type.BLOCK) return; - BlockRayTraceResult rtr = (BlockRayTraceResult) event.getTarget(); - Entity entity = event.getInfo().getRenderViewEntity(); - if(!(entity instanceof PlayerEntity)) return; - PlayerEntity player = (PlayerEntity) entity; - Set blocks; - float colorR=0, colorG=0, colorB=0; + BlockRayTraceResult rtr = (BlockRayTraceResult) event.getTarget(); + Entity entity = event.getInfo().getRenderViewEntity(); + if(!(entity instanceof PlayerEntity)) return; + PlayerEntity player = (PlayerEntity) entity; + Set blocks; + float colorR=0, colorG=0, colorB=0; - ItemStack wand = WandUtil.holdingWand(player); - if(wand == null) return; + ItemStack wand = WandUtil.holdingWand(player); + if(wand == null) return; - if(!(player.isSneaking() && ClientEvents.isOptKeyDown())) { - if(wandJob == null || !(wandJob.getRayTraceResult().equals(rtr)) || !(wandJob.getWand().equals(wand))) { - wandJob = WandJob.getJob(player, player.getEntityWorld(), rtr, wand); - } + if(!(player.isSneaking() && ClientEvents.isOptKeyDown())) { + if(wandJob == null || !(wandJob.getRayTraceResult().equals(rtr)) || !(wandJob.getWand().equals(wand))) { + wandJob = WandJob.getJob(player, player.getEntityWorld(), rtr, wand); + } - blocks = wandJob.getBlockPositions(); - } - else { - blocks = undoBlocks; - colorG = 1; - } + blocks = wandJob.getBlockPositions(); + } + else { + blocks = undoBlocks; + colorG = 1; + } - if(blocks == null || blocks.isEmpty()) return; + if(blocks == null || blocks.isEmpty()) return; - renderBlockList(blocks, event.getMatrix(), event.getBuffers(), colorR, colorG, colorB); + renderBlockList(blocks, event.getMatrix(), event.getBuffers(), colorR, colorG, colorB); - event.setCanceled(true); - } + event.setCanceled(true); + } - private void renderBlockList(Set blocks, MatrixStack ms, IRenderTypeBuffer buffer, float red, float green, float blue) { - double renderPosX = Minecraft.getInstance().getRenderManager().info.getProjectedView().getX(); - double renderPosY = Minecraft.getInstance().getRenderManager().info.getProjectedView().getY(); - double renderPosZ = Minecraft.getInstance().getRenderManager().info.getProjectedView().getZ(); + private void renderBlockList(Set blocks, MatrixStack ms, IRenderTypeBuffer buffer, float red, float green, float blue) { + double renderPosX = Minecraft.getInstance().getRenderManager().info.getProjectedView().getX(); + double renderPosY = Minecraft.getInstance().getRenderManager().info.getProjectedView().getY(); + double renderPosZ = Minecraft.getInstance().getRenderManager().info.getProjectedView().getZ(); - ms.push(); - ms.translate(-renderPosX, -renderPosY, -renderPosZ); + ms.push(); + ms.translate(-renderPosX, -renderPosY, -renderPosZ); - for(BlockPos block : blocks) { - AxisAlignedBB aabb = new AxisAlignedBB(block); - IVertexBuilder lineBuilder = buffer.getBuffer(RenderTypes.TRANSLUCENT_LINES); - WorldRenderer.drawBoundingBox(ms, lineBuilder, aabb, red, green, blue, 0.4F); - } - ms.pop(); - } + for(BlockPos block : blocks) { + AxisAlignedBB aabb = new AxisAlignedBB(block); + IVertexBuilder lineBuilder = buffer.getBuffer(RenderTypes.TRANSLUCENT_LINES); + WorldRenderer.drawBoundingBox(ms, lineBuilder, aabb, red, green, blue, 0.4F); + } + ms.pop(); + } } diff --git a/src/main/java/thetadev/constructionwand/client/RenderTypes.java b/src/main/java/thetadev/constructionwand/client/RenderTypes.java index 9f665c5..bb33f4e 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderTypes.java +++ b/src/main/java/thetadev/constructionwand/client/RenderTypes.java @@ -11,27 +11,27 @@ import java.util.OptionalDouble; public class RenderTypes { - public static final RenderType TRANSLUCENT_LINES; + public static final RenderType TRANSLUCENT_LINES; - protected static final RenderState.TransparencyState TRANSLUCENT_TRANSPARENCY = new RenderState.TransparencyState("translucent_transparency", () -> { - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - }, RenderSystem::disableBlend); - protected static final RenderState.DepthTestState DEPTH_ALWAYS = new RenderState.DepthTestState("always", GL11.GL_ALWAYS); + protected static final RenderState.TransparencyState TRANSLUCENT_TRANSPARENCY = new RenderState.TransparencyState("translucent_transparency", () -> { + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + }, RenderSystem::disableBlend); + protected static final RenderState.DepthTestState DEPTH_ALWAYS = new RenderState.DepthTestState("always", GL11.GL_ALWAYS); - static { - RenderType.State translucentNoDepthState = RenderType.State.getBuilder().transparency(TRANSLUCENT_TRANSPARENCY) - .line(new RenderState.LineState(OptionalDouble.of(2))) - .texture(new RenderState.TextureState()) - .depthTest(DEPTH_ALWAYS) - .build(false); + static { + RenderType.State translucentNoDepthState = RenderType.State.getBuilder().transparency(TRANSLUCENT_TRANSPARENCY) + .line(new RenderState.LineState(OptionalDouble.of(2))) + .texture(new RenderState.TextureState()) + .depthTest(DEPTH_ALWAYS) + .build(false); - TRANSLUCENT_LINES = RenderType.makeType( - ConstructionWand.MODID+":translucent_lines", - DefaultVertexFormats.POSITION_COLOR, - GL11.GL_LINES, - 256, - translucentNoDepthState - ); - } + TRANSLUCENT_LINES = RenderType.makeType( + ConstructionWand.MODID+":translucent_lines", + DefaultVertexFormats.POSITION_COLOR, + GL11.GL_LINES, + 256, + translucentNoDepthState + ); + } } diff --git a/src/main/java/thetadev/constructionwand/client/ScreenWand.java b/src/main/java/thetadev/constructionwand/client/ScreenWand.java index bc312f1..4f5b7fc 100644 --- a/src/main/java/thetadev/constructionwand/client/ScreenWand.java +++ b/src/main/java/thetadev/constructionwand/client/ScreenWand.java @@ -15,77 +15,77 @@ import thetadev.constructionwand.network.PacketWandOption; public class ScreenWand extends Screen { - private final ItemStack wand; - private final WandOptions wandOptions; + private final ItemStack wand; + private final WandOptions wandOptions; - private static final int BUTTON_WIDTH = 160; - private static final int BUTTON_HEIGHT = 20; - private static final int SPACING_WIDTH = 50; - private static final int SPACING_HEIGHT = 30; - private static final int N_COLS = 2; - private static final int N_ROWS = 3; + private static final int BUTTON_WIDTH = 160; + private static final int BUTTON_HEIGHT = 20; + private static final int SPACING_WIDTH = 50; + private static final int SPACING_HEIGHT = 30; + private static final int N_COLS = 2; + private static final int N_ROWS = 3; - private static final int FIELD_WIDTH = N_COLS * (BUTTON_WIDTH+SPACING_WIDTH) - SPACING_WIDTH; - private static final int FIELD_HEIGHT = N_ROWS * (BUTTON_HEIGHT+SPACING_HEIGHT) - SPACING_HEIGHT; + private static final int FIELD_WIDTH = N_COLS * (BUTTON_WIDTH+SPACING_WIDTH) - SPACING_WIDTH; + private static final int FIELD_HEIGHT = N_ROWS * (BUTTON_HEIGHT+SPACING_HEIGHT) - SPACING_HEIGHT; - public ScreenWand(ItemStack wand) { - super(new StringTextComponent("ScreenWand")); - this.wand = wand; - wandOptions = new WandOptions(wand); - } + public ScreenWand(ItemStack wand) { + super(new StringTextComponent("ScreenWand")); + this.wand = wand; + wandOptions = new WandOptions(wand); + } - @Override - public void init(Minecraft minecraft, int width, int height) { - super.init(minecraft, width, height); + @Override + public void init(Minecraft minecraft, int width, int height) { + super.init(minecraft, width, height); - createButton(0, 0, wandOptions.mode); - createButton(0, 1, wandOptions.lock); - createButton(0, 2, wandOptions.direction); - createButton(1, 0, wandOptions.replace); - createButton(1, 1, wandOptions.match); - createButton(1, 2, wandOptions.random); - } + createButton(0, 0, wandOptions.mode); + createButton(0, 1, wandOptions.lock); + createButton(0, 2, wandOptions.direction); + createButton(1, 0, wandOptions.replace); + createButton(1, 1, wandOptions.match); + createButton(1, 2, wandOptions.random); + } - @Override - public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { - renderBackground(matrixStack); - super.render(matrixStack, mouseX, mouseY, partialTicks); - drawCenteredString(matrixStack, font, wand.getDisplayName(), width/2, height/2 - FIELD_HEIGHT/2 - SPACING_HEIGHT, 16777215); - } + @Override + public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { + renderBackground(matrixStack); + super.render(matrixStack, mouseX, mouseY, partialTicks); + drawCenteredString(matrixStack, font, wand.getDisplayName(), width/2, height/2 - FIELD_HEIGHT/2 - SPACING_HEIGHT, 16777215); + } - @Override - public boolean charTyped(char character, int code) { - if(character == 'e') closeScreen(); - return super.charTyped(character, code); - } + @Override + public boolean charTyped(char character, int code) { + if(character == 'e') closeScreen(); + return super.charTyped(character, code); + } - private void createButton(int cx, int cy, IOption option) { - Button button = new Button(getX(cx), getY(cy), BUTTON_WIDTH, BUTTON_HEIGHT, getButtonLabel(option), bt -> clickButton(bt, option), (bt, ms, x, y) -> drawTooltip(ms, x, y, option)); - button.active = option.isEnabled(); - addButton(button); - } + private void createButton(int cx, int cy, IOption option) { + Button button = new Button(getX(cx), getY(cy), BUTTON_WIDTH, BUTTON_HEIGHT, getButtonLabel(option), bt -> clickButton(bt, option), (bt, ms, x, y) -> drawTooltip(ms, x, y, option)); + button.active = option.isEnabled(); + addButton(button); + } - private void clickButton(Button button, IOption option) { - option.next(); - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(option, false)); - button.setMessage(getButtonLabel(option)); - } + private void clickButton(Button button, IOption option) { + option.next(); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(option, false)); + button.setMessage(getButtonLabel(option)); + } - private void drawTooltip(MatrixStack matrixStack, int mouseX, int mouseY, IOption option) { - if(isMouseOver(mouseX, mouseY)) { - renderTooltip(matrixStack, new TranslationTextComponent(option.getDescTranslation()), mouseX, mouseY); - } - } + private void drawTooltip(MatrixStack matrixStack, int mouseX, int mouseY, IOption option) { + if(isMouseOver(mouseX, mouseY)) { + renderTooltip(matrixStack, new TranslationTextComponent(option.getDescTranslation()), mouseX, mouseY); + } + } - private int getX(int n) { - return width/2 - FIELD_WIDTH/2 + n*(BUTTON_WIDTH+SPACING_WIDTH); - } + private int getX(int n) { + return width/2 - FIELD_WIDTH/2 + n*(BUTTON_WIDTH+SPACING_WIDTH); + } - private int getY(int n) { - return height/2 - FIELD_HEIGHT/2 + n*(BUTTON_HEIGHT+SPACING_HEIGHT); - } + private int getY(int n) { + return height/2 - FIELD_HEIGHT/2 + n*(BUTTON_HEIGHT+SPACING_HEIGHT); + } - private ITextComponent getButtonLabel(IOption option) { - return new TranslationTextComponent(option.getKeyTranslation()).append(new TranslationTextComponent(option.getValueTranslation())); - } + private ITextComponent getButtonLabel(IOption option) { + return new TranslationTextComponent(option.getKeyTranslation()).append(new TranslationTextComponent(option.getValueTranslation())); + } } diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerManager.java b/src/main/java/thetadev/constructionwand/containers/ContainerManager.java index d9f10a7..5f968cf 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerManager.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerManager.java @@ -7,31 +7,31 @@ import thetadev.constructionwand.api.IContainerHandler; import java.util.ArrayList; public class ContainerManager { - private ArrayList handlers; + private final ArrayList handlers; - public ContainerManager() { - handlers = new ArrayList(); - } + public ContainerManager() { + handlers = new ArrayList(); + } - public boolean register(IContainerHandler handler) { - return handlers.add(handler); - } + public boolean register(IContainerHandler handler) { + return handlers.add(handler); + } - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { - for(IContainerHandler handler : handlers) { - if(handler.matches(player, itemStack, inventoryStack)) { - return handler.countItems(player,itemStack, inventoryStack); - } - } - return 0; - } + public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + for(IContainerHandler handler : handlers) { + if(handler.matches(player, itemStack, inventoryStack)) { + return handler.countItems(player,itemStack, inventoryStack); + } + } + return 0; + } - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { - for(IContainerHandler handler : handlers) { - if(handler.matches(player, itemStack, inventoryStack)) { - return handler.useItems(player, itemStack, inventoryStack, count); - } - } - return count; - } + public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { + for(IContainerHandler handler : handlers) { + if(handler.matches(player, itemStack, inventoryStack)) { + return handler.useItems(player, itemStack, inventoryStack, count); + } + } + return count; + } } \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java index 64c2e6d..42642f6 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java @@ -2,17 +2,19 @@ package thetadev.constructionwand.containers; import net.minecraftforge.fml.ModList; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.containers.handlers.*; +import thetadev.constructionwand.containers.handlers.HandlerBotania; +import thetadev.constructionwand.containers.handlers.HandlerCapability; +import thetadev.constructionwand.containers.handlers.HandlerShulkerbox; public class ContainerRegistrar { - public static void register() { - ConstructionWand.instance.containerManager.register(new HandlerCapability()); - ConstructionWand.instance.containerManager.register(new HandlerShulkerbox()); + public static void register() { + ConstructionWand.instance.containerManager.register(new HandlerCapability()); + ConstructionWand.instance.containerManager.register(new HandlerShulkerbox()); - if(ModList.get().isLoaded("botania")) { - ConstructionWand.instance.containerManager.register(new HandlerBotania()); - ConstructionWand.LOGGER.info("Botania integration added"); - } - } + if(ModList.get().isLoaded("botania")) { + ConstructionWand.instance.containerManager.register(new HandlerBotania()); + ConstructionWand.LOGGER.info("Botania integration added"); + } + } } diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java index 86bfad6..7c48b95 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java @@ -13,66 +13,66 @@ import thetadev.constructionwand.basics.WandUtil; public class HandlerShulkerbox implements IContainerHandler { - private final int SLOTS = 27; + private final int SLOTS = 27; - @Override - public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) - { - return inventoryStack != null && inventoryStack.getCount() == 1 && Block.getBlockFromItem(inventoryStack.getItem()) instanceof ShulkerBoxBlock; - } + @Override + public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) + { + return inventoryStack != null && inventoryStack.getCount() == 1 && Block.getBlockFromItem(inventoryStack.getItem()) instanceof ShulkerBoxBlock; + } - @Override - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) - { - int count = 0; + @Override + public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) + { + int count = 0; - for(ItemStack stack : getItemList(inventoryStack)) { - if(WandUtil.stackEquals(stack, itemStack)) count += stack.getCount(); - } + for(ItemStack stack : getItemList(inventoryStack)) { + if(WandUtil.stackEquals(stack, itemStack)) count += stack.getCount(); + } - return count; - } + return count; + } - @Override - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) - { - NonNullList itemList = getItemList(inventoryStack); - boolean changed = false; + @Override + public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) + { + NonNullList itemList = getItemList(inventoryStack); + boolean changed = false; - for(ItemStack stack : itemList) { - if(WandUtil.stackEquals(stack, itemStack)) { - int toTake = Math.min(count, stack.getCount()); - stack.shrink(toTake); - count -= toTake; - changed = true; - if(count == 0) break; - } - } - if(changed) { - setItemList(inventoryStack, itemList); - player.inventory.markDirty(); - } + for(ItemStack stack : itemList) { + if(WandUtil.stackEquals(stack, itemStack)) { + int toTake = Math.min(count, stack.getCount()); + stack.shrink(toTake); + count -= toTake; + changed = true; + if(count == 0) break; + } + } + if(changed) { + setItemList(inventoryStack, itemList); + player.inventory.markDirty(); + } - return count; - } + return count; + } - private NonNullList getItemList(ItemStack itemStack) { - NonNullList itemStacks = NonNullList.withSize(SLOTS, ItemStack.EMPTY); - CompoundNBT rootTag = itemStack.getTag(); - if (rootTag != null && rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { - CompoundNBT entityTag = rootTag.getCompound("BlockEntityTag"); - if (entityTag.contains("Items", Constants.NBT.TAG_LIST)) { - ItemStackHelper.loadAllItems(entityTag, itemStacks); - } - } - return itemStacks; - } + private NonNullList getItemList(ItemStack itemStack) { + NonNullList itemStacks = NonNullList.withSize(SLOTS, ItemStack.EMPTY); + CompoundNBT rootTag = itemStack.getTag(); + if (rootTag != null && rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { + CompoundNBT entityTag = rootTag.getCompound("BlockEntityTag"); + if (entityTag.contains("Items", Constants.NBT.TAG_LIST)) { + ItemStackHelper.loadAllItems(entityTag, itemStacks); + } + } + return itemStacks; + } - private void setItemList(ItemStack itemStack, NonNullList itemStacks) { - CompoundNBT rootTag = itemStack.getOrCreateTag(); - if (!rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { - rootTag.put("BlockEntityTag", new CompoundNBT()); - } - ItemStackHelper.saveAllItems(rootTag.getCompound("BlockEntityTag"), itemStacks); - } + private void setItemList(ItemStack itemStack, NonNullList itemStacks) { + CompoundNBT rootTag = itemStack.getOrCreateTag(); + if (!rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { + rootTag.put("BlockEntityTag", new CompoundNBT()); + } + ItemStackHelper.saveAllItems(rootTag.getCompound("BlockEntityTag"), itemStacks); + } } diff --git a/src/main/java/thetadev/constructionwand/data/Inp.java b/src/main/java/thetadev/constructionwand/data/Inp.java index 07e62c0..cc488b1 100644 --- a/src/main/java/thetadev/constructionwand/data/Inp.java +++ b/src/main/java/thetadev/constructionwand/data/Inp.java @@ -8,20 +8,20 @@ import net.minecraft.util.IItemProvider; public class Inp { - public final String name; - public final Ingredient ingredient; - public final ItemPredicate predicate; + public final String name; + public final Ingredient ingredient; + public final ItemPredicate predicate; - public Inp(String name, Ingredient ingredient, ItemPredicate predicate) { - this.name = name; - this.ingredient = ingredient; - this.predicate = predicate; - } + public Inp(String name, Ingredient ingredient, ItemPredicate predicate) { + this.name = name; + this.ingredient = ingredient; + this.predicate = predicate; + } - public static Inp fromItem(IItemProvider in) { - return new Inp(in.asItem().getRegistryName().getPath(), Ingredient.fromItems(in), ItemPredicate.Builder.create().item(in).build()); - } - public static Inp fromTag(ITag.INamedTag in) { - return new Inp(in.getName().getPath(), Ingredient.fromTag(in), ItemPredicate.Builder.create().tag(in).build()); - } + public static Inp fromItem(IItemProvider in) { + return new Inp(in.asItem().getRegistryName().getPath(), Ingredient.fromItems(in), ItemPredicate.Builder.create().item(in).build()); + } + public static Inp fromTag(ITag.INamedTag in) { + return new Inp(in.getName().getPath(), Ingredient.fromTag(in), ItemPredicate.Builder.create().tag(in).build()); + } } \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/data/ModData.java b/src/main/java/thetadev/constructionwand/data/ModData.java index a6d94ad..d5f95a6 100644 --- a/src/main/java/thetadev/constructionwand/data/ModData.java +++ b/src/main/java/thetadev/constructionwand/data/ModData.java @@ -8,12 +8,12 @@ import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) public class ModData { - @SubscribeEvent - public static void gatherData(GatherDataEvent event) { - DataGenerator generator = event.getGenerator(); + @SubscribeEvent + public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); - if(event.includeServer()) { - generator.addProvider(new RecipeGenerator(generator)); - } - } + if(event.includeServer()) { + generator.addProvider(new RecipeGenerator(generator)); + } + } } diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index 36430c8..ee36cd6 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -14,31 +14,31 @@ import java.util.function.Consumer; public class RecipeGenerator extends RecipeProvider { - public RecipeGenerator(DataGenerator generatorIn) { - super(generatorIn); - } + public RecipeGenerator(DataGenerator generatorIn) { + super(generatorIn); + } - @Override - protected void registerRecipes(Consumer consumer) { - wandRecipe(consumer, ModItems.WAND_STONE, Inp.fromTag(ItemTags.field_232909_aa_)); //stone_tool_materials - wandRecipe(consumer, ModItems.WAND_IRON, Inp.fromTag(Tags.Items.INGOTS_IRON)); - wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); - wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); - } + @Override + protected void registerRecipes(Consumer consumer) { + wandRecipe(consumer, ModItems.WAND_STONE, Inp.fromTag(ItemTags.field_232909_aa_)); //stone_tool_materials + wandRecipe(consumer, ModItems.WAND_IRON, Inp.fromTag(Tags.Items.INGOTS_IRON)); + wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); + wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); + } - private void wandRecipe(Consumer consumer, IItemProvider wand, Inp material) { - ShapedRecipeBuilder.shapedRecipe(wand) - .key('X', material.ingredient) - .key('#', Tags.Items.RODS_WOODEN) - .patternLine(" X") - .patternLine(" # ") - .patternLine("# ") - .addCriterion("has_item", hasItem(material.predicate)) - .build(consumer); - } + private void wandRecipe(Consumer consumer, IItemProvider wand, Inp material) { + ShapedRecipeBuilder.shapedRecipe(wand) + .key('X', material.ingredient) + .key('#', Tags.Items.RODS_WOODEN) + .patternLine(" X") + .patternLine(" # ") + .patternLine("# ") + .addCriterion("has_item", hasItem(material.predicate)) + .build(consumer); + } - @Override - public String getName() { - return ConstructionWand.MODID + " crafting recipes"; - } + @Override + public String getName() { + return ConstructionWand.MODID + " crafting recipes"; + } } \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/items/ItemWand.java b/src/main/java/thetadev/constructionwand/items/ItemWand.java index 2b535a2..667ea81 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWand.java @@ -30,99 +30,99 @@ import java.util.List; public abstract class ItemWand extends Item { - public ItemWand(String name, Item.Properties properties) { - super(properties.group(ItemGroup.TOOLS)); - setRegistryName(ConstructionWand.loc(name)); - } + public ItemWand(String name, Item.Properties properties) { + super(properties.group(ItemGroup.TOOLS)); + setRegistryName(ConstructionWand.loc(name)); + } - @Override - public ActionResultType onItemUse(ItemUseContext context) - { - PlayerEntity player = context.getPlayer(); - Hand hand = context.getHand(); - World world = context.getWorld(); + @Override + public ActionResultType onItemUse(ItemUseContext context) + { + PlayerEntity player = context.getPlayer(); + Hand hand = context.getHand(); + World world = context.getWorld(); - if(world.isRemote || player == null) return ActionResultType.FAIL; + if(world.isRemote || player == null) return ActionResultType.FAIL; - ItemStack stack = player.getHeldItem(hand); + ItemStack stack = player.getHeldItem(hand); - if(player.isSneaking() && ConstructionWand.instance.undoHistory.isUndoActive(player)) { - return ConstructionWand.instance.undoHistory.undo(player, world, context.getPos()) ? ActionResultType.SUCCESS : ActionResultType.FAIL; - } - else { - WandJob job = WandJob.getJob(player, world, new BlockRayTraceResult(context.getHitVec(), context.getFace(), context.getPos(), false), stack); - return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; - } - } + if(player.isSneaking() && ConstructionWand.instance.undoHistory.isUndoActive(player)) { + return ConstructionWand.instance.undoHistory.undo(player, world, context.getPos()) ? ActionResultType.SUCCESS : ActionResultType.FAIL; + } + else { + WandJob job = WandJob.getJob(player, world, new BlockRayTraceResult(context.getHitVec(), context.getFace(), context.getPos(), false), stack); + return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; + } + } - @Override - public ActionResult onItemRightClick(World world, PlayerEntity player, Hand hand) { - ItemStack stack = player.getHeldItem(hand); + @Override + public ActionResult onItemRightClick(World world, PlayerEntity player, Hand hand) { + ItemStack stack = player.getHeldItem(hand); - if(!player.isSneaking()) { - if(world.isRemote) return ActionResult.resultFail(stack); + if(!player.isSneaking()) { + if(world.isRemote) return ActionResult.resultFail(stack); - // Right click: Place angel block - //ConstructionWand.LOGGER.debug("Place angel block"); - WandJob job = new AngelJob(player, world, stack); - return job.doIt() ? ActionResult.resultSuccess(stack) : ActionResult.resultFail(stack); - } - return ActionResult.resultFail(stack); - } + // Right click: Place angel block + //ConstructionWand.LOGGER.debug("Place angel block"); + WandJob job = new AngelJob(player, world, stack); + return job.doIt() ? ActionResult.resultSuccess(stack) : ActionResult.resultFail(stack); + } + return ActionResult.resultFail(stack); + } - @Override - public boolean canHarvestBlock(BlockState blockIn) { - return false; - } + @Override + public boolean canHarvestBlock(BlockState blockIn) { + return false; + } - @Override - public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) { - return false; - } + @Override + public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) { + return false; + } - public int getLimit(PlayerEntity player, ItemStack stack) { - return getLimit(); - } + public int getLimit(PlayerEntity player, ItemStack stack) { + return getLimit(); + } - protected int getLimit() { - return ConfigServer.getWandProperties(this).getLimit(); - } + protected int getLimit() { + return ConfigServer.getWandProperties(this).getLimit(); + } - public static int getWandMode(ItemStack stack) { - WandOptions options = new WandOptions(stack); - return options.mode.get().ordinal(); - } + public static int getWandMode(ItemStack stack) { + WandOptions options = new WandOptions(stack); + return options.mode.get().ordinal(); + } - @OnlyIn(Dist.CLIENT) - public void addInformation(ItemStack itemstack, World worldIn, List lines, ITooltipFlag extraInfo) { - ItemWand wand = (ItemWand) itemstack.getItem(); - WandOptions options = new WandOptions(itemstack); + @OnlyIn(Dist.CLIENT) + public void addInformation(ItemStack itemstack, World worldIn, List lines, ITooltipFlag extraInfo) { + ItemWand wand = (ItemWand) itemstack.getItem(); + WandOptions options = new WandOptions(itemstack); - String langTooltip = ConstructionWand.MODID + ".tooltip."; + String langTooltip = ConstructionWand.MODID + ".tooltip."; - if(Screen.hasShiftDown()) { - for(int i=1; i opt = options.allOptions[i]; - lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.GRAY)) - ); - } - } - else { - IOption opt = options.allOptions[0]; - lines.add(new TranslationTextComponent(langTooltip + "blocks", getLimit()).mergeStyle(TextFormatting.GRAY)); - lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.WHITE))); - lines.add(new TranslationTextComponent(langTooltip + "shift").mergeStyle(TextFormatting.AQUA)); - } - } + if(Screen.hasShiftDown()) { + for(int i=1; i opt = options.allOptions[i]; + lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) + .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.GRAY)) + ); + } + } + else { + IOption opt = options.allOptions[0]; + lines.add(new TranslationTextComponent(langTooltip + "blocks", getLimit()).mergeStyle(TextFormatting.GRAY)); + lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) + .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.WHITE))); + lines.add(new TranslationTextComponent(langTooltip + "shift").mergeStyle(TextFormatting.AQUA)); + } + } - public static void optionMessage(PlayerEntity player, IOption option) { - player.sendStatusMessage( - new TranslationTextComponent(option.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(option.getValueTranslation()).mergeStyle(TextFormatting.WHITE)) - .append(new StringTextComponent(" - ").mergeStyle(TextFormatting.GRAY)) - .append(new TranslationTextComponent(option.getDescTranslation()).mergeStyle(TextFormatting.WHITE)) - , true); - } + public static void optionMessage(PlayerEntity player, IOption option) { + player.sendStatusMessage( + new TranslationTextComponent(option.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) + .append(new TranslationTextComponent(option.getValueTranslation()).mergeStyle(TextFormatting.WHITE)) + .append(new StringTextComponent(" - ").mergeStyle(TextFormatting.GRAY)) + .append(new TranslationTextComponent(option.getDescTranslation()).mergeStyle(TextFormatting.WHITE)) + , true); + } } diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java index ff5d3dd..6ef5091 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java @@ -7,25 +7,25 @@ import thetadev.constructionwand.basics.ConfigServer; public class ItemWandBasic extends ItemWand { - private final IItemTier tier; + private final IItemTier tier; - public ItemWandBasic(String name, IItemTier tier) { - super(name, new Properties().maxDamage(tier.getMaxUses())); - this.tier = tier; - } + public ItemWandBasic(String name, IItemTier tier) { + super(name, new Properties().maxDamage(tier.getMaxUses())); + this.tier = tier; + } - @Override - public int getMaxDamage(ItemStack stack) { - return ConfigServer.getWandProperties(this).getDurability(); - } + @Override + public int getMaxDamage(ItemStack stack) { + return ConfigServer.getWandProperties(this).getDurability(); + } - @Override - public int getLimit(PlayerEntity player, ItemStack stack) { - return Math.min(stack.getMaxDamage() - stack.getDamage(), getLimit()); - } + @Override + public int getLimit(PlayerEntity player, ItemStack stack) { + return Math.min(stack.getMaxDamage() - stack.getDamage(), getLimit()); + } - @Override - public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) { - return this.tier.getRepairMaterial().test(repair); - } + @Override + public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) { + return this.tier.getRepairMaterial().test(repair); + } } diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java index 2af6ec9..ab225f9 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java @@ -6,13 +6,13 @@ import thetadev.constructionwand.basics.ConfigServer; public class ItemWandInfinity extends ItemWand { - public ItemWandInfinity(String name) - { - super(name, new Properties().maxStackSize(1).isBurnable()); - } + public ItemWandInfinity(String name) + { + super(name, new Properties().maxStackSize(1).isBurnable()); + } - @Override - public int getLimit(PlayerEntity player, ItemStack stack) { - return player.isCreative() ? ConfigServer.LIMIT_CREATIVE.get() : getLimit(); - } + @Override + public int getLimit(PlayerEntity player, ItemStack stack) { + return player.isCreative() ? ConfigServer.LIMIT_CREATIVE.get() : getLimit(); + } } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 2f92241..ab59c37 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -15,35 +15,35 @@ import thetadev.constructionwand.ConstructionWand; @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModItems { - public static final Item WAND_STONE = new ItemWandBasic("stone_wand", ItemTier.STONE); - public static final Item WAND_IRON = new ItemWandBasic("iron_wand", ItemTier.IRON); - public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", ItemTier.DIAMOND); - public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand"); + public static final Item WAND_STONE = new ItemWandBasic("stone_wand", ItemTier.STONE); + public static final Item WAND_IRON = new ItemWandBasic("iron_wand", ItemTier.IRON); + public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", ItemTier.DIAMOND); + public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand"); - public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; + public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; - @SubscribeEvent - public static void onRegisterItems(RegistryEvent.Register event) - { - event.getRegistry().registerAll(WANDS); - } + @SubscribeEvent + public static void onRegisterItems(RegistryEvent.Register event) + { + event.getRegistry().registerAll(WANDS); + } - @OnlyIn(Dist.CLIENT) - public static void registerModelProperties() { - for(Item item : WANDS) { - ItemModelsProperties.func_239418_a_( - item, ConstructionWand.loc("using_core"), - (stack, world, entity) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : ItemWand.getWandMode(stack) - ); - } - } + @OnlyIn(Dist.CLIENT) + public static void registerModelProperties() { + for(Item item : WANDS) { + ItemModelsProperties.func_239418_a_( + item, ConstructionWand.loc("using_core"), + (stack, world, entity) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : ItemWand.getWandMode(stack) + ); + } + } - @OnlyIn(Dist.CLIENT) - public static void registerItemColors() { - ItemColors colors = Minecraft.getInstance().getItemColors(); + @OnlyIn(Dist.CLIENT) + public static void registerItemColors() { + ItemColors colors = Minecraft.getInstance().getItemColors(); - for(Item item : WANDS) { - colors.register((stack, layer) -> layer == 1 ? 0xFF0000 : -1, item); - } - } + for(Item item : WANDS) { + colors.register((stack, layer) -> layer == 1 ? 0xFF0000 : -1, item); + } + } } diff --git a/src/main/java/thetadev/constructionwand/job/AngelJob.java b/src/main/java/thetadev/constructionwand/job/AngelJob.java index 59d11f3..af2ee3c 100644 --- a/src/main/java/thetadev/constructionwand/job/AngelJob.java +++ b/src/main/java/thetadev/constructionwand/job/AngelJob.java @@ -14,29 +14,29 @@ import thetadev.constructionwand.basics.option.WandOptions; public class AngelJob extends WandJob { - public AngelJob(PlayerEntity player, World world, ItemStack wand) { - super(player, world, new BlockRayTraceResult(player.getLookVec(), fromVector(player.getLookVec()), WandUtil.playerPos(player), false), wand); - } + public AngelJob(PlayerEntity player, World world, ItemStack wand) { + super(player, world, new BlockRayTraceResult(player.getLookVec(), fromVector(player.getLookVec()), WandUtil.playerPos(player), false), wand); + } - private static Direction fromVector(Vector3d vector) { - return Direction.getFacingFromVector(vector.x, vector.y, vector.z); - } + private static Direction fromVector(Vector3d vector) { + return Direction.getFacingFromVector(vector.x, vector.y, vector.z); + } - @Override - protected void getBlockPositionList() { - if(options.mode.get() != WandOptions.MODE.ANGEL || ConfigServer.getWandProperties(wandItem).getAngel() == 0) return; + @Override + protected void getBlockPositionList() { + if(options.mode.get() != WandOptions.MODE.ANGEL || ConfigServer.getWandProperties(wandItem).getAngel() == 0) return; - if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return; + if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return; - Vector3d playerVec = WandUtil.entityPositionVec(player); - Vector3d lookVec = player.getLookVec().mul(2, 2, 2); - Vector3d placeVec = playerVec.add(lookVec); + Vector3d playerVec = WandUtil.entityPositionVec(player); + Vector3d lookVec = player.getLookVec().mul(2, 2, 2); + Vector3d placeVec = playerVec.add(lookVec); - BlockPos currentPos = new BlockPos(placeVec); + BlockPos currentPos = new BlockPos(placeVec); - PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, Blocks.AIR.getDefaultState()); - if(snapshot != null) { - placeSnapshots.add(snapshot); - } - } + PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, Blocks.AIR.getDefaultState()); + if(snapshot != null) { + placeSnapshots.add(snapshot); + } + } } diff --git a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java index b3b48fd..83b7142 100644 --- a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java +++ b/src/main/java/thetadev/constructionwand/job/ConstructionJob.java @@ -14,96 +14,96 @@ import java.util.LinkedList; public class ConstructionJob extends WandJob { - public ConstructionJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { - super(player, world, rayTraceResult, itemStack); - } + public ConstructionJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { + super(player, world, rayTraceResult, itemStack); + } - @Override - protected void getBlockPositionList() { - LinkedList candidates = new LinkedList<>(); - HashSet allCandidates = new HashSet<>(); + @Override + protected void getBlockPositionList() { + LinkedList candidates = new LinkedList<>(); + HashSet allCandidates = new HashSet<>(); - Direction placeDirection = rayTraceResult.getFace(); - BlockState targetBlock = world.getBlockState(rayTraceResult.getPos()); - BlockPos startingPoint = rayTraceResult.getPos().offset(placeDirection); + Direction placeDirection = rayTraceResult.getFace(); + BlockState targetBlock = world.getBlockState(rayTraceResult.getPos()); + BlockPos startingPoint = rayTraceResult.getPos().offset(placeDirection); - // Is place direction allowed by lock? - if(placeDirection == Direction.UP || placeDirection == Direction.DOWN) { - if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) candidates.add(startingPoint); - } - else if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) candidates.add(startingPoint); + // Is place direction allowed by lock? + if(placeDirection == Direction.UP || placeDirection == Direction.DOWN) { + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) candidates.add(startingPoint); + } + else if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) candidates.add(startingPoint); - while(!candidates.isEmpty() && placeSnapshots.size() < maxBlocks) - { - BlockPos currentCandidate = candidates.removeFirst(); - try { - BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); - BlockState candidateSupportingBlock = world.getBlockState(supportingPoint); + while(!candidates.isEmpty() && placeSnapshots.size() < maxBlocks) + { + BlockPos currentCandidate = candidates.removeFirst(); + try { + BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); + BlockState candidateSupportingBlock = world.getBlockState(supportingPoint); - if(matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { - PlaceSnapshot snapshot = getPlaceSnapshot(currentCandidate, candidateSupportingBlock); - if(snapshot == null) continue; - placeSnapshots.add(snapshot); + if(matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { + PlaceSnapshot snapshot = getPlaceSnapshot(currentCandidate, candidateSupportingBlock); + if(snapshot == null) continue; + placeSnapshots.add(snapshot); - switch(placeDirection) { - case DOWN: - case UP: - if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { - candidates.add(currentCandidate.offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.SOUTH)); - } - if(options.testLock(WandOptions.LOCK.EASTWEST)) { - candidates.add(currentCandidate.offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.WEST)); - } - if(options.testLock(WandOptions.LOCK.NORTHSOUTH) && options.testLock(WandOptions.LOCK.EASTWEST)) { - candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.WEST)); - candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.WEST)); - } - break; - case NORTH: - case SOUTH: - if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { - candidates.add(currentCandidate.offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.WEST)); - } - if(options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP)); - candidates.add(currentCandidate.offset(Direction.DOWN)); - } - if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.WEST)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.WEST)); - } - break; - case EAST: - case WEST: - if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { - candidates.add(currentCandidate.offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.SOUTH)); - } - if(options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP)); - candidates.add(currentCandidate.offset(Direction.DOWN)); - } - if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.SOUTH)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.SOUTH)); - } - break; - } - } - } - catch(Exception e) { - // Can't do anything, could be anything. - // Skip if anything goes wrong. - } - } - } + switch(placeDirection) { + case DOWN: + case UP: + if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { + candidates.add(currentCandidate.offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.SOUTH)); + } + if(options.testLock(WandOptions.LOCK.EASTWEST)) { + candidates.add(currentCandidate.offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.WEST)); + } + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) && options.testLock(WandOptions.LOCK.EASTWEST)) { + candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.WEST)); + } + break; + case NORTH: + case SOUTH: + if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { + candidates.add(currentCandidate.offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.WEST)); + } + if(options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP)); + candidates.add(currentCandidate.offset(Direction.DOWN)); + } + if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.WEST)); + } + break; + case EAST: + case WEST: + if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { + candidates.add(currentCandidate.offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.SOUTH)); + } + if(options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP)); + candidates.add(currentCandidate.offset(Direction.DOWN)); + } + if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.SOUTH)); + } + break; + } + } + } + catch(Exception e) { + // Can't do anything, could be anything. + // Skip if anything goes wrong. + } + } + } } diff --git a/src/main/java/thetadev/constructionwand/job/TransductionJob.java b/src/main/java/thetadev/constructionwand/job/TransductionJob.java index 10b9d90..9ef858c 100644 --- a/src/main/java/thetadev/constructionwand/job/TransductionJob.java +++ b/src/main/java/thetadev/constructionwand/job/TransductionJob.java @@ -11,24 +11,24 @@ import thetadev.constructionwand.basics.ConfigServer; public class TransductionJob extends WandJob { - public TransductionJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { - super(player, world, rayTraceResult, wand); - } + public TransductionJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { + super(player, world, rayTraceResult, wand); + } - @Override - protected void getBlockPositionList() { - Direction placeDirection = rayTraceResult.getFace(); - BlockPos currentPos = rayTraceResult.getPos(); - BlockState supportingBlock = world.getBlockState(currentPos); + @Override + protected void getBlockPositionList() { + Direction placeDirection = rayTraceResult.getFace(); + BlockPos currentPos = rayTraceResult.getPos(); + BlockState supportingBlock = world.getBlockState(currentPos); - for(int i = 0; i< ConfigServer.getWandProperties(wandItem).getAngel(); i++) { - currentPos = currentPos.offset(placeDirection.getOpposite()); + for(int i = 0; i< ConfigServer.getWandProperties(wandItem).getAngel(); i++) { + currentPos = currentPos.offset(placeDirection.getOpposite()); - PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, supportingBlock); - if(snapshot != null) { - placeSnapshots.add(snapshot); - break; - } - } - } + PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, supportingBlock); + if(snapshot != null) { + placeSnapshots.add(snapshot); + break; + } + } + } } diff --git a/src/main/java/thetadev/constructionwand/job/UndoHistory.java b/src/main/java/thetadev/constructionwand/job/UndoHistory.java index 5aeb2ab..1eb16ea 100644 --- a/src/main/java/thetadev/constructionwand/job/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/job/UndoHistory.java @@ -1,6 +1,5 @@ package thetadev.constructionwand.job; -import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.item.ItemStack; @@ -9,12 +8,9 @@ import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvents; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.fml.network.PacketDistributor; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.ReplacementRegistry; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.network.PacketUndoBlocks; @@ -23,113 +19,113 @@ import java.util.stream.Collectors; public class UndoHistory { - private final HashMap history; + private final HashMap history; - public UndoHistory() { - history = new HashMap<>(); - } + public UndoHistory() { + history = new HashMap<>(); + } - private PlayerEntry getEntryFromPlayer(PlayerEntity player) { - return history.computeIfAbsent(player.getUniqueID(), k -> new PlayerEntry()); - } + private PlayerEntry getEntryFromPlayer(PlayerEntity player) { + return history.computeIfAbsent(player.getUniqueID(), k -> new PlayerEntry()); + } - public void add(PlayerEntity player, World world, LinkedList placeSnapshots) { - LinkedList list = getEntryFromPlayer(player).entries; - list.add(new HistoryEntry(placeSnapshots, world)); - while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); - } + public void add(PlayerEntity player, World world, LinkedList placeSnapshots) { + LinkedList list = getEntryFromPlayer(player).entries; + list.add(new HistoryEntry(placeSnapshots, world)); + while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); + } - public void removePlayer(PlayerEntity player) { - history.remove(player.getUniqueID()); - } + public void removePlayer(PlayerEntity player) { + history.remove(player.getUniqueID()); + } - public void updateClient(PlayerEntity player, boolean ctrlDown) { - World world = player.getEntityWorld(); - if(world.isRemote) return; + public void updateClient(PlayerEntity player, boolean ctrlDown) { + World world = player.getEntityWorld(); + if(world.isRemote) return; - // Set state of CTRL key - PlayerEntry playerEntry = getEntryFromPlayer(player); - playerEntry.undoActive = ctrlDown; + // Set state of CTRL key + PlayerEntry playerEntry = getEntryFromPlayer(player); + playerEntry.undoActive = ctrlDown; - LinkedList historyEntries = playerEntry.entries; - Set positions; + LinkedList historyEntries = playerEntry.entries; + Set positions; - // Send block positions of most recent entry to client - if(historyEntries.isEmpty()) positions = Collections.emptySet(); - else { - HistoryEntry entry = historyEntries.getLast(); + // Send block positions of most recent entry to client + if(historyEntries.isEmpty()) positions = Collections.emptySet(); + else { + HistoryEntry entry = historyEntries.getLast(); - if(entry == null || !entry.world.equals(world)) positions = Collections.emptySet(); - else positions = entry.getBlockPositions(); - } + if(entry == null || !entry.world.equals(world)) positions = Collections.emptySet(); + else positions = entry.getBlockPositions(); + } - PacketUndoBlocks packet = new PacketUndoBlocks(positions); - ConstructionWand.instance.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), packet); - } + PacketUndoBlocks packet = new PacketUndoBlocks(positions); + ConstructionWand.instance.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), packet); + } - public boolean isUndoActive(PlayerEntity player) { - return getEntryFromPlayer(player).undoActive; - } + public boolean isUndoActive(PlayerEntity player) { + return getEntryFromPlayer(player).undoActive; + } - public boolean undo(PlayerEntity player, World world, BlockPos pos) { - // If CTRL key is not pressed, return - PlayerEntry playerEntry = getEntryFromPlayer(player); - if(!playerEntry.undoActive) return false; + public boolean undo(PlayerEntity player, World world, BlockPos pos) { + // If CTRL key is not pressed, return + PlayerEntry playerEntry = getEntryFromPlayer(player); + if(!playerEntry.undoActive) return false; - // Get the most recent entry for undo - LinkedList historyEntries = playerEntry.entries; - if(historyEntries.isEmpty()) return false; - HistoryEntry entry = historyEntries.getLast(); + // Get the most recent entry for undo + LinkedList historyEntries = playerEntry.entries; + if(historyEntries.isEmpty()) return false; + HistoryEntry entry = historyEntries.getLast(); - if(entry.world.equals(world) && entry.getBlockPositions().contains(pos)) { - // Remove history entry, sent update to client and undo it - historyEntries.remove(entry); - updateClient(player, true); - return entry.undo(player); - } - return false; - } + if(entry.world.equals(world) && entry.getBlockPositions().contains(pos)) { + // Remove history entry, sent update to client and undo it + historyEntries.remove(entry); + updateClient(player, true); + return entry.undo(player); + } + return false; + } - private static class PlayerEntry { - public final LinkedList entries; - public boolean undoActive; + private static class PlayerEntry { + public final LinkedList entries; + public boolean undoActive; - public PlayerEntry() { - entries = new LinkedList<>(); - undoActive = false; - } - } + public PlayerEntry() { + entries = new LinkedList<>(); + undoActive = false; + } + } - private static class HistoryEntry { - public final LinkedList placeSnapshots; - public final World world; + private static class HistoryEntry { + public final LinkedList placeSnapshots; + public final World world; - public HistoryEntry(LinkedList placeSnapshots, World world) { - this.placeSnapshots = placeSnapshots; - this.world = world; - } + public HistoryEntry(LinkedList placeSnapshots, World world) { + this.placeSnapshots = placeSnapshots; + this.world = world; + } - public Set getBlockPositions() { - return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); - } + public Set getBlockPositions() { + return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); + } - public boolean undo(PlayerEntity player) { - for(ISnapshot snapshot : placeSnapshots) { - if(snapshot.restore(world, player) && !player.isCreative()) { - ItemStack stack = snapshot.getRequiredItems(); + public boolean undo(PlayerEntity player) { + for(ISnapshot snapshot : placeSnapshots) { + if(snapshot.restore(world, player) && !player.isCreative()) { + ItemStack stack = snapshot.getRequiredItems(); - if(!player.inventory.addItemStackToInventory(stack)) { - player.dropItem(stack, false); - } - } - } - player.inventory.markDirty(); + if(!player.inventory.addItemStackToInventory(stack)) { + player.dropItem(stack, false); + } + } + } + player.inventory.markDirty(); - // Play teleport sound - SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; - world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); + // Play teleport sound + SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; + world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); - return true; - } - } + return true; + } + } } diff --git a/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java b/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java index ca32780..e24ae82 100644 --- a/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java +++ b/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java @@ -11,20 +11,20 @@ import thetadev.constructionwand.basics.WandUtil; public class WandItemUseContext extends BlockItemUseContext { - public WandItemUseContext(WandJob job, BlockPos pos, BlockItem item) { - super(job.world, job.player, Hand.MAIN_HAND, new ItemStack(item), new BlockRayTraceResult(getBlockHitVec(job, pos), job.rayTraceResult.getFace(), pos, false)); - } + public WandItemUseContext(WandJob job, BlockPos pos, BlockItem item) { + super(job.world, job.player, Hand.MAIN_HAND, new ItemStack(item), new BlockRayTraceResult(getBlockHitVec(job, pos), job.rayTraceResult.getFace(), pos, false)); + } - private static Vector3d getBlockHitVec(WandJob job, BlockPos pos) { - Vector3d hitVec = job.rayTraceResult.getHitVec(); // Absolute coords of hit target + private static Vector3d getBlockHitVec(WandJob job, BlockPos pos) { + Vector3d hitVec = job.rayTraceResult.getHitVec(); // Absolute coords of hit target - Vector3d blockDelta = WandUtil.blockPosVec(job.rayTraceResult.getPos()).subtract(WandUtil.blockPosVec(pos)); // Vector between start and current block + Vector3d blockDelta = WandUtil.blockPosVec(job.rayTraceResult.getPos()).subtract(WandUtil.blockPosVec(pos)); // Vector between start and current block - return blockDelta.add(hitVec); // Absolute coords of current block hit target - } + return blockDelta.add(hitVec); // Absolute coords of current block hit target + } - @Override - public boolean canPlace() { - return replaceClicked; - } + @Override + public boolean canPlace() { + return replaceClicked; + } } diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java index 4a7e288..0839c01 100644 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ b/src/main/java/thetadev/constructionwand/job/WandJob.java @@ -1,22 +1,35 @@ package thetadev.constructionwand.job; -import net.minecraft.block.*; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.SoundType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.*; +import net.minecraft.item.BlockItem; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.state.Property; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.SlabType; -import net.minecraft.util.*; +import net.minecraft.util.EntityPredicates; +import net.minecraft.util.Hand; +import net.minecraft.util.SoundCategory; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.world.World; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.*; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.ModStats; +import thetadev.constructionwand.basics.ReplacementRegistry; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.basics.pool.*; +import thetadev.constructionwand.basics.pool.IPool; +import thetadev.constructionwand.basics.pool.OrderedPool; +import thetadev.constructionwand.basics.pool.RandomPool; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.ItemWand; @@ -26,262 +39,262 @@ import java.util.stream.Collectors; public abstract class WandJob { - protected final PlayerEntity player; - protected final World world; - protected final BlockRayTraceResult rayTraceResult; - protected ItemStack wand; - protected ItemWand wandItem; + protected final PlayerEntity player; + protected final World world; + protected final BlockRayTraceResult rayTraceResult; + protected ItemStack wand; + protected ItemWand wandItem; - // Wand options - protected WandOptions options; - protected int maxBlocks; + // Wand options + protected WandOptions options; + protected int maxBlocks; - protected HashMap itemCounts; - protected IPool itemPool; + protected HashMap itemCounts; + protected IPool itemPool; - protected LinkedList placeSnapshots; + protected LinkedList placeSnapshots; - protected WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) - { - this.player = player; - this.world = world; - this.rayTraceResult = rayTraceResult; - placeSnapshots = new LinkedList<>(); + protected WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) + { + this.player = player; + this.world = world; + this.rayTraceResult = rayTraceResult; + placeSnapshots = new LinkedList<>(); - // Get wand - if(wand == null || wand == ItemStack.EMPTY || !(wand.getItem() instanceof ItemWand)) return; - this.wand = wand; + // Get wand + if(wand == null || wand == ItemStack.EMPTY || !(wand.getItem() instanceof ItemWand)) return; + this.wand = wand; - wandItem = (ItemWand) wand.getItem(); + wandItem = (ItemWand) wand.getItem(); - // Get options - options = new WandOptions(wand); + // Get options + options = new WandOptions(wand); - // Get place item - addBlockItems(); - if(itemCounts.isEmpty()) return; + // Get place item + addBlockItems(); + if(itemCounts.isEmpty()) return; - // Get inventory supply - for(int v : itemCounts.values()) { - try { - maxBlocks = Math.addExact(maxBlocks, v); - } - catch(ArithmeticException e) { - maxBlocks = Integer.MAX_VALUE; - break; - } - } + // Get inventory supply + for(int v : itemCounts.values()) { + try { + maxBlocks = Math.addExact(maxBlocks, v); + } + catch(ArithmeticException e) { + maxBlocks = Integer.MAX_VALUE; + break; + } + } - maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand)); - if(maxBlocks == 0) return; + maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand)); + if(maxBlocks == 0) return; - getBlockPositionList(); - } + getBlockPositionList(); + } - public static WandJob getJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { - WandOptions options = new WandOptions(itemStack); + public static WandJob getJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { + WandOptions options = new WandOptions(itemStack); - if(options.mode.get() == WandOptions.MODE.ANGEL) return new TransductionJob(player, world, rayTraceResult, itemStack); - return new ConstructionJob(player, world, rayTraceResult, itemStack); - } + if(options.mode.get() == WandOptions.MODE.ANGEL) return new TransductionJob(player, world, rayTraceResult, itemStack); + return new ConstructionJob(player, world, rayTraceResult, itemStack); + } - public Set getBlockPositions() { - return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); - } + public Set getBlockPositions() { + return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); + } - public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; } + public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; } - public ItemStack getWand() { return wand; } + public ItemStack getWand() { return wand; } - private void addBlockItem(BlockItem item) { - int count = countItem(item); - if(count > 0) { - itemCounts.put(item, count); - itemPool.add(item); - } - } + private void addBlockItem(BlockItem item) { + int count = countItem(item); + if(count > 0) { + itemCounts.put(item, count); + itemPool.add(item); + } + } - private void addBlockItems() { - itemCounts = new LinkedHashMap<>(); + private void addBlockItems() { + itemCounts = new LinkedHashMap<>(); - BlockPos targetPos = rayTraceResult.getPos(); - BlockState targetState = world.getBlockState(targetPos); - Block targetBlock = targetState.getBlock(); - ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); + BlockPos targetPos = rayTraceResult.getPos(); + BlockState targetState = world.getBlockState(targetPos); + Block targetBlock = targetState.getBlock(); + ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); - // Random mode -> add all items from hotbar - if(options.random.get()) { - itemPool = new RandomPool<>(player.getRNG()); + // Random mode -> add all items from hotbar + if(options.random.get()) { + itemPool = new RandomPool<>(player.getRNG()); - for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { - if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); - } - } - else { - itemPool = new OrderedPool<>(); + for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { + if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); + } + } + else { + itemPool = new OrderedPool<>(); - // Block in offhand -> override - if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { - addBlockItem((BlockItem) offhandStack.getItem()); - } - // Otherwise use target block - else { - Item item = targetBlock.asItem(); - if(item instanceof BlockItem) { - addBlockItem((BlockItem) item); + // Block in offhand -> override + if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { + addBlockItem((BlockItem) offhandStack.getItem()); + } + // Otherwise use target block + else { + Item item = targetBlock.asItem(); + if(item instanceof BlockItem) { + addBlockItem((BlockItem) item); - // Add replacement items - if(options.match.get() != WandOptions.MATCH.EXACT) { - for(Item it : ReplacementRegistry.getMatchingSet(item)) { - if(it instanceof BlockItem) addBlockItem((BlockItem) it); - } - } - } - } - } - } + // Add replacement items + if(options.match.get() != WandOptions.MATCH.EXACT) { + for(Item it : ReplacementRegistry.getMatchingSet(item)) { + if(it instanceof BlockItem) addBlockItem((BlockItem) it); + } + } + } + } + } + } - private int countItem(Item item) { - if(player.inventory == null || player.inventory.mainInventory == null) return 0; - if(player.isCreative()) return Integer.MAX_VALUE; + private int countItem(Item item) { + if(player.inventory == null || player.inventory.mainInventory == null) return 0; + if(player.isCreative()) return Integer.MAX_VALUE; - int total = 0; - ContainerManager containerManager = ConstructionWand.instance.containerManager; - List inventory = WandUtil.getFullInv(player); + int total = 0; + ContainerManager containerManager = ConstructionWand.instance.containerManager; + List inventory = WandUtil.getFullInv(player); - for(ItemStack stack : inventory) { - if(stack == null) continue; + for(ItemStack stack : inventory) { + if(stack == null) continue; - if(WandUtil.stackEquals(stack, item)) { - total += stack.getCount(); - } - else { - int amount = containerManager.countItems(player, new ItemStack(item), stack); - if(amount == Integer.MAX_VALUE) return Integer.MAX_VALUE; - total += amount; - } - } - return total; - } + if(WandUtil.stackEquals(stack, item)) { + total += stack.getCount(); + } + else { + int amount = containerManager.countItems(player, new ItemStack(item), stack); + if(amount == Integer.MAX_VALUE) return Integer.MAX_VALUE; + total += amount; + } + } + return total; + } - // Attempts to take specified number of items, returns number of missing items - private int takeItemStack(ItemStack stack) - { - int count = stack.getCount(); - Item item = stack.getItem(); + // Attempts to take specified number of items, returns number of missing items + private int takeItemStack(ItemStack stack) + { + int count = stack.getCount(); + Item item = stack.getItem(); - if(player.inventory == null || player.inventory.mainInventory == null) return count; - if(player.isCreative()) return 0; + if(player.inventory == null || player.inventory.mainInventory == null) return count; + if(player.isCreative()) return 0; - List hotbar = WandUtil.getHotbarWithOffhand(player); - List mainInv = WandUtil.getMainInv(player); + List hotbar = WandUtil.getHotbarWithOffhand(player); + List mainInv = WandUtil.getMainInv(player); - // Take items from main inv, loose items first - count = takeItemsInvList(count, item, mainInv, false); - count = takeItemsInvList(count, item, mainInv, true); + // Take items from main inv, loose items first + count = takeItemsInvList(count, item, mainInv, false); + count = takeItemsInvList(count, item, mainInv, true); - // Take items from hotbar, containers first - count = takeItemsInvList(count, item, hotbar, true); - count = takeItemsInvList(count, item, hotbar, false); + // Take items from hotbar, containers first + count = takeItemsInvList(count, item, hotbar, true); + count = takeItemsInvList(count, item, hotbar, false); - return count; - } + return count; + } - private int takeItemsInvList(int count, Item item, List inv, boolean container) { - ContainerManager containerManager = ConstructionWand.instance.containerManager; + private int takeItemsInvList(int count, Item item, List inv, boolean container) { + ContainerManager containerManager = ConstructionWand.instance.containerManager; - for(ItemStack stack : inv) { - if(count == 0) break; + for(ItemStack stack : inv) { + if(count == 0) break; - if(container) { - int nCount = containerManager.useItems(player, new ItemStack(item), stack, count); - count = nCount; - } + if(container) { + int nCount = containerManager.useItems(player, new ItemStack(item), stack, count); + count = nCount; + } - if(!container && WandUtil.stackEquals(stack, item)) { - int toTake = Math.min(count, stack.getCount()); - stack.shrink(toTake); - count -= toTake; - player.inventory.markDirty(); - } - } - return count; - } + if(!container && WandUtil.stackEquals(stack, item)) { + int toTake = Math.min(count, stack.getCount()); + stack.shrink(toTake); + count -= toTake; + player.inventory.markDirty(); + } + } + return count; + } - protected abstract void getBlockPositionList(); + protected abstract void getBlockPositionList(); - // Get PlaceSnapshot, or null if no block can be placed - @Nullable - protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { - // Is position out of world? - if(!world.isBlockPresent(pos)) return null; + // Get PlaceSnapshot, or null if no block can be placed + @Nullable + protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { + // Is position out of world? + if(!world.isBlockPresent(pos)) return null; - // Is block modifiable? - if(!world.isBlockModifiable(player, pos)) return null; + // Is block modifiable? + if(!world.isBlockModifiable(player, pos)) return null; - // If replace mode is off, target has to be air - if(!options.replace.get() && !world.isAirBlock(pos)) return null; + // If replace mode is off, target has to be air + if(!options.replace.get() && !world.isAirBlock(pos)) return null; - // Limit placement range - if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) return null; + // Limit placement range + if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) return null; - itemPool.reset(); + itemPool.reset(); - while(true) { - // Draw item from pool (returns null if none are left) - BlockItem item = itemPool.draw(); - if(item == null) return null; + while(true) { + // Draw item from pool (returns null if none are left) + BlockItem item = itemPool.draw(); + if(item == null) return null; - int count = itemCounts.get(item); - if(count == 0) continue; + int count = itemCounts.get(item); + if(count == 0) continue; - // Is block at pos replaceable? - BlockItemUseContext ctx = new WandItemUseContext(this, pos, item); - if(!ctx.canPlace()) continue; + // Is block at pos replaceable? + BlockItemUseContext ctx = new WandItemUseContext(this, pos, item); + if(!ctx.canPlace()) continue; - // Can block be placed? - BlockState blockState = item.getBlock().getStateForPlacement(ctx); - if(blockState == null) continue; + // Can block be placed? + BlockState blockState = item.getBlock().getStateForPlacement(ctx); + if(blockState == null) continue; - // Forbidden Tile Entity? - if(!WandUtil.isTEAllowed(blockState)) continue; + // Forbidden Tile Entity? + if(!WandUtil.isTEAllowed(blockState)) continue; - // No entities colliding? - VoxelShape shape = blockState.getCollisionShape(world, pos); - if(!shape.isEmpty()) { - AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) continue; - } + // No entities colliding? + VoxelShape shape = blockState.getCollisionShape(world, pos); + if(!shape.isEmpty()) { + AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); + if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) continue; + } - // Adjust blockstate to neighbors - blockState = Block.getValidBlockForPosition(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; + // Adjust blockstate to neighbors + blockState = Block.getValidBlockForPosition(blockState, world, pos); + if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; - // Copy block properties from supporting block - if(options.direction.get() == WandOptions.DIRECTION.TARGET) { - // Block properties to be copied (alignment/rotation properties) - for(Property property : new Property[] { - BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, - BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) - { - if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { - blockState = blockState.with(property, supportingBlock.get(property)); - } - } + // Copy block properties from supporting block + if(options.direction.get() == WandOptions.DIRECTION.TARGET) { + // Block properties to be copied (alignment/rotation properties) + for(Property property : new Property[] { + BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, + BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) + { + if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { + blockState = blockState.with(property, supportingBlock.get(property)); + } + } - // Dont dupe double slabs - if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { - SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); - if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); - } - } + // Dont dupe double slabs + if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { + SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); + if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); + } + } - // Reduce item count - if(count < Integer.MAX_VALUE) itemCounts.merge(item, -1, Integer::sum); - return new PlaceSnapshot(blockState, pos, item); - } - } + // Reduce item count + if(count < Integer.MAX_VALUE) itemCounts.merge(item, -1, Integer::sum); + return new PlaceSnapshot(blockState, pos, item); + } + } protected boolean matchBlocks(Block b1, Block b2) { switch(options.match.get()) { @@ -295,33 +308,33 @@ public abstract class WandJob public boolean doIt() { LinkedList executed = new LinkedList<>(); - for(ISnapshot snapshot : placeSnapshots) { - if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; + for(ISnapshot snapshot : placeSnapshots) { + if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; - if(snapshot.execute(world, player)) { - // If the item cant be taken, undo the placement - if(takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); - else { - ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+ - snapshot.getBlockState().getBlock().toString()); - snapshot.forceRestore(world); - } + if(snapshot.execute(world, player)) { + // If the item cant be taken, undo the placement + if(takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); + else { + ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+ + snapshot.getBlockState().getBlock().toString()); + snapshot.forceRestore(world); + } - wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); - player.addStat(ModStats.USE_WAND); - } - } - placeSnapshots = executed; + wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); + player.addStat(ModStats.USE_WAND); + } + } + placeSnapshots = executed; - // Play place sound - if(!placeSnapshots.isEmpty()) { - SoundType sound = placeSnapshots.getFirst().getBlockState().getSoundType(); - world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); - } + // Play place sound + if(!placeSnapshots.isEmpty()) { + SoundType sound = placeSnapshots.getFirst().getBlockState().getSoundType(); + world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); + } - // Add to job history for undo - if(placeSnapshots.size() > 1) ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); + // Add to job history for undo + if(placeSnapshots.size() > 1) ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); - return !placeSnapshots.isEmpty(); - } + return !placeSnapshots.isEmpty(); + } } diff --git a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java index b0b888c..0c17d5b 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java +++ b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java @@ -9,31 +9,31 @@ import java.util.function.Supplier; public class PacketQueryUndo { - public boolean undoPressed; + public boolean undoPressed; - public PacketQueryUndo(boolean undoPressed) { - this.undoPressed = undoPressed; - } + public PacketQueryUndo(boolean undoPressed) { + this.undoPressed = undoPressed; + } - public static void encode(PacketQueryUndo msg, PacketBuffer buffer) { - buffer.writeBoolean(msg.undoPressed); - } + public static void encode(PacketQueryUndo msg, PacketBuffer buffer) { + buffer.writeBoolean(msg.undoPressed); + } - public static PacketQueryUndo decode(PacketBuffer buffer) { - return new PacketQueryUndo(buffer.readBoolean()); - } + public static PacketQueryUndo decode(PacketBuffer buffer) { + return new PacketQueryUndo(buffer.readBoolean()); + } - public static class Handler - { - public static void handle(final PacketQueryUndo msg, final Supplier ctx) { - if(!ctx.get().getDirection().getReceptionSide().isServer()) return; + public static class Handler + { + public static void handle(final PacketQueryUndo msg, final Supplier ctx) { + if(!ctx.get().getDirection().getReceptionSide().isServer()) return; - ServerPlayerEntity player = ctx.get().getSender(); - if(player == null) return; + ServerPlayerEntity player = ctx.get().getSender(); + if(player == null) return; - ConstructionWand.instance.undoHistory.updateClient(player, msg.undoPressed); + ConstructionWand.instance.undoHistory.updateClient(player, msg.undoPressed); - //ConstructionWand.LOGGER.debug("Undo queried"); - } - } + //ConstructionWand.LOGGER.debug("Undo queried"); + } + } } diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index 5d535f0..2c244fc 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -11,38 +11,38 @@ import java.util.function.Supplier; public class PacketUndoBlocks { - public HashSet undoBlocks; + public HashSet undoBlocks; - public PacketUndoBlocks(Set undoBlocks) { - this.undoBlocks = new HashSet<>(undoBlocks); - } - private PacketUndoBlocks(HashSet undoBlocks) { - this.undoBlocks = undoBlocks; - } + public PacketUndoBlocks(Set undoBlocks) { + this.undoBlocks = new HashSet<>(undoBlocks); + } + private PacketUndoBlocks(HashSet undoBlocks) { + this.undoBlocks = undoBlocks; + } - public static void encode(PacketUndoBlocks msg, PacketBuffer buffer) { - for(BlockPos pos : msg.undoBlocks) { - buffer.writeBlockPos(pos); - } - } + public static void encode(PacketUndoBlocks msg, PacketBuffer buffer) { + for(BlockPos pos : msg.undoBlocks) { + buffer.writeBlockPos(pos); + } + } - public static PacketUndoBlocks decode(PacketBuffer buffer) { - HashSet undoBlocks = new HashSet<>(); + public static PacketUndoBlocks decode(PacketBuffer buffer) { + HashSet undoBlocks = new HashSet<>(); - while(buffer.isReadable()) { - undoBlocks.add(buffer.readBlockPos()); - } - return new PacketUndoBlocks(undoBlocks); - } + while(buffer.isReadable()) { + undoBlocks.add(buffer.readBlockPos()); + } + return new PacketUndoBlocks(undoBlocks); + } - public static class Handler { - public static void handle(final PacketUndoBlocks msg, final Supplier ctx) { - if(!ctx.get().getDirection().getReceptionSide().isClient()) return; + public static class Handler { + public static void handle(final PacketUndoBlocks msg, final Supplier ctx) { + if(!ctx.get().getDirection().getReceptionSide().isClient()) return; - //ConstructionWand.LOGGER.debug("PacketUndoBlocks received, Blocks: " + msg.undoBlocks.size()); - ConstructionWand.instance.renderBlockPreview.undoBlocks = msg.undoBlocks; + //ConstructionWand.LOGGER.debug("PacketUndoBlocks received, Blocks: " + msg.undoBlocks.size()); + ConstructionWand.instance.renderBlockPreview.undoBlocks = msg.undoBlocks; - ctx.get().setPacketHandled(true); - } - } + ctx.get().setPacketHandled(true); + } + } } diff --git a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java index 4fd632c..c9612e4 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java +++ b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java @@ -13,48 +13,48 @@ import java.util.function.Supplier; public class PacketWandOption { - public final String key; - public final String value; - public final boolean notify; + public final String key; + public final String value; + public final boolean notify; - public PacketWandOption(IOption option, boolean notify) { - this(option.getKey(), option.getValueString(), notify); - } + public PacketWandOption(IOption option, boolean notify) { + this(option.getKey(), option.getValueString(), notify); + } - private PacketWandOption(String key, String value, boolean notify) { - this.key = key; - this.value = value; - this.notify = notify; - } + private PacketWandOption(String key, String value, boolean notify) { + this.key = key; + this.value = value; + this.notify = notify; + } - public static void encode(PacketWandOption msg, PacketBuffer buffer) { - buffer.writeString(msg.key); - buffer.writeString(msg.value); - buffer.writeBoolean(msg.notify); - } + public static void encode(PacketWandOption msg, PacketBuffer buffer) { + buffer.writeString(msg.key); + buffer.writeString(msg.value); + buffer.writeBoolean(msg.notify); + } - public static PacketWandOption decode(PacketBuffer buffer) { - return new PacketWandOption(buffer.readString(100), buffer.readString(100), buffer.readBoolean()); - } + public static PacketWandOption decode(PacketBuffer buffer) { + return new PacketWandOption(buffer.readString(100), buffer.readString(100), buffer.readBoolean()); + } - public static class Handler - { - public static void handle(final PacketWandOption msg, final Supplier ctx) { - if(!ctx.get().getDirection().getReceptionSide().isServer()) return; + public static class Handler + { + public static void handle(final PacketWandOption msg, final Supplier ctx) { + if(!ctx.get().getDirection().getReceptionSide().isServer()) return; - ServerPlayerEntity player = ctx.get().getSender(); - if(player == null) return; + ServerPlayerEntity player = ctx.get().getSender(); + if(player == null) return; - ItemStack wand = WandUtil.holdingWand(player); - if(wand == null) return; - WandOptions options = new WandOptions(wand); + ItemStack wand = WandUtil.holdingWand(player); + if(wand == null) return; + WandOptions options = new WandOptions(wand); - IOption option = options.get(msg.key); - if(option == null) return; - option.setValueString(msg.value); + IOption option = options.get(msg.key); + if(option == null) return; + option.setValueString(msg.value); - if(msg.notify) ItemWand.optionMessage(player, option); - player.inventory.markDirty(); - } - } + if(msg.notify) ItemWand.optionMessage(player, option); + player.inventory.markDirty(); + } + } } From bb3d36fa56a9b0f76ba28e56b179a920ac7852af Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Feb 2021 02:20:53 +0100 Subject: [PATCH 23/78] BIG Refactoring. Modular WandActions and WandSuppliers. Wand Cores and Wand Reservoirs can be added to your wand, they determine which action and supplier gets used. --- .../recipes/dynamic/wand_upgrade.json | 3 + .../constructionwand/ConstructionWand.java | 10 +- .../api/IContainerHandler.java | 5 +- .../constructionwand/api/IWandAction.java | 10 + .../constructionwand/api/IWandCore.java | 10 + .../constructionwand/api/IWandReservoir.java | 8 + .../constructionwand/api/IWandSupplier.java | 21 ++ .../constructionwand/api/IWandUpgrade.java | 8 + .../constructionwand/basics/ConfigServer.java | 2 + .../basics/ReplacementRegistry.java | 4 +- .../constructionwand/basics/WandUtil.java | 135 ++++++- .../basics/option/IOption.java | 8 + .../basics/option/OptionEnum.java | 2 +- .../basics/option/WandOptions.java | 52 ++- .../basics/option/WandUpgrades.java | 75 ++++ .../basics/option/WandUpgradesSelectable.java | 83 +++++ .../constructionwand/basics/pool/IPool.java | 2 + .../constructionwand/client/ClientEvents.java | 10 +- .../client/RenderBlockPreview.java | 19 +- .../constructionwand/client/RenderTypes.java | 2 +- .../constructionwand/client/ScreenWand.java | 24 +- .../containers/ContainerManager.java | 5 +- .../handlers/HandlerCapability.java | 6 +- .../handlers/HandlerShulkerbox.java | 15 +- .../crafting/RecipeWandUpgrade.java | 76 ++++ .../thetadev/constructionwand/data/Inp.java | 1 + .../data/RecipeGenerator.java | 16 +- .../constructionwand/items/ItemBase.java | 11 + .../constructionwand/items/ModItems.java | 56 ++- .../items/core/CoreDefault.java | 26 ++ .../items/core/ItemCoreAngel.java | 24 ++ .../items/reservoir/ItemReservoirRandom.java | 19 + .../items/reservoir/ReservoirDefault.java | 21 ++ .../items/{ => wand}/ItemWand.java | 53 +-- .../items/{ => wand}/ItemWandBasic.java | 10 +- .../items/{ => wand}/ItemWandInfinity.java | 7 +- .../constructionwand/job/AngelJob.java | 42 --- .../constructionwand/job/TransductionJob.java | 34 -- .../job/WandItemUseContext.java | 30 -- .../constructionwand/job/WandJob.java | 340 ------------------ .../network/PacketUndoBlocks.java | 4 +- .../network/PacketWandOption.java | 2 +- .../wand/WandItemUseContext.java | 33 ++ .../constructionwand/wand/WandJob.java | 102 ++++++ .../wand/action/ActionAngel.java | 78 ++++ .../action/ActionConstruction.java} | 48 ++- .../wand/supplier/SupplierInventory.java | 168 +++++++++ .../wand/supplier/SupplierRandom.java | 32 ++ .../{job => wand/undo}/DestroySnapshot.java | 2 +- .../{job => wand/undo}/ISnapshot.java | 6 +- .../{job => wand/undo}/PlaceSnapshot.java | 2 +- .../{job => wand/undo}/UndoHistory.java | 14 +- .../assets/constructionwand/lang/en_us.json | 25 +- 53 files changed, 1200 insertions(+), 601 deletions(-) create mode 100644 src/generated/resources/data/constructionwand/recipes/dynamic/wand_upgrade.json create mode 100644 src/main/java/thetadev/constructionwand/api/IWandAction.java create mode 100644 src/main/java/thetadev/constructionwand/api/IWandCore.java create mode 100644 src/main/java/thetadev/constructionwand/api/IWandReservoir.java create mode 100644 src/main/java/thetadev/constructionwand/api/IWandSupplier.java create mode 100644 src/main/java/thetadev/constructionwand/api/IWandUpgrade.java create mode 100644 src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java create mode 100644 src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java create mode 100644 src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java create mode 100644 src/main/java/thetadev/constructionwand/items/ItemBase.java create mode 100644 src/main/java/thetadev/constructionwand/items/core/CoreDefault.java create mode 100644 src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java create mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java create mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java rename src/main/java/thetadev/constructionwand/items/{ => wand}/ItemWand.java (70%) rename src/main/java/thetadev/constructionwand/items/{ => wand}/ItemWandBasic.java (68%) rename src/main/java/thetadev/constructionwand/items/{ => wand}/ItemWandInfinity.java (67%) delete mode 100644 src/main/java/thetadev/constructionwand/job/AngelJob.java delete mode 100644 src/main/java/thetadev/constructionwand/job/TransductionJob.java delete mode 100644 src/main/java/thetadev/constructionwand/job/WandItemUseContext.java delete mode 100644 src/main/java/thetadev/constructionwand/job/WandJob.java create mode 100644 src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java create mode 100644 src/main/java/thetadev/constructionwand/wand/WandJob.java create mode 100644 src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java rename src/main/java/thetadev/constructionwand/{job/ConstructionJob.java => wand/action/ActionConstruction.java} (76%) create mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java create mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java rename src/main/java/thetadev/constructionwand/{job => wand/undo}/DestroySnapshot.java (96%) rename src/main/java/thetadev/constructionwand/{job => wand/undo}/ISnapshot.java (90%) rename src/main/java/thetadev/constructionwand/{job => wand/undo}/PlaceSnapshot.java (96%) rename src/main/java/thetadev/constructionwand/{job => wand/undo}/UndoHistory.java (92%) diff --git a/src/generated/resources/data/constructionwand/recipes/dynamic/wand_upgrade.json b/src/generated/resources/data/constructionwand/recipes/dynamic/wand_upgrade.json new file mode 100644 index 0000000..e164828 --- /dev/null +++ b/src/generated/resources/data/constructionwand/recipes/dynamic/wand_upgrade.json @@ -0,0 +1,3 @@ +{ + "type": "constructionwand:wand_upgrade" +} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index ae183f8..cd11fe8 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -21,10 +21,10 @@ import thetadev.constructionwand.client.RenderBlockPreview; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.containers.ContainerRegistrar; import thetadev.constructionwand.items.ModItems; -import thetadev.constructionwand.job.UndoHistory; import thetadev.constructionwand.network.PacketQueryUndo; import thetadev.constructionwand.network.PacketUndoBlocks; import thetadev.constructionwand.network.PacketWandOption; +import thetadev.constructionwand.wand.undo.UndoHistory; @Mod(ConstructionWand.MODID) @@ -56,12 +56,11 @@ public class ConstructionWand ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ConfigClient.SPEC); } - private void commonSetup(final FMLCommonSetupEvent event) - { + private void commonSetup(final FMLCommonSetupEvent event) { LOGGER.info("ConstructionWand says hello - may the odds be ever in your favor."); // Register packets - HANDLER = NetworkRegistry.newSimpleChannel(new ResourceLocation(MODID, "main"), ()->PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals); + HANDLER = NetworkRegistry.newSimpleChannel(new ResourceLocation(MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals); int packetIndex = 0; HANDLER.registerMessage(packetIndex++, PacketUndoBlocks.class, PacketUndoBlocks::encode, PacketUndoBlocks::decode, PacketUndoBlocks.Handler::handle); HANDLER.registerMessage(packetIndex++, PacketQueryUndo.class, PacketQueryUndo::encode, PacketQueryUndo::decode, PacketQueryUndo.Handler::handle); @@ -77,8 +76,7 @@ public class ConstructionWand ModStats.register(); } - private void clientSetup(final FMLClientSetupEvent event) - { + private void clientSetup(final FMLClientSetupEvent event) { renderBlockPreview = new RenderBlockPreview(); MinecraftForge.EVENT_BUS.register(renderBlockPreview); MinecraftForge.EVENT_BUS.register(new ClientEvents()); diff --git a/src/main/java/thetadev/constructionwand/api/IContainerHandler.java b/src/main/java/thetadev/constructionwand/api/IContainerHandler.java index 31006b7..598d080 100644 --- a/src/main/java/thetadev/constructionwand/api/IContainerHandler.java +++ b/src/main/java/thetadev/constructionwand/api/IContainerHandler.java @@ -3,12 +3,11 @@ package thetadev.constructionwand.api; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; -/** - * Created by james on 28/12/16. - */ public interface IContainerHandler { boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack); + int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack); + int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count); } diff --git a/src/main/java/thetadev/constructionwand/api/IWandAction.java b/src/main/java/thetadev/constructionwand/api/IWandAction.java new file mode 100644 index 0000000..7878d93 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/api/IWandAction.java @@ -0,0 +1,10 @@ +package thetadev.constructionwand.api; + +import thetadev.constructionwand.wand.undo.ISnapshot; + +import java.util.List; + +public interface IWandAction +{ + List getSnapshots(IWandSupplier supplier); +} diff --git a/src/main/java/thetadev/constructionwand/api/IWandCore.java b/src/main/java/thetadev/constructionwand/api/IWandCore.java new file mode 100644 index 0000000..f1175ba --- /dev/null +++ b/src/main/java/thetadev/constructionwand/api/IWandCore.java @@ -0,0 +1,10 @@ +package thetadev.constructionwand.api; + +import thetadev.constructionwand.wand.WandJob; + +public interface IWandCore extends IWandUpgrade +{ + int getColor(); + + IWandAction getWandAction(WandJob wandJob); +} diff --git a/src/main/java/thetadev/constructionwand/api/IWandReservoir.java b/src/main/java/thetadev/constructionwand/api/IWandReservoir.java new file mode 100644 index 0000000..4cc30e1 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/api/IWandReservoir.java @@ -0,0 +1,8 @@ +package thetadev.constructionwand.api; + +import thetadev.constructionwand.wand.WandJob; + +public interface IWandReservoir extends IWandUpgrade +{ + IWandSupplier getWandSupplier(WandJob job); +} diff --git a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java new file mode 100644 index 0000000..9e953ff --- /dev/null +++ b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java @@ -0,0 +1,21 @@ +package thetadev.constructionwand.api; + +import net.minecraft.block.BlockState; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; + +import javax.annotation.Nullable; + +public interface IWandSupplier +{ + void getSupply(@Nullable BlockItem target); + + int getMaxBlocks(); + + @Nullable + PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock); + + int takeItemStack(ItemStack stack); +} diff --git a/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java b/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java new file mode 100644 index 0000000..48b7a8d --- /dev/null +++ b/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java @@ -0,0 +1,8 @@ +package thetadev.constructionwand.api; + +import net.minecraft.util.ResourceLocation; + +public interface IWandUpgrade +{ + ResourceLocation getRegistryName(); +} diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index d1bed30..4b2b6ec 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -67,9 +67,11 @@ public class ConfigServer public int getDurability() { return durability == null ? -1 : durability.get(); } + public int getLimit() { return limit == null ? 0 : limit.get(); } + public int getAngel() { return angel == null ? 0 : angel.get(); } diff --git a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java index d6c2f29..e767913 100644 --- a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java +++ b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java @@ -18,10 +18,10 @@ public class ReplacementRegistry if(!(key instanceof String)) continue; HashSet set = new HashSet<>(); - for(String id : ((String)key).split(";")) { + for(String id : ((String) key).split(";")) { Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(id)); if(item == null) { - ConstructionWand.LOGGER.warn("Replacement Registry: Could not find item "+id); + ConstructionWand.LOGGER.warn("Replacement Registry: Could not find item " + id); continue; } set.add(item); diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 67a81d8..8829b5f 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -1,23 +1,40 @@ package thetadev.constructionwand.basics; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; +import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.state.Property; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.state.properties.SlabType; import net.minecraft.stats.Stats; +import net.minecraft.util.Direction; +import net.minecraft.util.EntityPredicates; import net.minecraft.util.Hand; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.items.ItemWand; +import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.containers.ContainerManager; +import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.wand.WandItemUseContext; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -36,9 +53,10 @@ public class WandUtil if(player.getHeldItem(Hand.MAIN_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.MAIN_HAND).getItem() instanceof ItemWand) { return player.getHeldItem(Hand.MAIN_HAND); } - else if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { - return player.getHeldItem(Hand.OFF_HAND); - } + else + if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { + return player.getHeldItem(Hand.OFF_HAND); + } return null; } @@ -47,7 +65,7 @@ public class WandUtil } public static Vector3d entityPositionVec(Entity entity) { - return new Vector3d(entity.getPosX(), entity.getPosY() - entity.getYOffset() + entity.getHeight()/2, entity.getPosZ()); + return new Vector3d(entity.getPosX(), entity.getPosY() - entity.getYOffset() + entity.getHeight() / 2, entity.getPosZ()); } public static Vector3d blockPosVec(BlockPos pos) { @@ -138,4 +156,111 @@ public class WandUtil } return false; } + + public static int countItem(PlayerEntity player, Item item) { + if(player.inventory == null || player.inventory.mainInventory == null) return 0; + if(player.isCreative()) return Integer.MAX_VALUE; + + int total = 0; + ContainerManager containerManager = ConstructionWand.instance.containerManager; + List inventory = WandUtil.getFullInv(player); + + for(ItemStack stack : inventory) { + if(stack == null) continue; + + if(WandUtil.stackEquals(stack, item)) { + total += stack.getCount(); + } + else { + int amount = containerManager.countItems(player, new ItemStack(item), stack); + if(amount == Integer.MAX_VALUE) return Integer.MAX_VALUE; + total += amount; + } + } + return total; + } + + public static boolean matchBlocks(WandOptions options, Block b1, Block b2) { + switch(options.match.get()) { + case EXACT: + return b1 == b2; + case SIMILAR: + return ReplacementRegistry.matchBlocks(b1, b2); + case ANY: + return b1 != Blocks.AIR && b2 != Blocks.AIR; + } + return false; + } + + public static boolean isPositionPlaceable(World world, PlayerEntity player, BlockPos pos, + BlockRayTraceResult rayTraceResult, WandOptions options) { + // Is position out of world? + if(!world.isBlockPresent(pos)) return false; + + // Is block modifiable? + if(!world.isBlockModifiable(player, pos)) return false; + + // If replace mode is off, target has to be air + if(!options.replace.get() && !world.isAirBlock(pos)) return false; + + // Limit placement range + if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) + return false; + + return true; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Nullable + public static PlaceSnapshot getPlaceSnapshot(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + BlockPos pos, BlockItem item, + BlockState supportingBlock, WandOptions options) { + // Is block at pos replaceable? + BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); + if(!ctx.canPlace()) return null; + + // Can block be placed? + BlockState blockState = item.getBlock().getStateForPlacement(ctx); + if(blockState == null) return null; + + // Forbidden Tile Entity? + if(!isTEAllowed(blockState)) return null; + + // No entities colliding? + VoxelShape shape = blockState.getCollisionShape(world, pos); + if(!shape.isEmpty()) { + AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); + if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) + return null; + } + + // Adjust blockstate to neighbors + blockState = Block.getValidBlockForPosition(blockState, world, pos); + if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null; + + // Copy block properties from supporting block + if(options.direction.get() == WandOptions.DIRECTION.TARGET) { + // Block properties to be copied (alignment/rotation properties) + + for(Property property : new Property[]{ + BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, + BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) { + if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { + blockState = blockState.with(property, supportingBlock.get(property)); + } + } + + // Dont dupe double slabs + if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { + SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); + if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); + } + } + + return new PlaceSnapshot(blockState, pos, item); + } + + public static Direction fromVector(Vector3d vector) { + return Direction.getFacingFromVector(vector.x, vector.y, vector.z); + } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/IOption.java b/src/main/java/thetadev/constructionwand/basics/option/IOption.java index 4d921bc..e35365e 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/IOption.java +++ b/src/main/java/thetadev/constructionwand/basics/option/IOption.java @@ -5,23 +5,31 @@ import thetadev.constructionwand.ConstructionWand; public interface IOption { String getKey(); + String getValueString(); + void setValueString(String val); default String getKeyTranslation() { return ConstructionWand.MODID + ".option." + getKey(); } + default String getValueTranslation() { return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString(); } + default String getDescTranslation() { return ConstructionWand.MODID + ".option." + getKey() + "." + getValueString() + ".desc"; } boolean isEnabled(); + void set(T val); + T get(); + T next(boolean dir); + default T next() { return next(true); } diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java index e69fdb3..7042554 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java @@ -61,7 +61,7 @@ public class OptionEnum> implements IOption @Override public E next(boolean dir) { E[] enumValues = enumClass.getEnumConstants(); - int i = value.ordinal() + (dir ? 1:-1); + int i = value.ordinal() + (dir ? 1 : -1); if(i < 0) i += enumValues.length; set(enumValues[i % enumValues.length]); return value; diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java index 0982f04..54c8707 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -4,9 +4,13 @@ import net.minecraft.block.Block; import net.minecraft.block.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; -import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.api.IWandCore; +import thetadev.constructionwand.api.IWandReservoir; +import thetadev.constructionwand.api.IWandUpgrade; import thetadev.constructionwand.basics.ReplacementRegistry; -import thetadev.constructionwand.items.ItemWand; +import thetadev.constructionwand.items.core.CoreDefault; +import thetadev.constructionwand.items.reservoir.ReservoirDefault; +import thetadev.constructionwand.items.wand.ItemWand; import javax.annotation.Nullable; @@ -16,11 +20,6 @@ public class WandOptions private static final String TAG_ROOT = "wand_options"; - public enum MODE - { - DEFAULT, - ANGEL - } public enum LOCK { HORIZONTAL, @@ -29,11 +28,13 @@ public class WandOptions EASTWEST, NOLOCK } + public enum DIRECTION { TARGET, PLAYER } + public enum MATCH { EXACT, @@ -41,12 +42,13 @@ public class WandOptions ANY } - public final OptionEnum mode; + public final WandUpgradesSelectable cores; + public final WandUpgradesSelectable reservoirs; + public final OptionEnum lock; public final OptionEnum direction; public final OptionBoolean replace; public final OptionEnum match; - public final OptionBoolean random; public final IOption[] allOptions; @@ -54,18 +56,19 @@ public class WandOptions ItemWand wand = (ItemWand) wandStack.getItem(); tag = wandStack.getOrCreateChildTag(TAG_ROOT); - mode = new OptionEnum<>(tag, "mode", MODE.class, MODE.DEFAULT, ConfigServer.getWandProperties(wand).getAngel() > 0); + cores = new WandUpgradesSelectable<>(tag, "cores", new CoreDefault()); + reservoirs = new WandUpgradesSelectable<>(tag, "reservoirs", new ReservoirDefault()); + lock = new OptionEnum<>(tag, "lock", LOCK.class, LOCK.NOLOCK); direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.TARGET); replace = new OptionBoolean(tag, "replace", true); match = new OptionEnum<>(tag, "match", MATCH.class, MATCH.SIMILAR); - random = new OptionBoolean(tag, "random", false); - allOptions = new IOption[]{mode, lock, direction, replace, match, random}; + allOptions = new IOption[]{cores, reservoirs, lock, direction, replace, match}; } @Nullable - public IOption get(String key){ + public IOption get(String key) { for(IOption option : allOptions) { if(option.getKey().equals(key)) return option; } @@ -79,10 +82,27 @@ public class WandOptions public boolean matchBlocks(Block b1, Block b2) { switch(match.get()) { - case EXACT: return b1 == b2; - case SIMILAR: return ReplacementRegistry.matchBlocks(b1, b2); - case ANY: return b1 != Blocks.AIR && b2 != Blocks.AIR; + case EXACT: + return b1 == b2; + case SIMILAR: + return ReplacementRegistry.matchBlocks(b1, b2); + case ANY: + return b1 != Blocks.AIR && b2 != Blocks.AIR; } return false; } + + public boolean hasUpgrade(IWandUpgrade upgrade) { + if(upgrade instanceof IWandCore) return cores.hasUpgrade((IWandCore) upgrade); + else + if(upgrade instanceof IWandReservoir) return reservoirs.hasUpgrade((IWandReservoir) upgrade); + return false; + } + + public boolean addUpgrade(IWandUpgrade upgrade) { + if(upgrade instanceof IWandCore) return cores.addUpgrade((IWandCore) upgrade); + else + if(upgrade instanceof IWandReservoir) return reservoirs.addUpgrade((IWandReservoir) upgrade); + return false; + } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java b/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java new file mode 100644 index 0000000..4f1e900 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java @@ -0,0 +1,75 @@ +package thetadev.constructionwand.basics.option; + +import net.minecraft.item.Item; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.StringNBT; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.registries.ForgeRegistries; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandUpgrade; + +import java.util.ArrayList; + +public class WandUpgrades +{ + protected final CompoundNBT tag; + protected final String key; + protected final ArrayList upgrades; + protected final T dval; + + public WandUpgrades(CompoundNBT tag, String key, T dval) { + this.tag = tag; + this.key = key; + this.dval = dval; + + upgrades = new ArrayList<>(); + if(dval != null) upgrades.add(0, dval); + + deserialize(); + } + + protected void deserialize() { + ListNBT listnbt = tag.getList(key, Constants.NBT.TAG_STRING); + boolean require_fix = false; + + for(int i = 0; i < listnbt.size(); i++) { + String str = listnbt.getString(i); + Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(str)); + + T data; + try { + //noinspection unchecked + data = (T) item; + upgrades.add(data); + } catch(ClassCastException e) { + ConstructionWand.LOGGER.warn("Invalid wand upgrade: " + str); + require_fix = true; + } + } + if(require_fix) serialize(); + } + + protected void serialize() { + ListNBT listnbt = new ListNBT(); + + for(T item : upgrades) { + if(item == dval) continue; + listnbt.add(StringNBT.valueOf(item.getRegistryName().toString())); + } + tag.put(key, listnbt); + } + + public boolean addUpgrade(T upgrade) { + if(hasUpgrade(upgrade)) return false; + + upgrades.add(upgrade); + serialize(); + return true; + } + + public boolean hasUpgrade(T upgrade) { + return upgrades.contains(upgrade); + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java b/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java new file mode 100644 index 0000000..8d80eda --- /dev/null +++ b/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java @@ -0,0 +1,83 @@ +package thetadev.constructionwand.basics.option; + +import net.minecraft.nbt.CompoundNBT; +import thetadev.constructionwand.api.IWandUpgrade; + +public class WandUpgradesSelectable extends WandUpgrades implements IOption +{ + private byte selector; + + public WandUpgradesSelectable(CompoundNBT tag, String key, T dval) { + super(tag, key, dval); + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValueString() { + return get().getRegistryName().toString(); + } + + @Override + public void setValueString(String val) { + for(byte i = 0; i < upgrades.size(); i++) { + if(upgrades.get(i).getRegistryName().toString().equals(val)) { + selector = i; + serializeSelector(); + return; + } + } + } + + @Override + public boolean isEnabled() { + return upgrades.size() > 1; + } + + @Override + public void set(T val) { + selector = (byte) upgrades.indexOf(val); + fixSelector(); + serializeSelector(); + } + + @Override + public T get() { + fixSelector(); + return upgrades.get(selector); + } + + @Override + public T next(boolean dir) { + selector++; + fixSelector(); + serializeSelector(); + return get(); + } + + private void fixSelector() { + if(selector < 0 || selector >= upgrades.size()) selector = 0; + } + + @Override + protected void deserialize() { + super.deserialize(); + + selector = tag.getByte(key + "_sel"); + fixSelector(); + } + + @Override + protected void serialize() { + super.serialize(); + + serializeSelector(); + } + + private void serializeSelector() { + tag.putByte(key + "_sel", selector); + } +} diff --git a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java index 2e89e58..4cf3ce1 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java @@ -5,7 +5,9 @@ import javax.annotation.Nullable; public interface IPool { void add(T element); + @Nullable T draw(); + void reset(); } diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index e933d8b..954ace1 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -12,7 +12,7 @@ import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigClient; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.items.ItemWand; +import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.network.PacketQueryUndo; import thetadev.constructionwand.network.PacketWandOption; @@ -36,7 +36,7 @@ public class ClientEvents optPressed = optState; PacketQueryUndo packet = new PacketQueryUndo(optPressed); ConstructionWand.instance.HANDLER.sendToServer(packet); - ConstructionWand.LOGGER.debug("OPT key update: " + optPressed); + //ConstructionWand.LOGGER.debug("OPT key update: " + optPressed); } } @@ -57,7 +57,7 @@ public class ClientEvents event.setCanceled(true); } - // Sneak+(OPT)+Left click wand to change mode + // Sneak+(OPT)+Left click wand to change core @SubscribeEvent public void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { PlayerEntity player = event.getPlayer(); @@ -68,8 +68,8 @@ public class ClientEvents if(!(wand.getItem() instanceof ItemWand)) return; WandOptions wandOptions = new WandOptions(wand); - wandOptions.mode.next(); - ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.mode, true)); + wandOptions.cores.next(); + ConstructionWand.instance.HANDLER.sendToServer(new PacketWandOption(wandOptions.cores, true)); } // Sneak+(OPT)+Right click wand to open GUI diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index 9335f3d..c79006f 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -15,7 +15,8 @@ import net.minecraft.util.math.RayTraceResult; import net.minecraftforge.client.event.DrawHighlightEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.job.WandJob; +import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.wand.WandJob; import java.util.Set; @@ -25,25 +26,23 @@ public class RenderBlockPreview public Set undoBlocks; @SubscribeEvent - public void renderBlockHighlight(DrawHighlightEvent event) - { + public void renderBlockHighlight(DrawHighlightEvent event) { if(event.getTarget().getType() != RayTraceResult.Type.BLOCK) return; BlockRayTraceResult rtr = (BlockRayTraceResult) event.getTarget(); Entity entity = event.getInfo().getRenderViewEntity(); if(!(entity instanceof PlayerEntity)) return; PlayerEntity player = (PlayerEntity) entity; - Set blocks; - float colorR=0, colorG=0, colorB=0; + Set blocks = null; + float colorR = 0, colorG = 0, colorB = 0; ItemStack wand = WandUtil.holdingWand(player); if(wand == null) return; if(!(player.isSneaking() && ClientEvents.isOptKeyDown())) { - if(wandJob == null || !(wandJob.getRayTraceResult().equals(rtr)) || !(wandJob.getWand().equals(wand))) { - wandJob = WandJob.getJob(player, player.getEntityWorld(), rtr, wand); + if(wandJob == null || !compareRTR(wandJob.rayTraceResult, rtr) || !(wandJob.wand.equals(wand))) { + wandJob = ItemWand.getWandJob(player, player.getEntityWorld(), rtr, wand); } - blocks = wandJob.getBlockPositions(); } else { @@ -58,6 +57,10 @@ public class RenderBlockPreview event.setCanceled(true); } + private static boolean compareRTR(BlockRayTraceResult rtr1, BlockRayTraceResult rtr2) { + return rtr1.getPos().equals(rtr2.getPos()) && rtr1.getFace().equals(rtr2.getFace()); + } + private void renderBlockList(Set blocks, MatrixStack ms, IRenderTypeBuffer buffer, float red, float green, float blue) { double renderPosX = Minecraft.getInstance().getRenderManager().info.getProjectedView().getX(); double renderPosY = Minecraft.getInstance().getRenderManager().info.getProjectedView().getY(); diff --git a/src/main/java/thetadev/constructionwand/client/RenderTypes.java b/src/main/java/thetadev/constructionwand/client/RenderTypes.java index bb33f4e..c901bd3 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderTypes.java +++ b/src/main/java/thetadev/constructionwand/client/RenderTypes.java @@ -27,7 +27,7 @@ public class RenderTypes .build(false); TRANSLUCENT_LINES = RenderType.makeType( - ConstructionWand.MODID+":translucent_lines", + ConstructionWand.MODID + ":translucent_lines", DefaultVertexFormats.POSITION_COLOR, GL11.GL_LINES, 256, diff --git a/src/main/java/thetadev/constructionwand/client/ScreenWand.java b/src/main/java/thetadev/constructionwand/client/ScreenWand.java index 4f5b7fc..483ba6a 100644 --- a/src/main/java/thetadev/constructionwand/client/ScreenWand.java +++ b/src/main/java/thetadev/constructionwand/client/ScreenWand.java @@ -13,6 +13,8 @@ import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.network.PacketWandOption; +import javax.annotation.Nonnull; + public class ScreenWand extends Screen { private final ItemStack wand; @@ -25,8 +27,8 @@ public class ScreenWand extends Screen private static final int N_COLS = 2; private static final int N_ROWS = 3; - private static final int FIELD_WIDTH = N_COLS * (BUTTON_WIDTH+SPACING_WIDTH) - SPACING_WIDTH; - private static final int FIELD_HEIGHT = N_ROWS * (BUTTON_HEIGHT+SPACING_HEIGHT) - SPACING_HEIGHT; + private static final int FIELD_WIDTH = N_COLS * (BUTTON_WIDTH + SPACING_WIDTH) - SPACING_WIDTH; + private static final int FIELD_HEIGHT = N_ROWS * (BUTTON_HEIGHT + SPACING_HEIGHT) - SPACING_HEIGHT; public ScreenWand(ItemStack wand) { super(new StringTextComponent("ScreenWand")); @@ -35,22 +37,22 @@ public class ScreenWand extends Screen } @Override - public void init(Minecraft minecraft, int width, int height) { + public void init(@Nonnull Minecraft minecraft, int width, int height) { super.init(minecraft, width, height); - createButton(0, 0, wandOptions.mode); + createButton(0, 0, wandOptions.cores); createButton(0, 1, wandOptions.lock); createButton(0, 2, wandOptions.direction); - createButton(1, 0, wandOptions.replace); - createButton(1, 1, wandOptions.match); - createButton(1, 2, wandOptions.random); + createButton(1, 0, wandOptions.reservoirs); + createButton(1, 1, wandOptions.replace); + createButton(1, 2, wandOptions.match); } @Override - public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { + public void render(@Nonnull MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { renderBackground(matrixStack); super.render(matrixStack, mouseX, mouseY, partialTicks); - drawCenteredString(matrixStack, font, wand.getDisplayName(), width/2, height/2 - FIELD_HEIGHT/2 - SPACING_HEIGHT, 16777215); + drawCenteredString(matrixStack, font, wand.getDisplayName(), width / 2, height / 2 - FIELD_HEIGHT / 2 - SPACING_HEIGHT, 16777215); } @Override @@ -78,11 +80,11 @@ public class ScreenWand extends Screen } private int getX(int n) { - return width/2 - FIELD_WIDTH/2 + n*(BUTTON_WIDTH+SPACING_WIDTH); + return width / 2 - FIELD_WIDTH / 2 + n * (BUTTON_WIDTH + SPACING_WIDTH); } private int getY(int n) { - return height/2 - FIELD_HEIGHT/2 + n*(BUTTON_HEIGHT+SPACING_HEIGHT); + return height / 2 - FIELD_HEIGHT / 2 + n * (BUTTON_HEIGHT + SPACING_HEIGHT); } private ITextComponent getButtonLabel(IOption option) { diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerManager.java b/src/main/java/thetadev/constructionwand/containers/ContainerManager.java index 5f968cf..f7283af 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerManager.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerManager.java @@ -6,7 +6,8 @@ import thetadev.constructionwand.api.IContainerHandler; import java.util.ArrayList; -public class ContainerManager { +public class ContainerManager +{ private final ArrayList handlers; public ContainerManager() { @@ -20,7 +21,7 @@ public class ContainerManager { public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { for(IContainerHandler handler : handlers) { if(handler.matches(player, itemStack, inventoryStack)) { - return handler.countItems(player,itemStack, inventoryStack); + return handler.countItems(player, itemStack, inventoryStack); } } return 0; diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java index aaa08ce..c5c8b51 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java @@ -26,9 +26,9 @@ public class HandlerCapability implements IContainerHandler IItemHandler itemHandler = itemHandlerLazyOptional.orElse(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.getDefaultInstance()); - for (int i = 0; i < itemHandler.getSlots(); i++) { + for(int i = 0; i < itemHandler.getSlots(); i++) { ItemStack containerStack = itemHandler.getStackInSlot(i); - if (containerStack != null && itemStack.isItemEqual(containerStack)) { + if(containerStack != null && itemStack.isItemEqual(containerStack)) { total += Math.max(0, containerStack.getCount()); } @@ -46,7 +46,7 @@ public class HandlerCapability implements IContainerHandler IItemHandler itemHandler = itemHandlerLazyOptional.orElse(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.getDefaultInstance()); - for (int i = 0; i < itemHandler.getSlots(); i++) { + for(int i = 0; i < itemHandler.getSlots(); i++) { ItemStack handlerStack = itemHandler.getStackInSlot(i); if(handlerStack != null && handlerStack.isItemEqual(itemStack)) { ItemStack extracted = itemHandler.extractItem(i, count, false); diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java index 7c48b95..601a9bd 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java @@ -16,14 +16,12 @@ public class HandlerShulkerbox implements IContainerHandler private final int SLOTS = 27; @Override - public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) - { + public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { return inventoryStack != null && inventoryStack.getCount() == 1 && Block.getBlockFromItem(inventoryStack.getItem()) instanceof ShulkerBoxBlock; } @Override - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) - { + public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { int count = 0; for(ItemStack stack : getItemList(inventoryStack)) { @@ -34,8 +32,7 @@ public class HandlerShulkerbox implements IContainerHandler } @Override - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) - { + public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { NonNullList itemList = getItemList(inventoryStack); boolean changed = false; @@ -59,9 +56,9 @@ public class HandlerShulkerbox implements IContainerHandler private NonNullList getItemList(ItemStack itemStack) { NonNullList itemStacks = NonNullList.withSize(SLOTS, ItemStack.EMPTY); CompoundNBT rootTag = itemStack.getTag(); - if (rootTag != null && rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { + if(rootTag != null && rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { CompoundNBT entityTag = rootTag.getCompound("BlockEntityTag"); - if (entityTag.contains("Items", Constants.NBT.TAG_LIST)) { + if(entityTag.contains("Items", Constants.NBT.TAG_LIST)) { ItemStackHelper.loadAllItems(entityTag, itemStacks); } } @@ -70,7 +67,7 @@ public class HandlerShulkerbox implements IContainerHandler private void setItemList(ItemStack itemStack, NonNullList itemStacks) { CompoundNBT rootTag = itemStack.getOrCreateTag(); - if (!rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { + if(!rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { rootTag.put("BlockEntityTag", new CompoundNBT()); } ItemStackHelper.saveAllItems(rootTag.getCompound("BlockEntityTag"), itemStacks); diff --git a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java new file mode 100644 index 0000000..d9e27a9 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java @@ -0,0 +1,76 @@ +package thetadev.constructionwand.crafting; + +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipeSerializer; +import net.minecraft.item.crafting.SpecialRecipe; +import net.minecraft.item.crafting.SpecialRecipeSerializer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import thetadev.constructionwand.api.IWandUpgrade; +import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.items.wand.ItemWand; + +import javax.annotation.Nonnull; + +public class RecipeWandUpgrade extends SpecialRecipe +{ + public static final SpecialRecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>(RecipeWandUpgrade::new); + + public RecipeWandUpgrade(ResourceLocation resourceLocation) { + super(resourceLocation); + } + + @Override + public boolean matches(@Nonnull CraftingInventory inv, @Nonnull World worldIn) { + ItemStack wand = null; + IWandUpgrade upgrade = null; + + for(int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack stack = inv.getStackInSlot(i); + if(!stack.isEmpty()) { + if(wand == null && stack.getItem() instanceof ItemWand) wand = stack; + else + if(upgrade == null && stack.getItem() instanceof IWandUpgrade) + upgrade = (IWandUpgrade) stack.getItem(); + else return false; + } + } + + if(wand == null || upgrade == null) return false; + return !new WandOptions(wand).hasUpgrade(upgrade); + } + + @Nonnull + @Override + public ItemStack getCraftingResult(@Nonnull CraftingInventory inv) { + ItemStack wand = null; + IWandUpgrade upgrade = null; + + for(int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack stack = inv.getStackInSlot(i); + if(!stack.isEmpty()) { + if(stack.getItem() instanceof ItemWand) wand = stack; + else + if(stack.getItem() instanceof IWandUpgrade) upgrade = (IWandUpgrade) stack.getItem(); + } + } + + if(wand == null || upgrade == null) return ItemStack.EMPTY; + + ItemStack newWand = wand.copy(); + new WandOptions(newWand).addUpgrade(upgrade); + return newWand; + } + + @Override + public boolean canFit(int width, int height) { + return width * height >= 2; + } + + @Nonnull + @Override + public IRecipeSerializer getSerializer() { + return SERIALIZER; + } +} diff --git a/src/main/java/thetadev/constructionwand/data/Inp.java b/src/main/java/thetadev/constructionwand/data/Inp.java index cc488b1..cab5dbf 100644 --- a/src/main/java/thetadev/constructionwand/data/Inp.java +++ b/src/main/java/thetadev/constructionwand/data/Inp.java @@ -21,6 +21,7 @@ public class Inp public static Inp fromItem(IItemProvider in) { return new Inp(in.asItem().getRegistryName().getPath(), Ingredient.fromItems(in), ItemPredicate.Builder.create().item(in).build()); } + public static Inp fromTag(ITag.INamedTag in) { return new Inp(in.getName().getPath(), Ingredient.fromTag(in), ItemPredicate.Builder.create().tag(in).build()); } diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index ee36cd6..77ddbe2 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -1,13 +1,14 @@ package thetadev.constructionwand.data; -import net.minecraft.data.DataGenerator; -import net.minecraft.data.IFinishedRecipe; -import net.minecraft.data.RecipeProvider; -import net.minecraft.data.ShapedRecipeBuilder; +import net.minecraft.data.*; +import net.minecraft.item.crafting.SpecialRecipeSerializer; import net.minecraft.tags.ItemTags; import net.minecraft.util.IItemProvider; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.registry.Registry; import net.minecraftforge.common.Tags; import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.ModItems; import java.util.function.Consumer; @@ -24,6 +25,8 @@ public class RecipeGenerator extends RecipeProvider wandRecipe(consumer, ModItems.WAND_IRON, Inp.fromTag(Tags.Items.INGOTS_IRON)); wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); + + specialRecipe(consumer, RecipeWandUpgrade.SERIALIZER); } private void wandRecipe(Consumer consumer, IItemProvider wand, Inp material) { @@ -37,6 +40,11 @@ public class RecipeGenerator extends RecipeProvider .build(consumer); } + private void specialRecipe(Consumer consumer, SpecialRecipeSerializer serializer) { + ResourceLocation name = Registry.RECIPE_SERIALIZER.getKey(serializer); + CustomRecipeBuilder.customRecipe(serializer).build(consumer, ConstructionWand.loc("dynamic/" + name.getPath()).toString()); + } + @Override public String getName() { return ConstructionWand.MODID + " crafting recipes"; diff --git a/src/main/java/thetadev/constructionwand/items/ItemBase.java b/src/main/java/thetadev/constructionwand/items/ItemBase.java new file mode 100644 index 0000000..12b306c --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/ItemBase.java @@ -0,0 +1,11 @@ +package thetadev.constructionwand.items; + +import net.minecraft.item.Item; + +public class ItemBase extends Item +{ + public ItemBase(Properties properties, String name) { + super(properties); + setRegistryName(name); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index ab59c37..370c6af 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -3,29 +3,65 @@ package thetadev.constructionwand.items; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.color.ItemColors; import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemModelsProperties; import net.minecraft.item.ItemTier; +import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.IForgeRegistryEntry; import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.crafting.RecipeWandUpgrade; +import thetadev.constructionwand.items.core.ItemCoreAngel; +import thetadev.constructionwand.items.reservoir.ItemReservoirRandom; +import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.items.wand.ItemWandBasic; +import thetadev.constructionwand.items.wand.ItemWandInfinity; @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModItems { - public static final Item WAND_STONE = new ItemWandBasic("stone_wand", ItemTier.STONE); - public static final Item WAND_IRON = new ItemWandBasic("iron_wand", ItemTier.IRON); - public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", ItemTier.DIAMOND); - public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand"); + public static final Item WAND_STONE = new ItemWandBasic(itemprops(), "stone_wand", ItemTier.STONE); + public static final Item WAND_IRON = new ItemWandBasic(itemprops(), "iron_wand", ItemTier.IRON); + public static final Item WAND_DIAMOND = new ItemWandBasic(itemprops(), "diamond_wand", ItemTier.DIAMOND); + public static final Item WAND_INFINITY = new ItemWandInfinity(itemprops(), "infinity_wand"); + + public static final Item CORE_ANGEL = new ItemCoreAngel(unstackable(), "core_angel"); + + public static final Item RESERVOIR_RANDOM = new ItemReservoirRandom(unstackable(), "reservoir_random"); public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; + public static final Item[] UPGRADES = {CORE_ANGEL, RESERVOIR_RANDOM}; @SubscribeEvent - public static void onRegisterItems(RegistryEvent.Register event) - { - event.getRegistry().registerAll(WANDS); + public static void registerItems(RegistryEvent.Register event) { + IForgeRegistry r = event.getRegistry(); + + r.registerAll(WANDS); + r.registerAll(UPGRADES); + } + + public static Item.Properties itemprops() { + return new Item.Properties().group(ItemGroup.TOOLS); + } + + private static Item.Properties unstackable() { + return itemprops().maxStackSize(1); + } + + private static > void register(IForgeRegistry reg, String name, IForgeRegistryEntry thing) { + reg.register(thing.setRegistryName(ConstructionWand.loc(name))); + } + + @SubscribeEvent + public static void registerRecipeSerializers(RegistryEvent.Register> event) { + IForgeRegistry> r = event.getRegistry(); + register(r, "wand_upgrade", RecipeWandUpgrade.SERIALIZER); } @OnlyIn(Dist.CLIENT) @@ -33,7 +69,8 @@ public class ModItems for(Item item : WANDS) { ItemModelsProperties.func_239418_a_( item, ConstructionWand.loc("using_core"), - (stack, world, entity) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : ItemWand.getWandMode(stack) + (stack, world, entity) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : + new WandOptions(stack).cores.get().getColor() > -1 ? 1 : 0 ); } } @@ -43,7 +80,8 @@ public class ModItems ItemColors colors = Minecraft.getInstance().getItemColors(); for(Item item : WANDS) { - colors.register((stack, layer) -> layer == 1 ? 0xFF0000 : -1, item); + colors.register((stack, layer) -> (layer == 1 && stack.getItem() instanceof ItemWand) ? + new WandOptions(stack).cores.get().getColor() : -1, item); } } } diff --git a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java new file mode 100644 index 0000000..af07320 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java @@ -0,0 +1,26 @@ +package thetadev.constructionwand.items.core; + +import net.minecraft.util.ResourceLocation; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandCore; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.action.ActionConstruction; + +public class CoreDefault implements IWandCore +{ + @Override + public int getColor() { + return -1; + } + + @Override + public IWandAction getWandAction(WandJob wandJob) { + return new ActionConstruction(wandJob); + } + + @Override + public ResourceLocation getRegistryName() { + return ConstructionWand.loc("default"); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java new file mode 100644 index 0000000..70541c6 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -0,0 +1,24 @@ +package thetadev.constructionwand.items.core; + +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandCore; +import thetadev.constructionwand.items.ItemBase; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.action.ActionAngel; + +public class ItemCoreAngel extends ItemBase implements IWandCore +{ + public ItemCoreAngel(Properties properties, String name) { + super(properties, name); + } + + @Override + public int getColor() { + return 0xE9B115; + } + + @Override + public IWandAction getWandAction(WandJob wandJob) { + return new ActionAngel(wandJob); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java new file mode 100644 index 0000000..68016f9 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java @@ -0,0 +1,19 @@ +package thetadev.constructionwand.items.reservoir; + +import thetadev.constructionwand.api.IWandReservoir; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.items.ItemBase; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.supplier.SupplierRandom; + +public class ItemReservoirRandom extends ItemBase implements IWandReservoir +{ + public ItemReservoirRandom(Properties properties, String name) { + super(properties, name); + } + + @Override + public IWandSupplier getWandSupplier(WandJob job) { + return new SupplierRandom(job); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java b/src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java new file mode 100644 index 0000000..34b66ad --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java @@ -0,0 +1,21 @@ +package thetadev.constructionwand.items.reservoir; + +import net.minecraft.util.ResourceLocation; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandReservoir; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.supplier.SupplierInventory; + +public class ReservoirDefault implements IWandReservoir +{ + @Override + public IWandSupplier getWandSupplier(WandJob job) { + return new SupplierInventory(job); + } + + @Override + public ResourceLocation getRegistryName() { + return ConstructionWand.loc("default"); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java similarity index 70% rename from src/main/java/thetadev/constructionwand/items/ItemWand.java rename to src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 667ea81..ae437b2 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -1,11 +1,9 @@ -package thetadev.constructionwand.items; +package thetadev.constructionwand.items.wand; import net.minecraft.block.BlockState; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Item; -import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUseContext; import net.minecraft.util.ActionResult; @@ -21,23 +19,24 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.job.AngelJob; -import thetadev.constructionwand.job.WandJob; +import thetadev.constructionwand.items.ItemBase; +import thetadev.constructionwand.wand.WandJob; +import javax.annotation.Nonnull; import java.util.List; -public abstract class ItemWand extends Item +public abstract class ItemWand extends ItemBase { - public ItemWand(String name, Item.Properties properties) { - super(properties.group(ItemGroup.TOOLS)); - setRegistryName(ConstructionWand.loc(name)); + public ItemWand(Properties properties, String name) { + super(properties, name); } + @Nonnull @Override - public ActionResultType onItemUse(ItemUseContext context) - { + public ActionResultType onItemUse(ItemUseContext context) { PlayerEntity player = context.getPlayer(); Hand hand = context.getHand(); World world = context.getWorld(); @@ -50,33 +49,43 @@ public abstract class ItemWand extends Item return ConstructionWand.instance.undoHistory.undo(player, world, context.getPos()) ? ActionResultType.SUCCESS : ActionResultType.FAIL; } else { - WandJob job = WandJob.getJob(player, world, new BlockRayTraceResult(context.getHitVec(), context.getFace(), context.getPos(), false), stack); + WandJob job = getWandJob(player, world, new BlockRayTraceResult(context.getHitVec(), context.getFace(), context.getPos(), false), stack); return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; } } + @Nonnull @Override - public ActionResult onItemRightClick(World world, PlayerEntity player, Hand hand) { + public ActionResult onItemRightClick(@Nonnull World world, PlayerEntity player, @Nonnull Hand hand) { ItemStack stack = player.getHeldItem(hand); if(!player.isSneaking()) { if(world.isRemote) return ActionResult.resultFail(stack); // Right click: Place angel block - //ConstructionWand.LOGGER.debug("Place angel block"); - WandJob job = new AngelJob(player, world, stack); + WandJob job = getWandJob(player, world, new BlockRayTraceResult(player.getLookVec(), + WandUtil.fromVector(player.getLookVec()), WandUtil.playerPos(player), false), stack); return job.doIt() ? ActionResult.resultSuccess(stack) : ActionResult.resultFail(stack); } return ActionResult.resultFail(stack); } + public static WandJob getWandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { + WandOptions options = new WandOptions(wand); + + WandJob wandJob = new WandJob(player, world, rayTraceResult, wand); + wandJob.getPlaceSnapshots(options.cores.get().getWandAction(wandJob), options.reservoirs.get().getWandSupplier(wandJob)); + + return wandJob; + } + @Override - public boolean canHarvestBlock(BlockState blockIn) { + public boolean canHarvestBlock(@Nonnull BlockState blockIn) { return false; } @Override - public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) { + public boolean getIsRepairable(@Nonnull ItemStack toRepair, @Nonnull ItemStack repair) { return false; } @@ -88,20 +97,14 @@ public abstract class ItemWand extends Item return ConfigServer.getWandProperties(this).getLimit(); } - public static int getWandMode(ItemStack stack) { - WandOptions options = new WandOptions(stack); - return options.mode.get().ordinal(); - } - @OnlyIn(Dist.CLIENT) - public void addInformation(ItemStack itemstack, World worldIn, List lines, ITooltipFlag extraInfo) { - ItemWand wand = (ItemWand) itemstack.getItem(); + public void addInformation(@Nonnull ItemStack itemstack, World worldIn, @Nonnull List lines, @Nonnull ITooltipFlag extraInfo) { WandOptions options = new WandOptions(itemstack); String langTooltip = ConstructionWand.MODID + ".tooltip."; if(Screen.hasShiftDown()) { - for(int i=1; i opt = options.allOptions[i]; lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.GRAY)) diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java similarity index 68% rename from src/main/java/thetadev/constructionwand/items/ItemWandBasic.java rename to src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java index 6ef5091..2cfb411 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java @@ -1,16 +1,18 @@ -package thetadev.constructionwand.items; +package thetadev.constructionwand.items.wand; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.IItemTier; import net.minecraft.item.ItemStack; import thetadev.constructionwand.basics.ConfigServer; +import javax.annotation.Nonnull; + public class ItemWandBasic extends ItemWand { private final IItemTier tier; - public ItemWandBasic(String name, IItemTier tier) { - super(name, new Properties().maxDamage(tier.getMaxUses())); + public ItemWandBasic(Properties properties, String name, IItemTier tier) { + super(properties.maxDamage(tier.getMaxUses()), name); this.tier = tier; } @@ -25,7 +27,7 @@ public class ItemWandBasic extends ItemWand } @Override - public boolean getIsRepairable(ItemStack toRepair, ItemStack repair) { + public boolean getIsRepairable(@Nonnull ItemStack toRepair, @Nonnull ItemStack repair) { return this.tier.getRepairMaterial().test(repair); } } diff --git a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java similarity index 67% rename from src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java rename to src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java index ab225f9..edb175a 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java @@ -1,4 +1,4 @@ -package thetadev.constructionwand.items; +package thetadev.constructionwand.items.wand; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; @@ -6,9 +6,8 @@ import thetadev.constructionwand.basics.ConfigServer; public class ItemWandInfinity extends ItemWand { - public ItemWandInfinity(String name) - { - super(name, new Properties().maxStackSize(1).isBurnable()); + public ItemWandInfinity(Properties properties, String name) { + super(properties.maxStackSize(1).isBurnable(), name); } @Override diff --git a/src/main/java/thetadev/constructionwand/job/AngelJob.java b/src/main/java/thetadev/constructionwand/job/AngelJob.java deleted file mode 100644 index af2ee3c..0000000 --- a/src/main/java/thetadev/constructionwand/job/AngelJob.java +++ /dev/null @@ -1,42 +0,0 @@ -package thetadev.constructionwand.job; - -import net.minecraft.block.Blocks; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.vector.Vector3d; -import net.minecraft.world.World; -import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.basics.option.WandOptions; - -public class AngelJob extends WandJob -{ - public AngelJob(PlayerEntity player, World world, ItemStack wand) { - super(player, world, new BlockRayTraceResult(player.getLookVec(), fromVector(player.getLookVec()), WandUtil.playerPos(player), false), wand); - } - - private static Direction fromVector(Vector3d vector) { - return Direction.getFacingFromVector(vector.x, vector.y, vector.z); - } - - @Override - protected void getBlockPositionList() { - if(options.mode.get() != WandOptions.MODE.ANGEL || ConfigServer.getWandProperties(wandItem).getAngel() == 0) return; - - if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return; - - Vector3d playerVec = WandUtil.entityPositionVec(player); - Vector3d lookVec = player.getLookVec().mul(2, 2, 2); - Vector3d placeVec = playerVec.add(lookVec); - - BlockPos currentPos = new BlockPos(placeVec); - - PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, Blocks.AIR.getDefaultState()); - if(snapshot != null) { - placeSnapshots.add(snapshot); - } - } -} diff --git a/src/main/java/thetadev/constructionwand/job/TransductionJob.java b/src/main/java/thetadev/constructionwand/job/TransductionJob.java deleted file mode 100644 index 9ef858c..0000000 --- a/src/main/java/thetadev/constructionwand/job/TransductionJob.java +++ /dev/null @@ -1,34 +0,0 @@ -package thetadev.constructionwand.job; - -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; -import thetadev.constructionwand.basics.ConfigServer; - -public class TransductionJob extends WandJob -{ - public TransductionJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { - super(player, world, rayTraceResult, wand); - } - - @Override - protected void getBlockPositionList() { - Direction placeDirection = rayTraceResult.getFace(); - BlockPos currentPos = rayTraceResult.getPos(); - BlockState supportingBlock = world.getBlockState(currentPos); - - for(int i = 0; i< ConfigServer.getWandProperties(wandItem).getAngel(); i++) { - currentPos = currentPos.offset(placeDirection.getOpposite()); - - PlaceSnapshot snapshot = getPlaceSnapshot(currentPos, supportingBlock); - if(snapshot != null) { - placeSnapshots.add(snapshot); - break; - } - } - } -} diff --git a/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java b/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java deleted file mode 100644 index e24ae82..0000000 --- a/src/main/java/thetadev/constructionwand/job/WandItemUseContext.java +++ /dev/null @@ -1,30 +0,0 @@ -package thetadev.constructionwand.job; - -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.vector.Vector3d; -import thetadev.constructionwand.basics.WandUtil; - -public class WandItemUseContext extends BlockItemUseContext -{ - public WandItemUseContext(WandJob job, BlockPos pos, BlockItem item) { - super(job.world, job.player, Hand.MAIN_HAND, new ItemStack(item), new BlockRayTraceResult(getBlockHitVec(job, pos), job.rayTraceResult.getFace(), pos, false)); - } - - private static Vector3d getBlockHitVec(WandJob job, BlockPos pos) { - Vector3d hitVec = job.rayTraceResult.getHitVec(); // Absolute coords of hit target - - Vector3d blockDelta = WandUtil.blockPosVec(job.rayTraceResult.getPos()).subtract(WandUtil.blockPosVec(pos)); // Vector between start and current block - - return blockDelta.add(hitVec); // Absolute coords of current block hit target - } - - @Override - public boolean canPlace() { - return replaceClicked; - } -} diff --git a/src/main/java/thetadev/constructionwand/job/WandJob.java b/src/main/java/thetadev/constructionwand/job/WandJob.java deleted file mode 100644 index 0839c01..0000000 --- a/src/main/java/thetadev/constructionwand/job/WandJob.java +++ /dev/null @@ -1,340 +0,0 @@ -package thetadev.constructionwand.job; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.block.SoundType; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.state.Property; -import net.minecraft.state.properties.BlockStateProperties; -import net.minecraft.state.properties.SlabType; -import net.minecraft.util.EntityPredicates; -import net.minecraft.util.Hand; -import net.minecraft.util.SoundCategory; -import net.minecraft.util.math.AxisAlignedBB; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.shapes.VoxelShape; -import net.minecraft.world.World; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.ModStats; -import thetadev.constructionwand.basics.ReplacementRegistry; -import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.basics.pool.IPool; -import thetadev.constructionwand.basics.pool.OrderedPool; -import thetadev.constructionwand.basics.pool.RandomPool; -import thetadev.constructionwand.containers.ContainerManager; -import thetadev.constructionwand.items.ItemWand; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.stream.Collectors; - -public abstract class WandJob -{ - protected final PlayerEntity player; - protected final World world; - protected final BlockRayTraceResult rayTraceResult; - protected ItemStack wand; - protected ItemWand wandItem; - - // Wand options - protected WandOptions options; - protected int maxBlocks; - - protected HashMap itemCounts; - protected IPool itemPool; - - protected LinkedList placeSnapshots; - - - protected WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) - { - this.player = player; - this.world = world; - this.rayTraceResult = rayTraceResult; - placeSnapshots = new LinkedList<>(); - - // Get wand - if(wand == null || wand == ItemStack.EMPTY || !(wand.getItem() instanceof ItemWand)) return; - this.wand = wand; - - wandItem = (ItemWand) wand.getItem(); - - // Get options - options = new WandOptions(wand); - - // Get place item - addBlockItems(); - if(itemCounts.isEmpty()) return; - - // Get inventory supply - for(int v : itemCounts.values()) { - try { - maxBlocks = Math.addExact(maxBlocks, v); - } - catch(ArithmeticException e) { - maxBlocks = Integer.MAX_VALUE; - break; - } - } - - maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand)); - if(maxBlocks == 0) return; - - getBlockPositionList(); - } - - public static WandJob getJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { - WandOptions options = new WandOptions(itemStack); - - if(options.mode.get() == WandOptions.MODE.ANGEL) return new TransductionJob(player, world, rayTraceResult, itemStack); - return new ConstructionJob(player, world, rayTraceResult, itemStack); - } - - public Set getBlockPositions() { - return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); - } - - public BlockRayTraceResult getRayTraceResult() { return rayTraceResult; } - - public ItemStack getWand() { return wand; } - - private void addBlockItem(BlockItem item) { - int count = countItem(item); - if(count > 0) { - itemCounts.put(item, count); - itemPool.add(item); - } - } - - private void addBlockItems() { - itemCounts = new LinkedHashMap<>(); - - BlockPos targetPos = rayTraceResult.getPos(); - BlockState targetState = world.getBlockState(targetPos); - Block targetBlock = targetState.getBlock(); - ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); - - // Random mode -> add all items from hotbar - if(options.random.get()) { - itemPool = new RandomPool<>(player.getRNG()); - - for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { - if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); - } - } - else { - itemPool = new OrderedPool<>(); - - // Block in offhand -> override - if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { - addBlockItem((BlockItem) offhandStack.getItem()); - } - // Otherwise use target block - else { - Item item = targetBlock.asItem(); - if(item instanceof BlockItem) { - addBlockItem((BlockItem) item); - - // Add replacement items - if(options.match.get() != WandOptions.MATCH.EXACT) { - for(Item it : ReplacementRegistry.getMatchingSet(item)) { - if(it instanceof BlockItem) addBlockItem((BlockItem) it); - } - } - } - } - } - } - - private int countItem(Item item) { - if(player.inventory == null || player.inventory.mainInventory == null) return 0; - if(player.isCreative()) return Integer.MAX_VALUE; - - int total = 0; - ContainerManager containerManager = ConstructionWand.instance.containerManager; - List inventory = WandUtil.getFullInv(player); - - for(ItemStack stack : inventory) { - if(stack == null) continue; - - if(WandUtil.stackEquals(stack, item)) { - total += stack.getCount(); - } - else { - int amount = containerManager.countItems(player, new ItemStack(item), stack); - if(amount == Integer.MAX_VALUE) return Integer.MAX_VALUE; - total += amount; - } - } - return total; - } - - // Attempts to take specified number of items, returns number of missing items - private int takeItemStack(ItemStack stack) - { - int count = stack.getCount(); - Item item = stack.getItem(); - - if(player.inventory == null || player.inventory.mainInventory == null) return count; - if(player.isCreative()) return 0; - - List hotbar = WandUtil.getHotbarWithOffhand(player); - List mainInv = WandUtil.getMainInv(player); - - // Take items from main inv, loose items first - count = takeItemsInvList(count, item, mainInv, false); - count = takeItemsInvList(count, item, mainInv, true); - - // Take items from hotbar, containers first - count = takeItemsInvList(count, item, hotbar, true); - count = takeItemsInvList(count, item, hotbar, false); - - return count; - } - - private int takeItemsInvList(int count, Item item, List inv, boolean container) { - ContainerManager containerManager = ConstructionWand.instance.containerManager; - - for(ItemStack stack : inv) { - if(count == 0) break; - - if(container) { - int nCount = containerManager.useItems(player, new ItemStack(item), stack, count); - count = nCount; - } - - if(!container && WandUtil.stackEquals(stack, item)) { - int toTake = Math.min(count, stack.getCount()); - stack.shrink(toTake); - count -= toTake; - player.inventory.markDirty(); - } - } - return count; - } - - protected abstract void getBlockPositionList(); - - // Get PlaceSnapshot, or null if no block can be placed - @Nullable - protected PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { - // Is position out of world? - if(!world.isBlockPresent(pos)) return null; - - // Is block modifiable? - if(!world.isBlockModifiable(player, pos)) return null; - - // If replace mode is off, target has to be air - if(!options.replace.get() && !world.isAirBlock(pos)) return null; - - // Limit placement range - if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) return null; - - itemPool.reset(); - - while(true) { - // Draw item from pool (returns null if none are left) - BlockItem item = itemPool.draw(); - if(item == null) return null; - - int count = itemCounts.get(item); - if(count == 0) continue; - - // Is block at pos replaceable? - BlockItemUseContext ctx = new WandItemUseContext(this, pos, item); - if(!ctx.canPlace()) continue; - - // Can block be placed? - BlockState blockState = item.getBlock().getStateForPlacement(ctx); - if(blockState == null) continue; - - // Forbidden Tile Entity? - if(!WandUtil.isTEAllowed(blockState)) continue; - - // No entities colliding? - VoxelShape shape = blockState.getCollisionShape(world, pos); - if(!shape.isEmpty()) { - AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) continue; - } - - // Adjust blockstate to neighbors - blockState = Block.getValidBlockForPosition(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) continue; - - // Copy block properties from supporting block - if(options.direction.get() == WandOptions.DIRECTION.TARGET) { - // Block properties to be copied (alignment/rotation properties) - for(Property property : new Property[] { - BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, - BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) - { - if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { - blockState = blockState.with(property, supportingBlock.get(property)); - } - } - - // Dont dupe double slabs - if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { - SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); - if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); - } - } - - // Reduce item count - if(count < Integer.MAX_VALUE) itemCounts.merge(item, -1, Integer::sum); - return new PlaceSnapshot(blockState, pos, item); - } - } - - protected boolean matchBlocks(Block b1, Block b2) { - switch(options.match.get()) { - case EXACT: return b1 == b2; - case SIMILAR: return ReplacementRegistry.matchBlocks(b1, b2); - case ANY: return b1 != Blocks.AIR && b2 != Blocks.AIR; - } - return false; - } - - public boolean doIt() { - LinkedList executed = new LinkedList<>(); - - for(ISnapshot snapshot : placeSnapshots) { - if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; - - if(snapshot.execute(world, player)) { - // If the item cant be taken, undo the placement - if(takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); - else { - ConstructionWand.LOGGER.info("Item could not be taken. Remove block: "+ - snapshot.getBlockState().getBlock().toString()); - snapshot.forceRestore(world); - } - - wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); - player.addStat(ModStats.USE_WAND); - } - } - placeSnapshots = executed; - - // Play place sound - if(!placeSnapshots.isEmpty()) { - SoundType sound = placeSnapshots.getFirst().getBlockState().getSoundType(); - world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); - } - - // Add to job history for undo - if(placeSnapshots.size() > 1) ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); - - return !placeSnapshots.isEmpty(); - } -} diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index 2c244fc..7d831e9 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -16,6 +16,7 @@ public class PacketUndoBlocks public PacketUndoBlocks(Set undoBlocks) { this.undoBlocks = new HashSet<>(undoBlocks); } + private PacketUndoBlocks(HashSet undoBlocks) { this.undoBlocks = undoBlocks; } @@ -35,7 +36,8 @@ public class PacketUndoBlocks return new PacketUndoBlocks(undoBlocks); } - public static class Handler { + public static class Handler + { public static void handle(final PacketUndoBlocks msg, final Supplier ctx) { if(!ctx.get().getDirection().getReceptionSide().isClient()) return; diff --git a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java index c9612e4..cf20b15 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java +++ b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java @@ -7,7 +7,7 @@ import net.minecraftforge.fml.network.NetworkEvent; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.items.ItemWand; +import thetadev.constructionwand.items.wand.ItemWand; import java.util.function.Supplier; diff --git a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java new file mode 100644 index 0000000..b8c08f7 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java @@ -0,0 +1,33 @@ +package thetadev.constructionwand.wand; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; +import thetadev.constructionwand.basics.WandUtil; + +public class WandItemUseContext extends BlockItemUseContext +{ + public WandItemUseContext(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, BlockPos pos, BlockItem item) { + super(world, player, Hand.MAIN_HAND, new ItemStack(item), + new BlockRayTraceResult(getBlockHitVec(rayTraceResult, pos), rayTraceResult.getFace(), pos, false)); + } + + private static Vector3d getBlockHitVec(BlockRayTraceResult rayTraceResult, BlockPos pos) { + Vector3d hitVec = rayTraceResult.getHitVec(); // Absolute coords of hit target + + Vector3d blockDelta = WandUtil.blockPosVec(rayTraceResult.getPos()).subtract(WandUtil.blockPosVec(pos)); // Vector between start and current block + + return blockDelta.add(hitVec); // Absolute coords of current block hit target + } + + @Override + public boolean canPlace() { + return replaceClicked; + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java new file mode 100644 index 0000000..af14aa8 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -0,0 +1,102 @@ +package thetadev.constructionwand.wand; + +import net.minecraft.block.SoundType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.ModStats; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.wand.undo.ISnapshot; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class WandJob +{ + public final PlayerEntity player; + public final World world; + public final BlockRayTraceResult rayTraceResult; + public final WandOptions options; + public final BlockItem targetItem; + + public final ItemStack wand; + public final ItemWand wandItem; + + private IWandAction wandAction; + private IWandSupplier wandSupplier; + + private List placeSnapshots; + + public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { + this.player = player; + this.world = world; + this.rayTraceResult = rayTraceResult; + this.placeSnapshots = new LinkedList<>(); + + // Get wand + this.wand = wand; + this.wandItem = (ItemWand) wand.getItem(); + options = new WandOptions(wand); + + // Get target item + Item tgitem = world.getBlockState(rayTraceResult.getPos()).getBlock().asItem(); + if(!(tgitem instanceof BlockItem)) tgitem = null; + targetItem = (BlockItem) tgitem; + } + + public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier) { + this.wandSupplier = wandSupplier; + wandSupplier.getSupply((BlockItem) targetItem); + this.wandAction = wandAction; + placeSnapshots = wandAction.getSnapshots(wandSupplier); + } + + public Set getBlockPositions() { + return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); + } + + public boolean doIt() { + LinkedList executed = new LinkedList<>(); + + for(ISnapshot snapshot : placeSnapshots) { + if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; + + if(snapshot.execute(world, player)) { + // If the item cant be taken, undo the placement + if(wandSupplier.takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); + else { + ConstructionWand.LOGGER.info("Item could not be taken. Remove block: " + + snapshot.getBlockState().getBlock().toString()); + snapshot.forceRestore(world); + } + + wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); + player.addStat(ModStats.USE_WAND); + } + } + placeSnapshots = executed; + + // Play place sound + if(!placeSnapshots.isEmpty()) { + SoundType sound = placeSnapshots.get(0).getBlockState().getSoundType(); + world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); + } + + // Add to job history for undo + if(placeSnapshots.size() > 1) ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); + + return !placeSnapshots.isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java new file mode 100644 index 0000000..8b83ea2 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -0,0 +1,78 @@ +package thetadev.constructionwand.wand.action; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.undo.ISnapshot; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; + +import java.util.LinkedList; +import java.util.List; + +public class ActionAngel implements IWandAction +{ + private final World world; + private final PlayerEntity player; + private final BlockRayTraceResult rayTraceResult; + private final ItemWand wandItem; + + public ActionAngel(WandJob wandJob) { + world = wandJob.world; + player = wandJob.player; + rayTraceResult = wandJob.rayTraceResult; + wandItem = wandJob.wandItem; + } + + @Override + public List getSnapshots(IWandSupplier supplier) { + if(rayTraceResult == null) return getAngelSnapshots(supplier); + else return getTransductionSnapshots(supplier); + } + + public List getAngelSnapshots(IWandSupplier supplier) { + LinkedList placeSnapshots = new LinkedList<>(); + + if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return placeSnapshots; + + Vector3d playerVec = WandUtil.entityPositionVec(player); + Vector3d lookVec = player.getLookVec().mul(2, 2, 2); + Vector3d placeVec = playerVec.add(lookVec); + + BlockPos currentPos = new BlockPos(placeVec); + + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentPos, Blocks.AIR.getDefaultState()); + if(snapshot != null) placeSnapshots.add(snapshot); + + return placeSnapshots; + } + + public List getTransductionSnapshots(IWandSupplier supplier) { + LinkedList placeSnapshots = new LinkedList<>(); + + Direction placeDirection = rayTraceResult.getFace(); + BlockPos currentPos = rayTraceResult.getPos(); + BlockState supportingBlock = world.getBlockState(currentPos); + + for(int i = 0; i < ConfigServer.getWandProperties(wandItem).getAngel(); i++) { + currentPos = currentPos.offset(placeDirection.getOpposite()); + + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentPos, supportingBlock); + if(snapshot != null) { + placeSnapshots.add(snapshot); + break; + } + } + return placeSnapshots; + } +} diff --git a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java similarity index 76% rename from src/main/java/thetadev/constructionwand/job/ConstructionJob.java rename to src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java index 83b7142..e0a1746 100644 --- a/src/main/java/thetadev/constructionwand/job/ConstructionJob.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java @@ -1,25 +1,40 @@ -package thetadev.constructionwand.job; +package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.undo.ISnapshot; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; -public class ConstructionJob extends WandJob +/** + * Default WandAction. Extends your building on the side you're facing. + */ +public class ActionConstruction implements IWandAction { - public ConstructionJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack itemStack) { - super(player, world, rayTraceResult, itemStack); + private final World world; + private final BlockRayTraceResult rayTraceResult; + private final WandOptions options; + + public ActionConstruction(WandJob wandJob) { + world = wandJob.world; + rayTraceResult = wandJob.rayTraceResult; + options = wandJob.options; } @Override - protected void getBlockPositionList() { + public List getSnapshots(IWandSupplier supplier) { + LinkedList placeSnapshots = new LinkedList<>(); LinkedList candidates = new LinkedList<>(); HashSet allCandidates = new HashSet<>(); @@ -29,19 +44,22 @@ public class ConstructionJob extends WandJob // Is place direction allowed by lock? if(placeDirection == Direction.UP || placeDirection == Direction.DOWN) { - if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) candidates.add(startingPoint); + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) + candidates.add(startingPoint); } - else if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) candidates.add(startingPoint); + else + if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) + candidates.add(startingPoint); - while(!candidates.isEmpty() && placeSnapshots.size() < maxBlocks) - { + while(!candidates.isEmpty() && placeSnapshots.size() < supplier.getMaxBlocks()) { BlockPos currentCandidate = candidates.removeFirst(); try { BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); BlockState candidateSupportingBlock = world.getBlockState(supportingPoint); - if(matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { - PlaceSnapshot snapshot = getPlaceSnapshot(currentCandidate, candidateSupportingBlock); + if(WandUtil.matchBlocks(options, targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && + allCandidates.add(currentCandidate)) { + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentCandidate, candidateSupportingBlock); if(snapshot == null) continue; placeSnapshots.add(snapshot); @@ -99,11 +117,11 @@ public class ConstructionJob extends WandJob break; } } - } - catch(Exception e) { + } catch(Exception e) { // Can't do anything, could be anything. // Skip if anything goes wrong. } } + return placeSnapshots; } } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java new file mode 100644 index 0000000..854a7ac --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -0,0 +1,168 @@ +package thetadev.constructionwand.wand.supplier; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.ReplacementRegistry; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.basics.pool.IPool; +import thetadev.constructionwand.basics.pool.OrderedPool; +import thetadev.constructionwand.containers.ContainerManager; +import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * Default WandSupplier. Takes Items from player inventory. + */ +public class SupplierInventory implements IWandSupplier +{ + protected final PlayerEntity player; + protected final World world; + protected final BlockRayTraceResult rayTraceResult; + protected final WandOptions options; + protected final ItemStack wand; + protected final ItemWand wandItem; + + protected HashMap itemCounts; + protected IPool itemPool; + protected int maxBlocks; + + public SupplierInventory(WandJob job) { + player = job.player; + world = job.world; + rayTraceResult = job.rayTraceResult; + options = job.options; + wand = job.wand; + wandItem = job.wandItem; + } + + @Override + public void getSupply(@Nullable BlockItem target) { + itemCounts = new LinkedHashMap<>(); + ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); + + itemPool = new OrderedPool<>(); + + // Block in offhand -> override + if(!offhandStack.isEmpty() && offhandStack.getItem() instanceof BlockItem) { + addBlockItem((BlockItem) offhandStack.getItem()); + } + // Otherwise use target block + else { + addBlockItem(target); + + // Add replacement items + if(options.match.get() != WandOptions.MATCH.EXACT) { + for(Item it : ReplacementRegistry.getMatchingSet(target)) { + if(it instanceof BlockItem) addBlockItem((BlockItem) it); + } + } + } + + // Count inventory supply + countSupply(); + } + + protected void countSupply() { + maxBlocks = 0; + for(int v : itemCounts.values()) { + try { + maxBlocks = Math.addExact(maxBlocks, v); + } catch(ArithmeticException e) { + maxBlocks = Integer.MAX_VALUE; + break; + } + } + + maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand)); + } + + @Override + public int getMaxBlocks() { + return maxBlocks; + } + + protected void addBlockItem(BlockItem item) { + int count = WandUtil.countItem(player, item); + if(count > 0) { + itemCounts.put(item, count); + itemPool.add(item); + } + } + + @Override + @Nullable + public PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { + if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; + itemPool.reset(); + + while(true) { + // Draw item from pool (returns null if none are left) + BlockItem item = itemPool.draw(); + if(item == null) return null; + + int count = itemCounts.get(item); + if(count == 0) continue; + + PlaceSnapshot placeSnapshot = WandUtil.getPlaceSnapshot(world, player, rayTraceResult, pos, item, supportingBlock, options); + if(placeSnapshot != null) return placeSnapshot; + } + } + + @Override + public int takeItemStack(ItemStack stack) { + int count = stack.getCount(); + Item item = stack.getItem(); + + if(player.inventory == null || player.inventory.mainInventory == null) return count; + if(player.isCreative()) return 0; + + List hotbar = WandUtil.getHotbarWithOffhand(player); + List mainInv = WandUtil.getMainInv(player); + + // Take items from main inv, loose items first + count = takeItemsInvList(count, item, mainInv, false); + count = takeItemsInvList(count, item, mainInv, true); + + // Take items from hotbar, containers first + count = takeItemsInvList(count, item, hotbar, true); + count = takeItemsInvList(count, item, hotbar, false); + + return count; + } + + private int takeItemsInvList(int count, Item item, List inv, boolean container) { + ContainerManager containerManager = ConstructionWand.instance.containerManager; + + for(ItemStack stack : inv) { + if(count == 0) break; + + if(container) { + count = containerManager.useItems(player, new ItemStack(item), stack, count); + } + + if(!container && WandUtil.stackEquals(stack, item)) { + int toTake = Math.min(count, stack.getCount()); + stack.shrink(toTake); + count -= toTake; + player.inventory.markDirty(); + } + } + return count; + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java new file mode 100644 index 0000000..d275e40 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java @@ -0,0 +1,32 @@ +package thetadev.constructionwand.wand.supplier; + +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.pool.RandomPool; +import thetadev.constructionwand.wand.WandJob; + +import javax.annotation.Nullable; +import java.util.LinkedHashMap; + +public class SupplierRandom extends SupplierInventory +{ + public SupplierRandom(WandJob job) { + super(job); + } + + @Override + public void getSupply(@Nullable BlockItem target) { + itemCounts = new LinkedHashMap<>(); + + // Random mode -> add all items from hotbar + itemPool = new RandomPool<>(player.getRNG()); + + for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { + if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); + } + + // Count inventory supply + countSupply(); + } +} diff --git a/src/main/java/thetadev/constructionwand/job/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java similarity index 96% rename from src/main/java/thetadev/constructionwand/job/DestroySnapshot.java rename to src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index aa27b2b..2621d1c 100644 --- a/src/main/java/thetadev/constructionwand/job/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -1,4 +1,4 @@ -package thetadev.constructionwand.job; +package thetadev.constructionwand.wand.undo; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; diff --git a/src/main/java/thetadev/constructionwand/job/ISnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java similarity index 90% rename from src/main/java/thetadev/constructionwand/job/ISnapshot.java rename to src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java index 8e84145..199921e 100644 --- a/src/main/java/thetadev/constructionwand/job/ISnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java @@ -1,4 +1,4 @@ -package thetadev.constructionwand.job; +package thetadev.constructionwand.wand.undo; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; @@ -9,10 +9,14 @@ import net.minecraft.world.World; public interface ISnapshot { BlockPos getPos(); + BlockState getBlockState(); + ItemStack getRequiredItems(); boolean execute(World world, PlayerEntity player); + boolean restore(World world, PlayerEntity player); + void forceRestore(World world); } diff --git a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java similarity index 96% rename from src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java rename to src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index 3df8ab3..ea42ff1 100644 --- a/src/main/java/thetadev/constructionwand/job/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -1,4 +1,4 @@ -package thetadev.constructionwand.job; +package thetadev.constructionwand.wand.undo; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; diff --git a/src/main/java/thetadev/constructionwand/job/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java similarity index 92% rename from src/main/java/thetadev/constructionwand/job/UndoHistory.java rename to src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index 1eb16ea..15e24e8 100644 --- a/src/main/java/thetadev/constructionwand/job/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -1,4 +1,4 @@ -package thetadev.constructionwand.job; +package thetadev.constructionwand.wand.undo; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; @@ -29,7 +29,7 @@ public class UndoHistory return history.computeIfAbsent(player.getUniqueID(), k -> new PlayerEntry()); } - public void add(PlayerEntity player, World world, LinkedList placeSnapshots) { + public void add(PlayerEntity player, World world, List placeSnapshots) { LinkedList list = getEntryFromPlayer(player).entries; list.add(new HistoryEntry(placeSnapshots, world)); while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); @@ -86,7 +86,8 @@ public class UndoHistory return false; } - private static class PlayerEntry { + private static class PlayerEntry + { public final LinkedList entries; public boolean undoActive; @@ -96,11 +97,12 @@ public class UndoHistory } } - private static class HistoryEntry { - public final LinkedList placeSnapshots; + private static class HistoryEntry + { + public final List placeSnapshots; public final World world; - public HistoryEntry(LinkedList placeSnapshots, World world) { + public HistoryEntry(List placeSnapshots, World world) { this.placeSnapshots = placeSnapshots; this.world = world; } diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index ff6ce4e..c8ef751 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -4,14 +4,23 @@ "item.constructionwand.diamond_wand": "Diamond Wand", "item.constructionwand.infinity_wand": "Infinity Wand", + "item.constructionwand.core_angel": "Angel Wand Core", + "item.constructionwand.reservoir_random": "Random Wand Reservoir", + "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", - "constructionwand.option.mode": "", - "constructionwand.option.mode.default": "Default mode", - "constructionwand.option.mode.default.desc": "Extend your building on the side facing you", - "constructionwand.option.mode.angel": "§6Angel mode", - "constructionwand.option.mode.angel.desc": "Place behind blocks and in mid air", + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "Construction Core", + "constructionwand.option.cores.constructionwand:default.desc": "Extend your building on the side facing you", + "constructionwand.option.cores.constructionwand:core_angel": "§6Angel Core", + "constructionwand.option.cores.constructionwand:core_angel.desc": "Place behind blocks and in mid air", + + "constructionwand.option.reservoirs": "", + "constructionwand.option.reservoirs.constructionwand:default": "Inventory Reservoir", + "constructionwand.option.reservoirs.constructionwand:default.desc": "Delivers the matching building material from your inventory. Take a block into your offhand to specify a different material.", + "constructionwand.option.reservoirs.constructionwand:reservoir_random": "Random Reservoir", + "constructionwand.option.reservoirs.constructionwand:reservoir_random.desc": "Takes random blocks from your hotbar", "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", @@ -45,11 +54,5 @@ "constructionwand.option.match.any": "§cAny", "constructionwand.option.match.any.desc": "Extend any block", - "constructionwand.option.random": "Random: ", - "constructionwand.option.random.yes": "§aYes", - "constructionwand.option.random.yes.desc": "Place random blocks present in your hotbar", - "constructionwand.option.random.no": "§cNo", - "constructionwand.option.random.no.desc": "Don't randomize placed blocks", - "stat.constructionwand.use_wand": "Blocks placed using Wand" } \ No newline at end of file From 6618784804273f360f2ab8794fb3675a8029f90b Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 17 Feb 2021 00:33:10 +0100 Subject: [PATCH 24/78] Added conjured block Improved data generation --- gradle.properties | 2 +- .../blockstates/conjured_block.json | 7 ++ .../models/block/conjured_block.json | 6 + .../models/item/conjured_block.json | 3 + .../models/item/core_angel.json | 6 + .../models/item/diamond_wand.json | 14 +++ .../models/item/diamond_wand_core.json | 7 ++ .../models/item/infinity_wand.json | 14 +++ .../models/item/infinity_wand_core.json | 7 ++ .../models/item/iron_wand.json | 14 +++ .../models/item/iron_wand_core.json | 7 ++ .../models/item/reservoir_random.json | 6 + .../models/item/stone_wand.json | 14 +++ .../models/item/stone_wand_core.json | 7 ++ .../constructionwand/ConstructionWand.java | 4 + .../constructionwand/api/IWandSupplier.java | 2 +- .../constructionwand/basics/WandUtil.java | 32 ++--- .../basics/option/WandOptions.java | 1 - .../constructionwand/block/BlockConjured.java | 118 ++++++++++++++++++ .../constructionwand/block/ModBlocks.java | 30 +++++ .../data/BlockStateGenerator.java | 32 +++++ .../data/ICustomBlockState.java | 6 + .../data/ICustomItemModel.java | 6 + .../constructionwand/data/INoItemBlock.java | 5 + .../data/ItemModelGenerator.java | 37 ++++++ .../constructionwand/data/ModData.java | 7 ++ .../constructionwand/items/ItemBase.java | 5 +- .../constructionwand/items/ModItems.java | 50 +++++--- .../items/core/ItemCoreAngel.java | 4 +- .../items/reservoir/ItemReservoirRandom.java | 4 +- .../constructionwand/items/wand/ItemWand.java | 23 +++- .../items/wand/ItemWandBasic.java | 4 +- .../items/wand/ItemWandInfinity.java | 4 +- .../wand/WandItemUseContext.java | 5 + .../wand/supplier/SupplierInventory.java | 21 ++-- .../models/item/diamond_wand.json | 12 -- .../models/item/diamond_wand_core.json | 7 -- .../models/item/infinity_wand.json | 12 -- .../models/item/infinity_wand_core.json | 7 -- .../models/item/iron_wand.json | 12 -- .../models/item/iron_wand_core.json | 7 -- .../models/item/stone_wand.json | 12 -- .../models/item/stone_wand_core.json | 7 -- .../textures/block/conjured_block.png | Bin 0 -> 5594 bytes .../textures/item/core_angel.png | Bin 0 -> 660 bytes .../textures/item/core_destruction.png | Bin 0 -> 662 bytes .../textures/{items => item}/diamond_wand.png | Bin .../{items => item}/infinity_wand.png | Bin .../textures/{items => item}/iron_wand.png | Bin .../textures/{items => item}/overlay_core.png | Bin .../textures/item/reservoir_conjuration.png | Bin 0 -> 667 bytes .../textures/item/reservoir_random.png | Bin 0 -> 679 bytes .../textures/{items => item}/stone_wand.png | Bin .../textures/items/overlay_angel.png | Bin 208 -> 0 bytes 54 files changed, 462 insertions(+), 128 deletions(-) create mode 100644 src/generated/resources/assets/constructionwand/blockstates/conjured_block.json create mode 100644 src/generated/resources/assets/constructionwand/models/block/conjured_block.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/conjured_block.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/core_angel.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/diamond_wand.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/diamond_wand_core.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/infinity_wand.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/infinity_wand_core.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/iron_wand.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/iron_wand_core.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/reservoir_random.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/stone_wand.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/stone_wand_core.json create mode 100644 src/main/java/thetadev/constructionwand/block/BlockConjured.java create mode 100644 src/main/java/thetadev/constructionwand/block/ModBlocks.java create mode 100644 src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java create mode 100644 src/main/java/thetadev/constructionwand/data/ICustomBlockState.java create mode 100644 src/main/java/thetadev/constructionwand/data/ICustomItemModel.java create mode 100644 src/main/java/thetadev/constructionwand/data/INoItemBlock.java create mode 100644 src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java delete mode 100644 src/main/resources/assets/constructionwand/models/item/diamond_wand.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/infinity_wand.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/iron_wand.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/iron_wand_core.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/stone_wand.json delete mode 100644 src/main/resources/assets/constructionwand/models/item/stone_wand_core.json create mode 100644 src/main/resources/assets/constructionwand/textures/block/conjured_block.png create mode 100644 src/main/resources/assets/constructionwand/textures/item/core_angel.png create mode 100644 src/main/resources/assets/constructionwand/textures/item/core_destruction.png rename src/main/resources/assets/constructionwand/textures/{items => item}/diamond_wand.png (100%) rename src/main/resources/assets/constructionwand/textures/{items => item}/infinity_wand.png (100%) rename src/main/resources/assets/constructionwand/textures/{items => item}/iron_wand.png (100%) rename src/main/resources/assets/constructionwand/textures/{items => item}/overlay_core.png (100%) create mode 100644 src/main/resources/assets/constructionwand/textures/item/reservoir_conjuration.png create mode 100644 src/main/resources/assets/constructionwand/textures/item/reservoir_random.png rename src/main/resources/assets/constructionwand/textures/{items => item}/stone_wand.png (100%) delete mode 100644 src/main/resources/assets/constructionwand/textures/items/overlay_angel.png diff --git a/gradle.properties b/gradle.properties index 568f50b..20443b3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ author=thetadev modid=constructionwand mcversion=1.16.5 -forgeversion=36.0.13 +forgeversion=36.0.22 #mcversion=1.16.2 #forgeversion=33.0.60 mcp_mappings=20200723-1.16.1 diff --git a/src/generated/resources/assets/constructionwand/blockstates/conjured_block.json b/src/generated/resources/assets/constructionwand/blockstates/conjured_block.json new file mode 100644 index 0000000..ffb27e7 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/blockstates/conjured_block.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "constructionwand:block/conjured_block" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/block/conjured_block.json b/src/generated/resources/assets/constructionwand/models/block/conjured_block.json new file mode 100644 index 0000000..6a64996 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/block/conjured_block.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "constructionwand:block/conjured_block" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/conjured_block.json b/src/generated/resources/assets/constructionwand/models/item/conjured_block.json new file mode 100644 index 0000000..e08e448 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/conjured_block.json @@ -0,0 +1,3 @@ +{ + "parent": "constructionwand:block/conjured_block" +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/core_angel.json b/src/generated/resources/assets/constructionwand/models/item/core_angel.json new file mode 100644 index 0000000..8dd551e --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/core_angel.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "constructionwand:item/core_angel" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/diamond_wand.json b/src/generated/resources/assets/constructionwand/models/item/diamond_wand.json new file mode 100644 index 0000000..e9d1d77 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/diamond_wand.json @@ -0,0 +1,14 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/diamond_wand" + }, + "overrides": [ + { + "predicate": { + "constructionwand:using_core": 1.0 + }, + "model": "constructionwand:item/diamond_wand_core" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/diamond_wand_core.json b/src/generated/resources/assets/constructionwand/models/item/diamond_wand_core.json new file mode 100644 index 0000000..e762bfb --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/diamond_wand_core.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/diamond_wand", + "layer1": "constructionwand:item/overlay_core" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/infinity_wand.json b/src/generated/resources/assets/constructionwand/models/item/infinity_wand.json new file mode 100644 index 0000000..9edbd2d --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/infinity_wand.json @@ -0,0 +1,14 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/infinity_wand" + }, + "overrides": [ + { + "predicate": { + "constructionwand:using_core": 1.0 + }, + "model": "constructionwand:item/infinity_wand_core" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/infinity_wand_core.json b/src/generated/resources/assets/constructionwand/models/item/infinity_wand_core.json new file mode 100644 index 0000000..c16345c --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/infinity_wand_core.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/infinity_wand", + "layer1": "constructionwand:item/overlay_core" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/iron_wand.json b/src/generated/resources/assets/constructionwand/models/item/iron_wand.json new file mode 100644 index 0000000..71e224d --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/iron_wand.json @@ -0,0 +1,14 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/iron_wand" + }, + "overrides": [ + { + "predicate": { + "constructionwand:using_core": 1.0 + }, + "model": "constructionwand:item/iron_wand_core" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/iron_wand_core.json b/src/generated/resources/assets/constructionwand/models/item/iron_wand_core.json new file mode 100644 index 0000000..6533e05 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/iron_wand_core.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/iron_wand", + "layer1": "constructionwand:item/overlay_core" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/reservoir_random.json b/src/generated/resources/assets/constructionwand/models/item/reservoir_random.json new file mode 100644 index 0000000..3d5385a --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/reservoir_random.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "constructionwand:item/reservoir_random" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/stone_wand.json b/src/generated/resources/assets/constructionwand/models/item/stone_wand.json new file mode 100644 index 0000000..6958a75 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/stone_wand.json @@ -0,0 +1,14 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/stone_wand" + }, + "overrides": [ + { + "predicate": { + "constructionwand:using_core": 1.0 + }, + "model": "constructionwand:item/stone_wand_core" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/stone_wand_core.json b/src/generated/resources/assets/constructionwand/models/item/stone_wand_core.json new file mode 100644 index 0000000..18f0d78 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/stone_wand_core.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "constructionwand:item/stone_wand", + "layer1": "constructionwand:item/overlay_core" + } +} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index cd11fe8..e23078c 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -16,6 +16,7 @@ import thetadev.constructionwand.basics.ConfigClient; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.ReplacementRegistry; +import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.client.ClientEvents; import thetadev.constructionwand.client.RenderBlockPreview; import thetadev.constructionwand.containers.ContainerManager; @@ -31,6 +32,8 @@ import thetadev.constructionwand.wand.undo.UndoHistory; public class ConstructionWand { public static final String MODID = "constructionwand"; + public static final String MODNAME = "ConstructionWand"; + public static ConstructionWand instance; public static final Logger LOGGER = LogManager.getLogger(); private static final String PROTOCOL_VERSION = "1"; @@ -82,6 +85,7 @@ public class ConstructionWand MinecraftForge.EVENT_BUS.register(new ClientEvents()); ModItems.registerModelProperties(); ModItems.registerItemColors(); + ModBlocks.registerRenderLayers(); } public static ResourceLocation loc(String name) { diff --git a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java index 9e953ff..ce7c93a 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java +++ b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java @@ -15,7 +15,7 @@ public interface IWandSupplier int getMaxBlocks(); @Nullable - PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock); + PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock); int takeItemStack(ItemStack stack); } diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 8829b5f..3fb95d5 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -29,6 +29,7 @@ import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.WandItemUseContext; @@ -139,22 +140,24 @@ public class WandUtil return true; } - public static boolean removeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos) { + public static boolean removeBlock(World world, PlayerEntity player, @Nullable BlockState block, BlockPos pos) { BlockState currentBlock = world.getBlockState(pos); - if(world.isBlockModifiable(player, pos) && - (player.isCreative() || - (currentBlock.getBlockHardness(world, pos) > -1 && world.getTileEntity(pos) == null && - ReplacementRegistry.matchBlocks(currentBlock.getBlock(), block.getBlock())))) { + if(!world.isBlockModifiable(player, pos)) return false; - BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, pos, currentBlock, player); - MinecraftForge.EVENT_BUS.post(breakEvent); - if(breakEvent.isCanceled()) return false; + if(!player.isCreative()) { + if(currentBlock.getBlockHardness(world, pos) <= -1 || world.getTileEntity(pos) != null) return false; - world.removeBlock(pos, false); - return true; + if(block != null) + if(!ReplacementRegistry.matchBlocks(currentBlock.getBlock(), block.getBlock())) return false; } - return false; + + BlockEvent.BreakEvent breakEvent = new BlockEvent.BreakEvent(world, pos, currentBlock, player); + MinecraftForge.EVENT_BUS.post(breakEvent); + if(breakEvent.isCanceled()) return false; + + world.removeBlock(pos, false); + return true; } public static int countItem(PlayerEntity player, Item item) { @@ -201,7 +204,8 @@ public class WandUtil if(!world.isBlockModifiable(player, pos)) return false; // If replace mode is off, target has to be air - if(!options.replace.get() && !world.isAirBlock(pos)) return false; + if(!options.replace.get() && !world.isAirBlock(pos)) + if(world.getBlockState(pos).getBlock() != ModBlocks.CONJURED_BLOCK) return false; // Limit placement range if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) @@ -214,7 +218,7 @@ public class WandUtil @Nullable public static PlaceSnapshot getPlaceSnapshot(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, BlockPos pos, BlockItem item, - BlockState supportingBlock, WandOptions options) { + @Nullable BlockState supportingBlock, @Nullable WandOptions options) { // Is block at pos replaceable? BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); if(!ctx.canPlace()) return null; @@ -239,7 +243,7 @@ public class WandUtil if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null; // Copy block properties from supporting block - if(options.direction.get() == WandOptions.DIRECTION.TARGET) { + if(options != null && supportingBlock != null && options.direction.get() == WandOptions.DIRECTION.TARGET) { // Block properties to be copied (alignment/rotation properties) for(Property property : new Property[]{ diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java index 54c8707..f57a137 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -53,7 +53,6 @@ public class WandOptions public final IOption[] allOptions; public WandOptions(ItemStack wandStack) { - ItemWand wand = (ItemWand) wandStack.getItem(); tag = wandStack.getOrCreateChildTag(TAG_ROOT); cores = new WandUpgradesSelectable<>(tag, "cores", new CoreDefault()); diff --git a/src/main/java/thetadev/constructionwand/block/BlockConjured.java b/src/main/java/thetadev/constructionwand/block/BlockConjured.java new file mode 100644 index 0000000..ac5d3ff --- /dev/null +++ b/src/main/java/thetadev/constructionwand/block/BlockConjured.java @@ -0,0 +1,118 @@ +package thetadev.constructionwand.block; + +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.AbstractGlassBlock; +import net.minecraft.block.BlockState; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.wand.supplier.SupplierInventory; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashSet; +import java.util.LinkedList; + +public class BlockConjured extends AbstractGlassBlock +{ + private static final AbstractBlock.IExtendedPositionPredicate> NO_SPAWN = (state, world, pos, et) -> false; + private static final AbstractBlock.IPositionPredicate NOT_SOLID = (state, world, pos) -> false; + + public BlockConjured(String name) { + super(AbstractBlock.Properties.create(Material.SNOW).zeroHardnessAndResistance() + .sound(SoundType.CLOTH).notSolid().setAllowsSpawn(NO_SPAWN) + .setOpaque(NOT_SOLID).setSuffocates(NOT_SOLID).setBlocksVision(NOT_SOLID)); + setRegistryName(ConstructionWand.MODID, name); + } + + @Override + public boolean isReplaceable(@Nonnull BlockState state, @Nonnull BlockItemUseContext useContext) { + return useContext.getItem().getItem() != ModBlocks.CONJURED_BLOCK.asItem(); + } + + @Override + public void harvestBlock(@Nonnull World worldIn, @Nonnull PlayerEntity player, @Nonnull BlockPos pos, + @Nonnull BlockState state, @Nullable TileEntity te, @Nonnull ItemStack stack) { + super.harvestBlock(worldIn, player, pos, state, te, stack); + + if(player.isSneaking()) { + HashSet adjacentBlocks = getAdjacentBlocks(worldIn, pos, 64); + adjacentBlocks.forEach(blockPos -> WandUtil.removeBlock(worldIn, player, null, blockPos)); + } + } + + @Nonnull + @Override + public ActionResultType onBlockActivated(@Nonnull BlockState state, @Nonnull World world, @Nonnull BlockPos pos, + @Nonnull PlayerEntity player, @Nonnull Hand handIn, @Nonnull BlockRayTraceResult hit) { + ItemStack handStack = player.getHeldItem(handIn); + + if(!(handStack.getItem() instanceof BlockItem)) + return super.onBlockActivated(state, world, pos, player, handIn, hit); + BlockItem blockItem = (BlockItem) handStack.getItem(); + if(blockItem.getBlock() == ModBlocks.CONJURED_BLOCK) + return super.onBlockActivated(state, world, pos, player, handIn, hit); + + SupplierInventory supplier = new SupplierInventory(player, world, hit, 64); + supplier.getSupply(blockItem); + + HashSet placePositions = getAdjacentBlocks(world, pos, supplier.getMaxBlocks()); + boolean success = false; + + for(BlockPos placePos : placePositions) { + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(placePos, null); + + if(snapshot != null && snapshot.execute(world, player)) { + if(supplier.takeItemStack(snapshot.getRequiredItems()) > 0) { + ConstructionWand.LOGGER.info("Item could not be taken. Remove block: " + + snapshot.getBlockState().getBlock().toString()); + snapshot.forceRestore(world); + } + else success = true; + } + } + + if(success) { + // Play teleport sound + SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; + world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); + } + + return success ? ActionResultType.SUCCESS : ActionResultType.FAIL; + } + + private HashSet getAdjacentBlocks(World world, BlockPos pos, int maxBlocks) { + HashSet blockPositions = new HashSet<>(); + LinkedList candidates = new LinkedList<>(); + HashSet allCandidates = new HashSet<>(); + candidates.add(pos); + + while(!candidates.isEmpty() && blockPositions.size() < maxBlocks) { + BlockPos currentPos = candidates.removeFirst(); + allCandidates.add(currentPos); + + if(world.getBlockState(currentPos).getBlock() == this || currentPos.equals(pos)) { + blockPositions.add(currentPos); + + for(Direction dir : Direction.values()) { + BlockPos nPos = currentPos.offset(dir); + if(allCandidates.add(nPos)) candidates.add(nPos); + } + } + } + + return blockPositions; + } +} diff --git a/src/main/java/thetadev/constructionwand/block/ModBlocks.java b/src/main/java/thetadev/constructionwand/block/ModBlocks.java new file mode 100644 index 0000000..be9c22e --- /dev/null +++ b/src/main/java/thetadev/constructionwand/block/ModBlocks.java @@ -0,0 +1,30 @@ +package thetadev.constructionwand.block; + +import net.minecraft.block.*; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.RenderTypeLookup; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) +public class ModBlocks +{ + public static final Block CONJURED_BLOCK = new BlockConjured("conjured_block"); + + public static final Block[] ALL_BLOCKS = { + CONJURED_BLOCK + }; + + @SubscribeEvent + public static void registerBlocks(RegistryEvent.Register event) { + event.getRegistry().registerAll(ALL_BLOCKS); + } + + @OnlyIn(Dist.CLIENT) + public static void registerRenderLayers() { + RenderTypeLookup.setRenderLayer(CONJURED_BLOCK, RenderType.getTranslucent()); + } +} diff --git a/src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java b/src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java new file mode 100644 index 0000000..8fb96ee --- /dev/null +++ b/src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java @@ -0,0 +1,32 @@ +package thetadev.constructionwand.data; + +import net.minecraft.block.Block; +import net.minecraft.data.DataGenerator; +import net.minecraftforge.client.model.generators.BlockStateProvider; +import net.minecraftforge.common.data.ExistingFileHelper; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.block.ModBlocks; + +import javax.annotation.Nonnull; + +public class BlockStateGenerator extends BlockStateProvider +{ + public BlockStateGenerator(DataGenerator gen, ExistingFileHelper exFileHelper) { + super(gen, ConstructionWand.MODID, exFileHelper); + } + + @Override + protected void registerStatesAndModels() { + for(Block block : ModBlocks.ALL_BLOCKS) { + if(block instanceof ICustomBlockState) + ((ICustomBlockState) block).generateCustomBlockState(this); + else simpleBlock(block); + } + } + + @Nonnull + @Override + public String getName() { + return ConstructionWand.MODNAME + " blockstates"; + } +} diff --git a/src/main/java/thetadev/constructionwand/data/ICustomBlockState.java b/src/main/java/thetadev/constructionwand/data/ICustomBlockState.java new file mode 100644 index 0000000..cce2865 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/data/ICustomBlockState.java @@ -0,0 +1,6 @@ +package thetadev.constructionwand.data; + +public interface ICustomBlockState +{ + void generateCustomBlockState(BlockStateGenerator generator); +} diff --git a/src/main/java/thetadev/constructionwand/data/ICustomItemModel.java b/src/main/java/thetadev/constructionwand/data/ICustomItemModel.java new file mode 100644 index 0000000..6d7d3e2 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/data/ICustomItemModel.java @@ -0,0 +1,6 @@ +package thetadev.constructionwand.data; + +public interface ICustomItemModel +{ + void generateCustomItemModel(ItemModelGenerator generator, String name); +} diff --git a/src/main/java/thetadev/constructionwand/data/INoItemBlock.java b/src/main/java/thetadev/constructionwand/data/INoItemBlock.java new file mode 100644 index 0000000..65935f2 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/data/INoItemBlock.java @@ -0,0 +1,5 @@ +package thetadev.constructionwand.data; + +public interface INoItemBlock +{ +} diff --git a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java new file mode 100644 index 0000000..864c4f5 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java @@ -0,0 +1,37 @@ +package thetadev.constructionwand.data; + +import net.minecraft.data.DataGenerator; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraftforge.client.model.generators.ItemModelProvider; +import net.minecraftforge.common.data.ExistingFileHelper; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.items.ModItems; + +import javax.annotation.Nonnull; + +public class ItemModelGenerator extends ItemModelProvider +{ + public ItemModelGenerator(DataGenerator generator, ExistingFileHelper existingFileHelper) { + super(generator, ConstructionWand.MODID, existingFileHelper); + } + + @Override + protected void registerModels() { + for(Item item : ModItems.ALL_ITEMS) { + String name = item.getRegistryName().getPath(); + + if(item instanceof ICustomItemModel) + ((ICustomItemModel) item).generateCustomItemModel(this, name); + else if(item instanceof BlockItem) + withExistingParent(name, modLoc("block/"+name)); + else withExistingParent(name, "item/generated").texture("layer0", "item/" + name); + } + } + + @Nonnull + @Override + public String getName() { + return ConstructionWand.MODNAME + " item models"; + } +} diff --git a/src/main/java/thetadev/constructionwand/data/ModData.java b/src/main/java/thetadev/constructionwand/data/ModData.java index d5f95a6..79bba77 100644 --- a/src/main/java/thetadev/constructionwand/data/ModData.java +++ b/src/main/java/thetadev/constructionwand/data/ModData.java @@ -1,6 +1,7 @@ package thetadev.constructionwand.data; import net.minecraft.data.DataGenerator; +import net.minecraftforge.common.data.ExistingFileHelper; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; @@ -11,9 +12,15 @@ public class ModData @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); + ExistingFileHelper fileHelper = event.getExistingFileHelper(); if(event.includeServer()) { generator.addProvider(new RecipeGenerator(generator)); } + + if(event.includeClient()) { + generator.addProvider(new BlockStateGenerator(generator, fileHelper)); + generator.addProvider(new ItemModelGenerator(generator, fileHelper)); + } } } diff --git a/src/main/java/thetadev/constructionwand/items/ItemBase.java b/src/main/java/thetadev/constructionwand/items/ItemBase.java index 12b306c..ef40066 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemBase.java +++ b/src/main/java/thetadev/constructionwand/items/ItemBase.java @@ -1,11 +1,12 @@ package thetadev.constructionwand.items; import net.minecraft.item.Item; +import thetadev.constructionwand.ConstructionWand; public class ItemBase extends Item { - public ItemBase(Properties properties, String name) { + public ItemBase(String name, Properties properties) { super(properties); - setRegistryName(name); + setRegistryName(ConstructionWand.MODID, name); } } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 370c6af..6d6cef6 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -1,11 +1,9 @@ package thetadev.constructionwand.items; +import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.color.ItemColors; -import net.minecraft.item.Item; -import net.minecraft.item.ItemGroup; -import net.minecraft.item.ItemModelsProperties; -import net.minecraft.item.ItemTier; +import net.minecraft.item.*; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -16,6 +14,7 @@ import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.IForgeRegistryEntry; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.core.ItemCoreAngel; import thetadev.constructionwand.items.reservoir.ItemReservoirRandom; @@ -23,27 +22,43 @@ import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.items.wand.ItemWandBasic; import thetadev.constructionwand.items.wand.ItemWandInfinity; +import java.util.Arrays; +import java.util.HashSet; + @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModItems { - public static final Item WAND_STONE = new ItemWandBasic(itemprops(), "stone_wand", ItemTier.STONE); - public static final Item WAND_IRON = new ItemWandBasic(itemprops(), "iron_wand", ItemTier.IRON); - public static final Item WAND_DIAMOND = new ItemWandBasic(itemprops(), "diamond_wand", ItemTier.DIAMOND); - public static final Item WAND_INFINITY = new ItemWandInfinity(itemprops(), "infinity_wand"); + // Wands + public static final Item WAND_STONE = new ItemWandBasic("stone_wand", itemprops(), ItemTier.STONE); + public static final Item WAND_IRON = new ItemWandBasic("iron_wand", itemprops(), ItemTier.IRON); + public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", itemprops(), ItemTier.DIAMOND); + public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand", itemprops()); - public static final Item CORE_ANGEL = new ItemCoreAngel(unstackable(), "core_angel"); - - public static final Item RESERVOIR_RANDOM = new ItemReservoirRandom(unstackable(), "reservoir_random"); + // Upgrades + public static final Item CORE_ANGEL = new ItemCoreAngel("core_angel", unstackable()); + public static final Item RESERVOIR_RANDOM = new ItemReservoirRandom("reservoir_random", unstackable()); + // Collections public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; - public static final Item[] UPGRADES = {CORE_ANGEL, RESERVOIR_RANDOM}; + public static final HashSet ALL_ITEMS = new HashSet<>(); + @SubscribeEvent public static void registerItems(RegistryEvent.Register event) { IForgeRegistry r = event.getRegistry(); r.registerAll(WANDS); - r.registerAll(UPGRADES); + ALL_ITEMS.addAll(Arrays.asList(WANDS)); + + registerItem(r, CORE_ANGEL); + registerItem(r, RESERVOIR_RANDOM); + + // BlockItems + for(Block block : ModBlocks.ALL_BLOCKS) { + BlockItem item = new BlockItem(block, itemprops()); + item.setRegistryName(block.getRegistryName()); + registerItem(r, item); + } } public static Item.Properties itemprops() { @@ -54,8 +69,9 @@ public class ModItems return itemprops().maxStackSize(1); } - private static > void register(IForgeRegistry reg, String name, IForgeRegistryEntry thing) { - reg.register(thing.setRegistryName(ConstructionWand.loc(name))); + private static void registerItem(IForgeRegistry reg, Item item) { + reg.register(item); + ALL_ITEMS.add(item); } @SubscribeEvent @@ -84,4 +100,8 @@ public class ModItems new WandOptions(stack).cores.get().getColor() : -1, item); } } + + private static > void register(IForgeRegistry reg, String name, IForgeRegistryEntry thing) { + reg.register(thing.setRegistryName(ConstructionWand.loc(name))); + } } diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java index 70541c6..1fee01f 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -8,8 +8,8 @@ import thetadev.constructionwand.wand.action.ActionAngel; public class ItemCoreAngel extends ItemBase implements IWandCore { - public ItemCoreAngel(Properties properties, String name) { - super(properties, name); + public ItemCoreAngel(String name, Properties properties) { + super(name, properties); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java index 68016f9..f2c904f 100644 --- a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java +++ b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java @@ -8,8 +8,8 @@ import thetadev.constructionwand.wand.supplier.SupplierRandom; public class ItemReservoirRandom extends ItemBase implements IWandReservoir { - public ItemReservoirRandom(Properties properties, String name) { - super(properties, name); + public ItemReservoirRandom(String name, Properties properties) { + super(name, properties); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index ae437b2..04228a7 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -17,21 +17,24 @@ import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.model.generators.ModelFile; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.data.ICustomItemModel; +import thetadev.constructionwand.data.ItemModelGenerator; import thetadev.constructionwand.items.ItemBase; import thetadev.constructionwand.wand.WandJob; import javax.annotation.Nonnull; import java.util.List; -public abstract class ItemWand extends ItemBase +public abstract class ItemWand extends ItemBase implements ICustomItemModel { - public ItemWand(Properties properties, String name) { - super(properties, name); + public ItemWand(String name, Properties properties) { + super(name, properties); } @Nonnull @@ -128,4 +131,18 @@ public abstract class ItemWand extends ItemBase .append(new TranslationTextComponent(option.getDescTranslation()).mergeStyle(TextFormatting.WHITE)) , true); } + + @Override + public void generateCustomItemModel(ItemModelGenerator generator, String name) { + ModelFile wandWithCore = generator.withExistingParent(name + "_core", "item/handheld") + .texture("layer0", generator.modLoc("item/" + name)) + .texture("layer1", generator.modLoc("item/overlay_core")); + + generator.withExistingParent(name, "item/handheld") + .texture("layer0", generator.modLoc("item/" + name)) + .override() + .predicate(generator.modLoc("using_core"), 1) + .model(wandWithCore).end(); + + } } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java index 2cfb411..f67c61b 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java @@ -11,8 +11,8 @@ public class ItemWandBasic extends ItemWand { private final IItemTier tier; - public ItemWandBasic(Properties properties, String name, IItemTier tier) { - super(properties.maxDamage(tier.getMaxUses()), name); + public ItemWandBasic(String name, Properties properties, IItemTier tier) { + super(name, properties.maxDamage(tier.getMaxUses())); this.tier = tier; } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java index edb175a..304d8e7 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java @@ -6,8 +6,8 @@ import thetadev.constructionwand.basics.ConfigServer; public class ItemWandInfinity extends ItemWand { - public ItemWandInfinity(Properties properties, String name) { - super(properties.maxStackSize(1).isBurnable(), name); + public ItemWandInfinity(String name, Properties properties) { + super(name, properties.maxStackSize(1).isBurnable()); } @Override diff --git a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java index b8c08f7..598cfed 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java +++ b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java @@ -10,12 +10,17 @@ import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.block.ModBlocks; public class WandItemUseContext extends BlockItemUseContext { public WandItemUseContext(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, BlockPos pos, BlockItem item) { super(world, player, Hand.MAIN_HAND, new ItemStack(item), new BlockRayTraceResult(getBlockHitVec(rayTraceResult, pos), rayTraceResult.getFace(), pos, false)); + + // Conjured blocks can be replaced + if(world.getBlockState(pos).getBlock() == ModBlocks.CONJURED_BLOCK && item.getBlock() != ModBlocks.CONJURED_BLOCK) + replaceClicked = true; } private static Vector3d getBlockHitVec(BlockRayTraceResult rayTraceResult, BlockPos pos) { diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index 854a7ac..c59d279 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -17,6 +17,7 @@ import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.basics.pool.IPool; import thetadev.constructionwand.basics.pool.OrderedPool; import thetadev.constructionwand.containers.ContainerManager; +import thetadev.constructionwand.items.ModItems; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.PlaceSnapshot; @@ -27,7 +28,7 @@ import java.util.LinkedHashMap; import java.util.List; /** - * Default WandSupplier. Takes Items from player inventory. + * Default WandSupplier. Takes items from player inventory. */ public class SupplierInventory implements IWandSupplier { @@ -35,8 +36,7 @@ public class SupplierInventory implements IWandSupplier protected final World world; protected final BlockRayTraceResult rayTraceResult; protected final WandOptions options; - protected final ItemStack wand; - protected final ItemWand wandItem; + protected final int wandLimit; protected HashMap itemCounts; protected IPool itemPool; @@ -47,8 +47,15 @@ public class SupplierInventory implements IWandSupplier world = job.world; rayTraceResult = job.rayTraceResult; options = job.options; - wand = job.wand; - wandItem = job.wandItem; + wandLimit = job.wandItem.getLimit(player, job.wand); + } + + public SupplierInventory(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, int wandLimit) { + this.player = player; + this.world = world; + this.rayTraceResult = rayTraceResult; + this.options = new WandOptions(new ItemStack(ModItems.WAND_INFINITY)); + this.wandLimit = wandLimit; } @Override @@ -89,7 +96,7 @@ public class SupplierInventory implements IWandSupplier } } - maxBlocks = Math.min(maxBlocks, wandItem.getLimit(player, wand)); + maxBlocks = Math.min(maxBlocks, wandLimit); } @Override @@ -107,7 +114,7 @@ public class SupplierInventory implements IWandSupplier @Override @Nullable - public PlaceSnapshot getPlaceSnapshot(BlockPos pos, BlockState supportingBlock) { + public PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock) { if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; itemPool.reset(); diff --git a/src/main/resources/assets/constructionwand/models/item/diamond_wand.json b/src/main/resources/assets/constructionwand/models/item/diamond_wand.json deleted file mode 100644 index 01aff39..0000000 --- a/src/main/resources/assets/constructionwand/models/item/diamond_wand.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/diamond_wand" - }, - "overrides": [{ - "predicate": { - "constructionwand:using_core": 1 - }, - "model": "constructionwand:item/diamond_wand_core" - }] -} diff --git a/src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json b/src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json deleted file mode 100644 index fd16367..0000000 --- a/src/main/resources/assets/constructionwand/models/item/diamond_wand_core.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/diamond_wand", - "layer1": "constructionwand:items/overlay_core" - } -} diff --git a/src/main/resources/assets/constructionwand/models/item/infinity_wand.json b/src/main/resources/assets/constructionwand/models/item/infinity_wand.json deleted file mode 100644 index 403cdb5..0000000 --- a/src/main/resources/assets/constructionwand/models/item/infinity_wand.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/infinity_wand" - }, - "overrides": [{ - "predicate": { - "constructionwand:using_core": 1 - }, - "model": "constructionwand:item/infinity_wand_core" - }] -} diff --git a/src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json b/src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json deleted file mode 100644 index 1544e5f..0000000 --- a/src/main/resources/assets/constructionwand/models/item/infinity_wand_core.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/infinity_wand", - "layer1": "constructionwand:items/overlay_core" - } -} diff --git a/src/main/resources/assets/constructionwand/models/item/iron_wand.json b/src/main/resources/assets/constructionwand/models/item/iron_wand.json deleted file mode 100644 index 53e60d3..0000000 --- a/src/main/resources/assets/constructionwand/models/item/iron_wand.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/iron_wand" - }, - "overrides": [{ - "predicate": { - "constructionwand:using_core": 1 - }, - "model": "constructionwand:item/iron_wand_core" - }] -} diff --git a/src/main/resources/assets/constructionwand/models/item/iron_wand_core.json b/src/main/resources/assets/constructionwand/models/item/iron_wand_core.json deleted file mode 100644 index 687e0c5..0000000 --- a/src/main/resources/assets/constructionwand/models/item/iron_wand_core.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/iron_wand", - "layer1": "constructionwand:items/overlay_core" - } -} diff --git a/src/main/resources/assets/constructionwand/models/item/stone_wand.json b/src/main/resources/assets/constructionwand/models/item/stone_wand.json deleted file mode 100644 index d628f8f..0000000 --- a/src/main/resources/assets/constructionwand/models/item/stone_wand.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/stone_wand" - }, - "overrides": [{ - "predicate": { - "constructionwand:using_core": 1 - }, - "model": "constructionwand:item/stone_wand_core" - }] -} diff --git a/src/main/resources/assets/constructionwand/models/item/stone_wand_core.json b/src/main/resources/assets/constructionwand/models/item/stone_wand_core.json deleted file mode 100644 index b78fc5f..0000000 --- a/src/main/resources/assets/constructionwand/models/item/stone_wand_core.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "constructionwand:items/stone_wand", - "layer1": "constructionwand:items/overlay_core" - } -} diff --git a/src/main/resources/assets/constructionwand/textures/block/conjured_block.png b/src/main/resources/assets/constructionwand/textures/block/conjured_block.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc945cb73067de2671ef016b8fa8bd764c9255e GIT binary patch literal 5594 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/constructionwand/textures/item/core_angel.png b/src/main/resources/assets/constructionwand/textures/item/core_angel.png new file mode 100644 index 0000000000000000000000000000000000000000..df6e8fcd4683996c130cdc1f4bb8f6cd8542b5e0 GIT binary patch literal 660 zcmV;F0&D$=P)EX>4Tx04R}tkv&MmKpe$iQ>7}c4t5Z6$WWauh>D1lR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7X`w}o2gm(*ckglc4iIW3rdb_hfTr7K zG9DAtnN>0H3IV+cVFukYvy3@OO2Bh`-NVP%yC~1{KKJJcsacBwK9P8q8KzCVK|Hl- z8=Uuv!>k~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK* z!7Ar1&RVI$n)l={3}*F}Wvu?Dk!1^8&O(yQY<8CKjz^dbo~;!6mk{8 z$gzMjG{~+W{11M2Yvm@!8%eL-Bz8MxA0{&EeN{v^HH z(jrGd-!^b@-O`jj;Bp5Tcrs*Db|pVeA(sQ*&*+;nK>sb!v)1UXxsTHaAVpmzZh(VB zU?fl3>mKj!YVYmeGtK^f09=xCo(OoZ!T_CX>@2HM@dakSAh-}0001MNklsu`;b$Tr}C zhY*9pj(3#1Kw-x_lDxtwKk?mvTpEaR5eqKG#JYloSewudgy~_yr2*Mcd;v_oXe8F@ u^mhTw@3>sTLX0iA3{lwejyMDGB`g40k0>b@S!}2P0000EX>4Tx04R}tkv&MmKpe$iQ>7}c4t5Z6$WWauh>D1lR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7X`w}o2gm(*ckglc4iIW3rdb_hfTr7K zG9DAtnN>0H3IV+cVFukYvy3@OO2Bh`-NVP%yC~1{KKJJcsacBwK9P8q8KzCVK|Hl- z8=Uuv!>k~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK* z!7Ar1&RVI$n)l={3}*F}Wvu?Dk!1^8&O(yQY<8CKjz^dbo~;!6mk{8 z$gzMjG{~+W{11M2Yvm@!8%eL-Bz8MxA0{&EeN{v^HH z(jrGd-!^b@-O`jj;Bp5Tcrs*Db|pVeA(sQ*&*+;nK>sb!v)1UXxsTHaAVpmzZh(VB zU?fl3>mKj!YVYmeGtK^f09=xCo(OoZ!T2n~sD4000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0001ONkl89)l4*Z>w%6eD{J7Dg<%6rl$yuGnEAq=-Bn2Wd wQAyiqB*tqjq(&dQ;RL;a%@xREjFe_)04%O4EX>4Tx04R}tkv&MmKpe$iQ>7}c4t5Z6$WWauh>D1lR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7X`w}o2gm(*ckglc4iIW3rdb_hfTr7K zG9DAtnN>0H3IV+cVFukYvy3@OO2Bh`-NVP%yC~1{KKJJcsacBwK9P8q8KzCVK|Hl- z8=Uuv!>k~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK* z!7Ar1&RVI$n)l={3}*F}Wvu?Dk!1^8&O(yQY<8CKjz^dbo~;!6mk{8 z$gzMjG{~+W{11M2Yvm@!8%eL-Bz8MxA0{&EeN{v^HH z(jrGd-!^b@-O`jj;Bp5Tcrs*Db|pVeA(sQ*&*+;nK>sb!v)1UXxsTHaAVpmzZh(VB zU?fl3>mKj!YVYmeGtK^f09=xCo(OoZ!T?e}|000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0001TNklLy1 zEcpGGpa$A`fmqv-y_mqtj2Q;RXhyb(3E4FC0K^tP=&ocz4{?04fY$}c8ej^rg$Z64 z;IkQ505cI2aQI?}i4x6t4InmMP@EE(hzUe|0089fEeT1bTY~@q002ovPDHLkV1l$} B4y^zH literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/constructionwand/textures/item/reservoir_random.png b/src/main/resources/assets/constructionwand/textures/item/reservoir_random.png new file mode 100644 index 0000000000000000000000000000000000000000..7991eec84ee0fc2855bd751c5afd56ad3aab39ba GIT binary patch literal 679 zcmV;Y0$BZtP)EX>4Tx04R}tkv&MmKpe$iQ>7}c4t5Z6$WWauh>D1lR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7X`w}o2gm(*ckglc4iIW3rdb_hfTr7K zG9DAtnN>0H3IV+cVFukYvy3@OO2Bh`-NVP%yC~1{KKJJcsacBwK9P8q8KzCVK|Hl- z8=Uuv!>k~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK* z!7Ar1&RVI$n)l={3}*F}Wvu?Dk!1^8&O(yQY<8CKjz^dbo~;!6mk{8 z$gzMjG{~+W{11M2Yvm@!8%eL-Bz8MxA0{&EeN{v^HH z(jrGd-!^b@-O`jj;Bp5Tcrs*Db|pVeA(sQ*&*+;nK>sb!v)1UXxsTHaAVpmzZh(VB zU?fl3>mKj!YVYmeGtK^f09=xCo(OoZ!T_CX>@2HM@dakSAh-}0001fNkla_1!omEqMeS{1=s==TR1T=FfcH}(gCiRfN93% zJw`$aiBOa>5%MFUC}kuzDdX}A6S1L<9Kb{wKx~u}6^Qr}8Y89I831;QL@mHSlf?i4 N002ovPDHLkV1jP16E^?= literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/constructionwand/textures/items/stone_wand.png b/src/main/resources/assets/constructionwand/textures/item/stone_wand.png similarity index 100% rename from src/main/resources/assets/constructionwand/textures/items/stone_wand.png rename to src/main/resources/assets/constructionwand/textures/item/stone_wand.png diff --git a/src/main/resources/assets/constructionwand/textures/items/overlay_angel.png b/src/main/resources/assets/constructionwand/textures/items/overlay_angel.png deleted file mode 100644 index be348ade027820918082e4642f0377cc01f44439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)2#X*k?NMQuIx`Z7#Wz2w9M~b1`0`*xJHyX=jZ08=9Mrw7o{eaq^2m8 zXO?6rxO@5rgg5eu0~J|#x;TbdoK8-VhbP0l+XkKURyxH From a0317115b757f6abe56ed2f0a568685c661eb614 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 17 Feb 2021 23:17:42 +0100 Subject: [PATCH 25/78] Completed conjured block behavior Added all wand upgrades --- gradle.properties | 4 +- .../models/item/reservoir_conjuration.json | 6 ++ .../models/item/reservoir_petrogenesis.json | 6 ++ .../constructionwand/api/IWandCore.java | 2 +- .../constructionwand/basics/WandUtil.java | 4 +- .../constructionwand/block/BlockConjured.java | 67 ++++++++++-------- .../constructionwand/items/ModItems.java | 9 +++ .../items/core/CoreDefault.java | 4 +- .../items/core/ItemCoreAngel.java | 4 +- .../items/core/ItemCoreDestruction.java | 24 +++++++ .../reservoir/ItemReservoirConjuration.java | 19 +++++ .../reservoir/ItemReservoirPetrogenesis.java | 19 +++++ .../constructionwand/wand/WandJob.java | 20 +++--- .../wand/action/ActionAngel.java | 3 +- .../wand/action/ActionDestruction.java | 30 ++++++++ .../wand/supplier/SupplierBlockgen.java | 57 +++++++++++++++ .../wand/supplier/SupplierConjuration.java | 17 +++++ .../wand/supplier/SupplierPetrogenesis.java | 17 +++++ .../wand/undo/UndoHistory.java | 3 +- .../assets/constructionwand/lang/en_us.json | 15 ++-- .../textures/item/reservoir_petrogenesis.png | Bin 0 -> 1665 bytes 21 files changed, 275 insertions(+), 55 deletions(-) create mode 100644 src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json create mode 100644 src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json create mode 100644 src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java create mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java create mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java create mode 100644 src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java create mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java create mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java create mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java create mode 100644 src/main/resources/assets/constructionwand/textures/item/reservoir_petrogenesis.png diff --git a/gradle.properties b/gradle.properties index 20443b3..1a0ba34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,5 +12,5 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 -version_major=1 -version_minor=8 \ No newline at end of file +version_major=2 +version_minor=0 \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json b/src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json new file mode 100644 index 0000000..5fe4028 --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "constructionwand:item/reservoir_conjuration" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json b/src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json new file mode 100644 index 0000000..cc86deb --- /dev/null +++ b/src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "constructionwand:item/reservoir_petrogenesis" + } +} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/api/IWandCore.java b/src/main/java/thetadev/constructionwand/api/IWandCore.java index f1175ba..02ef03a 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandCore.java +++ b/src/main/java/thetadev/constructionwand/api/IWandCore.java @@ -6,5 +6,5 @@ public interface IWandCore extends IWandUpgrade { int getColor(); - IWandAction getWandAction(WandJob wandJob); + IWandAction getWandAction(WandJob job); } diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 3fb95d5..111db26 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -221,7 +221,9 @@ public class WandUtil @Nullable BlockState supportingBlock, @Nullable WandOptions options) { // Is block at pos replaceable? BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); - if(!ctx.canPlace()) return null; + + if(!(world.getBlockState(pos).getBlock() == ModBlocks.CONJURED_BLOCK && item.getBlock() != ModBlocks.CONJURED_BLOCK)) + if(!ctx.canPlace()) return null; // Can block be placed? BlockState blockState = item.getBlock().getStateForPlacement(ctx); diff --git a/src/main/java/thetadev/constructionwand/block/BlockConjured.java b/src/main/java/thetadev/constructionwand/block/BlockConjured.java index ac5d3ff..2b839a8 100644 --- a/src/main/java/thetadev/constructionwand/block/BlockConjured.java +++ b/src/main/java/thetadev/constructionwand/block/BlockConjured.java @@ -11,19 +11,28 @@ import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.*; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.items.ModItems; +import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.supplier.SupplierInventory; -import thetadev.constructionwand.wand.undo.PlaceSnapshot; +import thetadev.constructionwand.wand.undo.ISnapshot; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; public class BlockConjured extends AbstractGlassBlock { @@ -31,7 +40,7 @@ public class BlockConjured extends AbstractGlassBlock private static final AbstractBlock.IPositionPredicate NOT_SOLID = (state, world, pos) -> false; public BlockConjured(String name) { - super(AbstractBlock.Properties.create(Material.SNOW).zeroHardnessAndResistance() + super(AbstractBlock.Properties.create(Material.SNOW).hardnessAndResistance(0.2F) .sound(SoundType.CLOTH).notSolid().setAllowsSpawn(NO_SPAWN) .setOpaque(NOT_SOLID).setSuffocates(NOT_SOLID).setBlocksVision(NOT_SOLID)); setRegistryName(ConstructionWand.MODID, name); @@ -65,35 +74,12 @@ public class BlockConjured extends AbstractGlassBlock if(blockItem.getBlock() == ModBlocks.CONJURED_BLOCK) return super.onBlockActivated(state, world, pos, player, handIn, hit); - SupplierInventory supplier = new SupplierInventory(player, world, hit, 64); - supplier.getSupply(blockItem); - - HashSet placePositions = getAdjacentBlocks(world, pos, supplier.getMaxBlocks()); - boolean success = false; - - for(BlockPos placePos : placePositions) { - PlaceSnapshot snapshot = supplier.getPlaceSnapshot(placePos, null); - - if(snapshot != null && snapshot.execute(world, player)) { - if(supplier.takeItemStack(snapshot.getRequiredItems()) > 0) { - ConstructionWand.LOGGER.info("Item could not be taken. Remove block: " + - snapshot.getBlockState().getBlock().toString()); - snapshot.forceRestore(world); - } - else success = true; - } - } - - if(success) { - // Play teleport sound - SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; - world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); - } - - return success ? ActionResultType.SUCCESS : ActionResultType.FAIL; + WandJob job = new WandJob(player, world, hit, new ItemStack(ModItems.WAND_INFINITY)); + job.getPlaceSnapshots(new ActionConjuredBlocks(job), new SupplierInventory(job), blockItem); + return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; } - private HashSet getAdjacentBlocks(World world, BlockPos pos, int maxBlocks) { + public static HashSet getAdjacentBlocks(World world, BlockPos pos, int maxBlocks) { HashSet blockPositions = new HashSet<>(); LinkedList candidates = new LinkedList<>(); HashSet allCandidates = new HashSet<>(); @@ -103,7 +89,7 @@ public class BlockConjured extends AbstractGlassBlock BlockPos currentPos = candidates.removeFirst(); allCandidates.add(currentPos); - if(world.getBlockState(currentPos).getBlock() == this || currentPos.equals(pos)) { + if(world.getBlockState(currentPos).getBlock() == ModBlocks.CONJURED_BLOCK || currentPos.equals(pos)) { blockPositions.add(currentPos); for(Direction dir : Direction.values()) { @@ -115,4 +101,23 @@ public class BlockConjured extends AbstractGlassBlock return blockPositions; } + + private static class ActionConjuredBlocks implements IWandAction + { + private final World world; + private final BlockRayTraceResult rayTraceResult; + + public ActionConjuredBlocks(WandJob wandJob) { + world = wandJob.world; + rayTraceResult = wandJob.rayTraceResult; + } + + @Override + public List getSnapshots(IWandSupplier supplier) { + HashSet adjacentBlocks = BlockConjured.getAdjacentBlocks(world, rayTraceResult.getPos(), supplier.getMaxBlocks()); + + return adjacentBlocks.stream().map(blockPos -> supplier.getPlaceSnapshot(blockPos, null)) + .filter(Objects::nonNull).collect(Collectors.toList()); + } + } } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 6d6cef6..94dcdf1 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -17,6 +17,9 @@ import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.core.ItemCoreAngel; +import thetadev.constructionwand.items.core.ItemCoreDestruction; +import thetadev.constructionwand.items.reservoir.ItemReservoirConjuration; +import thetadev.constructionwand.items.reservoir.ItemReservoirPetrogenesis; import thetadev.constructionwand.items.reservoir.ItemReservoirRandom; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.items.wand.ItemWandBasic; @@ -36,7 +39,11 @@ public class ModItems // Upgrades public static final Item CORE_ANGEL = new ItemCoreAngel("core_angel", unstackable()); + public static final Item CORE_DESTRUCTION = new ItemCoreDestruction("core_destruction", unstackable()); + public static final Item RESERVOIR_RANDOM = new ItemReservoirRandom("reservoir_random", unstackable()); + public static final Item RESERVOIR_CONJURATION = new ItemReservoirConjuration("reservoir_conjuration", unstackable()); + public static final Item RESERVOIR_PETROGENESIS = new ItemReservoirPetrogenesis("reservoir_petrogenesis", unstackable()); // Collections public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; @@ -52,6 +59,8 @@ public class ModItems registerItem(r, CORE_ANGEL); registerItem(r, RESERVOIR_RANDOM); + registerItem(r, RESERVOIR_CONJURATION); + registerItem(r, RESERVOIR_PETROGENESIS); // BlockItems for(Block block : ModBlocks.ALL_BLOCKS) { diff --git a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java index af07320..84588d7 100644 --- a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java +++ b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java @@ -15,8 +15,8 @@ public class CoreDefault implements IWandCore } @Override - public IWandAction getWandAction(WandJob wandJob) { - return new ActionConstruction(wandJob); + public IWandAction getWandAction(WandJob job) { + return new ActionConstruction(job); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java index 1fee01f..6190382 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -18,7 +18,7 @@ public class ItemCoreAngel extends ItemBase implements IWandCore } @Override - public IWandAction getWandAction(WandJob wandJob) { - return new ActionAngel(wandJob); + public IWandAction getWandAction(WandJob job) { + return new ActionAngel(job); } } diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java new file mode 100644 index 0000000..6971129 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java @@ -0,0 +1,24 @@ +package thetadev.constructionwand.items.core; + +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandCore; +import thetadev.constructionwand.items.ItemBase; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.action.ActionDestruction; + +public class ItemCoreDestruction extends ItemBase implements IWandCore +{ + public ItemCoreDestruction(String name, Properties properties) { + super(name, properties); + } + + @Override + public int getColor() { + return 0xFF0000; + } + + @Override + public IWandAction getWandAction(WandJob job) { + return new ActionDestruction(job); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java new file mode 100644 index 0000000..4e22762 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java @@ -0,0 +1,19 @@ +package thetadev.constructionwand.items.reservoir; + +import thetadev.constructionwand.api.IWandReservoir; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.items.ItemBase; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.supplier.SupplierConjuration; + +public class ItemReservoirConjuration extends ItemBase implements IWandReservoir +{ + public ItemReservoirConjuration(String name, Properties properties) { + super(name, properties); + } + + @Override + public IWandSupplier getWandSupplier(WandJob job) { + return new SupplierConjuration(job); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java new file mode 100644 index 0000000..c6bb4b2 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java @@ -0,0 +1,19 @@ +package thetadev.constructionwand.items.reservoir; + +import thetadev.constructionwand.api.IWandReservoir; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.items.ItemBase; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.supplier.SupplierPetrogenesis; + +public class ItemReservoirPetrogenesis extends ItemBase implements IWandReservoir +{ + public ItemReservoirPetrogenesis(String name, Properties properties) { + super(name, properties); + } + + @Override + public IWandSupplier getWandSupplier(WandJob job) { + return new SupplierPetrogenesis(job); + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index af14aa8..2206db8 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -18,6 +18,7 @@ import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.undo.ISnapshot; +import javax.annotation.Nullable; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -29,7 +30,6 @@ public class WandJob public final World world; public final BlockRayTraceResult rayTraceResult; public final WandOptions options; - public final BlockItem targetItem; public final ItemStack wand; public final ItemWand wandItem; @@ -49,16 +49,20 @@ public class WandJob this.wand = wand; this.wandItem = (ItemWand) wand.getItem(); options = new WandOptions(wand); - - // Get target item - Item tgitem = world.getBlockState(rayTraceResult.getPos()).getBlock().asItem(); - if(!(tgitem instanceof BlockItem)) tgitem = null; - targetItem = (BlockItem) tgitem; } public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier) { + // Get target item + Item tgitem = world.getBlockState(rayTraceResult.getPos()).getBlock().asItem(); + if(!(tgitem instanceof BlockItem)) tgitem = null; + BlockItem targetItem = (BlockItem) tgitem; + + getPlaceSnapshots(wandAction, wandSupplier, targetItem); + } + + public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier, @Nullable BlockItem targetItem) { this.wandSupplier = wandSupplier; - wandSupplier.getSupply((BlockItem) targetItem); + wandSupplier.getSupply(targetItem); this.wandAction = wandAction; placeSnapshots = wandAction.getSnapshots(wandSupplier); } @@ -71,7 +75,7 @@ public class WandJob LinkedList executed = new LinkedList<>(); for(ISnapshot snapshot : placeSnapshots) { - if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) continue; + if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) break; if(snapshot.execute(world, player)) { // If the item cant be taken, undo the placement diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java index 8b83ea2..bb297e7 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -1,7 +1,6 @@ package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; @@ -51,7 +50,7 @@ public class ActionAngel implements IWandAction BlockPos currentPos = new BlockPos(placeVec); - PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentPos, Blocks.AIR.getDefaultState()); + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentPos, null); if(snapshot != null) placeSnapshots.add(snapshot); return placeSnapshots; diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java new file mode 100644 index 0000000..fe20c22 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -0,0 +1,30 @@ +package thetadev.constructionwand.wand.action; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.undo.ISnapshot; + +import java.util.List; + +public class ActionDestruction implements IWandAction +{ + private final World world; + private final PlayerEntity player; + private final BlockRayTraceResult rayTraceResult; + + public ActionDestruction(WandJob wandJob) { + world = wandJob.world; + player = wandJob.player; + rayTraceResult = wandJob.rayTraceResult; + } + + @Override + public List getSnapshots(IWandSupplier supplier) { + // TODO: Destruction action! + return null; + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java new file mode 100644 index 0000000..6bf178c --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java @@ -0,0 +1,57 @@ +package thetadev.constructionwand.wand.supplier; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.undo.PlaceSnapshot; + +import javax.annotation.Nullable; + +public abstract class SupplierBlockgen implements IWandSupplier +{ + private final PlayerEntity player; + private final World world; + private final BlockRayTraceResult rayTraceResult; + private final WandOptions options; + private final int wandLimit; + + public SupplierBlockgen(WandJob job) { + player = job.player; + world = job.world; + rayTraceResult = job.rayTraceResult; + options = job.options; + wandLimit = job.wandItem.getLimit(player, job.wand); + } + + @Override + public void getSupply(@Nullable BlockItem target) { + } + + @Override + public int getMaxBlocks() { + return wandLimit; + } + + @Nullable + @Override + public PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock) { + if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; + + return WandUtil.getPlaceSnapshot(world, player, rayTraceResult, pos, getBlockItem(), supportingBlock, options); + } + + @Override + public int takeItemStack(ItemStack stack) { + return 0; + } + + protected abstract BlockItem getBlockItem(); +} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java new file mode 100644 index 0000000..a9f570f --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java @@ -0,0 +1,17 @@ +package thetadev.constructionwand.wand.supplier; + +import net.minecraft.item.BlockItem; +import thetadev.constructionwand.block.ModBlocks; +import thetadev.constructionwand.wand.WandJob; + +public class SupplierConjuration extends SupplierBlockgen +{ + public SupplierConjuration(WandJob job) { + super(job); + } + + @Override + protected BlockItem getBlockItem() { + return (BlockItem) ModBlocks.CONJURED_BLOCK.asItem(); + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java new file mode 100644 index 0000000..93da96b --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java @@ -0,0 +1,17 @@ +package thetadev.constructionwand.wand.supplier; + +import net.minecraft.item.BlockItem; +import net.minecraft.item.Items; +import thetadev.constructionwand.wand.WandJob; + +public class SupplierPetrogenesis extends SupplierBlockgen +{ + public SupplierPetrogenesis(WandJob job) { + super(job); + } + + @Override + protected BlockItem getBlockItem() { + return (BlockItem) Items.COBBLESTONE; + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index 15e24e8..e0ba705 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -12,6 +12,7 @@ import net.minecraftforge.fml.network.PacketDistributor; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.network.PacketUndoBlocks; import java.util.*; @@ -113,7 +114,7 @@ public class UndoHistory public boolean undo(PlayerEntity player) { for(ISnapshot snapshot : placeSnapshots) { - if(snapshot.restore(world, player) && !player.isCreative()) { + if(snapshot.restore(world, player) && !player.isCreative() && snapshot.getBlockState().getBlock() != ModBlocks.CONJURED_BLOCK) { ItemStack stack = snapshot.getRequiredItems(); if(!player.inventory.addItemStackToInventory(stack)) { diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index c8ef751..2dcf6ae 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -3,25 +3,30 @@ "item.constructionwand.iron_wand": "Iron Wand", "item.constructionwand.diamond_wand": "Diamond Wand", "item.constructionwand.infinity_wand": "Infinity Wand", - "item.constructionwand.core_angel": "Angel Wand Core", + "item.constructionwand.core_destruction": "Destruction Wand Core", "item.constructionwand.reservoir_random": "Random Wand Reservoir", - + "item.constructionwand.reservoir_conjuration": "Conjuration Wand Reservoir", + "item.constructionwand.reservoir_petrogenesis": "Petrogenesis Wand Reservoir", + "block.constructionwand.conjured_block": "Conjured Block", "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", - "constructionwand.option.cores": "", "constructionwand.option.cores.constructionwand:default": "Construction Core", "constructionwand.option.cores.constructionwand:default.desc": "Extend your building on the side facing you", "constructionwand.option.cores.constructionwand:core_angel": "§6Angel Core", "constructionwand.option.cores.constructionwand:core_angel.desc": "Place behind blocks and in mid air", - + "constructionwand.option.cores.constructionwand:core_destruction": "§cDestruction Core", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "Destroys blocks on the side facing you", "constructionwand.option.reservoirs": "", "constructionwand.option.reservoirs.constructionwand:default": "Inventory Reservoir", "constructionwand.option.reservoirs.constructionwand:default.desc": "Delivers the matching building material from your inventory. Take a block into your offhand to specify a different material.", "constructionwand.option.reservoirs.constructionwand:reservoir_random": "Random Reservoir", "constructionwand.option.reservoirs.constructionwand:reservoir_random.desc": "Takes random blocks from your hotbar", - + "constructionwand.option.reservoirs.constructionwand:reservoir_conjuration": "Conjuration Reservoir", + "constructionwand.option.reservoirs.constructionwand:reservoir_conjuration.desc": "Places conjured blocks. These act as placeholder blocks and can be replaced by a block of choice.", + "constructionwand.option.reservoirs.constructionwand:reservoir_petrogenesis": "Petrogenesis Reservoir", + "constructionwand.option.reservoirs.constructionwand:reservoir_petrogenesis.desc": "Creates an infinite amount of cobblestone", "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in front of the original block", diff --git a/src/main/resources/assets/constructionwand/textures/item/reservoir_petrogenesis.png b/src/main/resources/assets/constructionwand/textures/item/reservoir_petrogenesis.png new file mode 100644 index 0000000000000000000000000000000000000000..2320311421d3d044f410cb1404aa7b90dde30c46 GIT binary patch literal 1665 zcmV-{27dX8P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+Rc`0vhyYkhW~37T>=vK<#3((cF^Vb1(WMZP8_?_ zshJoogCz7wIMn|B)9Eir|S!WNTb)= zV{l8}AQ#M+B`-@)g52IIifz5fk1x_vBw-o2SX_oQZw6FdiqcY~Wmz`cb6%ER4E}x# z^|FJoBw_o}xEbxmS;yYsZbW(SBE~_K@rYX;j`kvPth0I3^ZZPgsREy*Y1V4q!{((P zKh|mX2_r8TiJo^eXJRJ=WN-nV#}(VAuF-sm&6UdQi}q}5rNaa6>u99`{dn<|2Cgl+ zexm2D{gl)53%0LuPo{Ssd2=-^4<5NPB6W9#+pqT;dT-nAl5yCwTbsJ@CdVlW5>`9y!6xbi zSjJf7k2e81l&rSKTI;O0!A6^$c7{-4&UrVE+vK%3-g@V~4?g-7bTGjN7kmgIh7=`g zoW?ey9Ak(vrUaV_nv5UV@?I>Q%teN6<PpzJ?lW zYTS@Y%`{8RHQz#uEp_Bt*WGm6UH3io*wdBTqWYtKL5&wRnWV-%f29Vg_ghahU!Y_# z12HiS;(ic7Ld#&L0vjWP++e1npen4uNjun7R*9V$(x$Q4&R2Ffa$j*HXMW`-d&$v2 zcaq%XM$O)FdqJ&^&vu!_wkjMkRl1LFaYzzU&sjG%B=c!}QP1`Ny`rw{7)WlcdQNIS z;5@(dvN9+E4#?Gh|ME z&HfMnkKUCpo%}Ao&m;V6jPwsRb8y(8%9T9;00D(*LqkwWLqi~Na&Km7Y-Iodc$|Ha zJxIeq9K~N#r6Ls(JBWy6s7@9{MFbbELJ=y2TA@`3lS@B@CJjl7i=*ILaPVWX>fqw6 ztAnc`2!4RLxj8AiNQwVT3N2zhIPS;0dyl(!fKV$j&1xG3G~G5+iMW`_u85&m29s$1I#dwzgxj#pjnzI-X5Q%4)VcNv&#FLx0!FiuJ!ius=d`>)K z(glehxvqHp#<}3Kz%wIeIyFZeAr=cQth6vIni}y0aa7fG$`>*otDLtuYo!Wn+>^gB zoYR+=xK48b2`pd{5=1DdU;`!Ch|#K(Vj)HQaUXxb>zBx-kZS{s9P=ncgY5dj|KNAG zR(^cUOA5z<_7}(b7y?4OK)vcX-^Y$qKLLWzz?I(gmutY(C+XFu7Ci#Gw}Ff6rl#xx zmpee;lOdb3D+Or^`8@D`M&FbLdT)WwRj;?kK29HiGeSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2! zfese{004-vDmqJ`I({*(>>!2lXp Date: Thu, 18 Feb 2021 22:00:33 +0100 Subject: [PATCH 26/78] Better handling of generated (conjured) blocks Removed petrogenesis core --- .../models/item/reservoir_petrogenesis.json | 6 ---- .../constructionwand/basics/WandUtil.java | 12 +++---- .../constructionwand/block/BlockConjured.java | 9 +++-- .../constructionwand/items/ModItems.java | 3 -- .../reservoir/ItemReservoirPetrogenesis.java | 19 ---------- .../wand/supplier/SupplierBlockgen.java | 3 +- .../wand/supplier/SupplierInventory.java | 2 +- .../wand/supplier/SupplierPetrogenesis.java | 17 --------- .../wand/undo/BlockgenSnapshot.java | 34 ++++++++++++++++++ .../wand/undo/PlaceSnapshot.java | 13 +++++++ .../wand/undo/UndoHistory.java | 2 +- .../assets/constructionwand/lang/en_us.json | 3 -- .../textures/item/reservoir_petrogenesis.png | Bin 1665 -> 0 bytes 13 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json delete mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java delete mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java create mode 100644 src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java delete mode 100644 src/main/resources/assets/constructionwand/textures/item/reservoir_petrogenesis.png diff --git a/src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json b/src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json deleted file mode 100644 index cc86deb..0000000 --- a/src/generated/resources/assets/constructionwand/models/item/reservoir_petrogenesis.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "minecraft:item/generated", - "textures": { - "layer0": "constructionwand:item/reservoir_petrogenesis" - } -} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 111db26..20b06db 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -33,6 +33,7 @@ import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.WandItemUseContext; +import thetadev.constructionwand.wand.undo.BlockgenSnapshot; import thetadev.constructionwand.wand.undo.PlaceSnapshot; import javax.annotation.Nullable; @@ -112,7 +113,7 @@ public class WandUtil return isWhitelist == inList; } - public static boolean placeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos, BlockItem item) { + public static boolean placeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos, @Nullable BlockItem item) { if(!world.setBlockState(pos, block)) { ConstructionWand.LOGGER.info("Block could not be placed"); return false; @@ -216,9 +217,9 @@ public class WandUtil @SuppressWarnings({"rawtypes", "unchecked"}) @Nullable - public static PlaceSnapshot getPlaceSnapshot(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - BlockPos pos, BlockItem item, - @Nullable BlockState supportingBlock, @Nullable WandOptions options) { + public static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + BlockPos pos, BlockItem item, + @Nullable BlockState supportingBlock, @Nullable WandOptions options) { // Is block at pos replaceable? BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); @@ -262,8 +263,7 @@ public class WandUtil if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); } } - - return new PlaceSnapshot(blockState, pos, item); + return blockState; } public static Direction fromVector(Vector3d vector) { diff --git a/src/main/java/thetadev/constructionwand/block/BlockConjured.java b/src/main/java/thetadev/constructionwand/block/BlockConjured.java index 2b839a8..d2c7248 100644 --- a/src/main/java/thetadev/constructionwand/block/BlockConjured.java +++ b/src/main/java/thetadev/constructionwand/block/BlockConjured.java @@ -9,6 +9,7 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ActionResultType; @@ -67,15 +68,13 @@ public class BlockConjured extends AbstractGlassBlock public ActionResultType onBlockActivated(@Nonnull BlockState state, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull PlayerEntity player, @Nonnull Hand handIn, @Nonnull BlockRayTraceResult hit) { ItemStack handStack = player.getHeldItem(handIn); + Item handItem = handStack.getItem(); - if(!(handStack.getItem() instanceof BlockItem)) - return super.onBlockActivated(state, world, pos, player, handIn, hit); - BlockItem blockItem = (BlockItem) handStack.getItem(); - if(blockItem.getBlock() == ModBlocks.CONJURED_BLOCK) + if(!(handItem instanceof BlockItem) || ((BlockItem) handItem).getBlock() == ModBlocks.CONJURED_BLOCK) return super.onBlockActivated(state, world, pos, player, handIn, hit); WandJob job = new WandJob(player, world, hit, new ItemStack(ModItems.WAND_INFINITY)); - job.getPlaceSnapshots(new ActionConjuredBlocks(job), new SupplierInventory(job), blockItem); + job.getPlaceSnapshots(new ActionConjuredBlocks(job), new SupplierInventory(job), (BlockItem) handItem); return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 94dcdf1..9bea943 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -19,7 +19,6 @@ import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.core.ItemCoreAngel; import thetadev.constructionwand.items.core.ItemCoreDestruction; import thetadev.constructionwand.items.reservoir.ItemReservoirConjuration; -import thetadev.constructionwand.items.reservoir.ItemReservoirPetrogenesis; import thetadev.constructionwand.items.reservoir.ItemReservoirRandom; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.items.wand.ItemWandBasic; @@ -43,7 +42,6 @@ public class ModItems public static final Item RESERVOIR_RANDOM = new ItemReservoirRandom("reservoir_random", unstackable()); public static final Item RESERVOIR_CONJURATION = new ItemReservoirConjuration("reservoir_conjuration", unstackable()); - public static final Item RESERVOIR_PETROGENESIS = new ItemReservoirPetrogenesis("reservoir_petrogenesis", unstackable()); // Collections public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; @@ -60,7 +58,6 @@ public class ModItems registerItem(r, CORE_ANGEL); registerItem(r, RESERVOIR_RANDOM); registerItem(r, RESERVOIR_CONJURATION); - registerItem(r, RESERVOIR_PETROGENESIS); // BlockItems for(Block block : ModBlocks.ALL_BLOCKS) { diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java deleted file mode 100644 index c6bb4b2..0000000 --- a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirPetrogenesis.java +++ /dev/null @@ -1,19 +0,0 @@ -package thetadev.constructionwand.items.reservoir; - -import thetadev.constructionwand.api.IWandReservoir; -import thetadev.constructionwand.api.IWandSupplier; -import thetadev.constructionwand.items.ItemBase; -import thetadev.constructionwand.wand.WandJob; -import thetadev.constructionwand.wand.supplier.SupplierPetrogenesis; - -public class ItemReservoirPetrogenesis extends ItemBase implements IWandReservoir -{ - public ItemReservoirPetrogenesis(String name, Properties properties) { - super(name, properties); - } - - @Override - public IWandSupplier getWandSupplier(WandJob job) { - return new SupplierPetrogenesis(job); - } -} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java index 6bf178c..ed867b0 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java @@ -11,6 +11,7 @@ import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.wand.undo.BlockgenSnapshot; import thetadev.constructionwand.wand.undo.PlaceSnapshot; import javax.annotation.Nullable; @@ -45,7 +46,7 @@ public abstract class SupplierBlockgen implements IWandSupplier public PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock) { if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; - return WandUtil.getPlaceSnapshot(world, player, rayTraceResult, pos, getBlockItem(), supportingBlock, options); + return BlockgenSnapshot.get(world, player, rayTraceResult, pos, getBlockItem(), supportingBlock, options); } @Override diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index c59d279..4b491f4 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -126,7 +126,7 @@ public class SupplierInventory implements IWandSupplier int count = itemCounts.get(item); if(count == 0) continue; - PlaceSnapshot placeSnapshot = WandUtil.getPlaceSnapshot(world, player, rayTraceResult, pos, item, supportingBlock, options); + PlaceSnapshot placeSnapshot = PlaceSnapshot.get(world, player, rayTraceResult, pos, item, supportingBlock, options); if(placeSnapshot != null) return placeSnapshot; } } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java deleted file mode 100644 index 93da96b..0000000 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierPetrogenesis.java +++ /dev/null @@ -1,17 +0,0 @@ -package thetadev.constructionwand.wand.supplier; - -import net.minecraft.item.BlockItem; -import net.minecraft.item.Items; -import thetadev.constructionwand.wand.WandJob; - -public class SupplierPetrogenesis extends SupplierBlockgen -{ - public SupplierPetrogenesis(WandJob job) { - super(job); - } - - @Override - protected BlockItem getBlockItem() { - return (BlockItem) Items.COBBLESTONE; - } -} diff --git a/src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java new file mode 100644 index 0000000..d6e63ee --- /dev/null +++ b/src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java @@ -0,0 +1,34 @@ +package thetadev.constructionwand.wand.undo; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.option.WandOptions; + +import javax.annotation.Nullable; + +public class BlockgenSnapshot extends PlaceSnapshot +{ + public BlockgenSnapshot(BlockState block, BlockPos pos) { + super(block, pos, null); + } + + @Nullable + public static BlockgenSnapshot get(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + BlockPos pos, BlockItem item, + @Nullable BlockState supportingBlock, @Nullable WandOptions options) { + BlockState blockState = WandUtil.getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, options); + if(blockState == null) return null; + return new BlockgenSnapshot(blockState, pos); + } + + @Override + public ItemStack getRequiredItems() { + return ItemStack.EMPTY; + } +} diff --git a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index ea42ff1..d8a03f6 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -5,8 +5,12 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.option.WandOptions; + +import javax.annotation.Nullable; public class PlaceSnapshot implements ISnapshot { @@ -20,6 +24,15 @@ public class PlaceSnapshot implements ISnapshot this.item = item; } + @Nullable + public static PlaceSnapshot get(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + BlockPos pos, BlockItem item, + @Nullable BlockState supportingBlock, @Nullable WandOptions options) { + BlockState blockState = WandUtil.getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, options); + if(blockState == null) return null; + return new PlaceSnapshot(blockState, pos, item); + } + @Override public BlockPos getPos() { return pos; diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index e0ba705..19b7a17 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -114,7 +114,7 @@ public class UndoHistory public boolean undo(PlayerEntity player) { for(ISnapshot snapshot : placeSnapshots) { - if(snapshot.restore(world, player) && !player.isCreative() && snapshot.getBlockState().getBlock() != ModBlocks.CONJURED_BLOCK) { + if(snapshot.restore(world, player) && !player.isCreative()) { ItemStack stack = snapshot.getRequiredItems(); if(!player.inventory.addItemStackToInventory(stack)) { diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 2dcf6ae..bedd300 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -7,7 +7,6 @@ "item.constructionwand.core_destruction": "Destruction Wand Core", "item.constructionwand.reservoir_random": "Random Wand Reservoir", "item.constructionwand.reservoir_conjuration": "Conjuration Wand Reservoir", - "item.constructionwand.reservoir_petrogenesis": "Petrogenesis Wand Reservoir", "block.constructionwand.conjured_block": "Conjured Block", "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", @@ -25,8 +24,6 @@ "constructionwand.option.reservoirs.constructionwand:reservoir_random.desc": "Takes random blocks from your hotbar", "constructionwand.option.reservoirs.constructionwand:reservoir_conjuration": "Conjuration Reservoir", "constructionwand.option.reservoirs.constructionwand:reservoir_conjuration.desc": "Places conjured blocks. These act as placeholder blocks and can be replaced by a block of choice.", - "constructionwand.option.reservoirs.constructionwand:reservoir_petrogenesis": "Petrogenesis Reservoir", - "constructionwand.option.reservoirs.constructionwand:reservoir_petrogenesis.desc": "Creates an infinite amount of cobblestone", "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in front of the original block", diff --git a/src/main/resources/assets/constructionwand/textures/item/reservoir_petrogenesis.png b/src/main/resources/assets/constructionwand/textures/item/reservoir_petrogenesis.png deleted file mode 100644 index 2320311421d3d044f410cb1404aa7b90dde30c46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1665 zcmV-{27dX8P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+Rc`0vhyYkhW~37T>=vK<#3((cF^Vb1(WMZP8_?_ zshJoogCz7wIMn|B)9Eir|S!WNTb)= zV{l8}AQ#M+B`-@)g52IIifz5fk1x_vBw-o2SX_oQZw6FdiqcY~Wmz`cb6%ER4E}x# z^|FJoBw_o}xEbxmS;yYsZbW(SBE~_K@rYX;j`kvPth0I3^ZZPgsREy*Y1V4q!{((P zKh|mX2_r8TiJo^eXJRJ=WN-nV#}(VAuF-sm&6UdQi}q}5rNaa6>u99`{dn<|2Cgl+ zexm2D{gl)53%0LuPo{Ssd2=-^4<5NPB6W9#+pqT;dT-nAl5yCwTbsJ@CdVlW5>`9y!6xbi zSjJf7k2e81l&rSKTI;O0!A6^$c7{-4&UrVE+vK%3-g@V~4?g-7bTGjN7kmgIh7=`g zoW?ey9Ak(vrUaV_nv5UV@?I>Q%teN6<PpzJ?lW zYTS@Y%`{8RHQz#uEp_Bt*WGm6UH3io*wdBTqWYtKL5&wRnWV-%f29Vg_ghahU!Y_# z12HiS;(ic7Ld#&L0vjWP++e1npen4uNjun7R*9V$(x$Q4&R2Ffa$j*HXMW`-d&$v2 zcaq%XM$O)FdqJ&^&vu!_wkjMkRl1LFaYzzU&sjG%B=c!}QP1`Ny`rw{7)WlcdQNIS z;5@(dvN9+E4#?Gh|ME z&HfMnkKUCpo%}Ao&m;V6jPwsRb8y(8%9T9;00D(*LqkwWLqi~Na&Km7Y-Iodc$|Ha zJxIeq9K~N#r6Ls(JBWy6s7@9{MFbbELJ=y2TA@`3lS@B@CJjl7i=*ILaPVWX>fqw6 ztAnc`2!4RLxj8AiNQwVT3N2zhIPS;0dyl(!fKV$j&1xG3G~G5+iMW`_u85&m29s$1I#dwzgxj#pjnzI-X5Q%4)VcNv&#FLx0!FiuJ!ius=d`>)K z(glehxvqHp#<}3Kz%wIeIyFZeAr=cQth6vIni}y0aa7fG$`>*otDLtuYo!Wn+>^gB zoYR+=xK48b2`pd{5=1DdU;`!Ch|#K(Vj)HQaUXxb>zBx-kZS{s9P=ncgY5dj|KNAG zR(^cUOA5z<_7}(b7y?4OK)vcX-^Y$qKLLWzz?I(gmutY(C+XFu7Ci#Gw}Ff6rl#xx zmpee;lOdb3D+Or^`8@D`M&FbLdT)WwRj;?kK29HiGeSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2! zfese{004-vDmqJ`I({*(>>!2lXp Date: Fri, 19 Feb 2021 00:07:12 +0100 Subject: [PATCH 27/78] Fixed angel mode placing blocks on the wrong side --- .../constructionwand/block/BlockConjured.java | 4 +-- .../constructionwand/items/wand/ItemWand.java | 3 +- .../constructionwand/wand/WandJob.java | 33 ++++++++++++++----- .../wand/action/ActionAngel.java | 5 ++- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/block/BlockConjured.java b/src/main/java/thetadev/constructionwand/block/BlockConjured.java index d2c7248..82ece14 100644 --- a/src/main/java/thetadev/constructionwand/block/BlockConjured.java +++ b/src/main/java/thetadev/constructionwand/block/BlockConjured.java @@ -73,8 +73,8 @@ public class BlockConjured extends AbstractGlassBlock if(!(handItem instanceof BlockItem) || ((BlockItem) handItem).getBlock() == ModBlocks.CONJURED_BLOCK) return super.onBlockActivated(state, world, pos, player, handIn, hit); - WandJob job = new WandJob(player, world, hit, new ItemStack(ModItems.WAND_INFINITY)); - job.getPlaceSnapshots(new ActionConjuredBlocks(job), new SupplierInventory(job), (BlockItem) handItem); + WandJob job = WandJob.withDummyWand(player, world, hit, (BlockItem) handItem); + job.getPlaceSnapshots(new ActionConjuredBlocks(job), new SupplierInventory(job)); return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 04228a7..70b05e7 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -29,6 +29,7 @@ import thetadev.constructionwand.items.ItemBase; import thetadev.constructionwand.wand.WandJob; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.List; public abstract class ItemWand extends ItemBase implements ICustomItemModel @@ -73,7 +74,7 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel return ActionResult.resultFail(stack); } - public static WandJob getWandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { + public static WandJob getWandJob(PlayerEntity player, World world, @Nullable BlockRayTraceResult rayTraceResult, ItemStack wand) { WandOptions options = new WandOptions(wand); WandJob wandJob = new WandJob(player, world, rayTraceResult, wand); diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 2206db8..7bd4931 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -15,6 +15,7 @@ import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.items.ModItems; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.undo.ISnapshot; @@ -30,20 +31,35 @@ public class WandJob public final World world; public final BlockRayTraceResult rayTraceResult; public final WandOptions options; - public final ItemStack wand; public final ItemWand wandItem; + @Nullable + public final BlockItem targetItem; + private IWandAction wandAction; private IWandSupplier wandSupplier; private List placeSnapshots; public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { + this(player, world, rayTraceResult, wand, getTargetItem(world, rayTraceResult)); + } + + @Nullable + private static BlockItem getTargetItem(World world, BlockRayTraceResult rayTraceResult) { + // Get target item + Item tgitem = world.getBlockState(rayTraceResult.getPos()).getBlock().asItem(); + if(!(tgitem instanceof BlockItem)) return null; + return (BlockItem) tgitem; + } + + public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand, @Nullable BlockItem targetItem) { this.player = player; this.world = world; this.rayTraceResult = rayTraceResult; this.placeSnapshots = new LinkedList<>(); + this.targetItem = targetItem; // Get wand this.wand = wand; @@ -51,16 +67,15 @@ public class WandJob options = new WandOptions(wand); } - public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier) { - // Get target item - Item tgitem = world.getBlockState(rayTraceResult.getPos()).getBlock().asItem(); - if(!(tgitem instanceof BlockItem)) tgitem = null; - BlockItem targetItem = (BlockItem) tgitem; - - getPlaceSnapshots(wandAction, wandSupplier, targetItem); + /** + * Creates a WandJob with a dummy wand (Infinity with standard settings) for use of the wand behavior + * in other contexts + */ + public static WandJob withDummyWand(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, @Nullable BlockItem targetItem) { + return new WandJob(player, world, rayTraceResult, new ItemStack(ModItems.WAND_INFINITY), targetItem); } - public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier, @Nullable BlockItem targetItem) { + public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier) { this.wandSupplier = wandSupplier; wandSupplier.getSupply(targetItem); this.wandAction = wandAction; diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java index bb297e7..89073b3 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -2,6 +2,7 @@ package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; @@ -25,17 +26,19 @@ public class ActionAngel implements IWandAction private final PlayerEntity player; private final BlockRayTraceResult rayTraceResult; private final ItemWand wandItem; + private final BlockItem targetItem; public ActionAngel(WandJob wandJob) { world = wandJob.world; player = wandJob.player; rayTraceResult = wandJob.rayTraceResult; wandItem = wandJob.wandItem; + targetItem = wandJob.targetItem; } @Override public List getSnapshots(IWandSupplier supplier) { - if(rayTraceResult == null) return getAngelSnapshots(supplier); + if(targetItem == null) return getAngelSnapshots(supplier); else return getTransductionSnapshots(supplier); } From 997aa5c1054a1577db4057d002e25c39213666f0 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 6 Mar 2021 14:50:28 +0100 Subject: [PATCH 28/78] Removed wand reservoirs Forge update --- gradle.properties | 4 +- .../blockstates/conjured_block.json | 7 - .../models/block/conjured_block.json | 6 - .../models/item/conjured_block.json | 3 - ...voir_random.json => core_destruction.json} | 2 +- .../models/item/reservoir_conjuration.json | 6 - .../constructionwand/ConstructionWand.java | 2 - .../constructionwand/api/IWandReservoir.java | 8 -- .../constructionwand/api/IWandSupplier.java | 3 - .../constructionwand/basics/WandUtil.java | 24 ++-- .../basics/option/WandOptions.java | 13 +- .../constructionwand/block/BlockConjured.java | 122 ------------------ .../constructionwand/block/ModBlocks.java | 30 ----- .../constructionwand/client/ScreenWand.java | 6 +- .../data/BlockStateGenerator.java | 32 ----- .../data/ICustomBlockState.java | 6 - .../constructionwand/data/ModData.java | 1 - .../data/RecipeGenerator.java | 4 +- .../constructionwand/items/ModItems.java | 17 +-- .../reservoir/ItemReservoirConjuration.java | 19 --- .../items/reservoir/ItemReservoirRandom.java | 19 --- .../items/reservoir/ReservoirDefault.java | 21 --- .../constructionwand/items/wand/ItemWand.java | 4 +- .../wand/WandItemUseContext.java | 5 - .../constructionwand/wand/WandJob.java | 52 ++++---- .../wand/supplier/SupplierBlockgen.java | 58 --------- .../wand/supplier/SupplierConjuration.java | 17 --- .../wand/supplier/SupplierInventory.java | 13 +- .../wand/supplier/SupplierRandom.java | 2 +- .../wand/undo/UndoHistory.java | 1 - .../assets/constructionwand/lang/en_us.json | 17 +-- .../textures/item/reservoir_conjuration.png | Bin 667 -> 0 bytes .../textures/item/reservoir_random.png | Bin 679 -> 0 bytes 33 files changed, 60 insertions(+), 464 deletions(-) delete mode 100644 src/generated/resources/assets/constructionwand/blockstates/conjured_block.json delete mode 100644 src/generated/resources/assets/constructionwand/models/block/conjured_block.json delete mode 100644 src/generated/resources/assets/constructionwand/models/item/conjured_block.json rename src/generated/resources/assets/constructionwand/models/item/{reservoir_random.json => core_destruction.json} (53%) delete mode 100644 src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json delete mode 100644 src/main/java/thetadev/constructionwand/api/IWandReservoir.java delete mode 100644 src/main/java/thetadev/constructionwand/block/BlockConjured.java delete mode 100644 src/main/java/thetadev/constructionwand/block/ModBlocks.java delete mode 100644 src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java delete mode 100644 src/main/java/thetadev/constructionwand/data/ICustomBlockState.java delete mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java delete mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java delete mode 100644 src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java delete mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java delete mode 100644 src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java delete mode 100644 src/main/resources/assets/constructionwand/textures/item/reservoir_conjuration.png delete mode 100644 src/main/resources/assets/constructionwand/textures/item/reservoir_random.png diff --git a/gradle.properties b/gradle.properties index 1a0ba34..6505f86 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,9 +5,7 @@ author=thetadev modid=constructionwand mcversion=1.16.5 -forgeversion=36.0.22 -#mcversion=1.16.2 -#forgeversion=33.0.60 +forgeversion=36.0.46 mcp_mappings=20200723-1.16.1 botania=1.16.2-405 diff --git a/src/generated/resources/assets/constructionwand/blockstates/conjured_block.json b/src/generated/resources/assets/constructionwand/blockstates/conjured_block.json deleted file mode 100644 index ffb27e7..0000000 --- a/src/generated/resources/assets/constructionwand/blockstates/conjured_block.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "variants": { - "": { - "model": "constructionwand:block/conjured_block" - } - } -} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/block/conjured_block.json b/src/generated/resources/assets/constructionwand/models/block/conjured_block.json deleted file mode 100644 index 6a64996..0000000 --- a/src/generated/resources/assets/constructionwand/models/block/conjured_block.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "minecraft:block/cube_all", - "textures": { - "all": "constructionwand:block/conjured_block" - } -} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/conjured_block.json b/src/generated/resources/assets/constructionwand/models/item/conjured_block.json deleted file mode 100644 index e08e448..0000000 --- a/src/generated/resources/assets/constructionwand/models/item/conjured_block.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "parent": "constructionwand:block/conjured_block" -} \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/reservoir_random.json b/src/generated/resources/assets/constructionwand/models/item/core_destruction.json similarity index 53% rename from src/generated/resources/assets/constructionwand/models/item/reservoir_random.json rename to src/generated/resources/assets/constructionwand/models/item/core_destruction.json index 3d5385a..e47d329 100644 --- a/src/generated/resources/assets/constructionwand/models/item/reservoir_random.json +++ b/src/generated/resources/assets/constructionwand/models/item/core_destruction.json @@ -1,6 +1,6 @@ { "parent": "minecraft:item/generated", "textures": { - "layer0": "constructionwand:item/reservoir_random" + "layer0": "constructionwand:item/core_destruction" } } \ No newline at end of file diff --git a/src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json b/src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json deleted file mode 100644 index 5fe4028..0000000 --- a/src/generated/resources/assets/constructionwand/models/item/reservoir_conjuration.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "minecraft:item/generated", - "textures": { - "layer0": "constructionwand:item/reservoir_conjuration" - } -} \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index e23078c..95e49de 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -16,7 +16,6 @@ import thetadev.constructionwand.basics.ConfigClient; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.ReplacementRegistry; -import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.client.ClientEvents; import thetadev.constructionwand.client.RenderBlockPreview; import thetadev.constructionwand.containers.ContainerManager; @@ -85,7 +84,6 @@ public class ConstructionWand MinecraftForge.EVENT_BUS.register(new ClientEvents()); ModItems.registerModelProperties(); ModItems.registerItemColors(); - ModBlocks.registerRenderLayers(); } public static ResourceLocation loc(String name) { diff --git a/src/main/java/thetadev/constructionwand/api/IWandReservoir.java b/src/main/java/thetadev/constructionwand/api/IWandReservoir.java deleted file mode 100644 index 4cc30e1..0000000 --- a/src/main/java/thetadev/constructionwand/api/IWandReservoir.java +++ /dev/null @@ -1,8 +0,0 @@ -package thetadev.constructionwand.api; - -import thetadev.constructionwand.wand.WandJob; - -public interface IWandReservoir extends IWandUpgrade -{ - IWandSupplier getWandSupplier(WandJob job); -} diff --git a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java index ce7c93a..93b335b 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java +++ b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java @@ -1,7 +1,6 @@ package thetadev.constructionwand.api; import net.minecraft.block.BlockState; -import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; import thetadev.constructionwand.wand.undo.PlaceSnapshot; @@ -10,8 +9,6 @@ import javax.annotation.Nullable; public interface IWandSupplier { - void getSupply(@Nullable BlockItem target); - int getMaxBlocks(); @Nullable diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 20b06db..c959a84 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -29,12 +29,9 @@ import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.WandItemUseContext; -import thetadev.constructionwand.wand.undo.BlockgenSnapshot; -import thetadev.constructionwand.wand.undo.PlaceSnapshot; import javax.annotation.Nullable; import java.util.ArrayList; @@ -196,6 +193,10 @@ public class WandUtil return false; } + /** + * Tests if a wand can place a block at a certain position. + * This check is independent from the used block. + */ public static boolean isPositionPlaceable(World world, PlayerEntity player, BlockPos pos, BlockRayTraceResult rayTraceResult, WandOptions options) { // Is position out of world? @@ -205,16 +206,17 @@ public class WandUtil if(!world.isBlockModifiable(player, pos)) return false; // If replace mode is off, target has to be air - if(!options.replace.get() && !world.isAirBlock(pos)) - if(world.getBlockState(pos).getBlock() != ModBlocks.CONJURED_BLOCK) return false; + if(!options.replace.get() && !world.isAirBlock(pos)) return false; // Limit placement range - if(ConfigServer.MAX_RANGE.get() > 0 && WandUtil.maxRange(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) - return false; - - return true; + return ConfigServer.MAX_RANGE.get() <= 0 || + WandUtil.maxRange(rayTraceResult.getPos(), pos) <= ConfigServer.MAX_RANGE.get(); } + /** + * Tests if a certain block can be placed by the wand. + * If it can, returns the blockstate to be placed. + */ @SuppressWarnings({"rawtypes", "unchecked"}) @Nullable public static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, @@ -222,9 +224,7 @@ public class WandUtil @Nullable BlockState supportingBlock, @Nullable WandOptions options) { // Is block at pos replaceable? BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); - - if(!(world.getBlockState(pos).getBlock() == ModBlocks.CONJURED_BLOCK && item.getBlock() != ModBlocks.CONJURED_BLOCK)) - if(!ctx.canPlace()) return null; + if(!ctx.canPlace()) return null; // Can block be placed? BlockState blockState = item.getBlock().getStateForPlacement(ctx); diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java index f57a137..0cb920f 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -5,12 +5,9 @@ import net.minecraft.block.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import thetadev.constructionwand.api.IWandCore; -import thetadev.constructionwand.api.IWandReservoir; import thetadev.constructionwand.api.IWandUpgrade; import thetadev.constructionwand.basics.ReplacementRegistry; import thetadev.constructionwand.items.core.CoreDefault; -import thetadev.constructionwand.items.reservoir.ReservoirDefault; -import thetadev.constructionwand.items.wand.ItemWand; import javax.annotation.Nullable; @@ -43,12 +40,12 @@ public class WandOptions } public final WandUpgradesSelectable cores; - public final WandUpgradesSelectable reservoirs; public final OptionEnum lock; public final OptionEnum direction; public final OptionBoolean replace; public final OptionEnum match; + public final OptionBoolean random; public final IOption[] allOptions; @@ -56,14 +53,14 @@ public class WandOptions tag = wandStack.getOrCreateChildTag(TAG_ROOT); cores = new WandUpgradesSelectable<>(tag, "cores", new CoreDefault()); - reservoirs = new WandUpgradesSelectable<>(tag, "reservoirs", new ReservoirDefault()); lock = new OptionEnum<>(tag, "lock", LOCK.class, LOCK.NOLOCK); direction = new OptionEnum<>(tag, "direction", DIRECTION.class, DIRECTION.TARGET); replace = new OptionBoolean(tag, "replace", true); match = new OptionEnum<>(tag, "match", MATCH.class, MATCH.SIMILAR); + random = new OptionBoolean(tag, "random", false); - allOptions = new IOption[]{cores, reservoirs, lock, direction, replace, match}; + allOptions = new IOption[]{cores, lock, direction, replace, match, random}; } @Nullable @@ -93,15 +90,11 @@ public class WandOptions public boolean hasUpgrade(IWandUpgrade upgrade) { if(upgrade instanceof IWandCore) return cores.hasUpgrade((IWandCore) upgrade); - else - if(upgrade instanceof IWandReservoir) return reservoirs.hasUpgrade((IWandReservoir) upgrade); return false; } public boolean addUpgrade(IWandUpgrade upgrade) { if(upgrade instanceof IWandCore) return cores.addUpgrade((IWandCore) upgrade); - else - if(upgrade instanceof IWandReservoir) return reservoirs.addUpgrade((IWandReservoir) upgrade); return false; } } diff --git a/src/main/java/thetadev/constructionwand/block/BlockConjured.java b/src/main/java/thetadev/constructionwand/block/BlockConjured.java deleted file mode 100644 index 82ece14..0000000 --- a/src/main/java/thetadev/constructionwand/block/BlockConjured.java +++ /dev/null @@ -1,122 +0,0 @@ -package thetadev.constructionwand.block; - -import net.minecraft.block.AbstractBlock; -import net.minecraft.block.AbstractGlassBlock; -import net.minecraft.block.BlockState; -import net.minecraft.block.SoundType; -import net.minecraft.block.material.Material; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.ActionResultType; -import net.minecraft.util.Direction; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.api.IWandAction; -import thetadev.constructionwand.api.IWandSupplier; -import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.items.ModItems; -import thetadev.constructionwand.wand.WandJob; -import thetadev.constructionwand.wand.supplier.SupplierInventory; -import thetadev.constructionwand.wand.undo.ISnapshot; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -public class BlockConjured extends AbstractGlassBlock -{ - private static final AbstractBlock.IExtendedPositionPredicate> NO_SPAWN = (state, world, pos, et) -> false; - private static final AbstractBlock.IPositionPredicate NOT_SOLID = (state, world, pos) -> false; - - public BlockConjured(String name) { - super(AbstractBlock.Properties.create(Material.SNOW).hardnessAndResistance(0.2F) - .sound(SoundType.CLOTH).notSolid().setAllowsSpawn(NO_SPAWN) - .setOpaque(NOT_SOLID).setSuffocates(NOT_SOLID).setBlocksVision(NOT_SOLID)); - setRegistryName(ConstructionWand.MODID, name); - } - - @Override - public boolean isReplaceable(@Nonnull BlockState state, @Nonnull BlockItemUseContext useContext) { - return useContext.getItem().getItem() != ModBlocks.CONJURED_BLOCK.asItem(); - } - - @Override - public void harvestBlock(@Nonnull World worldIn, @Nonnull PlayerEntity player, @Nonnull BlockPos pos, - @Nonnull BlockState state, @Nullable TileEntity te, @Nonnull ItemStack stack) { - super.harvestBlock(worldIn, player, pos, state, te, stack); - - if(player.isSneaking()) { - HashSet adjacentBlocks = getAdjacentBlocks(worldIn, pos, 64); - adjacentBlocks.forEach(blockPos -> WandUtil.removeBlock(worldIn, player, null, blockPos)); - } - } - - @Nonnull - @Override - public ActionResultType onBlockActivated(@Nonnull BlockState state, @Nonnull World world, @Nonnull BlockPos pos, - @Nonnull PlayerEntity player, @Nonnull Hand handIn, @Nonnull BlockRayTraceResult hit) { - ItemStack handStack = player.getHeldItem(handIn); - Item handItem = handStack.getItem(); - - if(!(handItem instanceof BlockItem) || ((BlockItem) handItem).getBlock() == ModBlocks.CONJURED_BLOCK) - return super.onBlockActivated(state, world, pos, player, handIn, hit); - - WandJob job = WandJob.withDummyWand(player, world, hit, (BlockItem) handItem); - job.getPlaceSnapshots(new ActionConjuredBlocks(job), new SupplierInventory(job)); - return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; - } - - public static HashSet getAdjacentBlocks(World world, BlockPos pos, int maxBlocks) { - HashSet blockPositions = new HashSet<>(); - LinkedList candidates = new LinkedList<>(); - HashSet allCandidates = new HashSet<>(); - candidates.add(pos); - - while(!candidates.isEmpty() && blockPositions.size() < maxBlocks) { - BlockPos currentPos = candidates.removeFirst(); - allCandidates.add(currentPos); - - if(world.getBlockState(currentPos).getBlock() == ModBlocks.CONJURED_BLOCK || currentPos.equals(pos)) { - blockPositions.add(currentPos); - - for(Direction dir : Direction.values()) { - BlockPos nPos = currentPos.offset(dir); - if(allCandidates.add(nPos)) candidates.add(nPos); - } - } - } - - return blockPositions; - } - - private static class ActionConjuredBlocks implements IWandAction - { - private final World world; - private final BlockRayTraceResult rayTraceResult; - - public ActionConjuredBlocks(WandJob wandJob) { - world = wandJob.world; - rayTraceResult = wandJob.rayTraceResult; - } - - @Override - public List getSnapshots(IWandSupplier supplier) { - HashSet adjacentBlocks = BlockConjured.getAdjacentBlocks(world, rayTraceResult.getPos(), supplier.getMaxBlocks()); - - return adjacentBlocks.stream().map(blockPos -> supplier.getPlaceSnapshot(blockPos, null)) - .filter(Objects::nonNull).collect(Collectors.toList()); - } - } -} diff --git a/src/main/java/thetadev/constructionwand/block/ModBlocks.java b/src/main/java/thetadev/constructionwand/block/ModBlocks.java deleted file mode 100644 index be9c22e..0000000 --- a/src/main/java/thetadev/constructionwand/block/ModBlocks.java +++ /dev/null @@ -1,30 +0,0 @@ -package thetadev.constructionwand.block; - -import net.minecraft.block.*; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.RenderTypeLookup; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.event.RegistryEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) -public class ModBlocks -{ - public static final Block CONJURED_BLOCK = new BlockConjured("conjured_block"); - - public static final Block[] ALL_BLOCKS = { - CONJURED_BLOCK - }; - - @SubscribeEvent - public static void registerBlocks(RegistryEvent.Register event) { - event.getRegistry().registerAll(ALL_BLOCKS); - } - - @OnlyIn(Dist.CLIENT) - public static void registerRenderLayers() { - RenderTypeLookup.setRenderLayer(CONJURED_BLOCK, RenderType.getTranslucent()); - } -} diff --git a/src/main/java/thetadev/constructionwand/client/ScreenWand.java b/src/main/java/thetadev/constructionwand/client/ScreenWand.java index 483ba6a..b987e49 100644 --- a/src/main/java/thetadev/constructionwand/client/ScreenWand.java +++ b/src/main/java/thetadev/constructionwand/client/ScreenWand.java @@ -43,9 +43,9 @@ public class ScreenWand extends Screen createButton(0, 0, wandOptions.cores); createButton(0, 1, wandOptions.lock); createButton(0, 2, wandOptions.direction); - createButton(1, 0, wandOptions.reservoirs); - createButton(1, 1, wandOptions.replace); - createButton(1, 2, wandOptions.match); + createButton(1, 0, wandOptions.replace); + createButton(1, 1, wandOptions.match); + createButton(1, 2, wandOptions.random); } @Override diff --git a/src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java b/src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java deleted file mode 100644 index 8fb96ee..0000000 --- a/src/main/java/thetadev/constructionwand/data/BlockStateGenerator.java +++ /dev/null @@ -1,32 +0,0 @@ -package thetadev.constructionwand.data; - -import net.minecraft.block.Block; -import net.minecraft.data.DataGenerator; -import net.minecraftforge.client.model.generators.BlockStateProvider; -import net.minecraftforge.common.data.ExistingFileHelper; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.block.ModBlocks; - -import javax.annotation.Nonnull; - -public class BlockStateGenerator extends BlockStateProvider -{ - public BlockStateGenerator(DataGenerator gen, ExistingFileHelper exFileHelper) { - super(gen, ConstructionWand.MODID, exFileHelper); - } - - @Override - protected void registerStatesAndModels() { - for(Block block : ModBlocks.ALL_BLOCKS) { - if(block instanceof ICustomBlockState) - ((ICustomBlockState) block).generateCustomBlockState(this); - else simpleBlock(block); - } - } - - @Nonnull - @Override - public String getName() { - return ConstructionWand.MODNAME + " blockstates"; - } -} diff --git a/src/main/java/thetadev/constructionwand/data/ICustomBlockState.java b/src/main/java/thetadev/constructionwand/data/ICustomBlockState.java deleted file mode 100644 index cce2865..0000000 --- a/src/main/java/thetadev/constructionwand/data/ICustomBlockState.java +++ /dev/null @@ -1,6 +0,0 @@ -package thetadev.constructionwand.data; - -public interface ICustomBlockState -{ - void generateCustomBlockState(BlockStateGenerator generator); -} diff --git a/src/main/java/thetadev/constructionwand/data/ModData.java b/src/main/java/thetadev/constructionwand/data/ModData.java index 79bba77..45ccd4a 100644 --- a/src/main/java/thetadev/constructionwand/data/ModData.java +++ b/src/main/java/thetadev/constructionwand/data/ModData.java @@ -19,7 +19,6 @@ public class ModData } if(event.includeClient()) { - generator.addProvider(new BlockStateGenerator(generator, fileHelper)); generator.addProvider(new ItemModelGenerator(generator, fileHelper)); } } diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index 77ddbe2..d42f538 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -11,6 +11,7 @@ import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.ModItems; +import javax.annotation.Nonnull; import java.util.function.Consumer; public class RecipeGenerator extends RecipeProvider @@ -20,7 +21,7 @@ public class RecipeGenerator extends RecipeProvider } @Override - protected void registerRecipes(Consumer consumer) { + protected void registerRecipes(@Nonnull Consumer consumer) { wandRecipe(consumer, ModItems.WAND_STONE, Inp.fromTag(ItemTags.field_232909_aa_)); //stone_tool_materials wandRecipe(consumer, ModItems.WAND_IRON, Inp.fromTag(Tags.Items.INGOTS_IRON)); wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); @@ -45,6 +46,7 @@ public class RecipeGenerator extends RecipeProvider CustomRecipeBuilder.customRecipe(serializer).build(consumer, ConstructionWand.loc("dynamic/" + name.getPath()).toString()); } + @Nonnull @Override public String getName() { return ConstructionWand.MODID + " crafting recipes"; diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 9bea943..0da1fd0 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -1,6 +1,5 @@ package thetadev.constructionwand.items; -import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.color.ItemColors; import net.minecraft.item.*; @@ -14,12 +13,9 @@ import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.IForgeRegistryEntry; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.core.ItemCoreAngel; import thetadev.constructionwand.items.core.ItemCoreDestruction; -import thetadev.constructionwand.items.reservoir.ItemReservoirConjuration; -import thetadev.constructionwand.items.reservoir.ItemReservoirRandom; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.items.wand.ItemWandBasic; import thetadev.constructionwand.items.wand.ItemWandInfinity; @@ -40,9 +36,6 @@ public class ModItems public static final Item CORE_ANGEL = new ItemCoreAngel("core_angel", unstackable()); public static final Item CORE_DESTRUCTION = new ItemCoreDestruction("core_destruction", unstackable()); - public static final Item RESERVOIR_RANDOM = new ItemReservoirRandom("reservoir_random", unstackable()); - public static final Item RESERVOIR_CONJURATION = new ItemReservoirConjuration("reservoir_conjuration", unstackable()); - // Collections public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; public static final HashSet ALL_ITEMS = new HashSet<>(); @@ -56,15 +49,7 @@ public class ModItems ALL_ITEMS.addAll(Arrays.asList(WANDS)); registerItem(r, CORE_ANGEL); - registerItem(r, RESERVOIR_RANDOM); - registerItem(r, RESERVOIR_CONJURATION); - - // BlockItems - for(Block block : ModBlocks.ALL_BLOCKS) { - BlockItem item = new BlockItem(block, itemprops()); - item.setRegistryName(block.getRegistryName()); - registerItem(r, item); - } + registerItem(r, CORE_DESTRUCTION); } public static Item.Properties itemprops() { diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java deleted file mode 100644 index 4e22762..0000000 --- a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirConjuration.java +++ /dev/null @@ -1,19 +0,0 @@ -package thetadev.constructionwand.items.reservoir; - -import thetadev.constructionwand.api.IWandReservoir; -import thetadev.constructionwand.api.IWandSupplier; -import thetadev.constructionwand.items.ItemBase; -import thetadev.constructionwand.wand.WandJob; -import thetadev.constructionwand.wand.supplier.SupplierConjuration; - -public class ItemReservoirConjuration extends ItemBase implements IWandReservoir -{ - public ItemReservoirConjuration(String name, Properties properties) { - super(name, properties); - } - - @Override - public IWandSupplier getWandSupplier(WandJob job) { - return new SupplierConjuration(job); - } -} diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java b/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java deleted file mode 100644 index f2c904f..0000000 --- a/src/main/java/thetadev/constructionwand/items/reservoir/ItemReservoirRandom.java +++ /dev/null @@ -1,19 +0,0 @@ -package thetadev.constructionwand.items.reservoir; - -import thetadev.constructionwand.api.IWandReservoir; -import thetadev.constructionwand.api.IWandSupplier; -import thetadev.constructionwand.items.ItemBase; -import thetadev.constructionwand.wand.WandJob; -import thetadev.constructionwand.wand.supplier.SupplierRandom; - -public class ItemReservoirRandom extends ItemBase implements IWandReservoir -{ - public ItemReservoirRandom(String name, Properties properties) { - super(name, properties); - } - - @Override - public IWandSupplier getWandSupplier(WandJob job) { - return new SupplierRandom(job); - } -} diff --git a/src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java b/src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java deleted file mode 100644 index 34b66ad..0000000 --- a/src/main/java/thetadev/constructionwand/items/reservoir/ReservoirDefault.java +++ /dev/null @@ -1,21 +0,0 @@ -package thetadev.constructionwand.items.reservoir; - -import net.minecraft.util.ResourceLocation; -import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.api.IWandReservoir; -import thetadev.constructionwand.api.IWandSupplier; -import thetadev.constructionwand.wand.WandJob; -import thetadev.constructionwand.wand.supplier.SupplierInventory; - -public class ReservoirDefault implements IWandReservoir -{ - @Override - public IWandSupplier getWandSupplier(WandJob job) { - return new SupplierInventory(job); - } - - @Override - public ResourceLocation getRegistryName() { - return ConstructionWand.loc("default"); - } -} diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 70b05e7..8119637 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -75,10 +75,8 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel } public static WandJob getWandJob(PlayerEntity player, World world, @Nullable BlockRayTraceResult rayTraceResult, ItemStack wand) { - WandOptions options = new WandOptions(wand); - WandJob wandJob = new WandJob(player, world, rayTraceResult, wand); - wandJob.getPlaceSnapshots(options.cores.get().getWandAction(wandJob), options.reservoirs.get().getWandSupplier(wandJob)); + wandJob.getPlaceSnapshots(); return wandJob; } diff --git a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java index 598cfed..b8c08f7 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java +++ b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java @@ -10,17 +10,12 @@ import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.block.ModBlocks; public class WandItemUseContext extends BlockItemUseContext { public WandItemUseContext(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, BlockPos pos, BlockItem item) { super(world, player, Hand.MAIN_HAND, new ItemStack(item), new BlockRayTraceResult(getBlockHitVec(rayTraceResult, pos), rayTraceResult.getFace(), pos, false)); - - // Conjured blocks can be replaced - if(world.getBlockState(pos).getBlock() == ModBlocks.CONJURED_BLOCK && item.getBlock() != ModBlocks.CONJURED_BLOCK) - replaceClicked = true; } private static Vector3d getBlockHitVec(BlockRayTraceResult rayTraceResult, BlockPos pos) { diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 7bd4931..98df50e 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -11,12 +11,13 @@ import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandAction; -import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.items.ModItems; import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.wand.supplier.SupplierInventory; +import thetadev.constructionwand.wand.supplier.SupplierRandom; import thetadev.constructionwand.wand.undo.ISnapshot; import javax.annotation.Nullable; @@ -37,11 +38,28 @@ public class WandJob @Nullable public final BlockItem targetItem; - private IWandAction wandAction; - private IWandSupplier wandSupplier; + private final IWandAction wandAction; + private final IWandSupplier wandSupplier; private List placeSnapshots; + public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand, @Nullable BlockItem targetItem) { + this.player = player; + this.world = world; + this.rayTraceResult = rayTraceResult; + this.placeSnapshots = new LinkedList<>(); + this.targetItem = targetItem; + + // Get wand + this.wand = wand; + this.wandItem = (ItemWand) wand.getItem(); + options = new WandOptions(wand); + + // Select wand action and supplier based on options + wandSupplier = options.random.get() ? new SupplierRandom(this) : new SupplierInventory(this); + wandAction = options.cores.get().getWandAction(this); + } + public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { this(player, world, rayTraceResult, wand, getTargetItem(world, rayTraceResult)); } @@ -54,31 +72,7 @@ public class WandJob return (BlockItem) tgitem; } - public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand, @Nullable BlockItem targetItem) { - this.player = player; - this.world = world; - this.rayTraceResult = rayTraceResult; - this.placeSnapshots = new LinkedList<>(); - this.targetItem = targetItem; - - // Get wand - this.wand = wand; - this.wandItem = (ItemWand) wand.getItem(); - options = new WandOptions(wand); - } - - /** - * Creates a WandJob with a dummy wand (Infinity with standard settings) for use of the wand behavior - * in other contexts - */ - public static WandJob withDummyWand(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, @Nullable BlockItem targetItem) { - return new WandJob(player, world, rayTraceResult, new ItemStack(ModItems.WAND_INFINITY), targetItem); - } - - public void getPlaceSnapshots(IWandAction wandAction, IWandSupplier wandSupplier) { - this.wandSupplier = wandSupplier; - wandSupplier.getSupply(targetItem); - this.wandAction = wandAction; + public void getPlaceSnapshots() { placeSnapshots = wandAction.getSnapshots(wandSupplier); } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java deleted file mode 100644 index ed867b0..0000000 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierBlockgen.java +++ /dev/null @@ -1,58 +0,0 @@ -package thetadev.constructionwand.wand.supplier; - -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; -import thetadev.constructionwand.api.IWandSupplier; -import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.wand.WandJob; -import thetadev.constructionwand.wand.undo.BlockgenSnapshot; -import thetadev.constructionwand.wand.undo.PlaceSnapshot; - -import javax.annotation.Nullable; - -public abstract class SupplierBlockgen implements IWandSupplier -{ - private final PlayerEntity player; - private final World world; - private final BlockRayTraceResult rayTraceResult; - private final WandOptions options; - private final int wandLimit; - - public SupplierBlockgen(WandJob job) { - player = job.player; - world = job.world; - rayTraceResult = job.rayTraceResult; - options = job.options; - wandLimit = job.wandItem.getLimit(player, job.wand); - } - - @Override - public void getSupply(@Nullable BlockItem target) { - } - - @Override - public int getMaxBlocks() { - return wandLimit; - } - - @Nullable - @Override - public PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock) { - if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; - - return BlockgenSnapshot.get(world, player, rayTraceResult, pos, getBlockItem(), supportingBlock, options); - } - - @Override - public int takeItemStack(ItemStack stack) { - return 0; - } - - protected abstract BlockItem getBlockItem(); -} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java deleted file mode 100644 index a9f570f..0000000 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierConjuration.java +++ /dev/null @@ -1,17 +0,0 @@ -package thetadev.constructionwand.wand.supplier; - -import net.minecraft.item.BlockItem; -import thetadev.constructionwand.block.ModBlocks; -import thetadev.constructionwand.wand.WandJob; - -public class SupplierConjuration extends SupplierBlockgen -{ - public SupplierConjuration(WandJob job) { - super(job); - } - - @Override - protected BlockItem getBlockItem() { - return (BlockItem) ModBlocks.CONJURED_BLOCK.asItem(); - } -} diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index 4b491f4..8e8d087 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -18,7 +18,6 @@ import thetadev.constructionwand.basics.pool.IPool; import thetadev.constructionwand.basics.pool.OrderedPool; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.ModItems; -import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.PlaceSnapshot; @@ -48,18 +47,10 @@ public class SupplierInventory implements IWandSupplier rayTraceResult = job.rayTraceResult; options = job.options; wandLimit = job.wandItem.getLimit(player, job.wand); + getSupply(job.targetItem); } - public SupplierInventory(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, int wandLimit) { - this.player = player; - this.world = world; - this.rayTraceResult = rayTraceResult; - this.options = new WandOptions(new ItemStack(ModItems.WAND_INFINITY)); - this.wandLimit = wandLimit; - } - - @Override - public void getSupply(@Nullable BlockItem target) { + protected void getSupply(@Nullable BlockItem target) { itemCounts = new LinkedHashMap<>(); ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java index d275e40..2217086 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java @@ -16,7 +16,7 @@ public class SupplierRandom extends SupplierInventory } @Override - public void getSupply(@Nullable BlockItem target) { + protected void getSupply(@Nullable BlockItem target) { itemCounts = new LinkedHashMap<>(); // Random mode -> add all items from hotbar diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index 19b7a17..15e24e8 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -12,7 +12,6 @@ import net.minecraftforge.fml.network.PacketDistributor; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.block.ModBlocks; import thetadev.constructionwand.network.PacketUndoBlocks; import java.util.*; diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index bedd300..f6bcda4 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -7,9 +7,10 @@ "item.constructionwand.core_destruction": "Destruction Wand Core", "item.constructionwand.reservoir_random": "Random Wand Reservoir", "item.constructionwand.reservoir_conjuration": "Conjuration Wand Reservoir", - "block.constructionwand.conjured_block": "Conjured Block", + "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", + "constructionwand.option.cores": "", "constructionwand.option.cores.constructionwand:default": "Construction Core", "constructionwand.option.cores.constructionwand:default.desc": "Extend your building on the side facing you", @@ -17,13 +18,7 @@ "constructionwand.option.cores.constructionwand:core_angel.desc": "Place behind blocks and in mid air", "constructionwand.option.cores.constructionwand:core_destruction": "§cDestruction Core", "constructionwand.option.cores.constructionwand:core_destruction.desc": "Destroys blocks on the side facing you", - "constructionwand.option.reservoirs": "", - "constructionwand.option.reservoirs.constructionwand:default": "Inventory Reservoir", - "constructionwand.option.reservoirs.constructionwand:default.desc": "Delivers the matching building material from your inventory. Take a block into your offhand to specify a different material.", - "constructionwand.option.reservoirs.constructionwand:reservoir_random": "Random Reservoir", - "constructionwand.option.reservoirs.constructionwand:reservoir_random.desc": "Takes random blocks from your hotbar", - "constructionwand.option.reservoirs.constructionwand:reservoir_conjuration": "Conjuration Reservoir", - "constructionwand.option.reservoirs.constructionwand:reservoir_conjuration.desc": "Places conjured blocks. These act as placeholder blocks and can be replaced by a block of choice.", + "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in front of the original block", @@ -56,5 +51,11 @@ "constructionwand.option.match.any": "§cAny", "constructionwand.option.match.any.desc": "Extend any block", + "constructionwand.option.random": "Random: ", + "constructionwand.option.random.yes": "§aYes", + "constructionwand.option.random.yes.desc": "Place random blocks present in your hotbar", + "constructionwand.option.random.no": "§cNo", + "constructionwand.option.random.no.desc": "Don't randomize placed blocks", + "stat.constructionwand.use_wand": "Blocks placed using Wand" } \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/textures/item/reservoir_conjuration.png b/src/main/resources/assets/constructionwand/textures/item/reservoir_conjuration.png deleted file mode 100644 index d7611480dffb5c928904cfda194c0289db2be0a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 667 zcmV;M0%ZM(P)EX>4Tx04R}tkv&MmKpe$iQ>7}c4t5Z6$WWauh>D1lR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7X`w}o2gm(*ckglc4iIW3rdb_hfTr7K zG9DAtnN>0H3IV+cVFukYvy3@OO2Bh`-NVP%yC~1{KKJJcsacBwK9P8q8KzCVK|Hl- z8=Uuv!>k~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK* z!7Ar1&RVI$n)l={3}*F}Wvu?Dk!1^8&O(yQY<8CKjz^dbo~;!6mk{8 z$gzMjG{~+W{11M2Yvm@!8%eL-Bz8MxA0{&EeN{v^HH z(jrGd-!^b@-O`jj;Bp5Tcrs*Db|pVeA(sQ*&*+;nK>sb!v)1UXxsTHaAVpmzZh(VB zU?fl3>mKj!YVYmeGtK^f09=xCo(OoZ!T?e}|000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0001TNklLy1 zEcpGGpa$A`fmqv-y_mqtj2Q;RXhyb(3E4FC0K^tP=&ocz4{?04fY$}c8ej^rg$Z64 z;IkQ505cI2aQI?}i4x6t4InmMP@EE(hzUe|0089fEeT1bTY~@q002ovPDHLkV1l$} B4y^zH diff --git a/src/main/resources/assets/constructionwand/textures/item/reservoir_random.png b/src/main/resources/assets/constructionwand/textures/item/reservoir_random.png deleted file mode 100644 index 7991eec84ee0fc2855bd751c5afd56ad3aab39ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 679 zcmV;Y0$BZtP)EX>4Tx04R}tkv&MmKpe$iQ>7}c4t5Z6$WWauh>D1lR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7X`w}o2gm(*ckglc4iIW3rdb_hfTr7K zG9DAtnN>0H3IV+cVFukYvy3@OO2Bh`-NVP%yC~1{KKJJcsacBwK9P8q8KzCVK|Hl- z8=Uuv!>k~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK* z!7Ar1&RVI$n)l={3}*F}Wvu?Dk!1^8&O(yQY<8CKjz^dbo~;!6mk{8 z$gzMjG{~+W{11M2Yvm@!8%eL-Bz8MxA0{&EeN{v^HH z(jrGd-!^b@-O`jj;Bp5Tcrs*Db|pVeA(sQ*&*+;nK>sb!v)1UXxsTHaAVpmzZh(VB zU?fl3>mKj!YVYmeGtK^f09=xCo(OoZ!T_CX>@2HM@dakSAh-}0001fNkla_1!omEqMeS{1=s==TR1T=FfcH}(gCiRfN93% zJw`$aiBOa>5%MFUC}kuzDdX}A6S1L<9Kb{wKx~u}6^Qr}8Y89I831;QL@mHSlf?i4 N002ovPDHLkV1jP16E^?= From 28bd5a7c7080e1543b3d9c7ccad62aff183e512a Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 7 Mar 2021 17:50:25 +0100 Subject: [PATCH 29/78] Wand Supplier/Action Refactoring fixed inventory pickup order undo now works within range of blocks --- .../constructionwand/api/IWandAction.java | 16 ++++- .../constructionwand/api/IWandCore.java | 4 +- .../constructionwand/api/IWandSupplier.java | 16 ++++- .../constructionwand/basics/WandUtil.java | 4 +- .../constructionwand/basics/pool/IPool.java | 1 + .../basics/pool/OrderedPool.java | 5 ++ .../basics/pool/RandomPool.java | 6 ++ .../items/core/CoreDefault.java | 4 +- .../items/core/ItemCoreAngel.java | 4 +- .../items/core/ItemCoreDestruction.java | 4 +- .../constructionwand/items/wand/ItemWand.java | 4 +- .../constructionwand/wand/WandJob.java | 28 +++++--- .../wand/action/ActionAngel.java | 69 ++++++++----------- .../wand/action/ActionConstruction.java | 31 +++++---- .../wand/action/ActionDestruction.java | 25 ++++--- .../wand/supplier/SupplierInventory.java | 53 ++++---------- .../wand/supplier/SupplierRandom.java | 12 ++-- .../wand/undo/UndoHistory.java | 24 +++++-- 18 files changed, 166 insertions(+), 144 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/api/IWandAction.java b/src/main/java/thetadev/constructionwand/api/IWandAction.java index 7878d93..d86b7e6 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandAction.java +++ b/src/main/java/thetadev/constructionwand/api/IWandAction.java @@ -1,10 +1,24 @@ package thetadev.constructionwand.api; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.ISnapshot; +import javax.annotation.Nonnull; import java.util.List; public interface IWandAction { - List getSnapshots(IWandSupplier supplier); + @Nonnull + List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier supplier); + + @Nonnull + List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier supplier); } diff --git a/src/main/java/thetadev/constructionwand/api/IWandCore.java b/src/main/java/thetadev/constructionwand/api/IWandCore.java index 02ef03a..371d190 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandCore.java +++ b/src/main/java/thetadev/constructionwand/api/IWandCore.java @@ -1,10 +1,8 @@ package thetadev.constructionwand.api; -import thetadev.constructionwand.wand.WandJob; - public interface IWandCore extends IWandUpgrade { int getColor(); - IWandAction getWandAction(WandJob job); + IWandAction getWandAction(); } diff --git a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java index 93b335b..26370a0 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java +++ b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java @@ -1,18 +1,30 @@ package thetadev.constructionwand.api; import net.minecraft.block.BlockState; +import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.world.World; import thetadev.constructionwand.wand.undo.PlaceSnapshot; import javax.annotation.Nullable; public interface IWandSupplier { - int getMaxBlocks(); + void getSupply(@Nullable BlockItem target); + /** + * Tries to create a new PlaceSnapshot at the specified position. + * Returns null if there aren't any blocks available that can be placed + * in that position. + */ @Nullable - PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock); + PlaceSnapshot getPlaceSnapshot(World world, BlockPos pos, BlockRayTraceResult rayTraceResult, + @Nullable BlockState supportingBlock); + /** + * Consumes an item stack if the placement was successful + */ int takeItemStack(ItemStack stack); } diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index c959a84..a47cfb4 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -76,8 +76,8 @@ public class WandUtil } public static List getHotbarWithOffhand(PlayerEntity player) { - ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); - inventory.addAll(player.inventory.mainInventory.subList(0, 9)); + ArrayList inventory = new ArrayList<>(player.inventory.mainInventory.subList(0, 9)); + inventory.addAll(player.inventory.offHandInventory); return inventory; } diff --git a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java index 4cf3ce1..5e3c867 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; public interface IPool { void add(T element); + void remove(T element); @Nullable T draw(); diff --git a/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java b/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java index a5a8449..cf21358 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/OrderedPool.java @@ -18,6 +18,11 @@ public class OrderedPool implements IPool elements.add(element); } + @Override + public void remove(T element) { + elements.remove(element); + } + @Nullable @Override public T draw() { diff --git a/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java b/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java index 2b5f2dd..8e80d9d 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/RandomPool.java @@ -22,6 +22,12 @@ public class RandomPool implements IPool addWithWeight(element, 1); } + @Override + public void remove(T element) { + elements.remove(element); + pool.remove(element); + } + public void addWithWeight(T element, int weight) { if(weight < 1) return; elements.merge(element, weight, Integer::sum); diff --git a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java index 84588d7..a3f7271 100644 --- a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java +++ b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java @@ -15,8 +15,8 @@ public class CoreDefault implements IWandCore } @Override - public IWandAction getWandAction(WandJob job) { - return new ActionConstruction(job); + public IWandAction getWandAction() { + return new ActionConstruction(); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java index 6190382..126a8eb 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -18,7 +18,7 @@ public class ItemCoreAngel extends ItemBase implements IWandCore } @Override - public IWandAction getWandAction(WandJob job) { - return new ActionAngel(job); + public IWandAction getWandAction() { + return new ActionAngel(); } } diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java index 6971129..37df2d3 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java @@ -18,7 +18,7 @@ public class ItemCoreDestruction extends ItemBase implements IWandCore } @Override - public IWandAction getWandAction(WandJob job) { - return new ActionDestruction(job); + public IWandAction getWandAction() { + return new ActionDestruction(); } } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 8119637..6f158ac 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -67,8 +67,8 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel if(world.isRemote) return ActionResult.resultFail(stack); // Right click: Place angel block - WandJob job = getWandJob(player, world, new BlockRayTraceResult(player.getLookVec(), - WandUtil.fromVector(player.getLookVec()), WandUtil.playerPos(player), false), stack); + WandJob job = getWandJob(player, world, BlockRayTraceResult.createMiss(player.getLookVec(), + WandUtil.fromVector(player.getLookVec()), WandUtil.playerPos(player)), stack); return job.doIt() ? ActionResult.resultSuccess(stack) : ActionResult.resultFail(stack); } return ActionResult.resultFail(stack); diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 98df50e..3cd0517 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -8,9 +8,11 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; @@ -21,6 +23,7 @@ import thetadev.constructionwand.wand.supplier.SupplierRandom; import thetadev.constructionwand.wand.undo.ISnapshot; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -32,36 +35,33 @@ public class WandJob public final World world; public final BlockRayTraceResult rayTraceResult; public final WandOptions options; + public final ConfigServer.WandProperties properties; public final ItemStack wand; public final ItemWand wandItem; - @Nullable - public final BlockItem targetItem; - private final IWandAction wandAction; private final IWandSupplier wandSupplier; private List placeSnapshots; - public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand, @Nullable BlockItem targetItem) { + public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { this.player = player; this.world = world; this.rayTraceResult = rayTraceResult; this.placeSnapshots = new LinkedList<>(); - this.targetItem = targetItem; // Get wand this.wand = wand; this.wandItem = (ItemWand) wand.getItem(); options = new WandOptions(wand); + properties = ConfigServer.getWandProperties(wandItem); // Select wand action and supplier based on options - wandSupplier = options.random.get() ? new SupplierRandom(this) : new SupplierInventory(this); - wandAction = options.cores.get().getWandAction(this); - } + wandSupplier = options.random.get() ? + new SupplierRandom(player, options) : new SupplierInventory(player, options); + wandAction = options.cores.get().getWandAction(); - public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { - this(player, world, rayTraceResult, wand, getTargetItem(world, rayTraceResult)); + wandSupplier.getSupply(getTargetItem(world, rayTraceResult)); } @Nullable @@ -73,7 +73,13 @@ public class WandJob } public void getPlaceSnapshots() { - placeSnapshots = wandAction.getSnapshots(wandSupplier); + int limit = wandItem.getLimit(player, wand); + + if(limit == 0) placeSnapshots = new ArrayList<>(); + else if(rayTraceResult.getType() == RayTraceResult.Type.BLOCK) + placeSnapshots = wandAction.getSnapshots(world, player, rayTraceResult, options, properties, limit, wandSupplier); + else + placeSnapshots = wandAction.getSnapshotsFromAir(world, player, rayTraceResult, options, properties, limit, wandSupplier); } public Set getBlockPositions() { diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java index 89073b3..ef692ca 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -2,7 +2,6 @@ package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; @@ -12,37 +11,44 @@ import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.items.wand.ItemWand; -import thetadev.constructionwand.wand.WandJob; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.ISnapshot; import thetadev.constructionwand.wand.undo.PlaceSnapshot; +import javax.annotation.Nonnull; import java.util.LinkedList; import java.util.List; public class ActionAngel implements IWandAction { - private final World world; - private final PlayerEntity player; - private final BlockRayTraceResult rayTraceResult; - private final ItemWand wandItem; - private final BlockItem targetItem; - - public ActionAngel(WandJob wandJob) { - world = wandJob.world; - player = wandJob.player; - rayTraceResult = wandJob.rayTraceResult; - wandItem = wandJob.wandItem; - targetItem = wandJob.targetItem; - } - + @Nonnull @Override - public List getSnapshots(IWandSupplier supplier) { - if(targetItem == null) return getAngelSnapshots(supplier); - else return getTransductionSnapshots(supplier); + public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier supplier) { + LinkedList placeSnapshots = new LinkedList<>(); + + Direction placeDirection = rayTraceResult.getFace(); + BlockPos currentPos = rayTraceResult.getPos(); + BlockState supportingBlock = world.getBlockState(currentPos); + + for(int i = 0; i < properties.getAngel(); i++) { + currentPos = currentPos.offset(placeDirection.getOpposite()); + + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentPos, rayTraceResult, supportingBlock); + if(snapshot != null) { + placeSnapshots.add(snapshot); + break; + } + } + return placeSnapshots; } - public List getAngelSnapshots(IWandSupplier supplier) { + @Nonnull + @Override + public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier supplier) { LinkedList placeSnapshots = new LinkedList<>(); if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return placeSnapshots; @@ -53,28 +59,9 @@ public class ActionAngel implements IWandAction BlockPos currentPos = new BlockPos(placeVec); - PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentPos, null); + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentPos, rayTraceResult,null); if(snapshot != null) placeSnapshots.add(snapshot); return placeSnapshots; } - - public List getTransductionSnapshots(IWandSupplier supplier) { - LinkedList placeSnapshots = new LinkedList<>(); - - Direction placeDirection = rayTraceResult.getFace(); - BlockPos currentPos = rayTraceResult.getPos(); - BlockState supportingBlock = world.getBlockState(currentPos); - - for(int i = 0; i < ConfigServer.getWandProperties(wandItem).getAngel(); i++) { - currentPos = currentPos.offset(placeDirection.getOpposite()); - - PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentPos, supportingBlock); - if(snapshot != null) { - placeSnapshots.add(snapshot); - break; - } - } - return placeSnapshots; - } } diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java index e0a1746..4bd6b8a 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java @@ -1,18 +1,22 @@ package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.ISnapshot; import thetadev.constructionwand.wand.undo.PlaceSnapshot; +import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -22,18 +26,11 @@ import java.util.List; */ public class ActionConstruction implements IWandAction { - private final World world; - private final BlockRayTraceResult rayTraceResult; - private final WandOptions options; - - public ActionConstruction(WandJob wandJob) { - world = wandJob.world; - rayTraceResult = wandJob.rayTraceResult; - options = wandJob.options; - } - + @Nonnull @Override - public List getSnapshots(IWandSupplier supplier) { + public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier supplier) { LinkedList placeSnapshots = new LinkedList<>(); LinkedList candidates = new LinkedList<>(); HashSet allCandidates = new HashSet<>(); @@ -51,7 +48,7 @@ public class ActionConstruction implements IWandAction if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) candidates.add(startingPoint); - while(!candidates.isEmpty() && placeSnapshots.size() < supplier.getMaxBlocks()) { + while(!candidates.isEmpty() && placeSnapshots.size() < limit) { BlockPos currentCandidate = candidates.removeFirst(); try { BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); @@ -59,7 +56,7 @@ public class ActionConstruction implements IWandAction if(WandUtil.matchBlocks(options, targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { - PlaceSnapshot snapshot = supplier.getPlaceSnapshot(currentCandidate, candidateSupportingBlock); + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentCandidate, rayTraceResult, candidateSupportingBlock); if(snapshot == null) continue; placeSnapshots.add(snapshot); @@ -124,4 +121,12 @@ public class ActionConstruction implements IWandAction } return placeSnapshots; } + + @Nonnull + @Override + public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier supplier) { + return new ArrayList<>(); + } } diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java index fe20c22..aba9550 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -5,26 +5,29 @@ import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.ISnapshot; +import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.List; public class ActionDestruction implements IWandAction { - private final World world; - private final PlayerEntity player; - private final BlockRayTraceResult rayTraceResult; - - public ActionDestruction(WandJob wandJob) { - world = wandJob.world; - player = wandJob.player; - rayTraceResult = wandJob.rayTraceResult; + @Nonnull + @Override + public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, int limit, + IWandSupplier wandSupplier) { + // TODO: Destruction action! + return new ArrayList<>(); } + @Nonnull @Override - public List getSnapshots(IWandSupplier supplier) { - // TODO: Destruction action! - return null; + public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, WandOptions options, ConfigServer.WandProperties properties, int limit, IWandSupplier supplier) { + return new ArrayList<>(); } } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index 8e8d087..4593d15 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -17,8 +17,6 @@ import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.basics.pool.IPool; import thetadev.constructionwand.basics.pool.OrderedPool; import thetadev.constructionwand.containers.ContainerManager; -import thetadev.constructionwand.items.ModItems; -import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.PlaceSnapshot; import javax.annotation.Nullable; @@ -32,25 +30,17 @@ import java.util.List; public class SupplierInventory implements IWandSupplier { protected final PlayerEntity player; - protected final World world; - protected final BlockRayTraceResult rayTraceResult; protected final WandOptions options; - protected final int wandLimit; protected HashMap itemCounts; protected IPool itemPool; - protected int maxBlocks; - public SupplierInventory(WandJob job) { - player = job.player; - world = job.world; - rayTraceResult = job.rayTraceResult; - options = job.options; - wandLimit = job.wandItem.getLimit(player, job.wand); - getSupply(job.targetItem); + public SupplierInventory(PlayerEntity player, WandOptions options) { + this.player = player; + this.options = options; } - protected void getSupply(@Nullable BlockItem target) { + public void getSupply(@Nullable BlockItem target) { itemCounts = new LinkedHashMap<>(); ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); @@ -71,28 +61,6 @@ public class SupplierInventory implements IWandSupplier } } } - - // Count inventory supply - countSupply(); - } - - protected void countSupply() { - maxBlocks = 0; - for(int v : itemCounts.values()) { - try { - maxBlocks = Math.addExact(maxBlocks, v); - } catch(ArithmeticException e) { - maxBlocks = Integer.MAX_VALUE; - break; - } - } - - maxBlocks = Math.min(maxBlocks, wandLimit); - } - - @Override - public int getMaxBlocks() { - return maxBlocks; } protected void addBlockItem(BlockItem item) { @@ -105,7 +73,8 @@ public class SupplierInventory implements IWandSupplier @Override @Nullable - public PlaceSnapshot getPlaceSnapshot(BlockPos pos, @Nullable BlockState supportingBlock) { + public PlaceSnapshot getPlaceSnapshot(World world, BlockPos pos, BlockRayTraceResult rayTraceResult, + @Nullable BlockState supportingBlock) { if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; itemPool.reset(); @@ -118,7 +87,15 @@ public class SupplierInventory implements IWandSupplier if(count == 0) continue; PlaceSnapshot placeSnapshot = PlaceSnapshot.get(world, player, rayTraceResult, pos, item, supportingBlock, options); - if(placeSnapshot != null) return placeSnapshot; + if(placeSnapshot != null) { + int ncount = count - 1; + itemCounts.put(item, ncount); + + // Remove item from pool if there are no items left + if(ncount == 0) itemPool.remove(item); + + return placeSnapshot; + } } } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java index 2217086..07343a4 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java @@ -1,22 +1,23 @@ package thetadev.constructionwand.wand.supplier; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import thetadev.constructionwand.basics.WandUtil; +import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.basics.pool.RandomPool; -import thetadev.constructionwand.wand.WandJob; import javax.annotation.Nullable; import java.util.LinkedHashMap; public class SupplierRandom extends SupplierInventory { - public SupplierRandom(WandJob job) { - super(job); + public SupplierRandom(PlayerEntity player, WandOptions options) { + super(player, options); } @Override - protected void getSupply(@Nullable BlockItem target) { + public void getSupply(@Nullable BlockItem target) { itemCounts = new LinkedHashMap<>(); // Random mode -> add all items from hotbar @@ -25,8 +26,5 @@ public class SupplierRandom extends SupplierInventory for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); } - - // Count inventory supply - countSupply(); } } diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index 15e24e8..e2e407e 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -77,13 +77,12 @@ public class UndoHistory if(historyEntries.isEmpty()) return false; HistoryEntry entry = historyEntries.getLast(); - if(entry.world.equals(world) && entry.getBlockPositions().contains(pos)) { - // Remove history entry, sent update to client and undo it - historyEntries.remove(entry); - updateClient(player, true); - return entry.undo(player); - } - return false; + // Player has to be in the same world and near the blocks + if(!entry.world.equals(world) || !entry.withinRange(pos)) return false; + + historyEntries.remove(entry); + updateClient(player, true); + return entry.undo(player); } private static class PlayerEntry @@ -111,6 +110,17 @@ public class UndoHistory return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); } + public boolean withinRange(BlockPos pos) { + Set positions = getBlockPositions(); + + if(positions.contains(pos)) return true; + + for(BlockPos p: positions) { + if(pos.withinDistance(p, 2)) return true; + } + return false; + } + public boolean undo(PlayerEntity player) { for(ISnapshot snapshot : placeSnapshots) { if(snapshot.restore(world, player) && !player.isCreative()) { From 6675412bd15eb3d4fec18881c35143dd57a904cb Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 7 Mar 2021 18:00:10 +0100 Subject: [PATCH 30/78] Code cleanup --- .../thetadev/constructionwand/basics/WandUtil.java | 7 +++---- .../thetadev/constructionwand/basics/pool/IPool.java | 1 + .../constructionwand/crafting/RecipeWandUpgrade.java | 10 ++++------ .../constructionwand/data/ItemModelGenerator.java | 2 +- .../java/thetadev/constructionwand/items/ModItems.java | 5 ++++- .../constructionwand/items/core/CoreDefault.java | 1 - .../constructionwand/items/core/ItemCoreAngel.java | 1 - .../items/core/ItemCoreDestruction.java | 1 - .../java/thetadev/constructionwand/wand/WandJob.java | 2 +- .../constructionwand/wand/action/ActionAngel.java | 2 +- .../wand/action/ActionConstruction.java | 6 ++---- .../wand/action/ActionDestruction.java | 5 +++-- .../constructionwand/wand/undo/UndoHistory.java | 2 +- .../resources/assets/constructionwand/lang/de_de.json | 8 -------- .../resources/assets/constructionwand/lang/en_us.json | 8 -------- 15 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index a47cfb4..0834913 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -52,10 +52,9 @@ public class WandUtil if(player.getHeldItem(Hand.MAIN_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.MAIN_HAND).getItem() instanceof ItemWand) { return player.getHeldItem(Hand.MAIN_HAND); } - else - if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { - return player.getHeldItem(Hand.OFF_HAND); - } + else if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { + return player.getHeldItem(Hand.OFF_HAND); + } return null; } diff --git a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java index 5e3c867..3b64df0 100644 --- a/src/main/java/thetadev/constructionwand/basics/pool/IPool.java +++ b/src/main/java/thetadev/constructionwand/basics/pool/IPool.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; public interface IPool { void add(T element); + void remove(T element); @Nullable diff --git a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java index d9e27a9..29ca662 100644 --- a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java +++ b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java @@ -30,10 +30,9 @@ public class RecipeWandUpgrade extends SpecialRecipe ItemStack stack = inv.getStackInSlot(i); if(!stack.isEmpty()) { if(wand == null && stack.getItem() instanceof ItemWand) wand = stack; - else - if(upgrade == null && stack.getItem() instanceof IWandUpgrade) - upgrade = (IWandUpgrade) stack.getItem(); - else return false; + else if(upgrade == null && stack.getItem() instanceof IWandUpgrade) + upgrade = (IWandUpgrade) stack.getItem(); + else return false; } } @@ -51,8 +50,7 @@ public class RecipeWandUpgrade extends SpecialRecipe ItemStack stack = inv.getStackInSlot(i); if(!stack.isEmpty()) { if(stack.getItem() instanceof ItemWand) wand = stack; - else - if(stack.getItem() instanceof IWandUpgrade) upgrade = (IWandUpgrade) stack.getItem(); + else if(stack.getItem() instanceof IWandUpgrade) upgrade = (IWandUpgrade) stack.getItem(); } } diff --git a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java index 864c4f5..507bc4b 100644 --- a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java @@ -24,7 +24,7 @@ public class ItemModelGenerator extends ItemModelProvider if(item instanceof ICustomItemModel) ((ICustomItemModel) item).generateCustomItemModel(this, name); else if(item instanceof BlockItem) - withExistingParent(name, modLoc("block/"+name)); + withExistingParent(name, modLoc("block/" + name)); else withExistingParent(name, "item/generated").texture("layer0", "item/" + name); } } diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 0da1fd0..8949376 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -2,7 +2,10 @@ package thetadev.constructionwand.items; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.color.ItemColors; -import net.minecraft.item.*; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemModelsProperties; +import net.minecraft.item.ItemTier; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; diff --git a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java index a3f7271..4053eee 100644 --- a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java +++ b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java @@ -4,7 +4,6 @@ import net.minecraft.util.ResourceLocation; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandCore; -import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.action.ActionConstruction; public class CoreDefault implements IWandCore diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java index 126a8eb..6c9566b 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -3,7 +3,6 @@ package thetadev.constructionwand.items.core; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandCore; import thetadev.constructionwand.items.ItemBase; -import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.action.ActionAngel; public class ItemCoreAngel extends ItemBase implements IWandCore diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java index 37df2d3..4e25627 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java @@ -3,7 +3,6 @@ package thetadev.constructionwand.items.core; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandCore; import thetadev.constructionwand.items.ItemBase; -import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.action.ActionDestruction; public class ItemCoreDestruction extends ItemBase implements IWandCore diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 3cd0517..5edbcd1 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -12,12 +12,12 @@ import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandAction; +import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.items.wand.ItemWand; -import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.wand.supplier.SupplierInventory; import thetadev.constructionwand.wand.supplier.SupplierRandom; import thetadev.constructionwand.wand.undo.ISnapshot; diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java index ef692ca..26d4ff5 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -59,7 +59,7 @@ public class ActionAngel implements IWandAction BlockPos currentPos = new BlockPos(placeVec); - PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentPos, rayTraceResult,null); + PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentPos, rayTraceResult, null); if(snapshot != null) placeSnapshots.add(snapshot); return placeSnapshots; diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java index 4bd6b8a..a865248 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java @@ -11,7 +11,6 @@ import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.ISnapshot; import thetadev.constructionwand.wand.undo.PlaceSnapshot; @@ -44,9 +43,8 @@ public class ActionConstruction implements IWandAction if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) candidates.add(startingPoint); } - else - if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) - candidates.add(startingPoint); + else if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) + candidates.add(startingPoint); while(!candidates.isEmpty() && placeSnapshots.size() < limit) { BlockPos currentCandidate = candidates.removeFirst(); diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java index aba9550..e96d401 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -7,7 +7,6 @@ import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.option.WandOptions; -import thetadev.constructionwand.wand.WandJob; import thetadev.constructionwand.wand.undo.ISnapshot; import javax.annotation.Nonnull; @@ -27,7 +26,9 @@ public class ActionDestruction implements IWandAction @Nonnull @Override - public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, WandOptions options, ConfigServer.WandProperties properties, int limit, IWandSupplier supplier) { + public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + WandOptions options, ConfigServer.WandProperties properties, + int limit, IWandSupplier supplier) { return new ArrayList<>(); } } diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index e2e407e..d09c7fc 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -115,7 +115,7 @@ public class UndoHistory if(positions.contains(pos)) return true; - for(BlockPos p: positions) { + for(BlockPos p : positions) { if(pos.withinDistance(p, 2)) return true; } return false; diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index cf3f7d2..175031c 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -3,16 +3,13 @@ "item.constructionwand.iron_wand": "Eiserner Stab", "item.constructionwand.diamond_wand": "Diamantener Stab", "item.constructionwand.infinity_wand": "Stab der Unendlichkeit", - "constructionwand.tooltip.blocks": "Max. %d Blöcke", "constructionwand.tooltip.shift": "Drücke [SHIFT]", - "constructionwand.option.mode": "", "constructionwand.option.mode.default": "Standardmodus", "constructionwand.option.mode.default.desc": "Erweitere dein Gebäude auf der dir zugewandten Seite", "constructionwand.option.mode.angel": "§6Engelsmodus", "constructionwand.option.mode.angel.desc": "Platziere hinter Blöcken sowie in der Luft", - "constructionwand.option.lock": "Beschränkung: ", "constructionwand.option.lock.horizontal": "§aHorizontal", "constructionwand.option.lock.horizontal.desc": "Baut eine horizontale Säule vor dem Originalblock", @@ -24,19 +21,16 @@ "constructionwand.option.lock.eastwest.desc": "Baut eine Reihe in OW-Richtung auf dem Originalblock", "constructionwand.option.lock.nolock": "§cKeine", "constructionwand.option.lock.nolock.desc": "Erweitert in jede Richtung", - "constructionwand.option.direction": "Ausrichtung: ", "constructionwand.option.direction.target": "§6Zielblock", "constructionwand.option.direction.target.desc": "Platziert Blöcke mit der selben Ausrichtung wie der Zielblock", "constructionwand.option.direction.player": "§aSpieler", "constructionwand.option.direction.player.desc": "Platziert Blöcke in der Richtung, auf die der Spieler zeigt", - "constructionwand.option.replace": "Ersetzen: ", "constructionwand.option.replace.yes": "§aJa", "constructionwand.option.replace.yes.desc": "Ersetzt bestimmte Blöcke wie Flüssigkeiten, Schnee und hohes Gras", "constructionwand.option.replace.no": "§cNein", "constructionwand.option.replace.no.desc": "Ersetzt keine Blöcke", - "constructionwand.option.match": "Vergleich: ", "constructionwand.option.match.exact": "§aExakt", "constructionwand.option.match.exact.desc": "Erweitert nur Blöcke, die gleich dem Startblock sind", @@ -44,12 +38,10 @@ "constructionwand.option.match.similar.desc": "Behandle ähnliche Blöcke (Erde/Gras) gleich", "constructionwand.option.match.any": "§cAlle", "constructionwand.option.match.any.desc": "Erweitert alle Blöcke", - "constructionwand.option.random": "Zufallsmodus: ", "constructionwand.option.random.yes": "§aEin", "constructionwand.option.random.yes.desc": "Platziere zufällige Blöcke aus der Hotbar", "constructionwand.option.random.no": "§cAus", "constructionwand.option.random.no.desc": "Platziere Blöcke normal", - "stat.constructionwand.use_wand": "Blöcke mithilfe des Stabs platziert" } \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index f6bcda4..992b18d 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -7,10 +7,8 @@ "item.constructionwand.core_destruction": "Destruction Wand Core", "item.constructionwand.reservoir_random": "Random Wand Reservoir", "item.constructionwand.reservoir_conjuration": "Conjuration Wand Reservoir", - "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", - "constructionwand.option.cores": "", "constructionwand.option.cores.constructionwand:default": "Construction Core", "constructionwand.option.cores.constructionwand:default.desc": "Extend your building on the side facing you", @@ -18,7 +16,6 @@ "constructionwand.option.cores.constructionwand:core_angel.desc": "Place behind blocks and in mid air", "constructionwand.option.cores.constructionwand:core_destruction": "§cDestruction Core", "constructionwand.option.cores.constructionwand:core_destruction.desc": "Destroys blocks on the side facing you", - "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in front of the original block", @@ -30,19 +27,16 @@ "constructionwand.option.lock.eastwest.desc": "Build a row in E/W direction on top of the original block", "constructionwand.option.lock.nolock": "§cNone", "constructionwand.option.lock.nolock.desc": "Extend from any side of the original block", - "constructionwand.option.direction": "Direction: ", "constructionwand.option.direction.target": "§6Target", "constructionwand.option.direction.target.desc": "Place blocks with same direction as target block", "constructionwand.option.direction.player": "§aPlayer", "constructionwand.option.direction.player.desc": "Place blocks facing the player", - "constructionwand.option.replace": "Replacement: ", "constructionwand.option.replace.yes": "§aYes", "constructionwand.option.replace.yes.desc": "Replace certain blocks like fluids, snow and tallgrass", "constructionwand.option.replace.no": "§cNo", "constructionwand.option.replace.no.desc": "Don't replace blocks", - "constructionwand.option.match": "Matching: ", "constructionwand.option.match.exact": "§aExact", "constructionwand.option.match.exact.desc": "Only extend blocks that are exactly the same", @@ -50,12 +44,10 @@ "constructionwand.option.match.similar.desc": "Treat similar blocks (dirt/grass types) equally", "constructionwand.option.match.any": "§cAny", "constructionwand.option.match.any.desc": "Extend any block", - "constructionwand.option.random": "Random: ", "constructionwand.option.random.yes": "§aYes", "constructionwand.option.random.yes.desc": "Place random blocks present in your hotbar", "constructionwand.option.random.no": "§cNo", "constructionwand.option.random.no.desc": "Don't randomize placed blocks", - "stat.constructionwand.use_wand": "Blocks placed using Wand" } \ No newline at end of file From 243ac0e56e595ac78e3b66100e23e5924ee65ecb Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 7 Mar 2021 18:08:18 +0100 Subject: [PATCH 31/78] Updated language files --- .../assets/constructionwand/lang/de_de.json | 22 ++++++++++++++----- .../assets/constructionwand/lang/en_us.json | 10 +++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index 175031c..900c2c8 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -3,13 +3,20 @@ "item.constructionwand.iron_wand": "Eiserner Stab", "item.constructionwand.diamond_wand": "Diamantener Stab", "item.constructionwand.infinity_wand": "Stab der Unendlichkeit", + "item.constructionwand.core_angel": "Kristall der Engel", + "item.constructionwand.core_destruction": "Kristall der Zerstörung", + "constructionwand.tooltip.blocks": "Max. %d Blöcke", "constructionwand.tooltip.shift": "Drücke [SHIFT]", - "constructionwand.option.mode": "", - "constructionwand.option.mode.default": "Standardmodus", - "constructionwand.option.mode.default.desc": "Erweitere dein Gebäude auf der dir zugewandten Seite", - "constructionwand.option.mode.angel": "§6Engelsmodus", - "constructionwand.option.mode.angel.desc": "Platziere hinter Blöcken sowie in der Luft", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "Kristall der Konstruktion", + "constructionwand.option.cores.constructionwand:default.desc": "Platziere Blöcke, wohin du zeigst", + "constructionwand.option.cores.constructionwand:core_angel": "§6Kristall der Engel", + "constructionwand.option.cores.constructionwand:core_angel.desc": "Platziere hinter Blöcken sowie in der Luft", + "constructionwand.option.cores.constructionwand:core_destruction": "§cKristall der Zerstörung", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "Zerstöre Blöcke, wohin du zeigst", + "constructionwand.option.lock": "Beschränkung: ", "constructionwand.option.lock.horizontal": "§aHorizontal", "constructionwand.option.lock.horizontal.desc": "Baut eine horizontale Säule vor dem Originalblock", @@ -21,16 +28,19 @@ "constructionwand.option.lock.eastwest.desc": "Baut eine Reihe in OW-Richtung auf dem Originalblock", "constructionwand.option.lock.nolock": "§cKeine", "constructionwand.option.lock.nolock.desc": "Erweitert in jede Richtung", + "constructionwand.option.direction": "Ausrichtung: ", "constructionwand.option.direction.target": "§6Zielblock", "constructionwand.option.direction.target.desc": "Platziert Blöcke mit der selben Ausrichtung wie der Zielblock", "constructionwand.option.direction.player": "§aSpieler", "constructionwand.option.direction.player.desc": "Platziert Blöcke in der Richtung, auf die der Spieler zeigt", + "constructionwand.option.replace": "Ersetzen: ", "constructionwand.option.replace.yes": "§aJa", "constructionwand.option.replace.yes.desc": "Ersetzt bestimmte Blöcke wie Flüssigkeiten, Schnee und hohes Gras", "constructionwand.option.replace.no": "§cNein", "constructionwand.option.replace.no.desc": "Ersetzt keine Blöcke", + "constructionwand.option.match": "Vergleich: ", "constructionwand.option.match.exact": "§aExakt", "constructionwand.option.match.exact.desc": "Erweitert nur Blöcke, die gleich dem Startblock sind", @@ -38,10 +48,12 @@ "constructionwand.option.match.similar.desc": "Behandle ähnliche Blöcke (Erde/Gras) gleich", "constructionwand.option.match.any": "§cAlle", "constructionwand.option.match.any.desc": "Erweitert alle Blöcke", + "constructionwand.option.random": "Zufallsmodus: ", "constructionwand.option.random.yes": "§aEin", "constructionwand.option.random.yes.desc": "Platziere zufällige Blöcke aus der Hotbar", "constructionwand.option.random.no": "§cAus", "constructionwand.option.random.no.desc": "Platziere Blöcke normal", + "stat.constructionwand.use_wand": "Blöcke mithilfe des Stabs platziert" } \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 992b18d..586326c 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -5,10 +5,10 @@ "item.constructionwand.infinity_wand": "Infinity Wand", "item.constructionwand.core_angel": "Angel Wand Core", "item.constructionwand.core_destruction": "Destruction Wand Core", - "item.constructionwand.reservoir_random": "Random Wand Reservoir", - "item.constructionwand.reservoir_conjuration": "Conjuration Wand Reservoir", + "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", + "constructionwand.option.cores": "", "constructionwand.option.cores.constructionwand:default": "Construction Core", "constructionwand.option.cores.constructionwand:default.desc": "Extend your building on the side facing you", @@ -16,6 +16,7 @@ "constructionwand.option.cores.constructionwand:core_angel.desc": "Place behind blocks and in mid air", "constructionwand.option.cores.constructionwand:core_destruction": "§cDestruction Core", "constructionwand.option.cores.constructionwand:core_destruction.desc": "Destroys blocks on the side facing you", + "constructionwand.option.lock": "Restriction: ", "constructionwand.option.lock.horizontal": "§aLeft/Right", "constructionwand.option.lock.horizontal.desc": "Build a horizontal column in front of the original block", @@ -27,16 +28,19 @@ "constructionwand.option.lock.eastwest.desc": "Build a row in E/W direction on top of the original block", "constructionwand.option.lock.nolock": "§cNone", "constructionwand.option.lock.nolock.desc": "Extend from any side of the original block", + "constructionwand.option.direction": "Direction: ", "constructionwand.option.direction.target": "§6Target", "constructionwand.option.direction.target.desc": "Place blocks with same direction as target block", "constructionwand.option.direction.player": "§aPlayer", "constructionwand.option.direction.player.desc": "Place blocks facing the player", + "constructionwand.option.replace": "Replacement: ", "constructionwand.option.replace.yes": "§aYes", "constructionwand.option.replace.yes.desc": "Replace certain blocks like fluids, snow and tallgrass", "constructionwand.option.replace.no": "§cNo", "constructionwand.option.replace.no.desc": "Don't replace blocks", + "constructionwand.option.match": "Matching: ", "constructionwand.option.match.exact": "§aExact", "constructionwand.option.match.exact.desc": "Only extend blocks that are exactly the same", @@ -44,10 +48,12 @@ "constructionwand.option.match.similar.desc": "Treat similar blocks (dirt/grass types) equally", "constructionwand.option.match.any": "§cAny", "constructionwand.option.match.any.desc": "Extend any block", + "constructionwand.option.random": "Random: ", "constructionwand.option.random.yes": "§aYes", "constructionwand.option.random.yes.desc": "Place random blocks present in your hotbar", "constructionwand.option.random.no": "§cNo", "constructionwand.option.random.no.desc": "Don't randomize placed blocks", + "stat.constructionwand.use_wand": "Blocks placed using Wand" } \ No newline at end of file From ebf3ae95183b5c77b34dcec9df481682752bbffa Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 7 Mar 2021 23:43:31 +0100 Subject: [PATCH 32/78] Finally added DESTRUCTION ACTION Config options for destruction core + upgradeable Better block limit system --- .../constructionwand/api/IWandAction.java | 10 +- .../constructionwand/basics/ConfigServer.java | 35 ++++-- .../constructionwand/basics/WandUtil.java | 11 ++ .../crafting/RecipeWandUpgrade.java | 3 +- .../constructionwand/items/wand/ItemWand.java | 11 +- .../items/wand/ItemWandBasic.java | 4 +- .../items/wand/ItemWandInfinity.java | 8 -- .../constructionwand/wand/WandJob.java | 20 ++- .../wand/action/ActionAngel.java | 14 ++- .../wand/action/ActionConstruction.java | 12 +- .../wand/action/ActionDestruction.java | 116 +++++++++++++++++- .../wand/undo/BlockgenSnapshot.java | 34 ----- .../wand/undo/DestroySnapshot.java | 9 ++ 13 files changed, 196 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java diff --git a/src/main/java/thetadev/constructionwand/api/IWandAction.java b/src/main/java/thetadev/constructionwand/api/IWandAction.java index d86b7e6..fc2067d 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandAction.java +++ b/src/main/java/thetadev/constructionwand/api/IWandAction.java @@ -1,9 +1,9 @@ package thetadev.constructionwand.api; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; -import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.ISnapshot; @@ -12,13 +12,13 @@ import java.util.List; public interface IWandAction { + int getLimit(ItemStack wand); + @Nonnull List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier supplier); + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit); @Nonnull List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier supplier); + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit); } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 4b2b6ec..d63f0bf 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -35,19 +35,26 @@ public class ConfigServer public static class WandProperties { - public static final WandProperties DEFAULT = new WandProperties(null, null, null); + public static final WandProperties DEFAULT = new WandProperties(null, null, null, null, null); private final ForgeConfigSpec.IntValue durability; private final ForgeConfigSpec.IntValue limit; private final ForgeConfigSpec.IntValue angel; + private final ForgeConfigSpec.IntValue destruction; + private final ForgeConfigSpec.BooleanValue upgradeable; - private WandProperties(ForgeConfigSpec.IntValue durability, ForgeConfigSpec.IntValue limit, ForgeConfigSpec.IntValue angel) { + private WandProperties(ForgeConfigSpec.IntValue durability, ForgeConfigSpec.IntValue limit, + ForgeConfigSpec.IntValue angel, ForgeConfigSpec.IntValue destruction, + ForgeConfigSpec.BooleanValue upgradeable) { this.durability = durability; this.limit = limit; this.angel = angel; + this.destruction = destruction; + this.upgradeable = upgradeable; } - public WandProperties(ForgeConfigSpec.Builder builder, Item wand, int defDurability, int defLimit, int defAngel) { + public WandProperties(ForgeConfigSpec.Builder builder, Item wand, int defDurability, int defLimit, + int defAngel, int defDestruction, boolean defUpgradeable) { builder.push(wand.getRegistryName().getPath()); if(defDurability > 0) { @@ -57,8 +64,12 @@ public class ConfigServer else durability = null; builder.comment("Wand block limit"); limit = builder.defineInRange("limit", defLimit, 1, Integer.MAX_VALUE); - builder.comment("Max placement distance with angel mode (0 to disable angel mode)"); + builder.comment("Max placement distance with angel mode (0 to disable angel core)"); angel = builder.defineInRange("angel", defAngel, 0, Integer.MAX_VALUE); + builder.comment("Wand destruction block limit (0 to disable destruction core)"); + destruction = builder.defineInRange("destruction", defDestruction, 0, Integer.MAX_VALUE); + builder.comment("Allow wand upgrading by putting the wand together with a wand core in a crafting grid."); + upgradeable = builder.define("upgradeable", defUpgradeable); builder.pop(); wandProperties.put(wand, this); @@ -75,6 +86,14 @@ public class ConfigServer public int getAngel() { return angel == null ? 0 : angel.get(); } + + public int getDestruction() { + return destruction == null ? 0 : destruction.get(); + } + + public boolean isUpgradeable() { + return upgradeable != null && upgradeable.get(); + } } static { @@ -85,10 +104,10 @@ public class ConfigServer "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", "new worlds, copy the config files in the /defaultconfigs folder."); - new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0); - new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 1); - new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 4); - new WandProperties(BUILDER, ModItems.WAND_INFINITY, 0, 1024, 8); + new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0, 0, false); + new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 2, 9, true); + new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 8, 25, true); + new WandProperties(BUILDER, ModItems.WAND_INFINITY, 0, 1024, 16, 81, true); BUILDER.push("misc"); BUILDER.comment("Block limit for Infinity Wand used in creative mode"); diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 0834913..a57a23e 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -212,6 +212,17 @@ public class WandUtil WandUtil.maxRange(rayTraceResult.getPos(), pos) <= ConfigServer.MAX_RANGE.get(); } + public static boolean isBlockRemovable(World world, PlayerEntity player, BlockPos pos) { + BlockState currentBlock = world.getBlockState(pos); + + if(!world.isBlockModifiable(player, pos)) return false; + + if(!player.isCreative()) { + if(currentBlock.getBlockHardness(world, pos) <= -1 || world.getTileEntity(pos) != null) return false; + } + return true; + } + /** * Tests if a certain block can be placed by the wand. * If it can, returns the blockstate to be placed. diff --git a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java index 29ca662..5e15f3e 100644 --- a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java +++ b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java @@ -8,6 +8,7 @@ import net.minecraft.item.crafting.SpecialRecipeSerializer; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import thetadev.constructionwand.api.IWandUpgrade; +import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.items.wand.ItemWand; @@ -37,7 +38,7 @@ public class RecipeWandUpgrade extends SpecialRecipe } if(wand == null || upgrade == null) return false; - return !new WandOptions(wand).hasUpgrade(upgrade); + return !new WandOptions(wand).hasUpgrade(upgrade) && ConfigServer.getWandProperties(wand.getItem()).isUpgradeable(); } @Nonnull diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 6f158ac..a3cc9b3 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -91,17 +91,14 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel return false; } - public int getLimit(PlayerEntity player, ItemStack stack) { - return getLimit(); - } - - protected int getLimit() { - return ConfigServer.getWandProperties(this).getLimit(); + public int remainingDurability(ItemStack stack) { + return Integer.MAX_VALUE; } @OnlyIn(Dist.CLIENT) public void addInformation(@Nonnull ItemStack itemstack, World worldIn, @Nonnull List lines, @Nonnull ITooltipFlag extraInfo) { WandOptions options = new WandOptions(itemstack); + int limit = options.cores.get().getWandAction().getLimit(itemstack); String langTooltip = ConstructionWand.MODID + ".tooltip."; @@ -115,7 +112,7 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel } else { IOption opt = options.allOptions[0]; - lines.add(new TranslationTextComponent(langTooltip + "blocks", getLimit()).mergeStyle(TextFormatting.GRAY)); + lines.add(new TranslationTextComponent(langTooltip + "blocks", limit).mergeStyle(TextFormatting.GRAY)); lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.WHITE))); lines.add(new TranslationTextComponent(langTooltip + "shift").mergeStyle(TextFormatting.AQUA)); diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java index f67c61b..9f8d040 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java @@ -22,8 +22,8 @@ public class ItemWandBasic extends ItemWand } @Override - public int getLimit(PlayerEntity player, ItemStack stack) { - return Math.min(stack.getMaxDamage() - stack.getDamage(), getLimit()); + public int remainingDurability(ItemStack stack) { + return stack.getMaxDamage() - stack.getDamage(); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java index 304d8e7..f53cb39 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java @@ -1,17 +1,9 @@ package thetadev.constructionwand.items.wand; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import thetadev.constructionwand.basics.ConfigServer; public class ItemWandInfinity extends ItemWand { public ItemWandInfinity(String name, Properties properties) { super(name, properties.maxStackSize(1).isBurnable()); } - - @Override - public int getLimit(PlayerEntity player, ItemStack stack) { - return player.isCreative() ? ConfigServer.LIMIT_CREATIVE.get() : getLimit(); - } } diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 5edbcd1..8bacb2f 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -24,7 +24,6 @@ import thetadev.constructionwand.wand.undo.ISnapshot; import javax.annotation.Nullable; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -35,7 +34,6 @@ public class WandJob public final World world; public final BlockRayTraceResult rayTraceResult; public final WandOptions options; - public final ConfigServer.WandProperties properties; public final ItemStack wand; public final ItemWand wandItem; @@ -48,13 +46,12 @@ public class WandJob this.player = player; this.world = world; this.rayTraceResult = rayTraceResult; - this.placeSnapshots = new LinkedList<>(); + this.placeSnapshots = new ArrayList<>(); // Get wand this.wand = wand; this.wandItem = (ItemWand) wand.getItem(); options = new WandOptions(wand); - properties = ConfigServer.getWandProperties(wandItem); // Select wand action and supplier based on options wandSupplier = options.random.get() ? @@ -73,13 +70,14 @@ public class WandJob } public void getPlaceSnapshots() { - int limit = wandItem.getLimit(player, wand); + int limit; + if(player.isCreative()) limit = ConfigServer.LIMIT_CREATIVE.get(); + else limit = Math.min(wandItem.remainingDurability(wand), wandAction.getLimit(wand)); - if(limit == 0) placeSnapshots = new ArrayList<>(); - else if(rayTraceResult.getType() == RayTraceResult.Type.BLOCK) - placeSnapshots = wandAction.getSnapshots(world, player, rayTraceResult, options, properties, limit, wandSupplier); + if(rayTraceResult.getType() == RayTraceResult.Type.BLOCK) + placeSnapshots = wandAction.getSnapshots(world, player, rayTraceResult, wand, options, wandSupplier, limit); else - placeSnapshots = wandAction.getSnapshotsFromAir(world, player, rayTraceResult, options, properties, limit, wandSupplier); + placeSnapshots = wandAction.getSnapshotsFromAir(world, player, rayTraceResult, wand, options, wandSupplier, limit); } public Set getBlockPositions() { @@ -87,10 +85,10 @@ public class WandJob } public boolean doIt() { - LinkedList executed = new LinkedList<>(); + ArrayList executed = new ArrayList<>(); for(ISnapshot snapshot : placeSnapshots) { - if(wand.isEmpty() || wandItem.getLimit(player, wand) == 0) break; + if(wand.isEmpty() || wandItem.remainingDurability(wand) == 0) break; if(snapshot.execute(world, player)) { // If the item cant be taken, undo the placement diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java index 26d4ff5..f333d99 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -2,6 +2,7 @@ package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; @@ -21,18 +22,22 @@ import java.util.List; public class ActionAngel implements IWandAction { + @Override + public int getLimit(ItemStack wand) { + return ConfigServer.getWandProperties(wand.getItem()).getAngel(); + } + @Nonnull @Override public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier supplier) { + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList placeSnapshots = new LinkedList<>(); Direction placeDirection = rayTraceResult.getFace(); BlockPos currentPos = rayTraceResult.getPos(); BlockState supportingBlock = world.getBlockState(currentPos); - for(int i = 0; i < properties.getAngel(); i++) { + for(int i = 0; i < limit; i++) { currentPos = currentPos.offset(placeDirection.getOpposite()); PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentPos, rayTraceResult, supportingBlock); @@ -47,8 +52,7 @@ public class ActionAngel implements IWandAction @Nonnull @Override public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier supplier) { + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList placeSnapshots = new LinkedList<>(); if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return placeSnapshots; diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java index a865248..bbd1cf1 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java @@ -2,6 +2,7 @@ package thetadev.constructionwand.wand.action; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; @@ -25,11 +26,15 @@ import java.util.List; */ public class ActionConstruction implements IWandAction { + @Override + public int getLimit(ItemStack wand) { + return ConfigServer.getWandProperties(wand.getItem()).getLimit(); + } + @Nonnull @Override public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier supplier) { + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList placeSnapshots = new LinkedList<>(); LinkedList candidates = new LinkedList<>(); HashSet allCandidates = new HashSet<>(); @@ -123,8 +128,7 @@ public class ActionConstruction implements IWandAction @Nonnull @Override public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier supplier) { + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { return new ArrayList<>(); } } diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java index e96d401..72340fd 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -1,34 +1,138 @@ package thetadev.constructionwand.wand.action; +import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.wand.undo.DestroySnapshot; import thetadev.constructionwand.wand.undo.ISnapshot; import javax.annotation.Nonnull; import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; public class ActionDestruction implements IWandAction { + @Override + public int getLimit(ItemStack wand) { + return ConfigServer.getWandProperties(wand.getItem()).getDestruction(); + } + @Nonnull @Override public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, int limit, - IWandSupplier wandSupplier) { - // TODO: Destruction action! - return new ArrayList<>(); + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { + LinkedList destroySnapshots = new LinkedList<>(); + // Current list of block positions to process + LinkedList candidates = new LinkedList<>(); + // All positions that were processed (dont process blocks multiple times) + HashSet allCandidates = new HashSet<>(); + + // Block face the wand was pointed at + Direction breakDirection = rayTraceResult.getFace(); + // Block the wand was pointed at + BlockPos startingPoint = rayTraceResult.getPos(); + BlockState targetBlock = world.getBlockState(rayTraceResult.getPos()); + + // Is break direction allowed by lock? + // Tried to break blocks from top/bottom face, so the wand should allow breaking in NS/EW direction + if(breakDirection == Direction.UP || breakDirection == Direction.DOWN) { + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) + candidates.add(startingPoint); + } + // Tried to break blocks from side face, so the wand should allow breaking in horizontal/vertical direction + else if(options.testLock(WandOptions.LOCK.HORIZONTAL) || options.testLock(WandOptions.LOCK.VERTICAL)) + candidates.add(startingPoint); + + // Process current candidates, stop when none are avaiable or block limit is reached + while(!candidates.isEmpty() && destroySnapshots.size() < limit) { + BlockPos currentCandidate = candidates.removeFirst(); + try { + BlockState candidateBlock = world.getBlockState(currentCandidate); + + // If target and candidate blocks match and the current candidate has not been processed + if(WandUtil.matchBlocks(options, targetBlock.getBlock(), candidateBlock.getBlock()) && + allCandidates.add(currentCandidate)) { + DestroySnapshot snapshot = DestroySnapshot.get(world, player, currentCandidate); + if(snapshot == null) continue; + destroySnapshots.add(snapshot); + + switch(breakDirection) { + case DOWN: + case UP: + if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { + candidates.add(currentCandidate.offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.SOUTH)); + } + if(options.testLock(WandOptions.LOCK.EASTWEST)) { + candidates.add(currentCandidate.offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.WEST)); + } + if(options.testLock(WandOptions.LOCK.NORTHSOUTH) && options.testLock(WandOptions.LOCK.EASTWEST)) { + candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.WEST)); + } + break; + case NORTH: + case SOUTH: + if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { + candidates.add(currentCandidate.offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.WEST)); + } + if(options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP)); + candidates.add(currentCandidate.offset(Direction.DOWN)); + } + if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.EAST)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.WEST)); + } + break; + case EAST: + case WEST: + if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { + candidates.add(currentCandidate.offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.SOUTH)); + } + if(options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP)); + candidates.add(currentCandidate.offset(Direction.DOWN)); + } + if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.NORTH)); + candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.SOUTH)); + } + break; + } + } + } catch(Exception e) { + // Can't do anything, could be anything. + // Skip if anything goes wrong. + } + } + return destroySnapshots; } @Nonnull @Override public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - WandOptions options, ConfigServer.WandProperties properties, - int limit, IWandSupplier supplier) { + ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { return new ArrayList<>(); } } diff --git a/src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java deleted file mode 100644 index d6e63ee..0000000 --- a/src/main/java/thetadev/constructionwand/wand/undo/BlockgenSnapshot.java +++ /dev/null @@ -1,34 +0,0 @@ -package thetadev.constructionwand.wand.undo; - -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; -import thetadev.constructionwand.basics.WandUtil; -import thetadev.constructionwand.basics.option.WandOptions; - -import javax.annotation.Nullable; - -public class BlockgenSnapshot extends PlaceSnapshot -{ - public BlockgenSnapshot(BlockState block, BlockPos pos) { - super(block, pos, null); - } - - @Nullable - public static BlockgenSnapshot get(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - BlockPos pos, BlockItem item, - @Nullable BlockState supportingBlock, @Nullable WandOptions options) { - BlockState blockState = WandUtil.getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, options); - if(blockState == null) return null; - return new BlockgenSnapshot(blockState, pos); - } - - @Override - public ItemStack getRequiredItems() { - return ItemStack.EMPTY; - } -} diff --git a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index 2621d1c..eee0511 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -7,6 +7,8 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; +import javax.annotation.Nullable; + public class DestroySnapshot implements ISnapshot { public final BlockState block; @@ -17,6 +19,13 @@ public class DestroySnapshot implements ISnapshot this.block = block; } + @Nullable + public static DestroySnapshot get(World world, PlayerEntity player, BlockPos pos) { + if(!WandUtil.isBlockRemovable(world, player, pos)) return null; + + return new DestroySnapshot(world.getBlockState(pos), pos); + } + @Override public BlockPos getPos() { return pos; From 39e02d8fe622d4a3d49aa3952e5e9a116f559933 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 8 Mar 2021 01:33:19 +0100 Subject: [PATCH 33/78] Added crafting recipes for wand cores Wand core list in tooltip Tooltips for wand cores --- images/crafting5.png | Bin 0 -> 2256 bytes images/crafting6.png | Bin 0 -> 2389 bytes images/textures/core_angel.png | Bin 0 -> 788 bytes images/textures/core_destruction.png | Bin 0 -> 783 bytes images/textures/wand_core.xcf | Bin 0 -> 4032 bytes images/textures_old/ConstructionWand.xcf | Bin 8680 -> 0 bytes images/textures_old/diamond_wand.png | Bin 449 -> 0 bytes images/textures_old/infinity_wand.png | Bin 442 -> 0 bytes images/textures_old/iron_wand.png | Bin 429 -> 0 bytes images/textures_old/stone_wand.png | Bin 437 -> 0 bytes .../recipes/tools/core_angel.json | 32 ++++++++++++++++++ .../recipes/tools/core_destruction.json | 32 ++++++++++++++++++ .../constructionwand/recipes/core_angel.json | 22 ++++++++++++ .../recipes/core_destruction.json | 22 ++++++++++++ .../basics/option/WandUpgrades.java | 4 +++ .../data/RecipeGenerator.java | 16 +++++++++ .../constructionwand/items/ModItems.java | 20 +++++------ .../constructionwand/items/core/ItemCore.java | 31 +++++++++++++++++ .../items/core/ItemCoreAngel.java | 4 +-- .../items/core/ItemCoreDestruction.java | 4 +-- .../constructionwand/items/wand/ItemWand.java | 12 ++++++- .../items/wand/ItemWandBasic.java | 1 - .../assets/constructionwand/lang/de_de.json | 2 ++ .../assets/constructionwand/lang/en_us.json | 2 ++ .../textures/item/core_angel.png | Bin 660 -> 788 bytes .../textures/item/core_destruction.png | Bin 662 -> 783 bytes 26 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 images/crafting5.png create mode 100644 images/crafting6.png create mode 100644 images/textures/core_angel.png create mode 100644 images/textures/core_destruction.png create mode 100644 images/textures/wand_core.xcf delete mode 100644 images/textures_old/ConstructionWand.xcf delete mode 100644 images/textures_old/diamond_wand.png delete mode 100644 images/textures_old/infinity_wand.png delete mode 100644 images/textures_old/iron_wand.png delete mode 100644 images/textures_old/stone_wand.png create mode 100644 src/generated/resources/data/constructionwand/advancements/recipes/tools/core_angel.json create mode 100644 src/generated/resources/data/constructionwand/advancements/recipes/tools/core_destruction.json create mode 100644 src/generated/resources/data/constructionwand/recipes/core_angel.json create mode 100644 src/generated/resources/data/constructionwand/recipes/core_destruction.json create mode 100644 src/main/java/thetadev/constructionwand/items/core/ItemCore.java diff --git a/images/crafting5.png b/images/crafting5.png new file mode 100644 index 0000000000000000000000000000000000000000..8b51b86f68666788eb82d5fd69cd4d32602d5ece GIT binary patch literal 2256 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 literal 0 HcmV?d00001 diff --git a/images/textures/core_angel.png b/images/textures/core_angel.png new file mode 100644 index 0000000000000000000000000000000000000000..486908750b5a00a723804c23b4bbe8543d37f01a GIT binary patch literal 788 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 literal 0 HcmV?d00001 diff --git a/images/textures_old/ConstructionWand.xcf b/images/textures_old/ConstructionWand.xcf deleted file mode 100644 index 6fe835e8268a17e06cac91e00adc1fface74684a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8680 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 diff --git a/images/textures_old/diamond_wand.png b/images/textures_old/diamond_wand.png deleted file mode 100644 index 1059b8fe302eb1e9e8e63069754eb96e46b8bf90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 449 zcmV;y0Y3hTP)E010qNS#tmYE+YT{E+YYWr9XB6000McNliruq)TR#8*02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00AjUL_t(I%f(T@OT$1E{_bHe zxd;-FilDTMqSUq2B@PNI6?GB(3naRUqm#3vYbMA12M$qi=;Ee>Me61paWa<{dgovw z2%4)7`Yn(5c;64+V=&Jb2NY29lk9FQYGoP)Un#4U_qNGK`cEiHsaFS|)yY52@U zoH;+!BpfiCgYpEhImQ-e!?c?OWD*!iiSgZ)Q1$xo)*7LbvaNf40D#Ui7zK1D->b}! zL~-3)Z*+>8N>lRvH=O_9yH@9NJ9tV`|R=g(OQ{BTAg}b8}PkN*J7rQWHy3 zQxwWGOEMJPJ$(bh8~MZ;7#Nj2T^vI!PA8`*Fns!cFD(HG40wM2nE#fapZT*rAKN$k zJ3Kk{6_OGEHW_a)5a6uaSd%!R#C#s>&zDKNN~t$Yv!@H g{A6luV8Fnz_H32HnYErvfkDdP>FVdQ&MBb@02Q&Wr2qf` diff --git a/images/textures_old/iron_wand.png b/images/textures_old/iron_wand.png deleted file mode 100644 index c8c93d6b0f00c8d4c87aa08f1a7a6c4d759df2a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmV;e0aE^nP)E010qNS#tmYE+YT{E+YYWr9XB6000McNlirue zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{009+AL_t(I%f(Q!N&`U@oV|S; z5bJW-JNGV7up-L8k0AWp7X7mSxd^ulCr!7({6h2{+oVq{~ zYGo=Zf|P`Qua{5GCnoS2Fc*l7$=Od}fGh@OBW#u~VdGqvO&1y_dw?H9G=8wL!OCcYB=(Bqd0x6-y#K z>>gByciVM$679HVW7AdLiSNYQTN}8#dB*MIi@;B?Zt|BO{Au#15B?>2h2df2_hJBV X5C3|a0jUyO00000NkvXXu0mjf0=24` diff --git a/images/textures_old/stone_wand.png b/images/textures_old/stone_wand.png deleted file mode 100644 index 9bd11ea694629b0bc18773c68c8d3f8fc4630eb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 437 zcmV;m0ZRUfP)E010qNS#tmYE+YT{E+YYWr9XB6000McNlirue zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00A9IL_t(I%f*o~O9Md=h2P$c zg*h7yIikf`Dk&qRx+ZpM`~jvv1q**iTFd(t{sF-vT@b8-co3ryJ1Y^K-Q&jMP!KeG zTIg#AhJEur-WqsxdXWPF%JVa2V$ceKh`|9tje=2ARxBFpjSK*w3bD3B6ePxHJRA;{ z0|s$Wk(ofuSPR3fa77yU$%4(Tb|zxcY;I&CG8Pd4z^fH_4~W=mT8sJoz7s{!w6G2U z;GFY!;9PZ}a28rpGMn8c%#6k2F=ZlnJLvcKm5IjPz3%jVcbn&Fd@?csmM0kWTKeo9 zN-dMg public boolean hasUpgrade(T upgrade) { return upgrades.contains(upgrade); } + + public ArrayList getUpgrades() { + return upgrades; + } } diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index d42f538..8316eca 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -1,6 +1,7 @@ package thetadev.constructionwand.data; import net.minecraft.data.*; +import net.minecraft.item.Items; import net.minecraft.item.crafting.SpecialRecipeSerializer; import net.minecraft.tags.ItemTags; import net.minecraft.util.IItemProvider; @@ -27,6 +28,9 @@ public class RecipeGenerator extends RecipeProvider wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); + coreRecipe(consumer, ModItems.CORE_ANGEL, Inp.fromTag(Tags.Items.FEATHERS), Inp.fromTag(Tags.Items.INGOTS_GOLD)); + coreRecipe(consumer, ModItems.CORE_DESTRUCTION, Inp.fromItem(Items.DIAMOND_PICKAXE), Inp.fromTag(Tags.Items.GEMS_DIAMOND)); + specialRecipe(consumer, RecipeWandUpgrade.SERIALIZER); } @@ -41,6 +45,18 @@ public class RecipeGenerator extends RecipeProvider .build(consumer); } + private void coreRecipe(Consumer consumer, IItemProvider core, Inp item1, Inp item2) { + ShapedRecipeBuilder.shapedRecipe(core) + .key('O', item1.ingredient) + .key('X', item2.ingredient) + .key('#', Tags.Items.GLASS_PANES) + .patternLine(" #X") + .patternLine("#O#") + .patternLine("X# ") + .addCriterion("has_item", hasItem(item1.predicate)) + .build(consumer); + } + private void specialRecipe(Consumer consumer, SpecialRecipeSerializer serializer) { ResourceLocation name = Registry.RECIPE_SERIALIZER.getKey(serializer); CustomRecipeBuilder.customRecipe(serializer).build(consumer, ConstructionWand.loc("dynamic/" + name.getPath()).toString()); diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 8949376..43408dd 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -30,14 +30,14 @@ import java.util.HashSet; public class ModItems { // Wands - public static final Item WAND_STONE = new ItemWandBasic("stone_wand", itemprops(), ItemTier.STONE); - public static final Item WAND_IRON = new ItemWandBasic("iron_wand", itemprops(), ItemTier.IRON); - public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", itemprops(), ItemTier.DIAMOND); - public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand", itemprops()); + public static final Item WAND_STONE = new ItemWandBasic("stone_wand", propWand(), ItemTier.STONE); + public static final Item WAND_IRON = new ItemWandBasic("iron_wand", propWand(), ItemTier.IRON); + public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", propWand(), ItemTier.DIAMOND); + public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand", propWand()); - // Upgrades - public static final Item CORE_ANGEL = new ItemCoreAngel("core_angel", unstackable()); - public static final Item CORE_DESTRUCTION = new ItemCoreDestruction("core_destruction", unstackable()); + // Cores + public static final Item CORE_ANGEL = new ItemCoreAngel("core_angel", propUpgrade()); + public static final Item CORE_DESTRUCTION = new ItemCoreDestruction("core_destruction", propUpgrade()); // Collections public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; @@ -55,12 +55,12 @@ public class ModItems registerItem(r, CORE_DESTRUCTION); } - public static Item.Properties itemprops() { + public static Item.Properties propWand() { return new Item.Properties().group(ItemGroup.TOOLS); } - private static Item.Properties unstackable() { - return itemprops().maxStackSize(1); + private static Item.Properties propUpgrade() { + return new Item.Properties().group(ItemGroup.MISC).maxStackSize(1); } private static void registerItem(IForgeRegistry reg, Item item) { diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCore.java b/src/main/java/thetadev/constructionwand/items/core/ItemCore.java new file mode 100644 index 0000000..4df952d --- /dev/null +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCore.java @@ -0,0 +1,31 @@ +package thetadev.constructionwand.items.core; + +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.api.IWandCore; +import thetadev.constructionwand.items.ItemBase; + +import javax.annotation.Nonnull; +import java.util.List; + +public abstract class ItemCore extends ItemBase implements IWandCore +{ + public ItemCore(String name, Properties properties) { + super(name, properties); + } + + @OnlyIn(Dist.CLIENT) + public void addInformation(@Nonnull ItemStack itemstack, World worldIn, @Nonnull List lines, @Nonnull ITooltipFlag extraInfo) { + lines.add(new TranslationTextComponent(ConstructionWand.MODID + ".option.cores." + getRegistryName().toString() + ".desc") + .mergeStyle(TextFormatting.GRAY)); + lines.add(new TranslationTextComponent(ConstructionWand.MODID + ".tooltip.core_tip") + .mergeStyle(TextFormatting.AQUA)); + } +} diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java index 6c9566b..fc7a05a 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -1,11 +1,9 @@ package thetadev.constructionwand.items.core; import thetadev.constructionwand.api.IWandAction; -import thetadev.constructionwand.api.IWandCore; -import thetadev.constructionwand.items.ItemBase; import thetadev.constructionwand.wand.action.ActionAngel; -public class ItemCoreAngel extends ItemBase implements IWandCore +public class ItemCoreAngel extends ItemCore { public ItemCoreAngel(String name, Properties properties) { super(name, properties); diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java index 4e25627..e243f06 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java @@ -1,11 +1,9 @@ package thetadev.constructionwand.items.core; import thetadev.constructionwand.api.IWandAction; -import thetadev.constructionwand.api.IWandCore; -import thetadev.constructionwand.items.ItemBase; import thetadev.constructionwand.wand.action.ActionDestruction; -public class ItemCoreDestruction extends ItemBase implements IWandCore +public class ItemCoreDestruction extends ItemCore { public ItemCoreDestruction(String name, Properties properties) { super(name, properties); diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index a3cc9b3..ecbc0a4 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -19,7 +19,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.model.generators.ModelFile; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.api.IWandCore; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; @@ -102,6 +102,7 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel String langTooltip = ConstructionWand.MODID + ".tooltip."; + // +SHIFT tooltip: show all options + installed cores if(Screen.hasShiftDown()) { for(int i = 1; i < options.allOptions.length; i++) { IOption opt = options.allOptions[i]; @@ -109,7 +110,16 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.GRAY)) ); } + if(!options.cores.getUpgrades().isEmpty()) { + lines.add(new StringTextComponent("")); + lines.add(new TranslationTextComponent(langTooltip + "cores").mergeStyle(TextFormatting.GRAY)); + + for(IWandCore core : options.cores.getUpgrades()) { + lines.add(new TranslationTextComponent(options.cores.getKeyTranslation() + "." + core.getRegistryName().toString())); + } + } } + // Default tooltip: show block limit + active wand core else { IOption opt = options.allOptions[0]; lines.add(new TranslationTextComponent(langTooltip + "blocks", limit).mergeStyle(TextFormatting.GRAY)); diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java index 9f8d040..66b66a8 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java @@ -1,6 +1,5 @@ package thetadev.constructionwand.items.wand; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.IItemTier; import net.minecraft.item.ItemStack; import thetadev.constructionwand.basics.ConfigServer; diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index 900c2c8..74865f3 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -8,6 +8,8 @@ "constructionwand.tooltip.blocks": "Max. %d Blöcke", "constructionwand.tooltip.shift": "Drücke [SHIFT]", + "constructionwand.tooltip.cores": "Kristalle im Stab:", + "constructionwand.tooltip.core_tip": "Kombiniere den Kristall mit deinem Stab im Craftingfeld", "constructionwand.option.cores": "", "constructionwand.option.cores.constructionwand:default": "Kristall der Konstruktion", diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 586326c..5331b7b 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -8,6 +8,8 @@ "constructionwand.tooltip.blocks": "Max. %d blocks", "constructionwand.tooltip.shift": "Press [SHIFT]", + "constructionwand.tooltip.cores": "Wand cores:", + "constructionwand.tooltip.core_tip": "Combine the core with your wand in a crafting grid", "constructionwand.option.cores": "", "constructionwand.option.cores.constructionwand:default": "Construction Core", diff --git a/src/main/resources/assets/constructionwand/textures/item/core_angel.png b/src/main/resources/assets/constructionwand/textures/item/core_angel.png index df6e8fcd4683996c130cdc1f4bb8f6cd8542b5e0..486908750b5a00a723804c23b4bbe8543d37f01a 100644 GIT binary patch delta 729 zcmV;~0w(>G1(XJmB!7cxLqkwWLqi~Na&Km7Y-IodD3N`UJxIeq9K~N#r6LsvD~dQ| zs7@9{MI5yXMW_&Jg;pI*F8zWg4M~cNqu^R_@ME#+;Nq;SgR3A2et@{SIVrkGiT_Iq zEn+-4?#H`(kGpq(P%ksh>KF$!-8NH+xR}YVilJ8o5yAlaA%DuuGUg;H3E%N`j{slq zVm!;Ji;9VMSRbJ|`YC>4LCuVzla{Sbs>-e%!}D2C8-O`jj;Bp5Td@^KHcBLRqA)g1{ z&*+=7K;JFUz2^1S+{ftykfyGZH^9LmFj}DOb)R>4wMqB(@0n(QKSOMCl%&Aaod5s; z24YJ`L;$b=0002#gtKb^000SaNLh0L04^f{04^f|c%?sf00007bV*G`2jv3?7d9VF zS4)kP>;Wi$?MXyIR5;7+lCerdK@dgH%#IMs0jN*>%Sv5`6lS)PlFXGiNv~3x7VcXz<|fYYqX&5cI3>s?->3f~TH+ z`%Dn6_b&(jYgd03lbn6)M2QK?ojrUwg5eBI4ZbyhgPZcrH;*bgj;dYeSRh6X% zPZsD!xWBl@3@m^d+#P26qO$TI9H(~&*hnAqZoSoJUsd?e!h8b4j7;r2s**AQ0000< LMNUKnu0mjfxM@i= delta 600 zcmV-e0;m0y29yPmB!7izLqkwWLqi~Na&Km7Y-IodD3N`UJxIeq9K~N#r7Eosb`WvM zP@OD@iind|p$HX1tk~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK*!7Ar1&RVI$n)l={3}*F}Wvu? zDk!1^8&O(yQhzKYX+P%SA9Vc^xfF60!N{?IGBn7pAN&t~cWdP)#v4h&7|{9RI3L46 zU>B%Y9q0Slaq1_4{~5T_TmEtlnEoWa+R`FNK;JfSaoy6CJ>YT&70!a80ohP|0ZhGUB-ZKlcLB`rxLm?Q mj4ikfQP}a0I0Nt{EC5-LC@B|NY^VSL002ovPDHLkU;%>Uqy#bm diff --git a/src/main/resources/assets/constructionwand/textures/item/core_destruction.png b/src/main/resources/assets/constructionwand/textures/item/core_destruction.png index 0308908fb59951846e82b7668458c54b6c7e9dcd..987968a2dd8a6355c68d23260dc40d8fbabe7d5a 100644 GIT binary patch delta 723 zcmV;^0xbQO1&;=hB!7cxLqkwWLqi~Na&Km7Y-IodD3N`UJxIeq9K~N#r6LsvD~dQ| zs7@9{MI5yXMW_&Jg;pI*F8zWg4M~cNqu^R_@ME#+;Nq;SgR3A2et@{SIVrkGiT_Iq zEn+-4?#H`(kGpq(P%ksh>KF$!-8NH+xR}YVilJ8o5yAlaA%DuuGUg;H3E%N`j{slq zVm!;Ji;9VMSRbJ|`YC>4LCuVzla{Sbs>-e%!}D2C8-O`jj;Bp5Td@^KHcBLRqA)g1{ z&*+=7K;JFUz2^1S+{ftykfyGZH^9LmFj}DOb)R>4wMqB(@0n(QKSOMCl%&Aaod5s; z24YJ`L;$b=0002#gtKb^000SaNLh0L04^f{04^f|c%?sf00007bV*G`2jv3?7d8;x z=vEw)>;Wi$=t)FDR5;7+kugrgPz;7YJ4qX0psIw}*w{K$LSm}KL3)Jly$sjs0U{Pw zSh7R{RSHp~K5T}*rSzpc68~b!_GkUJ;m=Ek3UBUSIAl0Eg2wz*<%r2s@T;4bA{0Og zjDZaJG(YLp--*i1MU)1FAsWv?-mPa8rW`OwPFYC6K+pd z*Z~jod93Epwh-aH`}qMMDqI0n70hOFr;7KwrW|mfDF*;dCeXIL)HOZOxN)y4XW#f7 zdcVoX6!G#GF`5~V2{?I!CiL8fZ#tj@u16(L1Mt5N`T?m2OVZP)=79hJ002ovPDHLk FV1nLLML+-m delta 602 zcmV-g0;Tk~y#OK7LCS8#Dk?Tr>-#F)87IZL1yduQB#x+>PWeK*!7Ar1&RVI$n)l={3}*F}Wvu? zDk!1^8&O(yQhzKYX+P%SA9Vc^xfF60!N{?IGBn7pAN&t~cWdP)#v4h&7|{9RI3L46 zU>B%Y9q0Slaq1_4{~5T_TmEtlnEoWa+R`FNK;JfSaoy6CJ>YT&7 Date: Mon, 8 Mar 2021 18:57:15 +0100 Subject: [PATCH 34/78] Block placement checks when undoing --- .../constructionwand/basics/WandUtil.java | 29 ++++++++++++------- .../constructionwand/items/wand/ItemWand.java | 2 +- .../constructionwand/wand/WandJob.java | 2 +- .../wand/supplier/SupplierInventory.java | 2 +- .../wand/undo/DestroySnapshot.java | 6 ++++ .../constructionwand/wand/undo/ISnapshot.java | 2 ++ .../wand/undo/PlaceSnapshot.java | 5 ++++ .../wand/undo/UndoHistory.java | 15 +++++++--- 8 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index a57a23e..44a9ad0 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -90,7 +90,7 @@ public class WandUtil return inventory; } - public static int maxRange(BlockPos p1, BlockPos p2) { + public static int blockDistance(BlockPos p1, BlockPos p2) { return Math.max(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getZ() - p2.getZ())); } @@ -197,7 +197,7 @@ public class WandUtil * This check is independent from the used block. */ public static boolean isPositionPlaceable(World world, PlayerEntity player, BlockPos pos, - BlockRayTraceResult rayTraceResult, WandOptions options) { + boolean replace, @Nullable BlockRayTraceResult rayTraceResult) { // Is position out of world? if(!world.isBlockPresent(pos)) return false; @@ -205,11 +205,13 @@ public class WandUtil if(!world.isBlockModifiable(player, pos)) return false; // If replace mode is off, target has to be air - if(!options.replace.get() && !world.isAirBlock(pos)) return false; + if(!replace && !world.isAirBlock(pos)) return false; // Limit placement range - return ConfigServer.MAX_RANGE.get() <= 0 || - WandUtil.maxRange(rayTraceResult.getPos(), pos) <= ConfigServer.MAX_RANGE.get(); + if(rayTraceResult != null && ConfigServer.MAX_RANGE.get() > 0 && + WandUtil.blockDistance(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) return false; + + return true; } public static boolean isBlockRemovable(World world, PlayerEntity player, BlockPos pos) { @@ -223,6 +225,16 @@ public class WandUtil return true; } + public static boolean entitiesCollidingWithBlock(World world, BlockState blockState, BlockPos pos) { + VoxelShape shape = blockState.getCollisionShape(world, pos); + if(!shape.isEmpty()) { + AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); + if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) + return true; + } + return false; + } + /** * Tests if a certain block can be placed by the wand. * If it can, returns the blockstate to be placed. @@ -244,12 +256,7 @@ public class WandUtil if(!isTEAllowed(blockState)) return null; // No entities colliding? - VoxelShape shape = blockState.getCollisionShape(world, pos); - if(!shape.isEmpty()) { - AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) - return null; - } + if(entitiesCollidingWithBlock(world, blockState, pos)) return null; // Adjust blockstate to neighbors blockState = Block.getValidBlockForPosition(blockState, world, pos); diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index ecbc0a4..6cc75a9 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -76,7 +76,7 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel public static WandJob getWandJob(PlayerEntity player, World world, @Nullable BlockRayTraceResult rayTraceResult, ItemStack wand) { WandJob wandJob = new WandJob(player, world, rayTraceResult, wand); - wandJob.getPlaceSnapshots(); + wandJob.getSnapshots(); return wandJob; } diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 8bacb2f..9978655 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -69,7 +69,7 @@ public class WandJob return (BlockItem) tgitem; } - public void getPlaceSnapshots() { + public void getSnapshots() { int limit; if(player.isCreative()) limit = ConfigServer.LIMIT_CREATIVE.get(); else limit = Math.min(wandItem.remainingDurability(wand), wandAction.getLimit(wand)); diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index 4593d15..e990ffd 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -75,7 +75,7 @@ public class SupplierInventory implements IWandSupplier @Nullable public PlaceSnapshot getPlaceSnapshot(World world, BlockPos pos, BlockRayTraceResult rayTraceResult, @Nullable BlockState supportingBlock) { - if(!WandUtil.isPositionPlaceable(world, player, pos, rayTraceResult, options)) return null; + if(!WandUtil.isPositionPlaceable(world, player, pos, options.replace.get(), rayTraceResult)) return null; itemPool.reset(); while(true) { diff --git a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index eee0511..1f580b2 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -46,6 +46,12 @@ public class DestroySnapshot implements ISnapshot return WandUtil.removeBlock(world, player, block, pos); } + @Override + public boolean canRestore(World world, PlayerEntity player) { + return WandUtil.isPositionPlaceable(world, player, pos, false, null) && + !WandUtil.entitiesCollidingWithBlock(world, block, pos); + } + @Override public boolean restore(World world, PlayerEntity player) { return WandUtil.placeBlock(world, player, block, pos, null); diff --git a/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java index 199921e..0606ba2 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java @@ -16,6 +16,8 @@ public interface ISnapshot boolean execute(World world, PlayerEntity player); + boolean canRestore(World world, PlayerEntity player); + boolean restore(World world, PlayerEntity player); void forceRestore(World world); diff --git a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index d8a03f6..b3ec52c 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -53,6 +53,11 @@ public class PlaceSnapshot implements ISnapshot return WandUtil.placeBlock(world, player, block, pos, item); } + @Override + public boolean canRestore(World world, PlayerEntity player) { + return true; + } + @Override public boolean restore(World world, PlayerEntity player) { return WandUtil.removeBlock(world, player, block, pos); diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index d09c7fc..edf444c 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -80,9 +80,12 @@ public class UndoHistory // Player has to be in the same world and near the blocks if(!entry.world.equals(world) || !entry.withinRange(pos)) return false; - historyEntries.remove(entry); - updateClient(player, true); - return entry.undo(player); + if(entry.undo(player)) { + historyEntries.remove(entry); + updateClient(player, true); + return true; + } + return false; } private static class PlayerEntry @@ -116,12 +119,16 @@ public class UndoHistory if(positions.contains(pos)) return true; for(BlockPos p : positions) { - if(pos.withinDistance(p, 2)) return true; + if(pos.withinDistance(p, 3)) return true; } return false; } public boolean undo(PlayerEntity player) { + // Check first if all snapshots can be restored + for(ISnapshot snapshot : placeSnapshots) { + if(!snapshot.canRestore(world, player)) return false; + } for(ISnapshot snapshot : placeSnapshots) { if(snapshot.restore(world, player) && !player.isCreative()) { ItemStack stack = snapshot.getRequiredItems(); From cd55ff3c09d7473d8517bae81688f1cdbdbc62a2 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 16:24:24 +0100 Subject: [PATCH 35/78] Updated README changed Destruction core crafting recipe fixed block limits in creative mode --- README.md | 139 ++++++++++-------- images/crafting6.png | Bin 2389 -> 3336 bytes .../recipes/{tools => misc}/core_angel.json | 0 .../{tools => misc}/core_destruction.json | 2 +- .../recipes/core_destruction.json | 4 +- .../data/RecipeGenerator.java | 2 +- .../constructionwand/wand/WandJob.java | 4 +- 7 files changed, 88 insertions(+), 63 deletions(-) rename src/generated/resources/data/constructionwand/advancements/recipes/{tools => misc}/core_angel.json (100%) rename src/generated/resources/data/constructionwand/advancements/recipes/{tools => misc}/core_destruction.json (91%) diff --git a/README.md b/README.md index ff04050..6fa2575 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,107 @@ # Construction Wand -With a Construction Wand you can place multiple blocks (up to 1024) at once, extending your build on the side you're facing. -Sneak+Right click to activate angel mode which allows you to place a block at the opposite side of the block facing you. -If you concentrate enough, you can even conjure a block in mid air! -![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/wands.png) +With a Construction Wand you can place multiple blocks (up to 1024) at once, extending your build on the side you're +facing. Sneak+Right click to activate angel mode which allows you to place a block at the opposite side of the block +facing you. If you concentrate enough, you can even conjure a block in mid air! + +![](images/wands.png) + +**Note:** These are the instructions for ConstructionWand version 2.0+, which introduced some new features. +If you are still using version 1.x, refer to [those](https://github.com/Theta-Dev/ConstructionWand/tree/1.16.2-1.7) +instructions. ## Wands -There are basic wands made from stone, iron and diamond and the Infinity wand. -Wand properties can be changed in the config. -| Wand | Durability | Max. Blocks | Angel distance | -|----------|-------------|-------------|----------------| -| Stone | 131 | 9 | No angel mode | -| Iron | 250 | 27 | 1 | -| Diamond | 1561 | 128 | 4 | -| Infinity | Unbreakable | 1024 | 8 | +There are basic wands made from stone, iron and diamond and the Infinity wand. Wands from higher tiers are more powerful +and last longer. These properties can be changed in the config. + +| Wand | Durability | Max. Blocks | Upgradeable | Angel distance | Max. Blocks (Destroy) | +|----------|-------------|-------------|-------------|----------------|-----------------------| +| Stone | 131 | 9 | No | - | - | +| Iron | 250 | 27 | Yes | 1 | 9 | +| Diamond | 1561 | 128 | Yes | 4 | 25 | +| Infinity | Unbreakable | 1024 | Yes | 8 | 81 | ## Crafting -![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/crafting1.png) -![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/crafting2.png) -![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/crafting3.png) -![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/crafting4.png) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting1.png) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting2.png) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting3.png) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting4.png) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting5.png) +![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting6.png) -## Modes -**Default mode:** Extends your build on the side facing you. Maximum number of blocks depends on wand tier. -SHIFT+scroll to change the placement mode (Horizontal, Vertical, North/South, East/West, No lock). +## OPTKEY -**Angel mode:** Places a block on the opposite side of the block (or row of blocks) you are facing. -Maximum distance depends on wand tier. Right click empty space to place a block in midair (similar to angel blocks, -hence the name). To do that, you'll need to have the block you want to place in your offhand. -You can't place a block in midair if you've fallen more than 10 blocks deep -(no easy rescue tool from falling into the void). - -You can change the wand mode using the option screen or by SHIFT+Left clicking empty space. +To change a wand's core and options or undo your placement you need to hold down the wand option key +(refered as OPTKEY). By default, this is CTRL, but it can be changed in the client config file. To prevent unwanted +changes to your options, you can configure that you have to hold down SNEAK as well to change options or open up the +GUI. ## Wand cores -Make your wand even better by putting aditional wand cores into it. -- New modes: - - **Angel core** - - Places a block on the opposite side of the block (or row of blocks) you are facing. Maximum distance depends - on wand tier. Right click empty space to place a block in midair (similar to angel blocks, hence the name). - To do that, you'll need to have the block you want to place in your offhand. You can't place a block in midair - if you've fallen more than 10 blocks deep (no easy rescue tool from falling into the void). - - - **Destruction core** - - Point the wand at some blocks to destroy them. +Wand cores are magical gemstones that make your wand do its thing. -- Upgrades - - **Conjuration core** - - If the wand runs out of blocks, it conjures them from thin air. Conjured blocks are nearly transparent and - are very easy to break. Right click them with any block to replace them. - You can force the placement of conjured blocks by holding a feather in your offhand. - +Every wand comes with a Construction core. You can make your wand even better by putting +aditional cores into it. Put your new core together with your wand in a +crafting grid to install it. To switch between cores, hold down OPTKEY and left click empty space +with your wand. +Stone wands can't be upgraded. + +**Construction core** + +Extend your build on the side facing you. Maximum number of blocks depends on wand tier. +Hold down OPTKEY and scroll to change placement restriction +(Horizontal, Vertical, North/South, East/West, No lock). + +**Angel core** + +Place a block on the opposite side of the block (or row of blocks) you are facing. Maximum distance depends +on wand tier. Right click empty space to place a block in midair (similar to angel blocks, hence the name). +To do that, you'll need to have the block you want to place in your offhand. You can't place a block in midair +if you've fallen more than 10 blocks deep (no easy rescue tool from falling into the void). + +**Destruction core** + +Destroy blocks (no tile entities) on the side facing you. Maximum number of blocks depends on wand tier. +Restrictions work just like with the Construction core. Destroyed blocks disappear into the void, +you can use the undo feature if you've made a mistake. ## Options SHIFT+Right clicking empty space opens the option screen of your wand. ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/options.png) -**Restriction:** If restriction is enabled the wand will only place blocks in one row or column (choose between North/South, East/West on a horizontal plane and Horizontal, Vertical on a vertical plane). If the direction lock is switched off, the wand will extend the entire face of the building it's pointed at. This option has no effect in Angel mode. +**Restriction:** If restriction is enabled the wand will only place blocks in one row or column +(choose between North/South, East/West on a horizontal plane and Horizontal, Vertical on a vertical plane). +If the direction lock is switched off, the wand will extend the entire face of the building it's pointed at. +This option has no effect in Angel mode. -**Direction:** If set to "Player" the wand places blocks in the same direction as if they were placed by yourself. Target mode places the blocks in the same direction as their supporting block. See the picture below: +**Direction:** If set to "Player" the wand places blocks in the same direction as if they were placed by yourself. +Target mode places the blocks in the same direction as their supporting block. See the picture below: ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/placedir.png) **Replacement:** Enables/disables the replacement of replaceable blocks (Fluids, snow, tallgrass). -**Matching:** Select which blocks are extended by the wand. If set to "EXACT" it will only extend blocks that are exactly the same as the selected block. -"SIMILAR" will treat similar blocks equally (e.g. extend dirt and grass blocks). +**Matching:** Select which blocks are extended by the wand. If set to "EXACT" it will only extend blocks that +are exactly the same as the selected block.
+"SIMILAR" will treat similar blocks equally (e.g. extend dirt and grass blocks).
"ANY" will extend any block on the face of the building you're looking at. -**Random:** If random mode is enabled, the wand places random blocks from your hotbar. ~~Shamelessly stolen~~ Inspired by the Trowel from Quark. +**Random:** If random mode is enabled, the wand places random blocks from your hotbar.
+~~Shamelessly stolen~~ Inspired by the Trowel from Quark. + +## Undo +Holding down SHIFT+CTRL while looking at a blocks will show you the last blocks you placed with a green border +around them. SHIFT+CTRL+Right clickking any of them will undo the operation, giving you all the items back. +If you used the Destruction core, it will restore the blocks. ## Additional features - If you have shulker boxes in your inventory filled with blocks, the wand can pull them out and place them -- Botania compatibility: The Black Hole Talisman can supply blocks just like shulker boxes can. Having a Rod of the Lands / Rod of the Depths in your inventory will provide you with infinite dirt/cobble at the cost of Mana. - -- Holding down SHIFT+CTRL while looking at a blocks will show you the last blocks you placed with a green border around them. SHIFT+CTRL+Right clickking any of them will undo the operation, giving you all the items back. +- Botania compatibility: The Black Hole Talisman can supply blocks just like shulker boxes can. + Having a Rod of the Lands / Rod of the Depths in your inventory will provide you with infinite dirt/cobble + at the cost of Mana. - Having blocks in your offhand will place them instead of the block you're looking at @@ -92,15 +112,18 @@ SHIFT+Right clicking empty space opens the option screen of your wand. ## Contributions and #Hacktoberfest As #Hacktoberfest now requires repo owners to opt in, I added the tag to this repository. -I'd really appreciate translations. Currently ConstructionWand only has English and German, so if you speak any other language you can help translate the mod and add a new language file under `src/main/resources/assets/constructionwand/lang/`. +I'd really appreciate translations. Currently, ConstructionWand only has English and German, +so if you speak any other language you can help translate the mod and add a new language file +under `src/main/resources/assets/constructionwand/lang/`. ## TileEntity Blacklist -Some modded TileEntitys can cause issues when placed using a wand. They may turn into invisible and unremovable ghost blocks, -become unbreakable or cause other unwanted effects. +Some modded TileEntitys can cause issues when placed using a wand. They may turn into invisible and +unremovable ghost blocks, become unbreakable or cause other unwanted effects. That's why I've included a Black/Whitelist system -for TileEntities in CW Version 1.7. Chisels&Bits blocks are blacklisted by default. There are probably a few other tile entities -from other mods out there which may cause issues as well. If you find some of them you can tell me by creating +for TileEntities in CW Version 1.7. Chisels&Bits blocks are blacklisted by default. +There are probably a few other tile entities from other mods out there which may cause issues as well. +If you find some of them you can tell me by creating an issue, commenting on Curse or editing the default blacklist yourself (it is located at https://github.com/Theta-Dev/ConstructionWand/blob/1.16.2/src/main/java/thetadev/constructionwand/basics/ConfigServer.java#L28) and making a PR. diff --git a/images/crafting6.png b/images/crafting6.png index 4fb88e6114036cc55633369aa460d19a5b983b82..f9bc2867a9f73eaeb9b0a9863977e8d83a7c8d8f 100644 GIT binary patch literal 3336 zcmYk9cT^Kw*Tw@8M2a9?sd`aFx|Az~UKEht6$lC_h(M&1gl0iHNH3v_v_NQr5(y$* zIsz(H5HNvAAcPRA`I39@x7Pc|p0n1>*>j%roV{lMCf?Hg76YRH(6tQ%Z!Sb7@ve#V6Dfq@-<9~-Qn|$A{zOF)l;R+y(+)J3lk{(l^x#{-C!980(Yx)%LF_Gb2W2Y_g~1CK4CIC@!`+yVQfiZ5pPzoQpOL@hj zK;NK_B#eAVOXb=SDC8rHM9MC3ou@kn(^son;<*yo*+SGRi7;o!Ikt2>Nn*J`Pl&#P z&X*)=5AjV$QEDRujUu64E$>d+B%ijWqJXHV6{dUGqSre8HSzuN;&gc|US(o)6i$jI zpB&nw6<);-CS5x!T4(O~zQ^6L@{2=<3UYGpk1P3a?zgXb^)Ki3ZJHny`<+0Uh)@kc z!wK}>a!Q>J<-`J@$*Hv(_JW0#?x-wxgtsO-V{ixmVRm4wvDS&IJbtpzze7^EG(flh zt_k$c1bY0L1X{AbqdgZ!PGq@G&o|zMSUz$Ub(tcFax?rDFZWhFUQ72N zqOFYZJj68=e&6X-1}#l5n%gDf(4@}xkWg-N6d?I|XNg`YES6=iYN5fn>*}V{97|w>+evnit(4BBkPCm)ITQ!u37EIteUy1E%J`U!ro4t*IwmT z``V(cL0S_lW*_WMu4qM#XZPMlt7GewkTpQ_IPijcmhk_&1ojG@Fhbs1%;NNNq8wH%h z@fB<<4!00}oB{g^pb$L!BpRFT;J2R!FCKU5jR!~k%F=(cpUUt>r+Jz+Xjs?|u5I2A z7`Tj{?0>`-7X0V(JbcTgl6lA?G-?#t_$&jjJ=4I@&;($<(<#x@Gr!Qd$M+sJ*uHX>1e{=-A^q6*Rl9`o{=QBc%Z zzPsUmr*P1%z@>@@q!h^{nqi$RzWAL&*^0CC*fX5<#UIF>UD_P z-yq?u=fpkkuGXXEFeLNnAvxgp_VWX|S6;EMiCXK^1Qka{5s+2qk3O05$CiO^F+Zhf zB>-;=fc=BtRx32#P|x4Y&1+MieM{G-1e|-vnCR4{vo&Md<2F!rBBtxFeu}38=P>#RG^{SYi zTRI8+TyEC$Q*Y?SaF3X4C>U#(Kj=U`$iPcS9n3KVrOu>^TMT64OAW^f%|YBNFv`W5wUE|%JaUK0 zPgKNFcyL6Z=9)$M&+ZVxxL&>Pkp1HIOz(%$kMl2WRg88Z)IphM4An`R*=HG=IbO?V zTFvQ=cN$Q*Q#~Kao;nE20{Yrt@vecFsR_7usVVvB_VHn3M6dXmUYC;EC|v7a=6c0e zsUHi-mJ|6Af#Vw2R?vww$B1gexOgysBmWFC7>qa~vKXcu#M2T$oC@9cPFMA{MiI%s zeYyorD(wo8WdfI;#1k@&dZ%w9veuIvJC{BDP8rP#r$|gurC1Ut_ud9ROsAQ(#(6%>#13{rjogkTzcj|VQCaVr%wpB>e9t#Mf& z8y7XNR{gb^Mx62I!|GhifXB^RPAul^!eLgYY`EAWzMnafySt0`U&0}Klw+O|)%J7w#B>wUn;O?$bUOrK15!!+h*(vh>>#-V!sZlzr@)k>pt zdU>VcAg%1P9I@TK)AHMY4;7jh2m$5H-aJ*j8jiWj&7}3+zMi&-Bd*c0O544LH*{2% z@rSSCl-y*3gl`<6{Cex)R#(qKOm_8^`yIX>qlt-DR*(!{Se;h0vlP}vN`<3 zEKA17v1cE2PJbNq0K zJu_lvjZ41kV3c)a1MH7R^1Gl8`BjM4ae*aIVyooytm++oRGT6sZz-(@t#d=BpeiP> zn>21*k=pG$^u5g<=@TD8ij_wLv^d3-}IB~{%qP22@K7YDM>)}gzxWQc~hk-G2`h^cK)1IXN zM@SCg^^GuHCkLZ{x@9SI{32=AkMc^NlHWooWwoQSe!7Q}4*Ii0>bO-$$0yq?-2XDs z$`(%XVGDz%P%#jdOkvmzbfX~-ImX=tsWoJ%S#<=&ek(|)(}G<*>Ts*5g%kK$-s1gU zt2RYKfie-?Nrtooe$NN;;v7|o?;&Nhk9OH2ol$B`f_fi=AN){S=oFX(GltB!GvJ~; z6Y+xbFqE{!#w}WBr)dTI{2BIYVgO7ue1I5tm_Liwb5o}S zRF!`jQs!d^GWl3|2u-#CE~G*(YICk=uTR9##f@+;%;bQYo#VvmC_!UgIC-si3hDY{ zyZB%2;Q!~LE&)1(*qH9_90eFIoZjpp>~*zrMbh1@x-lWWIaFxk38j~HTBzh1Njev@ za$~-9v|FR=q$Q3eXa`}bTSnnXVTgeDRRw2D{{urBUXQI?wxx6c>Z{vKd$@t`TXwReSNR%dtKM}{^cF@_0Uw; zSBF3#nx2Q<{K0h*v^=#{;PZwkw-H>{B^(aMK_FTkN`n$iwG6;LRZlnP<7wf1HvUWi z&#+6Bx>$ZFP$%&mRnzsRf0aquG>HWh@H^WUnH>bv*`P!DFQgv-w-63+0 z##&OO;%bzVEd(-6`>!6o@WKR#Y@Yi$IC+FU~*DHu+ebU^PP|0 zW%YFGD5Lpjl6NHN)2#Ky-&U(L!$PCcqgDE!8{-TT#yaxma`X=?XrJmL5DwcY$3S!) zZc13rS&T%$-gH)0bDge%pq4mB05)^1X)W~uLVxldWU9plC;tZ^*fp*%hsjy~_2BWR zVjtpb*Y|ss5xa85ak3T3!`ex_Y=#2~O#L0!`wzurTAEsR5IjU5Ub0?^bG5X2KDw;S zI%D=^{F&vASg65E$-S%r1jrA8mel;MQ1)v^&l09n#D5&uwviTC88>5BDV(%CIS`4` z9k>>&WU7|{Ofx@>R;fSFl$wriFXqGs#dKK%_oh56BL~$-KHNbbrSnuk9=gM5H8Eo( zg&M4L&IP;Wl5UB|iS<(agDhi{MPRvis>Bb3jcQ@x2554om~tPs8G&qt<1pEbPtgh;t5{AD?@I4OHEu?rB3|ep*b!U}(y58Y zO5V;EBEwgIf}g+0G-W#245fI)!(f%Vf>zS@@M3z zN{OYCw!9?#c)O7kw+6GnG3+f+%h9To?$U>>Q-OI&&07e>u8-vz7O9Ha5buSTzi5EHP~%2pYpre=b~b*2 zDs1J=Gs28A!AJw{vWi+W&%S5jyCQHtA4W#D$>>+Q%`dx`4v9DmHDP*U%G2{`=jotQ$}Af}KN52oOCRM0&X2xrU^v#t99vVhn7IGjNs&n{-f1Z{pY32$=|KyAx!qM73! zTB<8G7r^LrYgpgDJotCw!qhO|5h@5b&@e=2&GFhUI%cl>JO{jPtK$I3C-YNG9>Je8 z;}ww!$oW*XledMc*{aGl*#P8(S{t2Aroh3_@&g2m5{k(E@s--hdcz9*2;Yb^FgihPKmwiKV?a@)N@F%!b>b#gD zy2Hm8{PE>YCocuklBu>N6E$U5AePrpvZdo~sE>i^46a@It8+4WA40z2X83cp1ix@@ zMPlswkre=Hhs+s*9Pv|jYQUAGAP1Y|zTqe5cU@|@R%R!G+Oy;RPdIu;a2W*90sm3U zgs2O1ImtHLz$RaX$~-0+8dr}X4=N4Kv#T+bx&^j5(j32U=~wW>Lt0!v6LnVju-yn$H+ zc1F}omWanJ%5Z$f3+0$QlvY|14Xsjm#!q5p8Y9M=6tOW_sKr)FR=uIJ5N4?jf?NDw z!csfo35`oST{Uu*dw3ux?axzJ`$nq2gZFfC1y(3 zI$U3z@TZ!A`@!*g-`^s|Vu~kDaYrV|`&pTednqz=B_oT_^Tols#8}DnLl)qHNjac) zo?A72LD4dg8jiw|%B7}#5~JNp`Hdd)iQs*8s9YiHAEDPy*YRv?x?QhBnHl~s>^Y{-}`ChyEC3NXz^r3$vC z(+zMWeaadz7_XyPjs`7iQ{Z8Eq6FUqUEJ?n#o=Lm>n?(QscoRt{lNdHoh7Fy(B|bh z8|6PTKLAT2Vvz08NDV4l^-R2GVL`PgC@(r?Lw$`w6-bqq#uT5)Gm5WnA`!c0io-Z| z`Z7q6kx3H~BiurF`AriTypHI1FshTj6_1TiX%IMm0o7Z-kFMZ4(;&4)t=4{VZGF(Q zOl%{+Xc!K!gqyN#qRxhKTzVtW){f~7r59*FhejA?r)Be?Oub+dyQXQM2Q zatJ^+c5;NH(2?air$m;5m2x{8IarPZ`ue|AsQz6%uQ2S+EW&twE2^*>1$y?nf$Rg? z@6%8|i%=vn87EwA8G&BjLQ;uPl9;5`?^tGPUijvFTUt3 diff --git a/src/generated/resources/data/constructionwand/advancements/recipes/tools/core_angel.json b/src/generated/resources/data/constructionwand/advancements/recipes/misc/core_angel.json similarity index 100% rename from src/generated/resources/data/constructionwand/advancements/recipes/tools/core_angel.json rename to src/generated/resources/data/constructionwand/advancements/recipes/misc/core_angel.json diff --git a/src/generated/resources/data/constructionwand/advancements/recipes/tools/core_destruction.json b/src/generated/resources/data/constructionwand/advancements/recipes/misc/core_destruction.json similarity index 91% rename from src/generated/resources/data/constructionwand/advancements/recipes/tools/core_destruction.json rename to src/generated/resources/data/constructionwand/advancements/recipes/misc/core_destruction.json index 5a99d5d..0fee178 100644 --- a/src/generated/resources/data/constructionwand/advancements/recipes/tools/core_destruction.json +++ b/src/generated/resources/data/constructionwand/advancements/recipes/misc/core_destruction.json @@ -11,7 +11,7 @@ "conditions": { "items": [ { - "item": "minecraft:diamond_pickaxe" + "tag": "forge:storage_blocks/diamond" } ] } diff --git a/src/generated/resources/data/constructionwand/recipes/core_destruction.json b/src/generated/resources/data/constructionwand/recipes/core_destruction.json index c12182a..2dff73c 100644 --- a/src/generated/resources/data/constructionwand/recipes/core_destruction.json +++ b/src/generated/resources/data/constructionwand/recipes/core_destruction.json @@ -7,10 +7,10 @@ ], "key": { "O": { - "item": "minecraft:diamond_pickaxe" + "tag": "forge:storage_blocks/diamond" }, "X": { - "tag": "forge:gems/diamond" + "item": "minecraft:diamond_pickaxe" }, "#": { "tag": "forge:glass_panes" diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index 8316eca..649fcce 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -29,7 +29,7 @@ public class RecipeGenerator extends RecipeProvider wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); coreRecipe(consumer, ModItems.CORE_ANGEL, Inp.fromTag(Tags.Items.FEATHERS), Inp.fromTag(Tags.Items.INGOTS_GOLD)); - coreRecipe(consumer, ModItems.CORE_DESTRUCTION, Inp.fromItem(Items.DIAMOND_PICKAXE), Inp.fromTag(Tags.Items.GEMS_DIAMOND)); + coreRecipe(consumer, ModItems.CORE_DESTRUCTION, Inp.fromTag(Tags.Items.STORAGE_BLOCKS_DIAMOND), Inp.fromItem(Items.DIAMOND_PICKAXE)); specialRecipe(consumer, RecipeWandUpgrade.SERIALIZER); } diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 9978655..e215eee 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -17,6 +17,7 @@ import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.ModStats; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.items.ModItems; import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.wand.supplier.SupplierInventory; import thetadev.constructionwand.wand.supplier.SupplierRandom; @@ -71,7 +72,8 @@ public class WandJob public void getSnapshots() { int limit; - if(player.isCreative()) limit = ConfigServer.LIMIT_CREATIVE.get(); + // Infinity wand gets enhanced limit in creative mode + if(player.isCreative() && wandItem == ModItems.WAND_INFINITY) limit = ConfigServer.LIMIT_CREATIVE.get(); else limit = Math.min(wandItem.remainingDurability(wand), wandAction.getLimit(wand)); if(rayTraceResult.getType() == RayTraceResult.Type.BLOCK) From 77f1f032100038071d03345db95141d88b5f26f0 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 16:51:24 +0100 Subject: [PATCH 36/78] DestroySnapshots can replace fluids on undo --- README.md | 2 +- .../wand/undo/DestroySnapshot.java | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6fa2575..fc58c42 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Restrictions work just like with the Construction core. Destroyed blocks disappe you can use the undo feature if you've made a mistake. ## Options -SHIFT+Right clicking empty space opens the option screen of your wand. +SNEAK+OPTKEY+Right clicking empty space opens the option screen of your wand. ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.15/images/options.png) diff --git a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index 1f580b2..cbc3a04 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -2,8 +2,13 @@ package thetadev.constructionwand.wand.undo; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; @@ -48,8 +53,18 @@ public class DestroySnapshot implements ISnapshot @Override public boolean canRestore(World world, PlayerEntity player) { - return WandUtil.isPositionPlaceable(world, player, pos, false, null) && - !WandUtil.entitiesCollidingWithBlock(world, block, pos); + // Is position out of world? + if(!world.isBlockPresent(pos)) return false; + + // Is block modifiable? + if(!world.isBlockModifiable(player, pos)) return false; + + // Entities colliding + if(WandUtil.entitiesCollidingWithBlock(world, block, pos)) return false; + + return world.isAirBlock(pos) || world.getBlockState(pos).isReplaceable( + new BlockItemUseContext(world, player, Hand.MAIN_HAND, new ItemStack(block.getBlock().asItem()), + new BlockRayTraceResult(new Vector3d(0,-1,0), Direction.DOWN, pos, false))); } @Override From fff371abf653b086f7a2316c288acb5aebaec128 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 18:07:32 +0100 Subject: [PATCH 37/78] DestroySnapshots can replace fluids on undo --- .../wand/undo/DestroySnapshot.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index cbc3a04..5e51738 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -2,13 +2,9 @@ package thetadev.constructionwand.wand.undo; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItemUseContext; +import net.minecraft.fluid.Fluids; import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; @@ -59,12 +55,13 @@ public class DestroySnapshot implements ISnapshot // Is block modifiable? if(!world.isBlockModifiable(player, pos)) return false; - // Entities colliding - if(WandUtil.entitiesCollidingWithBlock(world, block, pos)) return false; + // Ignore blocks and entities when in creative + if(player.isCreative()) return true; - return world.isAirBlock(pos) || world.getBlockState(pos).isReplaceable( - new BlockItemUseContext(world, player, Hand.MAIN_HAND, new ItemStack(block.getBlock().asItem()), - new BlockRayTraceResult(new Vector3d(0,-1,0), Direction.DOWN, pos, false))); + // Is block empty or fluid? + if(!world.isAirBlock(pos) && !world.getBlockState(pos).isReplaceable(Fluids.EMPTY)) return false; + + return !WandUtil.entitiesCollidingWithBlock(world, block, pos); } @Override From ba99d1e309329765d91303a412c892404e62616b Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 20:02:45 +0100 Subject: [PATCH 38/78] MaxRange set to 100 --- .../constructionwand/basics/ConfigServer.java | 2 +- .../constructionwand/basics/WandUtil.java | 36 +++++++++---------- .../wand/supplier/SupplierInventory.java | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index d63f0bf..dea4c06 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -113,7 +113,7 @@ public class ConfigServer BUILDER.comment("Block limit for Infinity Wand used in creative mode"); LIMIT_CREATIVE = BUILDER.defineInRange("InfinityWandCreative", 2048, 1, Integer.MAX_VALUE); BUILDER.comment("Maximum placement range (0: unlimited). Affects all wands and is meant for lag prevention, not game balancing."); - MAX_RANGE = BUILDER.defineInRange("MaxRange", 256, 0, Integer.MAX_VALUE); + MAX_RANGE = BUILDER.defineInRange("MaxRange", 100, 0, Integer.MAX_VALUE); BUILDER.comment("Number of operations that can be undone"); UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 44a9ad0..ac5963e 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -192,35 +192,36 @@ public class WandUtil return false; } - /** - * Tests if a wand can place a block at a certain position. - * This check is independent from the used block. - */ - public static boolean isPositionPlaceable(World world, PlayerEntity player, BlockPos pos, - boolean replace, @Nullable BlockRayTraceResult rayTraceResult) { + private static boolean isPositionModifiable(World world, PlayerEntity player, BlockPos pos) { // Is position out of world? if(!world.isBlockPresent(pos)) return false; // Is block modifiable? if(!world.isBlockModifiable(player, pos)) return false; - // If replace mode is off, target has to be air - if(!replace && !world.isAirBlock(pos)) return false; - - // Limit placement range - if(rayTraceResult != null && ConfigServer.MAX_RANGE.get() > 0 && - WandUtil.blockDistance(rayTraceResult.getPos(), pos) > ConfigServer.MAX_RANGE.get()) return false; + // Limit range + if(ConfigServer.MAX_RANGE.get() > 0 && + WandUtil.blockDistance(player.getPosition(), pos) > ConfigServer.MAX_RANGE.get()) return false; return true; } - public static boolean isBlockRemovable(World world, PlayerEntity player, BlockPos pos) { - BlockState currentBlock = world.getBlockState(pos); + /** + * Tests if a wand can place a block at a certain position. + * This check is independent from the used block. + */ + public static boolean isPositionPlaceable(World world, PlayerEntity player, BlockPos pos, boolean replace) { + if(!isPositionModifiable(world,player, pos)) return false; - if(!world.isBlockModifiable(player, pos)) return false; + // If replace mode is off, target has to be air + return replace || world.isAirBlock(pos); + } + + public static boolean isBlockRemovable(World world, PlayerEntity player, BlockPos pos) { + if(!isPositionModifiable(world,player, pos)) return false; if(!player.isCreative()) { - if(currentBlock.getBlockHardness(world, pos) <= -1 || world.getTileEntity(pos) != null) return false; + return !(world.getBlockState(pos).getBlockHardness(world, pos) <= -1) && world.getTileEntity(pos) == null; } return true; } @@ -229,8 +230,7 @@ public class WandUtil VoxelShape shape = blockState.getCollisionShape(world, pos); if(!shape.isEmpty()) { AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - if(!world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty()) - return true; + return !world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty(); } return false; } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index e990ffd..19105de 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -75,7 +75,7 @@ public class SupplierInventory implements IWandSupplier @Nullable public PlaceSnapshot getPlaceSnapshot(World world, BlockPos pos, BlockRayTraceResult rayTraceResult, @Nullable BlockState supportingBlock) { - if(!WandUtil.isPositionPlaceable(world, player, pos, options.replace.get(), rayTraceResult)) return null; + if(!WandUtil.isPositionPlaceable(world, player, pos, options.replace.get())) return null; itemPool.reset(); while(true) { From 503019af9a01cde0ebc1b9464d60ccd5c3b8eac9 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 20:29:39 +0100 Subject: [PATCH 39/78] Removed min undo limit --- src/main/java/thetadev/constructionwand/wand/WandJob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index e215eee..d95e27f 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -114,7 +114,7 @@ public class WandJob } // Add to job history for undo - if(placeSnapshots.size() > 1) ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); + ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); return !placeSnapshots.isEmpty(); } From 81d92aa49400f310c3a7ac7df1d7c12416097c76 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 20:33:55 +0100 Subject: [PATCH 40/78] Removed min undo limit --- src/main/java/thetadev/constructionwand/wand/WandJob.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index d95e27f..fba5eb4 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -111,10 +111,10 @@ public class WandJob if(!placeSnapshots.isEmpty()) { SoundType sound = placeSnapshots.get(0).getBlockState().getSoundType(); world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); - } - // Add to job history for undo - ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); + // Add to job history for undo + ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); + } return !placeSnapshots.isEmpty(); } From 5b29ecca80092dbf2656d8c16c38ea61de665da8 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 20:45:27 +0100 Subject: [PATCH 41/78] Added mod name to pack.mcmeta --- src/main/resources/pack.mcmeta | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 700e07e..89051f3 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,6 +1,6 @@ { "pack": { - "description": "examplemod resources", + "description": "ConstructionWand resources", "pack_format": 5, "_comment": "A pack_format of 5 requires json lang files and some texture changes from 1.15. Note: we require v5 pack meta for all mods." } From cdba987f8d881ed86d88313bd00745d01fc564e1 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 22:49:09 +0100 Subject: [PATCH 42/78] Fixed crash when placing many connectable blocks (iron bars, glass panes, redstone) The reason for this crash is that I precomputed the blockstates to be placed (which would be free standing poles when no blocks are present around them). Placing all blocks at once then causes lots of block updates which may crash the game (especially in MC1.15). So the solution is to calculate the blockstate directly before the placement. --- .../constructionwand/basics/WandUtil.java | 48 ----------- .../constructionwand/wand/WandJob.java | 2 +- .../wand/undo/DestroySnapshot.java | 7 +- .../constructionwand/wand/undo/ISnapshot.java | 3 +- .../wand/undo/PlaceSnapshot.java | 81 +++++++++++++++++-- 5 files changed, 80 insertions(+), 61 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index ac5963e..6f8a2fe 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -235,54 +235,6 @@ public class WandUtil return false; } - /** - * Tests if a certain block can be placed by the wand. - * If it can, returns the blockstate to be placed. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - @Nullable - public static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - BlockPos pos, BlockItem item, - @Nullable BlockState supportingBlock, @Nullable WandOptions options) { - // Is block at pos replaceable? - BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); - if(!ctx.canPlace()) return null; - - // Can block be placed? - BlockState blockState = item.getBlock().getStateForPlacement(ctx); - if(blockState == null) return null; - - // Forbidden Tile Entity? - if(!isTEAllowed(blockState)) return null; - - // No entities colliding? - if(entitiesCollidingWithBlock(world, blockState, pos)) return null; - - // Adjust blockstate to neighbors - blockState = Block.getValidBlockForPosition(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null; - - // Copy block properties from supporting block - if(options != null && supportingBlock != null && options.direction.get() == WandOptions.DIRECTION.TARGET) { - // Block properties to be copied (alignment/rotation properties) - - for(Property property : new Property[]{ - BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, - BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) { - if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { - blockState = blockState.with(property, supportingBlock.get(property)); - } - } - - // Dont dupe double slabs - if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { - SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); - if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); - } - } - return blockState; - } - public static Direction fromVector(Vector3d vector) { return Direction.getFacingFromVector(vector.x, vector.y, vector.z); } diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index fba5eb4..edcb566 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -92,7 +92,7 @@ public class WandJob for(ISnapshot snapshot : placeSnapshots) { if(wand.isEmpty() || wandItem.remainingDurability(wand) == 0) break; - if(snapshot.execute(world, player)) { + if(snapshot.execute(world, player, rayTraceResult)) { // If the item cant be taken, undo the placement if(wandSupplier.takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); else { diff --git a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index 5e51738..af62703 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -5,6 +5,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.fluid.Fluids; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; @@ -12,8 +13,8 @@ import javax.annotation.Nullable; public class DestroySnapshot implements ISnapshot { - public final BlockState block; - public final BlockPos pos; + private final BlockState block; + private final BlockPos pos; public DestroySnapshot(BlockState block, BlockPos pos) { this.pos = pos; @@ -43,7 +44,7 @@ public class DestroySnapshot implements ISnapshot } @Override - public boolean execute(World world, PlayerEntity player) { + public boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult) { return WandUtil.removeBlock(world, player, block, pos); } diff --git a/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java index 0606ba2..feec07d 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java @@ -4,6 +4,7 @@ import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; public interface ISnapshot @@ -14,7 +15,7 @@ public interface ISnapshot ItemStack getRequiredItems(); - boolean execute(World world, PlayerEntity player); + boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult); boolean canRestore(World world, PlayerEntity player); diff --git a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index b3ec52c..a53c9c0 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -1,36 +1,48 @@ package thetadev.constructionwand.wand.undo; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; +import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemStack; +import net.minecraft.state.Property; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.state.properties.SlabType; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.World; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; +import thetadev.constructionwand.wand.WandItemUseContext; import javax.annotation.Nullable; public class PlaceSnapshot implements ISnapshot { - public final BlockState block; - public final BlockPos pos; - public final BlockItem item; + private BlockState block; + private final BlockPos pos; + private final BlockItem item; + private final BlockState supportingBlock; + private final boolean targetMode; - public PlaceSnapshot(BlockState block, BlockPos pos, BlockItem item) { + public PlaceSnapshot(BlockState block, BlockPos pos, BlockItem item, BlockState supportingBlock, boolean targetMode) { this.block = block; this.pos = pos; this.item = item; + this.supportingBlock = supportingBlock; + this.targetMode = targetMode; } - @Nullable public static PlaceSnapshot get(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, BlockPos pos, BlockItem item, @Nullable BlockState supportingBlock, @Nullable WandOptions options) { - BlockState blockState = WandUtil.getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, options); + boolean targetMode = options != null && supportingBlock != null && options.direction.get() == WandOptions.DIRECTION.TARGET; + BlockState blockState = getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, targetMode); if(blockState == null) return null; - return new PlaceSnapshot(blockState, pos, item); + + return new PlaceSnapshot(blockState, pos, item, supportingBlock, targetMode); } @Override @@ -49,7 +61,12 @@ public class PlaceSnapshot implements ISnapshot } @Override - public boolean execute(World world, PlayerEntity player) { + public boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult) { + // Recalculate PlaceBlockState, because other blocks might be placed nearby + // Not doing this may cause game crashes (StackOverflowException) when placing lots of blocks + // with changing orientation like panes, iron bars or redstone. + block = getPlaceBlockstate(world, player, rayTraceResult, pos, item, supportingBlock, targetMode); + if(block == null) return false; return WandUtil.placeBlock(world, player, block, pos, item); } @@ -67,4 +84,52 @@ public class PlaceSnapshot implements ISnapshot public void forceRestore(World world) { world.removeBlock(pos, false); } + + /** + * Tests if a certain block can be placed by the wand. + * If it can, returns the blockstate to be placed. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + @Nullable + private static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + BlockPos pos, BlockItem item, + @Nullable BlockState supportingBlock, boolean targetMode) { + // Is block at pos replaceable? + BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); + if(!ctx.canPlace()) return null; + + // Can block be placed? + BlockState blockState = item.getBlock().getStateForPlacement(ctx); + if(blockState == null) return null; + + // Forbidden Tile Entity? + if(!WandUtil.isTEAllowed(blockState)) return null; + + // No entities colliding? + if(WandUtil.entitiesCollidingWithBlock(world, blockState, pos)) return null; + + // Adjust blockstate to neighbors + blockState = Block.getValidBlockForPosition(blockState, world, pos); + if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null; + + // Copy block properties from supporting block + if(targetMode) { + // Block properties to be copied (alignment/rotation properties) + + for(Property property : new Property[]{ + BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, + BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) { + if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { + blockState = blockState.with(property, supportingBlock.get(property)); + } + } + + // Dont dupe double slabs + if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { + SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); + if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); + } + } + return blockState; + } } From f60692f6d8edccd66c2de2b9c93cb1c5c1e61055 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 14 Mar 2021 22:52:27 +0100 Subject: [PATCH 43/78] Small fix (supportingBlock != null) --- .../java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index a53c9c0..63fae19 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -113,7 +113,7 @@ public class PlaceSnapshot implements ISnapshot if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null; // Copy block properties from supporting block - if(targetMode) { + if(targetMode && supportingBlock != null) { // Block properties to be copied (alignment/rotation properties) for(Property property : new Property[]{ From ddbe201155e618acf564919ccc8826e8d1f6ef83 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 31 Mar 2021 21:09:03 +0200 Subject: [PATCH 44/78] Fixed Bug #33 Preview broken in locations beyond 100k --- gradle.properties | 2 +- .../client/RenderBlockPreview.java | 36 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6505f86..b7b7785 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=2 -version_minor=0 \ No newline at end of file +version_minor=1 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index c79006f..d381c60 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -2,7 +2,6 @@ package thetadev.constructionwand.client; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.vertex.IVertexBuilder; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.IRenderTypeBuffer; import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.entity.Entity; @@ -33,7 +32,7 @@ public class RenderBlockPreview Entity entity = event.getInfo().getRenderViewEntity(); if(!(entity instanceof PlayerEntity)) return; PlayerEntity player = (PlayerEntity) entity; - Set blocks = null; + Set blocks; float colorR = 0, colorG = 0, colorB = 0; ItemStack wand = WandUtil.holdingWand(player); @@ -52,7 +51,22 @@ public class RenderBlockPreview if(blocks == null || blocks.isEmpty()) return; - renderBlockList(blocks, event.getMatrix(), event.getBuffers(), colorR, colorG, colorB); + MatrixStack ms = event.getMatrix(); + IRenderTypeBuffer buffer = event.getBuffers(); + IVertexBuilder lineBuilder = buffer.getBuffer(RenderTypes.TRANSLUCENT_LINES); + + double partialTicks = event.getPartialTicks(); + double d0 = player.lastTickPosX + (player.getPosX() - player.lastTickPosX) * partialTicks; + double d1 = player.lastTickPosY + player.getEyeHeight() + (player.getPosY() - player.lastTickPosY) * partialTicks; + double d2 = player.lastTickPosZ + (player.getPosZ() - player.lastTickPosZ) * partialTicks; + + ms.push(); + + for(BlockPos block : blocks) { + AxisAlignedBB aabb = new AxisAlignedBB(block).offset(-d0, -d1, -d2); + WorldRenderer.drawBoundingBox(ms, lineBuilder, aabb, colorR, colorG, colorB, 0.4F); + } + ms.pop(); event.setCanceled(true); } @@ -60,20 +74,4 @@ public class RenderBlockPreview private static boolean compareRTR(BlockRayTraceResult rtr1, BlockRayTraceResult rtr2) { return rtr1.getPos().equals(rtr2.getPos()) && rtr1.getFace().equals(rtr2.getFace()); } - - private void renderBlockList(Set blocks, MatrixStack ms, IRenderTypeBuffer buffer, float red, float green, float blue) { - double renderPosX = Minecraft.getInstance().getRenderManager().info.getProjectedView().getX(); - double renderPosY = Minecraft.getInstance().getRenderManager().info.getProjectedView().getY(); - double renderPosZ = Minecraft.getInstance().getRenderManager().info.getProjectedView().getZ(); - - ms.push(); - ms.translate(-renderPosX, -renderPosY, -renderPosZ); - - for(BlockPos block : blocks) { - AxisAlignedBB aabb = new AxisAlignedBB(block); - IVertexBuilder lineBuilder = buffer.getBuffer(RenderTypes.TRANSLUCENT_LINES); - WorldRenderer.drawBoundingBox(ms, lineBuilder, aabb, red, green, blue, 0.4F); - } - ms.pop(); - } } From f2d99a8251add1ac8b6fe0595e5944750a083174 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 22 Apr 2021 07:12:28 +0200 Subject: [PATCH 45/78] Fixed Bug #34 Crash on client startup --- gradle.properties | 2 +- .../java/thetadev/constructionwand/ConstructionWand.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index b7b7785..e97a662 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ mcp_mappings=20200723-1.16.1 botania=1.16.2-405 version_major=2 -version_minor=1 \ No newline at end of file +version_minor=2 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index 95e49de..7f811d7 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -66,7 +66,7 @@ public class ConstructionWand int packetIndex = 0; HANDLER.registerMessage(packetIndex++, PacketUndoBlocks.class, PacketUndoBlocks::encode, PacketUndoBlocks::decode, PacketUndoBlocks.Handler::handle); HANDLER.registerMessage(packetIndex++, PacketQueryUndo.class, PacketQueryUndo::encode, PacketQueryUndo::decode, PacketQueryUndo.Handler::handle); - HANDLER.registerMessage(packetIndex++, PacketWandOption.class, PacketWandOption::encode, PacketWandOption::decode, PacketWandOption.Handler::handle); + HANDLER.registerMessage(packetIndex, PacketWandOption.class, PacketWandOption::encode, PacketWandOption::decode, PacketWandOption.Handler::handle); // Container registry ContainerRegistrar.register(); @@ -82,8 +82,9 @@ public class ConstructionWand renderBlockPreview = new RenderBlockPreview(); MinecraftForge.EVENT_BUS.register(renderBlockPreview); MinecraftForge.EVENT_BUS.register(new ClientEvents()); - ModItems.registerModelProperties(); - ModItems.registerItemColors(); + + event.enqueueWork(ModItems::registerModelProperties); + event.enqueueWork(ModItems::registerItemColors); } public static ResourceLocation loc(String name) { From 489e050a7f95280b56256396148804f2eed62155 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 22 Apr 2021 07:26:32 +0200 Subject: [PATCH 46/78] Updated version info --- src/main/resources/META-INF/mods.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index e3238ee..bcbc6c5 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -25,7 +25,7 @@ This is my first minecraft mod. May the odds be ever in your favor. side="BOTH" [[dependencies.constructionwand]] modId="minecraft" - mandatory=true - versionRange="[1.16.2, 1.16.5]" - ordering="NONE" + mandatory = true +versionRange = "[1.16.2, 1.17)" +ordering = "NONE" side="BOTH" From 6b555d207777c0c68b5e25da748d7fb979e59ca0 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 2 Aug 2021 09:34:26 +0200 Subject: [PATCH 47/78] Updated Gradle, Forge and MCP for MC 1.17 --- build.gradle | 8 ++-- gradle.properties | 11 ++--- gradle/wrapper/gradle-wrapper.jar | Bin 54708 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 55 ++++++++++++++--------- gradlew.bat | 43 ++++++++++-------- 6 files changed, 67 insertions(+), 52 deletions(-) diff --git a/build.gradle b/build.gradle index f075063..555679a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,10 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true } } apply plugin: 'net.minecraftforge.gradle' @@ -17,10 +16,11 @@ version = "${mcversion}-${version_major}.${version_minor}" group = "${author}.${modid}" archivesBaseName = "${modid}" -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +// Mojang ships Java 16 to end users in 1.17+ instead of Java 8 in 1.16 or lower, so your mod should target Java 16. +java.toolchain.languageVersion = JavaLanguageVersion.of(16) minecraft { - mappings channel: 'snapshot', version: project.mcp_mappings + mappings channel: project.mcp_channel, version: project.mcp_mappings runs { client { diff --git a/gradle.properties b/gradle.properties index e97a662..d9915f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,11 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false - author=thetadev modid=constructionwand - -mcversion=1.16.5 -forgeversion=36.0.46 -mcp_mappings=20200723-1.16.1 - +mcversion=1.17.1 +forgeversion=37.0.19 +mcp_channel=official +mcp_mappings=1.17.1 botania=1.16.2-405 - version_major=2 version_minor=2 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee94c0ab25cf079ac8ccdf87f41d455d42..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 54708 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

_x$+l{{cH8$W#CT diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1d5b29f..0595cf7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-7.2-20210702220150+0000-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip diff --git a/gradlew b/gradlew index cccdd3d..744e882 100644 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -56,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..107acd3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From e2394ff05ee2f8d53a313bc29e574824487fc38d Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 2 Aug 2021 23:48:18 +0200 Subject: [PATCH 48/78] Ported to 1.17 --- .../constructionwand/ConstructionWand.java | 6 +- .../api/IContainerHandler.java | 10 +- .../constructionwand/api/IWandAction.java | 12 +- .../constructionwand/api/IWandSupplier.java | 14 +- .../constructionwand/api/IWandUpgrade.java | 2 +- .../constructionwand/basics/CommonEvents.java | 6 +- .../constructionwand/basics/ConfigServer.java | 12 +- .../constructionwand/basics/ModStats.java | 8 +- .../basics/ReplacementRegistry.java | 11 +- .../constructionwand/basics/WandUtil.java | 149 ++++++++---------- .../basics/option/OptionBoolean.java | 8 +- .../basics/option/OptionEnum.java | 8 +- .../basics/option/WandOptions.java | 12 +- .../basics/option/WandUpgrades.java | 20 +-- .../basics/option/WandUpgradesSelectable.java | 4 +- .../constructionwand/client/ClientEvents.java | 26 +-- .../client/RenderBlockPreview.java | 63 ++++---- .../constructionwand/client/RenderTypes.java | 37 ----- .../constructionwand/client/ScreenWand.java | 37 ++--- .../containers/ContainerManager.java | 8 +- .../containers/ContainerRegistrar.java | 6 +- .../containers/handlers/HandlerBotania.java | 23 +-- .../handlers/HandlerCapability.java | 27 ++-- .../handlers/HandlerShulkerbox.java | 36 ++--- .../crafting/RecipeWandUpgrade.java | 34 ++-- .../thetadev/constructionwand/data/Inp.java | 18 +-- .../data/ItemModelGenerator.java | 4 +- .../constructionwand/data/ModData.java | 2 +- .../data/RecipeGenerator.java | 64 ++++---- .../constructionwand/items/ItemBase.java | 2 +- .../constructionwand/items/ModItems.java | 30 ++-- .../items/core/CoreDefault.java | 2 +- .../constructionwand/items/core/ItemCore.java | 22 +-- .../constructionwand/items/wand/ItemWand.java | 106 +++++++------ .../items/wand/ItemWandBasic.java | 16 +- .../items/wand/ItemWandInfinity.java | 2 +- .../network/PacketQueryUndo.java | 12 +- .../network/PacketUndoBlocks.java | 10 +- .../network/PacketWandOption.java | 22 +-- .../wand/WandItemUseContext.java | 32 ++-- .../constructionwand/wand/WandJob.java | 41 ++--- .../wand/action/ActionAngel.java | 32 ++-- .../wand/action/ActionConstruction.java | 77 +++++---- .../wand/action/ActionDestruction.java | 75 +++++---- .../wand/supplier/SupplierInventory.java | 30 ++-- .../wand/supplier/SupplierRandom.java | 10 +- .../wand/undo/DestroySnapshot.java | 32 ++-- .../constructionwand/wand/undo/ISnapshot.java | 20 +-- .../wand/undo/PlaceSnapshot.java | 60 +++---- .../wand/undo/UndoHistory.java | 58 +++---- src/main/resources/META-INF/mods.toml | 16 +- 51 files changed, 660 insertions(+), 714 deletions(-) delete mode 100644 src/main/java/thetadev/constructionwand/client/RenderTypes.java diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index 7f811d7..bcfdeab 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -1,6 +1,6 @@ package thetadev.constructionwand; -import net.minecraft.util.ResourceLocation; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; @@ -8,8 +8,8 @@ import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fml.network.NetworkRegistry; -import net.minecraftforge.fml.network.simple.SimpleChannel; +import net.minecraftforge.fmllegacy.network.NetworkRegistry; +import net.minecraftforge.fmllegacy.network.simple.SimpleChannel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import thetadev.constructionwand.basics.ConfigClient; diff --git a/src/main/java/thetadev/constructionwand/api/IContainerHandler.java b/src/main/java/thetadev/constructionwand/api/IContainerHandler.java index 598d080..befd8d5 100644 --- a/src/main/java/thetadev/constructionwand/api/IContainerHandler.java +++ b/src/main/java/thetadev/constructionwand/api/IContainerHandler.java @@ -1,13 +1,13 @@ package thetadev.constructionwand.api; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; public interface IContainerHandler { - boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack); + boolean matches(Player player, ItemStack itemStack, ItemStack inventoryStack); - int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack); + int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack); - int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count); + int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count); } diff --git a/src/main/java/thetadev/constructionwand/api/IWandAction.java b/src/main/java/thetadev/constructionwand/api/IWandAction.java index fc2067d..beac255 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandAction.java +++ b/src/main/java/thetadev/constructionwand/api/IWandAction.java @@ -1,9 +1,9 @@ package thetadev.constructionwand.api; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.ISnapshot; @@ -15,10 +15,10 @@ public interface IWandAction int getLimit(ItemStack wand); @Nonnull - List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + List getSnapshots(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit); @Nonnull - List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + List getSnapshotsFromAir(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit); } diff --git a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java index 26370a0..8578ba6 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandSupplier.java +++ b/src/main/java/thetadev/constructionwand/api/IWandSupplier.java @@ -1,11 +1,11 @@ package thetadev.constructionwand.api; -import net.minecraft.block.BlockState; -import net.minecraft.item.BlockItem; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.wand.undo.PlaceSnapshot; import javax.annotation.Nullable; @@ -20,7 +20,7 @@ public interface IWandSupplier * in that position. */ @Nullable - PlaceSnapshot getPlaceSnapshot(World world, BlockPos pos, BlockRayTraceResult rayTraceResult, + PlaceSnapshot getPlaceSnapshot(Level world, BlockPos pos, BlockHitResult rayTraceResult, @Nullable BlockState supportingBlock); /** diff --git a/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java b/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java index 48b7a8d..7aab489 100644 --- a/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java +++ b/src/main/java/thetadev/constructionwand/api/IWandUpgrade.java @@ -1,6 +1,6 @@ package thetadev.constructionwand.api; -import net.minecraft.util.ResourceLocation; +import net.minecraft.resources.ResourceLocation; public interface IWandUpgrade { diff --git a/src/main/java/thetadev/constructionwand/basics/CommonEvents.java b/src/main/java/thetadev/constructionwand/basics/CommonEvents.java index e698c27..77a9071 100644 --- a/src/main/java/thetadev/constructionwand/basics/CommonEvents.java +++ b/src/main/java/thetadev/constructionwand/basics/CommonEvents.java @@ -1,6 +1,6 @@ package thetadev.constructionwand.basics; -import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -11,8 +11,8 @@ public class CommonEvents { @SubscribeEvent public static void logOut(PlayerEvent.PlayerLoggedOutEvent e) { - PlayerEntity player = e.getPlayer(); - if(player.getEntityWorld().isRemote) return; + Player player = e.getPlayer(); + if(player.level.isClientSide) return; ConstructionWand.instance.undoHistory.removePlayer(player); } } diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index dea4c06..5cbec16 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -1,7 +1,7 @@ package thetadev.constructionwand.basics; -import net.minecraft.item.Item; -import net.minecraft.item.ItemTier; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Tiers; import net.minecraftforge.common.ForgeConfigSpec; import thetadev.constructionwand.items.ModItems; @@ -20,7 +20,7 @@ public class ConfigServer public static final ForgeConfigSpec.ConfigValue> SIMILAR_BLOCKS; private static final String[] SIMILAR_BLOCKS_DEFAULT = { - "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:grass_path" + "minecraft:dirt;minecraft:grass_block;minecraft:coarse_dirt;minecraft:podzol;minecraft:mycelium;minecraft:farmland;minecraft:dirt_path;minecraft:rooted_dirt" }; public static final ForgeConfigSpec.BooleanValue TE_WHITELIST; @@ -104,9 +104,9 @@ public class ConfigServer "in the /saves/myworld/serverconfig folder. If you want to change the serverconfig for all", "new worlds, copy the config files in the /defaultconfigs folder."); - new WandProperties(BUILDER, ModItems.WAND_STONE, ItemTier.STONE.getMaxUses(), 9, 0, 0, false); - new WandProperties(BUILDER, ModItems.WAND_IRON, ItemTier.IRON.getMaxUses(), 27, 2, 9, true); - new WandProperties(BUILDER, ModItems.WAND_DIAMOND, ItemTier.DIAMOND.getMaxUses(), 128, 8, 25, true); + new WandProperties(BUILDER, ModItems.WAND_STONE, Tiers.STONE.getUses(), 9, 0, 0, false); + new WandProperties(BUILDER, ModItems.WAND_IRON, Tiers.IRON.getUses(), 27, 2, 9, true); + new WandProperties(BUILDER, ModItems.WAND_DIAMOND, Tiers.DIAMOND.getUses(), 128, 8, 25, true); new WandProperties(BUILDER, ModItems.WAND_INFINITY, 0, 1024, 16, 81, true); BUILDER.push("misc"); diff --git a/src/main/java/thetadev/constructionwand/basics/ModStats.java b/src/main/java/thetadev/constructionwand/basics/ModStats.java index c41fbd7..ccfa739 100644 --- a/src/main/java/thetadev/constructionwand/basics/ModStats.java +++ b/src/main/java/thetadev/constructionwand/basics/ModStats.java @@ -1,9 +1,9 @@ package thetadev.constructionwand.basics; -import net.minecraft.stats.IStatFormatter; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.stats.StatFormatter; import net.minecraft.stats.Stats; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.registry.Registry; import thetadev.constructionwand.ConstructionWand; public class ModStats @@ -17,6 +17,6 @@ public class ModStats private static void registerStat(ResourceLocation registryName) { // Compare with net.minecraft.stats.Stats#registerCustom Registry.register(Registry.CUSTOM_STAT, registryName.getPath(), registryName); - Stats.CUSTOM.get(registryName, IStatFormatter.DEFAULT); + Stats.CUSTOM.get(registryName, StatFormatter.DEFAULT); } } diff --git a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java index e767913..3677d09 100644 --- a/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java +++ b/src/main/java/thetadev/constructionwand/basics/ReplacementRegistry.java @@ -1,8 +1,10 @@ package thetadev.constructionwand.basics; -import net.minecraft.block.Block; -import net.minecraft.item.Item; -import net.minecraft.util.ResourceLocation; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraftforge.registries.ForgeRegistries; import thetadev.constructionwand.ConstructionWand; @@ -20,7 +22,7 @@ public class ReplacementRegistry for(String id : ((String) key).split(";")) { Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(id)); - if(item == null) { + if(item == null || item == Items.AIR) { ConstructionWand.LOGGER.warn("Replacement Registry: Could not find item " + id); continue; } @@ -42,6 +44,7 @@ public class ReplacementRegistry public static boolean matchBlocks(Block b1, Block b2) { if(b1 == b2) return true; + if(b1 == Blocks.AIR || b2 == Blocks.AIR) return false; for(HashSet set : replacements) { if(set.contains(b1.asItem()) && set.contains(b2.asItem())) return true; diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 6f8a2fe..6b4db45 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -1,46 +1,37 @@ package thetadev.constructionwand.basics; -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.entity.Entity; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.state.Property; -import net.minecraft.state.properties.BlockStateProperties; -import net.minecraft.state.properties.SlabType; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; import net.minecraft.stats.Stats; -import net.minecraft.util.Direction; -import net.minecraft.util.EntityPredicates; -import net.minecraft.util.Hand; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.AxisAlignedBB; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.shapes.VoxelShape; -import net.minecraft.util.math.vector.Vector3d; -import net.minecraft.world.World; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.BlockSnapshot; import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.wand.ItemWand; -import thetadev.constructionwand.wand.WandItemUseContext; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; public class WandUtil { public static boolean stackEquals(ItemStack stackA, ItemStack stackB) { - return ItemStack.areItemsEqual(stackA, stackB) && ItemStack.areItemStackTagsEqual(stackA, stackB); + return ItemStack.isSameItemSameTags(stackA, stackB); } public static boolean stackEquals(ItemStack stackA, Item item) { @@ -48,45 +39,45 @@ public class WandUtil return stackEquals(stackA, stackB); } - public static ItemStack holdingWand(PlayerEntity player) { - if(player.getHeldItem(Hand.MAIN_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.MAIN_HAND).getItem() instanceof ItemWand) { - return player.getHeldItem(Hand.MAIN_HAND); + public static ItemStack holdingWand(Player player) { + if(player.getItemInHand(InteractionHand.MAIN_HAND) != ItemStack.EMPTY && player.getItemInHand(InteractionHand.MAIN_HAND).getItem() instanceof ItemWand) { + return player.getItemInHand(InteractionHand.MAIN_HAND); } - else if(player.getHeldItem(Hand.OFF_HAND) != ItemStack.EMPTY && player.getHeldItem(Hand.OFF_HAND).getItem() instanceof ItemWand) { - return player.getHeldItem(Hand.OFF_HAND); + else if(player.getItemInHand(InteractionHand.OFF_HAND) != ItemStack.EMPTY && player.getItemInHand(InteractionHand.OFF_HAND).getItem() instanceof ItemWand) { + return player.getItemInHand(InteractionHand.OFF_HAND); } return null; } - public static BlockPos playerPos(PlayerEntity player) { - return new BlockPos(player.getPositionVec()); + public static BlockPos playerPos(Player player) { + return new BlockPos(player.position()); } - public static Vector3d entityPositionVec(Entity entity) { - return new Vector3d(entity.getPosX(), entity.getPosY() - entity.getYOffset() + entity.getHeight() / 2, entity.getPosZ()); + public static Vec3 entityPositionVec(Entity entity) { + return new Vec3(entity.getX(), entity.getY() - entity.getMyRidingOffset() + entity.getBbHeight() / 2, entity.getZ()); } - public static Vector3d blockPosVec(BlockPos pos) { - return new Vector3d(pos.getX(), pos.getY(), pos.getZ()); + public static Vec3 blockPosVec(BlockPos pos) { + return new Vec3(pos.getX(), pos.getY(), pos.getZ()); } - public static List getHotbar(PlayerEntity player) { - return player.inventory.mainInventory.subList(0, 9); + public static List getHotbar(Player player) { + return player.getInventory().items.subList(0, 9); } - public static List getHotbarWithOffhand(PlayerEntity player) { - ArrayList inventory = new ArrayList<>(player.inventory.mainInventory.subList(0, 9)); - inventory.addAll(player.inventory.offHandInventory); + public static List getHotbarWithOffhand(Player player) { + ArrayList inventory = new ArrayList<>(player.getInventory().items.subList(0, 9)); + inventory.addAll(player.getInventory().offhand); return inventory; } - public static List getMainInv(PlayerEntity player) { - return player.inventory.mainInventory.subList(9, player.inventory.mainInventory.size()); + public static List getMainInv(Player player) { + return player.getInventory().items.subList(9, player.getInventory().items.size()); } - public static List getFullInv(PlayerEntity player) { - ArrayList inventory = new ArrayList<>(player.inventory.offHandInventory); - inventory.addAll(player.inventory.mainInventory); + public static List getFullInv(Player player) { + ArrayList inventory = new ArrayList<>(player.getInventory().offhand); + inventory.addAll(player.getInventory().items); return inventory; } @@ -95,7 +86,7 @@ public class WandUtil } public static boolean isTEAllowed(BlockState state) { - if(!state.hasTileEntity()) return true; + if(!state.hasBlockEntity()) return true; ResourceLocation name = state.getBlock().getRegistryName(); if(name == null) return false; @@ -109,14 +100,14 @@ public class WandUtil return isWhitelist == inList; } - public static boolean placeBlock(World world, PlayerEntity player, BlockState block, BlockPos pos, @Nullable BlockItem item) { - if(!world.setBlockState(pos, block)) { + public static boolean placeBlock(Level world, Player player, BlockState block, BlockPos pos, @Nullable BlockItem item) { + if(!world.setBlockAndUpdate(pos, block)) { ConstructionWand.LOGGER.info("Block could not be placed"); return false; } // Remove block if placeEvent is canceled - BlockSnapshot snapshot = BlockSnapshot.create(world.func_234923_W_(), world, pos); + BlockSnapshot snapshot = BlockSnapshot.create(world.dimension(), world, pos); BlockEvent.EntityPlaceEvent placeEvent = new BlockEvent.EntityPlaceEvent(snapshot, block, player); MinecraftForge.EVENT_BUS.post(placeEvent); if(placeEvent.isCanceled()) { @@ -128,22 +119,22 @@ public class WandUtil if(item == null) stack = new ItemStack(block.getBlock().asItem()); else { stack = new ItemStack(item); - player.addStat(Stats.ITEM_USED.get(item)); + player.awardStat(Stats.ITEM_USED.get(item)); } // Call OnBlockPlaced method - block.getBlock().onBlockPlacedBy(world, pos, block, player, stack); + block.getBlock().setPlacedBy(world, pos, block, player, stack); return true; } - public static boolean removeBlock(World world, PlayerEntity player, @Nullable BlockState block, BlockPos pos) { + public static boolean removeBlock(Level world, Player player, @Nullable BlockState block, BlockPos pos) { BlockState currentBlock = world.getBlockState(pos); - if(!world.isBlockModifiable(player, pos)) return false; + if(!world.mayInteract(player, pos)) return false; if(!player.isCreative()) { - if(currentBlock.getBlockHardness(world, pos) <= -1 || world.getTileEntity(pos) != null) return false; + if(currentBlock.getDestroySpeed(world, pos) <= -1 || world.getBlockEntity(pos) != null) return false; if(block != null) if(!ReplacementRegistry.matchBlocks(currentBlock.getBlock(), block.getBlock())) return false; @@ -157,8 +148,8 @@ public class WandUtil return true; } - public static int countItem(PlayerEntity player, Item item) { - if(player.inventory == null || player.inventory.mainInventory == null) return 0; + public static int countItem(Player player, Item item) { + if(player.getInventory().items == null) return 0; if(player.isCreative()) return Integer.MAX_VALUE; int total = 0; @@ -180,28 +171,16 @@ public class WandUtil return total; } - public static boolean matchBlocks(WandOptions options, Block b1, Block b2) { - switch(options.match.get()) { - case EXACT: - return b1 == b2; - case SIMILAR: - return ReplacementRegistry.matchBlocks(b1, b2); - case ANY: - return b1 != Blocks.AIR && b2 != Blocks.AIR; - } - return false; - } - - private static boolean isPositionModifiable(World world, PlayerEntity player, BlockPos pos) { + private static boolean isPositionModifiable(Level world, Player player, BlockPos pos) { // Is position out of world? - if(!world.isBlockPresent(pos)) return false; + if(!world.isInWorldBounds(pos)) return false; // Is block modifiable? - if(!world.isBlockModifiable(player, pos)) return false; + if(!world.mayInteract(player, pos)) return false; // Limit range if(ConfigServer.MAX_RANGE.get() > 0 && - WandUtil.blockDistance(player.getPosition(), pos) > ConfigServer.MAX_RANGE.get()) return false; + WandUtil.blockDistance(player.blockPosition(), pos) > ConfigServer.MAX_RANGE.get()) return false; return true; } @@ -210,32 +189,32 @@ public class WandUtil * Tests if a wand can place a block at a certain position. * This check is independent from the used block. */ - public static boolean isPositionPlaceable(World world, PlayerEntity player, BlockPos pos, boolean replace) { - if(!isPositionModifiable(world,player, pos)) return false; + public static boolean isPositionPlaceable(Level world, Player player, BlockPos pos, boolean replace) { + if(!isPositionModifiable(world, player, pos)) return false; // If replace mode is off, target has to be air - return replace || world.isAirBlock(pos); + return replace || world.isEmptyBlock(pos); } - public static boolean isBlockRemovable(World world, PlayerEntity player, BlockPos pos) { - if(!isPositionModifiable(world,player, pos)) return false; + public static boolean isBlockRemovable(Level world, Player player, BlockPos pos) { + if(!isPositionModifiable(world, player, pos)) return false; if(!player.isCreative()) { - return !(world.getBlockState(pos).getBlockHardness(world, pos) <= -1) && world.getTileEntity(pos) == null; + return !(world.getBlockState(pos).getDestroySpeed(world, pos) <= -1) && world.getBlockEntity(pos) == null; } return true; } - public static boolean entitiesCollidingWithBlock(World world, BlockState blockState, BlockPos pos) { + public static boolean entitiesCollidingWithBlock(Level world, BlockState blockState, BlockPos pos) { VoxelShape shape = blockState.getCollisionShape(world, pos); if(!shape.isEmpty()) { - AxisAlignedBB blockBB = shape.getBoundingBox().offset(pos); - return !world.getEntitiesWithinAABB(LivingEntity.class, blockBB, EntityPredicates.NOT_SPECTATING).isEmpty(); + AABB blockBB = shape.bounds().move(pos); + return !world.getEntitiesOfClass(LivingEntity.class, blockBB, Predicate.not(Entity::isSpectator)).isEmpty(); } return false; } - public static Direction fromVector(Vector3d vector) { - return Direction.getFacingFromVector(vector.x, vector.y, vector.z); + public static Direction fromVector(Vec3 vector) { + return Direction.getNearest(vector.x, vector.y, vector.z); } } diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java b/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java index dfb5d00..6823509 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionBoolean.java @@ -1,15 +1,15 @@ package thetadev.constructionwand.basics.option; -import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.CompoundTag; public class OptionBoolean implements IOption { - private final CompoundNBT tag; + private final CompoundTag tag; private final String key; private final boolean enabled; private boolean value; - public OptionBoolean(CompoundNBT tag, String key, boolean dval, boolean enabled) { + public OptionBoolean(CompoundTag tag, String key, boolean dval, boolean enabled) { this.tag = tag; this.key = key; this.enabled = enabled; @@ -18,7 +18,7 @@ public class OptionBoolean implements IOption else value = dval; } - public OptionBoolean(CompoundNBT tag, String key, boolean dval) { + public OptionBoolean(CompoundTag tag, String key, boolean dval) { this(tag, key, dval, true); } diff --git a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java index 7042554..5c79dcd 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java +++ b/src/main/java/thetadev/constructionwand/basics/option/OptionEnum.java @@ -1,18 +1,18 @@ package thetadev.constructionwand.basics.option; import com.google.common.base.Enums; -import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.CompoundTag; public class OptionEnum> implements IOption { - private final CompoundNBT tag; + private final CompoundTag tag; private final String key; private final Class enumClass; private final boolean enabled; private final E dval; private E value; - public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval, boolean enabled) { + public OptionEnum(CompoundTag tag, String key, Class enumClass, E dval, boolean enabled) { this.tag = tag; this.key = key; this.enumClass = enumClass; @@ -22,7 +22,7 @@ public class OptionEnum> implements IOption value = Enums.getIfPresent(enumClass, tag.getString(key).toUpperCase()).or(dval); } - public OptionEnum(CompoundNBT tag, String key, Class enumClass, E dval) { + public OptionEnum(CompoundTag tag, String key, Class enumClass, E dval) { this(tag, key, enumClass, dval, true); } diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java index 0cb920f..2a9b39e 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandOptions.java @@ -1,9 +1,9 @@ package thetadev.constructionwand.basics.option; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import thetadev.constructionwand.api.IWandCore; import thetadev.constructionwand.api.IWandUpgrade; import thetadev.constructionwand.basics.ReplacementRegistry; @@ -13,7 +13,7 @@ import javax.annotation.Nullable; public class WandOptions { - public final CompoundNBT tag; + public final CompoundTag tag; private static final String TAG_ROOT = "wand_options"; @@ -50,7 +50,7 @@ public class WandOptions public final IOption[] allOptions; public WandOptions(ItemStack wandStack) { - tag = wandStack.getOrCreateChildTag(TAG_ROOT); + tag = wandStack.getOrCreateTagElement(TAG_ROOT); cores = new WandUpgradesSelectable<>(tag, "cores", new CoreDefault()); diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java b/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java index 19626c2..043907d 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java @@ -1,10 +1,10 @@ package thetadev.constructionwand.basics.option; -import net.minecraft.item.Item; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.nbt.ListNBT; -import net.minecraft.nbt.StringNBT; -import net.minecraft.util.ResourceLocation; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; import net.minecraftforge.common.util.Constants; import net.minecraftforge.registries.ForgeRegistries; import thetadev.constructionwand.ConstructionWand; @@ -14,12 +14,12 @@ import java.util.ArrayList; public class WandUpgrades { - protected final CompoundNBT tag; + protected final CompoundTag tag; protected final String key; protected final ArrayList upgrades; protected final T dval; - public WandUpgrades(CompoundNBT tag, String key, T dval) { + public WandUpgrades(CompoundTag tag, String key, T dval) { this.tag = tag; this.key = key; this.dval = dval; @@ -31,7 +31,7 @@ public class WandUpgrades } protected void deserialize() { - ListNBT listnbt = tag.getList(key, Constants.NBT.TAG_STRING); + ListTag listnbt = tag.getList(key, Constants.NBT.TAG_STRING); boolean require_fix = false; for(int i = 0; i < listnbt.size(); i++) { @@ -52,11 +52,11 @@ public class WandUpgrades } protected void serialize() { - ListNBT listnbt = new ListNBT(); + ListTag listnbt = new ListTag(); for(T item : upgrades) { if(item == dval) continue; - listnbt.add(StringNBT.valueOf(item.getRegistryName().toString())); + listnbt.add(StringTag.valueOf(item.getRegistryName().toString())); } tag.put(key, listnbt); } diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java b/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java index 8d80eda..3fec9a6 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandUpgradesSelectable.java @@ -1,13 +1,13 @@ package thetadev.constructionwand.basics.option; -import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.CompoundTag; import thetadev.constructionwand.api.IWandUpgrade; public class WandUpgradesSelectable extends WandUpgrades implements IOption { private byte selector; - public WandUpgradesSelectable(CompoundNBT tag, String key, T dval) { + public WandUpgradesSelectable(CompoundTag tag, String key, T dval) { super(tag, key, dval); } diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 954ace1..8cc184f 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -1,9 +1,9 @@ package thetadev.constructionwand.client; +import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.Minecraft; -import net.minecraft.client.util.InputMappings; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraftforge.client.event.InputEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.eventbus.api.EventPriority; @@ -27,7 +27,7 @@ public class ClientEvents // Send state of OPT key to server @SubscribeEvent public void KeyEvent(InputEvent.KeyInputEvent event) { - PlayerEntity player = Minecraft.getInstance().player; + Player player = Minecraft.getInstance().player; if(player == null) return; if(WandUtil.holdingWand(player) == null) return; @@ -43,7 +43,7 @@ public class ClientEvents // Sneak+(OPT)+Scroll to change direction lock @SubscribeEvent(priority = EventPriority.HIGHEST) public void MouseScrollEvent(InputEvent.MouseScrollEvent event) { - PlayerEntity player = Minecraft.getInstance().player; + Player player = Minecraft.getInstance().player; double scroll = event.getScrollDelta(); if(player == null || !modeKeyCombDown(player) || scroll == 0) return; @@ -60,7 +60,7 @@ public class ClientEvents // Sneak+(OPT)+Left click wand to change core @SubscribeEvent public void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { - PlayerEntity player = event.getPlayer(); + Player player = event.getPlayer(); if(player == null || !modeKeyCombDown(player)) return; @@ -75,29 +75,29 @@ public class ClientEvents // Sneak+(OPT)+Right click wand to open GUI @SubscribeEvent public void onRightClickItem(PlayerInteractEvent.RightClickItem event) { - PlayerEntity player = event.getPlayer(); + Player player = event.getPlayer(); if(player == null || !guiKeyCombDown(player)) return; ItemStack wand = event.getItemStack(); if(!(wand.getItem() instanceof ItemWand)) return; - Minecraft.getInstance().displayGuiScreen(new ScreenWand(wand)); + Minecraft.getInstance().setScreen(new ScreenWand(wand)); event.setCanceled(true); } private static boolean isKeyDown(int id) { - return InputMappings.isKeyDown(Minecraft.getInstance().getMainWindow().getHandle(), id); + return InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), id); } public static boolean isOptKeyDown() { return isKeyDown(ConfigClient.OPT_KEY.get()); } - public static boolean modeKeyCombDown(PlayerEntity player) { - return player.isSneaking() && (isOptKeyDown() || !ConfigClient.SHIFTOPT_MODE.get()); + public static boolean modeKeyCombDown(Player player) { + return player.isCrouching() && (isOptKeyDown() || !ConfigClient.SHIFTOPT_MODE.get()); } - public static boolean guiKeyCombDown(PlayerEntity player) { - return player.isSneaking() && (isOptKeyDown() || !ConfigClient.SHIFTOPT_GUI.get()); + public static boolean guiKeyCombDown(Player player) { + return player.isCrouching() && (isOptKeyDown() || !ConfigClient.SHIFTOPT_GUI.get()); } } diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index d381c60..1fe6b07 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -1,17 +1,18 @@ package thetadev.constructionwand.client; -import com.mojang.blaze3d.matrix.MatrixStack; -import com.mojang.blaze3d.vertex.IVertexBuilder; -import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.WorldRenderer; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.AxisAlignedBB; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.RayTraceResult; -import net.minecraftforge.client.event.DrawHighlightEvent; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraftforge.client.event.DrawSelectionEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.items.wand.ItemWand; @@ -25,22 +26,21 @@ public class RenderBlockPreview public Set undoBlocks; @SubscribeEvent - public void renderBlockHighlight(DrawHighlightEvent event) { - if(event.getTarget().getType() != RayTraceResult.Type.BLOCK) return; + public void renderBlockHighlight(DrawSelectionEvent.HighlightBlock event) { + if(event.getTarget().getType() != HitResult.Type.BLOCK) return; - BlockRayTraceResult rtr = (BlockRayTraceResult) event.getTarget(); - Entity entity = event.getInfo().getRenderViewEntity(); - if(!(entity instanceof PlayerEntity)) return; - PlayerEntity player = (PlayerEntity) entity; + BlockHitResult rtr = event.getTarget(); + Entity entity = event.getInfo().getEntity(); + if(!(entity instanceof Player player)) return; Set blocks; float colorR = 0, colorG = 0, colorB = 0; ItemStack wand = WandUtil.holdingWand(player); if(wand == null) return; - if(!(player.isSneaking() && ClientEvents.isOptKeyDown())) { + if(!(player.isCrouching() && ClientEvents.isOptKeyDown())) { if(wandJob == null || !compareRTR(wandJob.rayTraceResult, rtr) || !(wandJob.wand.equals(wand))) { - wandJob = ItemWand.getWandJob(player, player.getEntityWorld(), rtr, wand); + wandJob = ItemWand.getWandJob(player, player.level, rtr, wand); } blocks = wandJob.getBlockPositions(); } @@ -51,27 +51,24 @@ public class RenderBlockPreview if(blocks == null || blocks.isEmpty()) return; - MatrixStack ms = event.getMatrix(); - IRenderTypeBuffer buffer = event.getBuffers(); - IVertexBuilder lineBuilder = buffer.getBuffer(RenderTypes.TRANSLUCENT_LINES); + PoseStack ms = event.getMatrix(); + MultiBufferSource buffer = event.getBuffers(); + VertexConsumer lineBuilder = buffer.getBuffer(RenderType.LINES); double partialTicks = event.getPartialTicks(); - double d0 = player.lastTickPosX + (player.getPosX() - player.lastTickPosX) * partialTicks; - double d1 = player.lastTickPosY + player.getEyeHeight() + (player.getPosY() - player.lastTickPosY) * partialTicks; - double d2 = player.lastTickPosZ + (player.getPosZ() - player.lastTickPosZ) * partialTicks; - - ms.push(); + double d0 = player.xOld + (player.getX() - player.xOld) * partialTicks; + double d1 = player.yOld + player.getEyeHeight() + (player.getY() - player.yOld) * partialTicks; + double d2 = player.zOld + (player.getZ() - player.zOld) * partialTicks; for(BlockPos block : blocks) { - AxisAlignedBB aabb = new AxisAlignedBB(block).offset(-d0, -d1, -d2); - WorldRenderer.drawBoundingBox(ms, lineBuilder, aabb, colorR, colorG, colorB, 0.4F); + AABB aabb = new AABB(block).move(-d0, -d1, -d2); + LevelRenderer.renderLineBox(ms, lineBuilder, aabb, colorR, colorG, colorB, 0.4F); } - ms.pop(); event.setCanceled(true); } - private static boolean compareRTR(BlockRayTraceResult rtr1, BlockRayTraceResult rtr2) { - return rtr1.getPos().equals(rtr2.getPos()) && rtr1.getFace().equals(rtr2.getFace()); + private static boolean compareRTR(BlockHitResult rtr1, BlockHitResult rtr2) { + return rtr1.getBlockPos().equals(rtr2.getBlockPos()) && rtr1.getDirection().equals(rtr2.getDirection()); } } diff --git a/src/main/java/thetadev/constructionwand/client/RenderTypes.java b/src/main/java/thetadev/constructionwand/client/RenderTypes.java deleted file mode 100644 index c901bd3..0000000 --- a/src/main/java/thetadev/constructionwand/client/RenderTypes.java +++ /dev/null @@ -1,37 +0,0 @@ -package thetadev.constructionwand.client; - -import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.renderer.RenderState; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import org.lwjgl.opengl.GL11; -import thetadev.constructionwand.ConstructionWand; - -import java.util.OptionalDouble; - -public class RenderTypes -{ - public static final RenderType TRANSLUCENT_LINES; - - protected static final RenderState.TransparencyState TRANSLUCENT_TRANSPARENCY = new RenderState.TransparencyState("translucent_transparency", () -> { - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - }, RenderSystem::disableBlend); - protected static final RenderState.DepthTestState DEPTH_ALWAYS = new RenderState.DepthTestState("always", GL11.GL_ALWAYS); - - static { - RenderType.State translucentNoDepthState = RenderType.State.getBuilder().transparency(TRANSLUCENT_TRANSPARENCY) - .line(new RenderState.LineState(OptionalDouble.of(2))) - .texture(new RenderState.TextureState()) - .depthTest(DEPTH_ALWAYS) - .build(false); - - TRANSLUCENT_LINES = RenderType.makeType( - ConstructionWand.MODID + ":translucent_lines", - DefaultVertexFormats.POSITION_COLOR, - GL11.GL_LINES, - 256, - translucentNoDepthState - ); - } -} diff --git a/src/main/java/thetadev/constructionwand/client/ScreenWand.java b/src/main/java/thetadev/constructionwand/client/ScreenWand.java index b987e49..a746bab 100644 --- a/src/main/java/thetadev/constructionwand/client/ScreenWand.java +++ b/src/main/java/thetadev/constructionwand/client/ScreenWand.java @@ -1,13 +1,12 @@ package thetadev.constructionwand.client; -import com.mojang.blaze3d.matrix.MatrixStack; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.button.Button; -import net.minecraft.item.ItemStack; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.StringTextComponent; -import net.minecraft.util.text.TranslationTextComponent; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.item.ItemStack; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; @@ -31,15 +30,13 @@ public class ScreenWand extends Screen private static final int FIELD_HEIGHT = N_ROWS * (BUTTON_HEIGHT + SPACING_HEIGHT) - SPACING_HEIGHT; public ScreenWand(ItemStack wand) { - super(new StringTextComponent("ScreenWand")); + super(new TextComponent("ScreenWand")); this.wand = wand; wandOptions = new WandOptions(wand); } @Override - public void init(@Nonnull Minecraft minecraft, int width, int height) { - super.init(minecraft, width, height); - + protected void init() { createButton(0, 0, wandOptions.cores); createButton(0, 1, wandOptions.lock); createButton(0, 2, wandOptions.direction); @@ -49,22 +46,22 @@ public class ScreenWand extends Screen } @Override - public void render(@Nonnull MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { + public void render(@Nonnull PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) { renderBackground(matrixStack); - super.render(matrixStack, mouseX, mouseY, partialTicks); drawCenteredString(matrixStack, font, wand.getDisplayName(), width / 2, height / 2 - FIELD_HEIGHT / 2 - SPACING_HEIGHT, 16777215); + super.render(matrixStack, mouseX, mouseY, partialTicks); } @Override public boolean charTyped(char character, int code) { - if(character == 'e') closeScreen(); + if(character == 'e') onClose(); return super.charTyped(character, code); } private void createButton(int cx, int cy, IOption option) { Button button = new Button(getX(cx), getY(cy), BUTTON_WIDTH, BUTTON_HEIGHT, getButtonLabel(option), bt -> clickButton(bt, option), (bt, ms, x, y) -> drawTooltip(ms, x, y, option)); button.active = option.isEnabled(); - addButton(button); + addRenderableWidget(button); } private void clickButton(Button button, IOption option) { @@ -73,9 +70,9 @@ public class ScreenWand extends Screen button.setMessage(getButtonLabel(option)); } - private void drawTooltip(MatrixStack matrixStack, int mouseX, int mouseY, IOption option) { + private void drawTooltip(PoseStack matrixStack, int mouseX, int mouseY, IOption option) { if(isMouseOver(mouseX, mouseY)) { - renderTooltip(matrixStack, new TranslationTextComponent(option.getDescTranslation()), mouseX, mouseY); + renderTooltip(matrixStack, new TranslatableComponent(option.getDescTranslation()), mouseX, mouseY); } } @@ -87,7 +84,7 @@ public class ScreenWand extends Screen return height / 2 - FIELD_HEIGHT / 2 + n * (BUTTON_HEIGHT + SPACING_HEIGHT); } - private ITextComponent getButtonLabel(IOption option) { - return new TranslationTextComponent(option.getKeyTranslation()).append(new TranslationTextComponent(option.getValueTranslation())); + private Component getButtonLabel(IOption option) { + return new TranslatableComponent(option.getKeyTranslation()).append(new TranslatableComponent(option.getValueTranslation())); } } diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerManager.java b/src/main/java/thetadev/constructionwand/containers/ContainerManager.java index f7283af..ab2ee9a 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerManager.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerManager.java @@ -1,7 +1,7 @@ package thetadev.constructionwand.containers; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import thetadev.constructionwand.api.IContainerHandler; import java.util.ArrayList; @@ -18,7 +18,7 @@ public class ContainerManager return handlers.add(handler); } - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { for(IContainerHandler handler : handlers) { if(handler.matches(player, itemStack, inventoryStack)) { return handler.countItems(player, itemStack, inventoryStack); @@ -27,7 +27,7 @@ public class ContainerManager return 0; } - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { + public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { for(IContainerHandler handler : handlers) { if(handler.matches(player, itemStack, inventoryStack)) { return handler.useItems(player, itemStack, inventoryStack, count); diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java index 42642f6..6996fd2 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java @@ -1,8 +1,6 @@ package thetadev.constructionwand.containers; -import net.minecraftforge.fml.ModList; import thetadev.constructionwand.ConstructionWand; -import thetadev.constructionwand.containers.handlers.HandlerBotania; import thetadev.constructionwand.containers.handlers.HandlerCapability; import thetadev.constructionwand.containers.handlers.HandlerShulkerbox; @@ -12,9 +10,13 @@ public class ContainerRegistrar ConstructionWand.instance.containerManager.register(new HandlerCapability()); ConstructionWand.instance.containerManager.register(new HandlerShulkerbox()); + /* + TODO: Reenable this when Botania gets ported to 1.17 + if(ModList.get().isLoaded("botania")) { ConstructionWand.instance.containerManager.register(new HandlerBotania()); ConstructionWand.LOGGER.info("Botania integration added"); } + */ } } diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java index 359cb67..2d41e4b 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java @@ -1,35 +1,36 @@ +/* +TODO: Reenable this when Botania gets ported to 1.17 + package thetadev.constructionwand.containers.handlers; -import net.minecraft.block.Block; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import thetadev.constructionwand.api.IContainerHandler; import vazkii.botania.api.item.IBlockProvider; -/** - * Created by james on 28/12/16. - */ public class HandlerBotania implements IContainerHandler { @Override - public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + public boolean matches(Player player, ItemStack itemStack, ItemStack inventoryStack) { return inventoryStack != null && inventoryStack.getCount() == 1 && inventoryStack.getItem() instanceof IBlockProvider; } @Override - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { IBlockProvider prov = (IBlockProvider) inventoryStack.getItem(); - int provCount = prov.getBlockCount(player, itemStack, inventoryStack, Block.getBlockFromItem(itemStack.getItem())); + int provCount = prov.getBlockCount(player, itemStack, inventoryStack, Block.byItem(itemStack.getItem())); if(provCount == -1) return Integer.MAX_VALUE; return provCount; } @Override - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { + public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { IBlockProvider prov = (IBlockProvider) inventoryStack.getItem(); - if(prov.provideBlock(player, itemStack, inventoryStack, Block.getBlockFromItem(itemStack.getItem()), true)) + if(prov.provideBlock(player, itemStack, inventoryStack, Block.byItem(itemStack.getItem()), true)) return 0; return count; } } +*/ \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java index c5c8b51..a968655 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java @@ -1,11 +1,12 @@ package thetadev.constructionwand.containers.handlers; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.items.CapabilityItemHandler; import net.minecraftforge.items.IItemHandler; import thetadev.constructionwand.api.IContainerHandler; +import thetadev.constructionwand.basics.WandUtil; /** * Created by james on 28/12/16. @@ -13,22 +14,22 @@ import thetadev.constructionwand.api.IContainerHandler; public class HandlerCapability implements IContainerHandler { @Override - public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + public boolean matches(Player player, ItemStack itemStack, ItemStack inventoryStack) { return inventoryStack != null && inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).isPresent(); } @Override - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { LazyOptional itemHandlerLazyOptional = inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); if(!itemHandlerLazyOptional.isPresent()) return 0; int total = 0; - IItemHandler itemHandler = itemHandlerLazyOptional.orElse(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.getDefaultInstance()); + IItemHandler itemHandler = itemHandlerLazyOptional.orElse((IItemHandler) CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); for(int i = 0; i < itemHandler.getSlots(); i++) { ItemStack containerStack = itemHandler.getStackInSlot(i); - if(containerStack != null && itemStack.isItemEqual(containerStack)) { + if(WandUtil.stackEquals(itemStack, containerStack)) { total += Math.max(0, containerStack.getCount()); } @@ -38,24 +39,20 @@ public class HandlerCapability implements IContainerHandler } @Override - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { + public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { int toUse = itemStack.getCount(); LazyOptional itemHandlerLazyOptional = inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); if(!itemHandlerLazyOptional.isPresent()) return 0; - IItemHandler itemHandler = itemHandlerLazyOptional.orElse(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.getDefaultInstance()); + IItemHandler itemHandler = itemHandlerLazyOptional.orElse((IItemHandler) CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); for(int i = 0; i < itemHandler.getSlots(); i++) { ItemStack handlerStack = itemHandler.getStackInSlot(i); - if(handlerStack != null && handlerStack.isItemEqual(itemStack)) { + if(WandUtil.stackEquals(itemStack, handlerStack)) { ItemStack extracted = itemHandler.extractItem(i, count, false); - if(extracted != null) { - count -= extracted.getCount(); - } - if(count <= 0) { - break; - } + count -= extracted.getCount(); + if(count <= 0) break; } } return count; diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java index 601a9bd..329d6a8 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java @@ -1,12 +1,12 @@ package thetadev.constructionwand.containers.handlers; -import net.minecraft.block.Block; -import net.minecraft.block.ShulkerBoxBlock; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.inventory.ItemStackHelper; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.util.NonNullList; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.ShulkerBoxBlock; import net.minecraftforge.common.util.Constants; import thetadev.constructionwand.api.IContainerHandler; import thetadev.constructionwand.basics.WandUtil; @@ -16,12 +16,12 @@ public class HandlerShulkerbox implements IContainerHandler private final int SLOTS = 27; @Override - public boolean matches(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { - return inventoryStack != null && inventoryStack.getCount() == 1 && Block.getBlockFromItem(inventoryStack.getItem()) instanceof ShulkerBoxBlock; + public boolean matches(Player player, ItemStack itemStack, ItemStack inventoryStack) { + return inventoryStack != null && inventoryStack.getCount() == 1 && Block.byItem(inventoryStack.getItem()) instanceof ShulkerBoxBlock; } @Override - public int countItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack) { + public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { int count = 0; for(ItemStack stack : getItemList(inventoryStack)) { @@ -32,7 +32,7 @@ public class HandlerShulkerbox implements IContainerHandler } @Override - public int useItems(PlayerEntity player, ItemStack itemStack, ItemStack inventoryStack, int count) { + public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { NonNullList itemList = getItemList(inventoryStack); boolean changed = false; @@ -47,7 +47,7 @@ public class HandlerShulkerbox implements IContainerHandler } if(changed) { setItemList(inventoryStack, itemList); - player.inventory.markDirty(); + player.getInventory().setChanged(); } return count; @@ -55,21 +55,21 @@ public class HandlerShulkerbox implements IContainerHandler private NonNullList getItemList(ItemStack itemStack) { NonNullList itemStacks = NonNullList.withSize(SLOTS, ItemStack.EMPTY); - CompoundNBT rootTag = itemStack.getTag(); + CompoundTag rootTag = itemStack.getTag(); if(rootTag != null && rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { - CompoundNBT entityTag = rootTag.getCompound("BlockEntityTag"); + CompoundTag entityTag = rootTag.getCompound("BlockEntityTag"); if(entityTag.contains("Items", Constants.NBT.TAG_LIST)) { - ItemStackHelper.loadAllItems(entityTag, itemStacks); + ContainerHelper.loadAllItems(entityTag, itemStacks); } } return itemStacks; } private void setItemList(ItemStack itemStack, NonNullList itemStacks) { - CompoundNBT rootTag = itemStack.getOrCreateTag(); + CompoundTag rootTag = itemStack.getOrCreateTag(); if(!rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { - rootTag.put("BlockEntityTag", new CompoundNBT()); + rootTag.put("BlockEntityTag", new CompoundTag()); } - ItemStackHelper.saveAllItems(rootTag.getCompound("BlockEntityTag"), itemStacks); + ContainerHelper.saveAllItems(rootTag.getCompound("BlockEntityTag"), itemStacks); } } diff --git a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java index 5e15f3e..7fafdaf 100644 --- a/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java +++ b/src/main/java/thetadev/constructionwand/crafting/RecipeWandUpgrade.java @@ -1,12 +1,12 @@ package thetadev.constructionwand.crafting; -import net.minecraft.inventory.CraftingInventory; -import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.IRecipeSerializer; -import net.minecraft.item.crafting.SpecialRecipe; -import net.minecraft.item.crafting.SpecialRecipeSerializer; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.World; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.SimpleRecipeSerializer; +import net.minecraft.world.level.Level; import thetadev.constructionwand.api.IWandUpgrade; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.option.WandOptions; @@ -14,21 +14,21 @@ import thetadev.constructionwand.items.wand.ItemWand; import javax.annotation.Nonnull; -public class RecipeWandUpgrade extends SpecialRecipe +public class RecipeWandUpgrade extends CustomRecipe { - public static final SpecialRecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>(RecipeWandUpgrade::new); + public static final SimpleRecipeSerializer SERIALIZER = new SimpleRecipeSerializer<>(RecipeWandUpgrade::new); public RecipeWandUpgrade(ResourceLocation resourceLocation) { super(resourceLocation); } @Override - public boolean matches(@Nonnull CraftingInventory inv, @Nonnull World worldIn) { + public boolean matches(@Nonnull CraftingContainer inv, @Nonnull Level worldIn) { ItemStack wand = null; IWandUpgrade upgrade = null; - for(int i = 0; i < inv.getSizeInventory(); i++) { - ItemStack stack = inv.getStackInSlot(i); + for(int i = 0; i < inv.getContainerSize(); i++) { + ItemStack stack = inv.getItem(i); if(!stack.isEmpty()) { if(wand == null && stack.getItem() instanceof ItemWand) wand = stack; else if(upgrade == null && stack.getItem() instanceof IWandUpgrade) @@ -43,12 +43,12 @@ public class RecipeWandUpgrade extends SpecialRecipe @Nonnull @Override - public ItemStack getCraftingResult(@Nonnull CraftingInventory inv) { + public ItemStack assemble(@Nonnull CraftingContainer inv) { ItemStack wand = null; IWandUpgrade upgrade = null; - for(int i = 0; i < inv.getSizeInventory(); i++) { - ItemStack stack = inv.getStackInSlot(i); + for(int i = 0; i < inv.getContainerSize(); i++) { + ItemStack stack = inv.getItem(i); if(!stack.isEmpty()) { if(stack.getItem() instanceof ItemWand) wand = stack; else if(stack.getItem() instanceof IWandUpgrade) upgrade = (IWandUpgrade) stack.getItem(); @@ -63,13 +63,13 @@ public class RecipeWandUpgrade extends SpecialRecipe } @Override - public boolean canFit(int width, int height) { + public boolean canCraftInDimensions(int width, int height) { return width * height >= 2; } @Nonnull @Override - public IRecipeSerializer getSerializer() { + public RecipeSerializer getSerializer() { return SERIALIZER; } } diff --git a/src/main/java/thetadev/constructionwand/data/Inp.java b/src/main/java/thetadev/constructionwand/data/Inp.java index cab5dbf..d85270a 100644 --- a/src/main/java/thetadev/constructionwand/data/Inp.java +++ b/src/main/java/thetadev/constructionwand/data/Inp.java @@ -1,10 +1,10 @@ package thetadev.constructionwand.data; -import net.minecraft.advancements.criterion.ItemPredicate; -import net.minecraft.item.Item; -import net.minecraft.item.crafting.Ingredient; -import net.minecraft.tags.ITag; -import net.minecraft.util.IItemProvider; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.tags.Tag; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; public class Inp { @@ -18,11 +18,11 @@ public class Inp this.predicate = predicate; } - public static Inp fromItem(IItemProvider in) { - return new Inp(in.asItem().getRegistryName().getPath(), Ingredient.fromItems(in), ItemPredicate.Builder.create().item(in).build()); + public static Inp fromItem(ItemLike in) { + return new Inp(in.asItem().getRegistryName().getPath(), Ingredient.of(in), ItemPredicate.Builder.item().of(in).build()); } - public static Inp fromTag(ITag.INamedTag in) { - return new Inp(in.getName().getPath(), Ingredient.fromTag(in), ItemPredicate.Builder.create().tag(in).build()); + public static Inp fromTag(Tag.Named in) { + return new Inp(in.getName().getPath(), Ingredient.of(in), ItemPredicate.Builder.item().of(in).build()); } } \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java index 507bc4b..b224ac8 100644 --- a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java @@ -1,8 +1,8 @@ package thetadev.constructionwand.data; import net.minecraft.data.DataGenerator; -import net.minecraft.item.BlockItem; -import net.minecraft.item.Item; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; import net.minecraftforge.client.model.generators.ItemModelProvider; import net.minecraftforge.common.data.ExistingFileHelper; import thetadev.constructionwand.ConstructionWand; diff --git a/src/main/java/thetadev/constructionwand/data/ModData.java b/src/main/java/thetadev/constructionwand/data/ModData.java index 45ccd4a..e389e41 100644 --- a/src/main/java/thetadev/constructionwand/data/ModData.java +++ b/src/main/java/thetadev/constructionwand/data/ModData.java @@ -4,7 +4,7 @@ import net.minecraft.data.DataGenerator; import net.minecraftforge.common.data.ExistingFileHelper; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; +import net.minecraftforge.forge.event.lifecycle.GatherDataEvent; @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) public class ModData diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index 649fcce..4407958 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -1,13 +1,17 @@ package thetadev.constructionwand.data; -import net.minecraft.data.*; -import net.minecraft.item.Items; -import net.minecraft.item.crafting.SpecialRecipeSerializer; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeProvider; +import net.minecraft.data.recipes.ShapedRecipeBuilder; +import net.minecraft.data.recipes.SpecialRecipeBuilder; +import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.ItemTags; -import net.minecraft.util.IItemProvider; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.registry.Registry; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.SimpleRecipeSerializer; +import net.minecraft.world.level.ItemLike; import net.minecraftforge.common.Tags; +import net.minecraftforge.registries.ForgeRegistries; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.crafting.RecipeWandUpgrade; import thetadev.constructionwand.items.ModItems; @@ -22,8 +26,8 @@ public class RecipeGenerator extends RecipeProvider } @Override - protected void registerRecipes(@Nonnull Consumer consumer) { - wandRecipe(consumer, ModItems.WAND_STONE, Inp.fromTag(ItemTags.field_232909_aa_)); //stone_tool_materials + protected void buildCraftingRecipes(@Nonnull Consumer consumer) { + wandRecipe(consumer, ModItems.WAND_STONE, Inp.fromTag(ItemTags.STONE_TOOL_MATERIALS)); wandRecipe(consumer, ModItems.WAND_IRON, Inp.fromTag(Tags.Items.INGOTS_IRON)); wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); @@ -34,32 +38,32 @@ public class RecipeGenerator extends RecipeProvider specialRecipe(consumer, RecipeWandUpgrade.SERIALIZER); } - private void wandRecipe(Consumer consumer, IItemProvider wand, Inp material) { - ShapedRecipeBuilder.shapedRecipe(wand) - .key('X', material.ingredient) - .key('#', Tags.Items.RODS_WOODEN) - .patternLine(" X") - .patternLine(" # ") - .patternLine("# ") - .addCriterion("has_item", hasItem(material.predicate)) - .build(consumer); + private void wandRecipe(Consumer consumer, ItemLike wand, Inp material) { + ShapedRecipeBuilder.shaped(wand) + .define('X', material.ingredient) + .define('#', Tags.Items.RODS_WOODEN) + .pattern(" X") + .pattern(" # ") + .pattern("# ") + .unlockedBy("has_item", inventoryTrigger(material.predicate)) + .save(consumer); } - private void coreRecipe(Consumer consumer, IItemProvider core, Inp item1, Inp item2) { - ShapedRecipeBuilder.shapedRecipe(core) - .key('O', item1.ingredient) - .key('X', item2.ingredient) - .key('#', Tags.Items.GLASS_PANES) - .patternLine(" #X") - .patternLine("#O#") - .patternLine("X# ") - .addCriterion("has_item", hasItem(item1.predicate)) - .build(consumer); + private void coreRecipe(Consumer consumer, ItemLike core, Inp item1, Inp item2) { + ShapedRecipeBuilder.shaped(core) + .define('O', item1.ingredient) + .define('X', item2.ingredient) + .define('#', Tags.Items.GLASS_PANES) + .pattern(" #X") + .pattern("#O#") + .pattern("X# ") + .unlockedBy("has_item", inventoryTrigger(item1.predicate)) + .save(consumer); } - private void specialRecipe(Consumer consumer, SpecialRecipeSerializer serializer) { - ResourceLocation name = Registry.RECIPE_SERIALIZER.getKey(serializer); - CustomRecipeBuilder.customRecipe(serializer).build(consumer, ConstructionWand.loc("dynamic/" + name.getPath()).toString()); + private void specialRecipe(Consumer consumer, SimpleRecipeSerializer serializer) { + ResourceLocation name = ForgeRegistries.RECIPE_SERIALIZERS.getKey(serializer); + SpecialRecipeBuilder.special(serializer).save(consumer, ConstructionWand.loc("dynamic/" + name.getPath()).toString()); } @Nonnull diff --git a/src/main/java/thetadev/constructionwand/items/ItemBase.java b/src/main/java/thetadev/constructionwand/items/ItemBase.java index ef40066..f47d0dc 100644 --- a/src/main/java/thetadev/constructionwand/items/ItemBase.java +++ b/src/main/java/thetadev/constructionwand/items/ItemBase.java @@ -1,6 +1,6 @@ package thetadev.constructionwand.items; -import net.minecraft.item.Item; +import net.minecraft.world.item.Item; import thetadev.constructionwand.ConstructionWand; public class ItemBase extends Item diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 43408dd..10d7916 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -1,12 +1,12 @@ package thetadev.constructionwand.items; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.color.ItemColors; -import net.minecraft.item.Item; -import net.minecraft.item.ItemGroup; -import net.minecraft.item.ItemModelsProperties; -import net.minecraft.item.ItemTier; -import net.minecraft.item.crafting.IRecipeSerializer; +import net.minecraft.client.color.item.ItemColors; +import net.minecraft.client.renderer.item.ItemProperties; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Tiers; +import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.RegistryEvent; @@ -30,9 +30,9 @@ import java.util.HashSet; public class ModItems { // Wands - public static final Item WAND_STONE = new ItemWandBasic("stone_wand", propWand(), ItemTier.STONE); - public static final Item WAND_IRON = new ItemWandBasic("iron_wand", propWand(), ItemTier.IRON); - public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", propWand(), ItemTier.DIAMOND); + public static final Item WAND_STONE = new ItemWandBasic("stone_wand", propWand(), Tiers.STONE); + public static final Item WAND_IRON = new ItemWandBasic("iron_wand", propWand(), Tiers.IRON); + public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", propWand(), Tiers.DIAMOND); public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand", propWand()); // Cores @@ -56,11 +56,11 @@ public class ModItems } public static Item.Properties propWand() { - return new Item.Properties().group(ItemGroup.TOOLS); + return new Item.Properties().tab(CreativeModeTab.TAB_TOOLS); } private static Item.Properties propUpgrade() { - return new Item.Properties().group(ItemGroup.MISC).maxStackSize(1); + return new Item.Properties().tab(CreativeModeTab.TAB_MISC).stacksTo(1); } private static void registerItem(IForgeRegistry reg, Item item) { @@ -69,17 +69,17 @@ public class ModItems } @SubscribeEvent - public static void registerRecipeSerializers(RegistryEvent.Register> event) { - IForgeRegistry> r = event.getRegistry(); + public static void registerRecipeSerializers(RegistryEvent.Register> event) { + IForgeRegistry> r = event.getRegistry(); register(r, "wand_upgrade", RecipeWandUpgrade.SERIALIZER); } @OnlyIn(Dist.CLIENT) public static void registerModelProperties() { for(Item item : WANDS) { - ItemModelsProperties.func_239418_a_( + ItemProperties.register( item, ConstructionWand.loc("using_core"), - (stack, world, entity) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : + (stack, world, entity, n) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : new WandOptions(stack).cores.get().getColor() > -1 ? 1 : 0 ); } diff --git a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java index 4053eee..02924af 100644 --- a/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java +++ b/src/main/java/thetadev/constructionwand/items/core/CoreDefault.java @@ -1,6 +1,6 @@ package thetadev.constructionwand.items.core; -import net.minecraft.util.ResourceLocation; +import net.minecraft.resources.ResourceLocation; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandCore; diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCore.java b/src/main/java/thetadev/constructionwand/items/core/ItemCore.java index 4df952d..fb3b93b 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCore.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCore.java @@ -1,11 +1,11 @@ package thetadev.constructionwand.items.core; -import net.minecraft.client.util.ITooltipFlag; -import net.minecraft.item.ItemStack; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TextFormatting; -import net.minecraft.util.text.TranslationTextComponent; -import net.minecraft.world.World; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import thetadev.constructionwand.ConstructionWand; @@ -22,10 +22,10 @@ public abstract class ItemCore extends ItemBase implements IWandCore } @OnlyIn(Dist.CLIENT) - public void addInformation(@Nonnull ItemStack itemstack, World worldIn, @Nonnull List lines, @Nonnull ITooltipFlag extraInfo) { - lines.add(new TranslationTextComponent(ConstructionWand.MODID + ".option.cores." + getRegistryName().toString() + ".desc") - .mergeStyle(TextFormatting.GRAY)); - lines.add(new TranslationTextComponent(ConstructionWand.MODID + ".tooltip.core_tip") - .mergeStyle(TextFormatting.AQUA)); + public void appendHoverText(@Nonnull ItemStack itemstack, Level worldIn, @Nonnull List lines, @Nonnull TooltipFlag extraInfo) { + lines.add(new TranslatableComponent(ConstructionWand.MODID + ".option.cores." + getRegistryName().toString() + ".desc") + .withStyle(ChatFormatting.GRAY)); + lines.add(new TranslatableComponent(ConstructionWand.MODID + ".tooltip.core_tip") + .withStyle(ChatFormatting.AQUA)); } } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 6cc75a9..539c96d 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -1,20 +1,20 @@ package thetadev.constructionwand.items.wand; -import net.minecraft.block.BlockState; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.util.ITooltipFlag; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemUseContext; -import net.minecraft.util.ActionResult; -import net.minecraft.util.ActionResultType; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.StringTextComponent; -import net.minecraft.util.text.TextFormatting; -import net.minecraft.util.text.TranslationTextComponent; -import net.minecraft.world.World; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.model.generators.ModelFile; @@ -40,41 +40,41 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel @Nonnull @Override - public ActionResultType onItemUse(ItemUseContext context) { - PlayerEntity player = context.getPlayer(); - Hand hand = context.getHand(); - World world = context.getWorld(); + public InteractionResult useOn(UseOnContext context) { + Player player = context.getPlayer(); + InteractionHand hand = context.getHand(); + Level world = context.getLevel(); - if(world.isRemote || player == null) return ActionResultType.FAIL; + if(world.isClientSide || player == null) return InteractionResult.FAIL; - ItemStack stack = player.getHeldItem(hand); + ItemStack stack = player.getItemInHand(hand); - if(player.isSneaking() && ConstructionWand.instance.undoHistory.isUndoActive(player)) { - return ConstructionWand.instance.undoHistory.undo(player, world, context.getPos()) ? ActionResultType.SUCCESS : ActionResultType.FAIL; + if(player.isCrouching() && ConstructionWand.instance.undoHistory.isUndoActive(player)) { + return ConstructionWand.instance.undoHistory.undo(player, world, context.getClickedPos()) ? InteractionResult.SUCCESS : InteractionResult.FAIL; } else { - WandJob job = getWandJob(player, world, new BlockRayTraceResult(context.getHitVec(), context.getFace(), context.getPos(), false), stack); - return job.doIt() ? ActionResultType.SUCCESS : ActionResultType.FAIL; + WandJob job = getWandJob(player, world, new BlockHitResult(context.getClickLocation(), context.getClickedFace(), context.getClickedPos(), false), stack); + return job.doIt() ? InteractionResult.SUCCESS : InteractionResult.FAIL; } } @Nonnull @Override - public ActionResult onItemRightClick(@Nonnull World world, PlayerEntity player, @Nonnull Hand hand) { - ItemStack stack = player.getHeldItem(hand); + public InteractionResultHolder use(@Nonnull Level world, Player player, @Nonnull InteractionHand hand) { + ItemStack stack = player.getItemInHand(hand); - if(!player.isSneaking()) { - if(world.isRemote) return ActionResult.resultFail(stack); + if(!player.isCrouching()) { + if(world.isClientSide) return InteractionResultHolder.fail(stack); // Right click: Place angel block - WandJob job = getWandJob(player, world, BlockRayTraceResult.createMiss(player.getLookVec(), - WandUtil.fromVector(player.getLookVec()), WandUtil.playerPos(player)), stack); - return job.doIt() ? ActionResult.resultSuccess(stack) : ActionResult.resultFail(stack); + WandJob job = getWandJob(player, world, BlockHitResult.miss(player.getLookAngle(), + WandUtil.fromVector(player.getLookAngle()), WandUtil.playerPos(player)), stack); + return job.doIt() ? InteractionResultHolder.success(stack) : InteractionResultHolder.fail(stack); } - return ActionResult.resultFail(stack); + return InteractionResultHolder.fail(stack); } - public static WandJob getWandJob(PlayerEntity player, World world, @Nullable BlockRayTraceResult rayTraceResult, ItemStack wand) { + public static WandJob getWandJob(Player player, Level world, @Nullable BlockHitResult rayTraceResult, ItemStack wand) { WandJob wandJob = new WandJob(player, world, rayTraceResult, wand); wandJob.getSnapshots(); @@ -82,12 +82,12 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel } @Override - public boolean canHarvestBlock(@Nonnull BlockState blockIn) { + public boolean isCorrectToolForDrops(@Nonnull BlockState blockIn) { return false; } @Override - public boolean getIsRepairable(@Nonnull ItemStack toRepair, @Nonnull ItemStack repair) { + public boolean isValidRepairItem(@Nonnull ItemStack toRepair, @Nonnull ItemStack repair) { return false; } @@ -95,8 +95,9 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel return Integer.MAX_VALUE; } + @Override @OnlyIn(Dist.CLIENT) - public void addInformation(@Nonnull ItemStack itemstack, World worldIn, @Nonnull List lines, @Nonnull ITooltipFlag extraInfo) { + public void appendHoverText(@Nonnull ItemStack itemstack, Level worldIn, @Nonnull List lines, @Nonnull TooltipFlag extraInfo) { WandOptions options = new WandOptions(itemstack); int limit = options.cores.get().getWandAction().getLimit(itemstack); @@ -106,35 +107,36 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel if(Screen.hasShiftDown()) { for(int i = 1; i < options.allOptions.length; i++) { IOption opt = options.allOptions[i]; - lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.GRAY)) + lines.add(new TranslatableComponent(opt.getKeyTranslation()).withStyle(ChatFormatting.AQUA) + .append(new TranslatableComponent(opt.getValueTranslation()).withStyle(ChatFormatting.GRAY)) ); } if(!options.cores.getUpgrades().isEmpty()) { - lines.add(new StringTextComponent("")); - lines.add(new TranslationTextComponent(langTooltip + "cores").mergeStyle(TextFormatting.GRAY)); + lines.add(new TextComponent("")); + lines.add(new TranslatableComponent(langTooltip + "cores").withStyle(ChatFormatting.GRAY)); for(IWandCore core : options.cores.getUpgrades()) { - lines.add(new TranslationTextComponent(options.cores.getKeyTranslation() + "." + core.getRegistryName().toString())); + lines.add(new TranslatableComponent(options.cores.getKeyTranslation() + "." + core.getRegistryName().toString())); } } } // Default tooltip: show block limit + active wand core else { IOption opt = options.allOptions[0]; - lines.add(new TranslationTextComponent(langTooltip + "blocks", limit).mergeStyle(TextFormatting.GRAY)); - lines.add(new TranslationTextComponent(opt.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(opt.getValueTranslation()).mergeStyle(TextFormatting.WHITE))); - lines.add(new TranslationTextComponent(langTooltip + "shift").mergeStyle(TextFormatting.AQUA)); + lines.add(new TranslatableComponent(langTooltip + "blocks", limit).withStyle(ChatFormatting.GRAY)); + lines.add(new TranslatableComponent(opt.getKeyTranslation()).withStyle(ChatFormatting.AQUA) + .append(new TranslatableComponent(opt.getValueTranslation()).withStyle(ChatFormatting.WHITE))); + lines.add(new TranslatableComponent(langTooltip + "shift").withStyle(ChatFormatting.AQUA)); } } - public static void optionMessage(PlayerEntity player, IOption option) { - player.sendStatusMessage( - new TranslationTextComponent(option.getKeyTranslation()).mergeStyle(TextFormatting.AQUA) - .append(new TranslationTextComponent(option.getValueTranslation()).mergeStyle(TextFormatting.WHITE)) - .append(new StringTextComponent(" - ").mergeStyle(TextFormatting.GRAY)) - .append(new TranslationTextComponent(option.getDescTranslation()).mergeStyle(TextFormatting.WHITE)) + @OnlyIn(Dist.CLIENT) + public static void optionMessage(Player player, IOption option) { + player.displayClientMessage( + new TranslatableComponent(option.getKeyTranslation()).withStyle(ChatFormatting.AQUA) + .append(new TranslatableComponent(option.getValueTranslation()).withStyle(ChatFormatting.WHITE)) + .append(new TextComponent(" - ").withStyle(ChatFormatting.GRAY)) + .append(new TranslatableComponent(option.getDescTranslation()).withStyle(ChatFormatting.WHITE)) , true); } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java index 66b66a8..518954b 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java @@ -1,17 +1,17 @@ package thetadev.constructionwand.items.wand; -import net.minecraft.item.IItemTier; -import net.minecraft.item.ItemStack; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Tier; import thetadev.constructionwand.basics.ConfigServer; import javax.annotation.Nonnull; public class ItemWandBasic extends ItemWand { - private final IItemTier tier; + private final Tier tier; - public ItemWandBasic(String name, Properties properties, IItemTier tier) { - super(name, properties.maxDamage(tier.getMaxUses())); + public ItemWandBasic(String name, Properties properties, Tier tier) { + super(name, properties.durability(tier.getUses())); this.tier = tier; } @@ -22,11 +22,11 @@ public class ItemWandBasic extends ItemWand @Override public int remainingDurability(ItemStack stack) { - return stack.getMaxDamage() - stack.getDamage(); + return stack.getMaxDamage() - stack.getDamageValue(); } @Override - public boolean getIsRepairable(@Nonnull ItemStack toRepair, @Nonnull ItemStack repair) { - return this.tier.getRepairMaterial().test(repair); + public boolean isValidRepairItem(@Nonnull ItemStack toRepair, @Nonnull ItemStack repair) { + return this.tier.getRepairIngredient().test(repair); } } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java index f53cb39..8907001 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java @@ -4,6 +4,6 @@ package thetadev.constructionwand.items.wand; public class ItemWandInfinity extends ItemWand { public ItemWandInfinity(String name, Properties properties) { - super(name, properties.maxStackSize(1).isBurnable()); + super(name, properties.stacksTo(1).fireResistant()); } } diff --git a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java index 0c17d5b..488b89e 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java +++ b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java @@ -1,8 +1,8 @@ package thetadev.constructionwand.network; -import net.minecraft.entity.player.ServerPlayerEntity; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.NetworkEvent; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.fmllegacy.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; import java.util.function.Supplier; @@ -15,11 +15,11 @@ public class PacketQueryUndo this.undoPressed = undoPressed; } - public static void encode(PacketQueryUndo msg, PacketBuffer buffer) { + public static void encode(PacketQueryUndo msg, FriendlyByteBuf buffer) { buffer.writeBoolean(msg.undoPressed); } - public static PacketQueryUndo decode(PacketBuffer buffer) { + public static PacketQueryUndo decode(FriendlyByteBuf buffer) { return new PacketQueryUndo(buffer.readBoolean()); } @@ -28,7 +28,7 @@ public class PacketQueryUndo public static void handle(final PacketQueryUndo msg, final Supplier ctx) { if(!ctx.get().getDirection().getReceptionSide().isServer()) return; - ServerPlayerEntity player = ctx.get().getSender(); + ServerPlayer player = ctx.get().getSender(); if(player == null) return; ConstructionWand.instance.undoHistory.updateClient(player, msg.undoPressed); diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index 7d831e9..b6ca550 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -1,8 +1,8 @@ package thetadev.constructionwand.network; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.math.BlockPos; -import net.minecraftforge.fml.network.NetworkEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.fmllegacy.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; import java.util.HashSet; @@ -21,13 +21,13 @@ public class PacketUndoBlocks this.undoBlocks = undoBlocks; } - public static void encode(PacketUndoBlocks msg, PacketBuffer buffer) { + public static void encode(PacketUndoBlocks msg, FriendlyByteBuf buffer) { for(BlockPos pos : msg.undoBlocks) { buffer.writeBlockPos(pos); } } - public static PacketUndoBlocks decode(PacketBuffer buffer) { + public static PacketUndoBlocks decode(FriendlyByteBuf buffer) { HashSet undoBlocks = new HashSet<>(); while(buffer.isReadable()) { diff --git a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java index cf20b15..e2bde2e 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java +++ b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java @@ -1,9 +1,9 @@ package thetadev.constructionwand.network; -import net.minecraft.entity.player.ServerPlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.NetworkEvent; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fmllegacy.network.NetworkEvent; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; @@ -27,14 +27,14 @@ public class PacketWandOption this.notify = notify; } - public static void encode(PacketWandOption msg, PacketBuffer buffer) { - buffer.writeString(msg.key); - buffer.writeString(msg.value); + public static void encode(PacketWandOption msg, FriendlyByteBuf buffer) { + buffer.writeUtf(msg.key); + buffer.writeUtf(msg.value); buffer.writeBoolean(msg.notify); } - public static PacketWandOption decode(PacketBuffer buffer) { - return new PacketWandOption(buffer.readString(100), buffer.readString(100), buffer.readBoolean()); + public static PacketWandOption decode(FriendlyByteBuf buffer) { + return new PacketWandOption(buffer.readUtf(100), buffer.readUtf(100), buffer.readBoolean()); } public static class Handler @@ -42,7 +42,7 @@ public class PacketWandOption public static void handle(final PacketWandOption msg, final Supplier ctx) { if(!ctx.get().getDirection().getReceptionSide().isServer()) return; - ServerPlayerEntity player = ctx.get().getSender(); + ServerPlayer player = ctx.get().getSender(); if(player == null) return; ItemStack wand = WandUtil.holdingWand(player); @@ -54,7 +54,7 @@ public class PacketWandOption option.setValueString(msg.value); if(msg.notify) ItemWand.optionMessage(player, option); - player.inventory.markDirty(); + player.getInventory().setChanged(); } } } diff --git a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java index b8c08f7..b1d5f50 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java +++ b/src/main/java/thetadev/constructionwand/wand/WandItemUseContext.java @@ -1,27 +1,27 @@ package thetadev.constructionwand.wand; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.vector.Vector3d; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; import thetadev.constructionwand.basics.WandUtil; -public class WandItemUseContext extends BlockItemUseContext +public class WandItemUseContext extends BlockPlaceContext { - public WandItemUseContext(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, BlockPos pos, BlockItem item) { - super(world, player, Hand.MAIN_HAND, new ItemStack(item), - new BlockRayTraceResult(getBlockHitVec(rayTraceResult, pos), rayTraceResult.getFace(), pos, false)); + public WandItemUseContext(Level world, Player player, BlockHitResult rayTraceResult, BlockPos pos, BlockItem item) { + super(world, player, InteractionHand.MAIN_HAND, new ItemStack(item), + new BlockHitResult(getBlockHitVec(rayTraceResult, pos), rayTraceResult.getDirection(), pos, false)); } - private static Vector3d getBlockHitVec(BlockRayTraceResult rayTraceResult, BlockPos pos) { - Vector3d hitVec = rayTraceResult.getHitVec(); // Absolute coords of hit target + private static Vec3 getBlockHitVec(BlockHitResult rayTraceResult, BlockPos pos) { + Vec3 hitVec = rayTraceResult.getLocation(); // Absolute coords of hit target - Vector3d blockDelta = WandUtil.blockPosVec(rayTraceResult.getPos()).subtract(WandUtil.blockPosVec(pos)); // Vector between start and current block + Vec3 blockDelta = WandUtil.blockPosVec(rayTraceResult.getBlockPos()).subtract(WandUtil.blockPosVec(pos)); // Vector between start and current block return blockDelta.add(hitVec); // Absolute coords of current block hit target } diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index edcb566..52cdbf3 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -1,15 +1,16 @@ package thetadev.constructionwand.wand; -import net.minecraft.block.SoundType; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.util.SoundCategory; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; @@ -31,9 +32,9 @@ import java.util.stream.Collectors; public class WandJob { - public final PlayerEntity player; - public final World world; - public final BlockRayTraceResult rayTraceResult; + public final Player player; + public final Level world; + public final BlockHitResult rayTraceResult; public final WandOptions options; public final ItemStack wand; public final ItemWand wandItem; @@ -43,7 +44,7 @@ public class WandJob private List placeSnapshots; - public WandJob(PlayerEntity player, World world, BlockRayTraceResult rayTraceResult, ItemStack wand) { + public WandJob(Player player, Level world, BlockHitResult rayTraceResult, ItemStack wand) { this.player = player; this.world = world; this.rayTraceResult = rayTraceResult; @@ -63,9 +64,9 @@ public class WandJob } @Nullable - private static BlockItem getTargetItem(World world, BlockRayTraceResult rayTraceResult) { + private static BlockItem getTargetItem(Level world, BlockHitResult rayTraceResult) { // Get target item - Item tgitem = world.getBlockState(rayTraceResult.getPos()).getBlock().asItem(); + Item tgitem = world.getBlockState(rayTraceResult.getBlockPos()).getBlock().asItem(); if(!(tgitem instanceof BlockItem)) return null; return (BlockItem) tgitem; } @@ -76,7 +77,7 @@ public class WandJob if(player.isCreative() && wandItem == ModItems.WAND_INFINITY) limit = ConfigServer.LIMIT_CREATIVE.get(); else limit = Math.min(wandItem.remainingDurability(wand), wandAction.getLimit(wand)); - if(rayTraceResult.getType() == RayTraceResult.Type.BLOCK) + if(rayTraceResult.getType() == HitResult.Type.BLOCK) placeSnapshots = wandAction.getSnapshots(world, player, rayTraceResult, wand, options, wandSupplier, limit); else placeSnapshots = wandAction.getSnapshotsFromAir(world, player, rayTraceResult, wand, options, wandSupplier, limit); @@ -101,8 +102,8 @@ public class WandJob snapshot.forceRestore(world); } - wand.damageItem(1, player, (e) -> e.sendBreakAnimation(player.swingingHand)); - player.addStat(ModStats.USE_WAND); + wand.hurt(1, player.getRandom(), (ServerPlayer) player); + player.awardStat(ModStats.USE_WAND); } } placeSnapshots = executed; @@ -110,7 +111,7 @@ public class WandJob // Play place sound if(!placeSnapshots.isEmpty()) { SoundType sound = placeSnapshots.get(0).getBlockState().getSoundType(); - world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundCategory.BLOCKS, sound.volume, sound.pitch); + world.playSound(null, WandUtil.playerPos(player), sound.getPlaceSound(), SoundSource.BLOCKS, sound.volume, sound.pitch); // Add to job history for undo ConstructionWand.instance.undoHistory.add(player, world, placeSnapshots); diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java index f333d99..36033ab 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionAngel.java @@ -1,13 +1,13 @@ package thetadev.constructionwand.wand.action; -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.util.math.vector.Vector3d; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; @@ -29,16 +29,16 @@ public class ActionAngel implements IWandAction @Nonnull @Override - public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public List getSnapshots(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList placeSnapshots = new LinkedList<>(); - Direction placeDirection = rayTraceResult.getFace(); - BlockPos currentPos = rayTraceResult.getPos(); + Direction placeDirection = rayTraceResult.getDirection(); + BlockPos currentPos = rayTraceResult.getBlockPos(); BlockState supportingBlock = world.getBlockState(currentPos); for(int i = 0; i < limit; i++) { - currentPos = currentPos.offset(placeDirection.getOpposite()); + currentPos = currentPos.offset(placeDirection.getOpposite().getNormal()); PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentPos, rayTraceResult, supportingBlock); if(snapshot != null) { @@ -51,15 +51,15 @@ public class ActionAngel implements IWandAction @Nonnull @Override - public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public List getSnapshotsFromAir(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList placeSnapshots = new LinkedList<>(); if(!player.isCreative() && !ConfigServer.ANGEL_FALLING.get() && player.fallDistance > 10) return placeSnapshots; - Vector3d playerVec = WandUtil.entityPositionVec(player); - Vector3d lookVec = player.getLookVec().mul(2, 2, 2); - Vector3d placeVec = playerVec.add(lookVec); + Vec3 playerVec = WandUtil.entityPositionVec(player); + Vec3 lookVec = player.getLookAngle().multiply(2, 2, 2); + Vec3 placeVec = playerVec.add(lookVec); BlockPos currentPos = new BlockPos(placeVec); diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java index bbd1cf1..c842ea0 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionConstruction.java @@ -1,16 +1,15 @@ package thetadev.constructionwand.wand.action; -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.ISnapshot; import thetadev.constructionwand.wand.undo.PlaceSnapshot; @@ -33,15 +32,15 @@ public class ActionConstruction implements IWandAction @Nonnull @Override - public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public List getSnapshots(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList placeSnapshots = new LinkedList<>(); LinkedList candidates = new LinkedList<>(); HashSet allCandidates = new HashSet<>(); - Direction placeDirection = rayTraceResult.getFace(); - BlockState targetBlock = world.getBlockState(rayTraceResult.getPos()); - BlockPos startingPoint = rayTraceResult.getPos().offset(placeDirection); + Direction placeDirection = rayTraceResult.getDirection(); + BlockState targetBlock = world.getBlockState(rayTraceResult.getBlockPos()); + BlockPos startingPoint = rayTraceResult.getBlockPos().offset(placeDirection.getNormal()); // Is place direction allowed by lock? if(placeDirection == Direction.UP || placeDirection == Direction.DOWN) { @@ -54,10 +53,10 @@ public class ActionConstruction implements IWandAction while(!candidates.isEmpty() && placeSnapshots.size() < limit) { BlockPos currentCandidate = candidates.removeFirst(); try { - BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite()); + BlockPos supportingPoint = currentCandidate.offset(placeDirection.getOpposite().getNormal()); BlockState candidateSupportingBlock = world.getBlockState(supportingPoint); - if(WandUtil.matchBlocks(options, targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && + if(options.matchBlocks(targetBlock.getBlock(), candidateSupportingBlock.getBlock()) && allCandidates.add(currentCandidate)) { PlaceSnapshot snapshot = supplier.getPlaceSnapshot(world, currentCandidate, rayTraceResult, candidateSupportingBlock); if(snapshot == null) continue; @@ -67,52 +66,52 @@ public class ActionConstruction implements IWandAction case DOWN: case UP: if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { - candidates.add(currentCandidate.offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal())); } if(options.testLock(WandOptions.LOCK.EASTWEST)) { - candidates.add(currentCandidate.offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.WEST.getNormal())); } if(options.testLock(WandOptions.LOCK.NORTHSOUTH) && options.testLock(WandOptions.LOCK.EASTWEST)) { - candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.WEST)); - candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal()).offset(Direction.WEST.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal()).offset(Direction.WEST.getNormal())); } break; case NORTH: case SOUTH: if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { - candidates.add(currentCandidate.offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.WEST.getNormal())); } if(options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP)); - candidates.add(currentCandidate.offset(Direction.DOWN)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal())); } if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.WEST)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.WEST.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.WEST.getNormal())); } break; case EAST: case WEST: if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { - candidates.add(currentCandidate.offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal())); } if(options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP)); - candidates.add(currentCandidate.offset(Direction.DOWN)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal())); } if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.SOUTH)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.SOUTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.SOUTH.getNormal())); } break; } @@ -127,7 +126,7 @@ public class ActionConstruction implements IWandAction @Nonnull @Override - public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public List getSnapshotsFromAir(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { return new ArrayList<>(); } diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java index 72340fd..335d908 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -1,16 +1,15 @@ package thetadev.constructionwand.wand.action; -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; -import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.DestroySnapshot; import thetadev.constructionwand.wand.undo.ISnapshot; @@ -30,7 +29,7 @@ public class ActionDestruction implements IWandAction @Nonnull @Override - public List getSnapshots(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public List getSnapshots(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { LinkedList destroySnapshots = new LinkedList<>(); // Current list of block positions to process @@ -39,10 +38,10 @@ public class ActionDestruction implements IWandAction HashSet allCandidates = new HashSet<>(); // Block face the wand was pointed at - Direction breakDirection = rayTraceResult.getFace(); + Direction breakDirection = rayTraceResult.getDirection(); // Block the wand was pointed at - BlockPos startingPoint = rayTraceResult.getPos(); - BlockState targetBlock = world.getBlockState(rayTraceResult.getPos()); + BlockPos startingPoint = rayTraceResult.getBlockPos(); + BlockState targetBlock = world.getBlockState(rayTraceResult.getBlockPos()); // Is break direction allowed by lock? // Tried to break blocks from top/bottom face, so the wand should allow breaking in NS/EW direction @@ -61,7 +60,7 @@ public class ActionDestruction implements IWandAction BlockState candidateBlock = world.getBlockState(currentCandidate); // If target and candidate blocks match and the current candidate has not been processed - if(WandUtil.matchBlocks(options, targetBlock.getBlock(), candidateBlock.getBlock()) && + if(options.matchBlocks(targetBlock.getBlock(), candidateBlock.getBlock()) && allCandidates.add(currentCandidate)) { DestroySnapshot snapshot = DestroySnapshot.get(world, player, currentCandidate); if(snapshot == null) continue; @@ -71,52 +70,52 @@ public class ActionDestruction implements IWandAction case DOWN: case UP: if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { - candidates.add(currentCandidate.offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal())); } if(options.testLock(WandOptions.LOCK.EASTWEST)) { - candidates.add(currentCandidate.offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.WEST.getNormal())); } if(options.testLock(WandOptions.LOCK.NORTHSOUTH) && options.testLock(WandOptions.LOCK.EASTWEST)) { - candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.NORTH).offset(Direction.WEST)); - candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.SOUTH).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal()).offset(Direction.WEST.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal()).offset(Direction.WEST.getNormal())); } break; case NORTH: case SOUTH: if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { - candidates.add(currentCandidate.offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.WEST.getNormal())); } if(options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP)); - candidates.add(currentCandidate.offset(Direction.DOWN)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal())); } if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.WEST)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.EAST)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.WEST)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.WEST.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.EAST.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.WEST.getNormal())); } break; case EAST: case WEST: if(options.testLock(WandOptions.LOCK.HORIZONTAL)) { - candidates.add(currentCandidate.offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.SOUTH.getNormal())); } if(options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP)); - candidates.add(currentCandidate.offset(Direction.DOWN)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal())); } if(options.testLock(WandOptions.LOCK.HORIZONTAL) && options.testLock(WandOptions.LOCK.VERTICAL)) { - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.UP).offset(Direction.SOUTH)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.NORTH)); - candidates.add(currentCandidate.offset(Direction.DOWN).offset(Direction.SOUTH)); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.UP.getNormal()).offset(Direction.SOUTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.NORTH.getNormal())); + candidates.add(currentCandidate.offset(Direction.DOWN.getNormal()).offset(Direction.SOUTH.getNormal())); } break; } @@ -131,7 +130,7 @@ public class ActionDestruction implements IWandAction @Nonnull @Override - public List getSnapshotsFromAir(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public List getSnapshotsFromAir(Level world, Player player, BlockHitResult rayTraceResult, ItemStack wand, WandOptions options, IWandSupplier supplier, int limit) { return new ArrayList<>(); } diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index 19105de..4e158ef 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -1,14 +1,14 @@ package thetadev.constructionwand.wand.supplier; -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ReplacementRegistry; @@ -29,20 +29,20 @@ import java.util.List; */ public class SupplierInventory implements IWandSupplier { - protected final PlayerEntity player; + protected final Player player; protected final WandOptions options; protected HashMap itemCounts; protected IPool itemPool; - public SupplierInventory(PlayerEntity player, WandOptions options) { + public SupplierInventory(Player player, WandOptions options) { this.player = player; this.options = options; } public void getSupply(@Nullable BlockItem target) { itemCounts = new LinkedHashMap<>(); - ItemStack offhandStack = player.getHeldItem(Hand.OFF_HAND); + ItemStack offhandStack = player.getItemInHand(InteractionHand.OFF_HAND); itemPool = new OrderedPool<>(); @@ -73,7 +73,7 @@ public class SupplierInventory implements IWandSupplier @Override @Nullable - public PlaceSnapshot getPlaceSnapshot(World world, BlockPos pos, BlockRayTraceResult rayTraceResult, + public PlaceSnapshot getPlaceSnapshot(Level world, BlockPos pos, BlockHitResult rayTraceResult, @Nullable BlockState supportingBlock) { if(!WandUtil.isPositionPlaceable(world, player, pos, options.replace.get())) return null; itemPool.reset(); @@ -104,7 +104,7 @@ public class SupplierInventory implements IWandSupplier int count = stack.getCount(); Item item = stack.getItem(); - if(player.inventory == null || player.inventory.mainInventory == null) return count; + if(player.getInventory().items == null) return count; if(player.isCreative()) return 0; List hotbar = WandUtil.getHotbarWithOffhand(player); @@ -135,7 +135,7 @@ public class SupplierInventory implements IWandSupplier int toTake = Math.min(count, stack.getCount()); stack.shrink(toTake); count -= toTake; - player.inventory.markDirty(); + player.getInventory().setChanged(); } } return count; diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java index 07343a4..8760aa4 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierRandom.java @@ -1,8 +1,8 @@ package thetadev.constructionwand.wand.supplier; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.ItemStack; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.basics.pool.RandomPool; @@ -12,7 +12,7 @@ import java.util.LinkedHashMap; public class SupplierRandom extends SupplierInventory { - public SupplierRandom(PlayerEntity player, WandOptions options) { + public SupplierRandom(Player player, WandOptions options) { super(player, options); } @@ -21,7 +21,7 @@ public class SupplierRandom extends SupplierInventory itemCounts = new LinkedHashMap<>(); // Random mode -> add all items from hotbar - itemPool = new RandomPool<>(player.getRNG()); + itemPool = new RandomPool<>(player.getRandom()); for(ItemStack stack : WandUtil.getHotbarWithOffhand(player)) { if(stack.getItem() instanceof BlockItem) addBlockItem((BlockItem) stack.getItem()); diff --git a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java index af62703..1606784 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/DestroySnapshot.java @@ -1,12 +1,12 @@ package thetadev.constructionwand.wand.undo; -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.fluid.Fluids; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.basics.WandUtil; import javax.annotation.Nullable; @@ -22,7 +22,7 @@ public class DestroySnapshot implements ISnapshot } @Nullable - public static DestroySnapshot get(World world, PlayerEntity player, BlockPos pos) { + public static DestroySnapshot get(Level world, Player player, BlockPos pos) { if(!WandUtil.isBlockRemovable(world, player, pos)) return null; return new DestroySnapshot(world.getBlockState(pos), pos); @@ -44,34 +44,34 @@ public class DestroySnapshot implements ISnapshot } @Override - public boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult) { + public boolean execute(Level world, Player player, BlockHitResult rayTraceResult) { return WandUtil.removeBlock(world, player, block, pos); } @Override - public boolean canRestore(World world, PlayerEntity player) { + public boolean canRestore(Level world, Player player) { // Is position out of world? - if(!world.isBlockPresent(pos)) return false; + if(!world.isInWorldBounds(pos)) return false; // Is block modifiable? - if(!world.isBlockModifiable(player, pos)) return false; + if(!world.mayInteract(player, pos)) return false; // Ignore blocks and entities when in creative if(player.isCreative()) return true; // Is block empty or fluid? - if(!world.isAirBlock(pos) && !world.getBlockState(pos).isReplaceable(Fluids.EMPTY)) return false; + if(!world.isEmptyBlock(pos) && !world.getBlockState(pos).canBeReplaced(Fluids.EMPTY)) return false; return !WandUtil.entitiesCollidingWithBlock(world, block, pos); } @Override - public boolean restore(World world, PlayerEntity player) { + public boolean restore(Level world, Player player) { return WandUtil.placeBlock(world, player, block, pos, null); } @Override - public void forceRestore(World world) { - world.setBlockState(pos, block); + public void forceRestore(Level world) { + world.setBlockAndUpdate(pos, block); } } diff --git a/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java index feec07d..c7ed971 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/ISnapshot.java @@ -1,11 +1,11 @@ package thetadev.constructionwand.wand.undo; -import net.minecraft.block.BlockState; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; public interface ISnapshot { @@ -15,11 +15,11 @@ public interface ISnapshot ItemStack getRequiredItems(); - boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult); + boolean execute(Level world, Player player, BlockHitResult rayTraceResult); - boolean canRestore(World world, PlayerEntity player); + boolean canRestore(Level world, Player player); - boolean restore(World world, PlayerEntity player); + boolean restore(Level world, Player player); - void forceRestore(World world); + void forceRestore(Level world); } diff --git a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index 63fae19..ca17038 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -1,18 +1,18 @@ package thetadev.constructionwand.wand.undo; -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.BlockItemUseContext; -import net.minecraft.item.ItemStack; -import net.minecraft.state.Property; -import net.minecraft.state.properties.BlockStateProperties; -import net.minecraft.state.properties.SlabType; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockRayTraceResult; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +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.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.WandItemUseContext; @@ -35,7 +35,7 @@ public class PlaceSnapshot implements ISnapshot this.targetMode = targetMode; } - public static PlaceSnapshot get(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, + public static PlaceSnapshot get(Level world, Player player, BlockHitResult rayTraceResult, BlockPos pos, BlockItem item, @Nullable BlockState supportingBlock, @Nullable WandOptions options) { boolean targetMode = options != null && supportingBlock != null && options.direction.get() == WandOptions.DIRECTION.TARGET; @@ -61,7 +61,7 @@ public class PlaceSnapshot implements ISnapshot } @Override - public boolean execute(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult) { + public boolean execute(Level world, Player player, BlockHitResult rayTraceResult) { // Recalculate PlaceBlockState, because other blocks might be placed nearby // Not doing this may cause game crashes (StackOverflowException) when placing lots of blocks // with changing orientation like panes, iron bars or redstone. @@ -71,17 +71,17 @@ public class PlaceSnapshot implements ISnapshot } @Override - public boolean canRestore(World world, PlayerEntity player) { + public boolean canRestore(Level world, Player player) { return true; } @Override - public boolean restore(World world, PlayerEntity player) { + public boolean restore(Level world, Player player) { return WandUtil.removeBlock(world, player, block, pos); } @Override - public void forceRestore(World world) { + public void forceRestore(Level world) { world.removeBlock(pos, false); } @@ -91,11 +91,11 @@ public class PlaceSnapshot implements ISnapshot */ @SuppressWarnings({"rawtypes", "unchecked"}) @Nullable - private static BlockState getPlaceBlockstate(World world, PlayerEntity player, BlockRayTraceResult rayTraceResult, - BlockPos pos, BlockItem item, - @Nullable BlockState supportingBlock, boolean targetMode) { + private static BlockState getPlaceBlockstate(Level world, Player player, BlockHitResult rayTraceResult, + BlockPos pos, BlockItem item, + @Nullable BlockState supportingBlock, boolean targetMode) { // Is block at pos replaceable? - BlockItemUseContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); + BlockPlaceContext ctx = new WandItemUseContext(world, player, rayTraceResult, pos, item); if(!ctx.canPlace()) return null; // Can block be placed? @@ -109,25 +109,27 @@ public class PlaceSnapshot implements ISnapshot if(WandUtil.entitiesCollidingWithBlock(world, blockState, pos)) return null; // Adjust blockstate to neighbors - blockState = Block.getValidBlockForPosition(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.isValidPosition(world, pos)) return null; + // TODO: verify that + blockState = Block.updateFromNeighbourShapes(blockState, world, pos); + if(blockState.getBlock() == Blocks.AIR || !blockState.canSurvive(world, pos)) return null; // Copy block properties from supporting block if(targetMode && supportingBlock != null) { // Block properties to be copied (alignment/rotation properties) for(Property property : new Property[]{ - BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_EXCEPT_UP, - BlockStateProperties.ROTATION_0_15, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) { + BlockStateProperties.HORIZONTAL_FACING, BlockStateProperties.FACING, BlockStateProperties.FACING_HOPPER, + BlockStateProperties.ROTATION_16, BlockStateProperties.AXIS, BlockStateProperties.HALF, BlockStateProperties.STAIRS_SHAPE}) { if(supportingBlock.hasProperty(property) && blockState.hasProperty(property)) { - blockState = blockState.with(property, supportingBlock.get(property)); + blockState = blockState.setValue(property, supportingBlock.getValue(property)); } } // Dont dupe double slabs if(supportingBlock.hasProperty(BlockStateProperties.SLAB_TYPE) && blockState.hasProperty(BlockStateProperties.SLAB_TYPE)) { - SlabType slabType = supportingBlock.get(BlockStateProperties.SLAB_TYPE); - if(slabType != SlabType.DOUBLE) blockState = blockState.with(BlockStateProperties.SLAB_TYPE, slabType); + SlabType slabType = supportingBlock.getValue(BlockStateProperties.SLAB_TYPE); + if(slabType != SlabType.DOUBLE) + blockState = blockState.setValue(BlockStateProperties.SLAB_TYPE, slabType); } } return blockState; diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index edf444c..5be6fdb 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -1,14 +1,14 @@ package thetadev.constructionwand.wand.undo; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.player.ServerPlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.util.SoundCategory; -import net.minecraft.util.SoundEvent; -import net.minecraft.util.SoundEvents; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import net.minecraftforge.fml.network.PacketDistributor; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraftforge.fmllegacy.network.PacketDistributor; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; @@ -25,23 +25,23 @@ public class UndoHistory history = new HashMap<>(); } - private PlayerEntry getEntryFromPlayer(PlayerEntity player) { - return history.computeIfAbsent(player.getUniqueID(), k -> new PlayerEntry()); + private PlayerEntry getEntryFromPlayer(Player player) { + return history.computeIfAbsent(player.getUUID(), k -> new PlayerEntry()); } - public void add(PlayerEntity player, World world, List placeSnapshots) { + public void add(Player player, Level world, List placeSnapshots) { LinkedList list = getEntryFromPlayer(player).entries; list.add(new HistoryEntry(placeSnapshots, world)); while(list.size() > ConfigServer.UNDO_HISTORY.get()) list.removeFirst(); } - public void removePlayer(PlayerEntity player) { - history.remove(player.getUniqueID()); + public void removePlayer(Player player) { + history.remove(player.getUUID()); } - public void updateClient(PlayerEntity player, boolean ctrlDown) { - World world = player.getEntityWorld(); - if(world.isRemote) return; + public void updateClient(Player player, boolean ctrlDown) { + Level world = player.level; + if(world.isClientSide) return; // Set state of CTRL key PlayerEntry playerEntry = getEntryFromPlayer(player); @@ -60,14 +60,14 @@ public class UndoHistory } PacketUndoBlocks packet = new PacketUndoBlocks(positions); - ConstructionWand.instance.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), packet); + ConstructionWand.instance.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), packet); } - public boolean isUndoActive(PlayerEntity player) { + public boolean isUndoActive(Player player) { return getEntryFromPlayer(player).undoActive; } - public boolean undo(PlayerEntity player, World world, BlockPos pos) { + public boolean undo(Player player, Level world, BlockPos pos) { // If CTRL key is not pressed, return PlayerEntry playerEntry = getEntryFromPlayer(player); if(!playerEntry.undoActive) return false; @@ -102,9 +102,9 @@ public class UndoHistory private static class HistoryEntry { public final List placeSnapshots; - public final World world; + public final Level world; - public HistoryEntry(List placeSnapshots, World world) { + public HistoryEntry(List placeSnapshots, Level world) { this.placeSnapshots = placeSnapshots; this.world = world; } @@ -119,12 +119,12 @@ public class UndoHistory if(positions.contains(pos)) return true; for(BlockPos p : positions) { - if(pos.withinDistance(p, 3)) return true; + if(pos.closerThan(p, 3)) return true; } return false; } - public boolean undo(PlayerEntity player) { + public boolean undo(Player player) { // Check first if all snapshots can be restored for(ISnapshot snapshot : placeSnapshots) { if(!snapshot.canRestore(world, player)) return false; @@ -133,16 +133,16 @@ public class UndoHistory if(snapshot.restore(world, player) && !player.isCreative()) { ItemStack stack = snapshot.getRequiredItems(); - if(!player.inventory.addItemStackToInventory(stack)) { - player.dropItem(stack, false); + if(!player.getInventory().add(stack)) { + player.drop(stack, false); } } } - player.inventory.markDirty(); + player.getInventory().setChanged(); // Play teleport sound - SoundEvent sound = SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT; - world.playSound(null, WandUtil.playerPos(player), sound, SoundCategory.PLAYERS, 1.0F, 1.0F); + SoundEvent sound = SoundEvents.CHORUS_FRUIT_TELEPORT; + world.playSound(null, WandUtil.playerPos(player), sound, SoundSource.PLAYERS, 1.0F, 1.0F); return true; } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index bcbc6c5..867d8b2 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,6 +1,6 @@ -modLoader="javafml" -loaderVersion="[33,)" -license="MIT License" +modLoader = "javafml" +loaderVersion = "[37,)" +license = "MIT License" [[mods]] modId="constructionwand" version="${file.jarVersion}" @@ -19,13 +19,13 @@ This is my first minecraft mod. May the odds be ever in your favor. ''' [[dependencies.constructionwand]] modId="forge" - mandatory=true - versionRange="[33,)" - ordering="NONE" +mandatory = true +versionRange = "[37,)" +ordering = "NONE" side="BOTH" [[dependencies.constructionwand]] modId="minecraft" - mandatory = true -versionRange = "[1.16.2, 1.17)" +mandatory = true +versionRange = "[1.17.1, 1.18)" ordering = "NONE" side="BOTH" From 45da4ad7a7f16584a377b822aa7761008965da86 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 21 Aug 2021 14:27:35 +0200 Subject: [PATCH 49/78] Add support for bundle Fix crash when opening wand GUI --- .../constructionwand/client/ClientEvents.java | 2 + .../containers/ContainerRegistrar.java | 2 + .../containers/handlers/HandlerBundle.java | 68 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/main/java/thetadev/constructionwand/containers/handlers/HandlerBundle.java diff --git a/src/main/java/thetadev/constructionwand/client/ClientEvents.java b/src/main/java/thetadev/constructionwand/client/ClientEvents.java index 8cc184f..a92bca2 100644 --- a/src/main/java/thetadev/constructionwand/client/ClientEvents.java +++ b/src/main/java/thetadev/constructionwand/client/ClientEvents.java @@ -75,6 +75,8 @@ public class ClientEvents // Sneak+(OPT)+Right click wand to open GUI @SubscribeEvent public void onRightClickItem(PlayerInteractEvent.RightClickItem event) { + if(event.getSide().isServer()) return; + Player player = event.getPlayer(); if(player == null || !guiKeyCombDown(player)) return; diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java index 6996fd2..4c3b8e6 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java @@ -1,6 +1,7 @@ package thetadev.constructionwand.containers; import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.containers.handlers.HandlerBundle; import thetadev.constructionwand.containers.handlers.HandlerCapability; import thetadev.constructionwand.containers.handlers.HandlerShulkerbox; @@ -9,6 +10,7 @@ public class ContainerRegistrar public static void register() { ConstructionWand.instance.containerManager.register(new HandlerCapability()); ConstructionWand.instance.containerManager.register(new HandlerShulkerbox()); + ConstructionWand.instance.containerManager.register(new HandlerBundle()); /* TODO: Reenable this when Botania gets ported to 1.17 diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBundle.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBundle.java new file mode 100644 index 0000000..27e5989 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBundle.java @@ -0,0 +1,68 @@ +package thetadev.constructionwand.containers.handlers; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import thetadev.constructionwand.api.IContainerHandler; +import thetadev.constructionwand.basics.WandUtil; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +public class HandlerBundle implements IContainerHandler +{ + @Override + public boolean matches(Player player, ItemStack itemStack, ItemStack inventoryStack) { + return inventoryStack != null && inventoryStack.getCount() == 1 && inventoryStack.getItem() == Items.BUNDLE; + } + + @Override + public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { + return getContents(inventoryStack).filter((stack) -> WandUtil.stackEquals(stack, itemStack)) + .map(ItemStack::getCount).reduce(0, Integer::sum); + } + + @Override + public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { + AtomicInteger newCount = new AtomicInteger(count); + + List itemStacks = getContents(inventoryStack).filter((stack -> { + if(WandUtil.stackEquals(stack, itemStack)) { + int toTake = Math.min(newCount.get(), stack.getCount()); + stack.shrink(toTake); + newCount.set(newCount.get() - toTake); + } + return !stack.isEmpty(); + })).toList(); + + setItemList(inventoryStack, itemStacks); + + return newCount.get(); + } + + private Stream getContents(ItemStack bundleStack) { + CompoundTag compoundtag = bundleStack.getTag(); + if(compoundtag == null) { + return Stream.empty(); + } + else { + ListTag listtag = compoundtag.getList("Items", 10); + return listtag.stream().map(CompoundTag.class::cast).map(ItemStack::of); + } + } + + private void setItemList(ItemStack itemStack, List itemStacks) { + CompoundTag rootTag = itemStack.getOrCreateTag(); + ListTag listTag = new ListTag(); + rootTag.put("Items", listTag); + + for(ItemStack stack : itemStacks) { + CompoundTag itemTag = new CompoundTag(); + stack.save(itemTag); + listTag.add(itemTag); + } + } +} From 9b036cdc50f7b19cfaba80b171993f23182b538c Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 21 Aug 2021 15:12:29 +0200 Subject: [PATCH 50/78] Fixed server crash when changing wand direction --- README.md | 8 ++++---- .../thetadev/constructionwand/items/wand/ItemWand.java | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fc58c42..75169d2 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,11 @@ around them. SHIFT+CTRL+Right clickking any of them will undo the operation, giv If you used the Destruction core, it will restore the blocks. ## Additional features -- If you have shulker boxes in your inventory filled with blocks, the wand can pull them out and place them +- If you have shulker boxes (or bundles in MC 1.17+) in your inventory filled with blocks, the wand can pull them out + and place them -- Botania compatibility: The Black Hole Talisman can supply blocks just like shulker boxes can. - Having a Rod of the Lands / Rod of the Depths in your inventory will provide you with infinite dirt/cobble - at the cost of Mana. +- Botania compatibility: The Black Hole Talisman can supply blocks just like shulker boxes can. Having a Rod of the + Lands / Rod of the Depths in your inventory will provide you with infinite dirt/cobble at the cost of Mana. - Having blocks in your offhand will place them instead of the block you're looking at diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 539c96d..5e19cc7 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -130,7 +130,6 @@ public abstract class ItemWand extends ItemBase implements ICustomItemModel } } - @OnlyIn(Dist.CLIENT) public static void optionMessage(Player player, IOption option) { player.displayClientMessage( new TranslatableComponent(option.getKeyTranslation()).withStyle(ChatFormatting.AQUA) From 5cad07d34d301efd7a73d2b354f5552245817c96 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 16 Sep 2021 19:56:54 +0200 Subject: [PATCH 51/78] Fixed crash caused by HandlerCapability --- gradle.properties | 2 +- .../handlers/HandlerCapability.java | 22 +++++++------------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/gradle.properties b/gradle.properties index d9915f8..29491ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ mcp_channel=official mcp_mappings=1.17.1 botania=1.16.2-405 version_major=2 -version_minor=2 \ No newline at end of file +version_minor=3 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java index a968655..d67ae0e 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerCapability.java @@ -2,15 +2,13 @@ package thetadev.constructionwand.containers.handlers; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.items.CapabilityItemHandler; import net.minecraftforge.items.IItemHandler; import thetadev.constructionwand.api.IContainerHandler; import thetadev.constructionwand.basics.WandUtil; -/** - * Created by james on 28/12/16. - */ +import java.util.Optional; + public class HandlerCapability implements IContainerHandler { @Override @@ -20,32 +18,28 @@ public class HandlerCapability implements IContainerHandler @Override public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { - LazyOptional itemHandlerLazyOptional = inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); - if(!itemHandlerLazyOptional.isPresent()) return 0; + Optional itemHandlerOptional = inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve(); + if(itemHandlerOptional.isEmpty()) return 0; int total = 0; - IItemHandler itemHandler = itemHandlerLazyOptional.orElse((IItemHandler) CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); + IItemHandler itemHandler = itemHandlerOptional.get(); for(int i = 0; i < itemHandler.getSlots(); i++) { ItemStack containerStack = itemHandler.getStackInSlot(i); if(WandUtil.stackEquals(itemStack, containerStack)) { total += Math.max(0, containerStack.getCount()); } - - // Already in a container. Don't inception this thing. } return total; } @Override public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { - int toUse = itemStack.getCount(); + Optional itemHandlerOptional = inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve(); + if(itemHandlerOptional.isEmpty()) return 0; - LazyOptional itemHandlerLazyOptional = inventoryStack.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); - if(!itemHandlerLazyOptional.isPresent()) return 0; - - IItemHandler itemHandler = itemHandlerLazyOptional.orElse((IItemHandler) CapabilityItemHandler.ITEM_HANDLER_CAPABILITY); + IItemHandler itemHandler = itemHandlerOptional.get(); for(int i = 0; i < itemHandler.getSlots(); i++) { ItemStack handlerStack = itemHandler.getStackInSlot(i); From 8fd5a093635dc5bfb850f0e283dd04ed017c8e8d Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Tue, 28 Sep 2021 20:49:31 +0200 Subject: [PATCH 52/78] Update Forge --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 29491ac..137e9cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.daemon=false author=thetadev modid=constructionwand mcversion=1.17.1 -forgeversion=37.0.19 +forgeversion=37.0.73 mcp_channel=official mcp_mappings=1.17.1 botania=1.16.2-405 From c60260ea5ce7645d5f39a1aad669388149600721 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 22 Oct 2021 13:35:05 +0200 Subject: [PATCH 53/78] Fix #40 wand replacing half slabs Fix destruction wand removing blocks not facing the player --- gradle.properties | 2 +- .../thetadev/constructionwand/basics/WandUtil.java | 13 +++++++++++-- .../wand/action/ActionDestruction.java | 10 +++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index 137e9cf..a893bfc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ mcp_channel=official mcp_mappings=1.17.1 botania=1.16.2-405 version_major=2 -version_minor=3 \ No newline at end of file +version_minor=4 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 6b4db45..0ce3056 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -11,9 +11,11 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraftforge.common.MinecraftForge; @@ -22,6 +24,7 @@ import net.minecraftforge.event.world.BlockEvent; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.containers.ContainerManager; import thetadev.constructionwand.items.wand.ItemWand; +import thetadev.constructionwand.wand.WandItemUseContext; import javax.annotation.Nullable; import java.util.ArrayList; @@ -187,13 +190,19 @@ public class WandUtil /** * Tests if a wand can place a block at a certain position. - * This check is independent from the used block. + * This check is independent of the used block. */ public static boolean isPositionPlaceable(Level world, Player player, BlockPos pos, boolean replace) { if(!isPositionModifiable(world, player, pos)) return false; // If replace mode is off, target has to be air - return replace || world.isEmptyBlock(pos); + if(world.isEmptyBlock(pos)) return true; + + // Otherwise, check if the block can be replaced by a generic block + return replace && world.getBlockState(pos).canBeReplaced( + new WandItemUseContext(world, player, + new BlockHitResult(new Vec3(0, 0, 0), Direction.DOWN, pos, false), + pos, (BlockItem) Items.STONE)); } public static boolean isBlockRemovable(Level world, Player player, BlockPos pos) { diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java index 335d908..182d632 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -38,14 +38,14 @@ public class ActionDestruction implements IWandAction HashSet allCandidates = new HashSet<>(); // Block face the wand was pointed at - Direction breakDirection = rayTraceResult.getDirection(); + Direction breakFace = rayTraceResult.getDirection(); // Block the wand was pointed at BlockPos startingPoint = rayTraceResult.getBlockPos(); BlockState targetBlock = world.getBlockState(rayTraceResult.getBlockPos()); // Is break direction allowed by lock? // Tried to break blocks from top/bottom face, so the wand should allow breaking in NS/EW direction - if(breakDirection == Direction.UP || breakDirection == Direction.DOWN) { + if(breakFace == Direction.UP || breakFace == Direction.DOWN) { if(options.testLock(WandOptions.LOCK.NORTHSOUTH) || options.testLock(WandOptions.LOCK.EASTWEST)) candidates.add(startingPoint); } @@ -56,6 +56,10 @@ public class ActionDestruction implements IWandAction // Process current candidates, stop when none are avaiable or block limit is reached while(!candidates.isEmpty() && destroySnapshots.size() < limit) { BlockPos currentCandidate = candidates.removeFirst(); + + // Only break blocks facing the player, with no blocks in between + if(!world.isEmptyBlock(currentCandidate.offset(breakFace.getNormal()))) continue; + try { BlockState candidateBlock = world.getBlockState(currentCandidate); @@ -66,7 +70,7 @@ public class ActionDestruction implements IWandAction if(snapshot == null) continue; destroySnapshots.add(snapshot); - switch(breakDirection) { + switch(breakFace) { case DOWN: case UP: if(options.testLock(WandOptions.LOCK.NORTHSOUTH)) { From bb48292c6f31854c2fa337fb9ae6a7b939b8abe5 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 22 Oct 2021 17:36:35 +0200 Subject: [PATCH 54/78] Fix destruction behind permeable blocks --- src/main/java/thetadev/constructionwand/basics/WandUtil.java | 4 ++++ .../constructionwand/wand/action/ActionDestruction.java | 5 +++-- .../constructionwand/wand/supplier/SupplierInventory.java | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 0ce3056..418ec5c 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -214,6 +214,10 @@ public class WandUtil return true; } + public static boolean isBlockPermeable(Level world, BlockPos pos) { + return world.isEmptyBlock(pos) || world.getBlockState(pos).getCollisionShape(world, pos).isEmpty(); + } + public static boolean entitiesCollidingWithBlock(Level world, BlockState blockState, BlockPos pos) { VoxelShape shape = blockState.getCollisionShape(world, pos); if(!shape.isEmpty()) { diff --git a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java index 182d632..9904c6d 100644 --- a/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java +++ b/src/main/java/thetadev/constructionwand/wand/action/ActionDestruction.java @@ -10,6 +10,7 @@ import net.minecraft.world.phys.BlockHitResult; import thetadev.constructionwand.api.IWandAction; import thetadev.constructionwand.api.IWandSupplier; import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.wand.undo.DestroySnapshot; import thetadev.constructionwand.wand.undo.ISnapshot; @@ -57,8 +58,8 @@ public class ActionDestruction implements IWandAction while(!candidates.isEmpty() && destroySnapshots.size() < limit) { BlockPos currentCandidate = candidates.removeFirst(); - // Only break blocks facing the player, with no blocks in between - if(!world.isEmptyBlock(currentCandidate.offset(breakFace.getNormal()))) continue; + // Only break blocks facing the player, with no collidable blocks in between + if(!WandUtil.isBlockPermeable(world, currentCandidate.offset(breakFace.getNormal()))) continue; try { BlockState candidateBlock = world.getBlockState(currentCandidate); diff --git a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java index 4e158ef..8a74155 100644 --- a/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java +++ b/src/main/java/thetadev/constructionwand/wand/supplier/SupplierInventory.java @@ -6,6 +6,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; @@ -51,7 +52,7 @@ public class SupplierInventory implements IWandSupplier addBlockItem((BlockItem) offhandStack.getItem()); } // Otherwise use target block - else { + else if(target != null && target != Items.AIR) { addBlockItem(target); // Add replacement items From 5be6d5c788e4fbec2d43a738c101d11641f711f4 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 8 Dec 2021 09:15:35 +0100 Subject: [PATCH 55/78] Ported to 1.18 --- build.gradle | 3 +-- gradle.properties | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/thetadev/constructionwand/ConstructionWand.java | 4 ++-- .../constructionwand/basics/option/WandUpgrades.java | 4 ++-- .../constructionwand/client/RenderBlockPreview.java | 6 +++--- .../containers/handlers/HandlerShulkerbox.java | 8 ++++---- .../constructionwand/network/PacketQueryUndo.java | 2 +- .../constructionwand/network/PacketUndoBlocks.java | 2 +- .../constructionwand/network/PacketWandOption.java | 2 +- .../thetadev/constructionwand/wand/undo/UndoHistory.java | 2 +- src/main/resources/META-INF/mods.toml | 6 +++--- 12 files changed, 23 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index 555679a..b548a91 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,7 @@ version = "${mcversion}-${version_major}.${version_minor}" group = "${author}.${modid}" archivesBaseName = "${modid}" -// Mojang ships Java 16 to end users in 1.17+ instead of Java 8 in 1.16 or lower, so your mod should target Java 16. -java.toolchain.languageVersion = JavaLanguageVersion.of(16) +java.toolchain.languageVersion = JavaLanguageVersion.of(17) minecraft { mappings channel: project.mcp_channel, version: project.mcp_mappings diff --git a/gradle.properties b/gradle.properties index a893bfc..da71d72 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,10 +2,10 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false author=thetadev modid=constructionwand -mcversion=1.17.1 -forgeversion=37.0.73 +mcversion=1.18 +forgeversion=38.0.16 mcp_channel=official -mcp_mappings=1.17.1 +mcp_mappings=1.18 botania=1.16.2-405 version_major=2 version_minor=4 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0595cf7..e750102 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-7.2-20210702220150+0000-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index bcfdeab..e8e71af 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -8,8 +8,8 @@ import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fmllegacy.network.NetworkRegistry; -import net.minecraftforge.fmllegacy.network.simple.SimpleChannel; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import thetadev.constructionwand.basics.ConfigClient; diff --git a/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java b/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java index 043907d..5b7f83b 100644 --- a/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java +++ b/src/main/java/thetadev/constructionwand/basics/option/WandUpgrades.java @@ -3,9 +3,9 @@ package thetadev.constructionwand.basics.option; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; -import net.minecraftforge.common.util.Constants; import net.minecraftforge.registries.ForgeRegistries; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandUpgrade; @@ -31,7 +31,7 @@ public class WandUpgrades } protected void deserialize() { - ListTag listnbt = tag.getList(key, Constants.NBT.TAG_STRING); + ListTag listnbt = tag.getList(key, Tag.TAG_STRING); boolean require_fix = false; for(int i = 0; i < listnbt.size(); i++) { diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index 1fe6b07..b8dec20 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -30,7 +30,7 @@ public class RenderBlockPreview if(event.getTarget().getType() != HitResult.Type.BLOCK) return; BlockHitResult rtr = event.getTarget(); - Entity entity = event.getInfo().getEntity(); + Entity entity = event.getCamera().getEntity(); if(!(entity instanceof Player player)) return; Set blocks; float colorR = 0, colorG = 0, colorB = 0; @@ -51,8 +51,8 @@ public class RenderBlockPreview if(blocks == null || blocks.isEmpty()) return; - PoseStack ms = event.getMatrix(); - MultiBufferSource buffer = event.getBuffers(); + PoseStack ms = event.getPoseStack(); + MultiBufferSource buffer = event.getMultiBufferSource(); VertexConsumer lineBuilder = buffer.getBuffer(RenderType.LINES); double partialTicks = event.getPartialTicks(); diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java index 329d6a8..c4881bd 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerShulkerbox.java @@ -2,12 +2,12 @@ package thetadev.constructionwand.containers.handlers; import net.minecraft.core.NonNullList; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; import net.minecraft.world.ContainerHelper; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.ShulkerBoxBlock; -import net.minecraftforge.common.util.Constants; import thetadev.constructionwand.api.IContainerHandler; import thetadev.constructionwand.basics.WandUtil; @@ -56,9 +56,9 @@ public class HandlerShulkerbox implements IContainerHandler private NonNullList getItemList(ItemStack itemStack) { NonNullList itemStacks = NonNullList.withSize(SLOTS, ItemStack.EMPTY); CompoundTag rootTag = itemStack.getTag(); - if(rootTag != null && rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { + if(rootTag != null && rootTag.contains("BlockEntityTag", Tag.TAG_COMPOUND)) { CompoundTag entityTag = rootTag.getCompound("BlockEntityTag"); - if(entityTag.contains("Items", Constants.NBT.TAG_LIST)) { + if(entityTag.contains("Items", Tag.TAG_LIST)) { ContainerHelper.loadAllItems(entityTag, itemStacks); } } @@ -67,7 +67,7 @@ public class HandlerShulkerbox implements IContainerHandler private void setItemList(ItemStack itemStack, NonNullList itemStacks) { CompoundTag rootTag = itemStack.getOrCreateTag(); - if(!rootTag.contains("BlockEntityTag", Constants.NBT.TAG_COMPOUND)) { + if(!rootTag.contains("BlockEntityTag", Tag.TAG_COMPOUND)) { rootTag.put("BlockEntityTag", new CompoundTag()); } ContainerHelper.saveAllItems(rootTag.getCompound("BlockEntityTag"), itemStacks); diff --git a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java index 488b89e..396d4a2 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java +++ b/src/main/java/thetadev/constructionwand/network/PacketQueryUndo.java @@ -2,7 +2,7 @@ package thetadev.constructionwand.network; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.fmllegacy.network.NetworkEvent; +import net.minecraftforge.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; import java.util.function.Supplier; diff --git a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java index b6ca550..566ca6f 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java +++ b/src/main/java/thetadev/constructionwand/network/PacketUndoBlocks.java @@ -2,7 +2,7 @@ package thetadev.constructionwand.network; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.fmllegacy.network.NetworkEvent; +import net.minecraftforge.network.NetworkEvent; import thetadev.constructionwand.ConstructionWand; import java.util.HashSet; diff --git a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java index e2bde2e..e81d012 100644 --- a/src/main/java/thetadev/constructionwand/network/PacketWandOption.java +++ b/src/main/java/thetadev/constructionwand/network/PacketWandOption.java @@ -3,7 +3,7 @@ package thetadev.constructionwand.network; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.fmllegacy.network.NetworkEvent; +import net.minecraftforge.network.NetworkEvent; import thetadev.constructionwand.basics.WandUtil; import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; diff --git a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java index 5be6fdb..9443df5 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/UndoHistory.java @@ -8,7 +8,7 @@ import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import net.minecraftforge.fmllegacy.network.PacketDistributor; +import net.minecraftforge.network.PacketDistributor; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigServer; import thetadev.constructionwand.basics.WandUtil; diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 867d8b2..bb40c85 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,5 +1,5 @@ modLoader = "javafml" -loaderVersion = "[37,)" +loaderVersion = "[38,)" license = "MIT License" [[mods]] modId="constructionwand" @@ -20,12 +20,12 @@ This is my first minecraft mod. May the odds be ever in your favor. [[dependencies.constructionwand]] modId="forge" mandatory = true -versionRange = "[37,)" +versionRange = "[38,)" ordering = "NONE" side="BOTH" [[dependencies.constructionwand]] modId="minecraft" mandatory = true -versionRange = "[1.17.1, 1.18)" +versionRange = "[1.18, 1.19)" ordering = "NONE" side="BOTH" From 3d62df6ac5b620b63264410ba3b6d7d16a6ebc1d Mon Sep 17 00:00:00 2001 From: "A. Regnander" Date: Tue, 7 Dec 2021 02:21:48 +0100 Subject: [PATCH 56/78] Create sv_se.json --- .../assets/constructionwand/lang/sv_se.json | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/resources/assets/constructionwand/lang/sv_se.json diff --git a/src/main/resources/assets/constructionwand/lang/sv_se.json b/src/main/resources/assets/constructionwand/lang/sv_se.json new file mode 100644 index 0000000..bab195b --- /dev/null +++ b/src/main/resources/assets/constructionwand/lang/sv_se.json @@ -0,0 +1,61 @@ +{ + "item.constructionwand.stone_wand": "Stenstav", + "item.constructionwand.iron_wand": "Järnstav", + "item.constructionwand.diamond_wand": "Diamantstav", + "item.constructionwand.infinity_wand": "Oändlighetsstav", + "item.constructionwand.core_angel": "Änglastavskärna", + "item.constructionwand.core_destruction": "Rivningsstavskärna", + + "constructionwand.tooltip.blocks": "Max. %d block", + "constructionwand.tooltip.shift": "Håll ned [SHIFT]", + "constructionwand.tooltip.cores": "Stavkärnor:", + "constructionwand.tooltip.core_tip": "Kombinera kärnan med din stav i ett tillverkningsrutnät", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "Byggkärna", + "constructionwand.option.cores.constructionwand:default.desc": "Utvidga din byggnad åt sidan som är riktad mot dig", + "constructionwand.option.cores.constructionwand:core_angel": "§6Änglakärna", + "constructionwand.option.cores.constructionwand:core_angel.desc": "Placera block bakom befintliga block och i luften", + "constructionwand.option.cores.constructionwand:core_destruction": "§cRivningskärna", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "Förstör block på sidan som är riktad mot dig", + + "constructionwand.option.lock": "Begränsning: ", + "constructionwand.option.lock.horizontal": "§aVänster/Höger", + "constructionwand.option.lock.horizontal.desc": "Bygg en horisontal kolumn framför originalblocket", + "constructionwand.option.lock.vertical": "§aUppåt/Nedåt", + "constructionwand.option.lock.vertical.desc": "Bygg en vertikal kolumn framför originalblocket", + "constructionwand.option.lock.northsouth": "§6Nord/Syd", + "constructionwand.option.lock.northsouth.desc": "Bygg en nord-/sydriktad rad ovanpå originalblocket", + "constructionwand.option.lock.eastwest": "§6Öst/Väst", + "constructionwand.option.lock.eastwest.desc": "Bygg en öst-/västriktad rad ovanpå originalblocket", + "constructionwand.option.lock.nolock": "§cIngen", + "constructionwand.option.lock.nolock.desc": "Utvidga från en valfri sida av originalblocket", + + "constructionwand.option.direction": "Riktning: ", + "constructionwand.option.direction.target": "§6Mål", + "constructionwand.option.direction.target.desc": "Placera block i samma riktning som målblocket", + "constructionwand.option.direction.player": "§aSpelare", + "constructionwand.option.direction.player.desc": "Placera block i samma riktning som spelaren tittar åt", + + "constructionwand.option.replace": "Ersättning: ", + "constructionwand.option.replace.yes": "§aJa", + "constructionwand.option.replace.yes.desc": "Ersätt vissa block, t.ex. vätskor, snö och högt gräs", + "constructionwand.option.replace.no": "§cNej", + "constructionwand.option.replace.no.desc": "Ersätt inte något block", + + "constructionwand.option.match": "Matchning: ", + "constructionwand.option.match.exact": "§aExakt", + "constructionwand.option.match.exact.desc": "Utvidga endast block som är exakt likadana", + "constructionwand.option.match.similar": "§6Liknande", + "constructionwand.option.match.similar.desc": "Behandla liknande block (jord-/grästyper) likadant", + "constructionwand.option.match.any": "§cAllting", + "constructionwand.option.match.any.desc": "Utvidga alla block", + + "constructionwand.option.random": "Slumpa: ", + "constructionwand.option.random.yes": "§aJa", + "constructionwand.option.random.yes.desc": "Placera slumpartade block från din föremålsmeny", + "constructionwand.option.random.no": "§cNej", + "constructionwand.option.random.no.desc": "Slumpa inte block som ska placeras ut", + + "stat.constructionwand.use_wand": "Block placerade med stavar" +} From 0ca129122ed7bba6a3b85aa605415b3706ceb7a8 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 8 Dec 2021 18:52:39 +0100 Subject: [PATCH 57/78] add JEI ingame documentation --- README.md | 27 +++++--- build.gradle | 9 +++ gradle.properties | 3 +- .../constructionwand/basics/ConfigServer.java | 4 +- .../jei/ConstructionWandJeiPlugin.java | 67 +++++++++++++++++++ .../constructionwand/items/ModItems.java | 1 + src/main/resources/META-INF/mods.toml | 6 +- .../assets/constructionwand/lang/de_de.json | 9 +++ .../assets/constructionwand/lang/en_us.json | 9 +++ 9 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java diff --git a/README.md b/README.md index 75169d2..e664f13 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Construction Wand With a Construction Wand you can place multiple blocks (up to 1024) at once, extending your build on the side you're -facing. Sneak+Right click to activate angel mode which allows you to place a block at the opposite side of the block -facing you. If you concentrate enough, you can even conjure a block in mid air! +facing. If that's not enough: you can upgrade your wand with additional cores, allowing you to place a block behind the +block you are facing, conjure blocks in mid air or destroy lots of blocks very fast. ![](images/wands.png) @@ -73,8 +73,8 @@ SNEAK+OPTKEY+Right clicking empty space opens the option screen of your wand. **Restriction:** If restriction is enabled the wand will only place blocks in one row or column (choose between North/South, East/West on a horizontal plane and Horizontal, Vertical on a vertical plane). -If the direction lock is switched off, the wand will extend the entire face of the building it's pointed at. -This option has no effect in Angel mode. +If the direction lock is switched off, the wand will extend the entire face of the building it's pointed at. This option +has no effect if the angel core is enabled. **Direction:** If set to "Player" the wand places blocks in the same direction as if they were placed by yourself. Target mode places the blocks in the same direction as their supporting block. See the picture below: @@ -92,13 +92,14 @@ are exactly the same as the selected block.
~~Shamelessly stolen~~ Inspired by the Trowel from Quark. ## Undo -Holding down SHIFT+CTRL while looking at a blocks will show you the last blocks you placed with a green border -around them. SHIFT+CTRL+Right clickking any of them will undo the operation, giving you all the items back. -If you used the Destruction core, it will restore the blocks. + +Holding down Sneak+OPTKEY while looking at a block will show you the last blocks you placed with a green border around +them. Sneak+OPTKEY+Right clicking any of them will undo the operation, giving you all the items back. If you used the +Destruction core, it will restore the blocks. ## Additional features -- If you have shulker boxes (or bundles in MC 1.17+) in your inventory filled with blocks, the wand can pull them out - and place them + +- Shulker boxes, bundles (MC 1.17+) and many containers from other mods can provide building blocks for the wand - Botania compatibility: The Black Hole Talisman can supply blocks just like shulker boxes can. Having a Rod of the Lands / Rod of the Depths in your inventory will provide you with infinite dirt/cobble at the cost of Mana. @@ -109,14 +110,18 @@ If you used the Destruction core, it will restore the blocks. - **1.16+ only:** The Infinity Wand won't burn in lava just like netherite gear. +- Ingame documentation with Just Enough Items (JEI) + ## Contributions and #Hacktoberfest + As #Hacktoberfest now requires repo owners to opt in, I added the tag to this repository. -I'd really appreciate translations. Currently, ConstructionWand only has English and German, -so if you speak any other language you can help translate the mod and add a new language file +I'd really appreciate translations. Currently, ConstructionWand only has English and German, so if you speak any other +language you can help translate the mod and add a new language file under `src/main/resources/assets/constructionwand/lang/`. ## TileEntity Blacklist + Some modded TileEntitys can cause issues when placed using a wand. They may turn into invisible and unremovable ghost blocks, become unbreakable or cause other unwanted effects. diff --git a/build.gradle b/build.gradle index 555679a..ee5bfc5 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,10 @@ repositories { maven { url = "https://maven.theillusivec4.top/" } + maven { + name = "JEI Maven" + url "https://dvs1.progwml6.com/files/maven" + } } dependencies { @@ -98,12 +102,17 @@ dependencies { version: "${project.mcversion}-${project.forgeversion}" ]) + compileOnly fg.deobf("mezz.jei:${jei_version}:api") + runtimeOnly fg.deobf("mezz.jei:${jei_version}") + + /* compileOnly fg.deobf([ group: "vazkii.botania", name: "Botania", version: "${project.botania}", classifier: "api" ]) + */ } jar { diff --git a/gradle.properties b/gradle.properties index a893bfc..abddd8c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ mcversion=1.17.1 forgeversion=37.0.73 mcp_channel=official mcp_mappings=1.17.1 -botania=1.16.2-405 +# botania=1.16.2-405 +jei_version=jei-1.18:9.0.0.40 version_major=2 version_minor=4 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 5cbec16..3e10b10 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -64,7 +64,7 @@ public class ConfigServer else durability = null; builder.comment("Wand block limit"); limit = builder.defineInRange("limit", defLimit, 1, Integer.MAX_VALUE); - builder.comment("Max placement distance with angel mode (0 to disable angel core)"); + builder.comment("Max placement distance with angel core (0 to disable angel core)"); angel = builder.defineInRange("angel", defAngel, 0, Integer.MAX_VALUE); builder.comment("Wand destruction block limit (0 to disable destruction core)"); destruction = builder.defineInRange("destruction", defDestruction, 0, Integer.MAX_VALUE); @@ -116,7 +116,7 @@ public class ConfigServer MAX_RANGE = BUILDER.defineInRange("MaxRange", 100, 0, Integer.MAX_VALUE); BUILDER.comment("Number of operations that can be undone"); UNDO_HISTORY = BUILDER.defineInRange("UndoHistory", 3, 0, Integer.MAX_VALUE); - BUILDER.comment("Place blocks below you while falling > 10 blocks with angel mode (Can be used to save you from drops/the void)"); + BUILDER.comment("Place blocks below you while falling > 10 blocks with angel core (Can be used to save you from drops/the void)"); ANGEL_FALLING = BUILDER.define("AngelFalling", false); BUILDER.comment("Blocks to treat equally when in Similar mode. Enter block IDs seperated by ;"); SIMILAR_BLOCKS = BUILDER.defineList("SimilarBlocks", Arrays.asList(SIMILAR_BLOCKS_DEFAULT), obj -> true); diff --git a/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java b/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java new file mode 100644 index 0000000..682d758 --- /dev/null +++ b/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java @@ -0,0 +1,67 @@ +package thetadev.constructionwand.integrations.jei; + +import com.mojang.blaze3d.platform.InputConstants; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.JeiPlugin; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.registration.IRecipeRegistration; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.basics.ConfigClient; +import thetadev.constructionwand.basics.ConfigServer; +import thetadev.constructionwand.items.ModItems; + +import javax.annotation.Nonnull; + +@JeiPlugin +public class ConstructionWandJeiPlugin implements IModPlugin +{ + private static final ResourceLocation pluginId = new ResourceLocation(ConstructionWand.MODID, ConstructionWand.MODID); + private static final String baseKey = ConstructionWand.MODID + ".description."; + private static final String baseKeyItem = "item." + ConstructionWand.MODID + "."; + + @Nonnull + @Override + public ResourceLocation getPluginUid() { + return pluginId; + } + + private Component keyComboComponent(boolean shiftOpt, Component optkeyComponent) { + String key = shiftOpt ? "sneak_opt" : "sneak"; + return new TranslatableComponent(baseKey + "key." + key, optkeyComponent).withStyle(ChatFormatting.BLUE); + } + + @Override + public void registerRecipes(IRecipeRegistration registration) { + Component optkeyComponent = new TranslatableComponent(InputConstants.getKey(ConfigClient.OPT_KEY.get(), -1).getName()) + .withStyle(ChatFormatting.BLUE); + Component wandModeComponent = keyComboComponent(ConfigClient.SHIFTOPT_MODE.get(), optkeyComponent); + Component wandGuiComponent = keyComboComponent(ConfigClient.SHIFTOPT_GUI.get(), optkeyComponent); + + for(Item wand : ModItems.WANDS) { + ConfigServer.WandProperties wandProperties = ConfigServer.getWandProperties(wand); + + String durabilityKey = wand == ModItems.WAND_INFINITY ? "unlimited" : "limited"; + Component durabilityComponent = new TranslatableComponent(baseKey + "durability." + durabilityKey, wandProperties.getDurability()); + + registration.addIngredientInfo(new ItemStack(wand), VanillaTypes.ITEM, + new TranslatableComponent(baseKey + "wand", + new TranslatableComponent(baseKeyItem + wand.getRegistryName().getPath()), + wandProperties.getLimit(), durabilityComponent, + optkeyComponent, wandModeComponent, wandGuiComponent) + ); + } + + for(Item core : ModItems.CORES) { + registration.addIngredientInfo(new ItemStack(core), VanillaTypes.ITEM, + new TranslatableComponent(baseKey + core.getRegistryName().getPath()), + new TranslatableComponent(baseKey + "core", wandModeComponent) + ); + } + } +} diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 10d7916..14c3255 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -41,6 +41,7 @@ public class ModItems // Collections public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; + public static final Item[] CORES = {CORE_ANGEL, CORE_DESTRUCTION}; public static final HashSet ALL_ITEMS = new HashSet<>(); diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 867d8b2..76b0eba 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -11,9 +11,9 @@ authors="ThetaDev" description=''' Construction Wands make building easier! -With a Construction Wand you can place multiple blocks (up to 1024) at once, extending you build on the side you're facing. -Sneak+Right click to activate angel mode which allows you to place a block at the opposite side of the block facing you. -If you concentrate enough, you can even conjure a block in mid air! +With a Construction Wand you can place multiple blocks (up to 1024) at once, extending your build on the side you're +facing. If that's not enough: you can upgrade your wand with additional cores, allowing you to place a block +behind the block you are facing, conjure blocks in mid air or destroy lots of blocks very fast. This is my first minecraft mod. May the odds be ever in your favor. ''' diff --git a/src/main/resources/assets/constructionwand/lang/de_de.json b/src/main/resources/assets/constructionwand/lang/de_de.json index 74865f3..a4d2f8f 100644 --- a/src/main/resources/assets/constructionwand/lang/de_de.json +++ b/src/main/resources/assets/constructionwand/lang/de_de.json @@ -57,5 +57,14 @@ "constructionwand.option.random.no": "§cAus", "constructionwand.option.random.no.desc": "Platziere Blöcke normal", + "constructionwand.description.wand": "Ein %1$s kann maximal %2$d Blöcke auf der dir zugewandten Seite eines Bauwerks platzieren und hält %3$s.\n\nHalte %5$s gedrückt und scrolle, um die Platzierung zu beschränken (Horizontal, Vertikal, Nord/Süd, Ost/West, Keine).\n\nÖffne den Optionsbildschirm mit %6$s§9+Rechtsklick§0.\n\n§5§nRÜCKGÄNGIG§0§r\nHalte §9Schleichen+§0%4$s während du einen Block fokussierst. Die letzten platzierten Blöcke werden mit einem grünen Rahmen markiert. §9Schleichen+§0%4$s§9+Rechtsklick§0 auf einen dieser Blöcke macht diese Operation rückgängig und gibt dir alle Items zurück. Wenn du den Kristall der Zerstörung benutzt hast, werden die zerstörten Blöcke wiederhergestellt.\n\n§5§nCONTAINER§0§r\nShulkerkisten, Bündel und viele Container von anderen Mods können Baumaterial für deinen Stab bereitstellen.\n\n§5§nLINKE-HAND-PRIORITÄT§0§r\nWenn du einen Block in der linken Hand hältst, wird der Stab diesen anstatt des Blocks, den du anschaust, platzieren.", + "constructionwand.description.durability.limited": "für %d Blöcke", + "constructionwand.description.durability.unlimited": "unendlich lang", + "constructionwand.description.key.sneak": "Schleichen", + "constructionwand.description.key.sneak_opt": "Schleichen+%s", + "constructionwand.description.core": "§5§nINSTALLATION§0§r\nLege deinen neuen Kristall zusammen mit dem Stab auf eine Werkbank, um ihn einzusetzen. Um zwischen den Kristallen zu wechseln, halte %s gedrückt und klicke mit der linken Maustaste ins Leere. Alternativ kannst du den Kristall auch im Optionsbildschirm auswählen.", + "constructionwand.description.core_angel": "Der Kristall der Engel platziert einen Block auf der gegenüberliegenden Seites des Blocks (oder der Blockreihe) den du anschaust. Die maximale Entfernung hängt vom Material des Stabes ab. Ein Rechtsklick ins Leere platziert einen Block mitten in der Luft. Hierfür musst du den Block, den du platzieren willst, in der linken Hand halten.", + "constructionwand.description.core_destruction": "Der Kristall der Zerstörung zerstört Blöcke (keine Tile Entities) auf der dir zugewandten Seite. Die maximale Anzahl Blöcke hängt vonm Material des Stabes ab. Zerstörte Blöcke verschwinden im Nichts, du kannst Fehler jedoch rückgängig machen.", + "stat.constructionwand.use_wand": "Blöcke mithilfe des Stabs platziert" } \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 5331b7b..0f274af 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -57,5 +57,14 @@ "constructionwand.option.random.no": "§cNo", "constructionwand.option.random.no.desc": "Don't randomize placed blocks", + "constructionwand.description.wand": "A %1$s can place up to %2$d blocks at the side of a building facing you and lasts %3$s.\n\nHold down %5$s and scroll to change placement restriction (Horizontal, Vertical, North/South, East/West, No lock).\n\nOpen the option screen with %6$s§9+Right click§0.\n\n§5§nUNDO§0§r\nHolding down §9Sneak+§0%4$s while looking at a blocks will show you the last blocks you placed with a green border around them. §9Sneak+§0%4$s§9+Right clicking§0 any of them will undo the operation, giving you all the items back. If you used the Destruction core, it will restore the blocks.\n\n§5§nCONTAINERS§0§r\nShulker boxes, bundles and many containers from other mods can provide building blocks for the wand.\n\n§5§nOFFHAND PRIORITY§0§r\nHaving blocks in your offhand will place them instead of the block you're looking at.", + "constructionwand.description.durability.limited": "for %d blocks", + "constructionwand.description.durability.unlimited": "forever", + "constructionwand.description.key.sneak": "Sneak", + "constructionwand.description.key.sneak_opt": "Sneak+%s", + "constructionwand.description.core": "§5§nINSTALLATION§0§r\nPut your new core together with your wand in a crafting grid to install it. To switch between cores, hold down %s and left click empty space with your wand or use the option screen.", + "constructionwand.description.core_angel": "The angel core places a block on the opposite side of the block (or row of blocks) you are facing. Maximum distance depends on wand tier. Right click empty space to place a block in midair. To do that, you'll need to have the block you want to place in your offhand.", + "constructionwand.description.core_destruction": "The destruction core destroys blocks (no tile entities) on the side facing you. Maximum number of blocks depends on wand tier. Destroyed blocks disappear into the void, you can use the undo feature if you've made a mistake.", + "stat.constructionwand.use_wand": "Blocks placed using Wand" } \ No newline at end of file From bc0668a1b760be8c453c324cf9c67cf27e71b6d3 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Wed, 8 Dec 2021 19:48:12 +0100 Subject: [PATCH 58/78] fix wand getting damaged in creative mode --- .../constructionwand/wand/WandJob.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 52cdbf3..313b6c8 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -94,15 +94,19 @@ public class WandJob if(wand.isEmpty() || wandItem.remainingDurability(wand) == 0) break; if(snapshot.execute(world, player, rayTraceResult)) { - // If the item cant be taken, undo the placement - if(wandSupplier.takeItemStack(snapshot.getRequiredItems()) == 0) executed.add(snapshot); + if(player.isCreative()) executed.add(snapshot); else { - ConstructionWand.LOGGER.info("Item could not be taken. Remove block: " + - snapshot.getBlockState().getBlock().toString()); - snapshot.forceRestore(world); + // If the item cant be taken, undo the placement + if(wandSupplier.takeItemStack(snapshot.getRequiredItems()) == 0) { + executed.add(snapshot); + wand.hurt(1, player.getRandom(), (ServerPlayer) player); + } + else { + ConstructionWand.LOGGER.info("Item could not be taken. Remove block: " + + snapshot.getBlockState().getBlock().toString()); + snapshot.forceRestore(world); + } } - - wand.hurt(1, player.getRandom(), (ServerPlayer) player); player.awardStat(ModStats.USE_WAND); } } From 439783744ce1001f94d153322a2f0a3200604955 Mon Sep 17 00:00:00 2001 From: "A. Regnander" Date: Thu, 9 Dec 2021 01:14:01 +0100 Subject: [PATCH 59/78] Update sv_se.json Added new strings --- .../resources/assets/constructionwand/lang/sv_se.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/resources/assets/constructionwand/lang/sv_se.json b/src/main/resources/assets/constructionwand/lang/sv_se.json index bab195b..23eb3bf 100644 --- a/src/main/resources/assets/constructionwand/lang/sv_se.json +++ b/src/main/resources/assets/constructionwand/lang/sv_se.json @@ -57,5 +57,14 @@ "constructionwand.option.random.no": "§cNej", "constructionwand.option.random.no.desc": "Slumpa inte block som ska placeras ut", + "constructionwand.description.wand": "En %1$s kan placera upp till %2$d block på sidan av en byggnad som är riktad mot dig och räcker %3$s.\n\nHåll ned %5$s och rulla med mushjulet för att ändra placeringsbegränsningen (horisontal, vertikal, nord/syd, öst/väst, ingen låsning).\n\nÖppna alternativmenyn med %6$s§9+Högerklick§0.\n\n§5§nÅNGRA§0§r\nNär du håller ned §9Smyga+§0%4$s medan du tittar på ett block kommer du se de senaste blocken du placerade omgivna av en grön ram. §9Smyg+§0%4$s§9+högerklicka§0 på något av dem för att ångra handlingen och få tillbaka alla föremål. Om du har använt rivningskärnan kommer blocken att återställas.\n\n§5§nBEHÅLLARE§0§r\nShulkerlådor, påsar och många behållare från andra moddar kan tillhandahålla byggblock för staven.\n\n§5§nPRIO FÖR SEKUNDÄR HAND§0§r\nBlocken i din sekundära hand placeras i stället för blocket du tittar på.", + "constructionwand.description.durability.limited": "för %d block", + "constructionwand.description.durability.unlimited": "för alltid", + "constructionwand.description.key.sneak": "Smyg", + "constructionwand.description.key.sneak_opt": "Smyg+%s", + "constructionwand.description.core": "§5§nINSTALLATION§0§r\nLägg din nya kärna tillsammans med din stav i ett tillverkningsrutnät för att installera den. Håll ned %s och vänsterklicka i luften med din stav eller använd alternativmenyn för att byta kärna.", + "constructionwand.description.core_angel": "Änglakärnan placerar ett block på den motsatta sidan av blocket (eller blockraden) som är riktad mot dig. Det maximala avståndet beror på stavens nivå. Högerklicka i luften för att placera ett block i luften. För att göra detta behöver du hålla blocket du vill placera i din sekundära hand.", + "constructionwand.description.core_destruction": "Rivningskärnan förstör block (inte blockentiteter) på sidan som är riktad mot dig. Det maximala antalet block beror på stavens nivå. Förstörda block försvinner helt och hållet, men du kan använda ångrafunktionen om du har gjort ett misstag.", + "stat.constructionwand.use_wand": "Block placerade med stavar" } From 138e66e4b7f6ad44abd22e0edab7ffadeee85c29 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 9 Dec 2021 09:52:49 +0100 Subject: [PATCH 60/78] update readme --- README.md | 18 +++++++++--------- gradle.properties | 6 ++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e664f13..a1263e3 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ and last longer. These properties can be changed in the config. ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting5.png) ![](https://raw.githubusercontent.com/Theta-Dev/ConstructionWand/1.16.2/images/crafting6.png) -## OPTKEY +## Keybindings To change a wand's core and options or undo your placement you need to hold down the wand option key (refered as OPTKEY). By default, this is CTRL, but it can be changed in the client config file. To prevent unwanted @@ -112,14 +112,6 @@ Destruction core, it will restore the blocks. - Ingame documentation with Just Enough Items (JEI) -## Contributions and #Hacktoberfest - -As #Hacktoberfest now requires repo owners to opt in, I added the tag to this repository. - -I'd really appreciate translations. Currently, ConstructionWand only has English and German, so if you speak any other -language you can help translate the mod and add a new language file -under `src/main/resources/assets/constructionwand/lang/`. - ## TileEntity Blacklist Some modded TileEntitys can cause issues when placed using a wand. They may turn into invisible and @@ -132,3 +124,11 @@ If you find some of them you can tell me by creating an issue, commenting on Curse or editing the default blacklist yourself (it is located at https://github.com/Theta-Dev/ConstructionWand/blob/1.16.2/src/main/java/thetadev/constructionwand/basics/ConfigServer.java#L28) and making a PR. + +## Contributions and #Hacktoberfest + +As #Hacktoberfest now requires repo owners to opt in, I added the tag to this repository. + +I'd really appreciate translations. Currently, ConstructionWand is available in English, German and Swedish. +If you speak any other language you can help translate the mod and add a new language file +under `src/main/resources/assets/constructionwand/lang/`. diff --git a/gradle.properties b/gradle.properties index abddd8c..058f847 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,18 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false + author=thetadev modid=constructionwand + mcversion=1.17.1 forgeversion=37.0.73 mcp_channel=official mcp_mappings=1.17.1 + +# Source: https://maven.blamejared.com/vazkii/botania/Botania/ # botania=1.16.2-405 +# Source: https://dvs1.progwml6.com/files/maven/mezz/jei/ jei_version=jei-1.18:9.0.0.40 + version_major=2 version_minor=4 \ No newline at end of file From d944f21655beab20ae3aced6aa162e037b5b8728 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 9 Dec 2021 10:21:08 +0100 Subject: [PATCH 61/78] bump version 2.4 -> 2.5 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 058f847..12526d8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ mcp_mappings=1.17.1 jei_version=jei-1.18:9.0.0.40 version_major=2 -version_minor=4 \ No newline at end of file +version_minor=5 \ No newline at end of file From 721d7543a990d10acbfc59245e83ddcb30402c9c Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 9 Dec 2021 10:45:30 +0100 Subject: [PATCH 62/78] update forge and jei --- gradle.properties | 4 ++-- src/main/resources/assets/constructionwand/lang/en_us.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 12526d8..dcc329f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,14 +5,14 @@ author=thetadev modid=constructionwand mcversion=1.17.1 -forgeversion=37.0.73 +forgeversion=37.1.0 mcp_channel=official mcp_mappings=1.17.1 # Source: https://maven.blamejared.com/vazkii/botania/Botania/ # botania=1.16.2-405 # Source: https://dvs1.progwml6.com/files/maven/mezz/jei/ -jei_version=jei-1.18:9.0.0.40 +jei_version=jei-1.17.1:8.3.0.39 version_major=2 version_minor=5 \ No newline at end of file diff --git a/src/main/resources/assets/constructionwand/lang/en_us.json b/src/main/resources/assets/constructionwand/lang/en_us.json index 0f274af..619fc45 100644 --- a/src/main/resources/assets/constructionwand/lang/en_us.json +++ b/src/main/resources/assets/constructionwand/lang/en_us.json @@ -57,7 +57,7 @@ "constructionwand.option.random.no": "§cNo", "constructionwand.option.random.no.desc": "Don't randomize placed blocks", - "constructionwand.description.wand": "A %1$s can place up to %2$d blocks at the side of a building facing you and lasts %3$s.\n\nHold down %5$s and scroll to change placement restriction (Horizontal, Vertical, North/South, East/West, No lock).\n\nOpen the option screen with %6$s§9+Right click§0.\n\n§5§nUNDO§0§r\nHolding down §9Sneak+§0%4$s while looking at a blocks will show you the last blocks you placed with a green border around them. §9Sneak+§0%4$s§9+Right clicking§0 any of them will undo the operation, giving you all the items back. If you used the Destruction core, it will restore the blocks.\n\n§5§nCONTAINERS§0§r\nShulker boxes, bundles and many containers from other mods can provide building blocks for the wand.\n\n§5§nOFFHAND PRIORITY§0§r\nHaving blocks in your offhand will place them instead of the block you're looking at.", + "constructionwand.description.wand": "The %1$s can place up to %2$d blocks at the side of a building facing you and lasts %3$s.\n\nHold down %5$s and scroll to change placement restriction (Horizontal, Vertical, North/South, East/West, No lock).\n\nOpen the option screen with %6$s§9+Right click§0.\n\n§5§nUNDO§0§r\nHolding down §9Sneak+§0%4$s while looking at a blocks will show you the last blocks you placed with a green border around them. §9Sneak+§0%4$s§9+Right clicking§0 any of them will undo the operation, giving you all the items back. If you used the Destruction core, it will restore the blocks.\n\n§5§nCONTAINERS§0§r\nShulker boxes, bundles and many containers from other mods can provide building blocks for the wand.\n\n§5§nOFFHAND PRIORITY§0§r\nHaving blocks in your offhand will place them instead of the block you're looking at.", "constructionwand.description.durability.limited": "for %d blocks", "constructionwand.description.durability.unlimited": "forever", "constructionwand.description.key.sneak": "Sneak", From f86a429beb693bc54a623166167abacdef9e1dff Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 9 Dec 2021 12:02:39 +0100 Subject: [PATCH 63/78] update pack.mcmeta version --- src/main/resources/pack.mcmeta | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 89051f3..665308b 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,6 @@ { "pack": { "description": "ConstructionWand resources", - "pack_format": 5, - "_comment": "A pack_format of 5 requires json lang files and some texture changes from 1.15. Note: we require v5 pack meta for all mods." + "pack_format": 8 } } From 030d5f72f2925c94179058dc585fcc28209c7cd4 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 12 Dec 2021 10:47:22 +0100 Subject: [PATCH 64/78] fix #47 crash when pressing E inside wand gui --- gradle.properties | 2 +- .../thetadev/constructionwand/client/ScreenWand.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index b8ebd1b..f361ed1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ mcp_mappings=1.18 jei_version=jei-1.18:9.0.0.40 version_major=2 -version_minor=5 \ No newline at end of file +version_minor=6 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/client/ScreenWand.java b/src/main/java/thetadev/constructionwand/client/ScreenWand.java index a746bab..d665e83 100644 --- a/src/main/java/thetadev/constructionwand/client/ScreenWand.java +++ b/src/main/java/thetadev/constructionwand/client/ScreenWand.java @@ -1,6 +1,7 @@ package thetadev.constructionwand.client; import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; @@ -53,9 +54,13 @@ public class ScreenWand extends Screen } @Override - public boolean charTyped(char character, int code) { - if(character == 'e') onClose(); - return super.charTyped(character, code); + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (Minecraft.getInstance().options.keyInventory.matches(keyCode, scanCode)) { + this.onClose(); + return true; + } else { + return super.keyPressed(keyCode, scanCode, modifiers); + } } private void createButton(int cx, int cy, IOption option) { From 0a1c0f2926511aa6d1531737e4f7977e28a32f01 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 12 Dec 2021 11:01:42 +0100 Subject: [PATCH 65/78] removed unused block texture --- .../textures/block/conjured_block.png | Bin 5594 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/resources/assets/constructionwand/textures/block/conjured_block.png diff --git a/src/main/resources/assets/constructionwand/textures/block/conjured_block.png b/src/main/resources/assets/constructionwand/textures/block/conjured_block.png deleted file mode 100644 index 7fc945cb73067de2671ef016b8fa8bd764c9255e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5594 zcmeHKc~}$I77r>*QI?{B2to)TpoA=BH&K?P7$lNHilR`M%uFCi7AAoNiWKo_T|ivw zQnzYZR4lH=mQqv-1+^+7ElMfk3TTx@3#qRzZzdqd_k8yGeXswS?+clG?(h80xxahP zz04}{yl@-qDb_d~&PF(gAC3OnV_z!^^smRmX8;a2wk%t+KpPF{@M=UQlc&LWZI&9w z!+N<4htvO35&M2-o!^AVm;HyV=HTrcYbRfAcfB{DJGFLuiRfktrEobMSCIUV_|#`l z+V6O{-9UElJ|kV*eBFu-`ET9YzLwi`?|W{ab1$`Opd~%*+JnQn)sb)!ssFdjW4F-t zRf8Mv>*|O2Io`WI@t9HcDBH#%x9zdp5Wh;XaVs$)EMbVBVYfp(l*md_$l_ZHy_S}K zaAcjcd{@o(C=H+U#yaBNeGRJlp=v{X*pyNvE)PN#)h{p-dk>d5U56HYjoOl%?bS3=fa2 zT;CsauEouzEWFPL2XEqH^ca8nfWzY9>8uYiW@9tEdpzbqp{G`d2^H2E{A2b8~ zm={*&t?(@Ss`s7XcUxog z06X(|{-6fh!gmPL)2t+U@rRjCey7Thjwu<}S1fkec!aS_$OQ#HXVt~b5Uasv-;|9@ zmM-x0yX$KnX5%450 zSaZ&Q(S@o>d-9iCBaZdXdZ)O@4o(R-HaIPM5Iz0u-WBEaR!K$I!q~Vcs~JZc=oQDi zDrsda<~4`5I4w$TMP^K`Y+YiP@X@{fi<)ZO%I}xwx{~%aRHsrmO-a8aw)rsP&W8N( znaz7#CT%<==GvHxFH&im2G7%CWlGo4q?moU5?e*e?LCvdClm^1di=7)Ul+lQo>{t` z8?$zW_H*%Ce21uJ?jwfY#&fvvG^{~u8zWL|SuF{j!{CIvh ztD~mvw`y85(3aDFX@Bs}AAdZ`NZh_%qxr_oP+9fsgzcFYUPDDgafF}l@`EM~WJaBH zB>g^%@^#lhe)~`@N}ao?3P136fnehySzL zWiDy3rReu!-_0MY9(vpfFCx}F8hkX3P-T~MP&JXKVxL@?J-F0&RZio{=+B;7-X$M9 zX*TdjG1|EBD?3)M)`D2a@*;$`dBIlI53N7PeWh&r?3}*8tM*xAM1dkN@Jd!kN7)$X zx`ta9u21?Hde2Zly=|RZbW49IGw`ZsYiG}7`U;%=2R@eX5foT9KGEy?(hauusXk?KQ^CBl zb{W-uV#{YAQ2MRSYODZ@4(hQ2wlLz9Ca0sW@p83a(}P)tZma!qYs_Y?*Kc+y6MYxL zt(3d&Fs!Qk5`Un+yS{i@!n!}!{}!>VlhvKzq?cWZ^yywzS1&zLxQQV%kMY{&y>{QW zd5=QBt)1RYys&Wp_uCA&NE2j(o8W^jZlP{#7UYk6*Lu3&7vo&tUziac;4Posb=^bz z#Gz^u;#hn7NFJ4T`k=>SyIkGk+tV6cg|(9}KDxp3s0&VV-Zy`0_n%kN@A~FFbNS}d zqNj+>)~DVzzg!}O2X`{m=^d9o{4NSDSjaQ-hxoFCJ^{si-Py zvBV1!e3W_1!jinB+J1BI>rL&u-x+lNX)Z2)1F{Qu05Zqn?D%r@%w8ahsl(tD`J8M7j>(=U0kao_ zY=|&>mNi$;K?xMF7QpKjX-W-8&m$OlIp{NHCK2#Ph;}iLus|fnhp7-4PYa|4l8FMn zJd;Y8WsT<|kc<<}e{X~W9q|Y$TCJKxBI$IxKwVIv3P~nW*lae5OeImNL=-{PWGS_P zo~YD#VHCq0d{_e_Xg$kSN<791NL3kH9)W<)<44G`Y8U8H<46E(Y^2v{A(9Xs(9rdu z01}lLW)Owi)Ri|ZWOk-wfV9aF9I}MVN(4>r!O?|?JBJrpXCS$T(p*DJ8Xj3Ev zj^flANSYA?fh0H$R-lP!P-e(LJTHK^S{t5dkuV$zoI3bO5HCPzjY9EuaKpj0zj9mC42lG-pm2 z0s~qVB2lT*cmymbJmzZb1w418$~loL5Wu>@2!dPr%sNS5#~7WHp9?r>SoL48$7mPz~= zIDL3De3GQejZ)$9#_r_+Af_UZpaC*r$hZZlj>A)63ZP7e(PMI?N1of|uP9eQMuw;i zfK8;cDP$roh>q5nG>A;3v&c*)Lnec1Oxerq8kJ0|0}wbQ8EqnTpV1aF?la!cSY`e% zqjf1Tn&b>Jg+nG2hPM~bC1EGw^W|}|vqB`|mBQFc{yiooN z&cuJDDF4dc6gKP~rc!62&6%Q&(kWjX{uRJshDbRGD>bUurEUrtmc``4Kyx0pp%)Q) zuaQPCwvn>HI_GcvjFj8o7y+ez8|01jeJj^nx!y>DHv+${uD5c%kpgc7ep_AtH@U1| zyc)ww^dCJP`r^xgzqy3IG+RjLhVyYAxb?W+-RsIeMJ?~B=fr7nxbYLQuNiLJj>)Le zQY#b*EN_powKek($;i8nnm{2xM8f(J{H}8_b=p{;JcqHpx|(fg>VrL#2(`!W{h4s+ zX!8$oD}VlS!8FFE0OxBhkr#2H+tRbm+76vg`*Ov*6AQ15De6gayF$j7xEwI6zp}bz md`W5pIZtwa0{SWVRF;gRTno8P*_@7r!wCiR_ysyF1SS From 210baba4cd5e5ec6bae881e994c5332c40b8014f Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 12 Dec 2021 11:26:46 +0100 Subject: [PATCH 66/78] update MC and Forge to 1.18.1 --- build.gradle | 2 +- gradle.properties | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index ddf9394..5b50303 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { repositories { - maven { url = 'https://files.minecraftforge.net/maven' } + maven { url = 'https://maven.minecraftforge.net' } mavenCentral() } dependencies { diff --git a/gradle.properties b/gradle.properties index f361ed1..40a8f6b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,15 +4,15 @@ org.gradle.daemon=false author=thetadev modid=constructionwand -mcversion=1.18 -forgeversion=38.0.16 +mcversion=1.18.1 +forgeversion=39.0.0 mcp_channel=official -mcp_mappings=1.18 +mcp_mappings=1.18.1 # Source: https://maven.blamejared.com/vazkii/botania/Botania/ # botania=1.16.2-405 # Source: https://dvs1.progwml6.com/files/maven/mezz/jei/ -jei_version=jei-1.18:9.0.0.40 +jei_version=jei-1.18.1:9.1.0.41 version_major=2 version_minor=6 \ No newline at end of file From 7a89175bf1272819127d9330756f9f3bc8f0cb67 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 25 Mar 2022 22:00:47 +0100 Subject: [PATCH 67/78] use DeferredRegister for registering items --- .../constructionwand/ConstructionWand.java | 3 ++ .../constructionwand/basics/ConfigServer.java | 13 +++-- .../data/ItemModelGenerator.java | 4 +- .../data/RecipeGenerator.java | 12 ++--- .../jei/ConstructionWandJeiPlugin.java | 9 ++-- .../constructionwand/items/ItemBase.java | 12 ----- .../constructionwand/items/ModItems.java | 48 ++++++------------- .../constructionwand/items/core/ItemCore.java | 8 ++-- .../items/core/ItemCoreAngel.java | 4 +- .../items/core/ItemCoreDestruction.java | 4 +- .../constructionwand/items/wand/ItemWand.java | 8 ++-- .../items/wand/ItemWandBasic.java | 4 +- .../items/wand/ItemWandInfinity.java | 4 +- .../constructionwand/wand/WandJob.java | 2 +- 14 files changed, 58 insertions(+), 77 deletions(-) delete mode 100644 src/main/java/thetadev/constructionwand/items/ItemBase.java diff --git a/src/main/java/thetadev/constructionwand/ConstructionWand.java b/src/main/java/thetadev/constructionwand/ConstructionWand.java index e8e71af..c5b9f76 100644 --- a/src/main/java/thetadev/constructionwand/ConstructionWand.java +++ b/src/main/java/thetadev/constructionwand/ConstructionWand.java @@ -53,6 +53,9 @@ public class ConstructionWand FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup); MinecraftForge.EVENT_BUS.register(this); + // Register Item DeferredRegister + ModItems.ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); + // Config setup ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ConfigServer.SPEC); ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ConfigClient.SPEC); diff --git a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java index 3e10b10..6fc0de9 100644 --- a/src/main/java/thetadev/constructionwand/basics/ConfigServer.java +++ b/src/main/java/thetadev/constructionwand/basics/ConfigServer.java @@ -1,8 +1,10 @@ package thetadev.constructionwand.basics; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.item.Tiers; import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.registries.RegistryObject; import thetadev.constructionwand.items.ModItems; import java.util.Arrays; @@ -27,10 +29,10 @@ public class ConfigServer public static final ForgeConfigSpec.ConfigValue> TE_LIST; private static final String[] TE_LIST_DEFAULT = {"chiselsandbits"}; - private static final HashMap wandProperties = new HashMap<>(); + private static final HashMap wandProperties = new HashMap<>(); public static WandProperties getWandProperties(Item wand) { - return wandProperties.getOrDefault(wand, WandProperties.DEFAULT); + return wandProperties.getOrDefault(wand.getRegistryName(), WandProperties.DEFAULT); } public static class WandProperties @@ -53,9 +55,10 @@ public class ConfigServer this.upgradeable = upgradeable; } - public WandProperties(ForgeConfigSpec.Builder builder, Item wand, int defDurability, int defLimit, + public WandProperties(ForgeConfigSpec.Builder builder, RegistryObject wandSupplier, int defDurability, int defLimit, int defAngel, int defDestruction, boolean defUpgradeable) { - builder.push(wand.getRegistryName().getPath()); + ResourceLocation registryName = wandSupplier.getId(); + builder.push(registryName.getPath()); if(defDurability > 0) { builder.comment("Wand durability"); @@ -72,7 +75,7 @@ public class ConfigServer upgradeable = builder.define("upgradeable", defUpgradeable); builder.pop(); - wandProperties.put(wand, this); + wandProperties.put(registryName, this); } public int getDurability() { diff --git a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java index b224ac8..c007b70 100644 --- a/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/ItemModelGenerator.java @@ -5,6 +5,7 @@ import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraftforge.client.model.generators.ItemModelProvider; import net.minecraftforge.common.data.ExistingFileHelper; +import net.minecraftforge.registries.RegistryObject; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.items.ModItems; @@ -18,7 +19,8 @@ public class ItemModelGenerator extends ItemModelProvider @Override protected void registerModels() { - for(Item item : ModItems.ALL_ITEMS) { + for(RegistryObject itemObject : ModItems.ITEMS.getEntries()) { + Item item = itemObject.get(); String name = item.getRegistryName().getPath(); if(item instanceof ICustomItemModel) diff --git a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java index 4407958..aaff00c 100644 --- a/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java +++ b/src/main/java/thetadev/constructionwand/data/RecipeGenerator.java @@ -27,13 +27,13 @@ public class RecipeGenerator extends RecipeProvider @Override protected void buildCraftingRecipes(@Nonnull Consumer consumer) { - wandRecipe(consumer, ModItems.WAND_STONE, Inp.fromTag(ItemTags.STONE_TOOL_MATERIALS)); - wandRecipe(consumer, ModItems.WAND_IRON, Inp.fromTag(Tags.Items.INGOTS_IRON)); - wandRecipe(consumer, ModItems.WAND_DIAMOND, Inp.fromTag(Tags.Items.GEMS_DIAMOND)); - wandRecipe(consumer, ModItems.WAND_INFINITY, Inp.fromTag(Tags.Items.NETHER_STARS)); + wandRecipe(consumer, ModItems.WAND_STONE.get(), Inp.fromTag(ItemTags.STONE_TOOL_MATERIALS)); + wandRecipe(consumer, ModItems.WAND_IRON.get(), Inp.fromTag(Tags.Items.INGOTS_IRON)); + wandRecipe(consumer, ModItems.WAND_DIAMOND.get(), Inp.fromTag(Tags.Items.GEMS_DIAMOND)); + wandRecipe(consumer, ModItems.WAND_INFINITY.get(), Inp.fromTag(Tags.Items.NETHER_STARS)); - coreRecipe(consumer, ModItems.CORE_ANGEL, Inp.fromTag(Tags.Items.FEATHERS), Inp.fromTag(Tags.Items.INGOTS_GOLD)); - coreRecipe(consumer, ModItems.CORE_DESTRUCTION, Inp.fromTag(Tags.Items.STORAGE_BLOCKS_DIAMOND), Inp.fromItem(Items.DIAMOND_PICKAXE)); + coreRecipe(consumer, ModItems.CORE_ANGEL.get(), Inp.fromTag(Tags.Items.FEATHERS), Inp.fromTag(Tags.Items.INGOTS_GOLD)); + coreRecipe(consumer, ModItems.CORE_DESTRUCTION.get(), Inp.fromTag(Tags.Items.STORAGE_BLOCKS_DIAMOND), Inp.fromItem(Items.DIAMOND_PICKAXE)); specialRecipe(consumer, RecipeWandUpgrade.SERIALIZER); } diff --git a/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java b/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java index 682d758..ed0ca4d 100644 --- a/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java +++ b/src/main/java/thetadev/constructionwand/integrations/jei/ConstructionWandJeiPlugin.java @@ -11,6 +11,7 @@ import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraftforge.registries.RegistryObject; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.ConfigClient; import thetadev.constructionwand.basics.ConfigServer; @@ -43,10 +44,11 @@ public class ConstructionWandJeiPlugin implements IModPlugin Component wandModeComponent = keyComboComponent(ConfigClient.SHIFTOPT_MODE.get(), optkeyComponent); Component wandGuiComponent = keyComboComponent(ConfigClient.SHIFTOPT_GUI.get(), optkeyComponent); - for(Item wand : ModItems.WANDS) { + for(RegistryObject wandSupplier : ModItems.WANDS) { + Item wand = wandSupplier.get(); ConfigServer.WandProperties wandProperties = ConfigServer.getWandProperties(wand); - String durabilityKey = wand == ModItems.WAND_INFINITY ? "unlimited" : "limited"; + String durabilityKey = wand == ModItems.WAND_INFINITY.get() ? "unlimited" : "limited"; Component durabilityComponent = new TranslatableComponent(baseKey + "durability." + durabilityKey, wandProperties.getDurability()); registration.addIngredientInfo(new ItemStack(wand), VanillaTypes.ITEM, @@ -57,7 +59,8 @@ public class ConstructionWandJeiPlugin implements IModPlugin ); } - for(Item core : ModItems.CORES) { + for(RegistryObject coreSupplier : ModItems.CORES) { + Item core = coreSupplier.get(); registration.addIngredientInfo(new ItemStack(core), VanillaTypes.ITEM, new TranslatableComponent(baseKey + core.getRegistryName().getPath()), new TranslatableComponent(baseKey + "core", wandModeComponent) diff --git a/src/main/java/thetadev/constructionwand/items/ItemBase.java b/src/main/java/thetadev/constructionwand/items/ItemBase.java deleted file mode 100644 index f47d0dc..0000000 --- a/src/main/java/thetadev/constructionwand/items/ItemBase.java +++ /dev/null @@ -1,12 +0,0 @@ -package thetadev.constructionwand.items; - -import net.minecraft.world.item.Item; -import thetadev.constructionwand.ConstructionWand; - -public class ItemBase extends Item -{ - public ItemBase(String name, Properties properties) { - super(properties); - setRegistryName(ConstructionWand.MODID, name); - } -} diff --git a/src/main/java/thetadev/constructionwand/items/ModItems.java b/src/main/java/thetadev/constructionwand/items/ModItems.java index 14c3255..aabd39d 100644 --- a/src/main/java/thetadev/constructionwand/items/ModItems.java +++ b/src/main/java/thetadev/constructionwand/items/ModItems.java @@ -12,8 +12,7 @@ import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.registries.IForgeRegistry; -import net.minecraftforge.registries.IForgeRegistryEntry; +import net.minecraftforge.registries.*; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.crafting.RecipeWandUpgrade; @@ -23,38 +22,24 @@ import thetadev.constructionwand.items.wand.ItemWand; import thetadev.constructionwand.items.wand.ItemWandBasic; import thetadev.constructionwand.items.wand.ItemWandInfinity; -import java.util.Arrays; -import java.util.HashSet; - @Mod.EventBusSubscriber(modid = ConstructionWand.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModItems { + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ConstructionWand.MODID); + // Wands - public static final Item WAND_STONE = new ItemWandBasic("stone_wand", propWand(), Tiers.STONE); - public static final Item WAND_IRON = new ItemWandBasic("iron_wand", propWand(), Tiers.IRON); - public static final Item WAND_DIAMOND = new ItemWandBasic("diamond_wand", propWand(), Tiers.DIAMOND); - public static final Item WAND_INFINITY = new ItemWandInfinity("infinity_wand", propWand()); + public static final RegistryObject WAND_STONE = ITEMS.register("stone_wand", () -> new ItemWandBasic(propWand(), Tiers.STONE)); + public static final RegistryObject WAND_IRON = ITEMS.register("iron_wand", () -> new ItemWandBasic(propWand(), Tiers.IRON)); + public static final RegistryObject WAND_DIAMOND = ITEMS.register("diamond_wand", () -> new ItemWandBasic(propWand(), Tiers.DIAMOND)); + public static final RegistryObject WAND_INFINITY = ITEMS.register("infinity_wand", () -> new ItemWandInfinity(propWand())); // Cores - public static final Item CORE_ANGEL = new ItemCoreAngel("core_angel", propUpgrade()); - public static final Item CORE_DESTRUCTION = new ItemCoreDestruction("core_destruction", propUpgrade()); + public static final RegistryObject CORE_ANGEL = ITEMS.register("core_angel", () -> new ItemCoreAngel(propUpgrade())); + public static final RegistryObject CORE_DESTRUCTION = ITEMS.register("core_destruction", () -> new ItemCoreDestruction(propUpgrade())); // Collections - public static final Item[] WANDS = {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; - public static final Item[] CORES = {CORE_ANGEL, CORE_DESTRUCTION}; - public static final HashSet ALL_ITEMS = new HashSet<>(); - - - @SubscribeEvent - public static void registerItems(RegistryEvent.Register event) { - IForgeRegistry r = event.getRegistry(); - - r.registerAll(WANDS); - ALL_ITEMS.addAll(Arrays.asList(WANDS)); - - registerItem(r, CORE_ANGEL); - registerItem(r, CORE_DESTRUCTION); - } + public static final RegistryObject[] WANDS = new RegistryObject[] {WAND_STONE, WAND_IRON, WAND_DIAMOND, WAND_INFINITY}; + public static final RegistryObject[] CORES = new RegistryObject[] {CORE_ANGEL, CORE_DESTRUCTION}; public static Item.Properties propWand() { return new Item.Properties().tab(CreativeModeTab.TAB_TOOLS); @@ -64,11 +49,6 @@ public class ModItems return new Item.Properties().tab(CreativeModeTab.TAB_MISC).stacksTo(1); } - private static void registerItem(IForgeRegistry reg, Item item) { - reg.register(item); - ALL_ITEMS.add(item); - } - @SubscribeEvent public static void registerRecipeSerializers(RegistryEvent.Register> event) { IForgeRegistry> r = event.getRegistry(); @@ -77,7 +57,8 @@ public class ModItems @OnlyIn(Dist.CLIENT) public static void registerModelProperties() { - for(Item item : WANDS) { + for(RegistryObject itemSupplier : WANDS) { + Item item = itemSupplier.get(); ItemProperties.register( item, ConstructionWand.loc("using_core"), (stack, world, entity, n) -> entity == null || !(stack.getItem() instanceof ItemWand) ? 0 : @@ -90,7 +71,8 @@ public class ModItems public static void registerItemColors() { ItemColors colors = Minecraft.getInstance().getItemColors(); - for(Item item : WANDS) { + for(RegistryObject itemSupplier : WANDS) { + Item item = itemSupplier.get(); colors.register((stack, layer) -> (layer == 1 && stack.getItem() instanceof ItemWand) ? new WandOptions(stack).cores.get().getColor() : -1, item); } diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCore.java b/src/main/java/thetadev/constructionwand/items/core/ItemCore.java index fb3b93b..6b3362c 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCore.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCore.java @@ -3,6 +3,7 @@ package thetadev.constructionwand.items.core; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; @@ -10,15 +11,14 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import thetadev.constructionwand.ConstructionWand; import thetadev.constructionwand.api.IWandCore; -import thetadev.constructionwand.items.ItemBase; import javax.annotation.Nonnull; import java.util.List; -public abstract class ItemCore extends ItemBase implements IWandCore +public abstract class ItemCore extends Item implements IWandCore { - public ItemCore(String name, Properties properties) { - super(name, properties); + public ItemCore(Properties properties) { + super(properties); } @OnlyIn(Dist.CLIENT) diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java index fc7a05a..5062761 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreAngel.java @@ -5,8 +5,8 @@ import thetadev.constructionwand.wand.action.ActionAngel; public class ItemCoreAngel extends ItemCore { - public ItemCoreAngel(String name, Properties properties) { - super(name, properties); + public ItemCoreAngel(Properties properties) { + super(properties); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java index e243f06..bb2b5d3 100644 --- a/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java +++ b/src/main/java/thetadev/constructionwand/items/core/ItemCoreDestruction.java @@ -5,8 +5,8 @@ import thetadev.constructionwand.wand.action.ActionDestruction; public class ItemCoreDestruction extends ItemCore { - public ItemCoreDestruction(String name, Properties properties) { - super(name, properties); + public ItemCoreDestruction(Properties properties) { + super(properties); } @Override diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java index 5e19cc7..4fa3529 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWand.java @@ -9,6 +9,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.context.UseOnContext; @@ -25,17 +26,16 @@ import thetadev.constructionwand.basics.option.IOption; import thetadev.constructionwand.basics.option.WandOptions; import thetadev.constructionwand.data.ICustomItemModel; import thetadev.constructionwand.data.ItemModelGenerator; -import thetadev.constructionwand.items.ItemBase; import thetadev.constructionwand.wand.WandJob; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.List; -public abstract class ItemWand extends ItemBase implements ICustomItemModel +public abstract class ItemWand extends Item implements ICustomItemModel { - public ItemWand(String name, Properties properties) { - super(name, properties); + public ItemWand(Properties properties) { + super(properties); } @Nonnull diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java index 518954b..3db9d28 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandBasic.java @@ -10,8 +10,8 @@ public class ItemWandBasic extends ItemWand { private final Tier tier; - public ItemWandBasic(String name, Properties properties, Tier tier) { - super(name, properties.durability(tier.getUses())); + public ItemWandBasic(Properties properties, Tier tier) { + super(properties.durability(tier.getUses())); this.tier = tier; } diff --git a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java index 8907001..16bcc8a 100644 --- a/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java +++ b/src/main/java/thetadev/constructionwand/items/wand/ItemWandInfinity.java @@ -3,7 +3,7 @@ package thetadev.constructionwand.items.wand; public class ItemWandInfinity extends ItemWand { - public ItemWandInfinity(String name, Properties properties) { - super(name, properties.stacksTo(1).fireResistant()); + public ItemWandInfinity(Properties properties) { + super(properties.stacksTo(1).fireResistant()); } } diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index 313b6c8..b35f6f6 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -74,7 +74,7 @@ public class WandJob public void getSnapshots() { int limit; // Infinity wand gets enhanced limit in creative mode - if(player.isCreative() && wandItem == ModItems.WAND_INFINITY) limit = ConfigServer.LIMIT_CREATIVE.get(); + if(player.isCreative() && wandItem == ModItems.WAND_INFINITY.get()) limit = ConfigServer.LIMIT_CREATIVE.get(); else limit = Math.min(wandItem.remainingDurability(wand), wandAction.getLimit(wand)); if(rayTraceResult.getType() == HitResult.Type.BLOCK) From 710a6a7ba15e16646aea2b57a0c67ab5746eda9b Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 26 Mar 2022 00:17:52 +0100 Subject: [PATCH 68/78] remove superfluous inventory checks --- src/main/java/thetadev/constructionwand/basics/WandUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/thetadev/constructionwand/basics/WandUtil.java b/src/main/java/thetadev/constructionwand/basics/WandUtil.java index 418ec5c..aa98cbe 100644 --- a/src/main/java/thetadev/constructionwand/basics/WandUtil.java +++ b/src/main/java/thetadev/constructionwand/basics/WandUtil.java @@ -160,7 +160,7 @@ public class WandUtil List inventory = WandUtil.getFullInv(player); for(ItemStack stack : inventory) { - if(stack == null) continue; + if(stack == null || stack.isEmpty()) continue; if(WandUtil.stackEquals(stack, item)) { total += stack.getCount(); From aefb3e138b421063d1b193989688eca5384ee858 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 26 Mar 2022 00:18:18 +0100 Subject: [PATCH 69/78] update Botania integration for MC1.18.1 --- build.gradle | 2 -- gradle.properties | 6 ++--- .../containers/ContainerRegistrar.java | 6 ++--- .../containers/handlers/HandlerBotania.java | 27 +++++++++++-------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/build.gradle b/build.gradle index 5b50303..546e606 100644 --- a/build.gradle +++ b/build.gradle @@ -104,14 +104,12 @@ dependencies { compileOnly fg.deobf("mezz.jei:${jei_version}:api") runtimeOnly fg.deobf("mezz.jei:${jei_version}") - /* compileOnly fg.deobf([ group: "vazkii.botania", name: "Botania", version: "${project.botania}", classifier: "api" ]) - */ } jar { diff --git a/gradle.properties b/gradle.properties index 40a8f6b..379cdfb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,14 +5,14 @@ author=thetadev modid=constructionwand mcversion=1.18.1 -forgeversion=39.0.0 +forgeversion=39.1.0 mcp_channel=official mcp_mappings=1.18.1 # Source: https://maven.blamejared.com/vazkii/botania/Botania/ -# botania=1.16.2-405 +botania=1.18.1-429 # Source: https://dvs1.progwml6.com/files/maven/mezz/jei/ jei_version=jei-1.18.1:9.1.0.41 version_major=2 -version_minor=6 \ No newline at end of file +version_minor=7 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java index 4c3b8e6..e9e87f6 100644 --- a/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java +++ b/src/main/java/thetadev/constructionwand/containers/ContainerRegistrar.java @@ -1,6 +1,8 @@ package thetadev.constructionwand.containers; +import net.minecraftforge.fml.ModList; import thetadev.constructionwand.ConstructionWand; +import thetadev.constructionwand.containers.handlers.HandlerBotania; import thetadev.constructionwand.containers.handlers.HandlerBundle; import thetadev.constructionwand.containers.handlers.HandlerCapability; import thetadev.constructionwand.containers.handlers.HandlerShulkerbox; @@ -12,13 +14,9 @@ public class ContainerRegistrar ConstructionWand.instance.containerManager.register(new HandlerShulkerbox()); ConstructionWand.instance.containerManager.register(new HandlerBundle()); - /* - TODO: Reenable this when Botania gets ported to 1.17 - if(ModList.get().isLoaded("botania")) { ConstructionWand.instance.containerManager.register(new HandlerBotania()); ConstructionWand.LOGGER.info("Botania integration added"); } - */ } } diff --git a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java index 2d41e4b..8ffb651 100644 --- a/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java +++ b/src/main/java/thetadev/constructionwand/containers/handlers/HandlerBotania.java @@ -1,25 +1,28 @@ -/* -TODO: Reenable this when Botania gets ported to 1.17 - package thetadev.constructionwand.containers.handlers; -import net.minecraft.world.level.block.Block; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; import thetadev.constructionwand.api.IContainerHandler; +import vazkii.botania.api.BotaniaForgeCapabilities; import vazkii.botania.api.item.IBlockProvider; +import java.util.Optional; + public class HandlerBotania implements IContainerHandler { @Override public boolean matches(Player player, ItemStack itemStack, ItemStack inventoryStack) { - return inventoryStack != null && inventoryStack.getCount() == 1 && inventoryStack.getItem() instanceof IBlockProvider; + return inventoryStack != null && inventoryStack.getCapability(BotaniaForgeCapabilities.BLOCK_PROVIDER).isPresent(); } @Override public int countItems(Player player, ItemStack itemStack, ItemStack inventoryStack) { - IBlockProvider prov = (IBlockProvider) inventoryStack.getItem(); - int provCount = prov.getBlockCount(player, itemStack, inventoryStack, Block.byItem(itemStack.getItem())); + Optional provOptional = inventoryStack.getCapability(BotaniaForgeCapabilities.BLOCK_PROVIDER).resolve(); + if(provOptional.isEmpty()) return 0; + + IBlockProvider prov = provOptional.get(); + int provCount = prov.getBlockCount(player, inventoryStack, Block.byItem(itemStack.getItem())); if(provCount == -1) return Integer.MAX_VALUE; return provCount; @@ -27,10 +30,12 @@ public class HandlerBotania implements IContainerHandler @Override public int useItems(Player player, ItemStack itemStack, ItemStack inventoryStack, int count) { - IBlockProvider prov = (IBlockProvider) inventoryStack.getItem(); - if(prov.provideBlock(player, itemStack, inventoryStack, Block.byItem(itemStack.getItem()), true)) + Optional provOptional = inventoryStack.getCapability(BotaniaForgeCapabilities.BLOCK_PROVIDER).resolve(); + if(provOptional.isEmpty()) return 0; + + IBlockProvider prov = provOptional.get(); + if(prov.provideBlock(player, inventoryStack, Block.byItem(itemStack.getItem()), true)) return 0; return count; } -} -*/ \ No newline at end of file +} \ No newline at end of file From d69901c0fa2798329f282d91c699a675dce18683 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 12 Feb 2023 17:47:50 +0100 Subject: [PATCH 70/78] fix: wands not breaking when out of durability --- src/main/java/thetadev/constructionwand/wand/WandJob.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index b35f6f6..b294afa 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -1,8 +1,8 @@ package thetadev.constructionwand.wand; import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; @@ -99,7 +99,7 @@ public class WandJob // If the item cant be taken, undo the placement if(wandSupplier.takeItemStack(snapshot.getRequiredItems()) == 0) { executed.add(snapshot); - wand.hurt(1, player.getRandom(), (ServerPlayer) player); + wand.hurtAndBreak(1, player, e -> e.broadcastBreakEvent(InteractionHand.MAIN_HAND)); } else { ConstructionWand.LOGGER.info("Item could not be taken. Remove block: " + From a9f703e28f4f203230878af07cd4f5687975564b Mon Sep 17 00:00:00 2001 From: gjeodnd12165 <61226524+gjeodnd12165@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:17:01 +0900 Subject: [PATCH 71/78] Create ko_kr.json --- .../assets/constructionwand/lang/ko_kr.json | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/assets/constructionwand/lang/ko_kr.json diff --git a/src/main/resources/assets/constructionwand/lang/ko_kr.json b/src/main/resources/assets/constructionwand/lang/ko_kr.json new file mode 100644 index 0000000..47df0b2 --- /dev/null +++ b/src/main/resources/assets/constructionwand/lang/ko_kr.json @@ -0,0 +1,70 @@ +{ + "item.constructionwand.stone_wand": "돌 완드", + "item.constructionwand.iron_wand": "철 완드", + "item.constructionwand.diamond_wand": "다이아몬드 완드", + "item.constructionwand.infinity_wand": "무한의 완드", + "item.constructionwand.core_angel": "천사 완드 코어", + "item.constructionwand.core_destruction": "파괴 완드 코어", + + "constructionwand.tooltip.blocks": "최대. %d 블록", + "constructionwand.tooltip.shift": "[SHIFT]를 누르세요.", + "constructionwand.tooltip.cores": "완드 코어:", + "constructionwand.tooltip.core_tip": "조합창에서 코어와 완드를 합치세요.", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "생성 코어", + "constructionwand.option.cores.constructionwand:default.desc": "당신 쪽으로 건물을 확장합니다.", + "constructionwand.option.cores.constructionwand:core_angel": "§6천사 코어", + "constructionwand.option.cores.constructionwand:core_angel.desc": "블록 뒤와 공중에 배치합니다.", + "constructionwand.option.cores.constructionwand:core_destruction": "§c파괴 코어", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "당신 쪽의 블록을 파괴합니다.", + + "constructionwand.option.lock": "제한: ", + "constructionwand.option.lock.horizontal": "§a오른쪽/왼쪽", + "constructionwand.option.lock.horizontal.desc": "원래 블록의 앞에 수평한 열을 만듭니다.", + "constructionwand.option.lock.vertical": "§a위/아래", + "constructionwand.option.lock.vertical.desc": "원래 블록의 앞에 수직한 열을 만듭니다.", + "constructionwand.option.lock.northsouth": "§6북쪽/남쪽", + "constructionwand.option.lock.northsouth.desc": "원래 블록의 위에 북/남 방향으로 행을 만듭니다.", + "constructionwand.option.lock.eastwest": "§6동쪽/서쪽", + "constructionwand.option.lock.eastwest.desc": "원래 블록의 위에 동/서 방향으로 행을 만듭니다.", + "constructionwand.option.lock.nolock": "§c없음", + "constructionwand.option.lock.nolock.desc": "원래 블록의 어느 방향으로도 확장합니다.", + + "constructionwand.option.direction": "방향: ", + "constructionwand.option.direction.target": "§6대상", + "constructionwand.option.direction.target.desc": "대상 블록과 같은 방향으로 블록을 배치합니다.", + "constructionwand.option.direction.player": "§a플레이어", + "constructionwand.option.direction.player.desc": "플레이어를 향해 블록을 배치합니다.", + + "constructionwand.option.replace": "재배치: ", + "constructionwand.option.replace.yes": "§a예", + "constructionwand.option.replace.yes.desc": "유체, 눈, 키 큰 잔디와 같은 특정 블록을 교체합니다.", + "constructionwand.option.replace.no": "§c아니오", + "constructionwand.option.replace.no.desc": "블록을 재배치하지 않습니다.", + + "constructionwand.option.match": "비교: ", + "constructionwand.option.match.exact": "§a정확", + "constructionwand.option.match.exact.desc": "완전히 같은 블록만 확장합니다.", + "constructionwand.option.match.similar": "§6유사", + "constructionwand.option.match.similar.desc": "비슷한 블록(흙/잔디)을 똑같이 취급합니다.", + "constructionwand.option.match.any": "§c아무거나", + "constructionwand.option.match.any.desc": "아무 블록이나 확장합니다.", + + "constructionwand.option.random": "무작위: ", + "constructionwand.option.random.yes": "§a예", + "constructionwand.option.random.yes.desc": "핫바에 있는 블록 중 무작위적으로 배치합니다.", + "constructionwand.option.random.no": "§c아니오", + "constructionwand.option.random.no.desc": "배치할 블록을 무작위적으로 하지 않습니다.", + + "constructionwand.description.wand": "%1$s는 당신 쪽으로 최대 %2$d 블록까지 배치할 수 있고, %3$s 지속됩니다.\n\n%5$s을(를) 누르고 스크롤 하여 배치 제한을 바꾸세요 (수평, 수직, 북쪽/남쪽, 동쪽/서쪽, 제한 없음).\n\n%6$s§9+우클릭§0으로 옵션 스크린을 여세요.\n\n§5§n실행 취소§0§r\n블록을 보면서 §9웅크리기+§0%4$s를 누르고 있으면 마지막으로 배치했던 블록들이 녹색 테두리로 표시됩니다. 그 중 아무거나 §9S웅크리기+§0%4$s§9+우클릭§0 하면 그 작업을 실행 취소하고, 모든 아이템을 돌려줍니다. 파괴 코어를 사용했다면, 블록들을 복원합니다.\n\n§5§n컨테이너§0§r\n셜커 상자, 꾸러미, 그리고 다른 모드의 컨테이너들은 완드에 건설 블록을 제공할 수 있습니다.\n\n§5§n보조손 우선도§0§r\n보조 손에 블록을 가지고 있으면 보고 있는 블록을 배치하는 대신에 보조 손의 블록을 배치할 것입니다.", + "constructionwand.description.durability.limited": "%d 블록 만큼", + "constructionwand.description.durability.unlimited": "영원히", + "constructionwand.description.key.sneak": "웅크리기", + "constructionwand.description.key.sneak_opt": "웅크리기+%s", + "constructionwand.description.core": "§5§n설치§0§r\n새 코어를 완드와 함께 조합창에 넣어 설치하세요. 코어 간에 전환하려면 %s 키를 누른 상태에서 완드로 빈 공간을 좌클릭하거나 옵션 화면을 사용하십시오.", + "constructionwand.description.core_angel": "엔젤 코어는 마주보고 있는 블록(또는 블록 행)의 반대쪽에 블록을 배치합니다. 최대 거리는 완드의 티어에 따라 다릅니다. 빈 공간을 우클릭하면 공중에 블록을 배치할 수 있습니다. 그렇게 하려면 보조 손에 배치하려는 블록이 있어야 합니다.", + "constructionwand.description.core_destruction": "파괴 코어는 당신 쪽의 (타일 엔티티가 없는)블록을 파괴합니다. 최대 블록 수는 완드의 티어에 따라 다릅니다. 파괴된 블록은 공허로 사라지며 실수를 했다면 실행 취소 기능을 사용할 수 있습니다.", + + "stat.constructionwand.use_wand": "완드로 배치한 블록 수" + } From e83cae7ae21022c8c358b58323a29805442364c6 Mon Sep 17 00:00:00 2001 From: mango_buff <106330917+buff-mango@users.noreply.github.com> Date: Thu, 24 Nov 2022 09:53:40 +0800 Subject: [PATCH 72/78] Create zh_cn.json --- .../assets/constructionwand/lang/zh_cn.json | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/assets/constructionwand/lang/zh_cn.json diff --git a/src/main/resources/assets/constructionwand/lang/zh_cn.json b/src/main/resources/assets/constructionwand/lang/zh_cn.json new file mode 100644 index 0000000..013bf8a --- /dev/null +++ b/src/main/resources/assets/constructionwand/lang/zh_cn.json @@ -0,0 +1,70 @@ +{ + "item.constructionwand.stone_wand": "石制手杖", + "item.constructionwand.iron_wand": "铁制手杖", + "item.constructionwand.diamond_wand": "钻石手杖", + "item.constructionwand.infinity_wand": "无尽手杖", + "item.constructionwand.core_angel": "天使手杖核心", + "item.constructionwand.core_destruction": "破坏手杖核心", + + "constructionwand.tooltip.blocks": "最多放置%d个方块", + "constructionwand.tooltip.shift": "按 [SHIFT]", + "constructionwand.tooltip.cores": "手杖核心:", + "constructionwand.tooltip.core_tip": "将手杖核心与手杖组合在一起", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "建筑核心", + "constructionwand.option.cores.constructionwand:default.desc": "在面向你的一侧放置方块", + "constructionwand.option.cores.constructionwand:core_angel": "§6天使核心", + "constructionwand.option.cores.constructionwand:core_angel.desc": "在面向你的方块的背面放置方块,还可以悬空放置方块", + "constructionwand.option.cores.constructionwand:core_destruction": "§c毁灭核心", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "破坏面向你一侧的方块", + + "constructionwand.option.lock": "锁定: ", + "constructionwand.option.lock.horizontal": "§a左 / 右", + "constructionwand.option.lock.horizontal.desc": "在起始方块的前面延伸一行水平方块", + "constructionwand.option.lock.vertical": "§a上 / 下", + "constructionwand.option.lock.vertical.desc": "在起始方块的前面延伸一列竖直方块", + "constructionwand.option.lock.northsouth": "§6南 / 北", + "constructionwand.option.lock.northsouth.desc": "在起始方块的上面,向南 / 北方向延伸一行", + "constructionwand.option.lock.eastwest": "§6东 / 西", + "constructionwand.option.lock.eastwest.desc": "在起始方块的上面,向东 / 西方向延伸一行", + "constructionwand.option.lock.nolock": "§c无", + "constructionwand.option.lock.nolock.desc": "从原始块的任意一面延伸", + + "constructionwand.option.direction": "方向: ", + "constructionwand.option.direction.target": "§6目标", + "constructionwand.option.direction.target.desc": "放置与的方块方向与目标方块的方向相同", + "constructionwand.option.direction.player": "§a玩家", + "constructionwand.option.direction.player.desc": "放置的方块面向玩家", + + "constructionwand.option.replace": "替换: ", + "constructionwand.option.replace.yes": "§a是", + "constructionwand.option.replace.yes.desc": "替换某些方块,如液体、雪、高草丛", + "constructionwand.option.replace.no": "§c否", + "constructionwand.option.replace.no.desc": "不替换方块", + + "constructionwand.option.match": "匹配: ", + "constructionwand.option.match.exact": "§a精确", + "constructionwand.option.match.exact.desc": "仅放置完全相同的方块", + "constructionwand.option.match.similar": "§6模糊", + "constructionwand.option.match.similar.desc": "相似的方块被认为是相同的(草方块 / 泥土类型)", + "constructionwand.option.match.any": "§c任意", + "constructionwand.option.match.any.desc": "放置任何方块", + + "constructionwand.option.random": "随机: ", + "constructionwand.option.random.yes": "§a是", + "constructionwand.option.random.yes.desc": "随机放置快捷栏中的方块", + "constructionwand.option.random.no": "§c否", + "constructionwand.option.random.no.desc": "不会随机放置方块", + + "constructionwand.description.wand": "%1$s可以在建筑物面向你的一侧放置最多%2$d个方块,持续时间为%3$s。\n\n按住%5$s并滚动以更改放置限制(水平、垂直、北/南、东/西、无锁定)。\n\n在选项配置GUI上打开%6$s§9+右键单击§0。\n\n§5§nUNDO§0§r\n在查看方块时向下折叠§9Sneak+§0%4$s将显示你放置的最后一个方块,并在其周围加上绿色边框。§9潜行+§0%4$s§9+右键单击其中任何一个方块将撤消操作,并将所有以此法放置的方块重返至玩家背包。如果你使用了破坏核心,它将恢复方块。\n\n§5§n容器§0§r\n潜影盒、收纳袋和许多其它模组存在于玩家背包内的容器都可以为建筑手杖提供构建所需的方块。\n\n§5§非即时优先级§0§r\n如果玩家在使用手杖时副手栏持有所需方块将被放置,而不是只是在你的手里放着。", + "constructionwand.description.durability.limited": "需要%d方块", + "constructionwand.description.durability.unlimited": "无限", + "constructionwand.description.key.sneak": "潜行", + "constructionwand.description.key.sneak_opt": "潜行+%s", + "constructionwand.description.core": "§5§n安装§0§r\n将新的手杖核心与你的手杖一起放入工作台中进行组装。如果你想要在核心功能之间切换,请按住%s并用手杖左键单击空地或使用手杖的选项配置GUI。", + "constructionwand.description.core_angel": "天使核心可将一个方块放置在你所面对的方块(或一排方块)的对面。最大距离取决于手杖材质。在空地上手持手杖并单击鼠标右键即可在空中放置方块。要做到这一点。你需要将想要被在空中放置的方块放在你的副手栏中。", + "constructionwand.description.core_destruction": "毁灭核心会破坏面向你一侧的方块(破坏时被破坏的方块不可存在实体)。最大破坏方块数取决于手杖材质。被使用毁灭核心破坏的方块会消失。如果你只是不小心使用了毁灭核心。可以使用“撤消”功能以撤回被破坏并消失的物品返回原处。", + + "stat.constructionwand.use_wand": "使用建筑手杖所放置的方块" +} From d006a24ab722744f5d5d67750a9523b7e5d9a301 Mon Sep 17 00:00:00 2001 From: FITFC <101124415+FITFC@users.noreply.github.com> Date: Sat, 29 Oct 2022 17:19:59 -0500 Subject: [PATCH 73/78] added pt_br.json --- .../assets/constructionwand/lang/pt_br.json | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/assets/constructionwand/lang/pt_br.json diff --git a/src/main/resources/assets/constructionwand/lang/pt_br.json b/src/main/resources/assets/constructionwand/lang/pt_br.json new file mode 100644 index 0000000..b4c06aa --- /dev/null +++ b/src/main/resources/assets/constructionwand/lang/pt_br.json @@ -0,0 +1,70 @@ +{ + "item.constructionwand.stone_wand": "Varinha de pedra", + "item.constructionwand.iron_wand": "Varinha de ferro", + "item.constructionwand.diamond_wand": "diamondWand", + "item.constructionwand.infinity_wand": "Varinha infinita", + "item.constructionwand.core_angel": "Angel Wand Core", + "item.constructionwand.core_destruction": "Destruction Wand Core", + + "constructionwand.tooltip.blocks": "Max. %d blocos", + "constructionwand.tooltip.shift": "Pressione Shift]", + "constructionwand.tooltip.cores": "Núcleos de varinhas:", + "constructionwand.tooltip.core_tip": "Combine o núcleo com sua varinha em uma grade de criação", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "Núcleo de construção", + "constructionwand.option.cores.constructionwand:default.desc": "Estender seu prédio do lado de frente para você", + "constructionwand.option.cores.constructionwand:core_angel": "§6angelCore", + "constructionwand.option.cores.constructionwand:core_angel.desc": "Coloque atrás dos quarteirões e no meio do ar", + "constructionwand.option.cores.constructionwand:core_destruction": "§cNúcleo de destruição", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "Destrói blocos do lado de frente para você", + + "constructionwand.option.lock": "Restrição: ", + "constructionwand.option.lock.horizontal": "§aEsquerda direita", + "constructionwand.option.lock.horizontal.desc": "Construa uma coluna horizontal em frente ao bloco original", + "constructionwand.option.lock.vertical": "§aCima baixo", + "constructionwand.option.lock.vertical.desc": "Construa uma coluna vertical em frente ao bloco original", + "constructionwand.option.lock.northsouth": "§6Norte Sul", + "constructionwand.option.lock.northsouth.desc": "Construa uma linha na direção N/s no topo do bloco original", + "constructionwand.option.lock.eastwest": "§6Leste Oeste", + "constructionwand.option.lock.eastwest.desc": "Construa uma linha na direção E/W no topo do bloco original", + "constructionwand.option.lock.nolock": "§cNenhum", + "constructionwand.option.lock.nolock.desc": "Estender de qualquer lado do bloco original", + + "constructionwand.option.direction": "Direção: ", + "constructionwand.option.direction.target": "§6Alvo", + "constructionwand.option.direction.target.desc": "Coloque blocos com a mesma direção que o bloco de destino", + "constructionwand.option.direction.player": "§aJogadora", + "constructionwand.option.direction.player.desc": "Coloque blocos de frente para o jogador", + + "constructionwand.option.replace": "Substituição: ", + "constructionwand.option.replace.yes": "§aSim", + "constructionwand.option.replace.yes.desc": "Substitua certos blocos como fluidos, neve e capim alto", + "constructionwand.option.replace.no": "§cNão", + "constructionwand.option.replace.no.desc": "Não substitua blocos", + + "constructionwand.option.match": "Coincidindo: ", + "constructionwand.option.match.exact": "§aExata", + "constructionwand.option.match.exact.desc": "Estender apenas blocos que são exatamente iguais", + "constructionwand.option.match.similar": "§6Semelhante", + "constructionwand.option.match.similar.desc": "Tratar blocos semelhantes (tipos de sujeira/grama) igualmente", + "constructionwand.option.match.any": "§cAlguma", + "constructionwand.option.match.any.desc": "Estender qualquer bloco", + + "constructionwand.option.random": "Aleatório: ", + "constructionwand.option.random.yes": "§aSim", + "constructionwand.option.random.yes.desc": "Coloque blocos aleatórios presentes em seu hotbar", + "constructionwand.option.random.no": "§cNão", + "constructionwand.option.random.no.desc": "Não randomize blocos colocados", + + "constructionwand.description.wand": "o %1$s pode colocar até %2$d bloqueios ao lado de um prédio de frente para você e dura %3$s.\n\nCalma %5$s e role para alterar a restrição de posicionamento (horizontal, vertical, norte/sul, leste/oeste, sem fechadura).\n\nAbra a tela de opção com %6$s§9+Clique com o botão direito do mouse§0.\n\n§5§nDESFAZER§0§r\nMantendo pressionada §9Esgueirar-se+§0%4$s Enquanto olha para um bloco, mostrará os últimos blocos que você colocou com uma borda verde ao redor deles. §9Esgueirar-se+§0%4$s§9+Certa clicando§0 Qualquer um deles desfazerá a operação, oferecendo todos os itens de volta.Se você usou o núcleo de destruição, ele restaurará os blocos.\n\n§5§nRECIPIENTES§0§r\nCaixas Shulker, pacotes e muitos contêineres de outros mods podem fornecer blocos de construção para a varinha.\n\n§5§nPrioridade imediata§0§r\nTer blocos em sua mão os colocará em vez do bloco que você está olhando.", + "constructionwand.description.durability.limited": "por %d blocos", + "constructionwand.description.durability.unlimited": "para todo sempre", + "constructionwand.description.key.sneak": "Esgueirar-se", + "constructionwand.description.key.sneak_opt": "Esgueirar-se+%s", + "constructionwand.description.core": "§5§nINSTALAÇÃO§0§r\nColoque seu novo núcleo junto com sua varinha em uma grade de criação para instalá -la.Para alternar entre núcleos, mantenha pressionado %s e o clique esquerdo, esvazie o espaço com sua varinha ou use a tela de opção.", + "constructionwand.description.core_angel": "O núcleo do anjo coloca um bloco no lado oposto do bloco (ou fileira de blocos) que você está enfrentando.A distância máxima depende da camada de varinha.Clique com o botão direito do mouse em espaço vazio para colocar um bloco no ar.Para fazer isso, você precisará ter o bloco que deseja colocar em sua mão.", + "constructionwand.description.core_destruction": "O núcleo de destruição destrói blocos (sem entidades de ladrilhos) do lado de frente para você.O número máximo de blocos depende da camada de varinha.Blocos destruídos desaparecem no vazio, você pode usar o recurso de desfazer se cometer um erro.", + + "stat.constructionwand.use_wand": "Blocos colocados usando varinha" + } \ No newline at end of file From 2497f85800fcf21348d8b8a0a68e28f38c2e58db Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 12 Feb 2023 18:06:17 +0100 Subject: [PATCH 74/78] bump version to 2.9 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 379cdfb..7531e6a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ botania=1.18.1-429 jei_version=jei-1.18.1:9.1.0.41 version_major=2 -version_minor=7 \ No newline at end of file +version_minor=9 \ No newline at end of file From dc21a8882f70b0a9734e9e51c09cfc7582e428de Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Thu, 1 Jun 2023 14:30:44 +0200 Subject: [PATCH 75/78] update translations --- .../assets/constructionwand/lang/ru_ru.json | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/assets/constructionwand/lang/ru_ru.json diff --git a/src/main/resources/assets/constructionwand/lang/ru_ru.json b/src/main/resources/assets/constructionwand/lang/ru_ru.json new file mode 100644 index 0000000..5e7ec8c --- /dev/null +++ b/src/main/resources/assets/constructionwand/lang/ru_ru.json @@ -0,0 +1,70 @@ +{ + "item.constructionwand.stone_wand": "Каменный жезл", + "item.constructionwand.iron_wand": "Железный жезл", + "item.constructionwand.diamond_wand": "Алмазный жезл", + "item.constructionwand.infinity_wand": "Бесконечный жезл", + "item.constructionwand.core_angel": "Ангельское ядро для жезла", + "item.constructionwand.core_destruction": "Ядро разрушения для жезла", + + "constructionwand.tooltip.blocks": "Максимум %d блоков", + "constructionwand.tooltip.shift": "Нажмите [SHIFT]", + "constructionwand.tooltip.cores": "Ядер жезла:", + "constructionwand.tooltip.core_tip": "Объедините ядро со своим жезлом в сетке создания.", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "Ядро строительства", + "constructionwand.option.cores.constructionwand:default.desc": "Расширяйте свои строения на стороне, обращённой к Вам.", + "constructionwand.option.cores.constructionwand:core_angel": "§6Ангельское ядро", + "constructionwand.option.cores.constructionwand:core_angel.desc": "Размещает за блоками и в воздухе.", + "constructionwand.option.cores.constructionwand:core_destruction": "§cЯдро разрушения", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "Уничтожает блоки на стороне, обращённой к Вам.", + + "constructionwand.option.lock": "Ограничение: ", + "constructionwand.option.lock.horizontal": "§aВлево/Вправо", + "constructionwand.option.lock.horizontal.desc": "Строить горизонтальную колонну перед основным блоком.", + "constructionwand.option.lock.vertical": "§aВверх/Вниз", + "constructionwand.option.lock.vertical.desc": "Строить вертикальную колонну перед основным блоком.", + "constructionwand.option.lock.northsouth": "§6Север/Юг", + "constructionwand.option.lock.northsouth.desc": "Строить ряд в С/Ю направлении непосредственно за основным блоком.", + "constructionwand.option.lock.eastwest": "§6Восток/Запад", + "constructionwand.option.lock.eastwest.desc": "Строить ряд в В/З направлении непосредственно за основным блоком.", + "constructionwand.option.lock.nolock": "§cНичего", + "constructionwand.option.lock.nolock.desc": "Расширять с любой стороны основного блока.", + + "constructionwand.option.direction": "Направление: ", + "constructionwand.option.direction.target": "§6Цель", + "constructionwand.option.direction.target.desc": "Размещать блоки с таким же направлением как целевой блок.", + "constructionwand.option.direction.player": "§aИгрок", + "constructionwand.option.direction.player.desc": "Размещать блоки, обращённые к игроку.", + + "constructionwand.option.replace": "Замена: ", + "constructionwand.option.replace.yes": "§aДа", + "constructionwand.option.replace.yes.desc": "Заменять некоторые блоки как жидкости, снег и высокорослая трава.", + "constructionwand.option.replace.no": "§cНет", + "constructionwand.option.replace.no.desc": "Не заменять блоки.", + + "constructionwand.option.match": "Совпадение: ", + "constructionwand.option.match.exact": "§aТочное", + "constructionwand.option.match.exact.desc": "Расширять только абсолютно одинаковые блоки.", + "constructionwand.option.match.similar": "§6Похожее", + "constructionwand.option.match.similar.desc": "Подносить аналогичные блоки (пример: земля/трава) поровну.", + "constructionwand.option.match.any": "§cНикакое", + "constructionwand.option.match.any.desc": "Расширять любой блок.", + + "constructionwand.option.random": "Случайно: ", + "constructionwand.option.random.yes": "§aДа", + "constructionwand.option.random.yes.desc": "Размещать случайные блоки, имеющиеся в Вашей горячей панели.", + "constructionwand.option.random.no": "§cНет", + "constructionwand.option.random.no.desc": "Не располагать блоки в случайном порядке.", + + "constructionwand.description.wand": "%1$s может размещать до %2$d блоков сбоку от строения, обращённое к Вам и его хватит на %3$s блоков.\n\nУдерживайте %5$s и прокрутите колёсиком для изменения ограничения по размещении (Горизонтально, Вертикально, Север/Юг, Восток/Запад, Без ограничивания).\n\nОткройте экран настроек при помощи %6$s§9+щелчок правой кнопкой мыши§0.\n\n§5§nОТМЕНА§0§r\nУдерживайте §9Приседание+§0%4$s пока смотрите на блоки, установленные Вами, они будут выделены зелёным контуром. §9Приседание+§0%4$s§9+щелчок правой кнопкой мыши§0 на любой из них отменит операцию, вернув Вам все предметы обратно. Если использовать Ядро разрушения, то он вернёт блоки.\n\n§5§nКОНТЕЙНЕР§0§r\nШалкеровые ящики, мешки и множество контейнеров из других модов могут предоставлять строительные блоки в жезл.\n\n§5§nПРИОРИТЕТ ЛЕВОЙ РУКИ§0§r\nЕсли у Вас в левой руке находятся блоки, то они будут размещаться вместо блока, на который Вы смотрите.", + "constructionwand.description.durability.limited": "на %d блоков", + "constructionwand.description.durability.unlimited": "вечно", + "constructionwand.description.key.sneak": "Приседание", + "constructionwand.description.key.sneak_opt": "Приседание+%s", + "constructionwand.description.core": "§5§nУСТАНОВКА§0§r\nПоложите своё новое ядро вместе со своим жезлом в сетку создания для его установки. Для того, чтобы переключаться между ядрами, удерживайте %s и нажмите левую кнопку мыши по пустому пространству с жезлом в руке или используйте экран настроек.", + "constructionwand.description.core_angel": "Ангельское ядро размещает блоки на противоположной стороне блока (или ряда блоков), обращённые к Вам. Максимальное расстояние зависит от уровня жезла. Щелчок правой кнопкой мыши по пустому воздуху разместит блок в воздухе. Чтобы это сделать, Вам нужно иметь необходимые блоки в левой руке, чтобы разместить их.", + "constructionwand.description.core_destruction": "Ядро разрушения разрушает блоки (не функциональные блоки), обращённые к Вам. Максимально количество блоков зависит от уровня жезла. Разрушенные блоки исчезают в пустоту, можно использовать функцию отмены в случае допущенной ошибки.", + + "stat.constructionwand.use_wand": "Блоки, размещённые при помощи Жезла" +} From ce0012dc3ce624037e958b8a0bffdc0103af0623 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 24 Mar 2023 23:33:28 +0100 Subject: [PATCH 76/78] fix: 1-block previews on consecutive placements --- .../constructionwand/client/RenderBlockPreview.java | 8 ++++++-- src/main/java/thetadev/constructionwand/wand/WandJob.java | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java index b8dec20..56797eb 100644 --- a/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java +++ b/src/main/java/thetadev/constructionwand/client/RenderBlockPreview.java @@ -22,7 +22,7 @@ import java.util.Set; public class RenderBlockPreview { - public WandJob wandJob; + private WandJob wandJob; public Set undoBlocks; @SubscribeEvent @@ -39,7 +39,11 @@ public class RenderBlockPreview if(wand == null) return; if(!(player.isCrouching() && ClientEvents.isOptKeyDown())) { - if(wandJob == null || !compareRTR(wandJob.rayTraceResult, rtr) || !(wandJob.wand.equals(wand))) { + // Use cached wandJob for previews of the same target pos/dir + // Exception: always update if blockCount < 2 to prevent 1-block previews when block updates + // from the last placement are lagging + if(wandJob == null || !compareRTR(wandJob.rayTraceResult, rtr) || !(wandJob.wand.equals(wand)) + || wandJob.blockCount() < 2) { wandJob = ItemWand.getWandJob(player, player.level, rtr, wand); } blocks = wandJob.getBlockPositions(); diff --git a/src/main/java/thetadev/constructionwand/wand/WandJob.java b/src/main/java/thetadev/constructionwand/wand/WandJob.java index b294afa..0eb6269 100644 --- a/src/main/java/thetadev/constructionwand/wand/WandJob.java +++ b/src/main/java/thetadev/constructionwand/wand/WandJob.java @@ -87,6 +87,10 @@ public class WandJob return placeSnapshots.stream().map(ISnapshot::getPos).collect(Collectors.toSet()); } + public int blockCount() { + return placeSnapshots.size(); + } + public boolean doIt() { ArrayList executed = new ArrayList<>(); From 31bfbdb7f1e4debd7313e32d379683862c56a60d Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Thu, 1 Jun 2023 13:52:40 +0200 Subject: [PATCH 77/78] fix: #76 Logic Issue with Placement --- gradle.properties | 2 +- .../constructionwand/wand/undo/PlaceSnapshot.java | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7531e6a..1d83096 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ botania=1.18.1-429 jei_version=jei-1.18.1:9.1.0.41 version_major=2 -version_minor=9 \ No newline at end of file +version_minor=11 \ No newline at end of file diff --git a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java index ca17038..732c2b0 100644 --- a/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java +++ b/src/main/java/thetadev/constructionwand/wand/undo/PlaceSnapshot.java @@ -6,8 +6,6 @@ import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; 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.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.Property; @@ -100,7 +98,7 @@ public class PlaceSnapshot implements ISnapshot // Can block be placed? BlockState blockState = item.getBlock().getStateForPlacement(ctx); - if(blockState == null) return null; + if(blockState == null || !blockState.canSurvive(world, pos)) return null; // Forbidden Tile Entity? if(!WandUtil.isTEAllowed(blockState)) return null; @@ -108,11 +106,6 @@ public class PlaceSnapshot implements ISnapshot // No entities colliding? if(WandUtil.entitiesCollidingWithBlock(world, blockState, pos)) return null; - // Adjust blockstate to neighbors - // TODO: verify that - blockState = Block.updateFromNeighbourShapes(blockState, world, pos); - if(blockState.getBlock() == Blocks.AIR || !blockState.canSurvive(world, pos)) return null; - // Copy block properties from supporting block if(targetMode && supportingBlock != null) { // Block properties to be copied (alignment/rotation properties) From 5d4fe7454a8fff37f9bff4499e2bd5661c96c017 Mon Sep 17 00:00:00 2001 From: RuyaSavascisi Date: Tue, 16 Jul 2024 09:27:21 +0300 Subject: [PATCH 78/78] tr_tr.json Turkish Localization for the mod --- .../assets/constructionwand/lang/tr_tr.json | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/assets/constructionwand/lang/tr_tr.json diff --git a/src/main/resources/assets/constructionwand/lang/tr_tr.json b/src/main/resources/assets/constructionwand/lang/tr_tr.json new file mode 100644 index 0000000..05f5044 --- /dev/null +++ b/src/main/resources/assets/constructionwand/lang/tr_tr.json @@ -0,0 +1,70 @@ +{ + "item.constructionwand.stone_wand": "Taş Asa", + "item.constructionwand.iron_wand": "Demir Asa", + "item.constructionwand.diamond_wand": "Elmas Asa", + "item.constructionwand.infinity_wand": "Sonsuzluk Asası", + "item.constructionwand.core_angel": "Melek Asa Çekirdeği", + "item.constructionwand.core_destruction": "Yıkım Asa Çekirdeği", + + "constructionwand.tooltip.blocks": "Maks. %d blok", + "constructionwand.tooltip.shift": "[SHIFT] bas", + "constructionwand.tooltip.cores": "Asa çekirdekleri:", + "constructionwand.tooltip.core_tip": "Çekirdeği asanızla birlikte üretim ızgarasında birleştirin", + + "constructionwand.option.cores": "", + "constructionwand.option.cores.constructionwand:default": "İnşa Çekirdeği", + "constructionwand.option.cores.constructionwand:default.desc": "Yapınızın size bakan tarafını uzatır", + "constructionwand.option.cores.constructionwand:core_angel": "§6Melek Çekirdeği", + "constructionwand.option.cores.constructionwand:core_angel.desc": "Blokların arkasına ve havaya yerleştirir", + "constructionwand.option.cores.constructionwand:core_destruction": "§cYıkım Çekirdeği", + "constructionwand.option.cores.constructionwand:core_destruction.desc": "Size bakan taraftaki blokları yok eder", + + "constructionwand.option.lock": "Sınırlama: ", + "constructionwand.option.lock.horizontal": "§aSol/Sağ", + "constructionwand.option.lock.horizontal.desc": "Orijinal bloğun önüne yatay bir sütun oluşturur", + "constructionwand.option.lock.vertical": "§aYukarı/Aşağı", + "constructionwand.option.lock.vertical.desc": "Orijinal bloğun önünde dikey bir sütun oluşturur", + "constructionwand.option.lock.northsouth": "§6Kuzey/Güney", + "constructionwand.option.lock.northsouth.desc": "Orijinal bloğun üstüne K/G yönünde bir sıra oluşturun", + "constructionwand.option.lock.eastwest": "§6Doğu/Batı", + "constructionwand.option.lock.eastwest.desc": "Orijinal bloğun üstüne D/B yönünde bir sıra oluşturur", + "constructionwand.option.lock.nolock": "§cYok", + "constructionwand.option.lock.nolock.desc": "Orijinal bloğun herhangi bir tarafından uzatır", + + "constructionwand.option.direction": "Yön: ", + "constructionwand.option.direction.target": "§6Hedef", + "constructionwand.option.direction.target.desc": "Blokları hedef blokla aynı yönde yerleştirir", + "constructionwand.option.direction.player": "§aOyuncu", + "constructionwand.option.direction.player.desc": "Blokları oyuncuya bakacak şekilde yerleştirir", + + "constructionwand.option.replace": "Değiştirme: ", + "constructionwand.option.replace.yes": "§aEvet", + "constructionwand.option.replace.yes.desc": "Sıvılar, kar ve uzun otlar gibi belirli blokları değiştirir", + "constructionwand.option.replace.no": "§cHayır", + "constructionwand.option.replace.no.desc": "Blokları değiştirmez", + + "constructionwand.option.match": "Eşleşen: ", + "constructionwand.option.match.exact": "§aAynı", + "constructionwand.option.match.exact.desc": "Yalnızca tamamen aynı olan blokları uzatır", + "constructionwand.option.match.similar": "§6Benzer", + "constructionwand.option.match.similar.desc": "Benzer bloklara (toprak/çimen türleri) eşit davranır", + "constructionwand.option.match.any": "§cHerhangi", + "constructionwand.option.match.any.desc": "Herhangi bir bloğu uzatır", + + "constructionwand.option.random": "Rastgele: ", + "constructionwand.option.random.yes": "§aEvet", + "constructionwand.option.random.yes.desc": "Hotbar'ınızdan rastgele bloklar yerleştirir", + "constructionwand.option.random.no": "§cHayır", + "constructionwand.option.random.no.desc": "Yerleştirilen blokları rastgeleleştirmez", + + "constructionwand.description.wand": "%1$s, bir yapının size bakan tarafına en fazla %2$d blok yerleştirebilir ve %3$s dayanıklılığı vardır.\n\n%5$s tuşunu basılı tutun ve yerleştirme sınırlamasını değiştirmek için kaydırın (Yatay, Dikey, Kuzey/Güney, Doğu/Batı, Kilitsiz).\n\n%6$s§9+Sağ tıklama ile seçenek ekranını açın§0.\n\n§5§nGERİ ALMA§0§r\nBir bloğa bakarken §9Eğil+§0%4$s tuşunu basılı tuttuğunuzda, yerleştirdiğiniz son bloklar, çevresinde yeşil bir çerçeveyle gösterilecektir. §9Eğil+§0%4$s§9+Bunlardan herhangi birine sağ tıklama§0 işlemi geri alacak ve tüm öğeleri size geri verecektir. Yıkım çekirdeğini kullandıysanız blokları geri koyacaktır.\n\n§5§nKONTEYNERLER§0§r\nShulker kutuları, paketler ve diğer modlardan birçok konteyner, asa için yapı taşları sağlar.\n\n§5§nBOŞTAKİ EL ÖNCELİĞݧ0§r\nBoştaki elinizde blok olduğunda, baktığınız blok yerine boştaki elinizdekini yerleştirirsiniz.", + "constructionwand.description.durability.limited": "%d blok için", + "constructionwand.description.durability.unlimited": "sonsuza kadar", + "constructionwand.description.key.sneak": "Eğil", + "constructionwand.description.key.sneak_opt": "Eğil+%s", + "constructionwand.description.core": "§5§nKURULUM§0§r\nTakmak için yeni çekirdeğinizi asanızla birlikte bir üretim ızgarasına koyun. Çekirdekler arasında geçiş yapmak için %s tuşunu basılı tutun ve asanızla boş alana sol tıklayın veya seçenek ekranını kullanın", + "constructionwand.description.core_angel": "Melek çekirdeği, karşı karşıya olduğunuz bloğun (veya blok sırasının) karşı tarafına bir blok yerleştirir. Maksimum mesafe asa seviyesine bağlıdır. Havada bir blok yerleştirmek için boş alana sağ tıklayın. Bunu yapmak için, yerleştirmek istediğiniz bloğu boştaki elinize almalısınız.", + "constructionwand.description.core_destruction": "Yıkım çekirdeği, size bakan taraftaki blokları (tile entities haricinde) yok eder. Maksimum blok sayısı asa seviyesine bağlıdır. Yok edilen bloklar boşluğa kaybolur, hata yaptıysanız geri alma özelliğini kullanabilirsiniz.", + + "stat.constructionwand.use_wand": "Asa kullanılarak yerleştirilen bloklar" +} \ No newline at end of file