feat(gear): add Tyrant legacy tools and weapons
Release / release (push) Successful in 2m56s
CI / build (push) Successful in 1m12s

This commit is contained in:
dmg
2026-09-09 22:02:59 -04:00
parent 553db666e1
commit 0c4a95801a
27 changed files with 411 additions and 44 deletions
@@ -11,10 +11,18 @@ public final class BukkitTyrantArmorItemFactory implements TyrantArmorItemFactor
public ItemStack create(TyrantArmorPiece piece, String tyrantName) {
TyrantArmorSpec spec = TyrantArmorSpec.forPiece(piece, tyrantName);
ItemStack item = new ItemStack(spec.material());
ItemMeta meta = Objects.requireNonNull(item.getItemMeta(), "Armor metadata is unavailable");
ItemMeta meta = Objects.requireNonNull(item.getItemMeta(), "Legacy reward metadata is unavailable");
meta.setDisplayName(ChatColor.GOLD + spec.displayName());
meta.setLore(spec.lore().stream().map(line -> ChatColor.GRAY + line).toList());
meta.addEnchant(Enchantment.PROTECTION, spec.protectionLevel(), true);
if (spec.protectionLevel() > 0) {
meta.addEnchant(Enchantment.PROTECTION, spec.protectionLevel(), true);
}
if (spec.sharpnessLevel() > 0) {
meta.addEnchant(Enchantment.SHARPNESS, spec.sharpnessLevel(), true);
}
if (spec.efficiencyLevel() > 0) {
meta.addEnchant(Enchantment.EFFICIENCY, spec.efficiencyLevel(), true);
}
meta.addEnchant(Enchantment.MENDING, spec.mendingLevel(), true);
item.setItemMeta(meta);
return item;
@@ -53,9 +53,9 @@ public final class BukkitTyrantControlPanelRenderer
inventory.setItem(16, item(Material.SPYGLASS, ChatColor.LIGHT_PURPLE + "Roster Intelligence",
List.of(intelligence, "Click to activate when ready.")));
inventory.setItem(22, item(Material.NETHERITE_CHESTPLATE,
ChatColor.GOLD + "Legacy Armor", List.of(
ChatColor.GOLD + "Legacy Rewards", List.of(
model.allStandardUnlocksPurchased()
? "Choose a once-per-reign armor reward."
? "Choose a once-per-reign armor, tool, or weapon."
: "Purchase all six standard unlocks first.",
"Claimed: " + (model.claimedArmor().isEmpty()
? "none" : model.claimedArmor())
@@ -113,9 +113,9 @@ public final class BukkitTyrantControlPanelRenderer
private void openArmor(Player player) {
TyrantControlPanelModel model = model(player);
MenuHolder holder = new MenuHolder(MenuView.ARMOR, null, null);
Inventory inventory = create(holder, 27, TITLE + " — Legacy Armor");
Inventory inventory = create(holder, 27, TITLE + " — Legacy Rewards");
TyrantArmorPiece[] pieces = TyrantArmorPiece.values();
int[] slots = {10, 12, 14, 16};
int[] slots = legacyRewardSlots();
for (int index = 0; index < pieces.length; index++) {
TyrantArmorPiece piece = pieces[index];
String status = model.claimedArmor().contains(piece) ? "CLAIMED"
@@ -136,7 +136,7 @@ public final class BukkitTyrantControlPanelRenderer
return;
}
TyrantArmorPiece[] pieces = TyrantArmorPiece.values();
int[] slots = {10, 12, 14, 16};
int[] slots = legacyRewardSlots();
for (int index = 0; index < slots.length; index++) {
if (slot == slots[index] && model(player).armorAvailable(pieces[index])) {
openArmorConfirmation(player, pieces[index]);
@@ -145,16 +145,20 @@ public final class BukkitTyrantControlPanelRenderer
}
}
static int[] legacyRewardSlots() {
return new int[] {9, 10, 11, 12, 13, 14, 15, 16, 17};
}
private void openArmorConfirmation(Player player, TyrantArmorPiece piece) {
MenuHolder holder = new MenuHolder(
MenuView.ARMOR_CONFIRMATION, null, null, null, piece
);
Inventory inventory = create(holder, 27, TITLE + " — Confirm Armor");
Inventory inventory = create(holder, 27, TITLE + " — Confirm Reward");
inventory.setItem(11, item(Material.RED_WOOL, ChatColor.RED + "Cancel", List.of()));
inventory.setItem(13, item(
TyrantArmorSpec.forPiece(piece, player.getName()).material(),
ChatColor.GOLD + "Tyrant's " + player.getName() + " " + readable(piece.name()),
List.of("Protection V", "Mending I", "Cost: 1 choice")
TyrantArmorSpec.forPiece(piece, player.getName()).confirmationLore()
));
inventory.setItem(15, item(Material.LIME_WOOL, ChatColor.GREEN + "Confirm", List.of()));
player.openInventory(inventory);
@@ -164,7 +168,8 @@ public final class BukkitTyrantControlPanelRenderer
if (slot == 11) {
openArmor(player);
} else if (slot == 15 && piece != null) {
player.performCommand("tyrant armor " + piece.commandName());
player.performCommand("tyrant " + (piece.isArmor() ? "armor " : "gear ")
+ piece.commandName());
openArmor(player);
}
}
@@ -190,7 +190,7 @@ public final class TyrantAdminCommand implements CommandExecutor {
sender.sendMessage("Level: " + game.tyrantLevel()
+ ", choices: " + game.unspentChoices()
+ ", purchases: " + game.purchases()
+ ", claimed armor: " + game.claimedArmor());
+ ", claimed legacy rewards: " + game.claimedArmor());
sender.sendMessage("Pending Tyrant: " + game.pendingTyrant());
sender.sendMessage("Pending Vigilante: " + game.pendingVigilante());
sender.sendMessage("Shared role arena: " + arenaLocations.location()
@@ -1,10 +1,34 @@
package games.dmg.spigottyrant;
/** Legacy rewards; the historical type and stored armor key also cover tools and weapons. */
public enum TyrantArmorPiece {
HELMET,
CHESTPLATE,
LEGGINGS,
BOOTS;
BOOTS,
AXE,
PICKAXE,
SWORD,
HOE,
SHOVEL;
public boolean isArmor() {
return switch (this) {
case HELMET, CHESTPLATE, LEGGINGS, BOOTS -> true;
default -> false;
};
}
public static TyrantArmorPiece fromGearCommand(String value) {
return switch (value.toLowerCase(java.util.Locale.ROOT)) {
case "axe" -> AXE;
case "pickaxe" -> PICKAXE;
case "sword" -> SWORD;
case "hoe" -> HOE;
case "shovel" -> SHOVEL;
default -> throw new IllegalArgumentException("Unknown gear piece");
};
}
public static TyrantArmorPiece fromCommand(String value) {
return switch (value.toLowerCase(java.util.Locale.ROOT)) {
@@ -8,7 +8,9 @@ public record TyrantArmorSpec(
String displayName,
List<String> lore,
int protectionLevel,
int mendingLevel
int mendingLevel,
int sharpnessLevel,
int efficiencyLevel
) {
public TyrantArmorSpec {
lore = List.copyOf(lore);
@@ -20,6 +22,11 @@ public record TyrantArmorSpec(
case CHESTPLATE -> Material.NETHERITE_CHESTPLATE;
case LEGGINGS -> Material.NETHERITE_LEGGINGS;
case BOOTS -> Material.NETHERITE_BOOTS;
case AXE -> Material.NETHERITE_AXE;
case PICKAXE -> Material.NETHERITE_PICKAXE;
case SWORD -> Material.NETHERITE_SWORD;
case HOE -> Material.NETHERITE_HOE;
case SHOVEL -> Material.NETHERITE_SHOVEL;
};
String pieceName = readable(piece);
return new TyrantArmorSpec(
@@ -29,11 +36,29 @@ public record TyrantArmorSpec(
"Forged for Tyrant " + tyrantName + ".",
"A lasting reward from " + tyrantName + "'s reign."
),
5,
1
piece.isArmor() ? 5 : 0,
1,
piece == TyrantArmorPiece.AXE || piece == TyrantArmorPiece.SWORD ? 6 : 0,
!piece.isArmor() && piece != TyrantArmorPiece.SWORD ? 6 : 0
);
}
public List<String> confirmationLore() {
java.util.ArrayList<String> lines = new java.util.ArrayList<>();
if (protectionLevel > 0) {
lines.add("Protection V");
}
if (sharpnessLevel > 0) {
lines.add("Sharpness VI");
}
if (efficiencyLevel > 0) {
lines.add("Efficiency VI");
}
lines.add("Mending I");
lines.add("Cost: 1 choice");
return List.copyOf(lines);
}
private static String readable(TyrantArmorPiece piece) {
String lower = piece.name().toLowerCase(java.util.Locale.ROOT);
return Character.toUpperCase(lower.charAt(0)) + lower.substring(1);
@@ -207,8 +207,12 @@ public final class TyrantCommand implements CommandExecutor {
buy(player, arguments[1]);
return true;
}
if (arguments.length == 2 && arguments[0].equalsIgnoreCase("gear")) {
claimLegacyReward(player, arguments[1], false);
return true;
}
if (arguments.length == 2 && arguments[0].equalsIgnoreCase("armor")) {
claimArmor(player, arguments[1]);
claimLegacyReward(player, arguments[1], true);
return true;
}
if (arguments.length == 3 && arguments[0].equalsIgnoreCase("assign")) {
@@ -241,7 +245,7 @@ public final class TyrantCommand implements CommandExecutor {
}
player.sendMessage(
ChatColor.YELLOW
+ "Usage: /tyrant <menu|status|choices|buy|armor|assign|item|intelligence|optout|optin|"
+ "Usage: /tyrant <menu|status|choices|buy|armor|gear|assign|item|intelligence|optout|optin|"
+ "relinquish confirm>"
);
return true;
@@ -264,10 +268,11 @@ public final class TyrantCommand implements CommandExecutor {
player.sendMessage(ChatColor.YELLOW + "[" + status + "] " + unlock.name());
}
if (state.purchases().size() == TyrantUnlock.values().length) {
player.sendMessage(ChatColor.GOLD + "Legacy armor claimed: "
player.sendMessage(ChatColor.GOLD + "Legacy rewards claimed: "
+ (state.claimedArmor().isEmpty() ? "none" : state.claimedArmor()));
player.sendMessage(ChatColor.YELLOW
+ "Use /tyrant armor <helmet|chestplate|leggings|boots>.");
+ "Use /tyrant armor <helmet|chestplate|leggings|boots> or "
+ "/tyrant gear <axe|pickaxe|sword|hoe|shovel>.");
}
}
@@ -295,13 +300,15 @@ public final class TyrantCommand implements CommandExecutor {
}
}
private void claimArmor(Player player, String requestedPiece) {
private void claimLegacyReward(Player player, String requestedPiece, boolean armorCommand) {
TyrantArmorPiece piece;
try {
piece = TyrantArmorPiece.fromCommand(requestedPiece);
piece = armorCommand ? TyrantArmorPiece.fromCommand(requestedPiece)
: TyrantArmorPiece.fromGearCommand(requestedPiece);
} catch (IllegalArgumentException exception) {
player.sendMessage(ChatColor.RED
+ "Unknown armor piece. Use helmet, chestplate, leggings, or boots.");
player.sendMessage(ChatColor.RED + (armorCommand
? "Unknown armor piece. Use helmet, chestplate, leggings, or boots."
: "Unknown gear piece. Use axe, pickaxe, sword, hoe, or shovel."));
return;
}
PersistentState snapshot = stateManager.snapshot();
@@ -12,7 +12,7 @@ import org.bukkit.entity.Player;
public final class TyrantTabCompleter implements TabCompleter {
private static final List<String> SUBCOMMANDS = List.of(
"menu", "status", "choices", "buy", "armor", "assign", "item", "intelligence",
"menu", "status", "choices", "buy", "armor", "gear", "assign", "item", "intelligence",
"optout", "optin", "relinquish"
);
private static final List<String> UNLOCKS = java.util.Arrays.stream(TyrantUnlock.values())
@@ -20,6 +20,11 @@ public final class TyrantTabCompleter implements TabCompleter {
.toList();
private static final List<String> CLASSES = List.of("assassin", "fixer", "tamer");
private static final List<String> ARMOR = java.util.Arrays.stream(TyrantArmorPiece.values())
.filter(TyrantArmorPiece::isArmor)
.map(TyrantArmorPiece::commandName)
.toList();
private static final List<String> GEAR = java.util.Arrays.stream(TyrantArmorPiece.values())
.filter(piece -> !piece.isArmor())
.map(TyrantArmorPiece::commandName)
.toList();
private final TyrantStateManager stateManager;
@@ -51,6 +56,7 @@ public final class TyrantTabCompleter implements TabCompleter {
return switch (subcommand) {
case "buy" -> TabSuggestions.matching(arguments[1], UNLOCKS);
case "armor" -> TabSuggestions.matching(arguments[1], ARMOR);
case "gear" -> TabSuggestions.matching(arguments[1], GEAR);
case "assign" -> TabSuggestions.matching(arguments[1], CLASSES);
case "relinquish" -> TabSuggestions.matching(
arguments[1], List.of("confirm")
@@ -193,6 +193,7 @@ public final class YamlTyrantStateRepository {
yaml.set("game.tyrant-level", game.tyrantLevel());
yaml.set("game.unspent-choices", game.unspentChoices());
yaml.set("game.purchases", game.purchases().stream().map(Enum::name).sorted().toList());
// Historical key deliberately stores all legacy rewards, including tools and weapons.
yaml.set(
"game.claimed-armor",
game.claimedArmor().stream().map(Enum::name).sorted().toList()