package tc.oc.pgm.score; import static com.google.common.base.Preconditions.checkState; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; import org.bukkit.ChatColor; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.util.Vector; import tc.oc.pgm.api.event.CoarsePlayerMoveEvent; import tc.oc.pgm.api.event.PlayerItemTransferEvent; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; import tc.oc.pgm.api.match.MatchScope; import tc.oc.pgm.api.party.Competitor; import tc.oc.pgm.api.party.event.CompetitorScoreChangeEvent; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.player.ParticipantState; import tc.oc.pgm.api.player.event.MatchPlayerDeathEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.util.chat.Sound; import tc.oc.pgm.util.collection.DefaultMapAdapter; import tc.oc.pgm.util.material.matcher.SingleMaterialMatcher; import tc.oc.pgm.util.named.NameStyle; @ListenerScope(MatchScope.RUNNING) public class ScoreMatchModule implements MatchModule, Listener { private final Match match; private final ScoreConfig config; private final Set<ScoreBox> scoreBoxes; private final Map<Competitor, Double> scores = new DefaultMapAdapter<>(new HashMap<>(), 0d); public ScoreMatchModule(Match match, ScoreConfig config, Set<ScoreBox> scoreBoxes) { this.match = match; this.config = config; this.scoreBoxes = scoreBoxes; } @Override public void load() { match.addVictoryCondition(new ScoreVictoryCondition()); } public boolean hasScoreLimit() { return this.config.scoreLimit > 0; } public int getScoreLimit() { checkState(hasScoreLimit()); return this.config.scoreLimit; } public double getScore(Competitor competitor) { return this.scores.get(competitor); } /** Gets the score message for the match. */ public String getScoreMessage() { List<String> scores = Lists.newArrayList(); for (Entry<Competitor, Double> scorePair : this.scores.entrySet()) { scores.add(scorePair.getKey().getColor().toString() + ((int) (double) scorePair.getValue())); } return ChatColor.DARK_AQUA + "Score: " + Joiner.on(" ").join(scores); } /** Gets the status message for the match. */ public String getStatusMessage() { StringBuilder message = new StringBuilder(this.getScoreMessage()); if (this.config.scoreLimit > 0) { message .append(" ") .append(ChatColor.GRAY) .append("[") .append(this.config.scoreLimit) .append("]"); } return message.toString(); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void incrementDeath(MatchPlayerDeathEvent event) { if (!event.getVictim().isParticipating()) return; // add +1 to killer's team if it was a kill, otherwise -1 to victim's team if (event.isChallengeKill()) { this.incrementScore(event.getKiller().getParty(), this.config.killScore); } else { this.incrementScore(event.getVictim().getCompetitor(), -this.config.deathScore); } } private double redeemItems(ScoreBox box, ItemStack stack) { if (stack == null) return 0; double points = 0; for (Entry<SingleMaterialMatcher, Double> entry : box.getRedeemables().entrySet()) { if (entry.getKey().matches(stack.getData())) { points += entry.getValue() * stack.getAmount(); stack.setAmount(0); } } return points; } private double redeemItems(ScoreBox box, ItemStack[] stacks) { double total = 0; for (int i = 0; i < stacks.length; i++) { double points = redeemItems(box, stacks[i]); if (points != 0) stacks[i] = null; total += points; } return total; } private double redeemItems(ScoreBox box, PlayerInventory inventory) { ItemStack[] notArmor = inventory.getContents(); ItemStack[] armor = inventory.getArmorContents(); double points = redeemItems(box, notArmor) + redeemItems(box, armor); if (points != 0) { inventory.setContents(notArmor); inventory.setArmorContents(armor); } return points; } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void playerEnterBox(CoarsePlayerMoveEvent event) { MatchPlayer player = this.match.getPlayer(event.getPlayer()); if (player == null || !player.canInteract() || player.getBukkit().isDead()) return; ParticipantState playerState = player.getParticipantState(); Vector from = event.getBlockFrom().toVector(); Vector to = event.getBlockTo().toVector(); for (ScoreBox box : this.scoreBoxes) { if (box.getRegion().enters(from, to) && box.canScore(playerState)) { if (box.isCoolingDown(playerState)) { match .getLogger() .warning( playerState.getId() + " tried to score multiple times in one second (from=" + from + " to=" + to + ")"); } else { this.playerScore(box, player, box.getScore() + redeemItems(box, player.getInventory())); } } } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void playerAcquireRedeemableInBox(final PlayerItemTransferEvent event) { if (!event.isAcquiring()) return; final MatchPlayer player = this.match.getPlayer(event.getPlayer()); if (player == null || !player.canInteract() || player.getBukkit().isDead()) return; for (final ScoreBox box : this.scoreBoxes) { if (!box.getRedeemables().isEmpty() && box.getRegion().contains(player.getBukkit()) && box.canScore(player.getParticipantState())) { match .getExecutor(MatchScope.RUNNING) .execute( () -> { if (player.getBukkit().isOnline()) { double points = redeemItems(box, player.getInventory()); ScoreMatchModule.this.playerScore(box, player, points); } }); } } } private void playerScore(ScoreBox box, MatchPlayer player, double points) { checkState(player.isParticipating()); if (points == 0) return; this.incrementScore(player.getCompetitor(), points); box.setLastScoreTime(player.getState(), Instant.now()); int wholePoints = (int) points; if (wholePoints < 1) return; match.sendMessage( TranslatableComponent.of( "scorebox.scored", player.getName(NameStyle.COLOR), TranslatableComponent.of( wholePoints == 1 ? "misc.point" : "misc.points", TextComponent.of(Integer.toString(wholePoints), TextColor.DARK_AQUA)), player.getParty().getName())); player.playSound(new Sound("random.levelup")); } public void incrementScore(Competitor competitor, double amount) { double oldScore = this.scores.get(competitor); double newScore = oldScore + amount; if (this.config.scoreLimit > 0 && newScore > this.config.scoreLimit) { newScore = this.config.scoreLimit; } CompetitorScoreChangeEvent event = new CompetitorScoreChangeEvent(competitor, oldScore, newScore); this.match.callEvent(event); this.scores.put(competitor, event.getNewScore()); this.match.calculateVictory(); } }