Skip to content

Commit

Permalink
feat: add a very simple gamemode command
Browse files Browse the repository at this point in the history
  • Loading branch information
Citymonstret committed Feb 1, 2024
1 parent 275c4e7 commit bc86277
Show file tree
Hide file tree
Showing 13 changed files with 402 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// 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.kitchensink.command.commands;

import jakarta.inject.Singleton;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.bean.CommandProperties;
import org.incendo.kitchensink.command.KitchenSinkCommandBean;
import org.incendo.kitchensink.command.KitchenSinkCommandSender;
import org.incendo.kitchensink.entity.player.GameMode;
import org.incendo.kitchensink.entity.player.KitchenSinkPlayer;

import static net.kyori.adventure.text.Component.text;
import static org.incendo.kitchensink.command.parser.GameModeParser.gameModeParser;

@Singleton
public final class GameModeCommand extends KitchenSinkCommandBean {

@Override
protected @NonNull CommandProperties properties() {
return CommandProperties.of("gamemode", "gm");
}

@Override
protected Command.@NonNull Builder<? extends KitchenSinkCommandSender> configureKitchenSinkCommand(
final Command.@NonNull Builder<KitchenSinkCommandSender> builder
) {
// TODO(City): Make the command take in an optional player argument that defaults
// to the executing player (if the sender is a player).
return builder.required("gameMode", gameModeParser())
.senderType(KitchenSinkPlayer.class)
.handler((FutureCommandExecutionHandler<KitchenSinkPlayer>) context -> {
final GameMode gameMode = context.get("gameMode");
return context.sender().gameMode(gameMode).whenComplete(($, error) -> {
context.sender()
.sendMessage(text("Your game mode has been set to ", NamedTextColor.GRAY)
.append(text(gameMode.key(), NamedTextColor.GOLD)));
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//
// 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.kitchensink.command.parser;

import java.util.Objects;
import java.util.stream.Collectors;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.caption.CaptionVariable;
import org.incendo.cloud.caption.StandardCaptionKeys;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.exception.parsing.ParserException;
import org.incendo.cloud.parser.ArgumentParseResult;
import org.incendo.cloud.parser.ArgumentParser;
import org.incendo.cloud.parser.ParserDescriptor;
import org.incendo.cloud.suggestion.BlockingSuggestionProvider;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.type.range.Range;
import org.incendo.kitchensink.command.KitchenSinkCommandSender;
import org.incendo.kitchensink.entity.player.GameMode;

public final class GameModeParser implements ArgumentParser<KitchenSinkCommandSender, GameMode>,
BlockingSuggestionProvider<KitchenSinkCommandSender> {

/**
* Creates a new game mode parser.
*
* @return the parser
*/
public static ParserDescriptor<KitchenSinkCommandSender, GameMode> gameModeParser() {
return ParserDescriptor.of(new GameModeParser(), GameMode.class);
}

@Override
public @NonNull ArgumentParseResult<@NonNull GameMode> parse(
final @NonNull CommandContext<@NonNull KitchenSinkCommandSender> commandContext,
final @NonNull CommandInput commandInput
) {
if (commandInput.isValidInteger(Range.intRange(0, 3))) {
return ArgumentParseResult.success(GameMode.fromId(commandInput.readInteger()));
}

final String input = commandInput.readString();
try {
return ArgumentParseResult.success(GameMode.fromKey(input));
} catch (final IllegalArgumentException ignored) {
return ArgumentParseResult.failure(new GameModeParseException(input, commandContext));
}
}

@Override
public @NonNull Iterable<@NonNull Suggestion> suggestions(
final @NonNull CommandContext<KitchenSinkCommandSender> context,
final @NonNull CommandInput input
) {
return GameMode.gameModes()
.stream()
.map(GameMode::key)
.map(Suggestion::simple)
.toList();
}


@API(status = API.Status.STABLE)
public static final class GameModeParseException extends ParserException {

private final String input;

/**
* Construct a new game mode parse exception.
*
* @param input input
* @param context command context
*/
public GameModeParseException(
final @NonNull String input,
final @NonNull CommandContext<?> context
) {
super(
GameModeParser.class,
context,
StandardCaptionKeys.ARGUMENT_PARSE_FAILURE_ENUM,
CaptionVariable.of("input", input),
CaptionVariable.of("acceptableValues", GameMode.gameModes()
.stream()
.map(GameMode::key)
.collect(Collectors.joining(", "))
)
);
this.input = input;
}

/**
* Returns the input provided by the sender.
*
* @return input
*/
public @NonNull String input() {
return this.input;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final GameModeParseException that = (GameModeParseException) o;
return this.input.equals(that.input);
}

@Override
public int hashCode() {
return Objects.hash(this.input);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Custom parsers.
*/
package org.incendo.kitchensink.command.parser;
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Optional;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.kitchensink.entity.player.KitchenSinkPlayer;

/**
* Repository that contains {@link KitchenSinkPlayer players}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// 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.kitchensink.entity.player;

import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;

public final class GameMode {

public static final GameMode SURVIVAL = new GameMode("survival", 0);
public static final GameMode CREATIVE = new GameMode("creative", 1);
public static final GameMode ADVENTURE = new GameMode("adventure", 2);
public static final GameMode SPECTATOR = new GameMode("spectator", 3);

// Has to come after because of the forward referencing that would otherwise occur.
private static final List<@NonNull GameMode> GAME_MODES = List.of(SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR);

/**
* Returns the game mode that corresponds to the given {@code key}.
*
* @param key game mode key
* @return the game mode
* @throws IllegalArgumentException if the game mode does not exist
*/
public static @NonNull GameMode fromKey(final @NonNull String key) {
return switch (key.toLowerCase(Locale.ENGLISH)) {
case "survival" -> GameMode.SURVIVAL;
case "creative" -> GameMode.CREATIVE;
case "adventure" -> GameMode.ADVENTURE;
case "spectator" -> GameMode.SPECTATOR;
default -> throw new IllegalArgumentException("Unknown game mode: " + key);
};
}

/**
* Returns the game mode that corresponds to the given {@code id}.
*
* @param id game mode id
* @return the game mode
* @throws IllegalArgumentException if the game mode does not exist
*/
public static @NonNull GameMode fromId(final int id) {
return switch (id) {
case 0 -> GameMode.SURVIVAL;
case 1 -> GameMode.CREATIVE;
case 2 -> GameMode.ADVENTURE;
case 3 -> GameMode.SPECTATOR;
default -> throw new IllegalArgumentException("Unknown game mode: " + id);
};
}

/**
* Returns the available game modes.
*
* @return the game modes
*/
public static @NonNull Collection<@NonNull GameMode> gameModes() {
return List.copyOf(GAME_MODES);
}

private final String key;
private final int id;

private GameMode(
final @NonNull String key,
final int id
) {
this.key = Objects.requireNonNull(key, "key");
this.id = id;
}

/**
* Returns the game mode key.
*
* @return the key
*/
public @NonNull String key() {
return this.key;
}

/**
* Returns the numerical id that represents this game mode.
*
* @return the id
*/
public int id() {
return this.id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,30 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package org.incendo.kitchensink.entity;
package org.incendo.kitchensink.entity.player;

import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.kitchensink.command.KitchenSinkCommandSender;
import org.incendo.kitchensink.entity.KitchenSinkEntity;

/**
* A player.
*/
public interface KitchenSinkPlayer extends KitchenSinkEntity, KitchenSinkCommandSender {

/**
* Returns the current game mode.
*
* @return the game mode
*/
@NonNull GameMode gameMode();

/**
* Sets the player game mode.
*
* @param gameMode new game mode
* @return future that completes when the game mode has been updated
*/
@NonNull CompletableFuture<Void> gameMode(@NonNull GameMode gameMode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Classes related to players.
*/
package org.incendo.kitchensink.entity.player;
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import org.incendo.kitchensink.command.KitchenSinkCommandBean;
import org.incendo.kitchensink.command.commands.GameModeCommand;
import org.incendo.kitchensink.command.commands.PingCommand;

/**
Expand All @@ -40,5 +41,6 @@ protected void configure() {
KitchenSinkCommandBean.class
);
commandBinder.addBinding().to(PingCommand.class);
commandBinder.addBinding().to(GameModeCommand.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public final class PaperKitchenSink extends JavaPlugin {

@Override
public void onLoad() {
this.injector = Guice.createInjector(new PaperModule(), new CloudModule(this), new CommandModule());
this.injector = Guice.createInjector(new PaperModule(this), new CloudModule(this), new CommandModule());
}

@Override
Expand Down
Loading

0 comments on commit bc86277

Please sign in to comment.