i18n: per-player EN/PT via Adventure GlobalTranslator #1
@@ -2,9 +2,13 @@
|
|||||||
|
|
||||||
Chat-only social features for the Canalhandia Minecraft server (Paper 26.2).
|
Chat-only social features for the Canalhandia Minecraft server (Paper 26.2).
|
||||||
|
|
||||||
**Nothing here touches gameplay.** No items, no world edits, no attributes, no
|
**Almost nothing here touches gameplay** — no world edits, no attributes, no
|
||||||
economy. Everything is chat messages, boss bars and clickable buttons, and every
|
economy, and no items anywhere except one: the `luto` tribute. Pressing F to pay
|
||||||
module can be switched off independently.
|
respects drops the dead player's head into the mourner's inventory as a symbolic
|
||||||
|
memento (toggle: `luto.cabeca`). Everything else is chat messages and clickable
|
||||||
|
buttons, and every module can be switched off independently. (The reaction-count
|
||||||
|
boss bar was removed; live counts ride on the reactor's action bar and a closing
|
||||||
|
tally line.)
|
||||||
|
|
||||||
All player-facing text is Portuguese (pt-BR).
|
All player-facing text is Portuguese (pt-BR).
|
||||||
|
|
||||||
@@ -16,11 +20,11 @@ All player-facing text is Portuguese (pt-BR).
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `curiosidades` | *"Sabia que o Fulano já minerou 5.966 blocos de Pedra?"* — a fact about a player, with reaction buttons. Fires on join by default. |
|
| `curiosidades` | *"Sabia que o Fulano já minerou 5.966 blocos de Pedra?"* — a fact about a player, with reaction buttons. Fires on join by default. |
|
||||||
| `adivinha` | The same fact with the name hidden, plus clickable player names. Reveals after 45s and names who guessed right. |
|
| `adivinha` | The same fact with the name hidden, plus clickable player names. Reveals after 45s and names who guessed right. |
|
||||||
| `luto` | A clickable `[F]` under each death message, with a count when the window closes. |
|
| `luto` | A clickable `[F]` under each death message, with a count when the window closes. Pressing F drops the **dead player's head** into the mourner's inventory (once per mourner per death, never to the dead player themselves) — the one gameplay-touching feature; toggle with `luto.cabeca`. Bedrock types `/f`. |
|
||||||
| `enquete` | `/enquete Pergunta \| A \| B` — clickable voting with a live tally on the boss bar. |
|
| `enquete` | `/enquete Pergunta \| A \| B` — clickable voting with a live tally on the boss bar. |
|
||||||
| `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. |
|
| `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. |
|
||||||
| `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. |
|
| `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. |
|
||||||
| `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player, so they can run back to their dropped items. No storage, no command. |
|
| `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player on respawn (the death screen swallows chat sent during the event), so they can run back to their dropped items. Respects `keepInventory`. No storage, no command. |
|
||||||
| `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Optional wiki grounding, per-player memory, operator corrections. |
|
| `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Optional wiki grounding, per-player memory, operator corrections. |
|
||||||
|
|
||||||
Toggle any of them: `/canalhandia modulo <nome> <on|off>`
|
Toggle any of them: `/canalhandia modulo <nome> <on|off>`
|
||||||
@@ -52,14 +56,14 @@ These shaped the design, and anyone changing the code should know them before
|
|||||||
|
|
||||||
There is no vanilla way to update a message that is already in the chat log. So
|
There is no vanilla way to update a message that is already in the chat log. So
|
||||||
the counts baked into the reaction buttons are **frozen at send time** and never
|
the counts baked into the reaction buttons are **frozen at send time** and never
|
||||||
change. The live numbers appear on three other surfaces instead:
|
change. The live numbers appear on two other surfaces instead:
|
||||||
|
|
||||||
- a **boss bar** while the window is open (`janela-reacao-segundos`, default 90)
|
|
||||||
- an **action bar** shown to whoever just reacted
|
- an **action bar** shown to whoever just reacted
|
||||||
- a **final tally line** broadcast when the window closes
|
- a **final tally line** broadcast when the window closes
|
||||||
|
|
||||||
An earlier version only had the boss bar, and it read as broken — the buttons
|
(A boss bar spanning the whole reaction window was tried and removed — it sat
|
||||||
showed no number at all.
|
on screen for `janela-reacao-segundos` and read as clutter, and the two
|
||||||
|
surfaces above already carry the counts.)
|
||||||
|
|
||||||
### 2. Who reacted, without filling the screen
|
### 2. Who reacted, without filling the screen
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.GameRule;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.Statistic;
|
import org.bukkit.Statistic;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
@@ -17,6 +19,9 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
|||||||
import org.bukkit.event.entity.EntityDamageEvent;
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
@@ -28,6 +33,8 @@ import java.util.Deque;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -35,8 +42,11 @@ import java.util.UUID;
|
|||||||
* Chat-only social features for the Canalhandia server: curiosities, a guess
|
* Chat-only social features for the Canalhandia server: curiosities, a guess
|
||||||
* game, polls, mourning reactions, milestones and rankings.
|
* game, polls, mourning reactions, milestones and rankings.
|
||||||
*
|
*
|
||||||
* <p>Nothing here touches gameplay — no items, no world edits, no attributes.
|
* <p>Almost nothing here touches gameplay — no items, no world edits, no
|
||||||
* Every module can be switched off independently.
|
* attributes. The one exception is the {@code luto} tribute: pressing F to pay
|
||||||
|
* respects drops the dead player's head into the mourner's inventory, a symbolic
|
||||||
|
* memento. Toggle it with {@code luto.cabeca} in config. Every module can be
|
||||||
|
* switched off independently.
|
||||||
*/
|
*/
|
||||||
public final class Canalhandia extends JavaPlugin implements Listener {
|
public final class Canalhandia extends JavaPlugin implements Listener {
|
||||||
|
|
||||||
@@ -45,6 +55,10 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
private final Deque<String> recentFacts = new ArrayDeque<>();
|
private final Deque<String> recentFacts = new ArrayDeque<>();
|
||||||
/** Recent reaction sets, newest last, so late clicks still land. */
|
/** Recent reaction sets, newest last, so late clicks still land. */
|
||||||
private final Deque<Reactions> reactionHistory = new ArrayDeque<>();
|
private final Deque<Reactions> reactionHistory = new ArrayDeque<>();
|
||||||
|
/** Mourning tribute per reaction id: who died, and who already got the head. */
|
||||||
|
private final Map<Integer, Tribute> tributes = new ConcurrentHashMap<>();
|
||||||
|
/** Death coords awaiting delivery on the player's next respawn (see onDeathComic). */
|
||||||
|
private final Map<UUID, DeathCoords> pendingDeathCoords = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
private OfflineStats offlineStats;
|
private OfflineStats offlineStats;
|
||||||
@@ -110,9 +124,6 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
if (liveReactions != null) {
|
|
||||||
liveReactions.hide();
|
|
||||||
}
|
|
||||||
if (poll != null) {
|
if (poll != null) {
|
||||||
poll.hide();
|
poll.hide();
|
||||||
}
|
}
|
||||||
@@ -293,10 +304,8 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
Reactions reactions = new Reactions(nextId++, settings.reactions());
|
Reactions reactions = new Reactions(nextId++, settings.reactions());
|
||||||
liveReactions = reactions;
|
liveReactions = reactions;
|
||||||
remember(reactions);
|
remember(reactions);
|
||||||
reactions.show();
|
|
||||||
|
|
||||||
getServer().getScheduler().runTaskLater(this, () -> {
|
getServer().getScheduler().runTaskLater(this, () -> {
|
||||||
reactions.hide();
|
|
||||||
if (liveReactions == reactions) {
|
if (liveReactions == reactions) {
|
||||||
liveReactions = null;
|
liveReactions = null;
|
||||||
}
|
}
|
||||||
@@ -327,9 +336,7 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
new ReactionDef("errado", "[❌]", "[ERRADO]", "errado")));
|
new ReactionDef("errado", "[❌]", "[ERRADO]", "errado")));
|
||||||
liveReactions = reactions;
|
liveReactions = reactions;
|
||||||
remember(reactions);
|
remember(reactions);
|
||||||
reactions.show();
|
|
||||||
getServer().getScheduler().runTaskLater(this, () -> {
|
getServer().getScheduler().runTaskLater(this, () -> {
|
||||||
reactions.hide();
|
|
||||||
if (liveReactions == reactions) {
|
if (liveReactions == reactions) {
|
||||||
liveReactions = null;
|
liveReactions = null;
|
||||||
}
|
}
|
||||||
@@ -360,14 +367,15 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
private void remember(Reactions reactions) {
|
private void remember(Reactions reactions) {
|
||||||
reactionHistory.addLast(reactions);
|
reactionHistory.addLast(reactions);
|
||||||
while (reactionHistory.size() > 8) {
|
while (reactionHistory.size() > 8) {
|
||||||
reactionHistory.removeFirst();
|
Reactions oldest = reactionHistory.removeFirst();
|
||||||
|
tributes.remove(oldest.id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a reaction set that is still accepting clicks. The boss bar only
|
* Finds a reaction set that is still accepting clicks. The reaction window
|
||||||
* lasts {@code janela-reacao-segundos}, but people scroll back and click
|
* closes after {@code janela-reacao-segundos}, but people scroll back and
|
||||||
* minutes later, so clicks stay valid for {@code reacao-validade-minutos}.
|
* click minutes later, so clicks stay valid for {@code reacao-validade-minutos}.
|
||||||
*/
|
*/
|
||||||
Reactions findReactions(int id) {
|
Reactions findReactions(int id) {
|
||||||
long limit = settings.reactionValidityMinutes() * 60_000L;
|
long limit = settings.reactionValidityMinutes() * 60_000L;
|
||||||
@@ -454,9 +462,6 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
@EventHandler
|
@EventHandler
|
||||||
public void onJoin(PlayerJoinEvent event) {
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
if (liveReactions != null) {
|
|
||||||
liveReactions.showTo(player);
|
|
||||||
}
|
|
||||||
if (poll != null) {
|
if (poll != null) {
|
||||||
poll.showTo(player);
|
poll.showTo(player);
|
||||||
}
|
}
|
||||||
@@ -483,8 +488,10 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
List.of(new ReactionDef("f", "[F]", "[F]", "f")));
|
List.of(new ReactionDef("f", "[F]", "[F]", "f")));
|
||||||
liveReactions = mourning;
|
liveReactions = mourning;
|
||||||
remember(mourning);
|
remember(mourning);
|
||||||
mourning.show();
|
|
||||||
String name = event.getEntity().getName();
|
String name = event.getEntity().getName();
|
||||||
|
if (settings.lutoHeadReward()) {
|
||||||
|
tributes.put(mourning.id(), new Tribute(event.getEntity().getUniqueId(), name));
|
||||||
|
}
|
||||||
|
|
||||||
// One tick later so it prints under the vanilla death message.
|
// One tick later so it prints under the vanilla death message.
|
||||||
getServer().getScheduler().runTaskLater(this, () -> broadcastPerPlatform(bedrock -> {
|
getServer().getScheduler().runTaskLater(this, () -> broadcastPerPlatform(bedrock -> {
|
||||||
@@ -501,7 +508,6 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
}), 2L);
|
}), 2L);
|
||||||
|
|
||||||
getServer().getScheduler().runTaskLater(this, () -> {
|
getServer().getScheduler().runTaskLater(this, () -> {
|
||||||
mourning.hide();
|
|
||||||
if (mourning.hasAnyVote()) {
|
if (mourning.hasAnyVote()) {
|
||||||
// One line, names truncated, so a busy death does not fill the screen.
|
// One line, names truncated, so a busy death does not fill the screen.
|
||||||
List<String> who = mourning.names("f");
|
List<String> who = mourning.names("f");
|
||||||
@@ -555,21 +561,90 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
+ DeathFlavor.ordinal(shown) + " morte)", NamedTextColor.YELLOW));
|
+ DeathFlavor.ordinal(shown) + " morte)", NamedTextColor.YELLOW));
|
||||||
|
|
||||||
// Private coords to the dead player only — never broadcast, so others
|
// Private coords to the dead player only — never broadcast, so others
|
||||||
// don't learn where to loot. Java: clickable copy; Bedrock: plain text.
|
// don't learn where to loot. Delivered on respawn (not at death): the
|
||||||
|
// Java death screen swallows chat sent during PlayerDeathEvent, so
|
||||||
|
// sending it then quietly failed. Java: clickable copy; Bedrock: plain.
|
||||||
Location loc = player.getLocation();
|
Location loc = player.getLocation();
|
||||||
String coords = loc.getBlockX() + " " + loc.getBlockY() + " " + loc.getBlockZ()
|
String coords = loc.getBlockX() + " " + loc.getBlockY() + " " + loc.getBlockZ()
|
||||||
+ " (" + loc.getWorld().getName() + ")";
|
+ " (" + loc.getWorld().getName() + ")";
|
||||||
Component coordsMsg;
|
boolean keepInventory = Boolean.TRUE.equals(
|
||||||
if (Platform.isBedrock(player)) {
|
loc.getWorld().getGameRuleValue(GameRule.KEEP_INVENTORY));
|
||||||
coordsMsg = Component.text("Você morreu em " + coords + ". Corre buscar seus itens!",
|
pendingDeathCoords.put(player.getUniqueId(), new DeathCoords(coords, keepInventory));
|
||||||
NamedTextColor.AQUA);
|
}
|
||||||
} else {
|
|
||||||
coordsMsg = Component.text("Você morreu em ", NamedTextColor.AQUA)
|
/**
|
||||||
.append(Component.text(coords, NamedTextColor.WHITE)
|
* Sends the death coordinates once the player has actually respawned and
|
||||||
.clickEvent(ClickEvent.copyToClipboard(coords)))
|
* can act on them. The death screen ate the message when it was sent
|
||||||
.append(Component.text(". Corre buscar seus itens!", NamedTextColor.AQUA));
|
* synchronously during {@link PlayerDeathEvent}.
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onRespawn(PlayerRespawnEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
DeathCoords dc = pendingDeathCoords.remove(player.getUniqueId());
|
||||||
|
if (dc == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
player.sendMessage(coordsMsg);
|
String tail = dc.keepInventory() ? "" : ". Corre buscar seus itens!";
|
||||||
|
getServer().getScheduler().runTaskLater(this, () -> {
|
||||||
|
Component msg;
|
||||||
|
if (Platform.isBedrock(player)) {
|
||||||
|
msg = Component.text("Você morreu em " + dc.coords() + tail, NamedTextColor.AQUA);
|
||||||
|
} else {
|
||||||
|
msg = Component.text("Você morreu em ", NamedTextColor.AQUA)
|
||||||
|
.append(Component.text(dc.coords(), NamedTextColor.WHITE)
|
||||||
|
.clickEvent(ClickEvent.copyToClipboard(dc.coords())))
|
||||||
|
.append(Component.text(tail, NamedTextColor.AQUA));
|
||||||
|
}
|
||||||
|
player.sendMessage(msg);
|
||||||
|
}, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after any successful reaction. For the mourning {@code f} reaction
|
||||||
|
* this drops the dead player's head into the mourner's inventory — once per
|
||||||
|
* mourner per death, and never to the dead player themselves.
|
||||||
|
*/
|
||||||
|
void afterReact(Player mourner, int reactionId, String key) {
|
||||||
|
if (!"f".equals(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Tribute tribute = tributes.get(reactionId);
|
||||||
|
if (tribute == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mourner.getUniqueId().equals(tribute.deadId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tribute.rewarded.add(mourner.getUniqueId())) {
|
||||||
|
return; // already got the head for this death
|
||||||
|
}
|
||||||
|
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
||||||
|
head.editMeta(SkullMeta.class, m -> {
|
||||||
|
m.setPlayerProfile(Bukkit.createProfile(tribute.deadId, tribute.deadName));
|
||||||
|
m.displayName(Component.text("Cabeça de " + tribute.deadName, NamedTextColor.GOLD));
|
||||||
|
});
|
||||||
|
for (ItemStack overflow : mourner.getInventory().addItem(head).values()) {
|
||||||
|
mourner.getWorld().dropItemNaturally(mourner.getLocation(), overflow);
|
||||||
|
}
|
||||||
|
mourner.sendMessage(Component.text(
|
||||||
|
"Você prestou luto e levou a cabeça de " + tribute.deadName + ".",
|
||||||
|
NamedTextColor.GOLD));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Who died for a mourning reaction set, and who has already been rewarded. */
|
||||||
|
private static final class Tribute {
|
||||||
|
final UUID deadId;
|
||||||
|
final String deadName;
|
||||||
|
final Set<UUID> rewarded = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
Tribute(UUID deadId, String deadName) {
|
||||||
|
this.deadId = deadId;
|
||||||
|
this.deadName = deadName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Death location captured at death, delivered at respawn. */
|
||||||
|
private record DeathCoords(String coords, boolean keepInventory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -581,6 +656,9 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
if (ai != null) {
|
if (ai != null) {
|
||||||
ai.conversations().forget(event.getPlayer().getUniqueId());
|
ai.conversations().forget(event.getPlayer().getUniqueId());
|
||||||
}
|
}
|
||||||
|
// Quitting on the death screen means no respawn fires for this death;
|
||||||
|
// drop the pending coords so they never deliver stale next session.
|
||||||
|
pendingDeathCoords.remove(event.getPlayer().getUniqueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- per-player opt out -------------------------------------------------
|
// --- per-player opt out -------------------------------------------------
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
case "reacoes" -> whoReacted(sender);
|
case "reacoes" -> whoReacted(sender);
|
||||||
case "ia" -> ia(sender, args, false);
|
case "ia" -> ia(sender, args, false);
|
||||||
case "iap" -> ia(sender, args, true);
|
case "iap" -> ia(sender, args, true);
|
||||||
|
// Typed twin of the [F] mourning button — "f" is not a configured
|
||||||
|
// reaction (the mourning set is hardcoded), so it can't be found via
|
||||||
|
// reactionForCommand; route it directly. Acts on the latest message,
|
||||||
|
// so it pays respects only if a mourning window is the most recent.
|
||||||
|
case "f" -> reactLatest(sender, "f");
|
||||||
default -> {
|
default -> {
|
||||||
String reaction = plugin.settings().reactionForCommand(command.getName());
|
String reaction = plugin.settings().reactionForCommand(command.getName());
|
||||||
if (reaction != null) {
|
if (reaction != null) {
|
||||||
@@ -397,6 +402,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
if (!reactions.react(player, key)) {
|
if (!reactions.react(player, key)) {
|
||||||
Msg.error(sender, "Essa reação não vale para a última mensagem.");
|
Msg.error(sender, "Essa reação não vale para a última mensagem.");
|
||||||
|
} else {
|
||||||
|
plugin.afterReact(player, reactions.id(), key);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -489,6 +496,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
if (!reactions.react(player, args[1])) {
|
if (!reactions.react(player, args[1])) {
|
||||||
player.sendActionBar(Component.text("Reação desconhecida.", NamedTextColor.RED));
|
player.sendActionBar(Component.text("Reação desconhecida.", NamedTextColor.RED));
|
||||||
|
} else {
|
||||||
|
plugin.afterReact(player, reactions.id(), args[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package dev.marcospaulo.canalhandia;
|
package dev.marcospaulo.canalhandia;
|
||||||
|
|
||||||
import net.kyori.adventure.bossbar.BossBar;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.event.ClickEvent;
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
import net.kyori.adventure.text.event.HoverEvent;
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -18,8 +16,9 @@ import java.util.UUID;
|
|||||||
* Reaction state for one announced message.
|
* Reaction state for one announced message.
|
||||||
*
|
*
|
||||||
* <p>Chat cannot be edited after sending, so the counts inside the buttons are
|
* <p>Chat cannot be edited after sending, so the counts inside the buttons are
|
||||||
* frozen at send time. Live numbers appear on the boss bar, on the reactor's
|
* frozen at send time. Live numbers appear on the reactor's action bar, and in
|
||||||
* action bar, and in a one-line summary when the window closes.
|
* a one-line summary when the window closes. (A boss bar was removed on
|
||||||
|
* request — it sat on screen for the whole reaction window and read as clutter.)
|
||||||
*
|
*
|
||||||
* <p>Who reacted is deliberately <em>not</em> given its own chat line — with
|
* <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
|
* several reactions and several players that would fill the screen. Instead the
|
||||||
@@ -36,15 +35,12 @@ final class Reactions {
|
|||||||
private final List<ReactionDef> defs;
|
private final List<ReactionDef> defs;
|
||||||
/** Reaction key to reactors, preserving both order and display name. */
|
/** Reaction key to reactors, preserving both order and display name. */
|
||||||
private final Map<String, LinkedHashMap<UUID, String>> votes = new LinkedHashMap<>();
|
private final Map<String, LinkedHashMap<UUID, String>> votes = new LinkedHashMap<>();
|
||||||
private final BossBar bar;
|
|
||||||
private final long createdAt = System.currentTimeMillis();
|
private final long createdAt = System.currentTimeMillis();
|
||||||
private boolean barVisible;
|
|
||||||
|
|
||||||
Reactions(int id, List<ReactionDef> defs) {
|
Reactions(int id, List<ReactionDef> defs) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.defs = defs;
|
this.defs = defs;
|
||||||
defs.forEach(def -> votes.put(def.key(), new LinkedHashMap<>()));
|
defs.forEach(def -> votes.put(def.key(), new LinkedHashMap<>()));
|
||||||
this.bar = BossBar.bossBar(tally(false), 1.0f, BossBar.Color.PURPLE, BossBar.Overlay.PROGRESS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int id() {
|
int id() {
|
||||||
@@ -72,9 +68,6 @@ final class Reactions {
|
|||||||
votes.values().forEach(map -> map.remove(player.getUniqueId()));
|
votes.values().forEach(map -> map.remove(player.getUniqueId()));
|
||||||
// Names are captured now so the summary still works if someone logs off.
|
// Names are captured now so the summary still works if someone logs off.
|
||||||
votes.get(key).put(player.getUniqueId(), player.getName());
|
votes.get(key).put(player.getUniqueId(), player.getName());
|
||||||
if (barVisible) {
|
|
||||||
bar.name(tally(false));
|
|
||||||
}
|
|
||||||
player.sendActionBar(tally(Platform.isBedrock(player)));
|
player.sendActionBar(tally(Platform.isBedrock(player)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -135,7 +128,7 @@ final class Reactions {
|
|||||||
return text.toString();
|
return text.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compact live counts, for the boss bar and action bar. */
|
/** Compact live counts, for the reactor's action bar. */
|
||||||
Component tally(boolean bedrock) {
|
Component tally(boolean bedrock) {
|
||||||
Component text = Component.text("Reações: ", NamedTextColor.WHITE);
|
Component text = Component.text("Reações: ", NamedTextColor.WHITE);
|
||||||
for (ReactionDef def : defs) {
|
for (ReactionDef def : defs) {
|
||||||
@@ -200,21 +193,4 @@ final class Reactions {
|
|||||||
}
|
}
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void show() {
|
|
||||||
barVisible = true;
|
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> p.showBossBar(bar));
|
|
||||||
}
|
|
||||||
|
|
||||||
void hide() {
|
|
||||||
barVisible = false;
|
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> p.hideBossBar(bar));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Shows the bar to someone who joined while the window was still open. */
|
|
||||||
void showTo(Player player) {
|
|
||||||
if (barVisible) {
|
|
||||||
player.showBossBar(bar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,15 @@ final class Settings {
|
|||||||
set("reacoes-ativas", enabled);
|
set("reacoes-ativas", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether pressing F to pay respects drops the dead player's head. */
|
||||||
|
boolean lutoHeadReward() {
|
||||||
|
return plugin.getConfig().getBoolean("luto.cabeca", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lutoHeadReward(boolean enabled) {
|
||||||
|
set("luto.cabeca", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
/** How long the boss bar stays up. */
|
/** How long the boss bar stays up. */
|
||||||
int reactionWindowSeconds() {
|
int reactionWindowSeconds() {
|
||||||
return Math.max(5, plugin.getConfig().getInt("janela-reacao-segundos", 90));
|
return Math.max(5, plugin.getConfig().getInt("janela-reacao-segundos", 90));
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ reacoes-ativas: true
|
|||||||
# Por quanto tempo a barra de reações fica visível, em segundos.
|
# Por quanto tempo a barra de reações fica visível, em segundos.
|
||||||
janela-reacao-segundos: 90
|
janela-reacao-segundos: 90
|
||||||
|
|
||||||
|
# Luto: ao prestar F para alguém que morreu, o jogador recebe a cabeça daquele
|
||||||
|
# jogador (uma lembrança simbólica). É o único recurso do plugin que mexe no
|
||||||
|
# inventário; desligue aqui se não quiser.
|
||||||
|
luto:
|
||||||
|
cabeca: true
|
||||||
|
|
||||||
# Quantos nomes cabem no resumo de reações antes do resto virar "+N".
|
# 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
|
# O resumo é sempre UMA linha, por mais gente que reaja; para ver a lista
|
||||||
# completa use /reacoes (privado, não polui o chat).
|
# completa use /reacoes (privado, não polui o chat).
|
||||||
|
|||||||
Reference in New Issue
Block a user