Skip to content

Commit

Permalink
Add angle and rotation parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
TonytheMacaroni committed May 11, 2024
1 parent cb960d3 commit 60be8eb
Show file tree
Hide file tree
Showing 11 changed files with 754 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ public final class BukkitCaptionKeys {
*/
public static final Caption ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE =
of("argument.parse.failure.namespacedkey.need_namespace");
/**
* Variables: {@code <input>}
*
* @since 2.0.0
*/
public static final Caption ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT = of(
"argument.parse.failure.rotation.invalid_format"
);

private BukkitCaptionKeys() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
import org.incendo.cloud.bukkit.parser.WorldParser;
import org.incendo.cloud.bukkit.parser.location.Location2DParser;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.bukkit.parser.rotation.AngleParser;
import org.incendo.cloud.bukkit.parser.rotation.RotationParser;
import org.incendo.cloud.bukkit.parser.selector.MultipleEntitySelectorParser;
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
import org.incendo.cloud.bukkit.parser.selector.SingleEntitySelectorParser;
Expand Down Expand Up @@ -130,7 +132,9 @@ protected BukkitCommandManager(
.registerParser(Location2DParser.location2DParser())
.registerParser(ItemStackParser.itemStackParser())
.registerParser(SingleEntitySelectorParser.singleEntitySelectorParser())
.registerParser(SinglePlayerSelectorParser.singlePlayerSelectorParser());
.registerParser(SinglePlayerSelectorParser.singlePlayerSelectorParser())
.registerParser(AngleParser.angleParser())
.registerParser(RotationParser.rotationParser());

/* Register Entity Selector Parsers */
this.parserRegistry().registerAnnotationMapper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ public final class BukkitDefaultCaptionsProvider<C> extends DelegatingCaptionPro
*/
public static final String ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE =
"Invalid input '<input>', requires an explicit namespace.";
/**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT}
*/
public static final String ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT =
"'<input>' is not a valid rotation. Required format is '<yaw> <pitch>'";

private static final CaptionProvider<?> PROVIDER = CaptionProvider.constantProvider()
.putCaption(
Expand Down Expand Up @@ -120,6 +125,10 @@ public final class BukkitDefaultCaptionsProvider<C> extends DelegatingCaptionPro
BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE,
ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE
)
.putCaption(
BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT,
ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT
)
.build();

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
import org.incendo.cloud.bukkit.parser.location.Location2DParser;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.bukkit.parser.rotation.AngleParser;
import org.incendo.cloud.bukkit.parser.rotation.RotationParser;
import org.incendo.cloud.bukkit.parser.selector.MultipleEntitySelectorParser;
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
import org.incendo.cloud.bukkit.parser.selector.SingleEntitySelectorParser;
Expand Down Expand Up @@ -111,6 +113,10 @@ public void registerBuiltInMappings() {
this.mapNMS(new TypeToken<LocationParser<C>>() {}, "vec3", this::argumentVec3);
/* Map Vec2 */
this.mapNMS(new TypeToken<Location2DParser<C>>() {}, "vec2", this::argumentVec2);
/* Map Angle */
this.mapSimpleNMS(new TypeToken<AngleParser<C>>() {}, "angle");
/* Map Rotation */
this.mapSimpleNMS(new TypeToken<RotationParser<C>>() {}, "rotation");
}

private <T extends ArgumentParser<C, ?>> void mapResourceKey(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// MIT License
//
// Copyright (c) 2024 Incendo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package org.incendo.cloud.bukkit.parser.rotation;

import org.checkerframework.checker.nullness.qual.NonNull;

/**
* Represents an angle that can be applied to a reference angle.
*
* @since 2.0.0
*/
public final class Angle {

private final float angle;
private final boolean relative;

private Angle(
final float angle,
final boolean relative
) {
this.angle = angle;
this.relative = relative;
}

/**
* Create a new angle object.
*
* @param angle angle
* @param relative whether the angle is relative
* @return Created angle instance.
*/
public static @NonNull Angle of(
final float angle,
final boolean relative
) {
return new Angle(angle, relative);
}

/**
* Returns the angle.
*
* @return angle
*/
public float angle() {
return this.angle;
}

/**
* Returns if this angle is relative.
*
* @return whether the angle is relative
*/
public boolean relative() {
return this.relative;
}

/**
* Applies this angle to a reference angle.
*
* @param angle the reference angle
* @return the modified angle
*/
public float apply(final float angle) {
return this.relative ? this.angle + angle : this.angle;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Angle that = (Angle) o;
return Float.compare(this.angle, that.angle) == 0 && this.relative == that.relative;
}

@Override
public int hashCode() {
int result = Float.hashCode(this.angle);
result = 31 * result + Boolean.hashCode(this.relative);
return result;
}

@Override
public String toString() {
return String.format("Angle{angle=%s, relative=%s}", this.angle, this.relative);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//
// MIT License
//
// Copyright (c) 2024 Incendo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package org.incendo.cloud.bukkit.parser.rotation;

import java.util.stream.Collectors;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.component.CommandComponent;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.ArgumentParseResult;
import org.incendo.cloud.parser.ArgumentParser;
import org.incendo.cloud.parser.ParserDescriptor;
import org.incendo.cloud.parser.standard.FloatParser;
import org.incendo.cloud.parser.standard.IntegerParser;
import org.incendo.cloud.suggestion.BlockingSuggestionProvider;
import org.incendo.cloud.type.range.Range;


/**
* Parser that parsers a {@link Angle} from two floats.
*
* @param <C> Command sender type
* @since 2.0.0
*/
public final class AngleParser<C> implements ArgumentParser<C, Angle>, BlockingSuggestionProvider.Strings<C> {

private static final Range<Integer> SUGGESTION_RANGE = Range.intRange(Integer.MIN_VALUE, Integer.MAX_VALUE);

/**
* Creates a new angle parser.
*
* @param <C> command sender type
* @return the created parser
* @since 2.0.0
*/
@API(status = API.Status.STABLE, since = "2.0.0")
public static <C> @NonNull ParserDescriptor<C, Angle> angleParser() {
return ParserDescriptor.of(new AngleParser<>(), Angle.class);
}

/**
* Returns a {@link CommandComponent.Builder} using {@link #angleParser()} as the parser.
*
* @param <C> the command sender type
* @return the component builder
* @since 2.0.0
*/
@API(status = API.Status.STABLE, since = "2.0.0")
public static <C> CommandComponent.@NonNull Builder<C, Angle> angleComponent() {
return CommandComponent.<C, Angle>builder().parser(angleParser());
}

@Override
public @NonNull ArgumentParseResult<@NonNull Angle> parse(
final @NonNull CommandContext<@NonNull C> commandContext,
final @NonNull CommandInput commandInput
) {
final String input = commandInput.skipWhitespace().peekString();

final boolean relative;
if (commandInput.peek() == '~') {
relative = true;
commandInput.moveCursor(1);
} else {
relative = false;
}

final float angle;
try {
final boolean empty = commandInput.peekString().isEmpty() || commandInput.peek() == ' ';
angle = empty ? 0 : commandInput.readFloat();

// You can have a prefix without a number, in which case we wouldn't consume the
// subsequent whitespace. We do it manually.
if (commandInput.hasRemainingInput() && commandInput.peek() == ' ') {
commandInput.read();
}
} catch (final Exception e) {
return ArgumentParseResult.failure(new FloatParser.FloatParseException(
input,
new FloatParser<>(
FloatParser.DEFAULT_MINIMUM,
FloatParser.DEFAULT_MAXIMUM
),
commandContext
));
}

return ArgumentParseResult.success(
Angle.of(
angle,
relative
)
);
}

@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull CommandInput input
) {
String prefix;
if (input.hasRemainingInput() && input.peek() == '~') {
prefix = "~";
input.moveCursor(1);
} else {
prefix = "";
}

return IntegerParser.getSuggestions(
SUGGESTION_RANGE,
input
).stream().map(string -> prefix + string).collect(Collectors.toList());
}

}
Loading

0 comments on commit 60be8eb

Please sign in to comment.