Skip to content

Commit 60be8eb

Browse files
Add angle and rotation parsers
1 parent cb960d3 commit 60be8eb

File tree

11 files changed

+754
-1
lines changed

11 files changed

+754
-1
lines changed

cloud-bukkit/src/main/java/org/incendo/cloud/bukkit/BukkitCaptionKeys.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ public final class BukkitCaptionKeys {
9292
*/
9393
public static final Caption ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE =
9494
of("argument.parse.failure.namespacedkey.need_namespace");
95+
/**
96+
* Variables: {@code <input>}
97+
*
98+
* @since 2.0.0
99+
*/
100+
public static final Caption ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT = of(
101+
"argument.parse.failure.rotation.invalid_format"
102+
);
95103

96104
private BukkitCaptionKeys() {
97105
}

cloud-bukkit/src/main/java/org/incendo/cloud/bukkit/BukkitCommandManager.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
import org.incendo.cloud.bukkit.parser.WorldParser;
5858
import org.incendo.cloud.bukkit.parser.location.Location2DParser;
5959
import org.incendo.cloud.bukkit.parser.location.LocationParser;
60+
import org.incendo.cloud.bukkit.parser.rotation.AngleParser;
61+
import org.incendo.cloud.bukkit.parser.rotation.RotationParser;
6062
import org.incendo.cloud.bukkit.parser.selector.MultipleEntitySelectorParser;
6163
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
6264
import org.incendo.cloud.bukkit.parser.selector.SingleEntitySelectorParser;
@@ -130,7 +132,9 @@ protected BukkitCommandManager(
130132
.registerParser(Location2DParser.location2DParser())
131133
.registerParser(ItemStackParser.itemStackParser())
132134
.registerParser(SingleEntitySelectorParser.singleEntitySelectorParser())
133-
.registerParser(SinglePlayerSelectorParser.singlePlayerSelectorParser());
135+
.registerParser(SinglePlayerSelectorParser.singlePlayerSelectorParser())
136+
.registerParser(AngleParser.angleParser())
137+
.registerParser(RotationParser.rotationParser());
134138

135139
/* Register Entity Selector Parsers */
136140
this.parserRegistry().registerAnnotationMapper(

cloud-bukkit/src/main/java/org/incendo/cloud/bukkit/BukkitDefaultCaptionsProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ public final class BukkitDefaultCaptionsProvider<C> extends DelegatingCaptionPro
8484
*/
8585
public static final String ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE =
8686
"Invalid input '<input>', requires an explicit namespace.";
87+
/**
88+
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT}
89+
*/
90+
public static final String ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT =
91+
"'<input>' is not a valid rotation. Required format is '<yaw> <pitch>'";
8792

8893
private static final CaptionProvider<?> PROVIDER = CaptionProvider.constantProvider()
8994
.putCaption(
@@ -120,6 +125,10 @@ public final class BukkitDefaultCaptionsProvider<C> extends DelegatingCaptionPro
120125
BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE,
121126
ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE
122127
)
128+
.putCaption(
129+
BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT,
130+
ARGUMENT_PARSE_FAILURE_ROTATION_INVALID_FORMAT
131+
)
123132
.build();
124133

125134
@SuppressWarnings("unchecked")

cloud-bukkit/src/main/java/org/incendo/cloud/bukkit/internal/BukkitBrigadierMapper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
4141
import org.incendo.cloud.bukkit.parser.location.Location2DParser;
4242
import org.incendo.cloud.bukkit.parser.location.LocationParser;
43+
import org.incendo.cloud.bukkit.parser.rotation.AngleParser;
44+
import org.incendo.cloud.bukkit.parser.rotation.RotationParser;
4345
import org.incendo.cloud.bukkit.parser.selector.MultipleEntitySelectorParser;
4446
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
4547
import org.incendo.cloud.bukkit.parser.selector.SingleEntitySelectorParser;
@@ -111,6 +113,10 @@ public void registerBuiltInMappings() {
111113
this.mapNMS(new TypeToken<LocationParser<C>>() {}, "vec3", this::argumentVec3);
112114
/* Map Vec2 */
113115
this.mapNMS(new TypeToken<Location2DParser<C>>() {}, "vec2", this::argumentVec2);
116+
/* Map Angle */
117+
this.mapSimpleNMS(new TypeToken<AngleParser<C>>() {}, "angle");
118+
/* Map Rotation */
119+
this.mapSimpleNMS(new TypeToken<RotationParser<C>>() {}, "rotation");
114120
}
115121

116122
private <T extends ArgumentParser<C, ?>> void mapResourceKey(
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2024 Incendo
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in all
14+
// copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
//
24+
package org.incendo.cloud.bukkit.parser.rotation;
25+
26+
import org.checkerframework.checker.nullness.qual.NonNull;
27+
28+
/**
29+
* Represents an angle that can be applied to a reference angle.
30+
*
31+
* @since 2.0.0
32+
*/
33+
public final class Angle {
34+
35+
private final float angle;
36+
private final boolean relative;
37+
38+
private Angle(
39+
final float angle,
40+
final boolean relative
41+
) {
42+
this.angle = angle;
43+
this.relative = relative;
44+
}
45+
46+
/**
47+
* Create a new angle object.
48+
*
49+
* @param angle angle
50+
* @param relative whether the angle is relative
51+
* @return Created angle instance.
52+
*/
53+
public static @NonNull Angle of(
54+
final float angle,
55+
final boolean relative
56+
) {
57+
return new Angle(angle, relative);
58+
}
59+
60+
/**
61+
* Returns the angle.
62+
*
63+
* @return angle
64+
*/
65+
public float angle() {
66+
return this.angle;
67+
}
68+
69+
/**
70+
* Returns if this angle is relative.
71+
*
72+
* @return whether the angle is relative
73+
*/
74+
public boolean relative() {
75+
return this.relative;
76+
}
77+
78+
/**
79+
* Applies this angle to a reference angle.
80+
*
81+
* @param angle the reference angle
82+
* @return the modified angle
83+
*/
84+
public float apply(final float angle) {
85+
return this.relative ? this.angle + angle : this.angle;
86+
}
87+
88+
@Override
89+
public boolean equals(final Object o) {
90+
if (this == o) {
91+
return true;
92+
}
93+
if (o == null || getClass() != o.getClass()) {
94+
return false;
95+
}
96+
Angle that = (Angle) o;
97+
return Float.compare(this.angle, that.angle) == 0 && this.relative == that.relative;
98+
}
99+
100+
@Override
101+
public int hashCode() {
102+
int result = Float.hashCode(this.angle);
103+
result = 31 * result + Boolean.hashCode(this.relative);
104+
return result;
105+
}
106+
107+
@Override
108+
public String toString() {
109+
return String.format("Angle{angle=%s, relative=%s}", this.angle, this.relative);
110+
}
111+
112+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2024 Incendo
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in all
14+
// copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
//
24+
package org.incendo.cloud.bukkit.parser.rotation;
25+
26+
import java.util.stream.Collectors;
27+
import org.apiguardian.api.API;
28+
import org.checkerframework.checker.nullness.qual.NonNull;
29+
import org.incendo.cloud.component.CommandComponent;
30+
import org.incendo.cloud.context.CommandContext;
31+
import org.incendo.cloud.context.CommandInput;
32+
import org.incendo.cloud.parser.ArgumentParseResult;
33+
import org.incendo.cloud.parser.ArgumentParser;
34+
import org.incendo.cloud.parser.ParserDescriptor;
35+
import org.incendo.cloud.parser.standard.FloatParser;
36+
import org.incendo.cloud.parser.standard.IntegerParser;
37+
import org.incendo.cloud.suggestion.BlockingSuggestionProvider;
38+
import org.incendo.cloud.type.range.Range;
39+
40+
41+
/**
42+
* Parser that parsers a {@link Angle} from two floats.
43+
*
44+
* @param <C> Command sender type
45+
* @since 2.0.0
46+
*/
47+
public final class AngleParser<C> implements ArgumentParser<C, Angle>, BlockingSuggestionProvider.Strings<C> {
48+
49+
private static final Range<Integer> SUGGESTION_RANGE = Range.intRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
50+
51+
/**
52+
* Creates a new angle parser.
53+
*
54+
* @param <C> command sender type
55+
* @return the created parser
56+
* @since 2.0.0
57+
*/
58+
@API(status = API.Status.STABLE, since = "2.0.0")
59+
public static <C> @NonNull ParserDescriptor<C, Angle> angleParser() {
60+
return ParserDescriptor.of(new AngleParser<>(), Angle.class);
61+
}
62+
63+
/**
64+
* Returns a {@link CommandComponent.Builder} using {@link #angleParser()} as the parser.
65+
*
66+
* @param <C> the command sender type
67+
* @return the component builder
68+
* @since 2.0.0
69+
*/
70+
@API(status = API.Status.STABLE, since = "2.0.0")
71+
public static <C> CommandComponent.@NonNull Builder<C, Angle> angleComponent() {
72+
return CommandComponent.<C, Angle>builder().parser(angleParser());
73+
}
74+
75+
@Override
76+
public @NonNull ArgumentParseResult<@NonNull Angle> parse(
77+
final @NonNull CommandContext<@NonNull C> commandContext,
78+
final @NonNull CommandInput commandInput
79+
) {
80+
final String input = commandInput.skipWhitespace().peekString();
81+
82+
final boolean relative;
83+
if (commandInput.peek() == '~') {
84+
relative = true;
85+
commandInput.moveCursor(1);
86+
} else {
87+
relative = false;
88+
}
89+
90+
final float angle;
91+
try {
92+
final boolean empty = commandInput.peekString().isEmpty() || commandInput.peek() == ' ';
93+
angle = empty ? 0 : commandInput.readFloat();
94+
95+
// You can have a prefix without a number, in which case we wouldn't consume the
96+
// subsequent whitespace. We do it manually.
97+
if (commandInput.hasRemainingInput() && commandInput.peek() == ' ') {
98+
commandInput.read();
99+
}
100+
} catch (final Exception e) {
101+
return ArgumentParseResult.failure(new FloatParser.FloatParseException(
102+
input,
103+
new FloatParser<>(
104+
FloatParser.DEFAULT_MINIMUM,
105+
FloatParser.DEFAULT_MAXIMUM
106+
),
107+
commandContext
108+
));
109+
}
110+
111+
return ArgumentParseResult.success(
112+
Angle.of(
113+
angle,
114+
relative
115+
)
116+
);
117+
}
118+
119+
@Override
120+
public @NonNull Iterable<@NonNull String> stringSuggestions(
121+
final @NonNull CommandContext<C> commandContext,
122+
final @NonNull CommandInput input
123+
) {
124+
String prefix;
125+
if (input.hasRemainingInput() && input.peek() == '~') {
126+
prefix = "~";
127+
input.moveCursor(1);
128+
} else {
129+
prefix = "";
130+
}
131+
132+
return IntegerParser.getSuggestions(
133+
SUGGESTION_RANGE,
134+
input
135+
).stream().map(string -> prefix + string).collect(Collectors.toList());
136+
}
137+
138+
}

0 commit comments

Comments
 (0)