Potionspy remake

Remade the entire potionspy and monitor class to avoid spam in the chat.

Furthermore, there is now a way to look at a history of potions thrown (individually and globally) however this history is limited to avoid too much useless data in the memory.
This commit is contained in:
CoolJWB 2020-07-14 21:00:22 +02:00
parent cb108e0c13
commit 6e622ad2f3
4 changed files with 357 additions and 48 deletions

View File

@ -1,26 +1,61 @@
package me.totalfreedom.totalfreedommod;
import java.text.DecimalFormat;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import me.totalfreedom.totalfreedommod.util.FUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.LingeringPotionSplashEvent;
import org.bukkit.event.entity.PotionSplashEvent;
import org.bukkit.projectiles.ProjectileSource;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public class Monitors extends FreedomService
{
private final DecimalFormat decimalFormat = new DecimalFormat("#");
private String potionSpyPrefix = ChatColor.DARK_GRAY + "[" + ChatColor.YELLOW + "PotionSpy" + ChatColor.DARK_GRAY + "] ";
@Getter
private List<Map.Entry<ThrownPotion, Long>> allThrownPotions = new ArrayList<>();
private Map<Player, List<ThrownPotion>> recentlyThrownPotions = new HashMap<>();
private final List<PotionEffectType> badPotionEffects = new ArrayList<>(Arrays.asList(PotionEffectType.BLINDNESS,
PotionEffectType.LEVITATION, PotionEffectType.CONFUSION, PotionEffectType.SLOW, PotionEffectType.SLOW_DIGGING, PotionEffectType.HUNGER)); // A list of all effects that count as "troll".
@Override
public void onStart()
{
Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, () ->
{
for (Player player : recentlyThrownPotions.keySet())
{
if (plugin.al.isAdmin(player) && plugin.al.getAdmin(player).getPotionSpy())
{
List<ThrownPotion> playerThrownPotions = recentlyThrownPotions.get(player);
ThrownPotion latestThrownPotion = playerThrownPotions.get(playerThrownPotions.size() - 1); // Get most recently thrown potion for the position.
int potionsThrown = playerThrownPotions.size();
boolean trollPotions = false;
for(ThrownPotion potion : playerThrownPotions)
{
if(isTrollPotion(potion))
{
trollPotions = true;
}
}
FUtil.playerMsg(player, ChatColor.translateAlternateColorCodes('&', String.format("&8[&ePotionSpy&8] &r%s splashed %s %s at X: %s Y: %s Z: %s in the world '%s'%s.",
player.getName(), potionsThrown, potionsThrown == 1 ? "potion" : "potions", latestThrownPotion.getLocation().getBlockX(), latestThrownPotion.getLocation().getBlockY(), latestThrownPotion.getLocation().getBlockZ(),
latestThrownPotion.getWorld().getName(), trollPotions ? " &c(most likely troll potion/potions)" : "")));
}
}
recentlyThrownPotions.clear();
}, 0L, 40L);
}
@Override
@ -31,26 +66,25 @@ public class Monitors extends FreedomService
@EventHandler(priority = EventPriority.MONITOR)
public void onLingeringPotionSplash(LingeringPotionSplashEvent event)
{
ProjectileSource source = event.getEntity().getShooter();
if (!(source instanceof Player))
if (event.getEntity().getShooter() instanceof Player)
{
return;
}
Player player = (Player)source;
if (plugin.al.isAdmin((Player)event.getEntity().getShooter()))
{
return;
}
final Material droppedItem = event.getEntity().getItem().getType();
final Location location = player.getLocation();
for (Player p : server.getOnlinePlayers())
{
if (plugin.al.isAdmin(p) && plugin.al.getAdmin(p).getPotionSpy())
ThrownPotion potion = event.getEntity();
if(potion.getShooter() instanceof Player)
{
FUtil.playerMsg(p, potionSpyPrefix + ChatColor.WHITE + player.getName() + " splashed " + event.getEntity().getItem().getAmount() + " " + droppedItem + " at X: " + decimalFormat.format(location.getX()) + ", Y: " + decimalFormat.format(location.getY()) + ", Z: " + decimalFormat.format(location.getZ()) + ", in the world '" + location.getWorld().getName() + "'.");
Player player = (Player)potion.getShooter();
recentlyThrownPotions.putIfAbsent(player, new ArrayList<>());
recentlyThrownPotions.get(player).add(potion);
allThrownPotions.add(new AbstractMap.SimpleEntry<>(potion, System.currentTimeMillis()));
if(recentlyThrownPotions.get(player).size() > 128)
{
recentlyThrownPotions.get(player).remove(0);
}
if(allThrownPotions.size() > 1024)
{
allThrownPotions.remove(0); // Remove the first element in the set.
}
}
}
}
@ -58,27 +92,67 @@ public class Monitors extends FreedomService
@EventHandler(priority = EventPriority.MONITOR)
public void onPotionSplash(PotionSplashEvent event)
{
ProjectileSource source = event.getEntity().getShooter();
if (!(source instanceof Player))
if (event.getEntity().getShooter() instanceof Player)
{
return;
}
Player player = (Player)source;
if (plugin.al.isAdmin((Player)event.getEntity().getShooter()))
{
return;
}
final Material droppedItem = event.getPotion().getItem().getType();
final Location location = player.getLocation();
for (Player p : server.getOnlinePlayers())
{
if (plugin.al.isAdmin(p) && plugin.al.getAdmin(p).getPotionSpy())
ThrownPotion potion = event.getEntity();
if(potion.getShooter() instanceof Player)
{
FUtil.playerMsg(p, potionSpyPrefix + ChatColor.WHITE + player.getName() + " splashed " + event.getEntity().getItem().getAmount() + " " + droppedItem + " at X: " + decimalFormat.format(location.getX()) + ", Y: " + decimalFormat.format(location.getY()) + ", Z: " + decimalFormat.format(location.getZ()) + ", in the world '" + location.getWorld().getName() + "'.");
Player player = (Player)potion.getShooter();
recentlyThrownPotions.putIfAbsent(player, new ArrayList<>());
recentlyThrownPotions.get(player).add(potion);
allThrownPotions.add(new AbstractMap.SimpleEntry<>(potion, System.currentTimeMillis()));
if(recentlyThrownPotions.get(player).size() > 128)
{
recentlyThrownPotions.get(player).remove(0);
}
if(allThrownPotions.size() > 1024)
{
allThrownPotions.remove(0); // Remove the first element in the set.
}
}
}
}
/**
* Get a list of potions the player has thrown with unix time stamps.
* @param player The player that has thrown potions.
* @return A list of map entries with the key as the thrown potion and the value as a long (unix time stamp when the throw happened).
*/
public List<Map.Entry<ThrownPotion, Long>> getPlayerThrownPotions(Player player)
{
List<Map.Entry<ThrownPotion, Long>> thrownPotions = new ArrayList<>();
for(Map.Entry<ThrownPotion, Long> potionEntry : allThrownPotions)
{
ThrownPotion potion = potionEntry.getKey();
if(potion.getShooter() != null && potion.getShooter().equals(player))
{
thrownPotions.add(potionEntry);
}
}
return thrownPotions;
}
/**
* Detects if a thrown potion is most likely a troll potion.
* @param potion Any thrown potion that should be checked.
* @return A boolean that indicates if the potion param is most likely a troll potion.
*/
public boolean isTrollPotion(ThrownPotion potion)
{
int badEffectsDetected = 0;
for(PotionEffect effect : potion.getEffects())
{
if(badPotionEffects.contains(effect.getType()) && effect.getAmplifier() > 2 && effect.getDuration() > 200)
{
badEffectsDetected++;
}
}
return badEffectsDetected > 0;
}
}

View File

@ -208,7 +208,8 @@ public class EventBlocker extends FreedomService
// TODO: Revert back to old redstone block system when (or if) it is fixed in Bukkit, Spigot or Paper.
private ArrayList<Material> redstoneBlocks = new ArrayList<>(Arrays.asList(Material.REDSTONE, Material.DISPENSER, Material.DROPPER, Material.REDSTONE_LAMP));
@EventHandler
public void onBlockPhysics(BlockPhysicsEvent event) {
public void onBlockPhysics(BlockPhysicsEvent event)
{
if (!ConfigEntry.ALLOW_REDSTONE.getBoolean())
{
// Check if the block is involved with redstone.

View File

@ -1,24 +1,227 @@
package me.totalfreedom.totalfreedommod.command;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import me.totalfreedom.totalfreedommod.admin.Admin;
import me.totalfreedom.totalfreedommod.rank.Rank;
import me.totalfreedom.totalfreedommod.util.FUtil;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.entity.ThrownPotion;
@CommandPermissions(level = Rank.SUPER_ADMIN, source = SourceType.ONLY_IN_GAME)
@CommandParameters(description = "Allows admins to see every time a potion is splashed.", usage = "/<command>", aliases = "potspy")
@CommandParameters(description = "Allows admins to see potions that are thrown.", usage = "/<command> <enable | on | disable | off> | history [player] <page>", aliases = "potspy")
public class Command_potionspy extends FreedomCommand
{
private String titleText = "&8&m------------------&r &ePotionSpy &8&m------------------&r";
private String validPageText = "Please specify a valid page number between 1 and %s.";
private String noPlayerRecord = "That player has not thrown any potions yet.";
private String splashedText = "&r%s splashed a potion at &eX: %s Y: %s Z: %s&r\nin the world '&e%s&r' about &e%s &rago%s.";
private String bottomText = "&8&m--------------------&r &e%s / %s &8&m--------------------&r";
@Override
public boolean run(CommandSender sender, Player playerSender, Command cmd, String commandLabel, String[] args, boolean senderIsConsole)
{
Admin admin = plugin.al.getAdmin(playerSender);
admin.setPotionSpy(!admin.getPotionSpy());
if(args.length <= 0)
{
setPotionSpyState(admin, !admin.getPotionSpy());
return true;
}
else
{
switch (args[0].toLowerCase())
{
case "enable":
case "on":
setPotionSpyState(admin, true);
break;
case "disable":
case "off":
setPotionSpyState(admin, false);
break;
case "history":
if(args.length == 3)
{
Player player = Bukkit.getPlayer(args[1]);
if(player == null)
{
msg(sender, "Please specify a valid player name.");
return true;
}
List<Map.Entry<ThrownPotion, Long>> thrownPotions = new ArrayList<>();
thrownPotions.addAll(plugin.mo.getPlayerThrownPotions(player)); // Make a copy of the list to avoid modifying the original.
List<String> potionThrowNotifications = new ArrayList<>();
int lastPage = (int)Math.ceil(thrownPotions.size() / 5.0);
if(thrownPotions.isEmpty())
{
msg(sender, noPlayerRecord);
return true;
}
if(!NumberUtils.isNumber(args[2]))
{
msg(sender, String.format(validPageText, lastPage));
return true;
}
Collections.reverse(thrownPotions);
int pageIndex = Integer.parseInt(args[2]);
for(Map.Entry<ThrownPotion, Long> potionEntry : thrownPotions)
{
ThrownPotion potion = potionEntry.getKey();
boolean trollPotions = plugin.mo.isTrollPotion(potion);
potionThrowNotifications.add(ChatColor.translateAlternateColorCodes('&', String.format(splashedText, player.getName(), potion.getLocation().getBlockX(),
potion.getLocation().getBlockY(), potion.getLocation().getBlockZ(), potion.getWorld().getName(), getUnixTimeDifference(potionEntry.getValue(), System.currentTimeMillis()), trollPotions ? " &c(most likely troll potion/potions)" : "")));
}
List<String> page = FUtil.getPageFromList(potionThrowNotifications, 5, pageIndex - 1);
if(!page.isEmpty())
{
msg(sender, ChatColor.translateAlternateColorCodes('&', titleText));
for (String potionThrowNotification : page)
{
msg(sender, potionThrowNotification);
}
}
else
{
msg(sender, String.format(validPageText, lastPage));
return true;
}
msg(sender, ChatColor.translateAlternateColorCodes('&', String.format(bottomText, pageIndex, lastPage)));
}
else if(args.length == 2)
{
List<Map.Entry<ThrownPotion, Long>> thrownPotions = new ArrayList<>();
thrownPotions.addAll(plugin.mo.getAllThrownPotions()); // Make a copy of the list to avoid modifying the original.
List<String> potionThrowNotifications = new ArrayList<>();
int lastPage = (int)Math.ceil(thrownPotions.size() / 5.0);
if(thrownPotions.isEmpty())
{
if(Bukkit.getPlayer(args[1]) != null)
{
msg(sender, noPlayerRecord);
}
else
{
msg(sender, "No potions have been thrown yet.");
}
return true;
}
if(!NumberUtils.isNumber(args[1]))
{
msg(sender, String.format(validPageText, lastPage));
return true;
}
Collections.reverse(thrownPotions);
int pageIndex = Integer.parseInt(args[1]);
for(Map.Entry<ThrownPotion, Long> potionEntry : thrownPotions)
{
ThrownPotion potion = potionEntry.getKey();
Player player = (Player)potion.getShooter();
boolean trollPotions = plugin.mo.isTrollPotion(potion);
if (player != null)
{
potionThrowNotifications.add(ChatColor.translateAlternateColorCodes('&', String.format(splashedText, player.getName(), potion.getLocation().getBlockX(),
potion.getLocation().getBlockY(), potion.getLocation().getBlockZ(), potion.getWorld().getName(), getUnixTimeDifference(potionEntry.getValue(), System.currentTimeMillis()), trollPotions ? " &c(most likely troll potion/potions)" : "")));
}
}
List<String> page = FUtil.getPageFromList(potionThrowNotifications, 5, pageIndex - 1);
if(!page.isEmpty())
{
msg(sender, ChatColor.translateAlternateColorCodes('&', titleText));
for (String potionThrowNotification : page)
{
msg(sender, potionThrowNotification);
}
}
else
{
msg(sender, String.format(validPageText, lastPage));
return true;
}
msg(sender, ChatColor.translateAlternateColorCodes('&', String.format(bottomText, pageIndex, lastPage)));
}
else
{
return false;
}
break;
default:
return false;
}
}
return true;
}
/**
* Sets and updates the potion spy state for an admin.
* @param admin The admin that the state should be changed for.
* @param state A boolean that will set the state of potion spy for the admin (enabled or disabled).
*/
private void setPotionSpyState(Admin admin, boolean state)
{
admin.setPotionSpy(state);
plugin.al.save(admin);
plugin.al.updateTables();
msg("PotionSpy is now " + (admin.getPotionSpy() ? "enabled." : "disabled."));
return true;
}
/**
* Get the unix time difference in string format (1h, 30m, 15s).
* @param past The unix time at the start.
* @param now The current unix time.
* @return A string that displays the time difference between the two unix time values.
*/
private String getUnixTimeDifference(long past, long now)
{
long unix = now - past;
long seconds = Math.round(unix / 1000.0);
if(seconds < 60)
{
return seconds + "s";
}
else
{
long minutes = Math.round(seconds / 60.0);
if(minutes < 60)
{
return minutes + "m";
}
else
{
long hours = Math.round(minutes / 60.0);
if(hours < 24)
{
return hours + "h";
}
else
{
return Math.round(hours / 24.0) + "d";
}
}
}
}
}

View File

@ -172,6 +172,37 @@ public class FUtil
return Arrays.asList(string.split(", "));
}
/**
* A way to get a sublist with a page index and a page size.
* @param list A list of objects that should be split into pages.
* @param size The size of the pages.
* @param index The page index, if outside of bounds error will be thrown. The page index starts at 0 as with all lists.
* @return A list of objects that is the page that has been selected from the previous last parameter.
*/
public static List<String> getPageFromList(List<String> list, int size, int index)
{
try
{
if (size >= list.size())
{
return list;
}
else if (size * (index + 1) <= list.size())
{
return list.subList(size * index, size * (index + 1));
}
else
{
return list.subList(size * index, (size * index) + (list.size() % size));
}
}
catch (IndexOutOfBoundsException e)
{
return new ArrayList<>();
}
}
public static List<String> getAllMaterialNames()
{
List<String> names = new ArrayList<>();