/*
* Project: UHC
* Class: gg.uhc.uhc.modules.team.RandomTeamsCommand
*
* The MIT License (MIT)
*
* Copyright (c) 2015 Graham Howden <graham_howden1 at yahoo.co.uk>.
*
* 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 gg.uhc.uhc.modules.team;
import gg.uhc.flagcommands.converters.IntegerConverter;
import gg.uhc.flagcommands.converters.OfflinePlayerConverter;
import gg.uhc.flagcommands.converters.OnlinePlayerConverter;
import gg.uhc.flagcommands.joptsimple.ArgumentAcceptingOptionSpec;
import gg.uhc.flagcommands.joptsimple.OptionSet;
import gg.uhc.flagcommands.joptsimple.OptionSpec;
import gg.uhc.flagcommands.predicates.IntegerPredicates;
import gg.uhc.flagcommands.tab.FixedValuesTabComplete;
import gg.uhc.flagcommands.tab.NonDuplicateTabComplete;
import gg.uhc.flagcommands.tab.OnlinePlayerTabComplete;
import gg.uhc.uhc.commands.TemplatedOptionCommand;
import gg.uhc.uhc.messages.MessageTemplates;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Team;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RandomTeamsCommand extends TemplatedOptionCommand {
protected final TeamModule module;
protected final OptionSpec<Player> playersSpec;
protected final ArgumentAcceptingOptionSpec<Integer> teamCountSpec;
protected final ArgumentAcceptingOptionSpec<Integer> teamSizeSpec;
protected final ArgumentAcceptingOptionSpec<OfflinePlayer> excludingSpec;
protected final OptionSpec<Void> excludeExtrasSpec;
protected final Predicate<OfflinePlayer> playerHasTeam = new Predicate<OfflinePlayer>() {
@Override
public boolean apply(OfflinePlayer input) {
return module.getScoreboard().getPlayerTeam(input) != null;
}
};
public RandomTeamsCommand(MessageTemplates messages, TeamModule module) {
super(messages);
this.module = module;
playersSpec = parser
.nonOptions("Players to put into random teams, leave empty for all players online")
.withValuesConvertedBy(new OnlinePlayerConverter());
nonOptionsTabComplete = new NonDuplicateTabComplete(OnlinePlayerTabComplete.INSTANCE);
excludeExtrasSpec = parser
.acceptsAll(
ImmutableList.of("x"),
"Players who don't fit into a team will be excluded instead of put into their own team"
);
teamCountSpec = parser
.acceptsAll(
ImmutableList.of("c", "count"),
"How many teams to create, teams will be as even as possible. Cannot be used with -s"
)
.withRequiredArg()
.withValuesConvertedBy(
new IntegerConverter()
.setPredicate(IntegerPredicates.GREATER_THAN_ZERO)
.setType("Integer > 0")
);
completers.put(teamCountSpec, new FixedValuesTabComplete("4"));
teamSizeSpec = parser
.acceptsAll(
ImmutableList.of("s", "size"),
"How big to attempt make each team. The final team may have less members."
+ " Cannot be used with -c"
)
.withRequiredArg()
.withValuesConvertedBy(
new IntegerConverter()
.setPredicate(IntegerPredicates.GREATER_THAN_ZERO)
.setType("Integer > 0")
);
completers.put(teamSizeSpec, new FixedValuesTabComplete("3"));
excludingSpec = parser
.acceptsAll(
ImmutableList.of("e", "exclude"),
"List of players to exclude from selection separated with commas"
)
.withRequiredArg()
.withValuesSeparatedBy(",")
.withValuesConvertedBy(new OfflinePlayerConverter());
completers.put(excludingSpec, new NonDuplicateTabComplete(OnlinePlayerTabComplete.INSTANCE));
}
@Override
protected boolean runCommand(CommandSender sender, OptionSet options) {
int size = -1;
int count = -1;
if (options.has(teamCountSpec)) {
count = teamCountSpec.value(options);
}
if (options.has(teamSizeSpec)) {
size = teamSizeSpec.value(options);
}
if ((size == -1 && count == -1) || (size != -1 && count != -1)) {
sender.sendMessage(messages.getRaw("count or size"));
return true;
}
Set<Player> choice = Sets.newHashSet(playersSpec.values(options));
// if none are provided then add all online players
if (choice.size() == 0) {
choice = Sets.newHashSet(Bukkit.getOnlinePlayers());
}
// parse excludes into online players
final Set<Player> excludes = Sets.newHashSet(
Iterables.filter(
Iterables.transform(
excludingSpec.values(options),
FunctionalUtil.ONLINE_VERSION
),
Predicates.notNull()
)
);
// final list with excludes removed and players that already in a team
final List<Player> toAssign = Lists.newArrayList(
Iterables.filter(
Sets.difference(choice, excludes),
Predicates.not(playerHasTeam)
)
);
if (toAssign.size() == 0) {
sender.sendMessage(messages.getRaw("no players"));
return true;
}
Collections.shuffle(toAssign);
// calculate team sizes to fit in count teams and assign it to size and carry on as if it was a sized command
if (count != -1) {
size = (int) Math.ceil((double) toAssign.size() / (double) count);
}
// partition into teams
final List<List<Player>> teams = Lists.newArrayList(Lists.partition(toAssign, size));
final int extras = toAssign.size() % size;
// if we're excluding leftovers and there were leftovers in a final
// team, then remove that team from the list. If it's the only team
// then send a message saying no teams could be created.
if (options.has(excludeExtrasSpec) && extras > 0) {
if (teams.size() == 1) {
sender.sendMessage(messages.evalTemplate("not enough players", ImmutableMap.of("size", size)));
return true;
}
teams.remove(teams.size() - 1);
}
// start assigning teams
for (final List<Player> teamPlayers : teams) {
final Optional<Team> optional = module.findFirstEmptyTeam();
if (!optional.isPresent()) {
sender.sendMessage(messages.getRaw("not enough teams"));
return true;
}
final Team team = optional.get();
final String playerNames = Joiner
.on(", ")
.join(Iterables.transform(teamPlayers, FunctionalUtil.PLAYER_NAME_FETCHER));
final Map<String, String> context = ImmutableMap.<String, String>builder()
.put("prefix", team.getPrefix())
.put("name", team.getName())
.put("display name", team.getDisplayName())
.put("players", playerNames)
.build();
final String message = messages.evalTemplate("teamup notification", context);
// add each player
for (final Player player : teamPlayers) {
team.addPlayer(player);
player.sendMessage(message);
}
}
final Map<String, Integer> context = ImmutableMap.<String, Integer>builder()
.put("count", teams.size())
.put("size", teams.get(0).size())
.put("players", toAssign.size())
.build();
sender.sendMessage(messages.evalTemplate("created", context));
if (options.has(excludeExtrasSpec) && extras > 0) {
sender.sendMessage(messages.evalTemplate("extras notice", ImmutableMap.of("extras", extras)));
}
return true;
}
}