package tc.oc.pgm.ffa;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Range;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.Sound;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import tc.oc.api.docs.PlayerId;
import tc.oc.commons.bukkit.nick.PlayerIdentityChangeEvent;
import tc.oc.commons.bukkit.util.NullCommandSender;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.util.Optionals;
import tc.oc.commons.core.util.UsageCollection;
import tc.oc.commons.bukkit.chat.Links;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.events.PartyRenameEvent;
import tc.oc.pgm.events.PlayerPartyChangeEvent;
import tc.oc.pgm.join.JoinAllowed;
import tc.oc.pgm.join.JoinConfiguration;
import tc.oc.pgm.join.JoinDenied;
import tc.oc.pgm.join.JoinHandler;
import tc.oc.pgm.join.JoinMatchModule;
import tc.oc.pgm.join.JoinMethod;
import tc.oc.pgm.join.JoinRequest;
import tc.oc.pgm.join.JoinResult;
import tc.oc.pgm.join.QueuedParticipants;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.start.StartMatchModule;
import tc.oc.pgm.start.UnreadyReason;
import static tc.oc.commons.core.util.Utils.getInstanceOf;
@ListenerScope(MatchScope.LOADED)
public class FreeForAllMatchModule extends MatchModule implements Listener, JoinHandler {
// 10 different colors that tributes are allowed to have
private static final ImmutableList<ChatColor> COLORS = ImmutableList.of(
ChatColor.RED,
ChatColor.BLUE,
ChatColor.GREEN,
ChatColor.YELLOW,
ChatColor.LIGHT_PURPLE,
ChatColor.GOLD,
ChatColor.DARK_GREEN,
ChatColor.DARK_AQUA,
ChatColor.DARK_PURPLE,
ChatColor.DARK_RED
);
class NeedMorePlayers implements UnreadyReason {
final int players;
NeedMorePlayers(int players) {
this.players = players;
}
@Override
public BaseComponent getReason() {
if(players == 1) {
return new TranslatableComponent("start.needMorePlayers.ffa.singular",
new Component(String.valueOf(players), ChatColor.AQUA));
} else {
return new TranslatableComponent("start.needMorePlayers.ffa.plural",
new Component(String.valueOf(players), ChatColor.AQUA));
}
}
@Override
public boolean canForceStart() {
return true;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{players=" + players + "}";
}
};
@Inject private Tribute.Factory tributeFactory;
@Inject private FreeForAllOptions options;
@Inject private JoinConfiguration joinConfiguration;
private JoinMatchModule jmm;
private @Nullable Integer minPlayers, maxPlayers, maxOverfill;
private int minPlayersNeeded = Integer.MAX_VALUE;
private final Map<PlayerId, Tribute> tributes = new HashMap<>();
private final UsageCollection<ChatColor> colors;
public FreeForAllMatchModule(Match match) {
super(match);
this.colors = new UsageCollection<>(match.getRandom(), COLORS);
}
public FreeForAllOptions getOptions() {
return options;
}
public int getMinPlayers() {
return minPlayers != null ? minPlayers : options.minPlayers;
}
public int getMaxPlayers() {
return maxPlayers != null ? maxPlayers : options.maxPlayers;
}
public int getMaxOverfill() {
return maxOverfill != null ? maxOverfill : options.maxOverfill;
}
private void updatePlayerLimits() {
getMatch().setPlayerLimits(Range.closed(getMinPlayers(), getMaxPlayers()));
}
public void setMinPlayers(@Nullable Integer minPlayers) {
this.minPlayers = minPlayers;
updatePlayerLimits();
updateReadiness();
}
public void setMaxPlayers(@Nullable Integer maxPlayers, @Nullable Integer maxOverfill) {
this.maxPlayers = maxPlayers;
this.maxOverfill = maxOverfill;
updatePlayerLimits();
}
@Override
public void load() {
super.load();
jmm = match.needMatchModule(JoinMatchModule.class);
jmm.registerHandler(this);
updatePlayerLimits();
updateReadiness();
}
protected void updateReadiness() {
if(getMatch().hasStarted()) return;
int players = 0;
for(Competitor competitor : getMatch().getCompetitors()) {
if(competitor instanceof Tribute) {
players += competitor.getPlayers().size();
}
}
int playersNeeded = getMinPlayers() - players;
final StartMatchModule smm = getMatch().needMatchModule(StartMatchModule.class);
if(playersNeeded > 0) {
smm.addUnreadyReason(new NeedMorePlayers(playersNeeded));
} else {
smm.removeUnreadyReason(NeedMorePlayers.class);
// Whenever playersNeeded reaches a new minimum, reset the unready timeout
if(playersNeeded < minPlayersNeeded) {
minPlayersNeeded = playersNeeded;
smm.restartUnreadyTimeout();
}
}
}
protected Optional<Tribute> tryTribute(MatchPlayer player) {
return Optional.ofNullable(tributes.get(player.getPlayerId()));
}
protected Tribute getTribute(MatchPlayer player) {
return tryTribute(player).orElseGet(() -> {
final Tribute tribute = tributeFactory.create(player, options.colors ? colors.next() : null);
tributes.put(player.getPlayerId(), tribute);
logger.fine("Created " + tribute);
return tribute;
});
}
protected boolean canPriorityKick(MatchPlayer joining) {
if(!jmm.canPriorityKick(joining)) return false;
for(MatchPlayer player : getMatch().getParticipatingPlayers()) {
if(!jmm.canPriorityKick(player)) return true;
}
return false;
}
protected boolean priorityKick(MatchPlayer joining) {
if(!jmm.canPriorityKick(joining)) return false;
List<MatchPlayer> kickable = new ArrayList<>();
for(MatchPlayer player : getMatch().getParticipatingPlayers()) {
if(!jmm.canPriorityKick(player)) kickable.add(player);
}
if(kickable.isEmpty()) return false;
MatchPlayer kickMe = kickable.get(getMatch().getRandom().nextInt(kickable.size()));
kickMe.sendWarning(new TranslatableComponent("gameplay.ffa.kickedForPremium"), false);
kickMe.sendMessage(Links.shopPlug("shop.plug.ffa.neverKicked"));
kickMe.playSound(Sound.ENTITY_VILLAGER_HURT, kickMe.getBukkit().getLocation(), 1, 1);
getMatch().setPlayerParty(kickMe, getMatch().getDefaultParty());
return true;
}
@Override
public @Nullable JoinResult queryJoin(MatchPlayer joining, JoinRequest request) {
if(Optionals.isInstance(joining.partyMaybe(), Tribute.class)) {
return JoinDenied.error("command.gameplay.join.alreadyJoined");
}
final Optional<Tribute> chosen = getInstanceOf(request.competitor(), Tribute.class);
if(chosen.isPresent() && !chosen.get().getPlayerId().equals(joining.getPlayerId())) {
return null;
}
if(request.method() == JoinMethod.USER) {
int players = getMatch().getParticipatingPlayers().size();
if(jmm.canJoinFull(joining)) {
if(players >= getMaxOverfill()) {
if(canPriorityKick(joining)) {
return JoinAllowed.auto(true);
} else {
return JoinDenied.unavailable("autoJoin.matchFull");
}
}
} else {
if(players >= getMaxPlayers()) {
return JoinDenied.unavailable("autoJoin.matchFull")
.also(Links.shopPlug("shop.plug.ffa.joinFull"));
}
}
}
return JoinAllowed.auto(false);
}
@Override
public boolean join(MatchPlayer joining, JoinRequest request, JoinResult result) {
if(result.isAllowed()) {
if(!forceJoin(joining)) {
return false;
}
if(result.priorityKickRequired()) {
priorityKick(joining);
}
return true;
}
return false;
}
@Override
public void queuedJoin(QueuedParticipants queue) {
final JoinRequest request = new JoinRequest(JoinMethod.USER, null);
for(MatchPlayer player : queue.getOrderedPlayers()) {
join(player, request, queryJoin(player, request));
}
}
public boolean forceJoin(MatchPlayer joining) {
if(Optionals.isInstance(joining.partyMaybe(), Tribute.class)) {
joining.sendWarning(new TranslatableComponent("command.gameplay.join.alreadyJoined"), false);
}
return getMatch().setPlayerParty(joining, getTribute(joining));
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPartyChange(PlayerPartyChangeEvent event) {
if(event.getNewParty() instanceof Tribute) {
event.getPlayer().sendMessage(new TranslatableComponent("ffa.join"));
}
updateReadiness();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onIdentityChange(PlayerIdentityChangeEvent event) {
MatchPlayer player = getMatch().getPlayer(event.getPlayer());
if(player != null && player.getParty() instanceof Tribute) {
getMatch().callEvent(new PartyRenameEvent(player.getParty(),
event.getOldIdentity().getName(NullCommandSender.INSTANCE),
event.getNewIdentity().getName(NullCommandSender.INSTANCE)));
}
}
}