Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
55128df
sparkles: Begin working on Sponge support
Citymonstret Oct 14, 2020
cc0464d
sparkles: Improve the Sponge module
Citymonstret Oct 15, 2020
0130f92
sparkles: Initial work on Sponge v8
Citymonstret Oct 16, 2020
cd27527
sponge: Update after rebase
zml2008 Mar 29, 2021
1cb2961
Map to Sponge API Command.Raw instead of directly to Brigadier
jpenilla Apr 19, 2021
aa31a7f
Lock registration after event
jpenilla Apr 19, 2021
a785e3f
sponge: Add createNative to CloudInjectionModule
jpenilla Apr 19, 2021
57f75fd
sponge: Add some argument types and an example/test plugin
jpenilla Apr 20, 2021
546f0a5
sponge: More work on Sponge API 8 implementation
jpenilla Apr 24, 2021
2234cc4
sponge: Prioritize registered parser mappers to allow for overriding …
jpenilla Apr 25, 2021
86f4fb7
sponge: Implement more parsers, random fixes and improvements
jpenilla Apr 26, 2021
052b6e8
sponge: Set fields accessible
jpenilla Apr 26, 2021
78533f4
sponge: Add more argument parsers
jpenilla Apr 26, 2021
b340187
sponge: More Javadoc
jpenilla Apr 26, 2021
db41961
sponge: Properly translate node requirements and executable status
jpenilla Apr 26, 2021
679eccc
sponge: Also set requirements on root nodes
jpenilla Apr 26, 2021
06757a7
sponge: More Javadoc
jpenilla Apr 27, 2021
24034a8
sponge: Fix injection module types
jpenilla Apr 27, 2021
c232ad4
Add macOS garbage to .gitignore
jpenilla Apr 27, 2021
ca7c4c2
sponge: Fix license header violations
jpenilla Apr 27, 2021
9891e65
sponge: Clean up number argument mapping, unwrap mapped parsers
jpenilla Apr 28, 2021
2f51e94
sponge: Map compound arguments
jpenilla Apr 30, 2021
31de9cf
sponge: Update for sponge math changes
jpenilla Apr 30, 2021
615faee
sponge: Implement Block and ItemStack predicate arguments
jpenilla May 2, 2021
5b65f92
sponge: Update for plugin-spi changes
jpenilla May 3, 2021
7477566
sponge: Update for command api renames
jpenilla May 4, 2021
f716271
sponge: Simplify canExecute check
jpenilla May 7, 2021
d393aca
build/sponge: Update VanillaGradle to 0.2
jpenilla Jul 5, 2021
8f55f6f
build/sponge: Update SpongeGradle to 1.1.1
jpenilla Jul 5, 2021
19ace41
sponge: Update for Sponge API changes
jpenilla Jul 5, 2021
6cae596
Update for Sponge API changes
jpenilla Jul 27, 2021
38234df
Update for Sponge API and SpongeGradle changes
jpenilla Sep 19, 2021
4201473
Update import order
jpenilla Jan 10, 2022
3b77c4f
sponge: Catch late command manager creation
jpenilla Jan 10, 2022
fdc93ee
sponge: Use usage message for description
jpenilla Jan 12, 2022
6279896
sponge: Improve WorldArgument suggestions
jpenilla Jan 12, 2022
58d8f44
Fixes for SpongeForge
jpenilla Jan 13, 2022
72f8baf
sponge: Update RegistryEntryArgument
jpenilla Jan 13, 2022
ed57f60
sponge: Make GameProfileCollection extend Collection<GameProfile>
jpenilla Jan 14, 2022
8bb3252
more spongeforge fixes
jpenilla Jan 14, 2022
17879c4
address review comments
jpenilla Jan 17, 2022
4fca969
build updates
jpenilla Mar 10, 2022
dd93ca4
Update for 1.7.0 deprecations
jpenilla Jun 19, 2022
6065d95
Update sponge
jpenilla Jun 19, 2022
62dc9b0
update sponge module
jpenilla Nov 6, 2022
1fef68f
Update headers
jpenilla Dec 2, 2022
a4850f0
Update for build changes
jpenilla Dec 11, 2022
4575d38
Get sponge project to import
jpenilla Jan 21, 2024
d814144
Begin porting to cloud v2
jpenilla Jan 21, 2024
441f7b7
Cloud number suggestions by default & check sender type in node requi…
jpenilla Jan 21, 2024
af872fe
Make it compile
jpenilla Jan 21, 2024
8bdca20
spotlessApply
jpenilla Jan 21, 2024
d9b49c9
Resolve non-javadoc style issues
jpenilla Jan 21, 2024
f4eec0f
Javadoc fixes
jpenilla Jan 21, 2024
a020643
Begin updating for 1.20.2+
jpenilla Jan 21, 2024
394173c
Fix rebase typo
jpenilla Jan 21, 2024
30483f2
Fix build
jpenilla Jan 21, 2024
ead9f0b
More updating for 1.20.2
jpenilla Jan 21, 2024
7fc27cc
Add missing check for aggregate arguments
jpenilla Jan 21, 2024
3e77f6d
Clean up & tooltip suggestion support
jpenilla Jan 21, 2024
1c68dd2
Update for cloud changes
jpenilla Jan 22, 2024
2973561
Handle all aggregates not just compound
jpenilla Jan 22, 2024
9e124ed
Update for caption changes
jpenilla Jan 23, 2024
e6669a1
Update
jpenilla Jan 25, 2024
1854d8c
Repackage to org.incendo.cloud
jpenilla Jan 25, 2024
69aeeb6
Update for cloud beta 3
jpenilla Feb 21, 2024
8121661
fix compile
jpenilla Mar 12, 2024
372a735
fix compound example compile
jpenilla Apr 17, 2024
213c810
Fix sponge compile
jpenilla Apr 30, 2024
34d5d5a
Update sponge for access check changes
jpenilla May 3, 2024
a31a7a6
1.20.6
jpenilla May 30, 2024
268abff
fix stack overflow in sponge registration
jpenilla Nov 30, 2024
8759083
Fix build
jpenilla Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions cloud-sponge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id("conventions.base")
id("conventions.publishing")
id("net.neoforged.moddev")
}

dependencies {
api(libs.cloud.core)
implementation(libs.cloud.brigadier)
offlineLinkedJavadoc(project(":cloud-minecraft-modded-common"))
implementation(project(":cloud-minecraft-modded-common"))
compileOnly("org.spongepowered:spongeapi:11.0.0-SNAPSHOT")
compileOnly("org.spongepowered:sponge:1.20.6-11.0.0-SNAPSHOT")
}

neoForge {
enable {
neoFormVersion = "1.20.6-20240627.102356"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// 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.sponge;

import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.util.Types;
import java.lang.reflect.Type;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.SenderMapper;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.spongepowered.api.command.CommandCause;

/**
* Injection module that allows for {@link SpongeCommandManager} to be injectable.
*
* @param <C> Command sender type
*/
public final class CloudInjectionModule<C> extends AbstractModule {

private final Class<C> commandSenderType;
private final ExecutionCoordinator<C> executionCoordinator;
private final SenderMapper<@NonNull CommandCause, @NonNull C> senderMapper;

/**
* Create a new injection module.
*
* @param commandSenderType Your command sender type
* @param executionCoordinator Command execution coordinator
* @param senderMapper Function mapping the custom command sender type to a Sponge CommandCause
*/
public CloudInjectionModule(
final @NonNull Class<C> commandSenderType,
final @NonNull ExecutionCoordinator<C> executionCoordinator,
final @NonNull SenderMapper<@NonNull CommandCause, @NonNull C> senderMapper
) {
this.commandSenderType = commandSenderType;
this.executionCoordinator = executionCoordinator;
this.senderMapper = senderMapper;
}

/**
* Create a new injection module using Sponge's {@link CommandCause} as the sender type.
*
* @param executionCoordinator Command execution coordinator
* @return new injection module
*/
public static @NonNull CloudInjectionModule<@NonNull CommandCause> createNative(
final @NonNull ExecutionCoordinator<CommandCause> executionCoordinator
) {
return new CloudInjectionModule<>(
CommandCause.class,
executionCoordinator,
SenderMapper.identity()
);
}

@SuppressWarnings({"unchecked", "rawtypes"})
@Override
protected void configure() {
final Type commandExecutionCoordinatorType = Types.newParameterizedType(
ExecutionCoordinator.class, this.commandSenderType
);
final Key coordinatorKey = Key.get(commandExecutionCoordinatorType);
this.bind(coordinatorKey).toInstance(this.executionCoordinator);

final Type commandSenderMapperFunction = Types.newParameterizedType(
SenderMapper.class, CommandCause.class, this.commandSenderType
);
final Key senderMapperKey = Key.get(commandSenderMapperFunction);
this.bind(senderMapperKey).toInstance(this.senderMapper);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
//
// 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.sponge;

import io.leangen.geantyref.GenericTypeReflector;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.component.CommandComponent;
import org.incendo.cloud.internal.CommandNode;
import org.incendo.cloud.parser.aggregate.AggregateParser;
import org.incendo.cloud.parser.standard.LiteralParser;
import org.incendo.cloud.permission.Permission;
import org.incendo.cloud.type.tuple.Pair;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.command.CommandCause;
import org.spongepowered.api.command.CommandCompletion;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.parameter.ArgumentReader;
import org.spongepowered.api.command.registrar.tree.CommandTreeNode;

import static net.kyori.adventure.text.Component.text;

final class CloudSpongeCommand<C> implements Command.Raw {

private final SpongeCommandManager<C> commandManager;
private final String label;

CloudSpongeCommand(
final @NonNull String label,
final @NonNull SpongeCommandManager<C> commandManager
) {
this.label = label;
this.commandManager = commandManager;
}

@Override
public CommandResult process(final @NonNull CommandCause cause, final ArgumentReader.@NonNull Mutable arguments) {
final C cloudSender = this.commandManager.senderMapper().map(cause);
final String input = this.formatCommandForParsing(arguments.input());
this.commandManager.commandExecutor().executeCommand(cloudSender, input);
return CommandResult.success();
}

@Override
public List<CommandCompletion> complete(
final @NonNull CommandCause cause,
final ArgumentReader.@NonNull Mutable arguments
) {
return this.commandManager.suggestionFactory()
.suggestImmediately(this.commandManager.senderMapper().map(cause),
this.formatCommandForSuggestions(arguments.input()))
.list()
.stream()
.map(s -> CommandCompletion.of(s.suggestion(), s.tooltip()))
.collect(Collectors.toList());
}

@Override
public boolean canExecute(final @NonNull CommandCause cause) {
return this.checkAccess(
cause,
this.namedNode().nodeMeta()
.getOrDefault(CommandNode.META_KEY_ACCESS, Collections.emptyMap())
);
}

@Override
public Optional<Component> shortDescription(final CommandCause cause) {
return Optional.of(this.usage(cause));
}

@Override
public Optional<Component> extendedDescription(final CommandCause cause) {
return Optional.of(this.usage(cause));
}

@Override
public Optional<Component> help(final @NonNull CommandCause cause) {
return Optional.of(this.usage(cause));
}

@Override
public Component usage(final CommandCause cause) {
return text(this.commandManager.commandSyntaxFormatter()
.apply(this.commandManager.senderMapper().map(cause), Collections.emptyList(), this.namedNode()));
}

private CommandNode<C> namedNode() {
return this.commandManager.commandTree().getNamedNode(this.label);
}

@Override
public CommandTreeNode.Root commandTree() {
final CommandTreeNode<CommandTreeNode.Root> root = CommandTreeNode.root();

final CommandNode<C> cloud = this.namedNode();

if (canExecute(cloud)) {
root.executable();
}

this.addRequirement(cloud, root);

this.addChildren(root, cloud);
return (CommandTreeNode.Root) root;
}

private void addChildren(final CommandTreeNode<?> node, final CommandNode<C> cloud) {
for (final CommandNode<C> child : cloud.children()) {
final CommandComponent<C> value = child.component();
final CommandTreeNode.Argument<? extends CommandTreeNode.Argument<?>> treeNode;
if (value.parser() instanceof LiteralParser) {
treeNode = (CommandTreeNode.Argument<? extends CommandTreeNode.Argument<?>>) CommandTreeNode.literal();
} else if (value.parser() instanceof AggregateParser<C, ?> aggregate) {
this.handleAggregate(node, child, aggregate);
continue;
} else {
treeNode = this.commandManager.parserMapper().mapComponent(value);
}
this.addRequirement(child, treeNode);
if (canExecute(child)) {
treeNode.executable();
}
this.addChildren(treeNode, child);
node.child(value.name(), treeNode);
}
}

private void handleAggregate(
final CommandTreeNode<?> node,
final CommandNode<C> child,
final AggregateParser<C, ?> compound
) {
final CommandTreeNode.Argument<? extends CommandTreeNode.Argument<?>> treeNode;
final ArrayDeque<Pair<String, CommandTreeNode.Argument<? extends CommandTreeNode.Argument<?>>>> nodes = new ArrayDeque<>();
for (final CommandComponent<C> component : compound.components()) {
final String name = component.name();
nodes.add(Pair.of(name, this.commandManager.parserMapper().mapParser(component.parser())));
}
Pair<String, CommandTreeNode.Argument<? extends CommandTreeNode.Argument<?>>> argument = null;
while (!nodes.isEmpty()) {
final Pair<String, CommandTreeNode.Argument<? extends CommandTreeNode.Argument<?>>> prev = argument;
argument = nodes.removeLast();
if (prev != null) {
argument.second().child(prev.first(), prev.second());
} else {
// last node
if (canExecute(child)) {
argument.second().executable();
}
}
this.addRequirement(child, argument.second());
}
treeNode = argument.second();
this.addChildren(treeNode, child);
node.child(compound.components().get(0).toString(), treeNode);
}

private static <C> boolean canExecute(final @NonNull CommandNode<C> node) {
return node.isLeaf()
|| !node.component().required()
|| node.command() != null
|| node.children().stream().noneMatch(c -> c.component().required());
}

private void addRequirement(
final @NonNull CommandNode<C> cloud,
final @NonNull CommandTreeNode<? extends CommandTreeNode<?>> node
) {
final Map<Type, Permission> accessMap =
cloud.nodeMeta().getOrDefault(CommandNode.META_KEY_ACCESS, Collections.emptyMap());
node.requires(cause -> this.checkAccess(cause, accessMap));
}

private boolean checkAccess(final CommandCause cause, final Map<Type, Permission> accessMap) {
final C cloudSender = this.commandManager.senderMapper().map(cause);
for (final Map.Entry<Type, Permission> entry : accessMap.entrySet()) {
if (GenericTypeReflector.isSuperType(entry.getKey(), cloudSender.getClass())) {
if (this.commandManager.testPermission(cloudSender, entry.getValue()).allowed()) {
return true;
}
}
}
return false;
}

private String formatCommandForParsing(final @NonNull String arguments) {
if (arguments.isEmpty()) {
return this.label;
}
return this.label + " " + arguments;
}

private String formatCommandForSuggestions(final @NonNull String arguments) {
return this.label + " " + arguments;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// 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.sponge;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.api.command.registrar.tree.CommandTreeNode;

/**
* Implemented by {@link org.incendo.cloud.parser.ArgumentParser} which also supply a special {@link CommandTreeNode.Argument}.
*/
public interface NodeSource {

/**
* Get the node for this parser.
*
* @return argument node
*/
CommandTreeNode.@NonNull Argument<? extends CommandTreeNode.Argument<?>> node();

}
Loading