feat(gear): add final unbreakable Elytra reward
Release / release (push) Successful in 2m51s
CI / build (push) Successful in 1m10s

This commit is contained in:
dmg
2026-09-09 22:28:40 -04:00
parent 0c4a95801a
commit afd8a03f1b
29 changed files with 494 additions and 61 deletions
@@ -5,6 +5,7 @@ public enum ArmorClaimStatus {
GAME_NOT_RUNNING,
NOT_TYRANT,
UNLOCKS_INCOMPLETE,
COLLECTION_INCOMPLETE,
NO_CHOICES,
ALREADY_CLAIMED
}
@@ -23,7 +23,10 @@ public final class BukkitTyrantArmorItemFactory implements TyrantArmorItemFactor
if (spec.efficiencyLevel() > 0) {
meta.addEnchant(Enchantment.EFFICIENCY, spec.efficiencyLevel(), true);
}
meta.addEnchant(Enchantment.MENDING, spec.mendingLevel(), true);
if (spec.mendingLevel() > 0) {
meta.addEnchant(Enchantment.MENDING, spec.mendingLevel(), true);
}
meta.setUnbreakable(spec.unbreakable());
item.setItemMeta(meta);
return item;
}
@@ -55,7 +55,7 @@ public final class BukkitTyrantControlPanelRenderer
inventory.setItem(22, item(Material.NETHERITE_CHESTPLATE,
ChatColor.GOLD + "Legacy Rewards", List.of(
model.allStandardUnlocksPurchased()
? "Choose a once-per-reign armor, tool, or weapon."
? "Choose legacy gear; complete the collection for Elytra."
: "Purchase all six standard unlocks first.",
"Claimed: " + (model.claimedArmor().isEmpty()
? "none" : model.claimedArmor())
@@ -118,12 +118,10 @@ public final class BukkitTyrantControlPanelRenderer
int[] slots = legacyRewardSlots();
for (int index = 0; index < pieces.length; index++) {
TyrantArmorPiece piece = pieces[index];
String status = model.claimedArmor().contains(piece) ? "CLAIMED"
: model.armorAvailable(piece) ? "AVAILABLE" : "LOCKED";
inventory.setItem(slots[index], item(
TyrantArmorSpec.forPiece(piece, player.getName()).material(),
ChatColor.GOLD + readable(piece.name()),
List.of("Status: " + status, "Cost: 1 choice")
model.armorLore(piece)
));
}
inventory.setItem(22, backItem());
@@ -146,7 +144,7 @@ public final class BukkitTyrantControlPanelRenderer
}
static int[] legacyRewardSlots() {
return new int[] {9, 10, 11, 12, 13, 14, 15, 16, 17};
return new int[] {9, 10, 11, 12, 13, 14, 15, 16, 17, 4};
}
private void openArmorConfirmation(Player player, TyrantArmorPiece piece) {
@@ -10,7 +10,14 @@ public enum TyrantArmorPiece {
PICKAXE,
SWORD,
HOE,
SHOVEL;
SHOVEL,
ELYTRA;
/** Explicitly excludes the final reward and any future reward additions. */
public static java.util.Set<TyrantArmorPiece> regularRewards() {
return java.util.Set.of(HELMET, CHESTPLATE, LEGGINGS, BOOTS,
AXE, PICKAXE, SWORD, HOE, SHOVEL);
}
public boolean isArmor() {
return switch (this) {
@@ -26,6 +33,7 @@ public enum TyrantArmorPiece {
case "sword" -> SWORD;
case "hoe" -> HOE;
case "shovel" -> SHOVEL;
case "elytra" -> ELYTRA;
default -> throw new IllegalArgumentException("Unknown gear piece");
};
}
@@ -21,6 +21,10 @@ public final class TyrantArmorService {
if (state.claimedArmor().contains(piece)) {
return result(state, ArmorClaimStatus.ALREADY_CLAIMED);
}
if (piece == TyrantArmorPiece.ELYTRA
&& !state.claimedArmor().containsAll(TyrantArmorPiece.regularRewards())) {
return result(state, ArmorClaimStatus.COLLECTION_INCOMPLETE);
}
Set<TyrantArmorPiece> claimed = EnumSet.noneOf(TyrantArmorPiece.class);
claimed.addAll(state.claimedArmor());
claimed.add(piece);
@@ -10,7 +10,8 @@ public record TyrantArmorSpec(
int protectionLevel,
int mendingLevel,
int sharpnessLevel,
int efficiencyLevel
int efficiencyLevel,
boolean unbreakable
) {
public TyrantArmorSpec {
lore = List.copyOf(lore);
@@ -27,19 +28,26 @@ public record TyrantArmorSpec(
case SWORD -> Material.NETHERITE_SWORD;
case HOE -> Material.NETHERITE_HOE;
case SHOVEL -> Material.NETHERITE_SHOVEL;
case ELYTRA -> Material.ELYTRA;
};
String pieceName = readable(piece);
boolean elytra = piece == TyrantArmorPiece.ELYTRA;
java.util.ArrayList<String> lore = new java.util.ArrayList<>(List.of(
"Forged for Tyrant " + tyrantName + ".",
"A lasting reward from " + tyrantName + "'s reign."
));
if (elytra) {
lore.add("Earned by completing the Tyrant's legacy collection.");
}
return new TyrantArmorSpec(
material,
"Tyrant's " + tyrantName + " " + pieceName,
List.of(
"Forged for Tyrant " + tyrantName + ".",
"A lasting reward from " + tyrantName + "'s reign."
),
lore,
piece.isArmor() ? 5 : 0,
1,
elytra ? 0 : 1,
piece == TyrantArmorPiece.AXE || piece == TyrantArmorPiece.SWORD ? 6 : 0,
!piece.isArmor() && piece != TyrantArmorPiece.SWORD ? 6 : 0
!piece.isArmor() && piece != TyrantArmorPiece.SWORD && !elytra ? 6 : 0,
elytra
);
}
@@ -54,7 +62,12 @@ public record TyrantArmorSpec(
if (efficiencyLevel > 0) {
lines.add("Efficiency VI");
}
lines.add("Mending I");
if (mendingLevel > 0) {
lines.add("Mending I");
}
if (unbreakable) {
lines.add("Unbreakable");
}
lines.add("Cost: 1 choice");
return List.copyOf(lines);
}
@@ -272,7 +272,7 @@ public final class TyrantCommand implements CommandExecutor {
+ (state.claimedArmor().isEmpty() ? "none" : state.claimedArmor()));
player.sendMessage(ChatColor.YELLOW
+ "Use /tyrant armor <helmet|chestplate|leggings|boots> or "
+ "/tyrant gear <axe|pickaxe|sword|hoe|shovel>.");
+ "/tyrant gear <axe|pickaxe|sword|hoe|shovel|elytra>.");
}
}
@@ -308,14 +308,16 @@ public final class TyrantCommand implements CommandExecutor {
} catch (IllegalArgumentException exception) {
player.sendMessage(ChatColor.RED + (armorCommand
? "Unknown armor piece. Use helmet, chestplate, leggings, or boots."
: "Unknown gear piece. Use axe, pickaxe, sword, hoe, or shovel."));
: "Unknown gear piece. Use axe, pickaxe, sword, hoe, shovel, or elytra."));
return;
}
PersistentState snapshot = stateManager.snapshot();
ArmorClaimResult result = armor.claim(snapshot.game(), player.getUniqueId(), piece);
if (result.status() != ArmorClaimStatus.CLAIMED) {
player.sendMessage(ChatColor.RED + "Could not claim " + piece.commandName()
+ ": " + readable(result.status()));
+ ": " + (result.status() == ArmorClaimStatus.COLLECTION_INCOMPLETE
? "claim all nine regular legacy rewards this reign first"
: readable(result.status())));
return;
}
int inventorySlot = player.getInventory().firstEmpty();
@@ -33,7 +33,31 @@ public record TyrantControlPanelModel(
public boolean armorAvailable(TyrantArmorPiece piece) {
return allStandardUnlocksPurchased() && unspentChoices > 0
&& !claimedArmor.contains(piece);
&& !claimedArmor.contains(piece)
&& (piece != TyrantArmorPiece.ELYTRA
|| claimedArmor.containsAll(TyrantArmorPiece.regularRewards()));
}
public java.util.List<String> armorLore(TyrantArmorPiece piece) {
java.util.List<String> lines = new java.util.ArrayList<>();
lines.add("Status: " + (claimedArmor.contains(piece) ? "CLAIMED"
: armorAvailable(piece) ? "AVAILABLE" : "LOCKED"));
lines.add("Cost: 1 choice");
if (piece == TyrantArmorPiece.ELYTRA) {
lines.add("Unbreakable");
long regularClaims = TyrantArmorPiece.regularRewards().stream()
.filter(claimedArmor::contains).count();
if (regularClaims < TyrantArmorPiece.regularRewards().size()) {
lines.add("Claim all nine regular legacy rewards first (" + regularClaims + "/9).");
}
}
if (!allStandardUnlocksPurchased()) {
lines.add("Purchase all six standard unlocks first.");
}
if (unspentChoices == 0) {
lines.add("Earn another choice first.");
}
return java.util.List.copyOf(lines);
}
public static TyrantControlPanelModel create(