feat(items): add bound role control items
This commit is contained in:
@@ -6,6 +6,26 @@ description: Chronological record of material decisions affecting the Spigot Tyr
|
|||||||
|
|
||||||
# Spigot Tyrant Design Log
|
# Spigot Tyrant Design Log
|
||||||
|
|
||||||
|
## 2026-08-21 — Bound role control items completed
|
||||||
|
|
||||||
|
- Completed US-017 and the reopened US-015 and US-016 criteria with configurable owner-bound Tyrant and Vigilante control items that open their respective panels.
|
||||||
|
- Role items are reconciled on login and periodically after role transitions, deduplicated, removed after role loss, and recoverable through `/tyrant item` or `/vigilante item` without dropping overflow.
|
||||||
|
- Drop, pickup, container, drag, hopper, placement, dispenser, item-frame, death, stale-owner, and stale-role paths prevent transfer or remove invalid items.
|
||||||
|
- Added default `Tyrant Control` Nether Star and `Vigilante Control` Compass settings and validation.
|
||||||
|
- Verified role policy, delayed reconciliation, panel interaction, command recovery, settings defaults, compiler warnings, tests, and packaging with `./gradlew clean check jar`.
|
||||||
|
|
||||||
|
## 2026-08-21 — Bound role control item implementation started
|
||||||
|
|
||||||
|
- US-015, US-016, and US-017 begin a test-first implementation of configurable owner-bound Tyrant and Vigilante control items.
|
||||||
|
- Delivery, command recovery, interaction, role validation, transfer prevention, role-loss cleanup, and duplicate resistance will build on the existing bound-item patterns.
|
||||||
|
|
||||||
|
## 2026-08-21 — Bound role control items planned
|
||||||
|
|
||||||
|
- Added US-017 for secure owner-bound Tyrant and Vigilante inventory items that open the corresponding control panels.
|
||||||
|
- The Tyrant item defaults to a Nether Star named `Tyrant Control`, and the Vigilante item defaults to a Compass named `Vigilante Control`; both material and name are configurable.
|
||||||
|
- Planned automatic role-assignment and login delivery, command recovery, full transfer prevention, removal on role loss or inventory departure, and duplicate resistance.
|
||||||
|
- Reopened US-015 and US-016 in the backlog to track control-panel access through the new role items.
|
||||||
|
|
||||||
## 2026-08-21 — Vigilante control panel completed
|
## 2026-08-21 — Vigilante control panel completed
|
||||||
|
|
||||||
- Completed US-016 and the reopened US-008 criteria with resistance overview, recruitment, Follower management, and confirmed role-relinquishment screens.
|
- Completed US-016 and the reopened US-008 criteria with resistance overview, recruitment, Follower management, and confirmed role-relinquishment screens.
|
||||||
|
|||||||
@@ -16,3 +16,4 @@
|
|||||||
14. [US-014: Use class ability items](us-014-use-class-ability-items.md)
|
14. [US-014: Use class ability items](us-014-use-class-ability-items.md)
|
||||||
15. [US-015: Manage a reign through the Tyrant control panel](us-015-manage-tyrant-control-panel.md)
|
15. [US-015: Manage a reign through the Tyrant control panel](us-015-manage-tyrant-control-panel.md)
|
||||||
16. [US-016: Manage Followers through the Vigilante control panel](us-016-manage-vigilante-control-panel.md)
|
16. [US-016: Manage Followers through the Vigilante control panel](us-016-manage-vigilante-control-panel.md)
|
||||||
|
17. [US-017: Use bound role control items](us-017-use-bound-role-control-items.md)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ As the **Tyrant**, I want a control panel for my reign so that I can inspect pro
|
|||||||
- [x] Stale, repeated, or concurrent panel interactions cannot duplicate purchases, bypass cooldowns, or create conflicting assignments.
|
- [x] Stale, repeated, or concurrent panel interactions cannot duplicate purchases, bypass cooldowns, or create conflicting assignments.
|
||||||
- [x] Panel actions become unavailable immediately if the game pauses, the reign ends, or the player is no longer the Tyrant.
|
- [x] Panel actions become unavailable immediately if the game pauses, the reign ends, or the player is no longer the Tyrant.
|
||||||
- [x] Existing `/tyrant choices`, `/tyrant buy`, `/tyrant assign`, and `/tyrant intelligence` commands remain available as alternatives.
|
- [x] Existing `/tyrant choices`, `/tyrant buy`, `/tyrant assign`, and `/tyrant intelligence` commands remain available as alternatives.
|
||||||
|
- [x] The active Tyrant can open the control panel by using the bound Tyrant control item.
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
@@ -42,3 +43,4 @@ Automated tests verify command entry points, active-Tyrant authorization, author
|
|||||||
- [Assign unlocked classes](us-004-assign-unlocked-classes.md)
|
- [Assign unlocked classes](us-004-assign-unlocked-classes.md)
|
||||||
- [Purchase and use Tyrant abilities](us-009-use-tyrant-abilities.md)
|
- [Purchase and use Tyrant abilities](us-009-use-tyrant-abilities.md)
|
||||||
- [Inform players and manage participation](us-011-inform-and-manage-participation.md)
|
- [Inform players and manage participation](us-011-inform-and-manage-participation.md)
|
||||||
|
- [Use bound role control items](us-017-use-bound-role-control-items.md)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ As the **Vigilante**, I want a control panel for managing Followers so that I ca
|
|||||||
- [x] Every action revalidates the running lifecycle and active Vigilante before changing state.
|
- [x] Every action revalidates the running lifecycle and active Vigilante before changing state.
|
||||||
- [x] Stale or repeated control-panel interactions cannot invite an ineligible player, duplicate an invitation, or dismiss the wrong Follower.
|
- [x] Stale or repeated control-panel interactions cannot invite an ineligible player, duplicate an invitation, or dismiss the wrong Follower.
|
||||||
- [x] Existing `/vigilante invite`, `/vigilante accept`, `/vigilante dismiss`, `/vigilante leave`, and `/tyrant relinquish confirm` commands remain available.
|
- [x] Existing `/vigilante invite`, `/vigilante accept`, `/vigilante dismiss`, `/vigilante leave`, and `/tyrant relinquish confirm` commands remain available.
|
||||||
|
- [x] The active Vigilante can open the control panel by using the bound Vigilante control item.
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
@@ -44,3 +45,4 @@ Automated tests verify default and explicit command entry points, active-Vigilan
|
|||||||
- [Handle inactivity and pending selections](us-010-handle-inactivity-and-selection.md)
|
- [Handle inactivity and pending selections](us-010-handle-inactivity-and-selection.md)
|
||||||
- [Manage a reign through the Tyrant control panel](us-015-manage-tyrant-control-panel.md)
|
- [Manage a reign through the Tyrant control panel](us-015-manage-tyrant-control-panel.md)
|
||||||
- [Role and class model](../concepts/role-and-class-model.md)
|
- [Role and class model](../concepts/role-and-class-model.md)
|
||||||
|
- [Use bound role control items](us-017-use-bound-role-control-items.md)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
type: User Story
|
||||||
|
title: "US-017: Use bound role control items"
|
||||||
|
description: Give the active Tyrant and Vigilante secure inventory items that open their control panels.
|
||||||
|
status: done
|
||||||
|
---
|
||||||
|
|
||||||
|
# US-017: Use bound role control items
|
||||||
|
|
||||||
|
As a **Tyrant or Vigilante**, I want a bound role item that opens my control panel so that I can access role controls without typing a command.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [x] A player receives the appropriate control item when becoming the active Tyrant or Vigilante.
|
||||||
|
- [x] The Tyrant control item opens the Tyrant control panel when used.
|
||||||
|
- [x] The Vigilante control item opens the Vigilante control panel when used.
|
||||||
|
- [x] The Tyrant control item defaults to a Nether Star named `Tyrant Control`.
|
||||||
|
- [x] The Vigilante control item defaults to a Compass named `Vigilante Control`.
|
||||||
|
- [x] Each item's material and name are configurable, and hidden metadata identifies its owner, associated role, and the plugin.
|
||||||
|
- [x] Only the current role holder identified by the item's metadata can use the item.
|
||||||
|
- [x] A former role holder or another player cannot use or retain the item.
|
||||||
|
- [x] `/tyrant item` restores a missing Tyrant control item in addition to existing eligible class items.
|
||||||
|
- [x] `/vigilante item` restores a missing Vigilante control item.
|
||||||
|
- [x] Recovery commands cannot create duplicate control items.
|
||||||
|
- [x] Using a recovery command while the inventory is full does not drop the item and explains how to retry.
|
||||||
|
- [x] A role control item cannot be placed, crafted with, stored, framed, dispensed, transferred, or moved into another inventory.
|
||||||
|
- [x] Dropping a role control item removes it immediately rather than creating a transferable dropped item.
|
||||||
|
- [x] If a role control item leaves its owner's inventory during death or another inventory event, it is removed and may be recovered after respawn.
|
||||||
|
- [x] A missing role control item is restored automatically on login or role assignment when inventory space is available.
|
||||||
|
- [x] Every copy is removed immediately when the player loses the associated role.
|
||||||
|
- [x] Repeated commands, role transitions, death, logout, restart, and concurrent inventory events cannot duplicate role control items.
|
||||||
|
- [x] Existing `/tyrant`, `/tyrant menu`, `/vigilante`, and `/vigilante menu` control-panel access remains available.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Automated tests verify role eligibility, paused-role retention, reconciliation and recovery, panel activation, role-specific commands, and configuration defaults. Inventory listeners cover transfer and departure paths, and the complete `./gradlew clean check jar` lifecycle passes.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Manage a reign through the Tyrant control panel](us-015-manage-tyrant-control-panel.md)
|
||||||
|
- [Manage Followers through the Vigilante control panel](us-016-manage-vigilante-control-panel.md)
|
||||||
|
- [Use class ability items](us-014-use-class-ability-items.md)
|
||||||
|
- [Configure and persist game state](us-012-configure-and-persist-state.md)
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
public final class BukkitRoleControlItemService implements RoleControlItemService {
|
||||||
|
private final NamespacedKey roleKey;
|
||||||
|
private final NamespacedKey ownerKey;
|
||||||
|
private final PluginSettings settings;
|
||||||
|
|
||||||
|
public BukkitRoleControlItemService(Plugin plugin, PluginSettings settings) {
|
||||||
|
roleKey = new NamespacedKey(plugin, "role-control");
|
||||||
|
ownerKey = new NamespacedKey(plugin, "role-control-owner");
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recover(Player player, GameState game) {
|
||||||
|
Optional<RoleControl> required = RoleControlItemPolicy.requiredRole(
|
||||||
|
game, player.getUniqueId()
|
||||||
|
);
|
||||||
|
if (required.isEmpty() || contains(player, required.orElseThrow())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Integer, ItemStack> leftovers = player.getInventory().addItem(
|
||||||
|
create(player.getUniqueId(), required.orElseThrow())
|
||||||
|
);
|
||||||
|
if (!leftovers.isEmpty()) {
|
||||||
|
String command = required.orElseThrow() == RoleControl.TYRANT
|
||||||
|
? "/tyrant item" : "/vigilante item";
|
||||||
|
player.sendMessage(ChatColor.RED
|
||||||
|
+ "Your inventory is full. Make room and use " + command + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBoundRoleControlItem(ItemStack item) {
|
||||||
|
return role(item).isPresent() && owner(item).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<RoleControl> role(ItemStack item) {
|
||||||
|
String value = metadata(item, roleKey);
|
||||||
|
if (value == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Optional.of(RoleControl.valueOf(value));
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<UUID> owner(ItemStack item) {
|
||||||
|
String value = metadata(item, ownerKey);
|
||||||
|
if (value == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Optional.of(UUID.fromString(value));
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeInvalid(Player player, GameState game) {
|
||||||
|
Optional<RoleControl> required = RoleControlItemPolicy.requiredRole(
|
||||||
|
game, player.getUniqueId()
|
||||||
|
);
|
||||||
|
boolean retained = false;
|
||||||
|
ItemStack[] contents = player.getInventory().getContents();
|
||||||
|
for (int index = 0; index < contents.length; index++) {
|
||||||
|
ItemStack item = contents[index];
|
||||||
|
if (!isBoundRoleControlItem(item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean valid = !retained
|
||||||
|
&& owner(item).filter(player.getUniqueId()::equals).isPresent()
|
||||||
|
&& role(item).equals(required);
|
||||||
|
if (valid) {
|
||||||
|
retained = true;
|
||||||
|
} else {
|
||||||
|
player.getInventory().setItem(index, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAll(Player player) {
|
||||||
|
ItemStack[] contents = player.getInventory().getContents();
|
||||||
|
for (int index = 0; index < contents.length; index++) {
|
||||||
|
if (isBoundRoleControlItem(contents[index])) {
|
||||||
|
player.getInventory().setItem(index, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack create(UUID ownerId, RoleControl role) {
|
||||||
|
PluginSettings.AbilityItemSettings configured = role == RoleControl.TYRANT
|
||||||
|
? settings.tyrantControlItem() : settings.vigilanteControlItem();
|
||||||
|
Material material = Material.matchMaterial(configured.material());
|
||||||
|
if (material == null) {
|
||||||
|
throw new IllegalStateException("Configured role control material is unavailable");
|
||||||
|
}
|
||||||
|
ItemStack item = new ItemStack(material);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
meta.setDisplayName(ChatColor.GOLD + configured.name());
|
||||||
|
meta.getPersistentDataContainer().set(
|
||||||
|
roleKey, PersistentDataType.STRING, role.name()
|
||||||
|
);
|
||||||
|
meta.getPersistentDataContainer().set(
|
||||||
|
ownerKey, PersistentDataType.STRING, ownerId.toString()
|
||||||
|
);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(Player player, RoleControl role) {
|
||||||
|
for (ItemStack item : player.getInventory().getContents()) {
|
||||||
|
if (owner(item).filter(player.getUniqueId()::equals).isPresent()
|
||||||
|
&& role(item).filter(role::equals).isPresent()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String metadata(ItemStack item, NamespacedKey key) {
|
||||||
|
if (item == null || !item.hasItemMeta()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item.getItemMeta().getPersistentDataContainer()
|
||||||
|
.get(key, PersistentDataType.STRING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,8 @@ public record PluginSettings(
|
|||||||
AbilityItemSettings fixerItem,
|
AbilityItemSettings fixerItem,
|
||||||
AbilityItemSettings tamerItem,
|
AbilityItemSettings tamerItem,
|
||||||
AbilityItemSettings capturedMobItem,
|
AbilityItemSettings capturedMobItem,
|
||||||
|
AbilityItemSettings tyrantControlItem,
|
||||||
|
AbilityItemSettings vigilanteControlItem,
|
||||||
String recoveryCommand,
|
String recoveryCommand,
|
||||||
String inventoryFullMessage,
|
String inventoryFullMessage,
|
||||||
boolean freezeTimersWhilePaused
|
boolean freezeTimersWhilePaused
|
||||||
@@ -78,6 +80,10 @@ public record PluginSettings(
|
|||||||
fixerItem = Objects.requireNonNull(fixerItem, "fixerItem");
|
fixerItem = Objects.requireNonNull(fixerItem, "fixerItem");
|
||||||
tamerItem = Objects.requireNonNull(tamerItem, "tamerItem");
|
tamerItem = Objects.requireNonNull(tamerItem, "tamerItem");
|
||||||
capturedMobItem = Objects.requireNonNull(capturedMobItem, "capturedMobItem");
|
capturedMobItem = Objects.requireNonNull(capturedMobItem, "capturedMobItem");
|
||||||
|
tyrantControlItem = Objects.requireNonNull(tyrantControlItem, "tyrantControlItem");
|
||||||
|
vigilanteControlItem = Objects.requireNonNull(
|
||||||
|
vigilanteControlItem, "vigilanteControlItem"
|
||||||
|
);
|
||||||
recoveryCommand = requireText(recoveryCommand, "recovery-command");
|
recoveryCommand = requireText(recoveryCommand, "recovery-command");
|
||||||
inventoryFullMessage = requireText(inventoryFullMessage, "inventory-full-message");
|
inventoryFullMessage = requireText(inventoryFullMessage, "inventory-full-message");
|
||||||
}
|
}
|
||||||
@@ -114,6 +120,14 @@ public record PluginSettings(
|
|||||||
item(values, "fixer-item", new AbilityItemSettings("STICK", "Fixer's Wrench")),
|
item(values, "fixer-item", new AbilityItemSettings("STICK", "Fixer's Wrench")),
|
||||||
item(values, "tamer-item", new AbilityItemSettings("FISHING_ROD", "Tamer's Lead")),
|
item(values, "tamer-item", new AbilityItemSettings("FISHING_ROD", "Tamer's Lead")),
|
||||||
item(values, "captured-mob-item", new AbilityItemSettings("PAPER", "Captured Mob")),
|
item(values, "captured-mob-item", new AbilityItemSettings("PAPER", "Captured Mob")),
|
||||||
|
item(
|
||||||
|
values, "tyrant-control-item",
|
||||||
|
new AbilityItemSettings("NETHER_STAR", "Tyrant Control")
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
values, "vigilante-control-item",
|
||||||
|
new AbilityItemSettings("COMPASS", "Vigilante Control")
|
||||||
|
),
|
||||||
string(values, "recovery-command", "/tyrant item"),
|
string(values, "recovery-command", "/tyrant item"),
|
||||||
string(
|
string(
|
||||||
values,
|
values,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public final class PluginSettingsValidator {
|
|||||||
validateItem(settings.fixerItem(), "fixer-item");
|
validateItem(settings.fixerItem(), "fixer-item");
|
||||||
validateItem(settings.tamerItem(), "tamer-item");
|
validateItem(settings.tamerItem(), "tamer-item");
|
||||||
validateItem(settings.capturedMobItem(), "captured-mob-item");
|
validateItem(settings.capturedMobItem(), "captured-mob-item");
|
||||||
|
validateItem(settings.tyrantControlItem(), "tyrant-control-item");
|
||||||
|
validateItem(settings.vigilanteControlItem(), "vigilante-control-item");
|
||||||
for (String entityType : settings.deniedMobTypes()) {
|
for (String entityType : settings.deniedMobTypes()) {
|
||||||
try {
|
try {
|
||||||
EntityType.valueOf(entityType);
|
EntityType.valueOf(entityType);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
public enum RoleControl {
|
||||||
|
TYRANT,
|
||||||
|
VIGILANTE
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import org.bukkit.entity.ItemFrame;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.Action;
|
||||||
|
import org.bukkit.event.block.BlockDispenseEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
public final class RoleControlItemListener implements Listener {
|
||||||
|
private final TyrantStateManager stateManager;
|
||||||
|
private final RoleControlItemService items;
|
||||||
|
private final TyrantControlPanel tyrantPanel;
|
||||||
|
private final VigilanteControlPanel vigilantePanel;
|
||||||
|
|
||||||
|
public RoleControlItemListener(
|
||||||
|
TyrantStateManager stateManager,
|
||||||
|
RoleControlItemService items,
|
||||||
|
TyrantControlPanel tyrantPanel,
|
||||||
|
VigilanteControlPanel vigilantePanel
|
||||||
|
) {
|
||||||
|
this.stateManager = stateManager;
|
||||||
|
this.items = items;
|
||||||
|
this.tyrantPanel = tyrantPanel;
|
||||||
|
this.vigilantePanel = vigilantePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInteract(PlayerInteractEvent event) {
|
||||||
|
ItemStack item = event.getItem();
|
||||||
|
if (!items.isBoundRoleControlItem(item)
|
||||||
|
|| event.getAction() != Action.RIGHT_CLICK_AIR
|
||||||
|
&& event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.setCancelled(true);
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
GameState game = stateManager.game();
|
||||||
|
java.util.Optional<RoleControl> required = RoleControlItemPolicy.requiredRole(
|
||||||
|
game, player.getUniqueId()
|
||||||
|
);
|
||||||
|
boolean valid = items.owner(item).filter(player.getUniqueId()::equals).isPresent()
|
||||||
|
&& items.role(item).equals(required)
|
||||||
|
&& game.lifecycle() == GameLifecycle.RUNNING;
|
||||||
|
if (!valid) {
|
||||||
|
items.removeInvalid(player, game);
|
||||||
|
player.sendMessage(org.bukkit.ChatColor.RED
|
||||||
|
+ "That role control item is no longer valid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (required.orElseThrow() == RoleControl.TYRANT) {
|
||||||
|
tyrantPanel.open(player);
|
||||||
|
} else {
|
||||||
|
vigilantePanel.open(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
|
items.removeInvalid(event.getPlayer(), stateManager.game());
|
||||||
|
items.recover(event.getPlayer(), stateManager.game());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDrop(PlayerDropItemEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getItemDrop().getItemStack())) {
|
||||||
|
event.getItemDrop().remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPickup(EntityPickupItemEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getItem().getItemStack())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
event.getItem().remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getCurrentItem())
|
||||||
|
|| items.isBoundRoleControlItem(event.getCursor())) {
|
||||||
|
if (!(event.getWhoClicked() instanceof Player player)
|
||||||
|
|| event.isShiftClick()
|
||||||
|
|| event.getClickedInventory() == null
|
||||||
|
|| !event.getClickedInventory().equals(player.getInventory())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryDrag(InventoryDragEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getOldCursor())
|
||||||
|
|| event.getNewItems().values().stream()
|
||||||
|
.anyMatch(items::isBoundRoleControlItem)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryMove(InventoryMoveItemEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getItem())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlace(BlockPlaceEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getItemInHand())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDispense(BlockDispenseEvent event) {
|
||||||
|
if (items.isBoundRoleControlItem(event.getItem())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onFrame(PlayerInteractEntityEvent event) {
|
||||||
|
if (event.getRightClicked() instanceof ItemFrame
|
||||||
|
&& items.isBoundRoleControlItem(event.getPlayer().getInventory()
|
||||||
|
.getItem(event.getHand()))) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDeath(PlayerDeathEvent event) {
|
||||||
|
event.getDrops().removeIf(items::isBoundRoleControlItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class RoleControlItemPolicy {
|
||||||
|
private RoleControlItemPolicy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<RoleControl> requiredRole(GameState game, UUID playerId) {
|
||||||
|
if (game.tyrantId().filter(playerId::equals).isPresent()) {
|
||||||
|
return Optional.of(RoleControl.TYRANT);
|
||||||
|
}
|
||||||
|
if (game.vigilanteId().filter(playerId::equals).isPresent()) {
|
||||||
|
return Optional.of(RoleControl.VIGILANTE);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public final class RoleControlItemRefreshTask implements Runnable {
|
||||||
|
private final TyrantStateManager stateManager;
|
||||||
|
private final RoleControlItemService items;
|
||||||
|
private final Server server;
|
||||||
|
|
||||||
|
public RoleControlItemRefreshTask(
|
||||||
|
TyrantStateManager stateManager,
|
||||||
|
RoleControlItemService items,
|
||||||
|
Server server
|
||||||
|
) {
|
||||||
|
this.stateManager = stateManager;
|
||||||
|
this.items = items;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
GameState game = stateManager.game();
|
||||||
|
for (Player player : server.getOnlinePlayers()) {
|
||||||
|
items.removeInvalid(player, game);
|
||||||
|
if (!player.isDead()) {
|
||||||
|
items.recover(player, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
public interface RoleControlItemService {
|
||||||
|
void recover(Player player, GameState game);
|
||||||
|
|
||||||
|
boolean isBoundRoleControlItem(ItemStack item);
|
||||||
|
|
||||||
|
Optional<RoleControl> role(ItemStack item);
|
||||||
|
|
||||||
|
Optional<UUID> owner(ItemStack item);
|
||||||
|
|
||||||
|
void removeInvalid(Player player, GameState game);
|
||||||
|
|
||||||
|
void removeAll(Player player);
|
||||||
|
}
|
||||||
@@ -54,6 +54,9 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
|
|||||||
FollowerService followers = new FollowerService();
|
FollowerService followers = new FollowerService();
|
||||||
AbilityReadinessService readiness = new AbilityReadinessService();
|
AbilityReadinessService readiness = new AbilityReadinessService();
|
||||||
AbilityItemService abilityItems = new BukkitAbilityItemService(this, settings);
|
AbilityItemService abilityItems = new BukkitAbilityItemService(this, settings);
|
||||||
|
RoleControlItemService roleControlItems = new BukkitRoleControlItemService(
|
||||||
|
this, settings
|
||||||
|
);
|
||||||
CapturedMobItemService capturedMobItems = new BukkitCapturedMobItemService(
|
CapturedMobItemService capturedMobItems = new BukkitCapturedMobItemService(
|
||||||
this, settings
|
this, settings
|
||||||
);
|
);
|
||||||
@@ -109,11 +112,13 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
|
|||||||
tyrantPresentation,
|
tyrantPresentation,
|
||||||
abilityItems,
|
abilityItems,
|
||||||
new TyrantAbilityService(settings.rosterIntelligenceCooldown()),
|
new TyrantAbilityService(settings.rosterIntelligenceCooldown()),
|
||||||
controlPanel
|
controlPanel,
|
||||||
|
roleControlItems
|
||||||
));
|
));
|
||||||
Objects.requireNonNull(getCommand("vigilante"), "Missing vigilante metadata")
|
Objects.requireNonNull(getCommand("vigilante"), "Missing vigilante metadata")
|
||||||
.setExecutor(new VigilanteCommand(
|
.setExecutor(new VigilanteCommand(
|
||||||
stateManager, followers, onlinePlayers, vigilanteControlPanel
|
stateManager, followers, onlinePlayers, vigilanteControlPanel,
|
||||||
|
roleControlItems
|
||||||
));
|
));
|
||||||
Objects.requireNonNull(getCommand("tyrantadmin"), "Missing tyrantadmin metadata")
|
Objects.requireNonNull(getCommand("tyrantadmin"), "Missing tyrantadmin metadata")
|
||||||
.setExecutor(new TyrantAdminCommand(
|
.setExecutor(new TyrantAdminCommand(
|
||||||
@@ -181,6 +186,12 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
|
|||||||
new BoundItemListener(abilityItems),
|
new BoundItemListener(abilityItems),
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
getServer().getPluginManager().registerEvents(
|
||||||
|
new RoleControlItemListener(
|
||||||
|
stateManager, roleControlItems, controlPanel, vigilanteControlPanel
|
||||||
|
),
|
||||||
|
this
|
||||||
|
);
|
||||||
getServer().getPluginManager().registerEvents(
|
getServer().getPluginManager().registerEvents(
|
||||||
new TamerListener(
|
new TamerListener(
|
||||||
stateManager, abilityItems, capturedMobItems, capturedMobs,
|
stateManager, abilityItems, capturedMobItems, capturedMobs,
|
||||||
@@ -229,6 +240,12 @@ public final class SpigotTyrantPlugin extends JavaPlugin {
|
|||||||
20L,
|
20L,
|
||||||
20L
|
20L
|
||||||
);
|
);
|
||||||
|
getServer().getScheduler().runTaskTimer(
|
||||||
|
this,
|
||||||
|
new RoleControlItemRefreshTask(stateManager, roleControlItems, getServer()),
|
||||||
|
1L,
|
||||||
|
10L
|
||||||
|
);
|
||||||
getServer().getScheduler().runTaskTimer(this, stateManager::saveIfDirty, 600L, 600L);
|
getServer().getScheduler().runTaskTimer(this, stateManager::saveIfDirty, 600L, 600L);
|
||||||
getLogger().info("Spigot Tyrant enabled.");
|
getLogger().info("Spigot Tyrant enabled.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
private final AbilityItemService abilityItems;
|
private final AbilityItemService abilityItems;
|
||||||
private final TyrantAbilityService tyrantAbilities;
|
private final TyrantAbilityService tyrantAbilities;
|
||||||
private final TyrantControlPanel controlPanel;
|
private final TyrantControlPanel controlPanel;
|
||||||
|
private final RoleControlItemService roleControlItems;
|
||||||
|
|
||||||
public TyrantCommand(
|
public TyrantCommand(
|
||||||
TyrantStateManager stateManager,
|
TyrantStateManager stateManager,
|
||||||
@@ -44,7 +45,21 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
stateManager, progression, new ClassAssignmentService(),
|
stateManager, progression, new ClassAssignmentService(),
|
||||||
new BukkitOnlinePlayerDirectory(), Clock.systemUTC(),
|
new BukkitOnlinePlayerDirectory(), Clock.systemUTC(),
|
||||||
new ParticipationService(Duration.ofDays(7)), null, null, null,
|
new ParticipationService(Duration.ofDays(7)), null, null, null,
|
||||||
new TyrantAbilityService(Duration.ofHours(24)), controlPanel
|
new TyrantAbilityService(Duration.ofHours(24)), controlPanel, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TyrantCommand(
|
||||||
|
TyrantStateManager stateManager,
|
||||||
|
TyrantProgressionService progression,
|
||||||
|
TyrantControlPanel controlPanel,
|
||||||
|
RoleControlItemService roleControlItems
|
||||||
|
) {
|
||||||
|
this(
|
||||||
|
stateManager, progression, new ClassAssignmentService(),
|
||||||
|
new BukkitOnlinePlayerDirectory(), Clock.systemUTC(),
|
||||||
|
new ParticipationService(Duration.ofDays(7)), null, null, null,
|
||||||
|
new TyrantAbilityService(Duration.ofHours(24)), controlPanel, roleControlItems
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +73,8 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
this(
|
this(
|
||||||
stateManager, progression, assignments, onlinePlayers, clock,
|
stateManager, progression, assignments, onlinePlayers, clock,
|
||||||
new ParticipationService(Duration.ofDays(7)), null, null, null,
|
new ParticipationService(Duration.ofDays(7)), null, null, null,
|
||||||
new TyrantAbilityService(Duration.ofHours(24)), TyrantCommand::unavailablePanel
|
new TyrantAbilityService(Duration.ofHours(24)), TyrantCommand::unavailablePanel,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +93,7 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
this(
|
this(
|
||||||
stateManager, progression, assignments, onlinePlayers, clock, participation,
|
stateManager, progression, assignments, onlinePlayers, clock, participation,
|
||||||
relinquishment, presentation, abilityItems, tyrantAbilities,
|
relinquishment, presentation, abilityItems, tyrantAbilities,
|
||||||
TyrantCommand::unavailablePanel
|
TyrantCommand::unavailablePanel, null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +109,26 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
AbilityItemService abilityItems,
|
AbilityItemService abilityItems,
|
||||||
TyrantAbilityService tyrantAbilities,
|
TyrantAbilityService tyrantAbilities,
|
||||||
TyrantControlPanel controlPanel
|
TyrantControlPanel controlPanel
|
||||||
|
) {
|
||||||
|
this(
|
||||||
|
stateManager, progression, assignments, onlinePlayers, clock, participation,
|
||||||
|
relinquishment, presentation, abilityItems, tyrantAbilities, controlPanel, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TyrantCommand(
|
||||||
|
TyrantStateManager stateManager,
|
||||||
|
TyrantProgressionService progression,
|
||||||
|
ClassAssignmentService assignments,
|
||||||
|
OnlinePlayerDirectory onlinePlayers,
|
||||||
|
Clock clock,
|
||||||
|
ParticipationService participation,
|
||||||
|
RoleRelinquishmentService relinquishment,
|
||||||
|
TyrantPresentation presentation,
|
||||||
|
AbilityItemService abilityItems,
|
||||||
|
TyrantAbilityService tyrantAbilities,
|
||||||
|
TyrantControlPanel controlPanel,
|
||||||
|
RoleControlItemService roleControlItems
|
||||||
) {
|
) {
|
||||||
this.stateManager = stateManager;
|
this.stateManager = stateManager;
|
||||||
this.progression = progression;
|
this.progression = progression;
|
||||||
@@ -105,6 +141,7 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
this.abilityItems = abilityItems;
|
this.abilityItems = abilityItems;
|
||||||
this.tyrantAbilities = tyrantAbilities;
|
this.tyrantAbilities = tyrantAbilities;
|
||||||
this.controlPanel = controlPanel;
|
this.controlPanel = controlPanel;
|
||||||
|
this.roleControlItems = roleControlItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -308,14 +345,21 @@ public final class TyrantCommand implements CommandExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void recoverItems(Player player) {
|
private void recoverItems(Player player) {
|
||||||
if (abilityItems == null) {
|
if (abilityItems == null && roleControlItems == null) {
|
||||||
player.sendMessage(ChatColor.RED + "Ability item recovery is unavailable.");
|
player.sendMessage(ChatColor.RED + "Item recovery is unavailable.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (abilityItems != null) {
|
||||||
PlayerState state = stateManager.player(player.getUniqueId(), player.getName());
|
PlayerState state = stateManager.player(player.getUniqueId(), player.getName());
|
||||||
abilityItems.recover(player, state);
|
abilityItems.recover(player, state);
|
||||||
|
}
|
||||||
|
GameState game = stateManager.game();
|
||||||
|
if (roleControlItems != null
|
||||||
|
&& game.tyrantId().filter(player.getUniqueId()::equals).isPresent()) {
|
||||||
|
roleControlItems.recover(player, game);
|
||||||
|
}
|
||||||
player.sendMessage(ChatColor.YELLOW
|
player.sendMessage(ChatColor.YELLOW
|
||||||
+ "Recovered every currently ready missing class item that could fit.");
|
+ "Recovered every currently available missing Tyrant item that could fit.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void relinquish(Player player, String[] arguments) {
|
private void relinquish(Player player, String[] arguments) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public final class VigilanteCommand implements CommandExecutor {
|
|||||||
private final FollowerService followers;
|
private final FollowerService followers;
|
||||||
private final OnlinePlayerDirectory onlinePlayers;
|
private final OnlinePlayerDirectory onlinePlayers;
|
||||||
private final VigilanteControlPanel controlPanel;
|
private final VigilanteControlPanel controlPanel;
|
||||||
|
private final RoleControlItemService roleControlItems;
|
||||||
|
|
||||||
public VigilanteCommand(
|
public VigilanteCommand(
|
||||||
TyrantStateManager stateManager,
|
TyrantStateManager stateManager,
|
||||||
@@ -21,7 +22,8 @@ public final class VigilanteCommand implements CommandExecutor {
|
|||||||
this(
|
this(
|
||||||
stateManager, followers, onlinePlayers,
|
stateManager, followers, onlinePlayers,
|
||||||
player -> player.sendMessage(ChatColor.RED
|
player -> player.sendMessage(ChatColor.RED
|
||||||
+ "The Vigilante control panel is unavailable.")
|
+ "The Vigilante control panel is unavailable."),
|
||||||
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +32,22 @@ public final class VigilanteCommand implements CommandExecutor {
|
|||||||
FollowerService followers,
|
FollowerService followers,
|
||||||
OnlinePlayerDirectory onlinePlayers,
|
OnlinePlayerDirectory onlinePlayers,
|
||||||
VigilanteControlPanel controlPanel
|
VigilanteControlPanel controlPanel
|
||||||
|
) {
|
||||||
|
this(stateManager, followers, onlinePlayers, controlPanel, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VigilanteCommand(
|
||||||
|
TyrantStateManager stateManager,
|
||||||
|
FollowerService followers,
|
||||||
|
OnlinePlayerDirectory onlinePlayers,
|
||||||
|
VigilanteControlPanel controlPanel,
|
||||||
|
RoleControlItemService roleControlItems
|
||||||
) {
|
) {
|
||||||
this.stateManager = stateManager;
|
this.stateManager = stateManager;
|
||||||
this.followers = followers;
|
this.followers = followers;
|
||||||
this.onlinePlayers = onlinePlayers;
|
this.onlinePlayers = onlinePlayers;
|
||||||
this.controlPanel = controlPanel;
|
this.controlPanel = controlPanel;
|
||||||
|
this.roleControlItems = roleControlItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,6 +66,16 @@ public final class VigilanteCommand implements CommandExecutor {
|
|||||||
controlPanel.open(player);
|
controlPanel.open(player);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (arguments.length == 1 && arguments[0].equalsIgnoreCase("item")) {
|
||||||
|
if (roleControlItems == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "Role item recovery is unavailable.");
|
||||||
|
} else {
|
||||||
|
roleControlItems.recover(player, stateManager.game());
|
||||||
|
player.sendMessage(ChatColor.YELLOW
|
||||||
|
+ "Recovered the missing Vigilante control item if it could fit.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
FollowerResult result;
|
FollowerResult result;
|
||||||
Player target = null;
|
Player target = null;
|
||||||
if (arguments.length == 1 && arguments[0].equalsIgnoreCase("accept")) {
|
if (arguments.length == 1 && arguments[0].equalsIgnoreCase("accept")) {
|
||||||
@@ -81,7 +104,7 @@ public final class VigilanteCommand implements CommandExecutor {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(ChatColor.YELLOW
|
player.sendMessage(ChatColor.YELLOW
|
||||||
+ "Usage: /vigilante <menu|invite <player>|accept|dismiss <player>|leave>");
|
+ "Usage: /vigilante <menu|item|invite <player>|accept|dismiss <player>|leave>");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (result.status() == FollowerStatus.JOINED
|
if (result.status() == FollowerStatus.JOINED
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ tyrant-resistance-level: 1
|
|||||||
follower-strength-cap: 5
|
follower-strength-cap: 5
|
||||||
follower-resistance-cap: 4
|
follower-resistance-cap: 4
|
||||||
|
|
||||||
|
# Role control items
|
||||||
|
tyrant-control-item:
|
||||||
|
material: NETHER_STAR
|
||||||
|
name: Tyrant Control
|
||||||
|
vigilante-control-item:
|
||||||
|
material: COMPASS
|
||||||
|
name: Vigilante Control
|
||||||
|
|
||||||
# Bound item recovery and delivery messages
|
# Bound item recovery and delivery messages
|
||||||
recovery-command: /tyrant item
|
recovery-command: /tyrant item
|
||||||
inventory-full-message: Your inventory is full. Make room and use /tyrant item.
|
inventory-full-message: Your inventory is full. Make room and use /tyrant item.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ commands:
|
|||||||
usage: /tyrant <menu|status|choices|buy|assign|item|intelligence|optout|optin|relinquish confirm>
|
usage: /tyrant <menu|status|choices|buy|assign|item|intelligence|optout|optin|relinquish confirm>
|
||||||
vigilante:
|
vigilante:
|
||||||
description: Manage Vigilante Followers.
|
description: Manage Vigilante Followers.
|
||||||
usage: /vigilante <menu|invite <player>|accept|dismiss <player>|leave>
|
usage: /vigilante <menu|item|invite <player>|accept|dismiss <player>|leave>
|
||||||
tyrantadmin:
|
tyrantadmin:
|
||||||
description: Administer the Spigot Tyrant game.
|
description: Administer the Spigot Tyrant game.
|
||||||
usage: /tyrantadmin <status|start|pause|resume|reset confirm>
|
usage: /tyrantadmin <status|start|pause|resume|reset confirm>
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ final class PluginSettingsTest {
|
|||||||
assertEquals("STICK", settings.assassinItem().material());
|
assertEquals("STICK", settings.assassinItem().material());
|
||||||
assertEquals("Assassin Cloak", settings.assassinItem().name());
|
assertEquals("Assassin Cloak", settings.assassinItem().name());
|
||||||
assertEquals("FISHING_ROD", settings.tamerItem().material());
|
assertEquals("FISHING_ROD", settings.tamerItem().material());
|
||||||
|
assertEquals("NETHER_STAR", settings.tyrantControlItem().material());
|
||||||
|
assertEquals("Tyrant Control", settings.tyrantControlItem().name());
|
||||||
|
assertEquals("COMPASS", settings.vigilanteControlItem().material());
|
||||||
|
assertEquals("Vigilante Control", settings.vigilanteControlItem().name());
|
||||||
assertEquals("/tyrant item", settings.recoveryCommand());
|
assertEquals("/tyrant item", settings.recoveryCommand());
|
||||||
assertEquals("Your inventory is full. Make room and use /tyrant item.",
|
assertEquals("Your inventory is full. Make room and use /tyrant item.",
|
||||||
settings.inventoryFullMessage());
|
settings.inventoryFullMessage());
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.bukkit.entity.Item;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.block.Action;
|
||||||
|
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
final class RoleControlItemListenerTest {
|
||||||
|
@Test
|
||||||
|
void droppedRoleControlItemVanishes() {
|
||||||
|
ItemStack item = mock(ItemStack.class);
|
||||||
|
Item dropped = mock(Item.class);
|
||||||
|
when(dropped.getItemStack()).thenReturn(item);
|
||||||
|
PlayerDropItemEvent event = mock(PlayerDropItemEvent.class);
|
||||||
|
when(event.getItemDrop()).thenReturn(dropped);
|
||||||
|
RoleControlItemService items = mock(RoleControlItemService.class);
|
||||||
|
when(items.isBoundRoleControlItem(item)).thenReturn(true);
|
||||||
|
RoleControlItemListener listener = listener(items);
|
||||||
|
|
||||||
|
listener.onDrop(event);
|
||||||
|
|
||||||
|
verify(dropped).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void automatedInventoryTransferIsBlocked() {
|
||||||
|
ItemStack item = mock(ItemStack.class);
|
||||||
|
InventoryMoveItemEvent event = mock(InventoryMoveItemEvent.class);
|
||||||
|
when(event.getItem()).thenReturn(item);
|
||||||
|
RoleControlItemService items = mock(RoleControlItemService.class);
|
||||||
|
when(items.isBoundRoleControlItem(item)).thenReturn(true);
|
||||||
|
RoleControlItemListener listener = listener(items);
|
||||||
|
|
||||||
|
listener.onInventoryMove(event);
|
||||||
|
|
||||||
|
verify(event).setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void currentTyrantUsesBoundItemToOpenControlPanel() {
|
||||||
|
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
GameState game = new GameState(
|
||||||
|
GameLifecycle.RUNNING, Optional.of(tyrantId), Optional.empty(),
|
||||||
|
Optional.empty(), Optional.empty(), Optional.empty(), Duration.ZERO,
|
||||||
|
0, 0, Set.of()
|
||||||
|
);
|
||||||
|
TyrantStateManager manager = mock(TyrantStateManager.class);
|
||||||
|
when(manager.game()).thenReturn(game);
|
||||||
|
Player tyrant = mock(Player.class);
|
||||||
|
when(tyrant.getUniqueId()).thenReturn(tyrantId);
|
||||||
|
ItemStack item = mock(ItemStack.class);
|
||||||
|
PlayerInteractEvent event = mock(PlayerInteractEvent.class);
|
||||||
|
when(event.getPlayer()).thenReturn(tyrant);
|
||||||
|
when(event.getItem()).thenReturn(item);
|
||||||
|
when(event.getAction()).thenReturn(Action.RIGHT_CLICK_AIR);
|
||||||
|
RoleControlItemService items = mock(RoleControlItemService.class);
|
||||||
|
when(items.isBoundRoleControlItem(item)).thenReturn(true);
|
||||||
|
when(items.owner(item)).thenReturn(Optional.of(tyrantId));
|
||||||
|
when(items.role(item)).thenReturn(Optional.of(RoleControl.TYRANT));
|
||||||
|
TyrantControlPanel tyrantPanel = mock(TyrantControlPanel.class);
|
||||||
|
RoleControlItemListener listener = new RoleControlItemListener(
|
||||||
|
manager, items, tyrantPanel, mock(VigilanteControlPanel.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
listener.onInteract(event);
|
||||||
|
|
||||||
|
verify(event).setCancelled(true);
|
||||||
|
verify(tyrantPanel).open(tyrant);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RoleControlItemListener listener(RoleControlItemService items) {
|
||||||
|
return new RoleControlItemListener(
|
||||||
|
mock(TyrantStateManager.class), items, mock(TyrantControlPanel.class),
|
||||||
|
mock(VigilanteControlPanel.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
final class RoleControlItemPolicyTest {
|
||||||
|
@Test
|
||||||
|
void runningRoleHoldersRequireOnlyTheirOwnControlItem() {
|
||||||
|
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
UUID vigilanteId = UUID.fromString("22222222-2222-2222-2222-222222222222");
|
||||||
|
GameState game = new GameState(
|
||||||
|
GameLifecycle.RUNNING, Optional.of(tyrantId), Optional.of(vigilanteId),
|
||||||
|
Optional.empty(), Optional.empty(), Optional.empty(), Duration.ZERO,
|
||||||
|
0, 0, Set.of()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(Optional.of(RoleControl.TYRANT),
|
||||||
|
RoleControlItemPolicy.requiredRole(game, tyrantId));
|
||||||
|
assertEquals(Optional.of(RoleControl.VIGILANTE),
|
||||||
|
RoleControlItemPolicy.requiredRole(game, vigilanteId));
|
||||||
|
assertEquals(Optional.empty(), RoleControlItemPolicy.requiredRole(game, UUID.randomUUID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pausedRoleHolderRetainsControlItem() {
|
||||||
|
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
GameState game = new GameState(
|
||||||
|
GameLifecycle.PAUSED, Optional.of(tyrantId), Optional.empty(),
|
||||||
|
Optional.empty(), Optional.empty(), Optional.of(java.time.Instant.EPOCH),
|
||||||
|
Duration.ZERO, 0, 0, Set.of()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(Optional.of(RoleControl.TYRANT),
|
||||||
|
RoleControlItemPolicy.requiredRole(game, tyrantId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package games.dmg.spigottyrant;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
final class RoleControlItemRefreshTaskTest {
|
||||||
|
@Test
|
||||||
|
void reconciliationRemovesInvalidCopiesAndRecoversRequiredRoleItem() {
|
||||||
|
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
GameState game = new GameState(
|
||||||
|
GameLifecycle.RUNNING, Optional.of(tyrantId), Optional.empty(),
|
||||||
|
Optional.empty(), Optional.empty(), Optional.empty(), Duration.ZERO,
|
||||||
|
0, 0, Set.of()
|
||||||
|
);
|
||||||
|
TyrantStateManager manager = mock(TyrantStateManager.class);
|
||||||
|
when(manager.game()).thenReturn(game);
|
||||||
|
Player tyrant = mock(Player.class);
|
||||||
|
Server server = mock(Server.class);
|
||||||
|
doReturn(Set.of(tyrant)).when(server).getOnlinePlayers();
|
||||||
|
RoleControlItemService items = mock(RoleControlItemService.class);
|
||||||
|
|
||||||
|
new RoleControlItemRefreshTask(manager, items, server).run();
|
||||||
|
|
||||||
|
verify(items).removeInvalid(tyrant, game);
|
||||||
|
verify(items).recover(tyrant, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,28 @@ final class TyrantCommandTest {
|
|||||||
verify(controlPanel, org.mockito.Mockito.times(2)).open(tyrant);
|
verify(controlPanel, org.mockito.Mockito.times(2)).open(tyrant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void itemCommandRecoversTyrantRoleControlItem() {
|
||||||
|
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
GameState game = new GameState(
|
||||||
|
GameLifecycle.RUNNING, Optional.of(tyrantId), Optional.empty(),
|
||||||
|
Optional.empty(), Optional.empty(), Optional.empty(), Duration.ZERO,
|
||||||
|
0, 0, Set.of()
|
||||||
|
);
|
||||||
|
TyrantStateManager manager = mock(TyrantStateManager.class);
|
||||||
|
when(manager.game()).thenReturn(game);
|
||||||
|
Player tyrant = mock(Player.class);
|
||||||
|
when(tyrant.getUniqueId()).thenReturn(tyrantId);
|
||||||
|
RoleControlItemService roleItems = mock(RoleControlItemService.class);
|
||||||
|
TyrantCommand command = new TyrantCommand(
|
||||||
|
manager, new TyrantProgressionService(), mock(TyrantControlPanel.class), roleItems
|
||||||
|
);
|
||||||
|
|
||||||
|
command.onCommand(tyrant, mock(Command.class), "tyrant", new String[] {"item"});
|
||||||
|
|
||||||
|
verify(roleItems).recover(tyrant, game);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void choicesClearlyIdentifyPurchasedAvailableAndUnavailableUnlocks() {
|
void choicesClearlyIdentifyPurchasedAvailableAndUnavailableUnlocks() {
|
||||||
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
UUID tyrantId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
|||||||
@@ -5,12 +5,39 @@ import static org.mockito.Mockito.times;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
final class VigilanteCommandTest {
|
final class VigilanteCommandTest {
|
||||||
|
@Test
|
||||||
|
void itemCommandRecoversVigilanteRoleControlItem() {
|
||||||
|
UUID vigilanteId = UUID.fromString("22222222-2222-2222-2222-222222222222");
|
||||||
|
GameState game = new GameState(
|
||||||
|
GameLifecycle.RUNNING,
|
||||||
|
Optional.of(UUID.fromString("11111111-1111-1111-1111-111111111111")),
|
||||||
|
Optional.of(vigilanteId), Optional.empty(), Optional.empty(), Optional.empty(),
|
||||||
|
Duration.ZERO, 0, 0, Set.of()
|
||||||
|
);
|
||||||
|
TyrantStateManager manager = mock(TyrantStateManager.class);
|
||||||
|
when(manager.game()).thenReturn(game);
|
||||||
|
Player vigilante = mock(Player.class);
|
||||||
|
when(vigilante.getUniqueId()).thenReturn(vigilanteId);
|
||||||
|
RoleControlItemService roleItems = mock(RoleControlItemService.class);
|
||||||
|
VigilanteCommand command = new VigilanteCommand(
|
||||||
|
manager, new FollowerService(), mock(OnlinePlayerDirectory.class),
|
||||||
|
mock(VigilanteControlPanel.class), roleItems
|
||||||
|
);
|
||||||
|
|
||||||
|
command.onCommand(vigilante, mock(Command.class), "vigilante", new String[] {"item"});
|
||||||
|
|
||||||
|
verify(roleItems).recover(vigilante, game);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void playerCanOpenControlPanelWithNoArgumentsOrMenuSubcommand() {
|
void playerCanOpenControlPanelWithNoArgumentsOrMenuSubcommand() {
|
||||||
UUID playerId = UUID.fromString("22222222-2222-2222-2222-222222222222");
|
UUID playerId = UUID.fromString("22222222-2222-2222-2222-222222222222");
|
||||||
|
|||||||
Reference in New Issue
Block a user