feat: show who reacted, and stop curiosities flooding chat
Reactions only ever showed counts, so with several reactors nobody could tell who had reacted. Naming everyone on its own line does not scale, so names now appear in three progressively larger places: a hover tooltip per button (Java only), a one-line closing summary naming the first resumo-nomes (3) and collapsing the rest to "+N", and /reacoes for the full breakdown sent privately. Reactors' names are captured at react time so the summary survives them logging off. Two separate causes of chat flooding: - every join fired its own curiosity, so a wave of joins produced one each. Automatic triggers are now rate-limited by intervalo-minimo-segundos (120); a typed /curiosidade still bypasses it. - a curiosity cost two messages. The button row is now appended to the headline instead of being broadcast separately. Also fixes "1 horas" — CuriosityFactory had its own duration formatting that missed the earlier plural fix; it now uses Msg.duration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ActvLGJApdxEAd2yfKPwqv
This commit is contained in:
@@ -59,14 +59,34 @@ change. The live numbers appear on three other surfaces instead:
|
||||
An earlier version only had the boss bar, and it read as broken — the buttons
|
||||
showed no number at all.
|
||||
|
||||
### 2. Clicks arrive late
|
||||
### 2. Who reacted, without filling the screen
|
||||
|
||||
Naming every reactor on its own chat line does not scale — five people
|
||||
reacting to five reactions is a wall of text. So the names live in three
|
||||
progressively larger places:
|
||||
|
||||
- **hover tooltip** on each button (Java only — Bedrock cannot hover)
|
||||
- **one-line closing summary** naming the first `resumo-nomes` (default 3) and
|
||||
collapsing the rest into `+N`
|
||||
- **`/reacoes`** for the full breakdown, sent privately so chat stays clean
|
||||
|
||||
The same cap applies to the mourning line, so a popular death is still one line.
|
||||
|
||||
Relatedly, a curiosity costs **one** chat message, not two — the button row is
|
||||
appended to the headline rather than broadcast separately.
|
||||
|
||||
And automatic curiosities are rate-limited by `intervalo-minimo-segundos`
|
||||
(default 120). Without it, five people joining together produced five
|
||||
curiosities back to back. A typed `/curiosidade` bypasses the limit.
|
||||
|
||||
### 3. Clicks arrive late
|
||||
|
||||
People scroll back and click minutes after a message. Reactions therefore keep
|
||||
counting for `reacao-validade-minutos` (default 15) even after the boss bar is
|
||||
gone, and the last 8 reaction sets stay in memory for that reason. Silently
|
||||
dropping a late click looks like a bug to the player.
|
||||
|
||||
### 3. Bedrock cannot click, and cannot show emoji
|
||||
### 4. Bedrock cannot click, and cannot show emoji
|
||||
|
||||
Geyser cannot deliver a chat `clickEvent` to a Bedrock client, and most emoji
|
||||
render as tofu boxes there. So every clickable interaction has a typed
|
||||
@@ -95,7 +115,7 @@ Changing a `comando` to a new name also requires adding that command to
|
||||
runtime dependency rather than a compile-time one. `/canalhandia plataformas`
|
||||
lists who is online and on which platform.
|
||||
|
||||
### 4. Names are translated by the client, not by us
|
||||
### 5. Names are translated by the client, not by us
|
||||
|
||||
Block, item and mob names are emitted as **translatable components**
|
||||
(`Component.translatable(material.translationKey())`), so a pt-BR client renders
|
||||
|
||||
@@ -48,6 +48,7 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
private BukkitTask timerTask;
|
||||
private BukkitTask milestoneTask;
|
||||
|
||||
private long lastAnnouncement;
|
||||
private int nextId = 1;
|
||||
private Reactions liveReactions;
|
||||
private GuessRound guessRound;
|
||||
@@ -63,7 +64,7 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
|
||||
CanalhandiaCommand root = new CanalhandiaCommand(this);
|
||||
for (String name : List.of("canalhandia", "curiosidade", "adivinha", "enquete", "ranking",
|
||||
"reagir", "palpite", "votar", "legal", "wow", "top", "f")) {
|
||||
"reagir", "reacoes", "palpite", "votar", "legal", "wow", "top", "f")) {
|
||||
register(name, root);
|
||||
}
|
||||
|
||||
@@ -153,14 +154,33 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
* @return false if nobody was eligible or there was nothing notable to say
|
||||
*/
|
||||
boolean announceCuriosity(Player subject) {
|
||||
return announceCuriosity(subject, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces one curiosity.
|
||||
*
|
||||
* @param subject who to talk about, or null to pick a random eligible player
|
||||
* @param automatic true for join/timer triggers, which are rate-limited so a
|
||||
* wave of simultaneous joins produces one curiosity instead
|
||||
* of one per player; false for an explicit command
|
||||
* @return false if nothing was announced
|
||||
*/
|
||||
boolean announceCuriosity(Player subject, boolean automatic) {
|
||||
if (!settings.moduleEnabled(Module.CURIOSIDADES)) {
|
||||
return false;
|
||||
}
|
||||
if (automatic && System.currentTimeMillis() - lastAnnouncement
|
||||
< settings.minGapSeconds() * 1000L) {
|
||||
return false;
|
||||
}
|
||||
Chosen chosen = pickFact(subject == null ? pickSubject() : subject);
|
||||
if (chosen == null) {
|
||||
return false;
|
||||
}
|
||||
Bukkit.broadcast(Msg.tag("Curiosidade", NamedTextColor.GOLD)
|
||||
lastAnnouncement = System.currentTimeMillis();
|
||||
|
||||
Component headline = Msg.tag("Curiosidade", NamedTextColor.GOLD)
|
||||
.append(Component.text("Sabia que o ", NamedTextColor.WHITE)
|
||||
.decoration(TextDecoration.BOLD, false))
|
||||
.append(Component.text(chosen.subject().getName(), NamedTextColor.GREEN)
|
||||
@@ -169,8 +189,17 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
.decoration(TextDecoration.BOLD, false))
|
||||
.append(chosen.fact().text().decoration(TextDecoration.BOLD, false))
|
||||
.append(Component.text("?", NamedTextColor.WHITE)
|
||||
.decoration(TextDecoration.BOLD, false)));
|
||||
openReactions();
|
||||
.decoration(TextDecoration.BOLD, false));
|
||||
|
||||
if (!settings.reactionsEnabled()) {
|
||||
Bukkit.broadcast(headline);
|
||||
return true;
|
||||
}
|
||||
// One message, not two: the buttons ride along on the same broadcast so
|
||||
// a curiosity costs a single chat entry.
|
||||
Reactions reactions = openReactions();
|
||||
broadcastPerPlatform(bedrock -> headline.append(Component.newline())
|
||||
.append(reactions.buttons(bedrock)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,16 +267,15 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/** Attaches a fresh reaction row to the message just broadcast. */
|
||||
private void openReactions() {
|
||||
if (!settings.reactionsEnabled()) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Creates a reaction set and schedules its close. The caller is responsible
|
||||
* for broadcasting {@link Reactions#buttons}, so the buttons can share a
|
||||
* message with whatever they belong to.
|
||||
*/
|
||||
private Reactions openReactions() {
|
||||
Reactions reactions = new Reactions(nextId++, settings.reactions());
|
||||
liveReactions = reactions;
|
||||
remember(reactions);
|
||||
|
||||
broadcastPerPlatform(reactions::buttons);
|
||||
reactions.show();
|
||||
|
||||
getServer().getScheduler().runTaskLater(this, () -> {
|
||||
@@ -255,12 +283,14 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
if (liveReactions == reactions) {
|
||||
liveReactions = null;
|
||||
}
|
||||
// Chat cannot be edited, so post the final count as its own line.
|
||||
// Chat cannot be edited, so the result gets one closing line — and
|
||||
// only if anyone actually reacted, to avoid noise.
|
||||
if (reactions.hasAnyVote()) {
|
||||
broadcastPerPlatform(bedrock ->
|
||||
Component.text(" ").append(reactions.tally(bedrock)));
|
||||
broadcastPerPlatform(bedrock -> Component.text(" ")
|
||||
.append(reactions.summary(bedrock, settings.summaryNames())));
|
||||
}
|
||||
}, settings.reactionWindowSeconds() * 20L);
|
||||
return reactions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,7 +419,7 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
// Delayed so the curiosity lands after the join message rather than racing it.
|
||||
getServer().getScheduler().runTaskLater(this, () -> {
|
||||
if (player.isOnline()) {
|
||||
announceCuriosity(player);
|
||||
announceCuriosity(player, true);
|
||||
}
|
||||
}, settings.joinDelaySeconds() * 20L);
|
||||
}
|
||||
@@ -404,6 +434,7 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
List.of(new ReactionDef("f", "[F]", "[F]", "f")));
|
||||
liveReactions = mourning;
|
||||
remember(mourning);
|
||||
mourning.show();
|
||||
String name = event.getEntity().getName();
|
||||
|
||||
// One tick later so it prints under the vanilla death message.
|
||||
@@ -421,10 +452,16 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
||||
}), 2L);
|
||||
|
||||
getServer().getScheduler().runTaskLater(this, () -> {
|
||||
mourning.hide();
|
||||
if (mourning.hasAnyVote()) {
|
||||
// One line, names truncated, so a busy death does not fill the screen.
|
||||
List<String> who = mourning.names("f");
|
||||
int shown = Math.min(who.size(), settings.summaryNames());
|
||||
String text = String.join(", ", who.subList(0, shown))
|
||||
+ (who.size() > shown ? " +" + (who.size() - shown) : "");
|
||||
Bukkit.broadcast(Component.text(" ")
|
||||
.append(Component.text(mourning.count("f")
|
||||
+ " pessoa(s) prestaram luto por " + name + ".", NamedTextColor.GRAY)));
|
||||
.append(Component.text(text + " prestaram luto por " + name + ".",
|
||||
NamedTextColor.GRAY)));
|
||||
}
|
||||
if (liveReactions == mourning) {
|
||||
liveReactions = null;
|
||||
|
||||
@@ -45,6 +45,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
case "reagir" -> reactLatest(sender, args.length > 0 ? args[0] : null);
|
||||
case "palpite" -> guessLatest(sender, args);
|
||||
case "votar" -> voteLatest(sender, args);
|
||||
case "reacoes" -> whoReacted(sender);
|
||||
default -> {
|
||||
String reaction = plugin.settings().reactionForCommand(command.getName());
|
||||
if (reaction != null) {
|
||||
@@ -141,6 +142,18 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
plugin.settings().reactionWindowSeconds(v);
|
||||
return "Janela de reação: " + plugin.settings().reactionWindowSeconds() + "s.";
|
||||
});
|
||||
case "resumo" -> setNumber(sender, rest, "resumo <quantidade>",
|
||||
v -> {
|
||||
plugin.settings().summaryNames(v);
|
||||
return "Mostrando até " + plugin.settings().summaryNames()
|
||||
+ " nomes no resumo.";
|
||||
});
|
||||
case "intervalo-minimo", "minimo" -> setNumber(sender, rest, "minimo <segundos>",
|
||||
v -> {
|
||||
plugin.settings().minGapSeconds(v);
|
||||
return "Intervalo mínimo entre curiosidades automáticas: "
|
||||
+ plugin.settings().minGapSeconds() + "s.";
|
||||
});
|
||||
case "validade" -> setNumber(sender, rest, "validade <minutos>",
|
||||
v -> {
|
||||
plugin.settings().reactionValidityMinutes(v);
|
||||
@@ -422,6 +435,19 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Who reacted to the last message — sent privately so chat stays clean. */
|
||||
private boolean whoReacted(CommandSender sender) {
|
||||
Reactions reactions = plugin.latestReactions();
|
||||
if (reactions == null || !reactions.hasAnyVote()) {
|
||||
Msg.error(sender, "Ninguém reagiu à última mensagem ainda.");
|
||||
return true;
|
||||
}
|
||||
boolean bedrock = sender instanceof Player player && Platform.isBedrock(player);
|
||||
Msg.header(sender, "Quem reagiu (" + reactions.total() + ")");
|
||||
reactions.breakdown(bedrock).forEach(sender::sendMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> reactionKeys() {
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (ReactionDef def : plugin.settings().reactions()) {
|
||||
@@ -609,6 +635,9 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
Msg.line(sender, "reações", settings.reactionsEnabled() ? "ativas" : "desativadas");
|
||||
Msg.line(sender, "barra de reação", settings.reactionWindowSeconds() + "s");
|
||||
Msg.line(sender, "cliques válidos por", settings.reactionValidityMinutes() + " min");
|
||||
Msg.line(sender, "nomes no resumo", settings.summaryNames() + " (resto vira +N)");
|
||||
Msg.line(sender, "intervalo mínimo", settings.minGapSeconds()
|
||||
+ "s entre curiosidades automáticas");
|
||||
StringBuilder labels = new StringBuilder();
|
||||
for (ReactionDef def : settings.reactions()) {
|
||||
labels.append(def.java()).append(' ').append(def.texto())
|
||||
@@ -653,6 +682,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
commands.put("/canalhandia modulos", "lista os módulos e seu estado");
|
||||
commands.put("/canalhandia plataformas", "quem está online e se é Java ou Bedrock");
|
||||
commands.put("/reagir <chave>", "reage à última mensagem (funciona no Bedrock)");
|
||||
commands.put("/reacoes", "mostra quem reagiu à última mensagem");
|
||||
commands.put("/palpite <nome>", "palpita na adivinha sem clicar");
|
||||
commands.put("/votar <número>", "vota na enquete sem clicar");
|
||||
if (sender.hasPermission(ADMIN)) {
|
||||
@@ -667,6 +697,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
commands.put("/curiosidade repetir <n>", "quantas recentes evitar repetir");
|
||||
commands.put("/curiosidade janela <seg>", "duração da barra de reações");
|
||||
commands.put("/curiosidade validade <min>", "por quanto tempo cliques ainda contam");
|
||||
commands.put("/curiosidade resumo <n>", "quantos nomes mostrar no resumo de reações");
|
||||
commands.put("/curiosidade minimo <seg>", "intervalo mínimo entre curiosidades automáticas");
|
||||
commands.put("/curiosidade reacoes <on|off>", "liga/desliga reações");
|
||||
commands.put("/curiosidade reacao add <chave> <rótulo>", "cria ou muda uma reação");
|
||||
commands.put("/curiosidade reacao remover <chave>", "remove uma reação");
|
||||
@@ -762,7 +794,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
||||
"status", "categorias", "ajuda"));
|
||||
if (sender.hasPermission(ADMIN)) {
|
||||
options.addAll(List.of("modo", "intervalo", "atraso", "cooldown", "repetir",
|
||||
"janela", "validade", "reacoes", "reacao", "categoria"));
|
||||
"janela", "validade", "resumo", "minimo", "reacoes", "reacao",
|
||||
"categoria"));
|
||||
}
|
||||
options.addAll(onlineNames());
|
||||
return filter(options, args[0]);
|
||||
|
||||
@@ -137,15 +137,12 @@ final class CuriosityFactory {
|
||||
private static void time(List<Fact> out, Player player, Statistic statistic,
|
||||
String prefix, String suffix) {
|
||||
int ticks = Stats.untyped(player, statistic);
|
||||
long hours = ticks / 20L / 3600L;
|
||||
if (hours < 1) {
|
||||
if (ticks / 20L / 3600L < 1) {
|
||||
return;
|
||||
}
|
||||
String text = hours >= 24
|
||||
? String.format(PT_BR, "%d dias e %d horas", hours / 24, hours % 24)
|
||||
: hours + " horas";
|
||||
// Msg.duration handles the pt-BR plural agreement ("1 hora", "2 horas").
|
||||
out.add(new Fact(Category.TEMPO, Component.text(prefix, NamedTextColor.WHITE)
|
||||
.append(Component.text(text, NamedTextColor.AQUA))
|
||||
.append(Component.text(Msg.duration(ticks), NamedTextColor.AQUA))
|
||||
.append(Component.text(suffix, NamedTextColor.WHITE))));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,31 +8,34 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Reaction state for one announced message.
|
||||
*
|
||||
* <p>Chat cannot be edited after sending, so the counts inside the buttons are
|
||||
* frozen at send time. Three other surfaces carry the live numbers: a boss bar
|
||||
* while the window is open, an action bar shown to whoever just reacted, and a
|
||||
* final tally line when the window closes. Reactions keep being accepted after
|
||||
* the boss bar disappears, because people scroll back and click late.
|
||||
* frozen at send time. Live numbers appear on the boss bar, on the reactor's
|
||||
* action bar, and in a one-line summary when the window closes.
|
||||
*
|
||||
* <p>Bedrock clients get a different rendering: ASCII labels instead of emoji,
|
||||
* and a typed-command hint instead of clickable text, because Geyser cannot
|
||||
* deliver a chat {@code clickEvent} to Bedrock.
|
||||
* <p>Who reacted is deliberately <em>not</em> given its own chat line — with
|
||||
* several reactions and several players that would fill the screen. Instead the
|
||||
* names live in a hover tooltip on Java, and behind {@code /reacoes} for
|
||||
* everyone (Bedrock cannot hover either). The closing summary shows the first
|
||||
* couple of names inline and abbreviates the rest as "+N".
|
||||
*
|
||||
* <p>Bedrock also cannot run a chat {@code clickEvent} and renders most emoji
|
||||
* as tofu, so it gets ASCII labels and typed commands.
|
||||
*/
|
||||
final class Reactions {
|
||||
|
||||
private final int id;
|
||||
private final List<ReactionDef> defs;
|
||||
private final Map<String, Set<UUID>> votes = new LinkedHashMap<>();
|
||||
/** Reaction key to reactors, preserving both order and display name. */
|
||||
private final Map<String, LinkedHashMap<UUID, String>> votes = new LinkedHashMap<>();
|
||||
private final BossBar bar;
|
||||
private final long createdAt = System.currentTimeMillis();
|
||||
private boolean barVisible;
|
||||
@@ -40,7 +43,7 @@ final class Reactions {
|
||||
Reactions(int id, List<ReactionDef> defs) {
|
||||
this.id = id;
|
||||
this.defs = defs;
|
||||
defs.forEach(def -> votes.put(def.key(), new LinkedHashSet<>()));
|
||||
defs.forEach(def -> votes.put(def.key(), new LinkedHashMap<>()));
|
||||
this.bar = BossBar.bossBar(tally(false), 1.0f, BossBar.Color.PURPLE, BossBar.Overlay.PROGRESS);
|
||||
}
|
||||
|
||||
@@ -53,11 +56,7 @@ final class Reactions {
|
||||
}
|
||||
|
||||
boolean hasAnyVote() {
|
||||
return votes.values().stream().anyMatch(set -> !set.isEmpty());
|
||||
}
|
||||
|
||||
boolean knows(String key) {
|
||||
return votes.containsKey(key);
|
||||
return votes.values().stream().anyMatch(map -> !map.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,8 +69,9 @@ final class Reactions {
|
||||
if (!votes.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
votes.values().forEach(set -> set.remove(player.getUniqueId()));
|
||||
votes.get(key).add(player.getUniqueId());
|
||||
votes.values().forEach(map -> map.remove(player.getUniqueId()));
|
||||
// Names are captured now so the summary still works if someone logs off.
|
||||
votes.get(key).put(player.getUniqueId(), player.getName());
|
||||
if (barVisible) {
|
||||
bar.name(tally(false));
|
||||
}
|
||||
@@ -80,25 +80,32 @@ final class Reactions {
|
||||
}
|
||||
|
||||
int count(String key) {
|
||||
Set<UUID> set = votes.get(key);
|
||||
return set == null ? 0 : set.size();
|
||||
Map<UUID, String> map = votes.get(key);
|
||||
return map == null ? 0 : map.size();
|
||||
}
|
||||
|
||||
List<String> names(String key) {
|
||||
Map<UUID, String> map = votes.get(key);
|
||||
return map == null ? List.of() : new ArrayList<>(map.values());
|
||||
}
|
||||
|
||||
int total() {
|
||||
return votes.values().stream().mapToInt(Map::size).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* The reaction row. On Java the labels are clickable; on Bedrock they are
|
||||
* plain text followed by the commands to type, since clicking does nothing
|
||||
* there.
|
||||
* The reaction row. Java gets clickable labels whose tooltip lists who
|
||||
* reacted; Bedrock gets plain labels plus the commands to type.
|
||||
*/
|
||||
Component buttons(boolean bedrock) {
|
||||
Component row = Component.text(" ");
|
||||
for (ReactionDef def : defs) {
|
||||
String text = def.label(bedrock) + " " + count(def.key()) + " ";
|
||||
Component piece = Component.text(text, NamedTextColor.YELLOW);
|
||||
Component piece = Component.text(def.label(bedrock) + " " + count(def.key()) + " ",
|
||||
NamedTextColor.YELLOW);
|
||||
if (!bedrock) {
|
||||
piece = piece
|
||||
.clickEvent(ClickEvent.runCommand("/canalhandia reagir " + id + " " + def.key()))
|
||||
.hoverEvent(HoverEvent.showText(Component.text(
|
||||
"Clique para reagir com " + def.java(), NamedTextColor.GRAY)));
|
||||
.hoverEvent(HoverEvent.showText(hover(def)));
|
||||
}
|
||||
row = row.append(piece);
|
||||
}
|
||||
@@ -108,6 +115,14 @@ final class Reactions {
|
||||
return row;
|
||||
}
|
||||
|
||||
private Component hover(ReactionDef def) {
|
||||
List<String> names = names(def.key());
|
||||
if (names.isEmpty()) {
|
||||
return Component.text("Clique para reagir com " + def.java(), NamedTextColor.GRAY);
|
||||
}
|
||||
return Component.text(String.join(", ", names), NamedTextColor.YELLOW);
|
||||
}
|
||||
|
||||
/** Comma-joined typed shortcuts, e.g. "/legal, /wow, /top". */
|
||||
String shortcuts() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
@@ -120,7 +135,7 @@ final class Reactions {
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
/** Live counts, used for the boss bar, action bar and closing line. */
|
||||
/** Compact live counts, for the boss bar and action bar. */
|
||||
Component tally(boolean bedrock) {
|
||||
Component text = Component.text("Reações: ", NamedTextColor.WHITE);
|
||||
for (ReactionDef def : defs) {
|
||||
@@ -130,6 +145,62 @@ final class Reactions {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-line closing summary naming the reactors, e.g.
|
||||
* {@code Reações: [+1] NegoncioZ, Guzada · [UAU] Larieus2 +2}.
|
||||
*
|
||||
* <p>Only reactions that actually got votes are listed, and at most
|
||||
* {@code maxNames} names appear in total, so this never grows past a line
|
||||
* however many people react.
|
||||
*/
|
||||
Component summary(boolean bedrock, int maxNames) {
|
||||
Component text = Component.text("Reações: ", NamedTextColor.WHITE);
|
||||
int shown = 0;
|
||||
boolean first = true;
|
||||
for (ReactionDef def : defs) {
|
||||
List<String> names = names(def.key());
|
||||
if (names.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!first) {
|
||||
text = text.append(Component.text(" · ", NamedTextColor.DARK_GRAY));
|
||||
}
|
||||
first = false;
|
||||
text = text.append(Component.text(def.label(bedrock) + " ", NamedTextColor.YELLOW));
|
||||
|
||||
List<String> visible = new ArrayList<>();
|
||||
int hidden = 0;
|
||||
for (String name : names) {
|
||||
if (shown < maxNames) {
|
||||
visible.add(name);
|
||||
shown++;
|
||||
} else {
|
||||
hidden++;
|
||||
}
|
||||
}
|
||||
text = text.append(Component.text(String.join(", ", visible), NamedTextColor.GREEN));
|
||||
if (hidden > 0) {
|
||||
text = text.append(Component.text(" +" + hidden, NamedTextColor.GRAY));
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/** Full per-reaction breakdown, sent privately by /reacoes so chat stays clean. */
|
||||
List<Component> breakdown(boolean bedrock) {
|
||||
List<Component> lines = new ArrayList<>();
|
||||
for (ReactionDef def : defs) {
|
||||
List<String> names = names(def.key());
|
||||
if (names.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
lines.add(Component.text(" " + def.label(bedrock) + " ", NamedTextColor.YELLOW)
|
||||
.append(Component.text(names.size() + " ", NamedTextColor.AQUA))
|
||||
.append(Component.text(String.join(", ", names), NamedTextColor.GREEN)));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
void show() {
|
||||
barVisible = true;
|
||||
Bukkit.getOnlinePlayers().forEach(p -> p.showBossBar(bar));
|
||||
|
||||
@@ -106,6 +106,31 @@ final class Settings {
|
||||
set("reacao-validade-minutos", Math.max(1, minutes));
|
||||
}
|
||||
|
||||
/**
|
||||
* How many reactor names fit in the one-line closing summary before the
|
||||
* rest collapse into "+N". Keeps the summary to a single line no matter how
|
||||
* many people react.
|
||||
*/
|
||||
int summaryNames() {
|
||||
return Math.max(0, plugin.getConfig().getInt("resumo-nomes", 3));
|
||||
}
|
||||
|
||||
void summaryNames(int count) {
|
||||
set("resumo-nomes", Math.max(0, count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum gap between automatic curiosities. Without this, five people
|
||||
* joining together produce five curiosities and flood the chat.
|
||||
*/
|
||||
int minGapSeconds() {
|
||||
return Math.max(0, plugin.getConfig().getInt("intervalo-minimo-segundos", 120));
|
||||
}
|
||||
|
||||
void minGapSeconds(int seconds) {
|
||||
set("intervalo-minimo-segundos", Math.max(0, seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configured reactions.
|
||||
*
|
||||
|
||||
@@ -34,6 +34,11 @@ intervalo-minutos: 20
|
||||
# Tempo mínimo antes do mesmo jogador ser citado de novo.
|
||||
cooldown-minutos: 30
|
||||
|
||||
# Intervalo mínimo entre curiosidades automáticas, em segundos. Sem isso,
|
||||
# cinco pessoas entrando juntas geram cinco curiosidades e enchem a tela.
|
||||
# Não afeta /curiosidade digitado à mão.
|
||||
intervalo-minimo-segundos: 120
|
||||
|
||||
# Quantas curiosidades recentes lembrar para não repetir.
|
||||
evitar-repetir: 15
|
||||
|
||||
@@ -54,6 +59,11 @@ reacoes-ativas: true
|
||||
# Por quanto tempo a barra de reações fica visível, em segundos.
|
||||
janela-reacao-segundos: 90
|
||||
|
||||
# Quantos nomes cabem no resumo de reações antes do resto virar "+N".
|
||||
# O resumo é sempre UMA linha, por mais gente que reaja; para ver a lista
|
||||
# completa use /reacoes (privado, não polui o chat).
|
||||
resumo-nomes: 3
|
||||
|
||||
# Por quanto tempo um clique ainda conta depois que a barra some. As pessoas
|
||||
# rolam o chat e clicam minutos depois; descartar esses cliques em silêncio
|
||||
# parece defeito.
|
||||
|
||||
@@ -33,6 +33,10 @@ commands:
|
||||
reagir:
|
||||
description: Reage à última mensagem.
|
||||
usage: /reagir <chave>
|
||||
reacoes:
|
||||
description: Mostra quem reagiu à última mensagem.
|
||||
usage: /reacoes
|
||||
aliases: [quemreagiu]
|
||||
palpite:
|
||||
description: Palpita na adivinha aberta.
|
||||
usage: /palpite <nome>
|
||||
|
||||
Reference in New Issue
Block a user