Using BlockSets. Added filtering and several replace settings.

Overhauled build.gradle based on Create. Added flywheel as jarInJar, no external dependency anymore. New mappings channel from parchment.
Added replace modes: air, air and solid, solid, offhand filtered. Offhand filtering looks in randomizer bag as well.
Protecting tile entities by default.
Other mods can cancel placement through BlockEvent.OnBlockPlaced, such as FTBChunks.
BlockPreviews.drawOutlinesIfNoBlockInHand overhaul.
This commit is contained in:
Christian Knaapen
2023-01-22 20:36:48 +01:00
parent ae75e48c01
commit af4ef73375
36 changed files with 858 additions and 424 deletions

View File

@@ -1,85 +1,66 @@
plugins { buildscript {
id 'eclipse' repositories {
id 'maven-publish' maven { url = 'https://maven.minecraftforge.net' }
id 'net.minecraftforge.gradle' version '5.1.+' mavenCentral()
jcenter()
maven { url = 'https://repo.spongepowered.org/repository/maven-public' }
maven { url = 'https://maven.parchmentmc.org' }
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: "${forgegradle_version}", changing: false
classpath "org.spongepowered:mixingradle:${mixingradle_version}"
classpath "org.parchmentmc:librarian:${librarian_version}"
}
} }
plugins {
id 'com.matthewprenger.cursegradle' version "${cursegradle_version}"
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'org.parchmentmc.librarian.forgegradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
apply plugin: 'org.spongepowered.mixin'
version = '1.19-2.40' jarJar.enable()
boolean flywheelInWorkspace = findProject(':Flywheel') != null
ext.buildNumber = System.getenv('BUILD_NUMBER')
version = mod_version
group = 'nl.requios.effortlessbuilding' group = 'nl.requios.effortlessbuilding'
archivesBaseName = 'effortlessbuilding' archivesBaseName = "effortlessbuilding-${artifact_minecraft_version}"
// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17.
java.toolchain.languageVersion = JavaLanguageVersion.of(17) java.toolchain.languageVersion = JavaLanguageVersion.of(17)
println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}"
minecraft { minecraft {
// The mappings can be changed at any time, and must be in the following format. mappings channel: 'parchment', version: "${parchment_version}-${minecraft_version}"
// snapshot_YYYYMMDD Snapshot are built nightly.
// stable_# Stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: 'official', version: '1.19.2'
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs { runs {
client { client {
workingDirectory project.file('run') workingDirectory project.file('run')
arg '-mixin.config=flywheel.mixins.json'
// Recommended logging data for a userdev environment (SCAN,REGISTRIES,REGISTRYDUMP) //jvmArgs '-XX:+UnlockCommercialFeatures' // uncomment for profiling
property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'info'
// Recommended logging level for the console
property 'forge.logging.console.level', 'debug'
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
property 'forge.enabledGameTestNamespaces', 'effortlessbuilding'
// Flywheel
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg"
mods { mods {
effortlessbuilding { effortlessbuilding {
source sourceSets.main source sourceSets.main
} }
if (flywheelInWorkspace) {
flywheel {
source project(":Flywheel").sourceSets.main
}
}
} }
} }
server { server {
workingDirectory project.file('run') workingDirectory project.file('run/server')
property 'forge.logging.console.level', 'info'
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
property 'forge.enabledGameTestNamespaces', 'effortlessbuilding'
// Flywheel
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg"
mods {
effortlessbuilding {
source sourceSets.main
}
}
}
// This run config launches GameTestServer and runs all registered gametests, then exits.
// By default, the server will crash when no gametests are provided.
// The gametest system is also enabled by default for other run configs under the /test command.
gameTestServer {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
property 'forge.enabledGameTestNamespaces', 'effortlessbuilding'
mods { mods {
effortlessbuilding { effortlessbuilding {
source sourceSets.main source sourceSets.main
@@ -89,73 +70,107 @@ minecraft {
data { data {
workingDirectory project.file('run') workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES,REGISTRYDUMP'
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug' property 'forge.logging.console.level', 'debug'
args '--mod', 'effortlessbuilding', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
args '--mod', 'effortlessbuilding', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
mods { mods {
effortlessbuilding { effortlessbuilding {
source sourceSets.main source sourceSets.main
} }
if (flywheelInWorkspace) {
flywheel {
source project(":Flywheel").sourceSets.main
}
}
} }
} }
} }
} }
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
repositories { repositories {
// Put repositories for dependencies here
// ForgeGradle automatically adds the Forge maven and Maven Central for you
// If you have mod jar dependencies in ./libs, you can declare them as a repository like so:
// flatDir {
// dir 'libs'
// }
//Flywheel
maven { maven {
name "tterrag maven" // Location of the maven that hosts JEI files (and TiC)
url "https://maven.tterrag.com/" name 'Progwml6 maven'
url 'https://dvs1.progwml6.com/files/maven'
}
/*maven {
// Location of a maven mirror for JEI files, as a fallback
name 'ModMaven'
url 'https://modmaven.k-4u.nl'
}*/
maven {
// Location of the maven for vazkii's mods
name 'blamejared'
url 'https://maven.blamejared.com'
}
maven {
// Location of the maven for mixed mappings, Registrate, and Flywheel
name 'tterrag maven'
url 'https://maven.tterrag.com'
}
maven {
url 'https://www.cursemaven.com'
content {
includeGroup "curse.maven"
}
}
maven {
//location of the maven for dynamic trees
url 'https://harleyoconnor.com/maven'
}
maven {
//location of the maven for curios api
url = "https://maven.theillusivec4.top/"
}
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
content {
includeGroup "maven.modrinth"
}
} }
} }
dependencies { dependencies {
// Specify the version of Minecraft to use. If this is any group other than 'net.minecraft', it is assumed minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
// that the dep is a ForgeGradle 'patcher' dependency, and its patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft 'net.minecraftforge:forge:1.19.2-43.1.47'
// Real mod deobf dependency examples - these get remapped to your current mappings jarJar(group: 'com.jozufozu.flywheel', name: "flywheel-forge-${flywheel_minecraft_version}", version: '[0.6.8,0.6.9)') {
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // Adds JEI API as a compile dependency jarJar.pin(it, project.flywheel_version)
// runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // Adds the full JEI mod as a runtime dependency }
// implementation fg.deobf("com.tterrag.registrate:Registrate:MC${mc_version}-${registrate_version}") // Adds registrate as a dependency
// Examples using mod jars from ./libs if (flywheelInWorkspace) {
// implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}") implementation project(':Flywheel')
} else {
implementation fg.deobf("com.jozufozu.flywheel:flywheel-forge-${flywheel_minecraft_version}:${flywheel_version}")
}
// For more info... // Prevent Mixin annotation processor from getting into IntelliJ's annotation processor settings
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html // This allows 'Settings > Build, Execution, and Deployment > Build Tools > Gradle > Build and run using' set to IntelliJ to work correctly
// http://www.gradle.org/docs/current/userguide/dependency_management.html if (System.getProperty('idea.sync.active') != 'true') {
annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor"
//Flywheel }
//Versions: https://maven.tterrag.com/com/jozufozu/flywheel/flywheel-forge-1.19.2/ }
implementation fg.deobf("com.jozufozu.flywheel:flywheel-forge-1.19.2:0.6.8-13")
sourceSets.main.resources {
srcDir 'src/generated/resources'
exclude '.cache/'
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
compileJava {
options.compilerArgs = ['-Xdiags:verbose']
} }
// Example for how to get properties into the manifest for reading by the runtime..
jar { jar {
manifest { manifest {
attributes([ attributes([
"Specification-Title": "effortlessbuilding", "Specification-Title": "effortlessbuilding",
"Specification-Vendor": "requios", "Specification-Vendor": "requios",
"Specification-Version": "1", // We are version 1 of ourselves "Specification-Version": "1",
"Implementation-Title": project.name, "Implementation-Title": project.jar.baseName,
"Implementation-Version": project.jar.archiveVersion, "Implementation-Version": project.jar.archiveVersion,
"Implementation-Vendor" :"requios", "Implementation-Vendor" :"requios",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
@@ -163,25 +178,37 @@ jar {
} }
} }
// Example configuration to allow publishing using the maven-publish plugin task jarJarRelease {
// This is the preferred method to reobfuscate your jar file doLast {
tasks.jarJar {
classifier = ''
}
}
finalizedBy tasks.jarJar
}
java {
withSourcesJar()
withJavadocJar()
}
jar.finalizedBy('reobfJar') jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing tasks.jarJar.finalizedBy('reobfJarJar')
// publish.dependsOn('reobfJar')
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
artifact jar artifactId = archivesBaseName
from components.java
fg.component(it)
jarJar.component(it)
} }
} }
repositories { repositories {
maven { if (project.hasProperty('mavendir')) {
url "file://${project.projectDir}/mcmodsrepo" maven { url mavendir }
} }
} }
} }
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}

View File

@@ -2,3 +2,19 @@
# This is required to provide enough memory for the Minecraft decompilation process. # This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false org.gradle.daemon=false
mod_version = 3.0
artifact_minecraft_version = 1.19.2
minecraft_version = 1.19.2
forge_version = 43.1.47
forgegradle_version = 5.1.53
mixingradle_version = 0.7-SNAPSHOT
mixin_version = 0.8.5
librarian_version = 1.+
cursegradle_version = 1.4.0
parchment_version = 2022.11.06
flywheel_minecraft_version = 1.19.2
flywheel_version = 0.6.8.a-14

View File

@@ -2,7 +2,6 @@ package nl.requios.effortlessbuilding;
import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.platform.InputConstants;
import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import net.minecraft.ChatFormatting;
import net.minecraft.client.KeyMapping; import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
@@ -13,7 +12,6 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.InputEvent; import net.minecraftforge.client.event.InputEvent;
@@ -27,12 +25,9 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import nl.requios.effortlessbuilding.buildmode.BuildModeEnum; import nl.requios.effortlessbuilding.buildmode.BuildModeEnum;
import nl.requios.effortlessbuilding.buildmode.ModeOptions; import nl.requios.effortlessbuilding.buildmode.ModeOptions;
import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager;
import nl.requios.effortlessbuilding.gui.buildmode.PlayerSettingsGui; import nl.requios.effortlessbuilding.gui.buildmode.PlayerSettingsGui;
import nl.requios.effortlessbuilding.gui.buildmode.RadialMenu; import nl.requios.effortlessbuilding.gui.buildmode.RadialMenu;
import nl.requios.effortlessbuilding.gui.buildmodifier.ModifierSettingsGui; import nl.requios.effortlessbuilding.gui.buildmodifier.ModifierSettingsGui;
import nl.requios.effortlessbuilding.render.BlockPreviews;
import nl.requios.effortlessbuilding.render.RenderHandler;
import nl.requios.effortlessbuilding.utilities.ReachHelper; import nl.requios.effortlessbuilding.utilities.ReachHelper;
import nl.requios.effortlessbuilding.network.*; import nl.requios.effortlessbuilding.network.*;
import nl.requios.effortlessbuilding.render.BuildRenderTypes; import nl.requios.effortlessbuilding.render.BuildRenderTypes;
@@ -161,7 +156,7 @@ public class ClientEvents {
//QuickReplace toggle //QuickReplace toggle
if (keyBindings[1].consumeClick()) { if (keyBindings[1].consumeClick()) {
EffortlessBuildingClient.QUICK_REPLACE.toggleQuickReplacing(); EffortlessBuildingClient.BUILD_SETTINGS.toggleQuickReplace();
} }
//Radial menu //Radial menu
@@ -241,7 +236,7 @@ public class ClientEvents {
keyBindings[keybindIndex].getKey().getValue()); keyBindings[keybindIndex].getKey().getValue());
} }
public static BlockHitResult getLookingAt(Player player) { public static BlockHitResult getLookingAtFar(Player player) {
Level world = player.level; Level world = player.level;
//base distance off of player ability (config) //base distance off of player ability (config)

View File

@@ -17,11 +17,11 @@ import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.PacketDistributor;
import nl.requios.effortlessbuilding.buildmode.BuildModeEnum;
import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager;
import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo;
import nl.requios.effortlessbuilding.capability.ModifierCapabilityManager; import nl.requios.effortlessbuilding.capability.ModifierCapabilityManager;
import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.compatibility.CompatHelper;
import nl.requios.effortlessbuilding.systems.ServerBuildState;
import nl.requios.effortlessbuilding.utilities.ReachHelper; import nl.requios.effortlessbuilding.utilities.ReachHelper;
import nl.requios.effortlessbuilding.network.AddUndoMessage; import nl.requios.effortlessbuilding.network.AddUndoMessage;
import nl.requios.effortlessbuilding.network.ClearUndoMessage; import nl.requios.effortlessbuilding.network.ClearUndoMessage;
@@ -55,7 +55,7 @@ public class CommonEvents {
EffortlessBuilding.DELAYED_BLOCK_PLACER.tick(); EffortlessBuilding.DELAYED_BLOCK_PLACER.tick();
} }
//Cancel event if necessary. Nothing more, rest is handled on mouse right click //Cancel event if necessary. Nothing more, rest is handled on mouseclick
@SubscribeEvent @SubscribeEvent
public static void onBlockPlaced(BlockEvent.EntityPlaceEvent event) { public static void onBlockPlaced(BlockEvent.EntityPlaceEvent event) {
if (event.getLevel().isClientSide()) return; //Never called clientside anyway, but just to be sure if (event.getLevel().isClientSide()) return; //Never called clientside anyway, but just to be sure
@@ -65,9 +65,7 @@ public class CommonEvents {
//Don't cancel event if our custom logic is breaking blocks //Don't cancel event if our custom logic is breaking blocks
if (EffortlessBuilding.SERVER_BLOCK_PLACER.isPlacingOrBreakingBlocks()) return; if (EffortlessBuilding.SERVER_BLOCK_PLACER.isPlacingOrBreakingBlocks()) return;
BuildModeEnum buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); if (!ServerBuildState.isLikeVanilla(player)) {
if (buildMode != BuildModeEnum.DISABLED || EffortlessBuildingClient.QUICK_REPLACE.isQuickReplacing()) {
//Only cancel if itemblock in hand //Only cancel if itemblock in hand
//Fixed issue with e.g. Create Wrench shift-rightclick disassembling being cancelled. //Fixed issue with e.g. Create Wrench shift-rightclick disassembling being cancelled.
@@ -83,6 +81,7 @@ public class CommonEvents {
} }
} }
//Cancel event if necessary. Nothing more, rest is handled on mouseclick
@SubscribeEvent @SubscribeEvent
public static void onBlockBroken(BlockEvent.BreakEvent event) { public static void onBlockBroken(BlockEvent.BreakEvent event) {
if (event.getLevel().isClientSide()) return; if (event.getLevel().isClientSide()) return;
@@ -92,10 +91,7 @@ public class CommonEvents {
//Don't cancel event if our custom logic is breaking blocks //Don't cancel event if our custom logic is breaking blocks
if (EffortlessBuilding.SERVER_BLOCK_PLACER.isPlacingOrBreakingBlocks()) return; if (EffortlessBuilding.SERVER_BLOCK_PLACER.isPlacingOrBreakingBlocks()) return;
//Cancel event if necessary if (!ServerBuildState.isLikeVanilla(player) && ReachHelper.canBreakFar(player)) {
//If cant break far then dont cancel event ever
BuildModeEnum buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode();
if (buildMode != BuildModeEnum.DISABLED && ReachHelper.canBreakFar(player)) {
event.setCanceled(true); event.setCanceled(true);
} else { } else {
//NORMAL mode, let vanilla handle block breaking //NORMAL mode, let vanilla handle block breaking

View File

@@ -10,14 +10,14 @@ import nl.requios.effortlessbuilding.gui.GoldenRandomizerBagScreen;
import nl.requios.effortlessbuilding.gui.RandomizerBagScreen; import nl.requios.effortlessbuilding.gui.RandomizerBagScreen;
import nl.requios.effortlessbuilding.render.BlockPreviews; import nl.requios.effortlessbuilding.render.BlockPreviews;
import nl.requios.effortlessbuilding.systems.BuilderChain; import nl.requios.effortlessbuilding.systems.BuilderChain;
import nl.requios.effortlessbuilding.systems.QuickReplace; import nl.requios.effortlessbuilding.systems.BuildSettings;
public class EffortlessBuildingClient { public class EffortlessBuildingClient {
public static final BuilderChain BUILDER_CHAIN = new BuilderChain(); public static final BuilderChain BUILDER_CHAIN = new BuilderChain();
public static final BuildModes BUILD_MODES = new BuildModes(); public static final BuildModes BUILD_MODES = new BuildModes();
public static final BuildModifiers BUILD_MODIFIERS = new BuildModifiers(); public static final BuildModifiers BUILD_MODIFIERS = new BuildModifiers();
public static final QuickReplace QUICK_REPLACE = new QuickReplace(); public static final BuildSettings BUILD_SETTINGS = new BuildSettings();
public static final BlockPreviews BLOCK_PREVIEWS = new BlockPreviews(); public static final BlockPreviews BLOCK_PREVIEWS = new BlockPreviews();
public static void onConstructorClient(IEventBus modEventBus, IEventBus forgeEventBus) { public static void onConstructorClient(IEventBus modEventBus, IEventBus forgeEventBus) {

View File

@@ -1,12 +1,6 @@
package nl.requios.effortlessbuilding.buildmode; package nl.requios.effortlessbuilding.buildmode;
import net.minecraft.world.entity.player.Player; import nl.requios.effortlessbuilding.utilities.BlockSet;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import nl.requios.effortlessbuilding.utilities.BlockEntry;
import java.util.List;
public abstract class BaseBuildMode implements IBuildMode { public abstract class BaseBuildMode implements IBuildMode {
@@ -18,7 +12,7 @@ public abstract class BaseBuildMode implements IBuildMode {
} }
@Override @Override
public boolean onClick(List<BlockEntry> blocks) { public boolean onClick(BlockSet blocks) {
clicks++; clicks++;
return false; return false;
} }

View File

@@ -9,6 +9,7 @@ import net.minecraftforge.api.distmarker.OnlyIn;
import nl.requios.effortlessbuilding.network.IsUsingBuildModePacket; import nl.requios.effortlessbuilding.network.IsUsingBuildModePacket;
import nl.requios.effortlessbuilding.network.PacketHandler; import nl.requios.effortlessbuilding.network.PacketHandler;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.ReachHelper; import nl.requios.effortlessbuilding.utilities.ReachHelper;
import java.util.*; import java.util.*;
@@ -17,14 +18,8 @@ import java.util.*;
public class BuildModes { public class BuildModes {
private BuildModeEnum buildMode = BuildModeEnum.DISABLED; private BuildModeEnum buildMode = BuildModeEnum.DISABLED;
public void findCoordinates(List<BlockEntry> blocks, Player player, BuildModeEnum buildMode) { public void findCoordinates(BlockSet blocks, Player player, BuildModeEnum buildMode) {
buildMode.instance.findCoordinates(blocks); buildMode.instance.findCoordinates(blocks);
//Limit number of blocks you can place
int limit = ReachHelper.getMaxBlocksPlacedAtOnce(player);
while (blocks.size() > limit) {
blocks.remove(blocks.size()-1);
}
} }
public BuildModeEnum getBuildMode() { public BuildModeEnum getBuildMode() {

View File

@@ -1,12 +1,6 @@
package nl.requios.effortlessbuilding.buildmode; package nl.requios.effortlessbuilding.buildmode;
import net.minecraft.world.entity.player.Player; import nl.requios.effortlessbuilding.utilities.BlockSet;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import nl.requios.effortlessbuilding.utilities.BlockEntry;
import java.util.List;
public interface IBuildMode { public interface IBuildMode {
@@ -14,7 +8,7 @@ public interface IBuildMode {
void initialize(); void initialize();
//Returns if we should place blocks now //Returns if we should place blocks now
boolean onClick(List<BlockEntry> blocks); boolean onClick(BlockSet blocks);
void findCoordinates(List<BlockEntry> blocks); void findCoordinates(BlockSet blocks);
} }

View File

@@ -1,11 +1,9 @@
package nl.requios.effortlessbuilding.buildmode; package nl.requios.effortlessbuilding.buildmode;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.ChatFormatting;
import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.ClientEvents;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.EffortlessBuildingClient;
import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager;
import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo;
public class ModeOptions { public class ModeOptions {
@@ -73,7 +71,7 @@ public class ModeOptions {
break; break;
case REPLACE: case REPLACE:
if (player.level.isClientSide) if (player.level.isClientSide)
EffortlessBuildingClient.QUICK_REPLACE.toggleQuickReplacing(); EffortlessBuildingClient.BUILD_SETTINGS.toggleQuickReplace();
break; break;
case OPEN_MODIFIER_SETTINGS: case OPEN_MODIFIER_SETTINGS:
if (player.level.isClientSide) if (player.level.isClientSide)

View File

@@ -5,6 +5,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.ReachHelper; import nl.requios.effortlessbuilding.utilities.ReachHelper;
import java.util.ArrayList; import java.util.ArrayList;
@@ -23,7 +24,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
} }
@Override @Override
public boolean onClick(List<BlockEntry> blocks) { public boolean onClick(BlockSet blocks) {
super.onClick(blocks); super.onClick(blocks);
if (clicks == 1) { if (clicks == 1) {
@@ -35,7 +36,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
return false; return false;
} }
firstBlockEntry = blocks.get(0); firstBlockEntry = blocks.getFirstBlockEntry();
} else if (clicks == 2) { } else if (clicks == 2) {
//Second click, find second position //Second click, find second position
@@ -57,7 +58,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
} }
@Override @Override
public void findCoordinates(List<BlockEntry> blocks) { public void findCoordinates(BlockSet blocks) {
if (clicks == 0) return; if (clicks == 0) return;
if (clicks == 1) { if (clicks == 1) {
@@ -85,6 +86,8 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
for (BlockPos pos : getIntermediateBlocks(player, x1, y1, z1, x2, y2, z2)) { for (BlockPos pos : getIntermediateBlocks(player, x1, y1, z1, x2, y2, z2)) {
blocks.add(new BlockEntry(pos)); blocks.add(new BlockEntry(pos));
} }
blocks.firstPos = firstPos;
blocks.lastPos = secondPos;
} else { } else {
var player = Minecraft.getInstance().player; var player = Minecraft.getInstance().player;
BlockPos firstPos = firstBlockEntry.blockPos; BlockPos firstPos = firstBlockEntry.blockPos;
@@ -118,6 +121,8 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
for (BlockPos pos : getFinalBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3)) { for (BlockPos pos : getFinalBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3)) {
blocks.add(new BlockEntry(pos)); blocks.add(new BlockEntry(pos));
} }
blocks.firstPos = firstPos;
blocks.lastPos = thirdPos;
} }
} }

View File

@@ -4,6 +4,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.ReachHelper; import nl.requios.effortlessbuilding.utilities.ReachHelper;
import java.util.List; import java.util.List;
@@ -19,7 +20,7 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode {
} }
@Override @Override
public boolean onClick(List<BlockEntry> blocks) { public boolean onClick(BlockSet blocks) {
super.onClick(blocks); super.onClick(blocks);
if (clicks == 1) { if (clicks == 1) {
@@ -31,7 +32,7 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode {
return false; return false;
} }
firstBlockEntry = blocks.get(0); firstBlockEntry = blocks.getFirstBlockEntry();
} else { } else {
//Second click, place blocks //Second click, place blocks
clicks = 0; clicks = 0;
@@ -41,7 +42,7 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode {
} }
@Override @Override
public void findCoordinates(List<BlockEntry> blocks) { public void findCoordinates(BlockSet blocks) {
if (clicks == 0) return; if (clicks == 0) return;
var player = Minecraft.getInstance().player; var player = Minecraft.getInstance().player;
@@ -68,6 +69,8 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode {
for (BlockPos pos : getAllBlocks(player, x1, y1, z1, x2, y2, z2)) { for (BlockPos pos : getAllBlocks(player, x1, y1, z1, x2, y2, z2)) {
blocks.add(new BlockEntry(pos)); blocks.add(new BlockEntry(pos));
} }
blocks.firstPos = firstPos;
blocks.lastPos = secondPos;
} }
//Finds the place of the second block pos based on criteria (floor must be on same height as first click, wall on same plane etc) //Finds the place of the second block pos based on criteria (floor must be on same height as first click, wall on same plane etc)

View File

@@ -1,9 +1,7 @@
package nl.requios.effortlessbuilding.buildmode.buildmodes; package nl.requios.effortlessbuilding.buildmode.buildmodes;
import nl.requios.effortlessbuilding.buildmode.IBuildMode; import nl.requios.effortlessbuilding.buildmode.IBuildMode;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockSet;
import java.util.List;
public class Disabled implements IBuildMode { public class Disabled implements IBuildMode {
@@ -13,12 +11,12 @@ public class Disabled implements IBuildMode {
} }
@Override @Override
public boolean onClick(List<BlockEntry> blocks) { public boolean onClick(BlockSet blocks) {
return true; return true;
} }
@Override @Override
public void findCoordinates(List<BlockEntry> blocks) { public void findCoordinates(BlockSet blocks) {
//Do nothing //Do nothing
} }
} }

View File

@@ -1,9 +1,7 @@
package nl.requios.effortlessbuilding.buildmode.buildmodes; package nl.requios.effortlessbuilding.buildmode.buildmodes;
import nl.requios.effortlessbuilding.buildmode.IBuildMode; import nl.requios.effortlessbuilding.buildmode.IBuildMode;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockSet;
import java.util.List;
public class Single implements IBuildMode { public class Single implements IBuildMode {
@@ -13,12 +11,12 @@ public class Single implements IBuildMode {
} }
@Override @Override
public boolean onClick(List<BlockEntry> blocks) { public boolean onClick(BlockSet blocks) {
return true; return true;
} }
@Override @Override
public void findCoordinates(List<BlockEntry> blocks) { public void findCoordinates(BlockSet blocks) {
//Do nothing //Do nothing
} }
} }

View File

@@ -20,9 +20,9 @@ import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.compatibility.CompatHelper;
import nl.requios.effortlessbuilding.systems.DelayedBlockPlacer; import nl.requios.effortlessbuilding.systems.DelayedBlockPlacer;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper; import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem;
import nl.requios.effortlessbuilding.render.BlockPreviews;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -30,7 +30,7 @@ import java.util.List;
public class BuildModifiers { public class BuildModifiers {
public void findCoordinates(List<BlockEntry> blocks, LocalPlayer player, ModifierSettingsManager.ModifierSettings modifierSettings) { public void findCoordinates(BlockSet blocks, LocalPlayer player, ModifierSettingsManager.ModifierSettings modifierSettings) {
} }
@@ -115,7 +115,7 @@ public class BuildModifiers {
//add to undo stack //add to undo stack
BlockPos firstPos = startCoordinates.get(0); BlockPos firstPos = startCoordinates.get(0);
BlockPos secondPos = startCoordinates.get(startCoordinates.size() - 1); BlockPos secondPos = startCoordinates.get(startCoordinates.size() - 1);
UndoRedo.addUndo(player, new BlockSet(coordinates, previousBlockStates, newBlockStates, firstPos, secondPos)); UndoRedo.addUndo(player, new UndoRedoBlockSet(coordinates, previousBlockStates, newBlockStates, firstPos, secondPos));
} }

View File

@@ -6,16 +6,13 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.CommonConfig;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.utilities.FixedStack; import nl.requios.effortlessbuilding.utilities.FixedStack;
import nl.requios.effortlessbuilding.utilities.InventoryHelper; import nl.requios.effortlessbuilding.utilities.InventoryHelper;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper; import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
import nl.requios.effortlessbuilding.render.BlockPreviews;
import java.util.*; import java.util.*;
@@ -23,14 +20,14 @@ public class UndoRedo {
//Undo and redo stacks per player //Undo and redo stacks per player
//Gets added to twice in singleplayer (server and client) if not careful. So separate stacks. //Gets added to twice in singleplayer (server and client) if not careful. So separate stacks.
private static final Map<UUID, FixedStack<BlockSet>> undoStacksClient = new HashMap<>(); private static final Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacksClient = new HashMap<>();
private static final Map<UUID, FixedStack<BlockSet>> undoStacksServer = new HashMap<>(); private static final Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacksServer = new HashMap<>();
private static final Map<UUID, FixedStack<BlockSet>> redoStacksClient = new HashMap<>(); private static final Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacksClient = new HashMap<>();
private static final Map<UUID, FixedStack<BlockSet>> redoStacksServer = new HashMap<>(); private static final Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacksServer = new HashMap<>();
//add to undo stack //add to undo stack
public static void addUndo(Player player, BlockSet blockSet) { public static void addUndo(Player player, UndoRedoBlockSet blockSet) {
Map<UUID, FixedStack<BlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer;
//Assert coordinates is as long as previous and new blockstate lists //Assert coordinates is as long as previous and new blockstate lists
if (blockSet.getCoordinates().size() != blockSet.getPreviousBlockStates().size() || if (blockSet.getCoordinates().size() != blockSet.getPreviousBlockStates().size() ||
@@ -50,35 +47,35 @@ public class UndoRedo {
//If no stack exists, make one //If no stack exists, make one
if (!undoStacks.containsKey(player.getUUID())) { if (!undoStacks.containsKey(player.getUUID())) {
undoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[CommonConfig.survivalBalancers.undoStackSize.get()])); undoStacks.put(player.getUUID(), new FixedStack<>(new UndoRedoBlockSet[CommonConfig.survivalBalancers.undoStackSize.get()]));
} }
undoStacks.get(player.getUUID()).push(blockSet); undoStacks.get(player.getUUID()).push(blockSet);
} }
private static void addRedo(Player player, BlockSet blockSet) { private static void addRedo(Player player, UndoRedoBlockSet blockSet) {
Map<UUID, FixedStack<BlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer;
//(No asserts necessary, it's private) //(No asserts necessary, it's private)
//If no stack exists, make one //If no stack exists, make one
if (!redoStacks.containsKey(player.getUUID())) { if (!redoStacks.containsKey(player.getUUID())) {
redoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[CommonConfig.survivalBalancers.undoStackSize.get()])); redoStacks.put(player.getUUID(), new FixedStack<>(new UndoRedoBlockSet[CommonConfig.survivalBalancers.undoStackSize.get()]));
} }
redoStacks.get(player.getUUID()).push(blockSet); redoStacks.get(player.getUUID()).push(blockSet);
} }
public static boolean undo(Player player) { public static boolean undo(Player player) {
Map<UUID, FixedStack<BlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer;
if (!undoStacks.containsKey(player.getUUID())) return false; if (!undoStacks.containsKey(player.getUUID())) return false;
FixedStack<BlockSet> undoStack = undoStacks.get(player.getUUID()); FixedStack<UndoRedoBlockSet> undoStack = undoStacks.get(player.getUUID());
if (undoStack.isEmpty()) return false; if (undoStack.isEmpty()) return false;
BlockSet blockSet = undoStack.pop(); UndoRedoBlockSet blockSet = undoStack.pop();
List<BlockPos> coordinates = blockSet.getCoordinates(); List<BlockPos> coordinates = blockSet.getCoordinates();
List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates(); List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates();
List<BlockState> newBlockStates = blockSet.getNewBlockStates(); List<BlockState> newBlockStates = blockSet.getNewBlockStates();
@@ -129,15 +126,15 @@ public class UndoRedo {
} }
public static boolean redo(Player player) { public static boolean redo(Player player) {
Map<UUID, FixedStack<BlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer;
if (!redoStacks.containsKey(player.getUUID())) return false; if (!redoStacks.containsKey(player.getUUID())) return false;
FixedStack<BlockSet> redoStack = redoStacks.get(player.getUUID()); FixedStack<UndoRedoBlockSet> redoStack = redoStacks.get(player.getUUID());
if (redoStack.isEmpty()) return false; if (redoStack.isEmpty()) return false;
BlockSet blockSet = redoStack.pop(); UndoRedoBlockSet blockSet = redoStack.pop();
List<BlockPos> coordinates = blockSet.getCoordinates(); List<BlockPos> coordinates = blockSet.getCoordinates();
List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates(); List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates();
List<BlockState> newBlockStates = blockSet.getNewBlockStates(); List<BlockState> newBlockStates = blockSet.getNewBlockStates();
@@ -187,8 +184,8 @@ public class UndoRedo {
} }
public static void clear(Player player) { public static void clear(Player player) {
Map<UUID, FixedStack<BlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer;
Map<UUID, FixedStack<BlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer;
if (undoStacks.containsKey(player.getUUID())) { if (undoStacks.containsKey(player.getUUID())) {
undoStacks.get(player.getUUID()).clear(); undoStacks.get(player.getUUID()).clear();
} }

View File

@@ -2,20 +2,19 @@ package nl.requios.effortlessbuilding.buildmodifier;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import java.util.List; import java.util.List;
//Used only for Undo //Used only for Undo
public class BlockSet { public class UndoRedoBlockSet {
private final List<BlockPos> coordinates; private final List<BlockPos> coordinates;
private final List<BlockState> previousBlockStates; private final List<BlockState> previousBlockStates;
private final List<BlockState> newBlockStates; private final List<BlockState> newBlockStates;
private final BlockPos firstPos; private final BlockPos firstPos;
private final BlockPos secondPos; private final BlockPos secondPos;
public BlockSet(List<BlockPos> coordinates, List<BlockState> previousBlockStates, List<BlockState> newBlockStates, public UndoRedoBlockSet(List<BlockPos> coordinates, List<BlockState> previousBlockStates, List<BlockState> newBlockStates,
BlockPos firstPos, BlockPos secondPos) { BlockPos firstPos, BlockPos secondPos) {
this.coordinates = coordinates; this.coordinates = coordinates;
this.previousBlockStates = previousBlockStates; this.previousBlockStates = previousBlockStates;
this.newBlockStates = newBlockStates; this.newBlockStates = newBlockStates;

View File

@@ -1,10 +1,14 @@
package nl.requios.effortlessbuilding.compatibility; package nl.requios.effortlessbuilding.compatibility;
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.BlockState;
import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemHandlerHelper;
import nl.requios.effortlessbuilding.create.foundation.item.ItemHelper;
import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem;
public class CompatHelper { public class CompatHelper {
@@ -64,4 +68,21 @@ public class CompatHelper {
return ItemStack.EMPTY; return ItemStack.EMPTY;
} }
public static boolean containsBlock(ItemStack stack, Block block) {
if (stack == null || stack.isEmpty() || !isItemBlockProxy(stack)) {
return block == null || block == Blocks.AIR;
}
if (stack.getItem() instanceof BlockItem) {
return ((BlockItem) stack.getItem()).getBlock() == block;
}
if (stack.getItem() instanceof AbstractRandomizerBagItem randomizerBagItem) {
IItemHandler bagInventory = randomizerBagItem.getBagInventory(stack);
ItemStack firstMatch = ItemHelper.findFirstMatch(bagInventory, s -> s.getItem() instanceof BlockItem);
return firstMatch != null && !firstMatch.isEmpty();
}
return false;
}
} }

View File

@@ -32,6 +32,7 @@ import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Material; import net.minecraft.world.level.material.Material;
import net.minecraftforge.common.IPlantable; import net.minecraftforge.common.IPlantable;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.event.level.BlockEvent; import net.minecraftforge.event.level.BlockEvent;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -144,7 +145,7 @@ public class BlockHelper {
destroyBlockAs(world, pos, null, ItemStack.EMPTY, effectChance, droppedItemCallback); destroyBlockAs(world, pos, null, ItemStack.EMPTY, effectChance, droppedItemCallback);
} }
public static void destroyBlockAs(Level world, BlockPos pos, @Nullable Player player, ItemStack usedTool, public static boolean destroyBlockAs(Level world, BlockPos pos, @Nullable Player player, ItemStack usedTool,
float effectChance, Consumer<ItemStack> droppedItemCallback) { float effectChance, Consumer<ItemStack> droppedItemCallback) {
FluidState fluidState = world.getFluidState(pos); FluidState fluidState = world.getFluidState(pos);
BlockState state = world.getBlockState(pos); BlockState state = world.getBlockState(pos);
@@ -157,7 +158,7 @@ public class BlockHelper {
BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, player); BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, player);
MinecraftForge.EVENT_BUS.post(event); MinecraftForge.EVENT_BUS.post(event);
if (event.isCanceled()) if (event.isCanceled())
return; return false;
if (event.getExpToDrop() > 0 && world instanceof ServerLevel) if (event.getExpToDrop() > 0 && world instanceof ServerLevel)
state.getBlock() state.getBlock()
@@ -178,19 +179,20 @@ public class BlockHelper {
if (state.getBlock() instanceof IceBlock && usedTool.getEnchantmentLevel(Enchantments.SILK_TOUCH) == 0) { if (state.getBlock() instanceof IceBlock && usedTool.getEnchantmentLevel(Enchantments.SILK_TOUCH) == 0) {
if (world.dimensionType() if (world.dimensionType()
.ultraWarm()) .ultraWarm())
return; return false;
Material material = world.getBlockState(pos.below()) Material material = world.getBlockState(pos.below())
.getMaterial(); .getMaterial();
if (material.blocksMotion() || material.isLiquid()) if (material.blocksMotion() || material.isLiquid())
world.setBlockAndUpdate(pos, Blocks.WATER.defaultBlockState()); world.setBlockAndUpdate(pos, Blocks.WATER.defaultBlockState());
return; return true;
} }
state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true);
} }
world.setBlockAndUpdate(pos, fluidState.createLegacyBlock()); world.setBlockAndUpdate(pos, fluidState.createLegacyBlock());
return true;
} }
public static boolean isSolidWall(BlockGetter reader, BlockPos fromPos, Direction toDirection) { public static boolean isSolidWall(BlockGetter reader, BlockPos fromPos, Direction toDirection) {
@@ -223,7 +225,7 @@ public class BlockHelper {
.getBlock(), target.below()); .getBlock(), target.below());
} }
public static void placeSchematicBlock(Level world, BlockState state, BlockPos target, ItemStack stack, public static boolean placeSchematicBlock(Level world, Player player, BlockState state, BlockPos target, ItemStack stack,
@Nullable CompoundTag data) { @Nullable CompoundTag data) {
BlockEntity existingTile = world.getBlockEntity(target); BlockEntity existingTile = world.getBlockEntity(target);
@@ -253,7 +255,7 @@ public class BlockHelper {
0.0D, 0.0D, 0.0D); 0.0D, 0.0D, 0.0D);
} }
Block.dropResources(state, world, target); Block.dropResources(state, world, target);
return; return true;
} }
if (state.getBlock() instanceof BaseRailBlock) { if (state.getBlock() instanceof BaseRailBlock) {
@@ -287,6 +289,7 @@ public class BlockHelper {
.setPlacedBy(world, target, state, null, stack); .setPlacedBy(world, target, state, null, stack);
} catch (Exception e) { } catch (Exception e) {
} }
return true;
} }
public static double getBounceMultiplier(Block block) { public static double getBounceMultiplier(Block block) {

View File

@@ -23,7 +23,6 @@ import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.network.NetworkHooks; import net.minecraftforge.network.NetworkHooks;
import net.minecraftforge.items.CapabilityItemHandler; import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandler;
import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager;
import nl.requios.effortlessbuilding.systems.ServerBuildState; import nl.requios.effortlessbuilding.systems.ServerBuildState;
import nl.requios.effortlessbuilding.capability.ItemHandlerCapabilityProvider; import nl.requios.effortlessbuilding.capability.ItemHandlerCapabilityProvider;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper; import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
@@ -133,7 +132,7 @@ public abstract class AbstractRandomizerBagItem extends Item {
if (world.isClientSide) return InteractionResult.SUCCESS; if (world.isClientSide) return InteractionResult.SUCCESS;
//Only place manually if in normal vanilla mode //Only place manually if in normal vanilla mode
if (ServerBuildState.isUsingBuildMode(player) || ServerBuildState.isQuickReplacing(player)) { if (!ServerBuildState.isLikeVanilla(player)) {
return InteractionResult.FAIL; return InteractionResult.FAIL;
} }

View File

@@ -5,14 +5,13 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.buildmodifier.BlockSet; import nl.requios.effortlessbuilding.buildmodifier.UndoRedoBlockSet;
import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo;
import java.util.ArrayList; import java.util.ArrayList;
@@ -84,7 +83,7 @@ public class AddUndoMessage {
Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx); Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
//Add to undo stack clientside //Add to undo stack clientside
//Only the appropriate player that needs to add this to the undo stack gets this message //Only the appropriate player that needs to add this to the undo stack gets this message
UndoRedo.addUndo(player, new BlockSet( UndoRedo.addUndo(player, new UndoRedoBlockSet(
new ArrayList<BlockPos>() {{ new ArrayList<BlockPos>() {{
add(message.getCoordinate()); add(message.getCoordinate());
}}, }},

View File

@@ -5,6 +5,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -14,25 +15,24 @@ import java.util.function.Supplier;
*/ */
public class ServerBreakBlocksPacket { public class ServerBreakBlocksPacket {
private List<BlockEntry> blocks; private BlockSet blocks;
public ServerBreakBlocksPacket() {} public ServerBreakBlocksPacket() {}
public ServerBreakBlocksPacket(List<BlockEntry> blocks) { public ServerBreakBlocksPacket(BlockSet blocks) {
this.blocks = blocks; this.blocks = blocks;
} }
public static void encode(ServerBreakBlocksPacket message, FriendlyByteBuf buf) { public static void encode(ServerBreakBlocksPacket message, FriendlyByteBuf buf) {
buf.writeCollection(message.blocks, BlockEntry::encode); BlockSet.encode(buf, message.blocks);
} }
public static ServerBreakBlocksPacket decode(FriendlyByteBuf buf) { public static ServerBreakBlocksPacket decode(FriendlyByteBuf buf) {
ServerBreakBlocksPacket message = new ServerBreakBlocksPacket(); ServerBreakBlocksPacket message = new ServerBreakBlocksPacket();
message.blocks = buf.readList(BlockEntry::decode); message.blocks = BlockSet.decode(buf);
return message; return message;
} }
public static class Handler { public static class Handler {
public static void handle(ServerBreakBlocksPacket message, Supplier<NetworkEvent.Context> ctx) { public static void handle(ServerBreakBlocksPacket message, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {

View File

@@ -5,6 +5,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -14,25 +15,24 @@ import java.util.function.Supplier;
*/ */
public class ServerPlaceBlocksPacket { public class ServerPlaceBlocksPacket {
private List<BlockEntry> blocks; private BlockSet blocks;
public ServerPlaceBlocksPacket() {} public ServerPlaceBlocksPacket() {}
public ServerPlaceBlocksPacket(List<BlockEntry> blocks) { public ServerPlaceBlocksPacket(BlockSet blocks) {
this.blocks = blocks; this.blocks = blocks;
} }
public static void encode(ServerPlaceBlocksPacket message, FriendlyByteBuf buf) { public static void encode(ServerPlaceBlocksPacket message, FriendlyByteBuf buf) {
buf.writeCollection(message.blocks, BlockEntry::encode); BlockSet.encode(buf, message.blocks);
} }
public static ServerPlaceBlocksPacket decode(FriendlyByteBuf buf) { public static ServerPlaceBlocksPacket decode(FriendlyByteBuf buf) {
ServerPlaceBlocksPacket message = new ServerPlaceBlocksPacket(); ServerPlaceBlocksPacket message = new ServerPlaceBlocksPacket();
message.blocks = buf.readList(BlockEntry::decode); message.blocks = BlockSet.decode(buf);
return message; return message;
} }
public static class Handler { public static class Handler {
public static void handle(ServerPlaceBlocksPacket message, Supplier<NetworkEvent.Context> ctx) { public static void handle(ServerPlaceBlocksPacket message, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {

View File

@@ -1,15 +1,8 @@
package nl.requios.effortlessbuilding.render; package nl.requios.effortlessbuilding.render;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@@ -17,20 +10,17 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import nl.requios.effortlessbuilding.*; import nl.requios.effortlessbuilding.*;
import nl.requios.effortlessbuilding.buildmode.BuildModeEnum; import nl.requios.effortlessbuilding.buildmode.BuildModeEnum;
import nl.requios.effortlessbuilding.buildmodifier.BuildModifiers;
import nl.requios.effortlessbuilding.compatibility.CompatHelper;
import nl.requios.effortlessbuilding.create.AllSpecialTextures; import nl.requios.effortlessbuilding.create.AllSpecialTextures;
import nl.requios.effortlessbuilding.create.CreateClient; import nl.requios.effortlessbuilding.create.CreateClient;
import nl.requios.effortlessbuilding.create.foundation.utility.Color; import nl.requios.effortlessbuilding.create.foundation.utility.Color;
import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem;
import nl.requios.effortlessbuilding.systems.BuilderChain; import nl.requios.effortlessbuilding.systems.BuilderChain;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.ReachHelper; import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class BlockPreviews { public class BlockPreviews {
@@ -69,16 +59,15 @@ public class BlockPreviews {
!ClientConfig.visuals.alwaysShowBlockPreview.get()) return; !ClientConfig.visuals.alwaysShowBlockPreview.get()) return;
var blocks = EffortlessBuildingClient.BUILDER_CHAIN.getBlocks(); var blocks = EffortlessBuildingClient.BUILDER_CHAIN.getBlocks();
var state = EffortlessBuildingClient.BUILDER_CHAIN.getState();
if (blocks.size() == 0) return; if (blocks.size() == 0) return;
var coordinates = EffortlessBuildingClient.BUILDER_CHAIN.getCoordinates(); var coordinates = blocks.getCoordinates();
var state = EffortlessBuildingClient.BUILDER_CHAIN.getState();
//Dont fade out the outline if we are still determining where to place //Dont fade out the outline if we are still determining where to place
//Every outline with same ID will not fade out (because it gets replaced) //Every outline with same ID will not fade out (because it gets replaced)
Object outlineID = "single"; Object outlineID = "single";
if (blocks.size() > 1) outlineID = blocks.get(0).blockPos; if (blocks.size() > 1) outlineID = blocks.firstPos;
if (state != BuilderChain.State.BREAKING) { if (state != BuilderChain.State.BREAKING) {
//Use fancy shader if config allows, otherwise outlines //Use fancy shader if config allows, otherwise outlines
@@ -138,57 +127,44 @@ public class BlockPreviews {
} }
public void drawOutlinesIfNoBlockInHand(Player player) { public void drawOutlinesIfNoBlockInHand(Player player) {
ItemStack mainhand = player.getMainHandItem(); var builderChain = EffortlessBuildingClient.BUILDER_CHAIN;
HitResult lookingAt = ClientEvents.getLookingAt(player); if (builderChain.isBlockInHand()) return;
if (EffortlessBuildingClient.BUILD_MODES.getBuildMode() == BuildModeEnum.DISABLED) if (builderChain.getState() != BuilderChain.State.IDLE) return;
lookingAt = Minecraft.getInstance().hitResult;
boolean noBlockInHand = !(!mainhand.isEmpty() && CompatHelper.isItemBlockProxy(mainhand)); var blocks = new ArrayList<>(builderChain.getBlocks().values());
if (!noBlockInHand) return; if (blocks.size() == 0) return;
//Draw outlines if no block in hand //Only render first outline if further than normal reach
//Find proper raytrace: either normal range or increased range depending on canBreakFar var lookingAtNear = Minecraft.getInstance().hitResult;
HitResult objectMouseOver = Minecraft.getInstance().hitResult; if (lookingAtNear != null && lookingAtNear.getType() == HitResult.Type.BLOCK)
HitResult breakingRaytrace = ReachHelper.canBreakFar(player) ? lookingAt : objectMouseOver; blocks.remove(0);
if (player.isCreative() && breakingRaytrace != null && breakingRaytrace.getType() == HitResult.Type.BLOCK) { //Only render outlines if there is a block, like vanilla
BlockHitResult blockBreakingRaytrace = (BlockHitResult) breakingRaytrace; blocks.removeIf(blockEntry -> blockEntry.existingBlockState == null ||
List<BlockPos> breakCoordinates = BuildModifiers.findCoordinates(player, blockBreakingRaytrace.getBlockPos()); blockEntry.existingBlockState.isAir() ||
blockEntry.existingBlockState.getMaterial().isLiquid());
//Only render first outline if further than normal reach if (!blocks.isEmpty()) {
if (objectMouseOver != null && objectMouseOver.getType() == HitResult.Type.BLOCK) var coordinates = blocks.stream().map(blockEntry -> blockEntry.blockPos).collect(Collectors.toList());
breakCoordinates.remove(0); CreateClient.OUTLINER.showCluster("break", coordinates)
.disableNormals()
breakCoordinates.removeIf(pos -> { .lineWidth(1 / 64f)
BlockState blockState = player.level.getBlockState(pos); .colored(0x222222);
if (blockState.isAir() || blockState.getMaterial().isLiquid()) return true;
return !SurvivalHelper.canBreak(player.level, player, pos);
});
if (!breakCoordinates.isEmpty()) {
CreateClient.OUTLINER.showCluster("break", breakCoordinates)
.disableNormals()
.lineWidth(1 / 64f)
.colored(0x222222);
}
} }
} }
protected void renderBlockPreviews(List<BlockEntry> blocks, boolean breaking, float dissolve) { protected void renderBlockPreviews(BlockSet blocks, boolean breaking, float dissolve) {
var firstPos = blocks.get(0).blockPos;
var lastPos = blocks.get(blocks.size() - 1).blockPos;
for (BlockEntry blockEntry : blocks) { for (BlockEntry blockEntry : blocks) {
renderBlockPreview(blockEntry, breaking, dissolve, firstPos, lastPos); renderBlockPreview(blockEntry, breaking, dissolve, blocks.firstPos, blocks.lastPos);
} }
} }
protected void renderBlockPreview(BlockEntry blockEntry, boolean breaking, float dissolve, BlockPos firstPos, BlockPos lastPos) { protected void renderBlockPreview(BlockEntry blockEntry, boolean breaking, float dissolve, BlockPos firstPos, BlockPos lastPos) {
if (blockEntry.blockState == null) return; if (blockEntry.newBlockState == null) return;
var blockPos = blockEntry.blockPos; var blockPos = blockEntry.blockPos;
var blockState = blockEntry.blockState; var blockState = blockEntry.newBlockState;
float scale = 0.5f; float scale = 0.5f;
float alpha = 0.7f; float alpha = 0.7f;
@@ -247,22 +223,22 @@ public class BlockPreviews {
return (ay * t3) + (by * t2) + (cy * t) + 0; return (ay * t3) + (by * t2) + (cy * t) + 0;
} }
public void onBlocksPlaced(List<BlockEntry> blocks) { public void onBlocksPlaced(BlockSet blocks) {
if (!ClientConfig.visuals.showBlockPreviews.get()) return; if (!ClientConfig.visuals.showBlockPreviews.get()) return;
if (blocks.size() <= 1 || blocks.size() > ClientConfig.visuals.maxBlockPreviews.get()) return; if (blocks.size() <= 1 || blocks.size() > ClientConfig.visuals.maxBlockPreviews.get()) return;
placedBlocksList.add(new PlacedBlocksEntry(ClientEvents.ticksInGame, false, new ArrayList<>(blocks))); placedBlocksList.add(new PlacedBlocksEntry(ClientEvents.ticksInGame, false, new BlockSet(blocks)));
CreateClient.OUTLINER.keep(blocks.get(0).blockPos, CommonConfig.visuals.appearAnimationLength.get()); CreateClient.OUTLINER.keep(blocks.firstPos, CommonConfig.visuals.appearAnimationLength.get());
} }
public void onBlocksBroken(List<BlockEntry> blocks) { public void onBlocksBroken(BlockSet blocks) {
if (!ClientConfig.visuals.showBlockPreviews.get()) return; if (!ClientConfig.visuals.showBlockPreviews.get()) return;
if (blocks.size() <= 1 || blocks.size() > ClientConfig.visuals.maxBlockPreviews.get()) return; if (blocks.size() <= 1 || blocks.size() > ClientConfig.visuals.maxBlockPreviews.get()) return;
placedBlocksList.add(new PlacedBlocksEntry(ClientEvents.ticksInGame, true, new ArrayList<>(blocks))); placedBlocksList.add(new PlacedBlocksEntry(ClientEvents.ticksInGame, true, new BlockSet(blocks)));
CreateClient.OUTLINER.keep(blocks.get(0).blockPos, CommonConfig.visuals.breakAnimationLength.get()); CreateClient.OUTLINER.keep(blocks.firstPos, CommonConfig.visuals.breakAnimationLength.get());
} }
private void sortOnDistanceToPlayer(List<BlockPos> coordinates, Player player) { private void sortOnDistanceToPlayer(List<BlockPos> coordinates, Player player) {
@@ -279,9 +255,9 @@ public class BlockPreviews {
public static class PlacedBlocksEntry { public static class PlacedBlocksEntry {
float time; float time;
boolean breaking; boolean breaking;
List<BlockEntry> blocks; BlockSet blocks;
public PlacedBlocksEntry(float time, boolean breaking, List<BlockEntry> blocks) { public PlacedBlocksEntry(float time, boolean breaking, BlockSet blocks) {
this.time = time; this.time = time;
this.breaking = breaking; this.breaking = breaking;
this.blocks = blocks; this.blocks = blocks;

View File

@@ -0,0 +1,67 @@
package nl.requios.effortlessbuilding.systems;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.network.IsQuickReplacingPacket;
import nl.requios.effortlessbuilding.network.PacketHandler;
@OnlyIn(Dist.CLIENT)
public class BuildSettings {
public enum ReplaceMode {
ONLY_AIR,
SOLID_AND_AIR,
SOLID_ONLY,
FILTERED_BY_OFFHAND
}
private boolean quickReplace = false;
public ReplaceMode replaceMode = ReplaceMode.ONLY_AIR;
private boolean replaceTileEntities;
public boolean isQuickReplacing() {
return quickReplace;
}
public void toggleQuickReplace() {
setQuickReplace(!quickReplace);
}
public void setQuickReplace(boolean quickReplace) {
this.quickReplace = quickReplace;
EffortlessBuilding.log(Minecraft.getInstance().player, "Set " + ChatFormatting.GOLD + "Quick Replace " +
ChatFormatting.RESET + (this.quickReplace ? "on" : "off"));
PacketHandler.INSTANCE.sendToServer(new IsQuickReplacingPacket(this.quickReplace));
}
public void setReplaceMode(ReplaceMode replaceMode) {
this.replaceMode = replaceMode;
}
public void setReplaceTileEntities(boolean replaceTileEntities) {
this.replaceTileEntities = replaceTileEntities;
}
public boolean shouldReplaceAir() {
return replaceMode == ReplaceMode.ONLY_AIR || replaceMode == ReplaceMode.SOLID_AND_AIR;
}
public boolean shouldReplaceSolid() {
return replaceMode == ReplaceMode.SOLID_ONLY || replaceMode == ReplaceMode.SOLID_AND_AIR;
}
public boolean shouldReplaceFiltered() {
return replaceMode == ReplaceMode.FILTERED_BY_OFFHAND;
}
public boolean shouldReplaceTileEntities() {
return replaceTileEntities;
}
public boolean shouldOffsetStartPosition() {
return shouldReplaceSolid() || shouldReplaceFiltered();
}
}

View File

@@ -1,9 +1,7 @@
package nl.requios.effortlessbuilding.systems; package nl.requios.effortlessbuilding.systems;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
@@ -12,10 +10,8 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; 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.SoundType; import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@@ -29,11 +25,10 @@ import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem;
import nl.requios.effortlessbuilding.network.PacketHandler; import nl.requios.effortlessbuilding.network.PacketHandler;
import nl.requios.effortlessbuilding.network.ServerBreakBlocksPacket; import nl.requios.effortlessbuilding.network.ServerBreakBlocksPacket;
import nl.requios.effortlessbuilding.network.ServerPlaceBlocksPacket; import nl.requios.effortlessbuilding.network.ServerPlaceBlocksPacket;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.*;
import nl.requios.effortlessbuilding.utilities.ReachHelper;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
// Receives block placed events, then finds additional blocks we want to place through various systems, // Receives block placed events, then finds additional blocks we want to place through various systems,
@@ -42,10 +37,11 @@ import java.util.List;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class BuilderChain { public class BuilderChain {
private final List<BlockEntry> blocks = new ArrayList<>(); private final BlockSet blocks = new BlockSet();
private final List<BlockPos> coordinates = new ArrayList<>(); private boolean blockInHand;
private int soundTime = 0; private boolean lookingAtInteractiveObject;
private Item previousHeldItem; private Item previousHeldItem;
private int soundTime = 0;
public enum State { public enum State {
IDLE, IDLE,
@@ -56,6 +52,7 @@ public class BuilderChain {
private State state = State.IDLE; private State state = State.IDLE;
public void onRightClick() { public void onRightClick() {
if (lookingAtInteractiveObject) return;
var player = Minecraft.getInstance().player; var player = Minecraft.getInstance().player;
if (state == State.BREAKING) { if (state == State.BREAKING) {
@@ -63,9 +60,6 @@ public class BuilderChain {
return; return;
} }
//Check if we have a BlockItem in hand
var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND);
boolean blockInHand = CompatHelper.isItemBlockProxy(itemStack);
if (!blockInHand) { if (!blockInHand) {
if (state == State.PLACING) cancel(); if (state == State.PLACING) cancel();
return; return;
@@ -83,13 +77,15 @@ public class BuilderChain {
if (!blocks.isEmpty()) { if (!blocks.isEmpty()) {
EffortlessBuildingClient.BLOCK_PREVIEWS.onBlocksPlaced(blocks); EffortlessBuildingClient.BLOCK_PREVIEWS.onBlocksPlaced(blocks);
playSoundIfFurtherThanNormal(player, blocks.get(0), false); BlockUtilities.playSoundIfFurtherThanNormal(player, blocks.getLastBlockEntry(), false);
player.swing(InteractionHand.MAIN_HAND);
PacketHandler.INSTANCE.sendToServer(new ServerPlaceBlocksPacket(blocks)); PacketHandler.INSTANCE.sendToServer(new ServerPlaceBlocksPacket(blocks));
} }
} }
} }
public void onLeftClick() { public void onLeftClick() {
if (lookingAtInteractiveObject) return;
var player = Minecraft.getInstance().player; var player = Minecraft.getInstance().player;
if (state == State.PLACING) { if (state == State.PLACING) {
@@ -114,53 +110,55 @@ public class BuilderChain {
if (!blocks.isEmpty()) { if (!blocks.isEmpty()) {
EffortlessBuildingClient.BLOCK_PREVIEWS.onBlocksBroken(blocks); EffortlessBuildingClient.BLOCK_PREVIEWS.onBlocksBroken(blocks);
playSoundIfFurtherThanNormal(player, blocks.get(0), true); BlockUtilities.playSoundIfFurtherThanNormal(player, blocks.getLastBlockEntry(), true);
player.swing(InteractionHand.MAIN_HAND);
PacketHandler.INSTANCE.sendToServer(new ServerBreakBlocksPacket(blocks)); PacketHandler.INSTANCE.sendToServer(new ServerBreakBlocksPacket(blocks));
} }
} }
} }
public void onTick() { public void onTick() {
var previousCoordinates = new ArrayList<>(coordinates); var previousCoordinates = new HashSet<>(blocks.getCoordinates());
blocks.clear(); blocks.clear();
var mc = Minecraft.getInstance(); var mc = Minecraft.getInstance();
var player = mc.player; var player = mc.player;
var level = mc.level; var world = mc.level;
//Check if we have a BlockItem in hand //Check if we have a BlockItem in hand
var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND); var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND);
boolean blockInHand = CompatHelper.isItemBlockProxy(itemStack); blockInHand = CompatHelper.isItemBlockProxy(itemStack);
//Cancel placing as soon as we aren't holding a block anymore lookingAtInteractiveObject = BlockUtilities.determineIfLookingAtInteractiveObject(mc, world);
// if (!blockInHand && state == State.PLACING) { if (lookingAtInteractiveObject) return;
// state = State.IDLE;
// }
var modifierSettings = ModifierSettingsManager.getModifierSettings(player);
var buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); var buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode();
var modifierSettings = ModifierSettingsManager.getModifierSettings(player);
if (state == State.IDLE) {
BlockHitResult lookingAt = ClientEvents.getLookingAt(player); //Find start position
BlockEntry startEntry = findStartPosition(player, lookingAt); BlockHitResult lookingAt = ClientEvents.getLookingAtFar(player);
if (startEntry != null) { BlockEntry startEntry = findStartPosition(player, lookingAt);
blocks.add(startEntry); if (startEntry != null) {
blocks.add(startEntry);
blocks.firstPos = startEntry.blockPos;
blocks.lastPos = startEntry.blockPos;
}
} }
EffortlessBuildingClient.BUILD_MODES.findCoordinates(blocks, player, buildMode); EffortlessBuildingClient.BUILD_MODES.findCoordinates(blocks, player, buildMode);
EffortlessBuildingClient.BUILD_MODIFIERS.findCoordinates(blocks, player, modifierSettings); EffortlessBuildingClient.BUILD_MODIFIERS.findCoordinates(blocks, player, modifierSettings);
removeDuplicateCoordinates(); BuilderFilter.filterOnCoordinates(blocks, player);
coordinates.clear(); findExistingBlockStates(world);
for (BlockEntry blockEntry : blocks) { BuilderFilter.filterOnExistingBlockStates(blocks, player);
coordinates.add(blockEntry.blockPos);
}
findBlockStates(player, itemStack); findNewBlockStates(player, itemStack);
BuilderFilter.filterOnNewBlockStates(blocks, player);
//Check if any changes are made //Check if any changes are made
if (previousHeldItem != itemStack.getItem() || !previousCoordinates.equals(coordinates)) { if (previousHeldItem != itemStack.getItem() || !previousCoordinates.equals(blocks.getCoordinates())) {
onBlocksChanged(player); onBlocksChanged(player);
} }
@@ -176,9 +174,9 @@ public class BuilderChain {
if (blocks.size() > 1 && soundTime < ClientEvents.ticksInGame) { if (blocks.size() > 1 && soundTime < ClientEvents.ticksInGame) {
soundTime = ClientEvents.ticksInGame; soundTime = ClientEvents.ticksInGame;
var firstBlockState = blocks.get(0).blockState; if (blocks.getLastBlockEntry() != null && blocks.getLastBlockEntry().newBlockState != null) {
if (firstBlockState != null) { var lastBlockState = blocks.getLastBlockEntry().newBlockState;
SoundType soundType = firstBlockState.getBlock().getSoundType(firstBlockState, player.level, blocks.get(0).blockPos, player); SoundType soundType = lastBlockState.getBlock().getSoundType(lastBlockState, player.level, blocks.lastPos, player);
SoundEvent soundEvent = state == BuilderChain.State.BREAKING ? soundType.getBreakSound() : soundType.getPlaceSound(); SoundEvent soundEvent = state == BuilderChain.State.BREAKING ? soundType.getBreakSound() : soundType.getPlaceSound();
player.level.playSound(player, player.blockPosition(), soundEvent, SoundSource.BLOCKS, 0.3f, 0.8f); player.level.playSound(player, player.blockPosition(), soundEvent, SoundSource.BLOCKS, 0.3f, 0.8f);
} }
@@ -192,34 +190,45 @@ public class BuilderChain {
Minecraft.getInstance().player.playSound(SoundEvents.UI_TOAST_OUT, 4, 1); Minecraft.getInstance().player.playSound(SoundEvents.UI_TOAST_OUT, 4, 1);
} }
private BlockEntry findStartPosition(Player player, BlockHitResult lookingAt) { private BlockEntry findStartPosition(Player player, BlockHitResult lookingAtFar) {
if (lookingAt == null || lookingAt.getType() == HitResult.Type.MISS) return null; if (lookingAtFar == null || lookingAtFar.getType() == HitResult.Type.MISS) return null;
var startPos = lookingAt.getBlockPos(); var startPos = lookingAtFar.getBlockPos();
//Check if out of reach //Check if out of reach
int maxReach = ReachHelper.getMaxReach(player); int maxReach = ReachHelper.getMaxReach(player);
if (player.blockPosition().distSqr(startPos) > maxReach * maxReach) return null; if (player.blockPosition().distSqr(startPos) > maxReach * maxReach) return null;
//TODO we are always at IDLE state here, find another way to check if we are breaking
if (state != State.BREAKING) { if (state != State.BREAKING) {
//Offset in direction of sidehit if not quickreplace and not replaceable //Offset in direction of sidehit if not quickreplace and not replaceable
boolean isQuickReplacing = EffortlessBuildingClient.QUICK_REPLACE.isQuickReplacing(); boolean shouldOffsetStartPosition = EffortlessBuildingClient.BUILD_SETTINGS.shouldOffsetStartPosition();
boolean replaceable = player.level.getBlockState(startPos).getMaterial().isReplaceable(); boolean replaceable = player.level.getBlockState(startPos).getMaterial().isReplaceable();
boolean becomesDoubleSlab = SurvivalHelper.doesBecomeDoubleSlab(player, startPos); boolean becomesDoubleSlab = SurvivalHelper.doesBecomeDoubleSlab(player, startPos);
if (!isQuickReplacing && !replaceable && !becomesDoubleSlab) { if (!shouldOffsetStartPosition && !replaceable && !becomesDoubleSlab) {
startPos = startPos.relative(lookingAt.getDirection()); startPos = startPos.relative(lookingAtFar.getDirection());
} }
//Get under tall grass and other replaceable blocks //Get under tall grass and other replaceable blocks
if (isQuickReplacing && replaceable) { if (shouldOffsetStartPosition && replaceable) {
startPos = startPos.below(); startPos = startPos.below();
} }
} else {
//Do not break far if we are not allowed to
if (!ReachHelper.canBreakFar(player)) {
boolean startPosIsNear = false;
var lookingAtNear = Minecraft.getInstance().hitResult;
if (lookingAtNear != null && lookingAtNear.getType() == HitResult.Type.BLOCK) {
startPosIsNear = ((BlockHitResult) lookingAtNear).getBlockPos().equals(startPos);
}
if (!startPosIsNear) return null;
}
} }
var blockEntry = new BlockEntry(startPos); var blockEntry = new BlockEntry(startPos);
//Place upside-down stairs if we aim high at block //Place upside-down stairs if we aim high at block
var hitVec = lookingAt.getLocation(); var hitVec = lookingAtFar.getLocation();
//Format hitvec to 0.x //Format hitvec to 0.x
hitVec = new Vec3(Math.abs(hitVec.x - ((int) hitVec.x)), Math.abs(hitVec.y - ((int) hitVec.y)), Math.abs(hitVec.z - ((int) hitVec.z))); hitVec = new Vec3(Math.abs(hitVec.x - ((int) hitVec.x)), Math.abs(hitVec.y - ((int) hitVec.y)), Math.abs(hitVec.z - ((int) hitVec.z)));
if (hitVec.y > 0.5) { if (hitVec.y > 0.5) {
@@ -229,32 +238,19 @@ public class BuilderChain {
return blockEntry; return blockEntry;
} }
private void removeDuplicateCoordinates() { private void findExistingBlockStates(Level world) {
for (int i = 0; i < blocks.size(); i++) { for (BlockEntry blockEntry : blocks) {
BlockEntry blockEntry = blocks.get(i); blockEntry.existingBlockState = world.getBlockState(blockEntry.blockPos);
for (int j = i + 1; j < blocks.size(); j++) {
BlockEntry blockEntry2 = blocks.get(j);
if (blockEntry.blockPos.equals(blockEntry2.blockPos)) {
blocks.remove(j);
j--;
}
}
} }
} }
private void findBlockStates(Player player, ItemStack itemStack) { private void findNewBlockStates(Player player, ItemStack itemStack) {
if (state == State.BREAKING) return;
if (state == State.BREAKING) {
for (BlockEntry blockEntry : blocks) {
blockEntry.blockState = Minecraft.getInstance().level.getBlockState(blockEntry.blockPos);
}
return;
}
if (itemStack.getItem() instanceof BlockItem) { if (itemStack.getItem() instanceof BlockItem) {
for (BlockEntry blockEntry : blocks) { for (BlockEntry blockEntry : blocks) {
blockEntry.blockState = getBlockState(player, InteractionHand.MAIN_HAND, itemStack, blockEntry); blockEntry.newBlockState = BlockUtilities.getBlockState(player, InteractionHand.MAIN_HAND, itemStack, blockEntry);
} }
} else if (CompatHelper.isItemBlockProxy(itemStack, false)) { } else if (CompatHelper.isItemBlockProxy(itemStack, false)) {
@@ -263,44 +259,25 @@ public class BuilderChain {
for (BlockEntry blockEntry : blocks) { for (BlockEntry blockEntry : blocks) {
ItemStack itemBlockStack = CompatHelper.getItemBlockFromStack(itemStack); ItemStack itemBlockStack = CompatHelper.getItemBlockFromStack(itemStack);
if (itemBlockStack == null || itemBlockStack.isEmpty()) continue; if (itemBlockStack == null || itemBlockStack.isEmpty()) continue;
blockEntry.blockState = getBlockState(player, InteractionHand.MAIN_HAND, itemBlockStack, blockEntry); blockEntry.newBlockState = BlockUtilities.getBlockState(player, InteractionHand.MAIN_HAND, itemBlockStack, blockEntry);
} }
} }
} }
public BlockState getBlockState(Player player, InteractionHand hand, ItemStack blockItemStack, BlockEntry blockEntry) {
Block block = Block.byItem(blockItemStack.getItem());
//TODO convert lookingAt hitvec to relative hitvec
var blockHitResult = new BlockHitResult(Vec3.ZERO, Direction.UP, blockEntry.blockPos, false);
return block.getStateForPlacement(new BlockPlaceContext(player, hand, blockItemStack, blockHitResult));
}
private void playSoundIfFurtherThanNormal(Player player, BlockEntry blockEntry, boolean breaking) {
if (Minecraft.getInstance().hitResult != null && Minecraft.getInstance().hitResult.getType() == HitResult.Type.BLOCK)
return;
if (blockEntry == null || blockEntry.blockState == null)
return;
SoundType soundType = blockEntry.blockState.getBlock().getSoundType(blockEntry.blockState, player.level, blockEntry.blockPos, player);
SoundEvent soundEvent = breaking ? soundType.getBreakSound() : soundType.getPlaceSound();
player.level.playSound(player, player.blockPosition(), soundEvent, SoundSource.BLOCKS, 0.6f, soundType.getPitch());
}
private void swingHand(Player player, InteractionHand hand) {
player.swing(hand);
}
public State getState() { public State getState() {
return state; return state;
} }
public List<BlockEntry> getBlocks() { public BlockSet getBlocks() {
return blocks; return blocks;
} }
public List<BlockPos> getCoordinates() { public boolean isBlockInHand() {
return coordinates; return blockInHand;
}
public boolean isLookingAtInteractiveObject() {
return lookingAtInteractiveObject;
} }
} }

View File

@@ -0,0 +1,57 @@
package nl.requios.effortlessbuilding.systems;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import nl.requios.effortlessbuilding.EffortlessBuildingClient;
import nl.requios.effortlessbuilding.compatibility.CompatHelper;
import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import nl.requios.effortlessbuilding.utilities.PlaceChecker;
@OnlyIn(Dist.CLIENT)
public class BuilderFilter {
public static void filterOnCoordinates(BlockSet blocks, Player player) {
}
public static void filterOnExistingBlockStates(BlockSet blocks, Player player) {
var buildSettings = EffortlessBuildingClient.BUILD_SETTINGS;
boolean placing = EffortlessBuildingClient.BUILDER_CHAIN.getState() == BuilderChain.State.PLACING;
var iter = blocks.entrySet().iterator();
while (iter.hasNext()) {
var blockEntry = iter.next().getValue();
var blockState = blockEntry.existingBlockState;
boolean remove = false;
if (!buildSettings.shouldReplaceTileEntities() && blockState.hasBlockEntity()) remove = true;
if (placing) {
if (!buildSettings.shouldReplaceAir() && blockState.isAir()) remove = true;
boolean isSolid = blockState.isRedstoneConductor(player.level, blockEntry.blockPos);
if (!buildSettings.shouldReplaceSolid() && isSolid) remove = true;
}
if (buildSettings.shouldReplaceFiltered()) {
var offhandItem = player.getOffhandItem();
if (!CompatHelper.containsBlock(offhandItem, blockState.getBlock())) remove = true;
}
if (remove) iter.remove();
}
}
public static void filterOnNewBlockStates(BlockSet blocks, Player player) {
var iter = blocks.entrySet().iterator();
while (iter.hasNext()) {
var blockEntry = iter.next().getValue();
boolean remove = false;
if (!PlaceChecker.shouldPlaceBlock(player.level, blockEntry)) remove = true;
if (remove) iter.remove();
}
}
}

View File

@@ -6,7 +6,7 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import nl.requios.effortlessbuilding.buildmodifier.BlockSet; import nl.requios.effortlessbuilding.buildmodifier.UndoRedoBlockSet;
import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo;
import nl.requios.effortlessbuilding.utilities.InventoryHelper; import nl.requios.effortlessbuilding.utilities.InventoryHelper;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper; import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
@@ -97,7 +97,7 @@ public class DelayedBlockPlacer {
//add to undo stack //add to undo stack
BlockPos firstPos = coordinates.get(0); BlockPos firstPos = coordinates.get(0);
BlockPos secondPos = coordinates.get(coordinates.size() - 1); BlockPos secondPos = coordinates.get(coordinates.size() - 1);
UndoRedo.addUndo(player, new BlockSet(coordinates, previousBlockStates, newBlockStates, firstPos, secondPos)); UndoRedo.addUndo(player, new UndoRedoBlockSet(coordinates, previousBlockStates, newBlockStates, firstPos, secondPos));
} }
} }

View File

@@ -1,30 +0,0 @@
package nl.requios.effortlessbuilding.systems;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.network.IsQuickReplacingPacket;
import nl.requios.effortlessbuilding.network.PacketHandler;
@OnlyIn(Dist.CLIENT)
public class QuickReplace {
private boolean isQuickReplacing = false;
public boolean isQuickReplacing() {
return isQuickReplacing;
}
public void toggleQuickReplacing() {
setIsQuickReplacing(!isQuickReplacing);
}
public void setIsQuickReplacing(boolean isQuickReplacing) {
this.isQuickReplacing = isQuickReplacing;
EffortlessBuilding.log(Minecraft.getInstance().player, "Set " + ChatFormatting.GOLD + "Quick Replace " +
ChatFormatting.RESET + (this.isQuickReplacing ? "on" : "off"));
PacketHandler.INSTANCE.sendToServer(new IsQuickReplacingPacket(this.isQuickReplacing));
}
}

View File

@@ -1,13 +1,27 @@
package nl.requios.effortlessbuilding.systems; package nl.requios.effortlessbuilding.systems;
import net.minecraft.core.BlockPos; import com.google.common.collect.Lists;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BucketItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.ItemHandlerHelper;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.create.foundation.utility.BlockHelper; import nl.requios.effortlessbuilding.create.foundation.utility.BlockHelper;
import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockEntry;
import nl.requios.effortlessbuilding.utilities.BlockSet;
import java.util.List; import java.util.List;
@@ -15,7 +29,7 @@ import java.util.List;
public class ServerBlockPlacer { public class ServerBlockPlacer {
private boolean isPlacingOrBreakingBlocks = false; private boolean isPlacingOrBreakingBlocks = false;
public void placeBlocks(Player player, List<BlockEntry> blocks) { public void placeBlocks(Player player, BlockSet blocks) {
EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks"); EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks");
for (BlockEntry block : blocks) { for (BlockEntry block : blocks) {
@@ -28,11 +42,11 @@ public class ServerBlockPlacer {
if (!world.isLoaded(block.blockPos)) return; if (!world.isLoaded(block.blockPos)) return;
isPlacingOrBreakingBlocks = true; isPlacingOrBreakingBlocks = true;
BlockHelper.placeSchematicBlock(world, block.blockState, block.blockPos, block.itemStack, null); boolean placedBlock = onPlaceItemIntoWorld(player, block) == InteractionResult.SUCCESS;
isPlacingOrBreakingBlocks = false; isPlacingOrBreakingBlocks = false;
} }
public void breakBlocks(Player player, List<BlockEntry> blocks) { public void breakBlocks(Player player, BlockSet blocks) {
EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks"); EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks");
for (BlockEntry block : blocks) { for (BlockEntry block : blocks) {
@@ -45,7 +59,7 @@ public class ServerBlockPlacer {
if (!world.isLoaded(block.blockPos) || world.isEmptyBlock(block.blockPos)) return; if (!world.isLoaded(block.blockPos) || world.isEmptyBlock(block.blockPos)) return;
isPlacingOrBreakingBlocks = true; isPlacingOrBreakingBlocks = true;
BlockHelper.destroyBlockAs(world, block.blockPos, player, player.getMainHandItem(), 0f, stack -> { boolean brokeBlock = BlockHelper.destroyBlockAs(world, block.blockPos, player, player.getMainHandItem(), 0f, stack -> {
if (!player.isCreative()) { if (!player.isCreative()) {
ItemHandlerHelper.giveItemToPlayer(player, stack); ItemHandlerHelper.giveItemToPlayer(player, stack);
} }
@@ -56,4 +70,95 @@ public class ServerBlockPlacer {
public boolean isPlacingOrBreakingBlocks() { public boolean isPlacingOrBreakingBlocks() {
return isPlacingOrBreakingBlocks; return isPlacingOrBreakingBlocks;
} }
//ForgeHooks::onPlaceItemIntoWorld
private InteractionResult onPlaceItemIntoWorld(Player player, BlockEntry block) {
ItemStack itemstack = block.itemStack;
Level level = player.level;
if (player != null && !player.getAbilities().mayBuild && !itemstack.hasAdventureModePlaceTagForBlock(level.registryAccess().registryOrThrow(Registry.BLOCK_REGISTRY), new BlockInWorld(level, block.blockPos, false)))
return InteractionResult.PASS;
// handle all placement events here
Item item = itemstack.getItem();
int size = itemstack.getCount();
CompoundTag nbt = null;
if (itemstack.getTag() != null)
nbt = itemstack.getTag().copy();
if (!(itemstack.getItem() instanceof BucketItem)) // if not bucket
level.captureBlockSnapshots = true;
ItemStack copy = itemstack.copy();
BlockHelper.placeSchematicBlock(level, player, block.newBlockState, block.blockPos, block.itemStack, null);
InteractionResult ret = InteractionResult.SUCCESS;
if (itemstack.isEmpty())
ForgeEventFactory.onPlayerDestroyItem(player, copy, InteractionHand.MAIN_HAND);
level.captureBlockSnapshots = false;
if (ret.consumesAction())
{
// save new item data
int newSize = itemstack.getCount();
CompoundTag newNBT = null;
if (itemstack.getTag() != null)
{
newNBT = itemstack.getTag().copy();
}
@SuppressWarnings("unchecked")
List<BlockSnapshot> blockSnapshots = (List<BlockSnapshot>)level.capturedBlockSnapshots.clone();
level.capturedBlockSnapshots.clear();
// make sure to set pre-placement item data for event
itemstack.setCount(size);
itemstack.setTag(nbt);
Direction side = Direction.UP;
boolean eventResult = false;
if (blockSnapshots.size() > 1)
{
eventResult = ForgeEventFactory.onMultiBlockPlace(player, blockSnapshots, side);
}
else if (blockSnapshots.size() == 1)
{
eventResult = ForgeEventFactory.onBlockPlace(player, blockSnapshots.get(0), side);
}
if (eventResult)
{
ret = InteractionResult.FAIL; // cancel placement
// revert back all captured blocks
for (BlockSnapshot blocksnapshot : Lists.reverse(blockSnapshots))
{
level.restoringBlockSnapshots = true;
blocksnapshot.restore(true, false);
level.restoringBlockSnapshots = false;
}
}
else
{
// Change the stack to its new content
itemstack.setCount(newSize);
itemstack.setTag(newNBT);
for (BlockSnapshot snap : blockSnapshots)
{
int updateFlag = snap.getFlag();
BlockState oldBlock = snap.getReplacedBlock();
BlockState newBlock = level.getBlockState(snap.getPos());
newBlock.onPlace(level, snap.getPos(), oldBlock, false);
level.markAndNotifyBlock(snap.getPos(), level.getChunkAt(snap.getPos()), oldBlock, newBlock, updateFlag, 512);
}
if (player != null)
player.awardStat(Stats.ITEM_USED.get(item));
}
}
level.capturedBlockSnapshots.clear();
return ret;
}
} }

View File

@@ -30,4 +30,8 @@ public class ServerBuildState {
player.getPersistentData().remove(IS_QUICK_REPLACING_KEY); player.getPersistentData().remove(IS_QUICK_REPLACING_KEY);
} }
} }
public static boolean isLikeVanilla(Player player) {
return !isUsingBuildMode(player) && !isQuickReplacing(player);
}
} }

View File

@@ -8,7 +8,6 @@ import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import java.util.BitSet; import java.util.BitSet;
import java.util.Objects;
public class BlockEntry { public class BlockEntry {
public final BlockPos blockPos; public final BlockPos blockPos;
@@ -16,7 +15,9 @@ public class BlockEntry {
public boolean mirrorY; public boolean mirrorY;
public boolean mirrorZ; public boolean mirrorZ;
public Rotation rotation; public Rotation rotation;
public BlockState blockState; //BlockState that is currently in the world
public BlockState existingBlockState;
public BlockState newBlockState;
public ItemStack itemStack = ItemStack.EMPTY; public ItemStack itemStack = ItemStack.EMPTY;
public BlockEntry(BlockPos blockPos) { public BlockEntry(BlockPos blockPos) {
@@ -24,7 +25,7 @@ public class BlockEntry {
} }
public boolean meansBreakBlock() { public boolean meansBreakBlock() {
return blockState == null || blockState.isAir(); return newBlockState == null || newBlockState.isAir();
} }
public BitSet getMirrorBitSet() { public BitSet getMirrorBitSet() {
@@ -43,13 +44,13 @@ public class BlockEntry {
public static void encode(FriendlyByteBuf buf, BlockEntry block) { public static void encode(FriendlyByteBuf buf, BlockEntry block) {
buf.writeBlockPos(block.blockPos); buf.writeBlockPos(block.blockPos);
buf.writeNullable(block.blockState, (buffer, blockState) -> buffer.writeNbt(NbtUtils.writeBlockState(blockState))); buf.writeNullable(block.newBlockState, (buffer, blockState) -> buffer.writeNbt(NbtUtils.writeBlockState(blockState)));
buf.writeItem(block.itemStack); buf.writeItem(block.itemStack);
} }
public static BlockEntry decode(FriendlyByteBuf buf) { public static BlockEntry decode(FriendlyByteBuf buf) {
BlockEntry block = new BlockEntry(buf.readBlockPos()); BlockEntry block = new BlockEntry(buf.readBlockPos());
block.blockState = buf.readNullable(buffer -> { block.newBlockState = buf.readNullable(buffer -> {
var nbt = buf.readNbt(); var nbt = buf.readNbt();
if (nbt == null) return null; if (nbt == null) return null;
return NbtUtils.readBlockState(nbt); return NbtUtils.readBlockState(nbt);

View File

@@ -0,0 +1,81 @@
package nl.requios.effortlessbuilding.utilities;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.FriendlyByteBuf;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
public class BlockSet extends HashMap<BlockPos, BlockEntry> implements Iterable<BlockEntry> {
public static boolean logging = true;
public BlockPos firstPos;
public BlockPos lastPos;
public BlockSet() {
super();
}
public BlockSet(BlockSet blockSet) {
super(blockSet);
this.firstPos = blockSet.firstPos;
this.lastPos = blockSet.lastPos;
}
public BlockSet(List<BlockEntry> blockEntries) {
super();
for (BlockEntry blockEntry : blockEntries) {
add(blockEntry);
}
}
public void add(BlockEntry blockEntry) {
if (!containsKey(blockEntry.blockPos)) {
//Limit number of blocks you can place
int limit = ReachHelper.getMaxBlocksPlacedAtOnce(Minecraft.getInstance().player);
if (size() >= limit) {
if (logging) EffortlessBuilding.log("BlockSet limit reached, not adding block at " + blockEntry.blockPos);
return;
}
put(blockEntry.blockPos, blockEntry);
} else {
if (logging) EffortlessBuilding.log("BlockSet already contains block at " + blockEntry.blockPos);
}
}
public HashSet<BlockPos> getCoordinates() {
return new HashSet<>(keySet());
}
public BlockEntry getFirstBlockEntry() {
return get(firstPos);
}
public BlockEntry getLastBlockEntry() {
return get(lastPos);
}
@NotNull
@Override
public Iterator<BlockEntry> iterator() {
return this.values().iterator();
}
public static void encode(FriendlyByteBuf buf, BlockSet block) {
buf.writeCollection(block.values(), BlockEntry::encode);
}
public static BlockSet decode(FriendlyByteBuf buf) {
return new BlockSet(buf.readList(BlockEntry::decode));
}
}

View File

@@ -0,0 +1,58 @@
package nl.requios.effortlessbuilding.utilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class BlockUtilities {
public static BlockState getBlockState(Player player, InteractionHand hand, ItemStack blockItemStack, BlockEntry blockEntry) {
Block block = Block.byItem(blockItemStack.getItem());
//TODO convert lookingAt hitvec to relative hitvec
var blockHitResult = new BlockHitResult(Vec3.ZERO, Direction.UP, blockEntry.blockPos, false);
return block.getStateForPlacement(new BlockPlaceContext(player, hand, blockItemStack, blockHitResult));
}
public static boolean determineIfLookingAtInteractiveObject(Minecraft mc, ClientLevel level) {
//Check if we are looking at an interactive object
var result = false;
if (mc.hitResult != null) {
if (mc.hitResult.getType() == HitResult.Type.BLOCK) {
var blockHitResult = (BlockHitResult) mc.hitResult;
var blockState = level.getBlockState(blockHitResult.getBlockPos());
if (blockState.hasBlockEntity()) {
result = true;
}
}
if (mc.hitResult.getType() == HitResult.Type.ENTITY) {
result = true;
}
}
return result;
}
public static void playSoundIfFurtherThanNormal(Player player, BlockEntry blockEntry, boolean breaking) {
if (Minecraft.getInstance().hitResult != null && Minecraft.getInstance().hitResult.getType() == HitResult.Type.BLOCK)
return;
if (blockEntry == null || blockEntry.newBlockState == null)
return;
SoundType soundType = blockEntry.newBlockState.getBlock().getSoundType(blockEntry.newBlockState, player.level, blockEntry.blockPos, player);
SoundEvent soundEvent = breaking ? soundType.getBreakSound() : soundType.getPlaceSound();
player.level.playSound(player, player.blockPosition(), soundEvent, SoundSource.BLOCKS, 0.6f, soundType.getPitch());
}
}

View File

@@ -7,6 +7,7 @@ import net.minecraft.world.item.ItemStack;
public class InventoryHelper { public class InventoryHelper {
@Deprecated //Use BlockHelper.findAndRemoveInInventory instead
public static ItemStack findItemStackInInventory(Player player, Block block) { public static ItemStack findItemStackInInventory(Player player, Block block) {
for (ItemStack invStack : player.getInventory().items) { for (ItemStack invStack : player.getInventory().items) {
if (!invStack.isEmpty() && invStack.getItem() instanceof BlockItem && if (!invStack.isEmpty() && invStack.getItem() instanceof BlockItem &&

View File

@@ -0,0 +1,105 @@
package nl.requios.effortlessbuilding.utilities;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.piston.PistonHeadBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BedPart;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public class PlaceChecker {
//SchematicPrinter::shouldPlaceBlock
public static boolean shouldPlaceBlock(Level world, BlockEntry blockEntry) {
if (world == null)
return false;
var pos = blockEntry.blockPos;
var state = blockEntry.newBlockState;
BlockEntity tileEntity = null;
BlockState toReplace = world.getBlockState(pos);
BlockEntity toReplaceTE = world.getBlockEntity(pos);
BlockState toReplaceOther = null;
if (state.hasProperty(BlockStateProperties.BED_PART) && state.hasProperty(BlockStateProperties.HORIZONTAL_FACING)
&& state.getValue(BlockStateProperties.BED_PART) == BedPart.FOOT)
toReplaceOther = world.getBlockState(pos.relative(state.getValue(BlockStateProperties.HORIZONTAL_FACING)));
if (state.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)
&& state.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.LOWER)
toReplaceOther = world.getBlockState(pos.above());
if (!world.isLoaded(pos))
return false;
if (!world.getWorldBorder().isWithinBounds(pos))
return false;
if (toReplace == state)
return false;
if (toReplace.getDestroySpeed(world, pos) == -1
|| (toReplaceOther != null && toReplaceOther.getDestroySpeed(world, pos) == -1))
return false;
boolean isNormalCube = state.isRedstoneConductor(world, pos);
return shouldPlace(world, pos, state, tileEntity, toReplace, toReplaceOther, isNormalCube);
}
//SchematicannonTileEntity::shouldPlace
private static boolean shouldPlace(Level level, BlockPos pos, BlockState state, BlockEntity tileEntity, BlockState toReplace,
BlockState toReplaceOther, boolean isNormalCube) {
return true;
// if (!replaceTileEntities
// && (toReplace.hasBlockEntity() || (toReplaceOther != null && toReplaceOther.hasBlockEntity())))
// return false;
//
// if (shouldIgnoreBlockState(state))
// return false;
//
// boolean placingAir = state.isAir();
//
// if (replaceMode == 3)
// return true;
// if (replaceMode == 2 && !placingAir)
// return true;
// if (replaceMode == 1 && (isNormalCube || (!toReplace.isRedstoneConductor(level, pos)
// && (toReplaceOther == null || !toReplaceOther.isRedstoneConductor(level, pos)))) && !placingAir)
// return true;
// if (replaceMode == 0 && !toReplace.isRedstoneConductor(level, pos)
// && (toReplaceOther == null || !toReplaceOther.isRedstoneConductor(level, pos)) && !placingAir)
// return true;
//
// return false;
}
//SchematicannonTileEntity::shouldIgnoreBlockState
private static boolean shouldIgnoreBlockState(BlockState state) {
// Block doesn't have a mapping (Water, lava, etc)
if (state.getBlock() == Blocks.STRUCTURE_VOID)
return true;
// ItemRequirement requirement = ItemRequirement.of(state, te);
// if (requirement.isEmpty())
// return false;
// if (requirement.isInvalid())
// return false;
// Block doesn't need to be placed twice (Doors, beds, double plants)
if (state.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)
&& state.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.UPPER)
return true;
if (state.hasProperty(BlockStateProperties.BED_PART)
&& state.getValue(BlockStateProperties.BED_PART) == BedPart.HEAD)
return true;
if (state.getBlock() instanceof PistonHeadBlock)
return true;
// if (AllBlocks.BELT.has(state))
// return state.getValue(BeltBlock.PART) == BeltPart.MIDDLE;
return false;
}
}

View File

@@ -2,7 +2,6 @@ package nl.requios.effortlessbuilding.utilities;
import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
@@ -20,12 +19,10 @@ import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.pattern.BlockInWorld; import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import net.minecraft.world.level.material.Material; import net.minecraft.world.level.material.Material;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.event.ForgeEventFactory;
import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.CommonConfig;
import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.EffortlessBuildingClient;
import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager;
import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.compatibility.CompatHelper;
import nl.requios.effortlessbuilding.systems.ServerBuildState; import nl.requios.effortlessbuilding.systems.ServerBuildState;
@@ -273,11 +270,9 @@ public class SurvivalHelper {
} }
//Check quickreplace //Check quickreplace
boolean isQuickReplacing = false;
if (placer instanceof Player player) { if (placer instanceof Player player) {
if (world.isClientSide) EffortlessBuildingClient.QUICK_REPLACE.isQuickReplacing(); boolean isQuickReplacing = world.isClientSide ? EffortlessBuildingClient.BUILD_SETTINGS.isQuickReplacing()
else isQuickReplacing = ServerBuildState.isQuickReplacing(player); : ServerBuildState.isQuickReplacing(player);
if (isQuickReplacing) return true; if (isQuickReplacing) return true;
} }