423 lines
18 KiB
Java
423 lines
18 KiB
Java
package tc.oc.pgm.inventory;
|
|
|
|
import java.time.Duration;
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.ChatColor;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.attribute.Attribute;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.EventPriority;
|
|
import org.bukkit.event.Listener;
|
|
import org.bukkit.event.entity.EntityDamageEvent;
|
|
import org.bukkit.event.entity.EntityRegainHealthEvent;
|
|
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
|
import org.bukkit.event.inventory.ClickType;
|
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
|
import org.bukkit.event.inventory.InventoryClickedEvent;
|
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
|
import org.bukkit.event.inventory.InventoryDragEvent;
|
|
import org.bukkit.event.inventory.InventoryType;
|
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
|
import org.bukkit.event.player.PlayerPickupItemEvent;
|
|
import org.bukkit.inventory.DoubleChestInventory;
|
|
import org.bukkit.inventory.Inventory;
|
|
import org.bukkit.inventory.InventoryHolder;
|
|
import org.bukkit.inventory.ItemFlag;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.bukkit.inventory.PlayerInventory;
|
|
import org.bukkit.inventory.meta.ItemMeta;
|
|
import org.bukkit.potion.PotionEffect;
|
|
import tc.oc.commons.bukkit.util.BukkitUtils;
|
|
import tc.oc.commons.core.commands.CommandBinder;
|
|
import tc.oc.pgm.PGMTranslations;
|
|
import tc.oc.pgm.blitz.BlitzMatchModuleImpl;
|
|
import tc.oc.pgm.doublejump.DoubleJumpMatchModule;
|
|
import tc.oc.pgm.events.ListenerScope;
|
|
import tc.oc.pgm.events.ObserverInteractEvent;
|
|
import tc.oc.pgm.events.PlayerBlockTransformEvent;
|
|
import tc.oc.pgm.events.PlayerPartyChangeEvent;
|
|
import tc.oc.pgm.kits.WalkSpeedKit;
|
|
import tc.oc.pgm.blitz.BlitzMatchModule;
|
|
import tc.oc.pgm.match.MatchModule;
|
|
import tc.oc.pgm.match.MatchPlayer;
|
|
import tc.oc.pgm.match.MatchScope;
|
|
import tc.oc.pgm.match.Repeatable;
|
|
import tc.oc.pgm.match.inject.MatchModuleFixtureManifest;
|
|
import tc.oc.pgm.spawns.events.ParticipantSpawnEvent;
|
|
import tc.oc.time.Time;
|
|
|
|
@ListenerScope(MatchScope.LOADED)
|
|
public class ViewInventoryMatchModule extends MatchModule implements Listener {
|
|
|
|
public static class Manifest extends MatchModuleFixtureManifest<ViewInventoryMatchModule> {
|
|
@Override protected void configure() {
|
|
super.configure();
|
|
|
|
new CommandBinder(binder())
|
|
.register(InventoryCommands.class);
|
|
}
|
|
}
|
|
|
|
public static final Duration TICK = Duration.ofMillis(50);
|
|
|
|
protected final Map<Player, View> views = new HashMap<>();
|
|
protected final Map<Player, Instant> updateQueue = new HashMap<>();
|
|
|
|
public static int getInventoryPreviewSlot(int inventorySlot) {
|
|
if(inventorySlot < 9) {
|
|
return inventorySlot + 36; // put hotbar on bottom
|
|
}
|
|
if(inventorySlot < 36) {
|
|
return inventorySlot; // rest of inventory
|
|
}
|
|
// TODO: investigate why this method doesn't work with CraftBukkit's armor slots
|
|
return inventorySlot; // default
|
|
}
|
|
|
|
@Repeatable(scope = MatchScope.LOADED, interval = @Time(ticks = 4))
|
|
public void queuedChecks() {
|
|
for(Iterator<Map.Entry<Player, Instant>> iterator = updateQueue.entrySet().iterator(); iterator.hasNext();) {
|
|
final Map.Entry<Player, Instant> entry = iterator.next();
|
|
if(entry.getValue().isAfter(Instant.now())) continue;
|
|
|
|
checkMonitoredInventories(entry.getKey());
|
|
iterator.remove();
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
public void closeMonitoredInventory(final InventoryCloseEvent event) {
|
|
views.remove(event.getActor());
|
|
}
|
|
|
|
@EventHandler
|
|
public void playerQuit(final PlayerPartyChangeEvent event) {
|
|
views.remove(event.getPlayer().getBukkit());
|
|
}
|
|
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void showInventories(final ObserverInteractEvent event) {
|
|
if(event.getClickType() != ClickType.RIGHT) return;
|
|
if(event.getPlayer().isDead()) return;
|
|
|
|
if(event.getClickedParticipant() != null) {
|
|
event.setCancelled(true);
|
|
if(canPreviewInventory(event.getPlayer(), event.getClickedParticipant())) {
|
|
this.previewPlayerInventory(event.getPlayer().getBukkit(), event.getClickedParticipant().getInventory());
|
|
}
|
|
} else if(event.getClickedEntity() instanceof InventoryHolder && !(event.getClickedEntity() instanceof Player)) {
|
|
event.setCancelled(true);
|
|
this.previewInventory(event.getPlayer().getBukkit(), ((InventoryHolder) event.getClickedEntity()).getInventory());
|
|
} else if(event.getClickedBlockState() instanceof InventoryHolder) {
|
|
event.setCancelled(true);
|
|
this.previewInventory(event.getPlayer().getBukkit(), ((InventoryHolder) event.getClickedBlockState()).getInventory());
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
|
public void cancelClicks(final InventoryClickEvent event) {
|
|
final View view = views.get(event.getActor());
|
|
if(view != null && event.getInventory().equals(view.preview)) {
|
|
event.setCancelled(true);
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR)
|
|
public void updateMonitoredClick(final InventoryClickedEvent event) {
|
|
if(event.getWhoClicked() instanceof Player) {
|
|
Player player = (Player) event.getWhoClicked();
|
|
|
|
boolean playerInventory = event.getInventory().getType() == InventoryType.CRAFTING; // cb bug fix
|
|
Inventory inventory;
|
|
|
|
if(playerInventory) {
|
|
inventory = player.getInventory();
|
|
} else {
|
|
inventory = event.getInventory();
|
|
}
|
|
|
|
invLoop: for(Map.Entry<Player, View> entry : new HashSet<>(this.views.entrySet())) { // avoid ConcurrentModificationException
|
|
final Player viewer = entry.getKey();
|
|
View view = entry.getValue();
|
|
|
|
// because a player can only be viewing one inventory at a time,
|
|
// this is how we determine if we have a match
|
|
if(inventory.getViewers().isEmpty() ||
|
|
view.watched.getViewers().isEmpty() ||
|
|
inventory.getViewers().size() > view.watched.getViewers().size()) continue invLoop;
|
|
|
|
for(int i = 0; i < inventory.getViewers().size(); i++) {
|
|
if(!inventory.getViewers().get(i).equals(view.watched.getViewers().get(i))) {
|
|
continue invLoop;
|
|
}
|
|
}
|
|
|
|
// a watched user is in a chest
|
|
if(view.isPlayerInventory() && !playerInventory) {
|
|
inventory = view.getPlayerInventory().getHolder().getInventory();
|
|
playerInventory = true;
|
|
}
|
|
|
|
if(playerInventory) {
|
|
this.previewPlayerInventory(viewer, (PlayerInventory) inventory);
|
|
} else {
|
|
this.previewInventory(viewer, inventory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredInventory(final InventoryClickEvent event) {
|
|
this.scheduleCheck((Player) event.getWhoClicked());
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredInventory(final InventoryDragEvent event) {
|
|
this.scheduleCheck((Player) event.getWhoClicked());
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredTransform(final PlayerBlockTransformEvent event) {
|
|
MatchPlayer player = event.getPlayer();
|
|
if(player != null) this.scheduleCheck(player.getBukkit());
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredPickup(final PlayerPickupItemEvent event) {
|
|
this.scheduleCheck(event.getPlayer());
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredDrop(final PlayerDropItemEvent event) {
|
|
this.scheduleCheck(event.getPlayer());
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredDamage(final EntityDamageEvent event) {
|
|
if(event.getEntity() instanceof Player) {
|
|
this.scheduleCheck((Player) event.getEntity());
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredHealth(final EntityRegainHealthEvent event) {
|
|
if(event.getEntity() instanceof Player) {
|
|
Player player = (Player) event.getEntity();
|
|
if(player.getHealth() == player.getMaxHealth()) return;
|
|
this.scheduleCheck((Player) event.getEntity());
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
public void updateMonitoredHunger(final FoodLevelChangeEvent event) {
|
|
this.scheduleCheck((Player) event.getEntity());
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.MONITOR)
|
|
public void updateMonitoredSpawn(final ParticipantSpawnEvent event) {
|
|
// must have this hack so we update player's inventories when they respawn and recieve a kit
|
|
ViewInventoryMatchModule.this.scheduleCheck(event.getPlayer().getBukkit());
|
|
}
|
|
|
|
public boolean canPreviewInventory(Player viewer, Player holder) {
|
|
MatchPlayer matchViewer = getMatch().getPlayer(viewer);
|
|
MatchPlayer matchHolder = getMatch().getPlayer(holder);
|
|
return matchViewer != null && matchHolder != null && canPreviewInventory(matchViewer, matchHolder);
|
|
}
|
|
|
|
public boolean canPreviewInventory(MatchPlayer viewer, MatchPlayer holder) {
|
|
return viewer.isObserving() && holder.isSpawned();
|
|
}
|
|
|
|
protected void scheduleCheck(Player updater) {
|
|
updateQueue.computeIfAbsent(updater, player -> Instant.now().plus(TICK));
|
|
}
|
|
|
|
protected void checkMonitoredInventories(Player updater) {
|
|
views.forEach((viewer, view) -> {
|
|
if(view.isPlayerInventory() && updater.equals(view.getPlayerInventory().getHolder())) {
|
|
previewPlayerInventory(viewer, view.getPlayerInventory());
|
|
}
|
|
});
|
|
}
|
|
|
|
protected void previewPlayerInventory(Player viewer, PlayerInventory inventory) {
|
|
if(viewer == null) { return; }
|
|
|
|
Player holder = (Player) inventory.getHolder();
|
|
// Ensure that the title of the inventory is <= 32 characters long to appease Minecraft's restrictions on inventory titles
|
|
String title = StringUtils.substring(holder.getDisplayName(viewer), 0, 32);
|
|
|
|
Inventory preview = Bukkit.getServer().createInventory(viewer, 45, title);
|
|
|
|
// handle inventory mapping
|
|
for(int i = 0; i <= 35; i++) {
|
|
preview.setItem(getInventoryPreviewSlot(i), inventory.getItem(i));
|
|
}
|
|
|
|
MatchPlayer matchHolder = this.match.getPlayer(holder);
|
|
if (matchHolder != null && matchHolder.isParticipating()) {
|
|
BlitzMatchModule module = matchHolder.getMatch().getMatchModule(BlitzMatchModuleImpl.class);
|
|
if(module != null) {
|
|
int livesLeft = module.livesCount(matchHolder);
|
|
ItemStack lives = new ItemStack(Material.EGG, livesLeft);
|
|
ItemMeta lifeMeta = lives.getItemMeta();
|
|
lifeMeta.addItemFlags(ItemFlag.values());
|
|
String key = livesLeft == 1 ? "match.blitz.livesRemaining.singularLives" : "match.blitz.livesRemaining.pluralLives";
|
|
lifeMeta.setDisplayName(ChatColor.GREEN + PGMTranslations.get().t(key, viewer, ChatColor.AQUA + String.valueOf(livesLeft) + ChatColor.GREEN));
|
|
lives.setItemMeta(lifeMeta);
|
|
preview.setItem(4, lives);
|
|
}
|
|
|
|
List<String> specialLore = new ArrayList<>();
|
|
|
|
if(holder.getAllowFlight()) {
|
|
specialLore.add(ChatColor.LIGHT_PURPLE + PGMTranslations.get().t("specialAbility.flying", viewer));
|
|
}
|
|
|
|
DoubleJumpMatchModule djmm = matchHolder.getMatch().getMatchModule(DoubleJumpMatchModule.class);
|
|
if(djmm != null && djmm.hasKit(matchHolder)) {
|
|
specialLore.add(ChatColor.LIGHT_PURPLE + PGMTranslations.get().t("specialAbility.doubleJump", viewer));
|
|
}
|
|
|
|
double knockbackResistance = holder.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE).getValue();
|
|
if(knockbackResistance > 0) {
|
|
specialLore.add(ChatColor.LIGHT_PURPLE + PGMTranslations.get().t("specialAbility.knockbackResistance", viewer, (int) Math.ceil(knockbackResistance * 100)));
|
|
}
|
|
|
|
double knockbackReduction = holder.getKnockbackReduction();
|
|
if(knockbackReduction > 0) {
|
|
specialLore.add(ChatColor.LIGHT_PURPLE + PGMTranslations.get().t("specialAbility.knockbackReduction", viewer, (int) Math.ceil(knockbackReduction * 100)));
|
|
}
|
|
|
|
double walkSpeed = holder.getWalkSpeed();
|
|
if(walkSpeed != WalkSpeedKit.BUKKIT_DEFAULT) {
|
|
specialLore.add(ChatColor.LIGHT_PURPLE + PGMTranslations.get().t("specialAbility.walkSpeed", viewer, String.format("%.1f", walkSpeed / WalkSpeedKit.BUKKIT_DEFAULT)));
|
|
}
|
|
|
|
|
|
if(!specialLore.isEmpty()) {
|
|
ItemStack special = new ItemStack(Material.NETHER_STAR);
|
|
ItemMeta specialMeta = special.getItemMeta();
|
|
specialMeta.addItemFlags(ItemFlag.values());
|
|
specialMeta.setDisplayName(ChatColor.AQUA.toString() + ChatColor.ITALIC + PGMTranslations.get().t("player.inventoryPreview.specialAbilities", viewer));
|
|
specialMeta.setLore(specialLore);
|
|
special.setItemMeta(specialMeta);
|
|
preview.setItem(5, special);
|
|
}
|
|
}
|
|
|
|
// potions
|
|
boolean hasPotions = holder.getActivePotionEffects().size() > 0;
|
|
ItemStack potions = new ItemStack(hasPotions? Material.POTION : Material.GLASS_BOTTLE);
|
|
ItemMeta potionMeta = potions.getItemMeta();
|
|
potionMeta.addItemFlags(ItemFlag.values());
|
|
potionMeta.setDisplayName(ChatColor.AQUA.toString() + ChatColor.ITALIC + PGMTranslations.get().t("player.inventoryPreview.potionEffects", viewer));
|
|
List<String> lore = Lists.newArrayList();
|
|
if(hasPotions) {
|
|
for(PotionEffect effect : holder.getActivePotionEffects()) {
|
|
lore.add(ChatColor.YELLOW + BukkitUtils.potionEffectTypeName(effect.getType()) + " " + (effect.getAmplifier() + 1));
|
|
}
|
|
} else {
|
|
lore.add(ChatColor.YELLOW + PGMTranslations.get().t("player.inventoryPreview.noPotionEffects", viewer));
|
|
}
|
|
potionMeta.setLore(lore);
|
|
potions.setItemMeta(potionMeta);
|
|
preview.setItem(6, potions);
|
|
|
|
// hunger and health
|
|
ItemStack hunger = new ItemStack(Material.COOKED_BEEF, holder.getFoodLevel());
|
|
ItemMeta hungerMeta = hunger.getItemMeta();
|
|
hungerMeta.addItemFlags(ItemFlag.values());
|
|
hungerMeta.setDisplayName(ChatColor.AQUA.toString() + ChatColor.ITALIC + PGMTranslations.get().t("player.inventoryPreview.hungerLevel", viewer));
|
|
hungerMeta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS);
|
|
hunger.setItemMeta(hungerMeta);
|
|
preview.setItem(7, hunger);
|
|
|
|
ItemStack health = new ItemStack(Material.REDSTONE, (int) holder.getHealth());
|
|
ItemMeta healthMeta = health.getItemMeta();
|
|
healthMeta.addItemFlags(ItemFlag.values());
|
|
healthMeta.setDisplayName(ChatColor.AQUA.toString() + ChatColor.ITALIC + PGMTranslations.get().t("player.inventoryPreview.healthLevel", viewer));
|
|
healthMeta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS);
|
|
health.setItemMeta(healthMeta);
|
|
preview.setItem(8, health);
|
|
|
|
// set armor manually because craftbukkit is a derp
|
|
preview.setItem(0, inventory.getHelmet());
|
|
preview.setItem(1, inventory.getChestplate());
|
|
preview.setItem(2, inventory.getLeggings());
|
|
preview.setItem(3, inventory.getBoots());
|
|
|
|
this.showInventoryPreview(viewer, inventory, preview);
|
|
}
|
|
|
|
public void previewInventory(Player viewer, Inventory realInventory) {
|
|
if(viewer == null) { return; }
|
|
|
|
if(realInventory instanceof PlayerInventory) {
|
|
previewPlayerInventory(viewer, (PlayerInventory) realInventory);
|
|
}else {
|
|
Inventory fakeInventory;
|
|
if(realInventory instanceof DoubleChestInventory) {
|
|
if(realInventory.hasCustomName()) {
|
|
fakeInventory = Bukkit.createInventory(viewer, realInventory.getSize(), realInventory.getName());
|
|
} else {
|
|
fakeInventory = Bukkit.createInventory(viewer, realInventory.getSize());
|
|
}
|
|
} else {
|
|
if(realInventory.hasCustomName()) {
|
|
fakeInventory = Bukkit.createInventory(viewer, realInventory.getType(), realInventory.getName());
|
|
} else {
|
|
fakeInventory = Bukkit.createInventory(viewer, realInventory.getType());
|
|
}
|
|
}
|
|
fakeInventory.setContents(realInventory.contents());
|
|
|
|
this.showInventoryPreview(viewer, realInventory, fakeInventory);
|
|
}
|
|
}
|
|
|
|
protected void showInventoryPreview(Player viewer, Inventory realInventory, Inventory fakeInventory) {
|
|
if(viewer == null) return;
|
|
|
|
View view = views.get(viewer);
|
|
if(view != null && view.watched.equals(realInventory) && view.preview.getSize() == fakeInventory.getSize()) {
|
|
view.preview.setContents(fakeInventory.contents());
|
|
} else {
|
|
view = new View(realInventory, fakeInventory);
|
|
views.put(viewer, view);
|
|
viewer.openInventory(fakeInventory);
|
|
}
|
|
}
|
|
|
|
private static class View {
|
|
final Inventory watched;
|
|
final Inventory preview;
|
|
|
|
View(Inventory watched, Inventory preview) {
|
|
this.watched = watched;
|
|
this.preview = preview;
|
|
}
|
|
|
|
boolean isPlayerInventory() {
|
|
return this.watched instanceof PlayerInventory;
|
|
}
|
|
|
|
PlayerInventory getPlayerInventory() {
|
|
return (PlayerInventory) this.watched;
|
|
}
|
|
}
|
|
}
|