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

Custom collision whitelists #148

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and Freecam's versioning is based on [Semantic Versioning](https://semver.org/sp

- Added a way to configure key bindings from Freecam's config menu ([#143](https://github.com/MinecraftFreecam/Freecam/pull/143)).
- Added an optional server whitelist/blacklist ([#146](https://github.com/MinecraftFreecam/Freecam/pull/146)).
- A custom "ignore collision" list, using block IDs or regular expressions ([#148](https://github.com/MinecraftFreecam/Freecam/pull/148)).

### Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package net.xolt.freecam.config;

import me.shedaniel.autoconfig.ConfigHolder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.level.block.*;
import net.xolt.freecam.variant.api.BuildVariant;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -10,7 +13,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CollisionWhitelist {
public class CollisionBehavior {

private static final Predicate<Block> transparent = Builder.builder()
.matching(TransparentBlock.class, IronBarsBlock.class)
Expand All @@ -22,12 +25,46 @@ public class CollisionWhitelist {
.matching(DoorBlock.class, TrapDoorBlock.class)
.build();

public static boolean isTransparent(Block block) {
return transparent.test(block);
private static Predicate<Block> custom = block -> false;

@SuppressWarnings("RedundantIfStatement")
public static boolean isIgnored(Block block) {
if (ModConfig.INSTANCE.collision.ignoreAll && BuildVariant.getInstance().cheatsPermitted()) {
return true;
}

System.out.println("Checking " + BuiltInRegistries.BLOCK.getKey(block));

if (ModConfig.INSTANCE.collision.ignoreTransparent && transparent.test(block)) {
return true;
}

if (ModConfig.INSTANCE.collision.ignoreOpenable && openable.test(block)) {
return true;
}

if (ModConfig.INSTANCE.collision.ignoreCustom && custom.test(block)) {
return true;
}

return false;
}

public static boolean isOpenable(Block block) {
return openable.test(block);
static InteractionResult onConfigChange(ConfigHolder<ModConfig> holder, ModConfig config) {
String[] ids = config.collision.whitelist.ids.stream()
.map(id -> id.contains(":") ? id : "minecraft:" + id)
.toArray(String[]::new);

Pattern[] patterns = config.collision.whitelist.patterns.stream()
.map(Pattern::compile)
.toArray(Pattern[]::new);

custom = Builder.builder()
.matching(ids)
.matching(patterns)
.build();

return InteractionResult.PASS;
}

private static String getBlockId(Block block) {
Expand Down
23 changes: 21 additions & 2 deletions common/src/main/java/net/xolt/freecam/config/ModConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import me.shedaniel.autoconfig.AutoConfig;
import me.shedaniel.autoconfig.ConfigData;
import me.shedaniel.autoconfig.ConfigHolder;
import me.shedaniel.autoconfig.annotation.Config;
import me.shedaniel.autoconfig.annotation.ConfigEntry;
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.EnumHandler.EnumDisplayOption;
import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer;
import me.shedaniel.clothconfig2.gui.entries.SelectionListEntry;
import net.xolt.freecam.config.gui.AutoConfigExtensions;
import net.xolt.freecam.config.gui.ValidateRegex;
import net.xolt.freecam.config.gui.BoundedContinuous;
import net.xolt.freecam.config.gui.ModBindingsConfig;
import net.xolt.freecam.config.gui.VariantTooltip;
Expand All @@ -24,9 +26,12 @@ public class ModConfig implements ConfigData {
public static ModConfig INSTANCE;

public static void init() {
AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new);
ConfigHolder<ModConfig> holder = AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new);
AutoConfigExtensions.apply(ModConfig.class);
INSTANCE = AutoConfig.getConfigHolder(ModConfig.class).getConfig();
holder.registerSaveListener(CollisionBehavior::onConfigChange);
holder.registerLoadListener(CollisionBehavior::onConfigChange);
INSTANCE = holder.getConfig();
CollisionBehavior.onConfigChange(holder, INSTANCE); // Listener isn't called on initial load...
}

@ConfigEntry.Gui.Tooltip
Expand Down Expand Up @@ -64,6 +69,20 @@ public static class CollisionConfig {
@ConfigEntry.Gui.Tooltip
public boolean ignoreOpenable = false;

@VariantTooltip(variant = "normal", count = 1)
@VariantTooltip(variant = "modrinth", count = 2)
public boolean ignoreCustom = false;

@ConfigEntry.Gui.TransitiveObject
public CollisionWhitelist whitelist = new CollisionWhitelist();
public static class CollisionWhitelist {
@ConfigEntry.Gui.Tooltip(count = 2)
public List<String> ids = new ArrayList<>();
@ValidateRegex
@ConfigEntry.Gui.Tooltip(count = 2)
public List<String> patterns = new ArrayList<>();
}

@VariantTooltip(variant = "normal", count = 2)
@VariantTooltip(variant = "modrinth", count = 3)
// Default to true, when not running a modrinth build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
import net.minecraft.network.chat.Component;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
Expand All @@ -18,6 +21,7 @@
* @see DefaultGuiProviders
* @see DefaultGuiTransformers
*/
@SuppressWarnings("JavadocReference")
public class AutoConfigExtensions {
static final Component RESET_TEXT = Component.translatable("text.cloth-config.reset_value");
static final ConfigEntryBuilder ENTRY_BUILDER = ConfigEntryBuilder.create();
Expand All @@ -26,14 +30,77 @@ private AutoConfigExtensions() {}

public static void apply(Class<? extends ConfigData> configClass) {
GuiRegistry registry = AutoConfig.getGuiRegistry(configClass);
Requirements.apply(registry);

ModBindingsConfigImpl.apply(registry);
VariantTooltipImpl.apply(registry);
ValidateRegexImpl.apply(registry);
BoundedContinuousImpl.apply(registry);

CollisionDependencies.apply(registry);
CollisionWhitelistDependencies.apply(registry);
ServerRestrictionDependencies.apply(registry);
}

static Predicate<Field> isField(Class<?> declaringClass, String... fieldNames) {
return field -> field.getDeclaringClass().equals(declaringClass) && Arrays.asList(fieldNames).contains(field.getName());
}

static Predicate<Field> isArrayOrListOfType(Type... types) {
return field -> {
if (field.getType().isArray()) {
Class<?> component = field.getType().getComponentType();
return Arrays.asList(types).contains(component);
}
return isListOfType(types).test(field);
};
}

static Predicate<Field> isNotArrayOrListOfType(Type... types) {
return field -> {
if (field.getType().isArray()) {
Class<?> component = field.getType().getComponentType();
return Arrays.stream(types).noneMatch(component::equals);
}
return isNotListOfType(types).test(field);
};
}

/**
* Returns a predicate that tests if the field is a {@link List} of a particular {@link Type}, e.g. a {@code List<Integer>}.
* <p>
* Based on a {@link DefaultGuiProviders#isListOfType(Type...) helper method} in cloth-config.
*
* @param types the types to check for in the list's parameter
* @return {@code true} if the field is a list containing the provided type, {@code false} otherwise
*/
static Predicate<Field> isListOfType(Type... types) {
return field -> {
if (List.class.isAssignableFrom(field.getType()) && field.getGenericType() instanceof ParameterizedType generic) {
Type[] args = generic.getActualTypeArguments();
return args.length == 1 && Arrays.asList(types).contains(args[0]);
} else {
return false;
}
};
}

/**
* Returns a predicate that tests if the field is a {@link List} <strong>not</strong> of a particular {@link Type},
* e.g. any {@code List} that isn't a {@code List<Integer>}.
* <p>
* Based on a {@link DefaultGuiProviders#isNotListOfType(Type...) helper method} in cloth-config.
*
* @param types the types to check for in the list's parameter
* @return {@code true} if the field is a list not containing the provided type, {@code false} otherwise
*/
static Predicate<Field> isNotListOfType(Type... types) {
return field -> {
if (List.class.isAssignableFrom(field.getType()) && field.getGenericType() instanceof ParameterizedType generic) {
Type[] args = generic.getActualTypeArguments();
return args.length == 1 && Arrays.stream(types).noneMatch(args[0]::equals);
} else {
return false;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
import static java.lang.Boolean.FALSE;

@SuppressWarnings("UnstableApiUsage")
class Requirements {
class CollisionDependencies {

private static final Logger LOGGER = LogManager.getLogger();
private static ValueHolder<Boolean> ignoreAllWidget;

private Requirements() {}
private CollisionDependencies() {}

static void apply(GuiRegistry guiRegistry) {
// FIXME These transformers assume that no subsequent GUI transformers will replace
Expand Down Expand Up @@ -55,9 +55,9 @@ static void apply(GuiRegistry guiRegistry) {
guis.stream()
.filter(BooleanListEntry.class::isInstance)
.map(BooleanListEntry.class::cast)
.forEach(gui -> gui.setRequirement(Requirements::notIgnoreAll));
.forEach(gui -> gui.setRequirement(CollisionDependencies::notIgnoreAll));
return guis;
}, field -> List.of("ignoreTransparent", "ignoreOpenable").contains(field.getName()));
}, field -> List.of("ignoreTransparent", "ignoreOpenable", "ignoreCustom").contains(field.getName()));
}

// Requirement handler: require ignoreAll is set to "No"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.gui.registry.GuiRegistry;
import me.shedaniel.clothconfig2.gui.entries.BooleanListEntry;
import net.xolt.freecam.config.ModConfig;

import static net.xolt.freecam.config.gui.AutoConfigExtensions.isField;

class CollisionWhitelistDependencies {

private static BooleanListEntry ignoreCustom;

@SuppressWarnings("UnstableApiUsage")
static void apply(GuiRegistry registry) {
// Capture a reference to the ignoreCustom entry
registry.registerPredicateTransformer(
(guis, i18n, field, config, defaults, guiProvider) -> {
ignoreCustom = guis.stream()
.filter(BooleanListEntry.class::isInstance)
.map(BooleanListEntry.class::cast)
.reduce((prev, next) -> { throw new IllegalStateException("Multiple BooleanListEntries added to %s.ignoreCustom".formatted(ModConfig.CollisionConfig.class.getSimpleName())); })
.orElseThrow(() -> new IllegalStateException("No BooleanListEntries added to %s.ignoreCustom".formatted(ModConfig.CollisionConfig.class.getSimpleName())));
return guis;
},
isField(ModConfig.CollisionConfig.class, "ignoreCustom")
);

// Whitelist group dependency
registry.registerPredicateTransformer(
(guis, i18n, field, config, defaults, guiProvider) -> {
guis.forEach(gui -> gui.setDisplayRequirement(() -> ignoreCustom == null || ignoreCustom.getValue()));
return guis;
},
isField(ModConfig.CollisionConfig.class, "whitelist")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.xolt.freecam.config.gui;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Show an error message when the config entry is not a valid {@link java.util.regex.Pattern regex}.
* <p>
* Can be applied to a {@link String} or {@link java.util.List List<String>}.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateRegex {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package net.xolt.freecam.config.gui;

import me.shedaniel.autoconfig.gui.registry.GuiRegistry;
import me.shedaniel.clothconfig2.gui.entries.StringListEntry;
import me.shedaniel.clothconfig2.gui.entries.StringListListEntry;
import net.minecraft.network.chat.Component;

import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import static net.xolt.freecam.config.gui.AutoConfigExtensions.isArrayOrListOfType;
import static org.apache.commons.lang3.StringUtils.substringBefore;

class ValidateRegexImpl {
private ValidateRegexImpl() {}

static void apply(GuiRegistry registry) {

registry.registerAnnotationTransformer(
(guis, i18n, field, config, defaults, guiProvider) -> {
guis.stream()
.filter(StringListEntry.class::isInstance)
.map(StringListEntry.class::cast)
.forEach(entry -> entry.setErrorSupplier(() -> regexCompileError(entry.getValue())));
return guis;
},
field -> Objects.equals(String.class, field.getType()),
ValidateRegex.class
);

registry.registerAnnotationTransformer(
(guis, i18n, field, config, defaults, guiProvider) -> {
guis.stream()
.filter(StringListListEntry.class::isInstance)
.map(StringListListEntry.class::cast)
.forEach(entry -> entry.setCellErrorSupplier(ValidateRegexImpl::regexCompileError));
return guis;
},
isArrayOrListOfType(String.class),
ValidateRegex.class
);
}

/**
* Supplies an error message when the text is not a valid {@link Pattern regex pattern}.
*
* @param text the text that should compile to a regex.
* @return an optional error message.
*/
private static Optional<Component> regexCompileError(String text) {
try {
Pattern.compile(text);
return Optional.empty();
} catch (PatternSyntaxException e) {
String message = substringBefore(e.getLocalizedMessage(), '\n');
return Optional.of(Component.translatable("text.autoconfig.freecam.error.invalidRegex", message));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
* Will try to use translations defined for the current build variant (e.g. {@code @ModrinthTooltip}), but will
* fall back to using the default {@code @Tooltip} translations if variant-specific ones are not defined.
* <p>
* If <strong>any</strong> variant has a {@code count} greater than 1, all variants must use the indexed syntax,
* e.g. {@code @Tooltip[0]}.
* <p>
* Can be declared multiple times on the same field.
*
* @see ConfigEntry.Gui.Tooltip
Expand Down
Loading