/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* 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.lanternpowered.server.command;
import static org.lanternpowered.server.text.translation.TranslationHelper.t;
import static org.lanternpowered.server.text.translation.TranslationHelper.tb;
import static org.spongepowered.api.command.CommandMessageFormatting.error;
import org.lanternpowered.server.command.element.GenericArguments2;
import org.lanternpowered.server.scoreboard.LanternScore;
import org.lanternpowered.server.scoreboard.LanternTeam;
import org.lanternpowered.server.text.LanternTexts;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scoreboard.Score;
import org.spongepowered.api.scoreboard.Scoreboard;
import org.spongepowered.api.scoreboard.Team;
import org.spongepowered.api.scoreboard.critieria.Criterion;
import org.spongepowered.api.scoreboard.displayslot.DisplaySlot;
import org.spongepowered.api.scoreboard.objective.Objective;
import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayMode;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColors;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
public final class CommandScoreboard extends CommandProvider {
public CommandScoreboard() {
super(2, "scoreboard");
}
@Override
public void completeSpec(PluginContainer pluginContainer, CommandSpec.Builder specBuilder) {
specBuilder
.arguments(
GenericArguments.flags()
.valueFlag(GenericArguments.world(CommandHelper.WORLD_KEY), "-world", "w")
.buildWith(GenericArguments.none())
)
.child(CommandSpec.builder()
.child(CommandSpec.builder()
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
final Set<Objective> objectives = scoreboard.getObjectives();
if (objectives.isEmpty()) {
throw new CommandException(t("commands.scoreboard.objectives.list.empty"));
}
src.sendMessage(tb("commands.scoreboard.objectives.list.count", objectives.size())
.color(TextColors.DARK_GREEN)
.build());
objectives.forEach(objective -> src.sendMessage(t("commands.scoreboard.objectives.list.entry",
objective.getName(), objective.getDisplayName(), objective.getCriterion().getName())));
return CommandResult.success();
})
.build(), "list")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("name")),
GenericArguments.catalogedElement(Text.of("criterion"), Criterion.class),
GenericArguments.flags()
.valueFlag(GenericArguments.catalogedElement(
Text.of("display-mode"), ObjectiveDisplayMode.class), "-display-mode", "-dm", "d")
.buildWith(GenericArguments.none()),
GenericArguments2.remainingString(Text.of("display-name"))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
final String name = args.<String>getOne("name").get();
final Criterion criterion = args.<Criterion>getOne("criterion").get();
if (scoreboard.getObjective(name).isPresent()) {
throw new CommandException(t("commands.scoreboard.objectives.add.alreadyExists", name));
}
if (name.length() > 16) {
throw new CommandException(t("commands.scoreboard.objectives.add.tooLong", name, 16));
}
String displayName = args.<String>getOne("display-name").orElse(null);
if (displayName != null && displayName.length() > 32) {
throw new CommandException(t("commands.scoreboard.objectives.add.displayTooLong", displayName, 32));
}
Objective.Builder builder = Objective.builder()
.name(name)
.criterion(criterion);
if (displayName != null && !displayName.isEmpty()) {
builder.displayName(Text.of(displayName));
}
args.<ObjectiveDisplayMode>getOne("display-mode").ifPresent(builder::objectiveDisplayMode);
scoreboard.addObjective(builder.build());
src.sendMessage(t("commands.scoreboard.objectives.add.success", name));
return CommandResult.success();
})
.build(), "add")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("name"))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
final String name = args.<String>getOne("name").get();
scoreboard.removeObjective(scoreboard.getObjective(name).orElseThrow(
() -> new CommandException(t("commands.scoreboard.objectiveNotFound", name))));
src.sendMessage(t("commands.scoreboard.objectives.remove.success", name));
return CommandResult.success();
})
.build(), "remove")
.child(CommandSpec.builder()
.arguments(
GenericArguments.catalogedElement(Text.of("display-slot"), DisplaySlot.class),
GenericArguments.optional(GenericArguments.string(Text.of("name")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
final Optional<String> optName = args.<String>getOne("name");
DisplaySlot displaySlot = args.<DisplaySlot>getOne("display-slot").get();
Objective objective = null;
if (optName.isPresent()) {
final String name = optName.get();
objective = scoreboard.getObjective(name).orElseThrow(
() -> new CommandException(t("commands.scoreboard.objectiveNotFound", name)));
src.sendMessage(t("commands.scoreboard.objectives.setdisplay.successSet", displaySlot.getName(), name));
} else {
src.sendMessage(t("commands.scoreboard.objectives.setdisplay.successCleared", displaySlot.getName()));
}
scoreboard.updateDisplaySlot(objective, displaySlot);
return CommandResult.success();
})
.build(), "setdisplay")
.build(), "objectives")
.child(CommandSpec.builder()
.child(CommandSpec.builder()
.arguments(
GenericArguments.optional(GenericArguments.string(Text.of("target")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
int result;
if (args.hasAny("target")) {
String entity = args.<String>getOne("target").get();
Set<Score> scores = scoreboard.getScores(Text.of(entity));
if (scores.isEmpty()) {
throw new CommandException(t("commands.scoreboard.players.list.player.empty"));
}
result = scores.size();
src.sendMessage(tb("commands.scoreboard.players.list.player.count", result, entity)
.color(TextColors.DARK_GREEN)
.build());
scores.forEach(score -> score.getObjectives().forEach(objective ->
src.sendMessage(t("commands.scoreboard.players.list.player.entry",
score.getScore(), objective.getDisplayName(), objective.getName()))));
} else {
Set<Text> names = scoreboard.getScores().stream().map(Score::getName).collect(Collectors.toSet());
if (names.isEmpty()) {
throw new CommandException(t("commands.scoreboard.players.list.empty"));
}
result = names.size();
src.sendMessage(tb("commands.scoreboard.players.list.count", result)
.color(TextColors.DARK_GREEN)
.build());
src.sendMessage(Text.joinWith(Text.of(", "), names));
}
return CommandResult.builder().successCount(1).queryResult(result).build();
})
.build(), "list")
.child(createPlayerScoreSpec(Score::setScore), "set")
.child(createPlayerScoreSpec((score, value) -> score.setScore(score.getScore() + value)), "add")
.child(createPlayerScoreSpec((score, value) -> score.setScore(score.getScore() - value)), "remove")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("target")),
GenericArguments.optional(GenericArguments.string(Text.of("objective")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
String target = args.<String>getOne("target").get();
if (args.hasAny("objective")) {
String objectiveName = args.<String>getOne("objective").get();
Objective objective = scoreboard.getObjective(objectiveName).orElseThrow(
() -> new CommandException(t("commands.scoreboard.objectiveNotFound", objectiveName)));
objective.removeScore(Text.of(target));
src.sendMessage(t("commands.scoreboard.players.resetscore.success", objectiveName, target));
} else {
scoreboard.removeScores(Text.of(target));
src.sendMessage(t("commands.scoreboard.players.reset.success", target));
}
return CommandResult.success();
})
.build(), "reset")
.build(), "players")
.child(CommandSpec.builder()
.child(CommandSpec.builder()
.arguments(
GenericArguments.optional(GenericArguments.string(Text.of("name")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
int result;
if (args.hasAny("name")) {
String teamName = args.<String>getOne("name").get();
Team team = scoreboard.getTeam(teamName).orElseThrow(
() -> new CommandException(t("commands.scoreboard.teamNotFound", teamName)));
Set<Text> members = team.getMembers();
if (members.isEmpty()) {
throw new CommandException(t("commands.scoreboard.teams.list.player.empty", teamName));
}
result = members.size();
src.sendMessage(tb("commands.scoreboard.teams.list.player.count", result, teamName)
.color(TextColors.DARK_GREEN)
.build());
src.sendMessage(Text.joinWith(Text.of(", "), members));
} else {
Set<Team> teams = scoreboard.getTeams();
if (teams.isEmpty()) {
throw new CommandException(t("commands.scoreboard.teams.list.empty"));
}
result = teams.size();
src.sendMessage(tb("commands.scoreboard.teams.list.count", result)
.color(TextColors.DARK_GREEN)
.build());
teams.forEach(team -> src.sendMessage(t("commands.scoreboard.teams.list.entry",
team.getName(), team.getDisplayName(), team.getMembers().size())));
}
return CommandResult.builder().successCount(1).queryResult(result).build();
})
.build(), "list")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("name")),
GenericArguments.optional(GenericArguments2.remainingString(Text.of("display-name")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
final String teamName = args.<String>getOne("name").get();
if (scoreboard.getTeam(teamName).isPresent()) {
throw new CommandException(t("commands.scoreboard.teams.add.alreadyExists", teamName));
}
if (teamName.length() > 16) {
throw new CommandException(t("commands.scoreboard.teams.add.tooLong", teamName, 16));
}
String displayName = args.<String>getOne("display-name").orElse(null);
if (displayName != null && displayName.length() > 32) {
throw new CommandException(t("commands.scoreboard.teams.add.displayTooLong", displayName, 32));
}
Team.Builder teamBuilder = Team.builder()
.name(teamName);
if (displayName != null && !displayName.isEmpty()) {
teamBuilder.displayName(Text.of(displayName));
}
scoreboard.registerTeam(teamBuilder.build());
src.sendMessage(t("commands.scoreboard.teams.add.success", teamName));
return CommandResult.success();
})
.build(), "add")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("name"))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
String teamName = args.<String>getOne("name").get();
final Team team = scoreboard.getTeam(teamName).orElseThrow(
() -> new CommandException(t("commands.scoreboard.teamNotFound", teamName)));
team.unregister();
src.sendMessage(t("commands.scoreboard.teams.remove.success", teamName));
return CommandResult.success();
})
.build(), "remove")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("name"))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
String teamName = args.<String>getOne("name").get();
final Team team = scoreboard.getTeam(teamName).orElseThrow(
() -> new CommandException(t("commands.scoreboard.teamNotFound", teamName)));
Set<Text> members = team.getMembers();
if (members.isEmpty()) {
throw new CommandException(t("commands.scoreboard.teams.empty.alreadyEmpty", teamName));
}
int result = members.size();
((LanternTeam) team).removeMembers(members);
src.sendMessage(t("commands.scoreboard.teams.empty.success", result, teamName));
return CommandResult.builder().successCount(1).queryResult(result).build();
})
.build(), "empty")
.child(CommandSpec.builder()
.arguments(
GenericArguments.optional(GenericArguments.string(Text.of("name"))),
GenericArguments.optional(GenericArguments2.remainingStringArray(Text.of("players")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
String teamName = args.<String>getOne("name").orElse(null);
final Team team = teamName == null ? null : scoreboard.getTeam(teamName).orElse(null);
Set<Text> members = args.<String[]>getOne("players")
.map(array -> Arrays.stream(array).map(LanternTexts::fromLegacy).collect(Collectors.toSet()))
.orElse(new HashSet<>());
// The team doesn't exist, then assume that the team name a player is
// that wants to leave a team
if (teamName != null && team == null) {
members.add(LanternTexts.fromLegacy(teamName));
}
// If there are no members found, use the source if possible
if (members.isEmpty() && src instanceof Player) {
members.add(((Player) src).getTeamRepresentation());
}
// If there is a team specified, remove the members from a specific team
Collection<Team> teams = team == null ? scoreboard.getTeams() : Collections.singleton(team);
List<Text> failedMembers = null;
for (Team team0 : teams) {
List<Text> failedMembers0 = ((LanternTeam) team0).removeMembers(members);
if (failedMembers == null) {
failedMembers = failedMembers0;
} else {
failedMembers.retainAll(failedMembers0);
}
}
if (failedMembers != null) {
members.removeAll(failedMembers);
}
int result = members.size();
if (result > 0) {
src.sendMessage(t("commands.scoreboard.teams.leave.success",
result, Text.joinWith(Text.of(", "), members)));
}
if (failedMembers != null && failedMembers.size() > 0) {
src.sendMessage(error(t("commands.scoreboard.teams.leave.failure",
failedMembers.size(), Text.joinWith(Text.of(", "), failedMembers))));
}
return CommandResult.builder().successCount(1).queryResult(result).build();
})
.build(), "leave")
.child(CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("name")),
GenericArguments.optional(GenericArguments2.remainingStringArray(Text.of("players")))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
String teamName = args.<String>getOne("name").get();
final Team team = scoreboard.getTeam(teamName).orElseThrow(
() -> new CommandException(t("commands.scoreboard.teamNotFound", teamName)));
Set<Text> members = args.<String[]>getOne("players")
.map(array -> Arrays.stream(array).map(LanternTexts::fromLegacy).collect(Collectors.toSet()))
.orElse(new HashSet<>());
// If there are no members found, use the source if possible
if (members.isEmpty() && src instanceof Player) {
members.add(((Player) src).getTeamRepresentation());
}
List<Text> failedMembers = ((LanternTeam) team).addMembers(members);
int result = members.size();
if (result > 0) {
src.sendMessage(t("commands.scoreboard.teams.join.success",
result, teamName, Text.joinWith(Text.of(", "), members)));
}
if (failedMembers.size() > 0) {
src.sendMessage(error(t("commands.scoreboard.teams.join.failure",
failedMembers.size(), teamName, Text.joinWith(Text.of(", "), failedMembers))));
}
return CommandResult.builder().successCount(1).queryResult(result).build();
})
.build(), "join")
.build(), "teams");
}
private static CommandSpec createPlayerScoreSpec(BiConsumer<Score, Integer> scoreConsumer) {
return CommandSpec.builder()
.arguments(
GenericArguments.string(Text.of("target")),
GenericArguments.string(Text.of("objective")),
GenericArguments.integer(Text.of("score"))
)
.executor((src, args) -> {
// Get the scoreboard of the world the command source is located in
final Scoreboard scoreboard = CommandHelper.getWorld(src, args).getScoreboard();
String objectiveName = args.<String>getOne("objective").get();
Objective objective = scoreboard.getObjective(objectiveName).orElseThrow(
() -> new CommandException(t("commands.scoreboard.objectiveNotFound", objectiveName)));
String target = args.<String>getOne("target").get();
Collection<Score> scores;
if (target.equals("*")) {
scores = objective.getScores().values();
} else {
scores = Collections.singletonList(objective.getOrCreateScore(Text.of(target)));
}
int value = args.<Integer>getOne("score").get();
scores.forEach(score -> {
scoreConsumer.accept(score, value);
src.sendMessage(t("commands.scoreboard.players.set.success",
objectiveName, ((LanternScore) score).getLegacyName(), score.getScore()));
});
return CommandResult.success();
})
.build();
}
}