Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
ba82049
sparkles: Begin working on Sponge support
Citymonstret Oct 14, 2020
512123e
sparkles: Improve the Sponge module
Citymonstret Oct 15, 2020
c23b4f2
sparkles: Initial work on Sponge v8
Citymonstret Oct 16, 2020
5d10f37
sponge: Update after rebase
zml2008 Mar 29, 2021
a77e07d
Map to Sponge API Command.Raw instead of directly to Brigadier
jpenilla Apr 19, 2021
44f8dc4
Lock registration after event
jpenilla Apr 19, 2021
2f3a3c8
sponge: Add createNative to CloudInjectionModule
jpenilla Apr 19, 2021
502779d
sponge: Add some argument types and an example/test plugin
jpenilla Apr 20, 2021
fea7c7d
sponge: More work on Sponge API 8 implementation
jpenilla Apr 24, 2021
e54779f
sponge: Prioritize registered parser mappers to allow for overriding …
jpenilla Apr 25, 2021
214d636
sponge: Implement more parsers, random fixes and improvements
jpenilla Apr 26, 2021
e13b9df
sponge: Set fields accessible
jpenilla Apr 26, 2021
f6e513e
sponge: Add more argument parsers
jpenilla Apr 26, 2021
752c832
sponge: More Javadoc
jpenilla Apr 26, 2021
a41c861
sponge: Properly translate node requirements and executable status
jpenilla Apr 26, 2021
b58e26e
sponge: Also set requirements on root nodes
jpenilla Apr 26, 2021
ce68635
sponge: More Javadoc
jpenilla Apr 27, 2021
0184c28
sponge: Fix injection module types
jpenilla Apr 27, 2021
d91494b
Add macOS garbage to .gitignore
jpenilla Apr 27, 2021
1a5c0d3
sponge: Fix license header violations
jpenilla Apr 27, 2021
09cbe3b
sponge: Clean up number argument mapping, unwrap mapped parsers
jpenilla Apr 28, 2021
2161015
sponge: Map compound arguments
jpenilla Apr 30, 2021
ed50b95
sponge: Update for sponge math changes
jpenilla Apr 30, 2021
0685022
sponge: Implement Block and ItemStack predicate arguments
jpenilla May 2, 2021
859c1b9
sponge: Update for plugin-spi changes
jpenilla May 3, 2021
759a1fb
sponge: Update for command api renames
jpenilla May 4, 2021
4d49ba8
sponge: Simplify canExecute check
jpenilla May 7, 2021
54ffa77
build/sponge: Update VanillaGradle to 0.2
jpenilla Jul 5, 2021
075c53a
build/sponge: Update SpongeGradle to 1.1.1
jpenilla Jul 5, 2021
13538ae
sponge: Update for Sponge API changes
jpenilla Jul 5, 2021
3c8752a
Update for Sponge API changes
jpenilla Jul 27, 2021
d499854
Update for Sponge API and SpongeGradle changes
jpenilla Sep 19, 2021
eccfd47
Update import order
jpenilla Jan 10, 2022
dc61cfd
sponge: Catch late command manager creation
jpenilla Jan 10, 2022
3da2381
sponge: Use usage message for description
jpenilla Jan 12, 2022
77f5286
sponge: Improve WorldArgument suggestions
jpenilla Jan 12, 2022
348bdee
Fixes for SpongeForge
jpenilla Jan 13, 2022
f518ae1
sponge: Update RegistryEntryArgument
jpenilla Jan 13, 2022
fe90c7a
sponge: Make GameProfileCollection extend Collection<GameProfile>
jpenilla Jan 14, 2022
f2909f5
more spongeforge fixes
jpenilla Jan 14, 2022
d84ba45
address review comments
jpenilla Jan 17, 2022
cea9599
build updates
jpenilla Mar 10, 2022
3cd042c
Update for 1.7.0 deprecations
jpenilla Jun 19, 2022
9fce1d3
Update sponge
jpenilla Jun 19, 2022
98950ec
update sponge module
jpenilla Nov 6, 2022
b6912bb
Update headers
jpenilla Dec 2, 2022
56aaa34
Update for build changes
jpenilla Dec 11, 2022
ebebede
Get sponge project to import
jpenilla Jan 21, 2024
7e3a08a
Begin porting to cloud v2
jpenilla Jan 21, 2024
44ac5e3
Cloud number suggestions by default & check sender type in node requi…
jpenilla Jan 21, 2024
789d148
Make it compile
jpenilla Jan 21, 2024
6c84e9b
spotlessApply
jpenilla Jan 21, 2024
ad98071
Resolve non-javadoc style issues
jpenilla Jan 21, 2024
a021650
Javadoc fixes
jpenilla Jan 21, 2024
551bc21
Begin updating for 1.20.2+
jpenilla Jan 21, 2024
aa21f54
Fix rebase typo
jpenilla Jan 21, 2024
6147285
Fix build
jpenilla Jan 21, 2024
32a9da4
More updating for 1.20.2
jpenilla Jan 21, 2024
a9d81c0
Add missing check for aggregate arguments
jpenilla Jan 21, 2024
ba9767f
Clean up & tooltip suggestion support
jpenilla Jan 21, 2024
b5e4589
Update for cloud changes
jpenilla Jan 22, 2024
9c775ec
Handle all aggregates not just compound
jpenilla Jan 22, 2024
c7c7093
Update for caption changes
jpenilla Jan 23, 2024
672f8c3
Update
jpenilla Jan 25, 2024
0bd8e63
Repackage to org.incendo.cloud
jpenilla Jan 25, 2024
b6490fe
Update for cloud beta 3
jpenilla Feb 21, 2024
9e80443
fix compile
jpenilla Mar 12, 2024
c66f2fc
fix compound example compile
jpenilla Apr 17, 2024
e14693d
Fix sponge compile
jpenilla Apr 30, 2024
ee4379d
Update sponge for access check changes
jpenilla May 3, 2024
e7a663a
1.20.6
jpenilla May 30, 2024
141aa8d
fix stack overflow in sponge registration
jpenilla Nov 30, 2024
2fbca1a
Fix build
jpenilla Oct 24, 2025
02fffbd
Update to API-12
Vilsu221 Oct 24, 2025
42d4e6e
Update to API-14 & fix parsers
Vilsu221 Nov 6, 2025
6bf49d0
Fix style violations
jpenilla Jan 24, 2026
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:14.1.0-SNAPSHOT")
compileOnly("org.spongepowered:sponge:1.21.4-14.0.0-SNAPSHOT")
}

neoForge {
enable {
neoFormVersion = "1.21.4-20241203.161809"
}
}
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,228 @@
//
// 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 org.spongepowered.api.registry.RegistryHolder;

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 RegistryHolder holder) {
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, holder);
return (CommandTreeNode.Root) root;
}

private void addChildren(final CommandTreeNode<?> node, final CommandNode<C> cloud, final RegistryHolder holder) {
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, holder);
continue;
} else {
treeNode = this.commandManager.parserMapper().mapComponent(value, holder);
}
this.addRequirement(child, treeNode);
if (canExecute(child)) {
treeNode.executable();
}
this.addChildren(treeNode, child, holder);
node.child(value.name(), treeNode);
}
}

private void handleAggregate(
final CommandTreeNode<?> node,
final CommandNode<C> child,
final AggregateParser<C, ?> compound,
final RegistryHolder holder
) {
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(), holder)));
}
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, holder);
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,43 @@
//
// 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;
import org.spongepowered.api.registry.RegistryHolder;

/**
* 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.
*
* @param registryHolder registry holder
* @return argument node
*/
CommandTreeNode.@NonNull Argument<? extends CommandTreeNode.Argument<?>> node(RegistryHolder registryHolder);

}
Loading