Create Game Rule API ()

* Create GameRule API

* Some tweaks

* gamerule -> game-rule

* Fix translation keys and update to 20w22a

* Command results and remove unnessecary factory method

* Update to 1.16-pre1

* Update fabric-game-rule-api-v1/src/main/java/net/fabricmc/fabric/api/gamerule/v1/FabricRuleTypeConsumer.java

Co-authored-by: liach <7806504+liach@users.noreply.github.com>

* I like final things

* Update to 1.16-pre3, change enum button style

* checkstyle

* use right min values

* Document ValidatableRule

* Document LiteralRule, clarify generics

* Update to 20w27a

* Rename some parts to compensate for future yarn renames.

Flatten some logic related to EnumRules

* forgot one

* javadoc galore

* finish javadoc

* Start things

* Update to 20w29a, drop float rule

* Make cycle naming more accurate

* Convert colors to hex

Co-authored-by: Erlend Åmdal <erlend@aamdal.com>

* Update to 20w30a

* imports again

Co-authored-by: liach <7806504+liach@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: Erlend Åmdal <erlend@aamdal.com>
This commit is contained in:
i509VCB 2020-07-24 10:25:09 -05:00 committed by GitHub
parent d21d463561
commit d532d74b83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1864 additions and 0 deletions

View file

@ -0,0 +1,16 @@
archivesBaseName = "fabric-game-rule-api-v1"
version = getSubprojectVersion(project, "1.0.0")
minecraft {
accessWidener = file("src/main/resources/fabric-game-rule-api-v1.accesswidener")
}
dependencies {
// MUST BE A DEPENDENCY, OTHERWISE COMPILE WILL FAIL IN "RuleListWidgetMixin"
// YOU'VE BEEN WARNED
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
testmodCompile project(path: ':fabric-api-base', configuration: 'dev')
testmodCompile project(path: ':fabric-lifecycle-events-v1', configuration: 'dev')
testmodCompile project(path: ':fabric-resource-loader-v0', configuration: 'dev')
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1;
import java.util.Optional;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.impl.gamerule.RuleKeyExtensions;
/**
* Utility class for creating custom game rule categories outside of the categories {@link GameRules.Category Minecraft provides}.
*/
public final class CustomGameRuleCategory {
private final Identifier id;
private final Text name;
/**
* Creates a custom game rule category.
*
* @param id the id of this category
* @param name the name of this category
*/
public CustomGameRuleCategory(Identifier id, Text name) {
this.id = id;
this.name = name;
}
public Identifier getId() {
return this.id;
}
public Text getName() {
return this.name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomGameRuleCategory that = (CustomGameRuleCategory) o;
return this.id.equals(that.id);
}
@Override
public int hashCode() {
return this.id.hashCode();
}
/**
* Gets the custom category a {@link GameRules.Key game rule key} is registered to.
*
* @param key the rule key
* @param <T> the type of value the rule holds
* @return the custom category this rule belongs to. Otherwise {@link Optional#empty() empty}
*/
public static <T extends GameRules.Rule<T>> Optional<CustomGameRuleCategory> getCategory(GameRules.Key<T> key) {
return Optional.ofNullable(((RuleKeyExtensions) (Object) key).fabric_getCustomCategory());
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.rule.DoubleRule;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
/**
* An extended game rule visitor which supports Fabric's own rule types.
*
* <p>Game rule visitors are typically used iterating all game rules.
* In vanilla, the visitor is used to register game rule commands and populate the {@code Edit Game Rules} screen.
*
* <p>Rule types specified by this interface are not exhaustive.
* New entries may be added in the future.
*/
public interface FabricGameRuleVisitor extends GameRules.Visitor {
/**
* Visit a double rule.
*
* <p>Note {@link #visit(GameRules.Key, GameRules.Type)} will be called before this method is visited.
*
* @param key the rule key
* @param type the rule type
*/
default void visitDouble(GameRules.Key<DoubleRule> key, GameRules.Type<DoubleRule> type) {
}
/**
* Visit an enum rule.
*
* <p>Note {@link #visit(GameRules.Key, GameRules.Type)} will be called before this method is visited.
*
* @param key the rule key
* @param type the rule type
*/
default <E extends Enum<E>> void visitEnum(GameRules.Key<EnumRule<E>> key, GameRules.Type<EnumRule<E>> type) {
}
}

View file

@ -0,0 +1,311 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.function.BiConsumer;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.rule.DoubleRule;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
import net.fabricmc.fabric.impl.gamerule.EnumRuleType;
import net.fabricmc.fabric.impl.gamerule.rule.BoundedIntRule;
import net.fabricmc.fabric.mixin.gamerule.BooleanRuleAccessor;
/**
* A utility class containing factory methods to create game rule types.
* A game rule is a persisted, per server data value which may control gameplay aspects.
*
* <p>Some factory methods allow specification of a callback that is invoked when the value of a game rule has changed.
* Typically the callback is used for game rules which may influence game logic, such as {@link GameRules#DISABLE_RAIDS disabling raids}.
*
* <p>To register a game rule, you can use {@link GameRuleRegistry#register(String, GameRules.Category, GameRules.Type)}.
* For example, to register a game rule that is an integer where the acceptable values are between 0 and 10, one would use the following:
* <blockquote><pre>
* public static final GameRules.Key&lt;GameRules.IntRule&gt; EXAMPLE_INT_RULE = GameRuleRegistry.register("exampleIntRule", GameRules.Category.UPDATES, GameRuleFactory.createIntRule(1, 10));
* </pre></blockquote>
*
* <p>To register a game rule in a custom category, {@link GameRuleRegistry#register(String, CustomGameRuleCategory, GameRules.Type)} should be used.
*
* @see GameRuleRegistry
*/
public final class GameRuleFactory {
/**
* Creates a boolean rule type.
*
* @param defaultValue the default value of the game rule
* @return a boolean rule type
*/
public static GameRules.Type<GameRules.BooleanRule> createBooleanRule(boolean defaultValue) {
return createBooleanRule(defaultValue, (server, rule) -> {
});
}
/**
* Creates a boolean rule type.
*
* @param defaultValue the default value of the game rule
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return a boolean rule type
*/
public static GameRules.Type<GameRules.BooleanRule> createBooleanRule(boolean defaultValue, BiConsumer<MinecraftServer, GameRules.BooleanRule> changedCallback) {
return BooleanRuleAccessor.invokeCreate(defaultValue, changedCallback);
}
/**
* Creates an integer rule type.
*
* @param defaultValue the default value of the game rule
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue) {
return createIntRule(defaultValue, (server, rule) -> {
});
}
/**
* Creates an integer rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, int minimumValue) {
return createIntRule(defaultValue, minimumValue, Integer.MAX_VALUE, (server, rule) -> {
});
}
/**
* Creates an integer rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, int minimumValue, BiConsumer<MinecraftServer, GameRules.IntRule> changedCallback) {
return createIntRule(defaultValue, minimumValue, Integer.MAX_VALUE, changedCallback);
}
/**
* Creates an integer rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @param maximumValue the maximum value the game rule may accept
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, int minimumValue, int maximumValue) {
return createIntRule(defaultValue, minimumValue, maximumValue, (server, rule) -> {
});
}
/**
* Creates an integer rule type.
*
* @param defaultValue the default value of the game rule
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, BiConsumer<MinecraftServer, GameRules.IntRule> changedCallback) {
return createIntRule(defaultValue, Integer.MIN_VALUE, Integer.MAX_VALUE, changedCallback);
}
/**
* Creates an integer rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @param maximumValue the maximum value the game rule may accept
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, int minimumValue, int maximumValue, /* @Nullable */ BiConsumer<MinecraftServer, GameRules.IntRule> changedCallback) {
return new GameRules.Type<>(
() -> IntegerArgumentType.integer(minimumValue, maximumValue),
type -> new BoundedIntRule(type, defaultValue, minimumValue, maximumValue), // Internally use a bounded int rule
changedCallback,
GameRules.Visitor::visitInt
);
}
/**
* Creates a double rule type.
*
* @param defaultValue the default value of the game rule
* @return a double rule type
*/
public static GameRules.Type<DoubleRule> createDoubleRule(double defaultValue) {
return createDoubleRule(defaultValue, (server, rule) -> {
});
}
/**
* Creates a double rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @return a double rule type
*/
public static GameRules.Type<DoubleRule> createDoubleRule(double defaultValue, double minimumValue) {
return createDoubleRule(defaultValue, minimumValue, Double.MAX_VALUE, (server, rule) -> {
});
}
/**
* Creates a double rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return a double rule type
*/
public static GameRules.Type<DoubleRule> createDoubleRule(double defaultValue, double minimumValue, BiConsumer<MinecraftServer, DoubleRule> changedCallback) {
return createDoubleRule(defaultValue, minimumValue, Double.MAX_VALUE, changedCallback);
}
/**
* Creates a double rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @param maximumValue the maximum value the game rule may accept
* @return a double rule type
*/
public static GameRules.Type<DoubleRule> createDoubleRule(double defaultValue, double minimumValue, double maximumValue) {
return createDoubleRule(defaultValue, minimumValue, maximumValue, (server, rule) -> {
});
}
/**
* Creates a double rule type.
*
* @param defaultValue the default value of the game rule
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return a double rule type
*/
public static GameRules.Type<DoubleRule> createDoubleRule(double defaultValue, BiConsumer<MinecraftServer, DoubleRule> changedCallback) {
return createDoubleRule(defaultValue, Double.MIN_VALUE, Double.MAX_VALUE, changedCallback);
}
/**
* Creates a double rule type.
*
* @param defaultValue the default value of the game rule
* @param minimumValue the minimum value the game rule may accept
* @param maximumValue the maximum value the game rule may accept
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return a double rule type
*/
public static GameRules.Type<DoubleRule> createDoubleRule(double defaultValue, double minimumValue, double maximumValue, BiConsumer<MinecraftServer, DoubleRule> changedCallback) {
return new GameRules.Type<>(
() -> DoubleArgumentType.doubleArg(minimumValue, maximumValue),
type -> new DoubleRule(type, defaultValue, minimumValue, maximumValue),
changedCallback,
GameRuleFactory::visitDouble
);
}
/**
* Creates an enum rule type.
*
* <p>All enum values are supported.
*
* @param defaultValue the default value of the game rule
* @param <E> the type of enum this game rule stores
* @return an enum rule type
*/
public static <E extends Enum<E>> GameRules.Type<EnumRule<E>> createEnumRule(E defaultValue) {
return createEnumRule(defaultValue, (server, rule) -> {
});
}
/**
* Creates an enum rule type.
*
* <p>All enum values are supported.
*
* @param defaultValue the default value of the game rule
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @param <E> the type of enum this game rule stores
* @return an enum rule type
*/
public static <E extends Enum<E>> GameRules.Type<EnumRule<E>> createEnumRule(E defaultValue, BiConsumer<MinecraftServer, EnumRule<E>> changedCallback) {
return createEnumRule(defaultValue, defaultValue.getDeclaringClass().getEnumConstants(), changedCallback);
}
/**
* Creates an enum rule type.
*
* @param defaultValue the default value of the game rule
* @param supportedValues the values the game rule may support
* @param <E> the type of enum this game rule stores
* @return an enum rule type
*/
public static <E extends Enum<E>> GameRules.Type<EnumRule<E>> createEnumRule(E defaultValue, E[] supportedValues) {
return createEnumRule(defaultValue, supportedValues, (server, rule) -> {
});
}
/**
* Creates an enum rule type.
*
* @param defaultValue the default value of the game rule
* @param supportedValues the values the game rule may support
* @param changedCallback a callback that is invoked when the value of a game rule has changed.
* @param <E> the type of enum this game rule stores
* @return an enum rule type
*/
public static <E extends Enum<E>> GameRules.Type<EnumRule<E>> createEnumRule(E defaultValue, E[] supportedValues, BiConsumer<MinecraftServer, EnumRule<E>> changedCallback) {
checkNotNull(defaultValue, "Default rule value cannot be null");
checkNotNull(supportedValues, "Supported Values cannot be null");
if (supportedValues.length == 0) {
throw new IllegalArgumentException("Cannot register an enum rule where no values are supported");
}
return new EnumRuleType<>(
type -> new EnumRule<>(type, defaultValue, supportedValues),
changedCallback,
supportedValues,
GameRuleFactory::visitEnum
);
}
// RULE VISITORS - INTERNAL
private static void visitDouble(GameRules.Visitor visitor, GameRules.Key<DoubleRule> key, GameRules.Type<DoubleRule> type) {
if (visitor instanceof FabricGameRuleVisitor) {
((FabricGameRuleVisitor) visitor).visitDouble(key, type);
}
}
private static <E extends Enum<E>> void visitEnum(GameRules.Visitor visitor, GameRules.Key<EnumRule<E>> key, GameRules.Type<EnumRule<E>> type) {
if (visitor instanceof FabricGameRuleVisitor) {
((FabricGameRuleVisitor) visitor).visitEnum(key, type);
}
}
private GameRuleFactory() {
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.impl.gamerule.RuleKeyExtensions;
import net.fabricmc.fabric.mixin.gamerule.GameRulesAccessor;
/**
* A utility class which allows for registration of game rules.
* Note game rules with duplicate keys are not allowed.
* Checking if a game rule key is already taken can be done using {@link GameRuleRegistry#hasRegistration(String)}.
*
* <p>Creation of rule types is done using {@link GameRuleFactory}.
*
* @see GameRuleFactory
*/
public final class GameRuleRegistry {
private GameRuleRegistry() {
}
/**
* Registers a {@link GameRules.Rule}.
*
* @param name the name of the rule
* @param category the category of this rule
* @param type the rule type
* @param <T> the type of rule
* @return a rule key which can be used to query the value of the rule
* @throws IllegalStateException if a rule of the same name already exists
*/
public static <T extends GameRules.Rule<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
return GameRulesAccessor.callRegister(name, category, type);
}
/**
* Registers a {@link GameRules.Rule} with a custom category.
*
* @param name the name of the rule
* @param category the category of this rule
* @param type the rule type
* @param <T> the type of rule
* @return a rule key which can be used to query the value of the rule
* @throws IllegalStateException if a rule of the same name already exists
*/
public static <T extends GameRules.Rule<T>> GameRules.Key<T> register(String name, CustomGameRuleCategory category, GameRules.Type<T> type) {
final GameRules.Key<T> key = GameRulesAccessor.callRegister(name, GameRules.Category.MISC, type);
((RuleKeyExtensions) (Object) key).fabric_setCustomCategory(category);
return key;
}
/**
* Checks if a name for a game rule is already registered.
*
* @param ruleName the rule name to test
* @return true if the name is taken.
*/
public static boolean hasRegistration(String ruleName) {
return GameRulesAccessor.getRuleTypes().keySet().stream().anyMatch(key -> key.getName().equals(ruleName));
}
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1.rule;
import com.mojang.brigadier.context.CommandContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry;
public final class DoubleRule extends GameRules.Rule<DoubleRule> implements ValidateableRule {
private static final Logger LOGGER = LogManager.getLogger(GameRuleRegistry.class);
private final double minimumValue;
private final double maximumValue;
private double value;
/**
* @deprecated You should not be calling this constructor!
*/
@Deprecated
public DoubleRule(GameRules.Type<DoubleRule> type, double value, double minimumValue, double maximumValue) {
super(type);
this.value = value;
this.minimumValue = minimumValue;
this.maximumValue = maximumValue;
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new IllegalArgumentException("Value cannot be infinite or NaN");
}
if (Double.isInfinite(minimumValue) || Double.isNaN(minimumValue)) {
throw new IllegalArgumentException("Minimum value cannot be infinite or NaN");
}
if (Double.isInfinite(maximumValue) || Double.isNaN(maximumValue)) {
throw new IllegalArgumentException("Maximum value cannot be infinite or NaN");
}
}
@Override
protected void setFromArgument(CommandContext<ServerCommandSource> context, String name) {
this.value = context.getArgument(name, Double.class);
}
@Override
protected void deserialize(String value) {
if (!value.isEmpty()) {
try {
final double d = Double.parseDouble(value);
if (this.inBounds(d)) {
this.value = d;
} else {
LOGGER.warn("Failed to parse double {}. Was out of bounds {} - {}", value, this.minimumValue, this.maximumValue);
}
} catch (NumberFormatException e) {
LOGGER.warn("Failed to parse double {}", value);
}
}
}
@Override
public String serialize() {
return Double.toString(this.value);
}
@Override
public int getCommandResult() {
return Double.compare(this.value, 0.0D);
}
@Override
protected DoubleRule getThis() {
return this;
}
@Override
protected DoubleRule copy() {
return new DoubleRule(this.type, this.value, this.minimumValue, this.maximumValue);
}
@Override
public void setValue(DoubleRule rule, MinecraftServer minecraftServer) {
if (!this.inBounds(rule.value)) {
throw new IllegalArgumentException(String.format("Could not set value to %s. Was out of bounds %s - %s", rule.value, this.minimumValue, this.maximumValue));
}
this.value = rule.value;
this.changed(minecraftServer);
}
@Override
public boolean validate(String value) {
try {
final double d = Double.parseDouble(value);
return this.inBounds(d);
} catch (NumberFormatException ignored) {
return false;
}
}
public double get() {
return this.value;
}
private boolean inBounds(double value) {
return value >= this.minimumValue && value <= this.maximumValue;
}
}

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1.rule;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import com.mojang.brigadier.context.CommandContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry;
public final class EnumRule<E extends Enum<E>> extends GameRules.Rule<EnumRule<E>> {
private static final Logger LOGGER = LogManager.getLogger(GameRuleRegistry.class);
private final Class<E> classType;
private final List<E> supportedValues;
private E value;
/**
* @deprecated You should not be calling this constructor!
*/
@Deprecated
public EnumRule(GameRules.Type<EnumRule<E>> type, E value, E[] supportedValues) {
this(type, value, Arrays.asList(supportedValues));
}
/**
* You should not be calling this constructor!
*/
@Deprecated
public EnumRule(GameRules.Type<EnumRule<E>> type, E value, Collection<E> supportedValues) {
super(type);
this.classType = value.getDeclaringClass();
this.value = value;
this.supportedValues = new ArrayList<>(supportedValues);
if (!this.supports(value)) {
throw new IllegalArgumentException("Cannot set default value");
}
}
@Override
protected void setFromArgument(CommandContext<ServerCommandSource> context, String name) {
// Do nothing. We use a different system for application with literals
}
@Override
protected void deserialize(String value) {
try {
final E deserialized = Enum.valueOf(this.classType, value);
if (!this.supports(deserialized)) {
LOGGER.warn("Failed to parse rule of value {} for rule of type {}. Since the value {}, is unsupported.", value, this.classType, value);
}
this.set(deserialized, null);
} catch (IllegalArgumentException e) {
LOGGER.warn("Failed to parse rule of value {} for rule of type {}", value, this.classType);
}
}
@Override
public String serialize() {
return this.value.name();
}
@Override
public int getCommandResult() {
// For now we are gonna use the ordinal as the command result. Could be changed or set to relate to something else entirely.
return this.value.ordinal();
}
@Override
protected EnumRule<E> getThis() {
return this;
}
public Class<E> getEnumClass() {
return this.classType;
}
@Override
public String toString() {
return this.value.toString();
}
@Override
protected EnumRule<E> copy() {
return new EnumRule<>(this.type, this.value, this.supportedValues);
}
@Override
public void setValue(EnumRule<E> rule, MinecraftServer minecraftServer) {
if (!this.supports(rule.value)) {
throw new IllegalArgumentException(String.format("Rule does not support value: %s", rule.value));
}
this.value = rule.value;
this.changed(minecraftServer);
}
public E get() {
return this.value;
}
/**
* Cycles the value of this enum rule to the next supported value.
*/
public void cycle() {
int index = this.supportedValues.indexOf(this.value);
if (index < 0) {
throw new IllegalArgumentException(String.format("Invalid value: %s", this.value));
}
this.set(this.supportedValues.get((index + 1) % this.supportedValues.size()), null);
}
public boolean supports(E value) {
return this.supportedValues.contains(value);
}
public void set(E value, /* @Nullable */ MinecraftServer server) throws IllegalArgumentException {
checkNotNull(value);
if (!this.supports(value)) {
throw new IllegalArgumentException("Tried to set an unsupported value: " + value.toString());
}
this.value = value;
this.changed(server);
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gamerule.v1.rule;
/**
* A type of game rule which can validate an input.
* This can be used to enforce syntax or clamp values.
*/
public interface ValidateableRule {
/**
* Validates if a rule can accept the input.
*
* @param value the value to validate
* @return true if the value can be accepted.
*/
boolean validate(String value);
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.gamerule;
import static net.minecraft.server.command.CommandManager.literal;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TranslatableText;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
import net.fabricmc.fabric.mixin.gamerule.GameRuleCommandAccessor;
public final class EnumRuleCommand {
public static <E extends Enum<E>> void register(LiteralArgumentBuilder<ServerCommandSource> literalArgumentBuilder, GameRules.Key<EnumRule<E>> key, EnumRuleType<E> type) {
literalArgumentBuilder.then(literal(key.getName()).executes(context -> {
// We can use the vanilla query method
return GameRuleCommandAccessor.invokeExecuteQuery(context.getSource(), key);
}));
// The LiteralRuleType handles the executeSet
type.register(literalArgumentBuilder, key);
}
public static <E extends Enum<E>> int executeAndSetEnum(CommandContext<ServerCommandSource> context, E value, GameRules.Key<EnumRule<E>> key) throws CommandSyntaxException {
// Mostly copied from vanilla, but tweaked so we can use literals
ServerCommandSource serverCommandSource = context.getSource();
EnumRule<E> rule = serverCommandSource.getMinecraftServer().getGameRules().get(key);
try {
rule.set(value, serverCommandSource.getMinecraftServer());
} catch (IllegalArgumentException e) {
throw new SimpleCommandExceptionType(new LiteralText(e.getMessage())).create();
}
serverCommandSource.sendFeedback(new TranslatableText("commands.gamerule.set", key.getName(), rule.toString()), true);
return rule.getCommandResult();
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.gamerule;
import static net.minecraft.server.command.CommandManager.literal;
import java.util.function.BiConsumer;
import java.util.function.Function;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
public final class EnumRuleType<E extends Enum<E>> extends GameRules.Type<EnumRule<E>> {
private final E[] supportedValues;
public EnumRuleType(Function<GameRules.Type<EnumRule<E>>, EnumRule<E>> ruleFactory, BiConsumer<MinecraftServer, EnumRule<E>> changeCallback, E[] supportedValues, GameRules.Acceptor<EnumRule<E>> acceptor) {
super(null, ruleFactory, changeCallback, acceptor);
this.supportedValues = supportedValues;
}
public void register(LiteralArgumentBuilder<ServerCommandSource> literalArgumentBuilder, GameRules.Key<EnumRule<E>> key) {
LiteralCommandNode<ServerCommandSource> ruleNode = literal(key.getName()).build();
for (E supportedValue : this.supportedValues) {
ruleNode.addChild(literal(supportedValue.toString()).executes(context -> EnumRuleCommand.executeAndSetEnum(context, supportedValue, key)).build());
}
literalArgumentBuilder.then(ruleNode);
}
@Override
@Deprecated
public RequiredArgumentBuilder<ServerCommandSource, ?> argument(String name) {
return super.argument(name);
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.gamerule;
import net.fabricmc.fabric.api.gamerule.v1.CustomGameRuleCategory;
public interface RuleKeyExtensions {
/* @Nullable */
CustomGameRuleCategory fabric_getCustomCategory();
void fabric_setCustomCategory(CustomGameRuleCategory customCategory);
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.gamerule.rule;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.world.GameRules;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry;
import net.fabricmc.fabric.mixin.gamerule.IntRuleAccessor;
public final class BoundedIntRule extends GameRules.IntRule {
private static final Logger LOGGER = LogManager.getLogger(GameRuleRegistry.class);
private final int minimumValue;
private final int maximumValue;
public BoundedIntRule(GameRules.Type<GameRules.IntRule> type, int initialValue, int minimumValue, int maximumValue) {
super(type, initialValue);
this.minimumValue = minimumValue;
this.maximumValue = maximumValue;
}
@Override
protected void deserialize(String value) {
final int i = BoundedIntRule.parseInt(value);
if (this.minimumValue > i || this.maximumValue < i) {
LOGGER.warn("Failed to parse integer {}. Was out of bounds {} - {}", value, this.minimumValue, this.maximumValue);
return;
}
((IntRuleAccessor) (Object) this).setValue(i);
}
@Override
@Environment(EnvType.CLIENT)
public boolean validate(String input) {
try {
int value = Integer.parseInt(input);
if (this.minimumValue > value || this.maximumValue < value) {
return false;
}
((IntRuleAccessor) (Object) this).setValue(value);
return true;
} catch (NumberFormatException var3) {
return false;
}
}
@Override
protected GameRules.IntRule copy() {
return new BoundedIntRule(this.type, ((IntRuleAccessor) (Object) this).getValue(), this.minimumValue, this.maximumValue);
}
private static int parseInt(String input) {
if (!input.isEmpty()) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException var2) {
LOGGER.warn("Failed to parse integer {}", input);
}
}
return 0;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.gamerule.widget;
import java.util.List;
import net.minecraft.class_5481;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.world.EditGameRulesScreen;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.gamerule.v1.rule.DoubleRule;
import net.fabricmc.fabric.mixin.gamerule.client.EditGameRulesScreenAccessor;
@Environment(EnvType.CLIENT)
public final class DoubleRuleWidget extends EditGameRulesScreen.NamedRuleWidget {
private final TextFieldWidget textFieldWidget;
public DoubleRuleWidget(EditGameRulesScreen gameRuleScreen, Text name, List<class_5481> description, final String ruleName, DoubleRule rule) {
gameRuleScreen.super(description, name);
EditGameRulesScreenAccessor accessor = (EditGameRulesScreenAccessor) gameRuleScreen;
this.textFieldWidget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 10, 5, 42, 20,
name.shallowCopy()
.append("\n")
.append(ruleName)
.append("\n")
);
this.textFieldWidget.setText(Double.toString(rule.get()));
this.textFieldWidget.setChangedListener(value -> {
if (rule.validate(value)) {
this.textFieldWidget.setEditableColor(0xE0E0E0);
accessor.callMarkValid(this);
} else {
this.textFieldWidget.setEditableColor(0xFF0000);
accessor.callMarkInvalid(this);
}
});
this.children.add(this.textFieldWidget);
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
// FIXME: Param names nightmare
this.drawName(matrices, y, x);
this.textFieldWidget.x = x + entryWidth - 44;
this.textFieldWidget.y = y;
this.textFieldWidget.render(matrices, mouseX, mouseY, tickDelta);
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.gamerule.widget;
import java.util.List;
import java.util.Locale;
import net.minecraft.class_5481;
import net.minecraft.client.gui.screen.world.EditGameRulesScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
@Environment(EnvType.CLIENT)
public final class EnumRuleWidget<E extends Enum<E>> extends EditGameRulesScreen.NamedRuleWidget {
private final ButtonWidget buttonWidget;
private final String rootTranslationKey;
public EnumRuleWidget(EditGameRulesScreen gameRuleScreen, Text name, List<class_5481> description, final String ruleName, EnumRule<E> rule, String translationKey) {
gameRuleScreen.super(description, name);
// Base translation key needs to be set before the button widget is created.
this.rootTranslationKey = translationKey;
this.buttonWidget = new ButtonWidget(10, 5, 88, 20, this.getValueText(rule.get()), (buttonWidget) -> {
rule.cycle();
buttonWidget.setMessage(this.getValueText(rule.get()));
});
this.children.add(this.buttonWidget);
}
public Text getValueText(E value) {
final String key = this.rootTranslationKey + "." + value.name().toLowerCase(Locale.ROOT);
if (I18n.hasTranslation(key)) {
return new TranslatableText(key);
}
return new LiteralText(value.toString());
}
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
// FIXME: Param names nightmare
this.drawName(matrices, y, x);
this.buttonWidget.x = x + entryWidth - 89;
this.buttonWidget.y = y;
this.buttonWidget.render(matrices, mouseX, mouseY, tickDelta);
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule;
import java.util.function.BiConsumer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.GameRules;
@Mixin(GameRules.BooleanRule.class)
public interface BooleanRuleAccessor {
@Invoker
static GameRules.Type<GameRules.BooleanRule> invokeCreate(boolean initialValue, BiConsumer<MinecraftServer, GameRules.BooleanRule> changeCallback) {
throw new AssertionError("This shouldn't happen!");
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule;
import com.mojang.brigadier.context.CommandContext;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.server.command.GameRuleCommand;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.GameRules;
@Mixin(GameRuleCommand.class)
public interface GameRuleCommandAccessor {
@Invoker
static <T extends GameRules.Rule<T>> int invokeExecuteSet(CommandContext<ServerCommandSource> commandContext, GameRules.Key<T> ruleKey) {
throw new AssertionError("This shouldn't happen!");
}
@Invoker
static <T extends GameRules.Rule<T>> int invokeExecuteQuery(ServerCommandSource serverCommandSource, GameRules.Key<T> ruleKey) {
throw new AssertionError("This shouldn't happen!");
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.impl.gamerule.EnumRuleCommand;
import net.fabricmc.fabric.impl.gamerule.EnumRuleType;
@Mixin(targets = "net/minecraft/server/command/GameRuleCommand$1")
public abstract class GameRuleCommandVisitorMixin {
@Shadow
private LiteralArgumentBuilder<ServerCommandSource> field_19419;
@Inject(at = @At("HEAD"), method = "visit(Lnet/minecraft/world/GameRules$Key;Lnet/minecraft/world/GameRules$Type;)V", cancellable = true)
private <T extends GameRules.Rule<T>> void onRegisterCommand(GameRules.Key<T> key, GameRules.Type<T> type, CallbackInfo ci) {
// Check if our type is a EnumRuleType
if (type instanceof EnumRuleType) {
//noinspection rawtypes,unchecked
EnumRuleCommand.register(this.field_19419, (GameRules.Key) key, (EnumRuleType) type);
ci.cancel();
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.world.GameRules;
@Mixin(GameRules.class)
public interface GameRulesAccessor {
@Invoker("register")
static <T extends GameRules.Rule<T>> GameRules.Key<T> callRegister(String name, GameRules.Category category, GameRules.Type<T> type) {
throw new AssertionError("This shouldn't happen!");
}
@Accessor("RULE_TYPES")
static Map<GameRules.Key<?>, GameRules.Type<?>> getRuleTypes() {
throw new AssertionError("This shouldn't happen!");
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.world.GameRules;
@Mixin(GameRules.IntRule.class)
public interface IntRuleAccessor {
@Accessor
int getValue();
@Accessor
void setValue(int value);
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.CustomGameRuleCategory;
import net.fabricmc.fabric.impl.gamerule.RuleKeyExtensions;
@Mixin(GameRules.Key.class)
public abstract class RuleKeyMixin implements RuleKeyExtensions {
/* @Nullable */
@Unique
private CustomGameRuleCategory customCategory;
@Override
public CustomGameRuleCategory fabric_getCustomCategory() {
return this.customCategory;
}
@Override
public void fabric_setCustomCategory(CustomGameRuleCategory customCategory) {
this.customCategory = customCategory;
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.client.gui.screen.world.EditGameRulesScreen;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Mixin(EditGameRulesScreen.class)
@Environment(EnvType.CLIENT)
public interface EditGameRulesScreenAccessor {
@Invoker("markValid")
void callMarkValid(EditGameRulesScreen.AbstractRuleWidget ruleWidget);
@Invoker("markInvalid")
void callMarkInvalid(EditGameRulesScreen.AbstractRuleWidget ruleWidget);
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule.client;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.world.EditGameRulesScreen;
import net.minecraft.world.GameRules;
import net.fabricmc.fabric.api.gamerule.v1.CustomGameRuleCategory;
@Mixin(EditGameRulesScreen.RuleListWidget.class)
public abstract class RuleListWidgetMixin extends net.minecraft.client.gui.widget.EntryListWidget<EditGameRulesScreen.AbstractRuleWidget> {
@Unique
private final Map<CustomGameRuleCategory, List<EditGameRulesScreen.AbstractRuleWidget>> fabricCategories = new HashMap<>();
public RuleListWidgetMixin(MinecraftClient client, int width, int height, int top, int bottom, int itemHeight) {
super(client, width, height, top, bottom, itemHeight);
}
@Inject(method = "<init>(Lnet/minecraft/world/GameRules;)V", at = @At("TAIL"))
private void initializeFabricGameruleCategories(EditGameRulesScreen screen, GameRules gameRules, CallbackInfo ci) {
this.fabricCategories.forEach((category, widgetList) -> {
this.addEntry(screen.new RuleCategoryWidget(category.getName()));
for (EditGameRulesScreen.AbstractRuleWidget widget : widgetList) {
this.addEntry(widget);
}
});
}
@Inject(method = "method_27638(Ljava/util/Map$Entry;)V", at = @At("HEAD"), cancellable = true)
private void ignoreKeysWithCustomCategories(Map.Entry<GameRules.Key<?>, EditGameRulesScreen.AbstractRuleWidget> entry, CallbackInfo ci) {
final GameRules.Key<?> ruleKey = entry.getKey();
CustomGameRuleCategory.getCategory(ruleKey).ifPresent(key -> {
this.fabricCategories.computeIfAbsent(key, c -> new ArrayList<>()).add(entry.getValue());
ci.cancel();
});
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gamerule.client;
import java.util.Locale;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import net.minecraft.client.gui.screen.world.EditGameRulesScreen;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.world.GameRules;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.gamerule.v1.FabricGameRuleVisitor;
import net.fabricmc.fabric.api.gamerule.v1.rule.DoubleRule;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
import net.fabricmc.fabric.impl.gamerule.widget.DoubleRuleWidget;
import net.fabricmc.fabric.impl.gamerule.widget.EnumRuleWidget;
@Environment(EnvType.CLIENT)
@Mixin(targets = "net/minecraft/client/gui/screen/world/EditGameRulesScreen$RuleListWidget$1")
public abstract class RuleListWidgetVisitorMixin implements GameRules.Visitor, FabricGameRuleVisitor {
@Final
@Shadow
private EditGameRulesScreen field_24314;
@Shadow
protected abstract <T extends GameRules.Rule<T>> void createRuleWidget(GameRules.Key<T> key, EditGameRulesScreen.RuleWidgetFactory<T> ruleWidgetFactory);
@Override
public void visitDouble(GameRules.Key<DoubleRule> key, GameRules.Type<DoubleRule> type) {
this.createRuleWidget(key, (name, description, ruleName, rule) -> {
return new DoubleRuleWidget(this.field_24314, name, description, ruleName, rule);
});
}
@Override
public <E extends Enum<E>> void visitEnum(GameRules.Key<EnumRule<E>> key, GameRules.Type<EnumRule<E>> type) {
this.createRuleWidget(key, (name, description, ruleName, rule) -> {
return new EnumRuleWidget<>(this.field_24314, name, description, ruleName, rule, key.getTranslationKey());
});
}
/**
* @reason We need to display an enum rule's default value as translated.
*/
@Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/world/GameRules$Rule;serialize()Ljava/lang/String;"), method = "createRuleWidget")
private <T extends GameRules.Rule<T>> String displayProperEnumName(GameRules.Rule<T> rule, GameRules.Key<T> key, EditGameRulesScreen.RuleWidgetFactory<T> widgetFactory) {
if (rule instanceof EnumRule) {
String translationKey = key.getTranslationKey() + "." + ((EnumRule<?>) rule).get().name().toLowerCase(Locale.ROOT);
if (I18n.hasTranslation(translationKey)) {
return I18n.translate(translationKey);
}
return ((EnumRule<?>) rule).get().toString();
}
return rule.serialize();
}
}

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

@ -0,0 +1,5 @@
accessWidener v1 named
accessible method net/minecraft/world/GameRules$Type <init> (Ljava/util/function/Supplier;Ljava/util/function/Function;Ljava/util/function/BiConsumer;Lnet/minecraft/world/GameRules$Acceptor;)V
extendable method net/minecraft/world/GameRules$Type <init> (Ljava/util/function/Supplier;Ljava/util/function/Function;Ljava/util/function/BiConsumer;Lnet/minecraft/world/GameRules$Acceptor;)V
accessible class net/minecraft/world/GameRules$Acceptor
accessible class net/minecraft/client/gui/screen/world/EditGameRulesScreen$RuleWidgetFactory

View file

@ -0,0 +1,21 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.gamerule",
"compatibilityLevel": "JAVA_8",
"mixins": [
"BooleanRuleAccessor",
"GameRuleCommandAccessor",
"GameRuleCommandVisitorMixin",
"GameRulesAccessor",
"IntRuleAccessor",
"RuleKeyMixin"
],
"client": [
"client.EditGameRulesScreenAccessor",
"client.RuleListWidgetMixin",
"client.RuleListWidgetVisitorMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,26 @@
{
"schemaVersion": 1,
"id": "fabric-game-rule-api-v1",
"name": "Fabric Game Rule API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-game-rule-api-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.8.2"
},
"description": "Allows registration of custom game rules.",
"mixins": [
"fabric-game-rule-api-v1.mixins.json"
],
"accessWidener" : "fabric-game-rule-api-v1.accesswidener"
}

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.gamerule;
import java.util.Arrays;
import java.util.Collection;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.world.GameRules;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.gamerule.v1.CustomGameRuleCategory;
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry;
import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory;
import net.fabricmc.fabric.api.gamerule.v1.rule.DoubleRule;
import net.fabricmc.fabric.api.gamerule.v1.rule.EnumRule;
public class GameRulesTestMod implements ModInitializer {
private static final Logger LOGGER = LogManager.getLogger(GameRulesTestMod.class);
private static final Direction[] CARDINAL_DIRECTIONS = Arrays.stream(Direction.values()).filter(direction -> direction != Direction.UP && direction != Direction.DOWN).toArray(Direction[]::new);
public static final CustomGameRuleCategory GREEN_CATEGORY = new CustomGameRuleCategory(new Identifier("fabric", "green"), new LiteralText("This One is Green").styled(style -> style.withBold(true).withColor(Formatting.DARK_GREEN)));
public static final CustomGameRuleCategory RED_CATEGORY = new CustomGameRuleCategory(new Identifier("fabric", "red"), new LiteralText("This One is Red").styled(style -> style.withBold(true).withColor(Formatting.DARK_RED)));
// Bounded, Integer, Double and Float rules
public static final GameRules.Key<GameRules.IntRule> POSITIVE_ONLY_TEST_INT = register("positiveOnlyTestInteger", GameRules.Category.UPDATES, GameRuleFactory.createIntRule(2, 0));
public static final GameRules.Key<DoubleRule> ONE_TO_TEN_DOUBLE = register("oneToTenDouble", GameRules.Category.MISC, GameRuleFactory.createDoubleRule(1.0D, 1.0D, 10.0D));
// Test enum rule, with only some supported values.
public static final GameRules.Key<EnumRule<Direction>> CARDINAL_DIRECTION_ENUM = register("cardinalDirection", GameRules.Category.MISC, GameRuleFactory.createEnumRule(Direction.NORTH, CARDINAL_DIRECTIONS, (server, rule) -> {
LOGGER.info("Changed rule value to {}", rule.get());
}));
// Rules in custom categories
public static final GameRules.Key<GameRules.BooleanRule> RED_BOOLEAN = register("redBoolean", RED_CATEGORY, GameRuleFactory.createBooleanRule(true));
public static final GameRules.Key<GameRules.BooleanRule> GREEN_BOOLEAN = register("greenBoolean", GREEN_CATEGORY, GameRuleFactory.createBooleanRule(false));
// An enum rule with no "toString" logic
public static final GameRules.Key<EnumRule<PlayerEntity.SleepFailureReason>> RED_SLEEP_FAILURE_ENUM = register("redSleepFailureEnum", RED_CATEGORY, GameRuleFactory.createEnumRule(PlayerEntity.SleepFailureReason.NOT_POSSIBLE_HERE));
private static <T extends GameRules.Rule<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
return GameRuleRegistry.register(name, category, type);
}
private static <T extends GameRules.Rule<T>> GameRules.Key<T> register(String name, CustomGameRuleCategory category, GameRules.Type<T> type) {
return GameRuleRegistry.register(name, category, type);
}
@Override
public void onInitialize() {
LOGGER.info("Loading GameRules test mod.");
// Test a vanilla rule
if (!GameRuleRegistry.hasRegistration("keepInventory")) {
throw new AssertionError("Expected to find \"keepInventory\" already registered, but it was not detected as registered");
}
// Test our own rule
if (!GameRuleRegistry.hasRegistration("redSleepFailureEnum")) {
throw new AssertionError("Expected to find \"redSleepFailureEnum\" already registered, but it was not detected as registered");
}
LOGGER.info("Loaded GameRules test mod.");
// Validate the EnumRule has registered it's commands
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
RootCommandNode<ServerCommandSource> dispatcher = server.getCommandManager().getDispatcher().getRoot();
// Find the GameRule node
CommandNode<ServerCommandSource> gamerule = dispatcher.getChild("gamerule");
if (gamerule == null) {
throw new AssertionError("Failed to find GameRule command node on server's command dispatcher");
}
// Find the literal corresponding to our enum rule, using cardinal directions here.
CommandNode<ServerCommandSource> cardinalDirection = gamerule.getChild("cardinalDirection");
if (cardinalDirection == null) {
throw new AssertionError("Failed to find \"cardinalDirection\" literal node corresponding a rule.");
}
// Verify we have a query command set.
if (cardinalDirection.getCommand() == null) {
throw new AssertionError("Expected to find a query command on \"cardinalDirection\" command node, but it was not present");
}
Collection<CommandNode<ServerCommandSource>> children = cardinalDirection.getChildren();
// There should only be 4 child nodes.
if (children.size() != 4) {
throw new AssertionError(String.format("Expected only 4 child nodes on \"cardinalDirection\" command node, but %s were found", children.size()));
}
// All children should be literals
children.stream().filter(node -> !(node instanceof LiteralCommandNode)).findAny().ifPresent(node -> {
throw new AssertionError(String.format("Found non-literal child node on \"cardinalDirection\" command node %s", node));
});
// Verify we have all the correct nodes
for (CommandNode<ServerCommandSource> child : children) {
LiteralCommandNode<ServerCommandSource> node = (LiteralCommandNode<ServerCommandSource>) child;
String name = node.getName();
switch (name) {
case "north":
case "south":
case "east":
case "west":
continue;
default:
throw new AssertionError(String.format("Found unexpected literal name. Found %s but only \"north, south, east, west\" are allowed", name));
}
}
children.stream().filter(node -> node.getCommand() == null).findAny().ifPresent(node -> {
throw new AssertionError(String.format("Found child node with no command literal name. %s", node));
});
LOGGER.info("GameRule command checks have passed. Try giving the enum rules a test.");
});
}
}

View file

@ -0,0 +1,17 @@
{
"gamerule.cardinalDirection": "Random Cardinal Direction",
"gamerule.cardinalDirection.description": "A cardinal direction. Should never be up or down",
"gamerule.cardinalDirection.north": "Northbound",
"gamerule.cardinalDirection.south": "Southbound",
"gamerule.cardinalDirection.east": "Eastbound",
"gamerule.cardinalDirection.west": "Westbound",
"gamerule.redSleepFailureEnum": "Why I didn't go to bed",
"gamerule.redSleepFailureEnum.description": "Bed bugs are nasty man.",
"gamerule.redSleepFailureEnum.not_possible_here": "Not here",
"gamerule.redSleepFailureEnum.not_possible_now": "Not now",
"gamerule.redSleepFailureEnum.too_far_away": "Too far away",
"gamerule.redSleepFailureEnum.obstructed": "Obstructed",
"gamerule.redSleepFailureEnum.other_problem": "Other problem",
"gamerule.redSleepFailureEnum.not_safe": "Not safe"
}

View file

@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"id": "fabric-game-rule-api-v1-testmod",
"name": "Fabric Game Rule API (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-game-rule-api-v1": "*",
"fabric-lifecycle-events-v1":"*",
"fabric-resource-loader-v0": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.gamerule.GameRulesTestMod"
]
}
}

View file

@ -24,6 +24,7 @@ include 'fabric-crash-report-info-v1'
//include 'fabric-dimensions-v1'
include 'fabric-events-interaction-v0'
include 'fabric-events-lifecycle-v0'
include 'fabric-game-rule-api-v1'
include 'fabric-item-api-v1'
include 'fabric-item-groups-v0'
include 'fabric-keybindings-v0'