diff --git a/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java b/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java index 48e1a40..0e998a6 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java @@ -98,6 +98,7 @@ public final class Canalhandia extends JavaPlugin implements Listener { private GuessRound guessRound; private Poll poll; private Titles titles; + private DeathGift deathGift; @Override public void onEnable() { @@ -113,6 +114,7 @@ public final class Canalhandia extends JavaPlugin implements Listener { milestones = new Milestones(this); achievements = new Achievements(this); titles = new Titles(this); + deathGift = new DeathGift(this); // Load the achievement catalogue from config, then silently bank any // history the current definitions already imply (both here and for // milestones), so an expanded catalogue never spams returning players. @@ -247,6 +249,7 @@ public final class Canalhandia extends JavaPlugin implements Listener { Achievement.load(Achievement.loadFrom(conquistasCatalogo(), getLogger())); milestones.reload(); achievements.syncCatalogue(); + deathGift.reload(); } /** The weekly ranking baseline. Never null. */ @@ -895,6 +898,11 @@ public final class Canalhandia extends JavaPlugin implements Listener { .append(Component.text(tail, NamedTextColor.AQUA)); } player.sendMessage(msg); + // A comic consolation item, given once they can actually hold it. + // Gameplay-neutral by design (a poppy, a wilted bush) — just a laugh. + if (deathGift.active()) { + deathGift.give(player); + } }, 1L); } diff --git a/src/main/java/dev/marcospaulo/canalhandia/DeathGift.java b/src/main/java/dev/marcospaulo/canalhandia/DeathGift.java new file mode 100644 index 0000000..86fb738 --- /dev/null +++ b/src/main/java/dev/marcospaulo/canalhandia/DeathGift.java @@ -0,0 +1,111 @@ +package dev.marcospaulo.canalhandia; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.logging.Logger; + +/** + * A small consolation prize handed to a player when they respawn — a funeral + * poppy, a wilted bush, whatever. Comic, never useful: the point is a laugh at + * the death, not a leg up, so gifts are cosmetic-tier items given one at a time. + * + *
Config-driven like the achievement catalogue. If {@code mortes.presente} is
+ * absent the built-in list is used, so it works the moment the plugin loads;
+ * operators expand or mute it under that key and {@code /canalhandia reload}
+ * picks it up. Each line is {@code "MATERIAL | Nome | mensagem"}.
+ */
+final class DeathGift {
+
+ /** Baked-in default so a fresh server has something without editing config. */
+ private static final List Rooted on an empty, colourless component on purpose: the chat message is
+ * appended to this in {@link #onChat}, and a coloured root would bleed its
+ * colour into any unstyled message text — which turned title-holders' chat
+ * grey. Empty root → the message falls back to the client default (white). */
static Component tag(Achievement achievement) {
- return Component.text("[", NamedTextColor.DARK_GRAY)
+ return Component.empty()
+ .append(Component.text("[", NamedTextColor.DARK_GRAY))
.append(Component.text(achievement.title(), achievement.color()))
.append(Component.text("] ", NamedTextColor.DARK_GRAY))
.decoration(TextDecoration.BOLD, false);
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 0f54fab..5d47b64 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -329,3 +329,18 @@ ia:
- "O servidor se chama Canalhandia e roda Minecraft 26.2 (Paper)."
- "Jogadores de Bedrock entram pelo Geyser e o nome deles começa com ponto."
- "O servidor tem BlueMap, voice chat e Distant Horizons."
+
+# Presente de consolação: quando um jogador renasce, ganha um item cômico e
+# inofensivo (uma flor de velório, um arbusto murcho). Parte do módulo "mortes".
+# Sem esta seção, uma lista padrão embutida é usada. Rode /canalhandia reload
+# depois de editar. Cada item é "MATERIAL | Nome | mensagem".
+mortes:
+ presente:
+ ativo: true
+ itens:
+ - "POPPY | Flor do Velório | Uma florzinha pro seu velório. Sentimos muito."
+ - "DEAD_BUSH | Buquê Murcho | Um buquê à altura do seu último desempenho."
+ - "WET_SPONGE | Esponja das Lágrimas | Toma, pra enxugar as lágrimas."
+ - "BONE | Osso da Sorte | Um ossinho pra você, campeão."
+ - "COOKIE | Cookie de Consolação | Cookie de consolação. Vai que melhora."
+ - "ROTTEN_FLESH | Carne Podre | É o que tinha sobrado na despensa."
diff --git a/src/test/java/dev/marcospaulo/canalhandia/DeathGiftTest.java b/src/test/java/dev/marcospaulo/canalhandia/DeathGiftTest.java
new file mode 100644
index 0000000..30d22c6
--- /dev/null
+++ b/src/test/java/dev/marcospaulo/canalhandia/DeathGiftTest.java
@@ -0,0 +1,38 @@
+package dev.marcospaulo.canalhandia;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.Random;
+import java.util.logging.Logger;
+
+import org.bukkit.Material;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The consolation-gift parsing and pick. Deliberately avoids the happy path of
+ * {@link DeathGift#parse}, which calls {@code Material.isItem()} — that throws in
+ * a unit JVM without a bootstrapped registry (same limit RecipeBookTest notes),
+ * so the item-resolution branch is verified live instead.
+ */
+class DeathGiftTest {
+
+ private static final Logger LOG = Logger.getAnonymousLogger();
+
+ @Test
+ void skipsMalformedAndUnknownLines() {
+ assertTrue(DeathGift.parse(List.of("sem as barras certas"), LOG).isEmpty());
+ assertTrue(DeathGift.parse(List.of("SÓ | DUAS_PARTES"), LOG).isEmpty());
+ // Unknown material name is rejected at matchMaterial, before isItem().
+ assertTrue(DeathGift.parse(List.of("ITEM_QUE_NAO_EXISTE_XYZ | Nome | msg"), LOG).isEmpty());
+ }
+
+ @Test
+ void pickIsNullOnEmptyAndAMemberOtherwise() {
+ assertNull(DeathGift.pick(List.of(), new Random()));
+ DeathGift.Gift only = new DeathGift.Gift(Material.POPPY, "Flor do Velório", "oi");
+ assertSame(only, DeathGift.pick(List.of(only), new Random()));
+ }
+}
diff --git a/src/test/java/dev/marcospaulo/canalhandia/TitlesTest.java b/src/test/java/dev/marcospaulo/canalhandia/TitlesTest.java
index 6b8bfe9..89a39c9 100644
--- a/src/test/java/dev/marcospaulo/canalhandia/TitlesTest.java
+++ b/src/test/java/dev/marcospaulo/canalhandia/TitlesTest.java
@@ -54,4 +54,11 @@ class TitlesTest {
void tagCarriesTheTitle() {
assertNotNull(TitleChatListener.tag(Achievement.byKey("pedreiro")));
}
+
+ @Test
+ void tagRootIsColourlessSoMessageStaysWhite() {
+ // The chat message is appended to this tag; a coloured root would bleed
+ // into unstyled message text and grey it out. Root must carry no colour.
+ assertNull(TitleChatListener.tag(Achievement.byKey("pedreiro")).color());
+ }
}