Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove WorldEdit hard-dependency #212

Merged
merged 13 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import rip.hippo.inject.Injector;
import tools.redstone.redstonetools.macros.WorldlessCommandHelper;
import tools.redstone.redstonetools.utils.ReflectionUtils;
import tools.redstone.redstonetools.utils.WorldEditUtils;

public class RedstoneToolsClient implements ClientModInitializer {
public static final String MOD_ID = "redstonetools";
Expand All @@ -24,8 +25,12 @@ public void onInitializeClient() {

// Register features
ReflectionUtils.getFeatures().forEach(feature -> {
LOGGER.trace("Registering feature {}", feature);
LOGGER.trace("Registering feature {}", feature.getClass().getName());

if (feature.requiresWorldEdit() && !WorldEditUtils.WORLDEDIT_LOADED) {
LOGGER.warn("Feature {} requires WorldEdit, but WorldEdit is not loaded. Skipping registration.", feature.getName());
return;
}
feature.register();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory;
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry;
import net.minecraft.world.GameRules;
import tools.redstone.redstonetools.utils.WorldEditUtils;


public class RedstoneToolsGameRules {
Expand All @@ -14,6 +15,9 @@ private RedstoneToolsGameRules() {

public static void register() {
DO_CONTAINER_DROPS = GameRuleRegistry.register("doContainerDrops", GameRules.Category.DROPS, GameRuleFactory.createBooleanRule(true));
DO_BLOCK_UPDATES_AFTER_EDIT = GameRuleRegistry.register("doBlockUpdatesAfterEdit", GameRules.Category.UPDATES, GameRuleFactory.createBooleanRule(false));

if (WorldEditUtils.WORLDEDIT_LOADED) {
DO_BLOCK_UPDATES_AFTER_EDIT = GameRuleRegistry.register("doBlockUpdatesAfterEdit", GameRules.Category.UPDATES, GameRuleFactory.createBooleanRule(false));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public String getCommand() {
return feature.command();
}

public boolean requiresWorldEdit() {
return feature.worldedit();
}

/**
* Register this feature.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
String name();
String description();
String command();
boolean worldedit() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import static tools.redstone.redstonetools.features.arguments.serializers.NumberBaseSerializer.numberBase;

@AutoService(AbstractFeature.class)
@Feature(name = "Binary Block Read", description = "Interprets your WorldEdit selection as a binary number.", command = "/read")
@Feature(name = "Binary Block Read", description = "Interprets your WorldEdit selection as a binary number.", command = "/read", worldedit = true)
public class BinaryBlockReadFeature extends CommandFeature {
private static final BlockStateArgument LIT_LAMP_ARG = new BlockStateArgument(
Blocks.REDSTONE_LAMP.getDefaultState().with(RedstoneLampBlock.LIT, true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import static tools.redstone.redstonetools.features.arguments.serializers.BlockColorSerializer.blockColor;

@AutoService(AbstractFeature.class)
@Feature(name = "Color Code", description = "Color codes all color-able blocks in your WorldEdit selection.", command = "/colorcode")
@Feature(name = "Color Code", description = "Color codes all color-able blocks in your WorldEdit selection.", command = "/colorcode", worldedit = true)
public class ColorCodeFeature extends CommandFeature {
public static final Argument<BlockColor> color = Argument
.ofType(blockColor());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.List;

@AutoService(AbstractFeature.class)
@Feature(command = "/minsel", description = "Removes all air-only layers from a selection", name = "Minimize Selection")
@Feature(command = "/minsel", description = "Removes all air-only layers from a selection", name = "Minimize Selection", worldedit = true)
public class MinSelectionFeature extends CommandFeature {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import static tools.redstone.redstonetools.utils.DirectionUtils.matchDirection;

@AutoService(AbstractFeature.class)
@Feature(name = "RStack", description = "Stacks with custom distance", command = "/rstack")
@Feature(name = "RStack", description = "Stacks with custom distance", command = "/rstack", worldedit = true)
public class RStackFeature extends CommandFeature {
public static final Argument<Integer> count = Argument
.ofType(integer())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import static tools.redstone.redstonetools.RedstoneToolsGameRules.DO_CONTAINER_DROPS;
import tools.redstone.redstonetools.RedstoneToolsGameRules;

@Mixin(ItemScatterer.class)
public class DoContainerDropsMixin {
@Inject(method = "spawn(Lnet/minecraft/world/World;DDDLnet/minecraft/item/ItemStack;)V", at = @At("HEAD"), cancellable = true)
private static void spawn(World world, double x, double y, double z, ItemStack stack, CallbackInfo ci) {
if (!world.getGameRules().getBoolean(DO_CONTAINER_DROPS)) ci.cancel();
if (RedstoneToolsGameRules.DO_CONTAINER_DROPS != null && !world.getGameRules().getBoolean(RedstoneToolsGameRules.DO_CONTAINER_DROPS)) {
xtrm-en marked this conversation as resolved.
Show resolved Hide resolved
ci.cancel();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package tools.redstone.redstonetools.utils;

import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import rip.hippo.inject.DoctorModule;
import tools.redstone.redstonetools.features.AbstractFeature;
import tools.redstone.redstonetools.features.Feature;
import tools.redstone.redstonetools.features.arguments.Argument;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

public class ReflectionUtils {
private static final ServiceLoader<DoctorModule> modulesLoader =
ServiceLoader.load(DoctorModule.class);
private static final ServiceLoader<AbstractFeature> featuresLoader =
ServiceLoader.load(AbstractFeature.class);
private static final Logger LOGGER = LogManager.getLogger();
private static DoctorModule[] modules;
private static Set<? extends AbstractFeature> features;

Expand All @@ -26,22 +26,65 @@ private ReflectionUtils() {

public static DoctorModule[] getModules() {
if (modules == null) {
modules = modulesLoader.stream()
.map(ServiceLoader.Provider::get)
.toArray(DoctorModule[]::new);
try {
modules = serviceLoad(DoctorModule.class)
.toArray(DoctorModule[]::new);
} catch (IOException e) {
throw new RuntimeException("Failed to load modules", e);
}
}
return modules;
}

public static Set<? extends AbstractFeature> getFeatures() {
if (features == null) {
features = featuresLoader.stream()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toSet());
try {
features = serviceLoad(AbstractFeature.class);
} catch (IOException e) {
throw new RuntimeException("Failed to load features", e);
}
}
return features;
}

private static <T> Set<? extends T> serviceLoad(Class<T> clazz) throws IOException {
ClassLoader cl = ReflectionUtils.class.getClassLoader();
Enumeration<URL> serviceFiles = cl.getResources("META-INF/services/" + clazz.getName());
Set<String> classNames = new HashSet<>();
while (serviceFiles.hasMoreElements()) {
URL serviceFile = serviceFiles.nextElement();
try (var reader = serviceFile.openStream()) {
classNames.addAll(IOUtils.readLines(reader, "UTF-8"));
}
}
return classNames.stream()
.filter(it -> !it.isEmpty() && !it.isBlank())
.map(ReflectionUtils::loadClass)
.filter(Objects::nonNull)
.filter(clazz::isAssignableFrom)
.map(it -> {
try {
return it.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to instantiate " + it, e);
}
})
.map(clazz::cast)
.collect(Collectors.toSet());
}

@SuppressWarnings("unchecked")
private static <T> @Nullable Class<? extends T> loadClass(String className) {
try {
return (Class<? extends T>) Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to load class " + className, e);
} catch (NoClassDefFoundError e) {
LOGGER.warn("Failed to load class {}, required {}", className, e.getMessage());
}
return null;
}

public static List<Argument<?>> getArguments(Class<? extends AbstractFeature> featureClass) {
return Arrays.stream(featureClass.getFields())
.filter(field -> Argument.class.isAssignableFrom(field.getType()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.function.Consumer;

public class WorldEditUtils {
public static final boolean WORLDEDIT_LOADED;

/**
* Execute a function for each block in
* the provided region.
Expand All @@ -28,6 +30,10 @@ public class WorldEditUtils {
*/
public static void forEachBlockInRegion(Region region,
Consumer<BlockVector3> consumer) {
if (!WORLDEDIT_LOADED) {
throw new IllegalStateException("WorldEdit is not loaded.");
}

CuboidRegion bb = region.getBoundingBox();
BlockVector3 min = bb.getMinimumPoint();
BlockVector3 max = bb.getMaximumPoint();
Expand All @@ -44,6 +50,10 @@ public static void forEachBlockInRegion(Region region,
}

public static Either<Region, Feedback> getSelection(ServerPlayerEntity player) {
if (!WORLDEDIT_LOADED) {
return Either.right(Feedback.invalidUsage("WorldEdit is not loaded."));
xtrm-en marked this conversation as resolved.
Show resolved Hide resolved
}

var actor = FabricAdapter.adaptPlayer(player);

var localSession = WorldEdit.getInstance()
Expand All @@ -58,4 +68,14 @@ public static Either<Region, Feedback> getSelection(ServerPlayerEntity player) {
return Either.right(Feedback.invalidUsage("Please make a selection with WorldEdit first"));
}
}

static {
boolean loaded = false;
try {
Class.forName("com.sk89q.worldedit.WorldEdit");
loaded = true;
} catch (Throwable ignored) {
}
WORLDEDIT_LOADED = loaded;
}
}
8 changes: 5 additions & 3 deletions src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
],

"depends": {
"fabricloader": ">=0.14.6",
"fabricloader": ">=0.13.0",
"fabric": "*",
"minecraft": "~1.18.2",
"java": ">=17",
"worldedit": "7.2.10"
"java": ">=17"
},
"recommends": {
"worldedit": "*"
}
}