Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e7b3b3b2 | ||
|
|
ed31471e4f | ||
|
|
20db0ac775 | ||
|
|
e4c1164b7a | ||
|
|
2ec3b0e232 | ||
|
|
c7f310da18 | ||
|
|
5d11841e4a | ||
|
|
804a60f169 | ||
|
|
d9176f84b5 | ||
|
|
27d37b5519 | ||
|
|
9861b67ab1 | ||
|
|
40f1810322 | ||
|
|
abcc4f318f | ||
|
|
34602a43da | ||
|
|
3f57b8cac3 | ||
|
|
51994670f9 | ||
|
|
0ce0b16a33 | ||
|
|
947225a8ce | ||
|
|
173c29d76f | ||
|
|
a77a433303 | ||
|
|
c3ee1b3975 | ||
|
|
a41089d8f2 | ||
|
|
5c26b304da | ||
|
|
20020bc97f | ||
|
|
c2fcca0ed5 | ||
|
|
3624823d71 | ||
|
|
e0bfcf7d5a | ||
|
|
a0df064a76 | ||
|
|
fb710722b5 | ||
|
|
8f21a6a6d8 | ||
|
|
9045f1c2a3 | ||
|
|
fd23d6ec55 | ||
|
|
fdaa22e435 | ||
|
|
a8671c27f6 | ||
|
|
540e091661 | ||
|
|
064ff5d338 | ||
|
|
3b22331bb1 | ||
|
|
c9d882bf71 | ||
|
|
2c76209b08 | ||
|
|
0299987a17 | ||
|
|
5a17d84a86 | ||
|
|
b637d83a93 | ||
|
|
811ce25a7f | ||
|
|
7e822d0bb3 | ||
|
|
d6f69499bc | ||
|
|
1214e3a885 | ||
|
|
0363713fb6 | ||
|
|
ba2507cdab | ||
|
|
60ccc22e20 | ||
|
|
34410393f1 | ||
|
|
88cdcc6ebb | ||
|
|
306b4a9087 | ||
|
|
4e09b5bc2c | ||
|
|
755be49173 | ||
|
|
ab83ed2500 | ||
|
|
71c7ef1ae2 | ||
|
|
99fd783aa6 | ||
|
|
6d9b21dcad | ||
|
|
e08078b050 | ||
|
|
a5630d47ba | ||
|
|
8449e3e261 | ||
|
|
ae41533e60 | ||
|
|
0d13425ae2 | ||
|
|
0a613a7f14 | ||
|
|
601294ae91 | ||
|
|
58dc0576c3 | ||
|
|
25dcf204f6 | ||
|
|
6ca76cce58 | ||
|
|
45e8fd8414 | ||
|
|
b838f148a8 | ||
|
|
605309ef33 | ||
|
|
f19fcd5abb | ||
|
|
6f6d97af07 | ||
|
|
73b26beb86 | ||
|
|
8a0e6f6b29 | ||
|
|
50c9be6bcf | ||
|
|
a6832c864d | ||
|
|
f2b0adf9d4 | ||
|
|
da1541c6fb | ||
|
|
2bf277a627 | ||
|
|
a04e60eec8 | ||
|
|
16c5cb96b5 | ||
|
|
7ff18f2808 | ||
|
|
b1c712bd79 | ||
|
|
6266a6e9da | ||
|
|
e61bb5159d | ||
|
|
052b8b0834 | ||
|
|
56566ef997 | ||
|
|
5bd0b64e4c | ||
|
|
07fb6624eb | ||
|
|
824d3a876f | ||
|
|
f6c457759e | ||
|
|
d8d65b19f4 | ||
|
|
2c15a6fd99 |
No files matched your search
@@ -0,0 +1,25 @@
|
||||
# Editorconfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
continuation_indent_size = 8
|
||||
|
||||
[*.java]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.xml]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,158 @@
|
||||
Simple Admin Hacks
|
||||
==================
|
||||
|
||||
Landing spot for fixes, tuning, micro game modifications that have no other home.
|
||||
|
||||
This is tweak central.
|
||||
|
||||
Brought to you by DevotedMC, we're taking a pretty inclusive and permissive view
|
||||
for this plugin; if you need a particular "hack" to get things working in your
|
||||
use case, send us a PR for inclusion, we'd love to host it.
|
||||
|
||||
This plugin is designed from the ground up to support easy creation of "microplugins"
|
||||
-- things with featuresets too small to make sense for a full sized plugin, or fixes
|
||||
for game problems or simple heuristic loggers.
|
||||
|
||||
Making a new "Hack" is always an option, but take a look at this list of active
|
||||
Hacks and see if your contribution might fit with an existing module.
|
||||
|
||||
## Active Hacks
|
||||
|
||||
Any hack can be theoretically live-disabled and enabled using:
|
||||
`hacks <hackname> enable` or `hacks <hackname> disable` where the hackname is
|
||||
as listed.
|
||||
|
||||
Type `hacks` to see what the current enable/disable status of all loaded hacks is.
|
||||
|
||||
Type `hacks <hackname>` to see a readout of that particular hack's status or config.
|
||||
|
||||
Type `hacks <hackname> get <configitem>` to see the current configuration setting for that config element in that hack.
|
||||
|
||||
Type `hacks <hackname> set <configitem> <new value>` to set a new configuratoin value for that config element in that hack. Not all hacks support this.
|
||||
|
||||
### BadBoyWatch
|
||||
|
||||
This hack keeps track of who breaks what, and what they broke to get there. I've got
|
||||
bigger plans for more heuristics but for now, that's it. Please, feel free to improve
|
||||
on this.
|
||||
|
||||
### CTAnnounce
|
||||
|
||||
An integration into CombatTagPlus, this announces to in-game operators when combat
|
||||
is under way. Invaluable when you like your admins to monitor in-game fights.
|
||||
|
||||
### Experimental
|
||||
|
||||
This is a catch-all for anything micro that has no other home. Useful features like
|
||||
`/serialize` command live here too, but mostly its logging trackers that are great
|
||||
for debugging edge cases and useless for normal runtime.
|
||||
|
||||
### GameFeatures
|
||||
|
||||
The principle focus here is on enabling and disabling game features, like elytra or
|
||||
skulker boxes or those sorts of things. Try to keep the focus on boolean or otherwise
|
||||
simple on / off or discrete state options that relate to existing Minecraft game
|
||||
mechanics.
|
||||
|
||||
### GameFixes
|
||||
|
||||
Sometimes things in Minecraft just... don't work. Or don't work the way you want them
|
||||
to. Put those fixes here.
|
||||
|
||||
### GameTuning
|
||||
|
||||
Similar to Game Fixes, but distinct in that the intent here is on hacks that _tune_
|
||||
or alter existing mechanics without fully disabling them or without fixing them.
|
||||
Things like altering armor damage reduction or tool impact would be good fits.
|
||||
|
||||
### HackBot
|
||||
|
||||
An experimental module whose complexity is approaching divorce from SAH, this is
|
||||
a bit of mid-work code that allows operators to define static Bots that show up
|
||||
like "real" players but aren't.
|
||||
|
||||
### Introbook
|
||||
|
||||
Allows the creation of customizeable, helpful first join books that are distributed
|
||||
to players when they first join the server. Safe to turn off but super useful
|
||||
if you have a core set of additional "good to know" information or introductory
|
||||
material you'd like players to receive.
|
||||
|
||||
### InvControl
|
||||
|
||||
Host of the `/invsee` command, gives insight into active player inventory, armor,
|
||||
health and more. I hope / intend to add another portion to this that allows online
|
||||
and offline inventory management via `/invmod` but this is as of now not started.
|
||||
|
||||
### InvisibleFix and InvisibleFixTwo
|
||||
|
||||
My hope is to remove these in time, but for 1.9 and 1.10 minecraft players
|
||||
go invisible all the time. Both of these hacks use different approaches to help
|
||||
preserve the visiblity of players. The second hack is the more invasive, sending
|
||||
explicit location packets when the situation indicates its usefulness.
|
||||
|
||||
### NewfriendAssist
|
||||
|
||||
A new join tracker, that also doubles as an introkit delivery mechanism.
|
||||
It also exposes the `introkit` command you can use to send specific people a new
|
||||
introkit if they lost theirs.
|
||||
|
||||
### ReinforcedChestBreak
|
||||
|
||||
Another useful alerting hack, sends a message to online operators when players
|
||||
are actively breaking reinforced containers.
|
||||
|
||||
### SanityHack
|
||||
|
||||
This one is pretty Devoted iteration 3 specific. Allows tracking server wide of
|
||||
all breaks or builds under a specific y level.
|
||||
|
||||
### TimingsHack
|
||||
|
||||
This is mature enough to be its own plugin now, but for the moment I'm leaving it in
|
||||
because IMHO it is an essential component for debugging and maintaining your server.
|
||||
|
||||
This hack begins where /timings and warmroast end. It generates in-game visualizations
|
||||
of per-tick time utilization, and allows you to investigate with extreme effectiveness
|
||||
exactly what is causing instantaneous or irregular lag. Things that average out, but lead
|
||||
to bad experiences for players.
|
||||
|
||||
To use it, there are a number of important commands.
|
||||
|
||||
First:
|
||||
|
||||
* `/showtimings` This gives the requester a "map" object that paints a heatmap, heightmap,
|
||||
and linegraph of TPS. Each vertical pixel is a full 20 ticks; each dot in the heatmap
|
||||
is a single tick. The heightmap above the heatmap (heatmap is on the bottom) is both
|
||||
colored and sized based on time-in-tick. At the very top is line graph of relative "health"
|
||||
of the tick. The closer to the grey line the better; the closer to the top, the worse.
|
||||
* `/thresholdtimings` This starts a very high resolution (1ms) stack inspection tracker.
|
||||
It keeps track _per tick_ with usually 50 samples per tick of which methods and classes
|
||||
are being taking up time within the tick. Give it a threshold factor -- please, something more
|
||||
then 1, 5 to 10 works well -- where any tick that takes longer then threshold factor * average
|
||||
tick length, dumps the class/method list to requestor (either console or in-game character).
|
||||
The list is in sorted order of class where most time is spent down to class where least time
|
||||
is spent.
|
||||
* `/listtimings` In you already suspect something, you can use this command to begin a spammy
|
||||
dump of all classes encountered during high-resolution tracking. Only new classes are printed,
|
||||
and it shuts itself off if nothing new has been printed for a full second.
|
||||
* `/bindtimings` This generates an in-game map displaying (like `/showtimings`) a heatmap, heightmap,
|
||||
and line graph of per-tick utilization of any Classes that _contain_ whatever is passed in after
|
||||
bindtimings. Note this is saved, and on reload of the server if HQ timings are restarted those
|
||||
maps will re-bind to the saved bindings. This is a massively useful deep inspection tool;
|
||||
you can put the "based" class of a suspect plugin as the parameter, and watch its actual per-tick
|
||||
time utilization in real-time. Destroy the map object to release the drain on overall TPS that
|
||||
results from the comparison functions used to update the map.
|
||||
* `/stoptimings` This turns off any HQ functions. Has the impact of _pausing_ any bindtimings maps,
|
||||
and releasing all HQ functions from impacting Tick (also stops tracking data, so data tracking will
|
||||
reset if HQ restarted by calling any other HQ-tagged command).
|
||||
|
||||
In game example with some descriptions:
|
||||
|
||||
* From live server: http://imgur.com/a/zWNWo
|
||||
|
||||
## Conclusion
|
||||
|
||||
There is room for many more hacks and the list grows regularly. The pattern is simple;
|
||||
check any of the existing hacks for details by example.
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<groupId>com.programmerdan.minecraft</groupId>
|
||||
<artifactId>SimpleAdminHacks</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0.6</version>
|
||||
<version>1.2.1</version>
|
||||
<name>SimpleAdminHacks</name>
|
||||
<url>https://github.com/DevotedMC/SimpleAdminHacks</url>
|
||||
|
||||
|
||||
<build>
|
||||
<finalName>${project.name}-${project.version}</finalName>
|
||||
<plugins>
|
||||
@@ -17,8 +17,8 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<compilerArgument></compilerArgument>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -97,20 +97,30 @@
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>devoted-repo</id>
|
||||
<url>https://build.devotedmc.com/plugin/repository/everything/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>1.10</version>
|
||||
<version>1.12-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.minelink</groupId>
|
||||
<artifactId>CombatTagPlus</artifactId>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<version>1.3.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.mcstats.bukkit</groupId>
|
||||
<artifactId>metrics-lite</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.inventivetalent.npclib</groupId>
|
||||
@@ -132,14 +142,35 @@
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib-API</artifactId>
|
||||
<version>4.0-SNAPSHOT</version>
|
||||
<version>4.3.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>vg.civcraft.mc.namelayer</groupId>
|
||||
<artifactId>NameLayer</artifactId>
|
||||
<version>2.8.03</version>
|
||||
<version>2.11.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>vg.civcraft.mc.citadel</groupId>
|
||||
<artifactId>Citadel</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>vg.civcraft.mc.civmodcore</groupId>
|
||||
<artifactId>CivModCore</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Note: CrazyCrates isn't distributed in POM form.
|
||||
For this to build successfully, use install:install-file with
|
||||
attributes as below. -->
|
||||
<dependency>
|
||||
<groupId>me.badbones69</groupId>
|
||||
<artifactId>CrazyCrates</artifactId>
|
||||
<version>1.7.1.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -8,10 +11,10 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -19,6 +22,8 @@ import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
|
||||
import com.google.common.reflect.ClassPath;
|
||||
|
||||
/**
|
||||
* Wrapper for simple admin hacks, each doing a thing and each configurable.
|
||||
*
|
||||
@@ -50,14 +55,76 @@ public class SimpleAdminHacks extends JavaPlugin {
|
||||
try {
|
||||
this.config = new SimpleAdminHacksConfig(this, conf);
|
||||
} catch(InvalidConfigException e) {
|
||||
this.log(Level.SEVERE, "Failed to load config. Disabling plugin.", e);
|
||||
log(Level.SEVERE, "Failed to load config. Disabling plugin.", e);
|
||||
this.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Now load all the Hacks and register.
|
||||
ConfigurationSection hackConfigs = this.getConfig().getConfigurationSection("hacks");
|
||||
try {
|
||||
ClassPath getSamplersPath = ClassPath.from(this.getClassLoader());
|
||||
|
||||
for (ClassPath.ClassInfo clsInfo : getSamplersPath.getTopLevelClasses("com.programmerdan.minecraft.simpleadminhacks.hacks")) {
|
||||
try {
|
||||
Class<?> clazz = clsInfo.load();
|
||||
if (clazz != null && SimpleHack.class.isAssignableFrom(clazz)) {
|
||||
log(Level.INFO, "Found a hack class {0}, attempting to find a generating method and constructor", clazz.getName());
|
||||
ConfigurationSection hackConfig = hackConfigs.getConfigurationSection(clazz.getSimpleName());
|
||||
SimpleHackConfig hackingConfig = null;
|
||||
if (hackConfig != null) {
|
||||
try {
|
||||
Method genBasic = clazz.getMethod("generate", SimpleAdminHacks.class, ConfigurationSection.class);
|
||||
hackingConfig = (SimpleHackConfig) genBasic.invoke(null, this, hackConfig);
|
||||
} catch (IllegalAccessException failure) {
|
||||
log(Level.WARNING, "Creating configuration for hack {0} failed, illegal access failure", clazz.getName());
|
||||
} catch (IllegalArgumentException failure) {
|
||||
log(Level.WARNING, "Creating configuration for hack {0} failed, illegal argument failure", clazz.getName());
|
||||
} catch (InvocationTargetException failure) {
|
||||
log(Level.WARNING, "Creating configuration for hack {0} failed, invocation target failure", clazz.getName());
|
||||
}
|
||||
} else {
|
||||
log(Level.INFO, "Hack for {0} found but no configuration, skipping.", clazz.getSimpleName());
|
||||
}
|
||||
|
||||
if (hackingConfig != null) {
|
||||
log(Level.INFO, "Configuration for Hack {0} found, instance: {1}", clazz.getSimpleName(), hackingConfig.toString());
|
||||
SimpleHack<?> hack = null;
|
||||
try {
|
||||
Constructor<?> constructBasic = clazz.getConstructor(SimpleAdminHacks.class, hackingConfig.getClass());
|
||||
hack = (SimpleHack<?>) constructBasic.newInstance(this, hackingConfig);
|
||||
log(Level.INFO, "Created a new Hack of type {0}", clazz.getSimpleName());
|
||||
} catch (InvalidConfigException ice) {
|
||||
log(Level.WARNING, "Failed to activate {0} hack, configuration failed", clazz.getSimpleName());
|
||||
} catch (Exception e) {
|
||||
log(Level.WARNING, "Failed to activate {0} hack, configuration failed: {1}", clazz.getSimpleName(), e.getMessage());
|
||||
}
|
||||
|
||||
if (hack == null) {
|
||||
log(Level.WARNING, "Failed to create a Hack of type {0}", clazz.getSimpleName());
|
||||
} else {
|
||||
register(hack);
|
||||
log(Level.INFO, "Registered a new hack: {0}", clazz.getSimpleName());
|
||||
}
|
||||
} else {
|
||||
log(Level.INFO, "Configuration generation for Hack {0} failed, skipping.", clazz.getSimpleName());
|
||||
}
|
||||
}
|
||||
} catch (NoClassDefFoundError e) {
|
||||
log(Level.INFO, "Unable to load discovered class {0} due to dependency failure", clsInfo.getName());
|
||||
} catch (Exception e) {
|
||||
log(Level.WARNING, "Failed to complete hack discovery {0}", clsInfo.getName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log(Level.WARNING, "Failed to complete hack registration");
|
||||
}
|
||||
|
||||
|
||||
// Warning if no hacks.
|
||||
if (hacks == null || hacks.size() == 0) {
|
||||
this.log("No hacks enabled.");
|
||||
log(Level.WARNING, "No hacks enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -263,5 +330,13 @@ public class SimpleAdminHacks extends JavaPlugin {
|
||||
log(Level.WARNING, "Failed to register Executor for {0}, please define that command in the plugin.yml first.", command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Definitely not safe. Used for Insight hack.
|
||||
* @return the ClassLoader for this plugin.
|
||||
*/
|
||||
public ClassLoader exposeClassLoader() {
|
||||
return this.getClassLoader();
|
||||
}
|
||||
|
||||
}
|
||||
-57
@@ -4,19 +4,6 @@ import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.IntrobookConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.InvControlConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.NewfriendAssistConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.ExperimentalConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.hacks.CTAnnounce;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.hacks.HackBot;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.hacks.Introbook;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.hacks.InvControl;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.hacks.NewfriendAssist;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.hacks.Experimental;
|
||||
|
||||
/**
|
||||
* Baseline configuration for SimpleAdminHacks and parser for all actual Hacks.
|
||||
*
|
||||
@@ -60,23 +47,6 @@ public class SimpleAdminHacksConfig {
|
||||
if (this.debug) {
|
||||
this.plugin.log(Level.INFO, "broadcast_permission set to {0}", this.broadcastPermission);
|
||||
}
|
||||
|
||||
// Now load all the Hacks and register.
|
||||
ConfigurationSection hacks = config.getConfigurationSection("hacks");
|
||||
for (String key : hacks.getKeys(false)) {
|
||||
ConfigurationSection hack = hacks.getConfigurationSection(key);
|
||||
|
||||
// TODO eventually, replace this with reflection based load. For tonight, hack it.
|
||||
try {
|
||||
SimpleHack<?> newHack = bootstrapHack(hack);
|
||||
plugin.register(newHack);
|
||||
if (this.debug) {
|
||||
this.plugin.log(Level.INFO, "Registered a new hack: {0}", key);
|
||||
}
|
||||
} catch (InvalidConfigException ice) {
|
||||
this.plugin.log(Level.WARNING, key + " could not be mapped to a Hack, config values problem", ice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -96,31 +66,4 @@ public class SimpleAdminHacksConfig {
|
||||
public String getBroadcastPermission() {
|
||||
return this.broadcastPermission;
|
||||
}
|
||||
|
||||
private SimpleHack<?> bootstrapHack(ConfigurationSection boot) {
|
||||
String hackName = boot.getString("name");
|
||||
if (hackName == null) {
|
||||
throw new InvalidConfigException("Hack stubbed but config lacks a name, cannot determine which hack to load");
|
||||
}
|
||||
|
||||
try {
|
||||
if (hackName.equals(CTAnnounce.NAME)) {
|
||||
return new CTAnnounce(this.plugin, new CTAnnounceConfig(this.plugin, boot));
|
||||
} else if (hackName.equals(NewfriendAssist.NAME)) {
|
||||
return new NewfriendAssist(this.plugin, new NewfriendAssistConfig(this.plugin, boot));
|
||||
} else if (hackName.equals(Introbook.NAME)) {
|
||||
return new Introbook(this.plugin, new IntrobookConfig(this.plugin, boot));
|
||||
} else if (hackName.equals(HackBot.NAME)) {
|
||||
return new HackBot(this.plugin, new HackBotConfig(this.plugin, boot));
|
||||
} else if (hackName.equals(InvControl.NAME)) {
|
||||
return new InvControl(this.plugin, new InvControlConfig(this.plugin, boot));
|
||||
} else if (hackName.equals(Experimental.NAME)) {
|
||||
return new Experimental(this.plugin, new ExperimentalConfig(this.plugin, boot));
|
||||
}
|
||||
} catch (InvalidConfigException ice) {
|
||||
plugin.log(Level.WARNING, "Failed to activate " + hackName + " hack");
|
||||
}
|
||||
|
||||
throw new InvalidConfigException("Claimed to be a viable hack but isn't: " + hackName);
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ public abstract class SimpleHackConfig {
|
||||
public SimpleHackConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
this.plugin = plugin;
|
||||
this.base = base;
|
||||
this.name = base.getString("name");
|
||||
this.name = base.getString("name", base.getName());
|
||||
this.enabled = base.getBoolean("enabled", false);
|
||||
plugin.log(Level.INFO, "Config for {0}, enabled set to {1}", this.name, this.enabled);
|
||||
plugin.log(Level.INFO, "Config for {0}, enabled set to {1}, instance {2}", this.name, this.enabled, this.toString());
|
||||
this.wireup(base);
|
||||
}
|
||||
|
||||
@@ -85,4 +85,4 @@ public abstract class SimpleHackConfig {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* Configures this hack, determines which blocks to watch and the like.
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class BadBoyWatchConfig extends SimpleHackConfig {
|
||||
|
||||
private int trackingDepth;
|
||||
private boolean clearOnMatch;
|
||||
private int minDepthToMatch;
|
||||
private Set<Material> watchedMaterials;
|
||||
|
||||
private static final List<Material> defaultWatched = Arrays.asList(
|
||||
Material.CHEST,
|
||||
Material.JUKEBOX,
|
||||
Material.NOTE_BLOCK,
|
||||
Material.BEACON,
|
||||
Material.ENCHANTMENT_TABLE,
|
||||
Material.ENDER_CHEST,
|
||||
Material.FURNACE,
|
||||
Material.SPONGE);
|
||||
|
||||
public BadBoyWatchConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
this.trackingDepth = config.getInt("trackingDepth", 8); // keep up to 8 breaks
|
||||
plugin().log(Level.INFO, " Tracking Depth: {0}", this.trackingDepth);
|
||||
this.clearOnMatch = config.getBoolean("clearOnMatch", true); // once you print out don't keep old records
|
||||
plugin().log(Level.INFO, " Clear On Match: {0}", this.clearOnMatch);
|
||||
this.minDepthToMatch = config.getInt("minDepthToMatch", 2); // this block + 1
|
||||
plugin().log(Level.INFO, " Minimum Breakpath Depth To Match: {0}", this.minDepthToMatch);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> watch = (List<String>) config.getList("watchedMaterials");
|
||||
this.watchedMaterials = new HashSet<Material>();
|
||||
if (watch == null || watch.isEmpty()) {
|
||||
this.watchedMaterials.addAll(defaultWatched);
|
||||
plugin().log(Level.INFO, " Adding default watch set");
|
||||
} else {
|
||||
for (String mat : watch) {
|
||||
try {
|
||||
Material nmat = Material.matchMaterial(mat);
|
||||
this.watchedMaterials.add(nmat);
|
||||
plugin().log(Level.INFO, " Watching material {0}", nmat);
|
||||
} catch (Exception e) {
|
||||
plugin().log(Level.WARNING, " Invalid material {0} listed", mat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getTrackingDepth() {
|
||||
return trackingDepth;
|
||||
}
|
||||
|
||||
public boolean isClearOnMatch() {
|
||||
return clearOnMatch;
|
||||
}
|
||||
|
||||
public int getMinDepthToMatch() {
|
||||
return minDepthToMatch;
|
||||
}
|
||||
|
||||
public Set<Material> getWatchedMaterials() {
|
||||
return watchedMaterials;
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* CrazyCrates event hook hack
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*
|
||||
*/
|
||||
public class CrazyCratesAnnounceConfig extends SimpleHackConfig {
|
||||
|
||||
public CrazyCratesAnnounceConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
public CrazyCratesAnnounceConfig(ConfigurationSection base) {
|
||||
super(SimpleAdminHacks.instance(), base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* Holds configurations for the GameFeatures module.
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class GameFeaturesConfig extends SimpleHackConfig {
|
||||
|
||||
private boolean potatoXPEnabled;
|
||||
private boolean villagerTrading;
|
||||
private boolean witherSpawning;
|
||||
private boolean enderChestPlacement;
|
||||
private boolean enderChestUse;
|
||||
private boolean shulkerBoxUse;
|
||||
private boolean totemPowers;
|
||||
private boolean elytraUse;
|
||||
private boolean chorusFruitUse;
|
||||
|
||||
private boolean weepingAngel;
|
||||
private int weepingAngelEnv;
|
||||
private int weepingAngelPlayer;
|
||||
|
||||
private boolean blockWaterInHell;
|
||||
|
||||
public GameFeaturesConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
this.potatoXPEnabled = !config.getBoolean("disablePotatoXP", false);
|
||||
if (!this.potatoXPEnabled) plugin().log(" Potato XP Disabled");
|
||||
|
||||
this.villagerTrading = config.getBoolean("villagerTrading", false);
|
||||
if (!villagerTrading) plugin().log(" VillagerTrading is disabled");
|
||||
|
||||
this.witherSpawning = config.getBoolean("witherSpawning", false);
|
||||
if (!this.witherSpawning) plugin().log(" Wither Spawning is disabled");
|
||||
|
||||
this.enderChestPlacement = config.getBoolean("enderChestPlacement", true);
|
||||
if (!this.enderChestPlacement) plugin().log(" Placing EnderChests is disabled");
|
||||
|
||||
this.enderChestUse = config.getBoolean("enderChestUse", false);
|
||||
if (!this.enderChestUse) plugin().log(" Using EnderChests is disabled");
|
||||
|
||||
this.shulkerBoxUse = config.getBoolean("shulkerBoxUse", false);
|
||||
if (!this.shulkerBoxUse) plugin().log(" Using Shulker Boxes is disabled");
|
||||
|
||||
this.totemPowers = config.getBoolean("totemPower", false);
|
||||
if (!this.totemPowers) plugin().log(" Undeath via totems is disabled");
|
||||
|
||||
this.elytraUse = config.getBoolean("elytraUse", true);
|
||||
if (!this.elytraUse) plugin().log(" Elytra use is disabled");
|
||||
|
||||
this.chorusFruitUse = config.getBoolean("chorusFruitTeleportation", false);
|
||||
if (!this.chorusFruitUse) plugin().log(" Chorus Fruit Teleportation is disabled");
|
||||
|
||||
this.weepingAngel = config.getBoolean("weepingAngel.enabled", false);
|
||||
if (this.weepingAngel) {
|
||||
this.weepingAngelEnv = config.getInt("weepingAngel.environment", 1);
|
||||
this.weepingAngelPlayer = config.getInt("weepingAngel.playerKill", 5);
|
||||
|
||||
plugin().log(" Weeping Angel is enabled. Times | Env[" + weepingAngelEnv + "] PK[" + weepingAngelPlayer + "]");
|
||||
}
|
||||
|
||||
this.blockWaterInHell = config.getBoolean("blockWaterInHell", true);
|
||||
if (this.blockWaterInHell) plugin().log(" Blocking bucket use in hell biomes");
|
||||
|
||||
/* Add additional feature config grabs here. */
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If getting XP from potatos is enabled.
|
||||
*/
|
||||
public boolean isPotatoXPEnabled() {
|
||||
return this.potatoXPEnabled;
|
||||
}
|
||||
|
||||
public boolean isVillagerTrading() {
|
||||
return this.villagerTrading;
|
||||
}
|
||||
|
||||
public boolean isWitherSpawning() {
|
||||
return this.witherSpawning;
|
||||
}
|
||||
|
||||
public boolean isEnderChestPlacement() {
|
||||
return this.enderChestPlacement;
|
||||
}
|
||||
|
||||
public boolean isEnderChestUse() {
|
||||
return this.enderChestUse;
|
||||
}
|
||||
|
||||
public boolean isShulkerBoxUse() {
|
||||
return this.shulkerBoxUse;
|
||||
}
|
||||
|
||||
public boolean isTotemPowers() {
|
||||
return this.totemPowers;
|
||||
}
|
||||
|
||||
public boolean isElytraUse() {
|
||||
return this.elytraUse;
|
||||
}
|
||||
|
||||
public boolean isChorusFruitTeleportation() {
|
||||
return this.chorusFruitUse;
|
||||
}
|
||||
|
||||
public boolean isWeepingAngel() {
|
||||
return this.weepingAngel;
|
||||
}
|
||||
|
||||
public int getWeepingAngelEnv() {
|
||||
return this.weepingAngelEnv;
|
||||
}
|
||||
|
||||
public int getWeepingAngelPlayer() {
|
||||
return this.weepingAngelPlayer;
|
||||
}
|
||||
|
||||
public boolean isBlockWaterInHell() {
|
||||
return this.blockWaterInHell;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class GameFixesConfig extends SimpleHackConfig {
|
||||
|
||||
private boolean blockElytraBreakBug;
|
||||
private double damageOnElytraBreakBug;
|
||||
private boolean canStorageTeleport;
|
||||
private boolean stopHopperDupe;
|
||||
private boolean stopRailDupe;
|
||||
private boolean stopEndPortalDeletion;
|
||||
private boolean stopBedBombing;
|
||||
|
||||
private ArrayList<BlockFace> bfArray;
|
||||
private ArrayList<Material> railArray;
|
||||
private ArrayList<Material> pistonArray;
|
||||
|
||||
public GameFixesConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
wireUpArrays();
|
||||
}
|
||||
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
blockElytraBreakBug = config.getBoolean("blockElytraBreakBug", true);
|
||||
damageOnElytraBreakBug = config.getDouble("damageOnElytraBreakBug", 0.0d);
|
||||
if (blockElytraBreakBug) plugin().log(Level.INFO, " Block Elytra 1height break bug is enabled, doing {} damage to violators", damageOnElytraBreakBug);
|
||||
|
||||
canStorageTeleport = config.getBoolean("canStorageTeleport");
|
||||
if (!canStorageTeleport) plugin().log(" Storage holder teleportation is disabled.");
|
||||
|
||||
stopHopperDupe = config.getBoolean("stopHopperDupe");
|
||||
if (stopHopperDupe) plugin().log(" Stop Hopper self-feeding Dupe is enabled.");
|
||||
|
||||
stopRailDupe = config.getBoolean("stopRailDupe", true);
|
||||
if (stopRailDupe) plugin().log(" Stop Rail Dupe is enabled.");
|
||||
|
||||
stopEndPortalDeletion = config.getBoolean("stopEndPortalDeletion", true);
|
||||
if (stopEndPortalDeletion) plugin().log(" Stop End Portal Deletion is enabled.");
|
||||
|
||||
stopBedBombing = config.getBoolean("stopBedBombingInHellBiomes", true);
|
||||
if (stopBedBombing) plugin().log(" Stop Bed Bombing In Hell Biomes is enabled.");
|
||||
}
|
||||
|
||||
private void wireUpArrays() {
|
||||
bfArray = new ArrayList<BlockFace>();
|
||||
railArray = new ArrayList<Material>();
|
||||
pistonArray = new ArrayList<Material>();
|
||||
|
||||
railArray.add(Material.RAILS);
|
||||
railArray.add(Material.ACTIVATOR_RAIL);
|
||||
railArray.add(Material.DETECTOR_RAIL);
|
||||
railArray.add(Material.POWERED_RAIL);
|
||||
railArray.add(Material.CARPET);
|
||||
|
||||
bfArray.add(BlockFace.NORTH);
|
||||
bfArray.add(BlockFace.SOUTH);
|
||||
bfArray.add(BlockFace.EAST);
|
||||
bfArray.add(BlockFace.WEST);
|
||||
bfArray.add(BlockFace.UP);
|
||||
bfArray.add(BlockFace.DOWN);
|
||||
|
||||
pistonArray.add(Material.PISTON_BASE);
|
||||
pistonArray.add(Material.PISTON_EXTENSION);
|
||||
pistonArray.add(Material.PISTON_MOVING_PIECE);
|
||||
pistonArray.add(Material.PISTON_STICKY_BASE);
|
||||
}
|
||||
|
||||
public boolean isBlockElytraBreakBug() {
|
||||
return blockElytraBreakBug;
|
||||
}
|
||||
|
||||
public double getDamageOnElytraBreakBug() {
|
||||
return damageOnElytraBreakBug;
|
||||
}
|
||||
|
||||
public boolean canStorageTeleport() {
|
||||
return canStorageTeleport;
|
||||
}
|
||||
|
||||
public boolean isStopHopperDupe() {
|
||||
return stopHopperDupe;
|
||||
}
|
||||
|
||||
public boolean isStopRailDupe() {
|
||||
return stopRailDupe;
|
||||
}
|
||||
|
||||
public boolean isStopEndPortalDeletion() {
|
||||
return stopEndPortalDeletion;
|
||||
}
|
||||
|
||||
public ArrayList<BlockFace> getBfArray() {
|
||||
return bfArray;
|
||||
}
|
||||
|
||||
public ArrayList<Material> getRailArray() {
|
||||
return railArray;
|
||||
}
|
||||
|
||||
public ArrayList<Material> getPistonArray() {
|
||||
return pistonArray;
|
||||
}
|
||||
|
||||
public boolean stopBedBombing() {
|
||||
return this.stopBedBombing;
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* Holds configurations for the GameTuning module.
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class GameTuningConfig extends SimpleHackConfig {
|
||||
|
||||
private boolean chunkLimitsEnabled;
|
||||
private Map<Material, Integer> blockEntityLimits;
|
||||
private Set<UUID> exemptFromLimits;
|
||||
private String chunkLimitsExceededMessage;
|
||||
|
||||
private boolean daytimeBedEnabled;
|
||||
private String daytimeBedSpawnSetMessage;
|
||||
|
||||
private boolean oneToOneNether;
|
||||
private boolean returnNetherPortal;
|
||||
|
||||
private boolean chestedMinecartInventories;
|
||||
private boolean hopperMinecartInventories;
|
||||
|
||||
public GameTuningConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
ConfigurationSection chunkLimits = config.getConfigurationSection("chunkLimits");
|
||||
wireupChunkLimits(chunkLimits);
|
||||
|
||||
ConfigurationSection daytimeBed = config.getConfigurationSection("daytimeBed");
|
||||
wireupDaytimeBed(daytimeBed);
|
||||
|
||||
this.oneToOneNether = config.getBoolean("oneToOneNether", false);
|
||||
if (oneToOneNether) plugin().log("One to One Nether is enabled.");
|
||||
|
||||
this.returnNetherPortal = config.getBoolean("returnNetherPortal", true);
|
||||
if (!returnNetherPortal) plugin().log("Return Nether Portals disabled.");
|
||||
|
||||
this.chestedMinecartInventories = config.getBoolean("chestedMinecartInventories", true);
|
||||
if (!chestedMinecartInventories) plugin().log("Chested Minecart Inventories are disabled.");
|
||||
|
||||
this.hopperMinecartInventories = config.getBoolean("hopperMinecartInventories", true);
|
||||
if (!hopperMinecartInventories) plugin().log("Hopper Minecart Inventories are disabled.");
|
||||
|
||||
/* Add additional tuning config grabs here. */
|
||||
}
|
||||
|
||||
/**
|
||||
* Wireup for Chunk Limits configuration
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
private void wireupChunkLimits(ConfigurationSection config) {
|
||||
this.blockEntityLimits = new HashMap<Material, Integer>();
|
||||
this.exemptFromLimits = new HashSet<UUID>();
|
||||
this.chunkLimitsEnabled = false;
|
||||
if (config == null) return;
|
||||
|
||||
this.chunkLimitsEnabled = config.getBoolean("enabled", false);
|
||||
this.chunkLimitsExceededMessage = ChatColor.translateAlternateColorCodes('&',
|
||||
config.getString("exceededMessage",
|
||||
ChatColor.RED + "Limit for this chunk reached, you cannot place that! Use a different block."));
|
||||
|
||||
// for each "chunkLimits.tileEntities: " entry, record limit.
|
||||
ConfigurationSection tileEntities = config.getConfigurationSection("tileEntities");
|
||||
for (String key : tileEntities.getKeys(false)) {
|
||||
int limit = tileEntities.getInt(key);
|
||||
Material toBlock = Material.getMaterial(key);
|
||||
if (toBlock == null) continue;
|
||||
|
||||
this.blockEntityLimits.put(toBlock, limit);
|
||||
plugin().log(Level.INFO, " Limiting {0} to {1} per chunk", toBlock.toString(), limit);
|
||||
}
|
||||
|
||||
// for each "chunkLimits.exempt: " entry, record limit.
|
||||
List<String> exempts = config.getStringList("exempt");
|
||||
for (String exempt : exempts) {
|
||||
UUID pExempt = null;
|
||||
try {
|
||||
pExempt = UUID.fromString(exempt);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
pExempt = null;
|
||||
}
|
||||
if (pExempt == null) {
|
||||
OfflinePlayer ofp = Bukkit.getOfflinePlayer(exempt);
|
||||
if (ofp != null) {
|
||||
pExempt = ofp.getUniqueId();
|
||||
}
|
||||
}
|
||||
if (pExempt == null) {
|
||||
plugin().log(Level.INFO, " Unable to exempt {0}, player not found", exempt);
|
||||
} else {
|
||||
plugin().log(Level.INFO, " Adding limits exemption for {0} as {1}", exempt, pExempt);
|
||||
this.exemptFromLimits.add(pExempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wireup for enabling setting your spawn during the day.
|
||||
*
|
||||
* @author Amelorate
|
||||
*/
|
||||
private void wireupDaytimeBed(ConfigurationSection config) {
|
||||
if (config == null) {
|
||||
this.daytimeBedEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.daytimeBedEnabled = config.getBoolean("enabled", false);
|
||||
this.daytimeBedSpawnSetMessage = ChatColor.translateAlternateColorCodes('&',
|
||||
config.getString("spawnSetMessage", ChatColor.GRAY + "Your spawn has been set."));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true / false if chunk limits are on
|
||||
*/
|
||||
public boolean areChunkLimitsEnabled() {
|
||||
return this.chunkLimitsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chunk limit for a specified material.
|
||||
* Returns null if no limit is set.
|
||||
*
|
||||
* @param mat the Material to return a limit on, if set.
|
||||
* @return the max # per chunk, or null if no limit set.
|
||||
*/
|
||||
public Integer getChunkLimit(Material mat) {
|
||||
return this.chunkLimitsEnabled ? this.blockEntityLimits.get(mat) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the supplied UUID is exempt from limits.
|
||||
*
|
||||
* @param player the UUID to check
|
||||
* @returns false it the player is exempt, true otherwise.
|
||||
*/
|
||||
public boolean applyChunkLimits(UUID player) {
|
||||
return this.chunkLimitsEnabled ? !this.exemptFromLimits.contains(player) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the generic limit message. TODO: Replace with customized message tailored per player.
|
||||
*/
|
||||
public String getChunkLimitsExceededMessage() {
|
||||
return this.chunkLimitsExceededMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The message that is sent to the player if they right click on a bed and set their spawn.
|
||||
* Empty string if there should not be a message, and null if daytime beds are disabled.
|
||||
*/
|
||||
public String getDaytimeBedSpawnSetMessage() {
|
||||
return daytimeBedEnabled ? daytimeBedSpawnSetMessage : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If setting your spawn with a bed during the daytime is enabled.
|
||||
*/
|
||||
public boolean areDaytimeBedsEnabled() {
|
||||
return daytimeBedEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If one to one nether is enabled.
|
||||
*/
|
||||
public boolean isOneToOneNether() {
|
||||
return oneToOneNether;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If return portals are enabled.
|
||||
*/
|
||||
public boolean isReturnNetherPortal() {
|
||||
return returnNetherPortal;
|
||||
}
|
||||
|
||||
public boolean isChestedMinecartInventories() {
|
||||
return chestedMinecartInventories;
|
||||
}
|
||||
|
||||
public boolean isHopperMinecartInventories() {
|
||||
return hopperMinecartInventories;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* Define an "into" list of Strings where the Strings are event classpaths.
|
||||
* Can monitor not only bukkit events but also plugin / custom events; just give the
|
||||
* valid classpath.
|
||||
*
|
||||
* This bypasses the "safe" bukkit way of registering listeners for events. So it
|
||||
* also bypasses Timings.
|
||||
*
|
||||
* This is the only warning I'll give. Be careful with this and prefer to leave it off.
|
||||
*
|
||||
* Wireups only happen on restart, I'm dubious about injecting live for now.
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*
|
||||
*/
|
||||
public class InsightConfig extends SimpleHackConfig {
|
||||
private List<String> insightOn;
|
||||
|
||||
public InsightConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
if (config.contains("into")) {
|
||||
insightOn = config.getStringList("into");
|
||||
} else {
|
||||
insightOn = null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getInsightOn() {
|
||||
return insightOn;
|
||||
}
|
||||
}
|
||||
+20
-4
@@ -7,6 +7,7 @@ import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
@@ -82,12 +83,25 @@ public class IntrobookConfig extends SimpleHackConfig {
|
||||
*
|
||||
* @return ItemStack containing the book.
|
||||
*/
|
||||
public ItemStack getIntroBook() {
|
||||
public ItemStack getIntroBook(Player p) {
|
||||
ItemStack is = new ItemStack(Material.WRITTEN_BOOK);
|
||||
BookMeta bm = (BookMeta) is.getItemMeta();
|
||||
bm.setTitle(this.title);
|
||||
if (p == null) {
|
||||
bm.setTitle(this.title.replaceAll("\\$\\{player\\}", ""));
|
||||
} else {
|
||||
bm.setTitle(this.title.replaceAll("\\$\\{player\\}", p.getName()));
|
||||
}
|
||||
bm.setAuthor(this.author);
|
||||
bm.setPages(this.pages);
|
||||
ArrayList<String> alist = null;
|
||||
if (p == null) {
|
||||
alist = new ArrayList<String>(this.pages);
|
||||
} else {
|
||||
alist = new ArrayList<String>();
|
||||
for (String page : this.pages) {
|
||||
alist.add(page.replaceAll("\\$\\{player\\}", p.getName()));
|
||||
}
|
||||
}
|
||||
bm.setPages(alist);
|
||||
is.setItemMeta(bm);
|
||||
return is;
|
||||
}
|
||||
@@ -99,7 +113,9 @@ public class IntrobookConfig extends SimpleHackConfig {
|
||||
if (!(meta instanceof BookMeta)) return false;
|
||||
|
||||
BookMeta book = (BookMeta) meta;
|
||||
if (!(getTitle().equals(book.getTitle()))) return false;
|
||||
if (!(getTitle().contains(
|
||||
book.getTitle().replaceAll("\\$\\{player\\}", "")
|
||||
))) return false;
|
||||
if (!(getAuthor().equals(book.getAuthor()))) return false;
|
||||
|
||||
return true;
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* Lightweight config for InvisibleFix
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class InvisibleFixConfig extends SimpleHackConfig {
|
||||
|
||||
private boolean ignoreOps;
|
||||
private String ignorePermission;
|
||||
private long recheckInterval;
|
||||
private int maxPlayersPerRecheck;
|
||||
|
||||
public InvisibleFixConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
|
||||
this.ignoreOps = config.getBoolean("ignoreOps", true);
|
||||
this.ignorePermission = config.getString("ignorePermission", "sah.allowInvisible");
|
||||
if (this.ignorePermission.equals("")) this.ignorePermission = null;
|
||||
this.recheckInterval = config.getLong("recheckInterval", 500l);
|
||||
this.maxPlayersPerRecheck = config.getInt("maxPlayerPerRecheck", 5);
|
||||
}
|
||||
|
||||
public boolean getIgnoreOps() {
|
||||
return this.ignoreOps;
|
||||
}
|
||||
|
||||
public String getIgnorePermission() {
|
||||
return this.ignorePermission;
|
||||
}
|
||||
|
||||
public long getRecheckInterval() {
|
||||
return this.recheckInterval;
|
||||
}
|
||||
|
||||
public int getMaxPlayersPerRecheck() {
|
||||
return this.maxPlayersPerRecheck;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
/**
|
||||
* Lightweight config for InvisibleFixTwo
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class InvisibleFixTwoConfig extends SimpleHackConfig {
|
||||
|
||||
/**
|
||||
* How many ticks after teleport to attempt to force a refresh of player position.
|
||||
*/
|
||||
private long teleportFixDelay;
|
||||
|
||||
/**
|
||||
* How many milliseconds in between each forced update of player position.
|
||||
*/
|
||||
private long fixInterval;
|
||||
|
||||
private boolean ignoreOps;
|
||||
private String ignorePermission;
|
||||
|
||||
public InvisibleFixTwoConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
this.teleportFixDelay = config.getLong("teleportDelay", 5l);
|
||||
this.fixInterval = config.getLong("minInterval", 100l);
|
||||
this.ignoreOps = config.getBoolean("ignoreOps", true);
|
||||
this.ignorePermission = config.getString("ignorePermission", "sah.allowInvisible");
|
||||
if (this.ignorePermission.equals("")) this.ignorePermission = null;
|
||||
}
|
||||
|
||||
public long getTeleportFixDelay() {
|
||||
return this.teleportFixDelay;
|
||||
}
|
||||
|
||||
public long getFixInterval() {
|
||||
return this.fixInterval;
|
||||
}
|
||||
|
||||
public boolean getIgnoreOps() {
|
||||
return this.ignoreOps;
|
||||
}
|
||||
|
||||
public String getIgnorePermission() {
|
||||
return this.ignorePermission;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
|
||||
/**
|
||||
* What should I send to people when I announce a newfriend?
|
||||
*/
|
||||
private String announceMessage = "&%Player% is brand new!";
|
||||
private String announceMessage;
|
||||
/**
|
||||
* Who should I broadcast to?
|
||||
*/
|
||||
@@ -48,7 +48,7 @@ public class NewfriendAssistConfig extends SimpleHackConfig {
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
this.announceMessage = config.getString("announce.message", announceMessage);
|
||||
this.announceMessage = config.getString("announce", "&f%Player% is brand new!");
|
||||
|
||||
List<String> broadcastTo = config.getStringList("broadcast");
|
||||
if (this.announceBroadcast == null) {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
public class ReinforcedChestBreakConfig extends SimpleHackConfig{
|
||||
|
||||
/**
|
||||
* This is the delay between messages
|
||||
*/
|
||||
private int delay;
|
||||
|
||||
/**
|
||||
* The message if someone is raiding a reinforced chest
|
||||
*/
|
||||
private String message;
|
||||
|
||||
public ReinforcedChestBreakConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
this.delay = config.getInt("delay", 180);
|
||||
this.message = config.getString("message", "&4%player% is raiding a chest at %x% %y% %z%.");
|
||||
}
|
||||
|
||||
public int getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
public class SanityHackConfig extends SimpleHackConfig {
|
||||
|
||||
private boolean trackPlace; //Track block placed
|
||||
private boolean trackBreak; //Track block broken
|
||||
//private boolean trackOpen; //Track block opened (chest)
|
||||
private int belowYLevel; //Track things below this y level
|
||||
|
||||
public SanityHackConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
this.trackPlace = config.getBoolean("trackPlace", true);
|
||||
plugin().log(Level.INFO, "Tracking placement: {0}", this.trackPlace);
|
||||
|
||||
this.trackBreak = config.getBoolean("trackBreak", true);
|
||||
plugin().log(Level.INFO, "Tracking breaking: {0}", this.trackBreak);
|
||||
|
||||
/*this.trackOpen = config.getBoolean("trackOpen", false);
|
||||
plugin().log(Level.INFO, "Tracking opening: {0}", this.trackOpen);*/
|
||||
|
||||
this.belowYLevel = config.getInt("belowYLevel", 7);
|
||||
plugin().log(Level.INFO, "Tracking below Y: {0}", this.belowYLevel);
|
||||
}
|
||||
|
||||
public boolean isTrackingPlace(){
|
||||
return this.trackPlace;
|
||||
}
|
||||
|
||||
public boolean isTrackingBreak(){
|
||||
return this.trackBreak;
|
||||
}
|
||||
|
||||
/*public boolean isTrackingOpen(){
|
||||
return this.trackOpen;
|
||||
}*/
|
||||
|
||||
public int getTrackingLevel(){
|
||||
return this.belowYLevel;
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.configs;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHackConfig;
|
||||
|
||||
public class TimingsHackConfig extends SimpleHackConfig {
|
||||
|
||||
private Short timingsMap;
|
||||
private Map<String, Short> bindMaps;
|
||||
private Map<Short, String> reverseBindMaps;
|
||||
|
||||
public TimingsHackConfig(SimpleAdminHacks plugin, ConfigurationSection base) {
|
||||
super(plugin, base);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wireup(ConfigurationSection config) {
|
||||
timingsMap = config.contains("timingMap") ? (short) config.getInt("timingMap") : null;
|
||||
bindMaps = new ConcurrentHashMap<String, Short>();
|
||||
reverseBindMaps = new ConcurrentHashMap<Short, String>();
|
||||
if (config.contains("bindings")) {
|
||||
ConfigurationSection bindings = config.getConfigurationSection("bindings");
|
||||
Set<String> keys = bindings.getKeys(false);
|
||||
for (String key : keys) {
|
||||
bindMaps.put(key, (short) bindings.getInt(key));
|
||||
reverseBindMaps.put((short) bindings.getInt(key), key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Short getTimingsMap() {
|
||||
return timingsMap;
|
||||
}
|
||||
|
||||
public void setTimingsMap(short mapId) {
|
||||
timingsMap = mapId;
|
||||
getBase().set("timingMap", mapId);
|
||||
}
|
||||
|
||||
public Short getBindMap(String bind) {
|
||||
if (bind == null) {
|
||||
return null;
|
||||
}
|
||||
return bindMaps.get(bind);
|
||||
}
|
||||
|
||||
public void setBindMap(String bind, short mapId) {
|
||||
Short prior = bindMaps.replace(bind, mapId);
|
||||
if (prior != null) {
|
||||
reverseBindMaps.remove(prior);
|
||||
}
|
||||
reverseBindMaps.replace(mapId, bind);
|
||||
getBase().set("bindings." + bind, mapId);
|
||||
}
|
||||
|
||||
public String getBindFromId(short mapId) {
|
||||
return reverseBindMaps.get(mapId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.BadBoyWatchConfig;
|
||||
|
||||
/**
|
||||
* Watches for bad boys by tracking block breaks.
|
||||
* FIRST HIT will also catch breaks that block protection plugins cancel, which if you skip
|
||||
* can lead to confusion
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*
|
||||
*/
|
||||
public class BadBoyWatch extends SimpleHack<BadBoyWatchConfig> implements Listener {
|
||||
|
||||
/**
|
||||
* tracking cache, persists and ignores logouts.
|
||||
*/
|
||||
private Map<UUID, BadBoyRecord> boys = null;
|
||||
private Map<UUID, BadBoyRecord> lowBoys = null;
|
||||
|
||||
public BadBoyWatch(SimpleAdminHacks plugin, BadBoyWatchConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true)
|
||||
public void breakListenLow(BlockBreakEvent bbe) {
|
||||
if (!config.isEnabled()) return;
|
||||
try {
|
||||
Player player = bbe.getPlayer();
|
||||
Block block = bbe.getBlock();
|
||||
BlockState bs = block.getState();
|
||||
Material material = bs.getType();
|
||||
|
||||
lowTrackAndReport(player.getUniqueId(), block.getLocation(), material);
|
||||
} catch (Exception e) {
|
||||
// insane catchall
|
||||
plugin().log(Level.WARNING, "Failed to track a low break for badboy", e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled=true)
|
||||
public void breakListen(BlockBreakEvent bbe) {
|
||||
if (!config.isEnabled()) return;
|
||||
try {
|
||||
Player player = bbe.getPlayer();
|
||||
Block block = bbe.getBlock();
|
||||
BlockState bs = block.getState();
|
||||
Material material = bs.getType();
|
||||
|
||||
trackAndReport(player.getUniqueId(), block.getLocation(), material);
|
||||
} catch (Exception e) {
|
||||
// insane catchall
|
||||
plugin().log(Level.WARNING, "Failed to track a break for badboy", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void trackAndReport(UUID uuid, Location location, Material material) {
|
||||
if (uuid == null || location == null || material == null) return;
|
||||
|
||||
BadBoyRecord record = boys.get(uuid);
|
||||
if (record == null) {
|
||||
record = new BadBoyRecord(config);
|
||||
boys.put(uuid, record);
|
||||
}
|
||||
|
||||
String report = record.registerBreak(location, material);
|
||||
if (report != null) {
|
||||
plugin().log(uuid.toString() + "} " + report);
|
||||
}
|
||||
}
|
||||
|
||||
private void lowTrackAndReport(UUID uuid, Location location, Material material) {
|
||||
if (uuid == null || location == null || material == null) return;
|
||||
|
||||
BadBoyRecord record = lowBoys.get(uuid);
|
||||
if (record == null) {
|
||||
record = new BadBoyRecord(config);
|
||||
lowBoys.put(uuid, record);
|
||||
}
|
||||
|
||||
String report = record.registerBreak(location, material);
|
||||
if (report != null) {
|
||||
plugin().log(uuid.toString() + "} FIRST HITS " + report);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering BadBoyWatcher listener");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
boys = new ConcurrentHashMap<UUID, BadBoyRecord>();
|
||||
lowBoys = new ConcurrentHashMap<UUID, BadBoyRecord>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
boys.clear();
|
||||
lowBoys.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
if (!config.isEnabled()) {
|
||||
return "Bad Boy Listening disabled.";
|
||||
}
|
||||
StringBuffer status = new StringBuffer("Listening for bad boy breaks");
|
||||
|
||||
status.append("\n Currently watching ").append(boys.size()).append(" players.");
|
||||
status.append("\n Currently watching LOW ").append(lowBoys.size()).append(" players.");
|
||||
return status.toString();
|
||||
}
|
||||
|
||||
public static BadBoyWatchConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new BadBoyWatchConfig(plugin, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {}
|
||||
|
||||
private static class BadBoyRecord {
|
||||
private int nextBreak;
|
||||
private int breakCount;
|
||||
private BadBoyBlock[] breaks;
|
||||
private BadBoyWatchConfig config;
|
||||
|
||||
public BadBoyRecord(BadBoyWatchConfig config) {
|
||||
this.config = config;
|
||||
this.nextBreak = 0;
|
||||
this.breakCount = 0;
|
||||
this.breaks = new BadBoyBlock[config.getTrackingDepth() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string if this is a watched break that passes all checks.
|
||||
* Handles abiding the config too.
|
||||
* @return null if not watched break or if conditions not met, string of
|
||||
* breaks leading to this otherwise.
|
||||
*/
|
||||
public synchronized String registerBreak(Location location, Material material) {
|
||||
breaks[nextBreak] = new BadBoyBlock(location, material);
|
||||
|
||||
nextBreak = (nextBreak + 1) % breaks.length;
|
||||
breakCount ++;
|
||||
|
||||
if (config.getWatchedMaterials().contains(material)) {
|
||||
String breaks = null;
|
||||
if (breakCount >= config.getMinDepthToMatch()) {
|
||||
breaks = showBreaks();
|
||||
}
|
||||
if (config.isClearOnMatch()) {
|
||||
this.clear();
|
||||
breakCount = 0;
|
||||
}
|
||||
return breaks;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears everything tracked and resets break pointer.
|
||||
*/
|
||||
private void clear() {
|
||||
for (int i = 0; i < breaks.length; i++) {
|
||||
breaks[i] = null;
|
||||
}
|
||||
nextBreak = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just outputs what we know right now.
|
||||
* @return a String showing locations and block materials
|
||||
*/
|
||||
private String showBreaks() {
|
||||
StringBuilder sb = new StringBuilder("BadBoyBreak: ");
|
||||
int max = breaks.length;
|
||||
|
||||
for (int i = (nextBreak - 1 + max) % max; i != nextBreak; i = (i - 1 + max) % max) {
|
||||
BadBoyBlock bbb = breaks[i];
|
||||
if (bbb == null) break;
|
||||
sb.append('[').append(bbb.toString()).append(']');
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class BadBoyBlock {
|
||||
public final Location location;
|
||||
public final Material material;
|
||||
|
||||
public BadBoyBlock(Location location, Material material) {
|
||||
this.location = location;
|
||||
this.material = material;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder se = new StringBuilder();
|
||||
|
||||
se.append(location.getWorld().getName());
|
||||
se.append('(').append(location.getBlockX()).append(',')
|
||||
.append(location.getBlockY()).append(',')
|
||||
.append(location.getBlockZ()).append(')');
|
||||
se.append(material.toString());
|
||||
return se.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.minelink.ctplus.event.PlayerCombatTagEvent;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
@@ -123,4 +124,8 @@ public class CTAnnounce extends SimpleHack<CTAnnounceConfig> implements Listener
|
||||
return "CombatTagPlus.PlayerCombatTagEvent monitoring not active";
|
||||
}
|
||||
}
|
||||
|
||||
public static CTAnnounceConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new CTAnnounceConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import me.badbones69.crazycrates.api.PlayerPrizeEvent;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.CrazyCratesAnnounceConfig;
|
||||
|
||||
/**
|
||||
* Ties into CrazyCrates, listens for {@link PlayerPrizeEvent}
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class CrazyCratesAnnounce extends SimpleHack<CrazyCratesAnnounceConfig> implements Listener{
|
||||
|
||||
public static final String NAME = "CrazyCratesAnnounce";
|
||||
|
||||
public CrazyCratesAnnounce(SimpleAdminHacks plugin, CrazyCratesAnnounceConfig config) {
|
||||
super(plugin, config);
|
||||
|
||||
if (!plugin.serverHasPlugin("CrazyCrates")){
|
||||
plugin.log("CrazyCrates not found, disabling broadcast hook.");
|
||||
config.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Capture player combattag and broadcast it
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPrizeEvent(PlayerPrizeEvent event) {
|
||||
if (!config.isEnabled()) return; // ignore if off
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CrazyCrates opening crate ").append(event.getCrateName()).append(" [").append(event.getCrateType()).append("] for ");
|
||||
if (event.getPlayer() != null) {
|
||||
sb.append(event.getPlayer().getName());
|
||||
} else {
|
||||
sb.append("Unknown");
|
||||
}
|
||||
if (event.getPrize() != null) {
|
||||
sb.append(" holding ").append(event.getPrize().getName());
|
||||
} else {
|
||||
sb.append(" holding unnamed with empty");
|
||||
return;
|
||||
}
|
||||
|
||||
sb.append(" with");
|
||||
|
||||
if (event.getPrize().getItems() != null && !event.getPrize().getItems().isEmpty()) {
|
||||
for (ItemStack item : event.getPrize().getItems()) {
|
||||
sb.append(" ").append(item.getType());
|
||||
if (item.getAmount() > 1) {
|
||||
sb.append("x").append(item.getAmount());
|
||||
}
|
||||
if (item.hasItemMeta()) {
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta.hasDisplayName()) {
|
||||
sb.append(" {").append(meta.getDisplayName()).append("}");
|
||||
}
|
||||
if (meta.hasLore()) {
|
||||
sb.append(" ");
|
||||
for (String lore : meta.getLore()) {
|
||||
sb.append(" [").append(lore).append("]");
|
||||
}
|
||||
}
|
||||
if (meta.hasEnchants()) {
|
||||
sb.append(" Ench");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
sb.append(" empty");
|
||||
}
|
||||
|
||||
plugin().log(Level.INFO, sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering CrazyCrates listener");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
if (config != null && config.isEnabled()) {
|
||||
return "CrazyCrates.PlayerPrizeEvent monitoring active";
|
||||
} else {
|
||||
return "CrazyCrates.PlayerPrizeEvent monitoring not active";
|
||||
}
|
||||
}
|
||||
|
||||
public static CrazyCratesAnnounceConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new CrazyCratesAnnounceConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,14 @@ import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.ExperimentalConfig;
|
||||
|
||||
public class Experimental extends SimpleHack<ExperimentalConfig> implements Listener, CommandExecutor {
|
||||
@@ -103,6 +105,7 @@ public class Experimental extends SimpleHack<ExperimentalConfig> implements List
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
|
||||
private void monitorTeleportLow(PlayerTeleportEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isTeleportSpy()) return;
|
||||
StringBuffer sb = new StringBuffer("[LO] ");
|
||||
logTeleport(event, sb);
|
||||
@@ -110,6 +113,7 @@ public class Experimental extends SimpleHack<ExperimentalConfig> implements List
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
|
||||
private void monitorTeleportHigh(PlayerTeleportEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isTeleportSpy()) return;
|
||||
StringBuffer sb = new StringBuffer("[HI] ");
|
||||
logTeleport(event, sb);
|
||||
@@ -117,6 +121,7 @@ public class Experimental extends SimpleHack<ExperimentalConfig> implements List
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
private void monitorTeleportTrack(PlayerPortalEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isPostTeleportSpy()) return;
|
||||
|
||||
final Player player = event.getPlayer();
|
||||
@@ -192,6 +197,7 @@ public class Experimental extends SimpleHack<ExperimentalConfig> implements List
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
|
||||
private void monitorCombatLow(EntityDamageByEntityEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isCombatSpy()) return;
|
||||
StringBuffer sb = new StringBuffer("[LO] ");
|
||||
logCombat(event, sb);
|
||||
@@ -217,9 +223,14 @@ public class Experimental extends SimpleHack<ExperimentalConfig> implements List
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false)
|
||||
private void monitorCombatHigh(EntityDamageByEntityEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isCombatSpy()) return;
|
||||
StringBuffer sb = new StringBuffer("[HI]: ");
|
||||
logCombat(event, sb);
|
||||
}
|
||||
|
||||
public static ExperimentalConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new ExperimentalConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
import org.bukkit.BanList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.CreatureSpawner;
|
||||
import org.bukkit.entity.*;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockDispenseEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityResurrectEvent;
|
||||
import org.bukkit.event.entity.EntityToggleGlideEvent;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.inventory.FurnaceExtractEvent;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.GameFeaturesConfig;
|
||||
|
||||
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerItemDamageEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.material.Dispenser;
|
||||
import org.bukkit.material.Hopper;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
/**
|
||||
* This is a grab-bag class to hold any _features_ related configurations that impact the
|
||||
* game, server-wide. Mostly focused on turning things on or off.
|
||||
*
|
||||
* It's part of a series of focused hacks.
|
||||
*
|
||||
* {@link GameFixes} is focused on things that are broken or don't work, and attempts to fix them.
|
||||
* {@link GameFeatures} focuses on enabling and disabling features, like elytra, various potion states.
|
||||
* {@link GameTuning} neither fixes nor disables, but rather adjusts and reconfigures.
|
||||
*
|
||||
* Currently you can control the following:
|
||||
* - Disable Potato XP
|
||||
*/
|
||||
public class GameFeatures extends SimpleHack<GameFeaturesConfig> implements Listener {
|
||||
public static final String NAME = "GameFeatures";
|
||||
|
||||
public GameFeatures(SimpleAdminHacks plugin, GameFeaturesConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config != null && config.isEnabled()) {
|
||||
plugin().log("Registering GameFeatures listeners");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
// Bukkit does this for us.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
StringBuilder genStatus = new StringBuilder();
|
||||
genStatus.append("GameFeatures is ");
|
||||
if (config != null && config.isEnabled()) {
|
||||
genStatus.append("active\n");
|
||||
if (config.isPotatoXPEnabled()) {
|
||||
genStatus.append(" Potato XP is enabled\n");
|
||||
} else {
|
||||
genStatus.append(" Potato XP is disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Villager Trading is ");
|
||||
if (config.isVillagerTrading()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Wither Spawning is ");
|
||||
if (config.isWitherSpawning()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Ender Chest placement is ");
|
||||
if (config.isEnderChestPlacement()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Ender Chest use is ");
|
||||
if (config.isEnderChestUse()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Shulker Box use is ");
|
||||
if (config.isShulkerBoxUse()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Totem of Undying effects are ");
|
||||
if (config.isTotemPowers()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Elytra use is ");
|
||||
if (config.isElytraUse()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Chorus Fruit teleportation is ");
|
||||
if (config.isChorusFruitTeleportation()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" WeepAngel is ");
|
||||
if (config.isWeepingAngel()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" Block water in HELL biomes is ");
|
||||
if (config.isBlockWaterInHell()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
// more?
|
||||
} else {
|
||||
genStatus.append("inactive");
|
||||
}
|
||||
return genStatus.toString();
|
||||
}
|
||||
|
||||
public static GameFeaturesConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new GameFeaturesConfig(plugin, config);
|
||||
}
|
||||
|
||||
/* From here on, the actual meat of the hack. Above is basically boilerplate for micro-plugins.*/
|
||||
|
||||
/**
|
||||
* Perhaps eventually generalize?
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void potatoXP(FurnaceExtractEvent event) {
|
||||
if (!config.isEnabled() || config.isPotatoXPEnabled()) return;
|
||||
try {
|
||||
Material mat = event.getItemType();
|
||||
if (mat == null) return;
|
||||
|
||||
if (Material.BAKED_POTATO.equals(mat)) {
|
||||
event.setExpToDrop(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin().log(Level.WARNING, "Failed to stop potato XP", e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void disableVillagerTrading(PlayerInteractEntityEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isVillagerTrading()) {
|
||||
Entity npc = event.getRightClicked();
|
||||
|
||||
if (npc != null) {
|
||||
if (npc.getType().equals(EntityType.VILLAGER)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void disableWitherSpawning(CreatureSpawnEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isWitherSpawning()) {
|
||||
if (event.getEntityType().equals(EntityType.WITHER)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void disableEnderChestPlacement(BlockPlaceEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isEnderChestPlacement()) {
|
||||
if (event.getBlock().getType().equals(Material.ENDER_CHEST)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void disableEnderChestUse(PlayerInteractEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isEnderChestUse()) {
|
||||
Action action = event.getAction();
|
||||
Material material = event.getClickedBlock().getType();
|
||||
boolean ender_chest = action == Action.RIGHT_CLICK_BLOCK &&
|
||||
material.equals(Material.ENDER_CHEST);
|
||||
if (ender_chest) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void disableShulkerBoxUse(InventoryOpenEvent event){
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isShulkerBoxUse() && InventoryType.SHULKER_BOX.equals(event.getInventory().getType())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void disabledShulkerBoxHoppering(InventoryMoveItemEvent event) {
|
||||
if (!config.isEnabled() || config.isShulkerBoxUse()) return;
|
||||
|
||||
if ((event.getDestination() == null) || (event.getSource() == null)) return;
|
||||
if (InventoryType.SHULKER_BOX.equals(event.getDestination().getType()) ||
|
||||
InventoryType.SHULKER_BOX.equals(event.getSource().getType())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void disableTotemPowers(EntityResurrectEvent event) {
|
||||
if (!config.isEnabled() || config.isTotemPowers()) return;
|
||||
|
||||
if (EntityType.PLAYER.equals(event.getEntityType())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void disableChorusFruitTeleportation(PlayerTeleportEvent event) {
|
||||
if (!config.isEnabled() || config.isChorusFruitTeleportation()) return;
|
||||
|
||||
if (event.getCause().equals(PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void disableElytraUse(EntityToggleGlideEvent event) {
|
||||
if (!config.isEnabled() || config.isElytraUse()) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void weepingAngelListener(PlayerDeathEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isWeepingAngel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent) {
|
||||
EntityDamageByEntityEvent evt = ((EntityDamageByEntityEvent) event.getEntity().getLastDamageCause());
|
||||
LivingEntity killer = null;
|
||||
|
||||
if (evt.getDamager() instanceof LivingEntity) {
|
||||
killer = (LivingEntity) evt.getDamager();
|
||||
} else if (evt.getDamager() instanceof Projectile) {
|
||||
Projectile projectile = (Projectile) evt.getDamager();
|
||||
|
||||
if (projectile.getShooter() instanceof LivingEntity) {
|
||||
killer = (LivingEntity) projectile.getShooter();
|
||||
}
|
||||
}
|
||||
|
||||
if (killer != null) {
|
||||
if (killer instanceof Player) {
|
||||
banPlayer(event.getEntity().getPlayer(), config.getWeepingAngelPlayer());
|
||||
} else {
|
||||
banPlayer(event.getEntity().getPlayer(), config.getWeepingAngelEnv());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
banPlayer(event.getEntity().getPlayer(), config.getWeepingAngelEnv());
|
||||
}
|
||||
}
|
||||
|
||||
private void banPlayer(Player p, int minutes) {
|
||||
if (!config.isWeepingAngel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date exp = DateUtils.addMinutes(new Date(), minutes);
|
||||
Bukkit.getServer().getBanList(BanList.Type.NAME).addBan(p.getName(), "You've been banned for " + minutes +
|
||||
" minutes due to your death.", exp, "weepingAngel");
|
||||
|
||||
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(SimpleAdminHacks.instance(), new Runnable() {
|
||||
public void run() {
|
||||
p.kickPlayer("You've been banned for " + minutes + " minutes due to your death.");
|
||||
}
|
||||
}, 2L);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerBucketEmptyEvent(PlayerBucketEmptyEvent e) {
|
||||
if (config.isEnabled() && config.isBlockWaterInHell()) {
|
||||
if ((e.getBlockClicked().getBiome() == Biome.HELL) && (e.getBucket() == Material.WATER_BUCKET)) {
|
||||
e.setCancelled(true);
|
||||
e.getItemStack().setType(Material.BUCKET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onDispenseEvent(BlockDispenseEvent event) {
|
||||
if (config.isEnabled() && config.isBlockWaterInHell()) {
|
||||
if (event.getBlock().getType() == Material.DISPENSER) {
|
||||
Dispenser disp = (Dispenser) event.getBlock().getState().getData();
|
||||
Biome biome = event.getBlock().getRelative(disp.getFacing()).getBiome();
|
||||
|
||||
if (Biome.HELL.equals(biome) && event.getItem() != null && event.getItem().getType().equals(Material.WATER_BUCKET)) {
|
||||
event.setItem(new ItemStack(Material.BUCKET, event.getItem().getAmount()));
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockDispenseEvent;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityPortalEvent;
|
||||
import org.bukkit.event.entity.EntityTeleportEvent;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.material.Dispenser;
|
||||
import org.bukkit.material.Hopper;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.GameFixesConfig;
|
||||
|
||||
public class GameFixes extends SimpleHack<GameFixesConfig> implements Listener {
|
||||
public static final String NAME = "GameFixes";
|
||||
|
||||
public GameFixes(SimpleAdminHacks plugin, GameFixesConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config != null && config.isEnabled()) {
|
||||
plugin().log("Registering GameFixes listeners");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
//Bukkit does this for us (why is this a method then?)
|
||||
// incase your listener semantics include things beyond the default
|
||||
// like maybe alerting bungee of a connected listener to disable, etc.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
StringBuilder genStatus = new StringBuilder();
|
||||
genStatus.append("GameFixes is ");
|
||||
if (config != null && config.isEnabled()) {
|
||||
genStatus.append(ChatColor.GREEN).append("active\n").append(ChatColor.RESET);
|
||||
genStatus.append(" Block elytra break bug is ");
|
||||
if (config.isBlockElytraBreakBug()) {
|
||||
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
|
||||
genStatus.append(" Will deal " + config.getDamageOnElytraBreakBug() + " damage to players\n");
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
|
||||
}
|
||||
genStatus.append(" Block storage entities from teleporting to prevents exploits ");
|
||||
if (!config.canStorageTeleport()) {
|
||||
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
|
||||
}
|
||||
genStatus.append(" Hopper self-feed duplication exploit fix ");
|
||||
if (config.isStopHopperDupe()) {
|
||||
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
|
||||
}
|
||||
genStatus.append(" Duplications using rails exploit fix ");
|
||||
if (config.isStopRailDupe()) {
|
||||
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
|
||||
}
|
||||
genStatus.append(" End Portal removal exploit fix ");
|
||||
if (config.isStopEndPortalDeletion()) {
|
||||
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
|
||||
}
|
||||
genStatus.append(" Bed Bombing in Nether / Hell Biomes fix ");
|
||||
if (config.stopBedBombing()) {
|
||||
genStatus.append(ChatColor.GREEN).append("enabled\n").append(ChatColor.RESET);
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("disabled\n").append(ChatColor.RESET);
|
||||
}
|
||||
} else {
|
||||
genStatus.append(ChatColor.RED).append("inactive").append(ChatColor.RESET);
|
||||
}
|
||||
return genStatus.toString();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
if (!config.isEnabled() || !config.isBlockElytraBreakBug()) return;
|
||||
Block block = event.getBlock();
|
||||
Player player = event.getPlayer();
|
||||
if (block == null || player == null) return;
|
||||
|
||||
if (!player.getLocation().equals(block.getLocation())
|
||||
&& player.getEyeLocation().getBlock().getType() != Material.AIR) {
|
||||
event.setCancelled(true);
|
||||
player.damage(config.getDamageOnElytraBreakBug());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onEntityTeleport(EntityTeleportEvent event) {
|
||||
if (!config.isEnabled() || config.canStorageTeleport()) return;
|
||||
if (event.getEntity() instanceof InventoryHolder) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onEntityPortal(EntityPortalEvent event) {
|
||||
if (!config.isEnabled() || config.canStorageTeleport()) return;
|
||||
if (event.getEntity() instanceof InventoryHolder) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onInventoryMoveItem(InventoryMoveItemEvent event) {
|
||||
if (!config.isEnabled() || !config.isStopHopperDupe()) return;
|
||||
if ((event.getDestination() == null) || (event.getSource() == null) ||
|
||||
!(InventoryType.HOPPER.equals(event.getDestination().getType())) ||
|
||||
!(InventoryType.HOPPER.equals(event.getSource().getType())) ||
|
||||
!(Material.HOPPER.equals(event.getDestination().getLocation().getBlock().getType())) ||
|
||||
!(Material.HOPPER.equals(event.getSource().getLocation().getBlock().getType()))) return;
|
||||
Hopper source = (Hopper) event.getSource().getLocation().getBlock().getState().getData();
|
||||
Hopper dest = (Hopper) event.getDestination().getLocation().getBlock().getState().getData();
|
||||
if (source.getFacing().getOppositeFace() == dest.getFacing()) {
|
||||
//They're pointing into each other and will eventually dupe
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onPistonPushRail(BlockPistonExtendEvent event) {
|
||||
if (config.isEnabled() && config.isStopRailDupe()) {
|
||||
for (Block block : event.getBlocks()) {
|
||||
Material type = block.getType();
|
||||
|
||||
if (config.getRailArray().contains(type)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onRailPlace(BlockPlaceEvent event) {
|
||||
if (config.isEnabled() && config.isStopRailDupe()) {
|
||||
Block block = event.getBlock();
|
||||
Material type = block.getType();
|
||||
|
||||
if (config.getRailArray().contains(type)) {
|
||||
for (BlockFace face : config.getBfArray()) {
|
||||
type = block.getRelative(face).getType();
|
||||
|
||||
if (config.getPistonArray().contains(type)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Trying to stop players from deleting end portals
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) {
|
||||
if (config.isEnabled() && config.isStopEndPortalDeletion()) {
|
||||
Block block = event.getBlockClicked().getRelative(event.getBlockFace());
|
||||
|
||||
if (block.getType() == Material.ENDER_PORTAL) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onDispenseEvent(BlockDispenseEvent event) {
|
||||
if (config.isEnabled() && config.isStopEndPortalDeletion()) {
|
||||
if (event.getBlock().getType() == Material.DISPENSER) {
|
||||
Dispenser disp = (Dispenser) event.getBlock().getState().getData();
|
||||
Material type = event.getBlock().getRelative(disp.getFacing()).getType();
|
||||
|
||||
if (type == Material.ENDER_PORTAL) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerEnterBed(BlockPlaceEvent event) {
|
||||
if (!config.isEnabled() || !config.stopBedBombing()) return;
|
||||
|
||||
Block b = event.getBlock();
|
||||
if (!(b.getType() == Material.BED || b.getType() == Material.BED_BLOCK))
|
||||
return;
|
||||
|
||||
Environment env = b.getLocation().getWorld().getEnvironment();
|
||||
Biome biome = b.getLocation().getBlock().getBiome();
|
||||
if (env == Environment.NETHER || env == Environment.THE_END || Biome.HELL == biome || Biome.SKY == biome) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static GameFixesConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new GameFixesConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.minecart.HopperMinecart;
|
||||
import org.bukkit.entity.minecart.StorageMinecart;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.bukkit.event.block.BlockPistonRetractEvent;
|
||||
import org.bukkit.event.entity.EntityPortalEvent;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerBedEnterEvent;
|
||||
import org.bukkit.event.player.PlayerPortalEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.GameTuningConfig;
|
||||
|
||||
import org.bukkit.event.world.PortalCreateEvent;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
|
||||
/**
|
||||
* This is a grab-bag class to hold any _tuning_ related configurations that impact the
|
||||
* game, server-wide.
|
||||
*
|
||||
* It's part of a series of focused hacks.
|
||||
*
|
||||
* {@link GameFixes} is focused on things that are broken or don't work, and attempts to fix them.
|
||||
* {@link GameFeatures} focuses on enabling and disabling features, like elytra, various potion states.
|
||||
* {@link GameTuning} neither fixes nor disables, but rather adjusts and reconfigures.
|
||||
*
|
||||
* Currently you can control the following:
|
||||
* - BlockEntity limits per chunk
|
||||
* - Setting bed during the day instead of just at night
|
||||
*/
|
||||
public class GameTuning extends SimpleHack<GameTuningConfig> implements Listener {
|
||||
public static final String NAME = "GameTuning";
|
||||
|
||||
public GameTuning(SimpleAdminHacks plugin, GameTuningConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config != null && config.isEnabled()) {
|
||||
plugin().log("Registering GameTuning listeners");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
// Bukkit does this for us.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
StringBuilder genStatus = new StringBuilder();
|
||||
genStatus.append("GameTuning is ");
|
||||
if (config != null && config.isEnabled()) {
|
||||
genStatus.append("active\n");
|
||||
if (config.areChunkLimitsEnabled()) {
|
||||
genStatus.append(" Chunk Limits are enabled\n");
|
||||
} else {
|
||||
genStatus.append(" Chunk Limits are disabled\n");
|
||||
}
|
||||
if (config.areDaytimeBedsEnabled()) {
|
||||
genStatus.append(" Daytime Beds are enabled\n");
|
||||
} else {
|
||||
genStatus.append(" Daytime Beds are disabled\n");
|
||||
}
|
||||
|
||||
genStatus.append(" One To One Nether is ");
|
||||
if (config.isOneToOneNether()) {
|
||||
genStatus.append("enabled\n");
|
||||
} else {
|
||||
genStatus.append("disabled\n");
|
||||
}
|
||||
|
||||
// more?
|
||||
} else {
|
||||
genStatus.append("inactive");
|
||||
}
|
||||
return genStatus.toString();
|
||||
}
|
||||
|
||||
public static GameTuningConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new GameTuningConfig(plugin, config);
|
||||
}
|
||||
|
||||
/* From here on, the actual meat of the hack. Above is basically boilerplate for micro-plugins.*/
|
||||
|
||||
/**
|
||||
* Many thanks to BlackXNT for his work on this event in Humbug, which I have largely copied and expanded.
|
||||
*
|
||||
* This tracks block placements, and if a limit is configured and the block is a TileEntity w/ state,
|
||||
* will reject the placement if otherwise it would exceed limits for the Chunk.
|
||||
*
|
||||
* @param event the Placement event.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void chunkLimits(BlockPlaceEvent event) {
|
||||
if (!config.isEnabled() || !config.areChunkLimitsEnabled()) return;
|
||||
try {
|
||||
Player player = event.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
Block block = event.getBlock();
|
||||
if (block == null) return;
|
||||
|
||||
if (!config.applyChunkLimits(player.getUniqueId())) return;
|
||||
|
||||
Material mat = block.getType();
|
||||
if (mat == null) return;
|
||||
|
||||
Integer limit = config.getChunkLimit(mat);
|
||||
if (limit == null) return;
|
||||
|
||||
if (block.getChunk().getTileEntities() == null) return;
|
||||
|
||||
int current = 0;
|
||||
for (BlockState state : block.getChunk().getTileEntities()) {
|
||||
if (state != null && mat.equals(state.getType())) {
|
||||
if (++current > limit) {
|
||||
event.setCancelled(true);
|
||||
player.sendMessage(config.getChunkLimitsExceededMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin().log(Level.WARNING, "Failed to measure chunk limit", e);
|
||||
}
|
||||
}
|
||||
|
||||
// If any limit at all, cancel the piston event.
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void chunkLimitsExploitExtend(BlockPistonExtendEvent event) {
|
||||
if (!config.isEnabled() || !config.areChunkLimitsEnabled()) return;
|
||||
List<Block> blocks = event.getBlocks();
|
||||
if (blocks != null && blocks.size() > 0) {
|
||||
for (Block b : blocks) {
|
||||
if (b != null && b.getType() != null && config.getChunkLimit(b.getType()) != null) {
|
||||
event.setCancelled(true);
|
||||
return; // TODO send message to nearby player warning of reason for stopping.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, this is identical ...
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void chunkLimitsExploitRetract(BlockPistonRetractEvent event) {
|
||||
if (!config.isEnabled() || !config.areChunkLimitsEnabled()) return;
|
||||
List<Block> blocks = event.getBlocks();
|
||||
if (blocks != null && blocks.size() > 0) {
|
||||
for (Block b : blocks) {
|
||||
if (b != null && b.getType() != null && config.getChunkLimit(b.getType()) != null) {
|
||||
event.setCancelled(true);
|
||||
return; // TODO send message to nearby player warning of reason for stopping.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void bedRClickToSetSpawn(PlayerInteractEvent event) {
|
||||
if (!config.isEnabled() || !config.areDaytimeBedsEnabled() || event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock().getType() != Material.BED_BLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getPlayer() == null) return;
|
||||
if (event.getClickedBlock() == null) return;
|
||||
|
||||
// Let plugins that already watch for and cancel this event have a turn, like ExilePearl
|
||||
PlayerBedEnterEvent pbee = new PlayerBedEnterEvent(event.getPlayer(), event.getClickedBlock());
|
||||
Bukkit.getServer().getPluginManager().callEvent(pbee);
|
||||
if (pbee.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.getPlayer().setBedSpawnLocation(event.getClickedBlock().getLocation(), false);
|
||||
Location loc = event.getClickedBlock().getLocation();
|
||||
event.getPlayer().sendTitle("", config.getDaytimeBedSpawnSetMessage());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPortalTravel(EntityPortalEvent event) {
|
||||
if (config.isEnabled() && config.isOneToOneNether()) {
|
||||
Location newLoc = event.getFrom();
|
||||
newLoc.setWorld(event.getTo().getWorld());
|
||||
event.setTo(newLoc);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerPortalTravel(PlayerPortalEvent event) {
|
||||
if (config.isEnabled() && config.isOneToOneNether() && event.getCause() == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL) {
|
||||
Location newLoc = event.getFrom();
|
||||
newLoc.setWorld(event.getTo().getWorld());
|
||||
event.setTo(newLoc);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onPortalCreate(PortalCreateEvent event) {
|
||||
if (config.isEnabled() && !config.isReturnNetherPortal()) {
|
||||
if (event.getReason() == PortalCreateEvent.CreateReason.FIRE && event.getWorld().getName().equals("world_nether")) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Trying to stop dupe bugs via minecart inventories
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onEntityRightClick(PlayerInteractEntityEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isChestedMinecartInventories() || !config.isHopperMinecartInventories()) {
|
||||
Entity target = event.getRightClicked();
|
||||
|
||||
if (target.getType().equals(EntityType.MINECART_CHEST) && !config.isChestedMinecartInventories()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
if (target.getType().equals(EntityType.MINECART_HOPPER) && !config.isHopperMinecartInventories()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onInventoryMoveItemEvent(InventoryMoveItemEvent event) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (!config.isChestedMinecartInventories() || !config.isHopperMinecartInventories()) {
|
||||
InventoryHolder holder = event.getDestination().getHolder();
|
||||
if (holder instanceof StorageMinecart && !config.isChestedMinecartInventories()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
if (holder instanceof HopperMinecart && !config.isHopperMinecartInventories()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.comphenix.protocol.ProtocolManager;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.bots.Bot;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.CTAnnounceConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
|
||||
|
||||
import de.inventivegames.npc.NPC;
|
||||
@@ -395,5 +396,9 @@ public class HackBot extends SimpleHack<HackBotConfig> implements Listener, Comm
|
||||
sb.append(ChatColor.RESET).append("; details: ").append(abot.status());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static HackBotConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new HackBotConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventException;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.server.PluginEnableEvent;
|
||||
import org.bukkit.plugin.EventExecutor;
|
||||
import org.bukkit.plugin.IllegalPluginAccessException;
|
||||
import org.bukkit.plugin.RegisteredListener;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.reflect.ClassPath;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.InsightConfig;
|
||||
|
||||
/**
|
||||
* Config example:
|
||||
* <code>
|
||||
* Insight:
|
||||
* enabled: true
|
||||
* into:
|
||||
* - org.bukkit.event.player
|
||||
* - org.bukkit.event.block
|
||||
* - org.bukkit.event.inventory
|
||||
*
|
||||
* This "into" list defines event classpaths. Events found in that classpath are
|
||||
* instrumented; listeners are reordered to put _this_ hack's listeners as first and last to run.
|
||||
*
|
||||
* These listeners bypass safe bukkit convention, Timings, and can handle
|
||||
* async or sync events.
|
||||
*
|
||||
* Can monitor not only bukkit events but also plugin / custom events; just give the
|
||||
* valid classpath.
|
||||
*
|
||||
* This bypasses the "safe" bukkit way of registering listeners for events. So it
|
||||
* also bypasses Timings.
|
||||
*
|
||||
* This is the only warning I'll give. Be careful with this and prefer to leave it off.
|
||||
*
|
||||
* Wireups only happen on restart, I'm dubious about injecting live for now.
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class Insight extends SimpleHack<InsightConfig> implements CommandExecutor, Listener {
|
||||
|
||||
public static final String NAME = "Insight";
|
||||
|
||||
private Map<String, InsightStat> tracking;
|
||||
private List<Class<? extends Event>> rebounders;
|
||||
|
||||
public Insight(SimpleAdminHacks plugin, InsightConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 1) { // show help
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering Insight reordering hack");
|
||||
plugin().registerListener(this);
|
||||
|
||||
plugin().log("Registering Insight event instrumentations");
|
||||
|
||||
if (config.getInsightOn() == null) {
|
||||
plugin().log("No instrumentations defined, disabling.");
|
||||
this.softDisable();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ClassPath getSamplersPath = ClassPath.from(plugin().exposeClassLoader());
|
||||
|
||||
for (String clsPackage : config.getInsightOn()) {
|
||||
ImmutableSet<ClassPath.ClassInfo> clsInfoSet = getSamplersPath.getTopLevelClasses(clsPackage);
|
||||
if (clsInfoSet == null || clsInfoSet.isEmpty()) {
|
||||
plugin().log(Level.INFO, "Package {0} doesn't exist or is empty.", clsPackage);
|
||||
continue;
|
||||
}
|
||||
plugin().log(Level.INFO, "Scanning package {0} for events.", clsPackage);
|
||||
for (ClassPath.ClassInfo clsInfo : clsInfoSet) {
|
||||
try {
|
||||
Class<?> clazz = clsInfo.load();
|
||||
if (clazz != null && Event.class.isAssignableFrom(clazz)) {
|
||||
plugin().log(Level.INFO, "...Found event {0}, registering", clazz.getName());
|
||||
// This is some advanced shit. It also bypasses the timings, so, be careful, ok?
|
||||
plugin().getServer().getPluginManager().registerEvent((Class<? extends Event>) clazz, this, EventPriority.LOWEST, new EventExecutor() {
|
||||
@Override
|
||||
public void execute(Listener arg0, Event arg1) throws EventException {
|
||||
try {
|
||||
((Insight) arg0).start(arg1);
|
||||
} catch (Exception e) {
|
||||
// no-op, we were never here
|
||||
}
|
||||
}
|
||||
}, plugin(), true);
|
||||
plugin().getServer().getPluginManager().registerEvent((Class<? extends Event>) clazz, this, EventPriority.MONITOR, new EventExecutor() {
|
||||
@Override
|
||||
public void execute(Listener arg0, Event arg1) throws EventException {
|
||||
try {
|
||||
((Insight) arg0).end(arg1);
|
||||
} catch (Exception e) {
|
||||
// no-op, we were never here
|
||||
}
|
||||
}
|
||||
}, plugin(), true);
|
||||
rebounders.add((Class<? extends Event>) clazz);
|
||||
}
|
||||
} catch (NoClassDefFoundError e) {
|
||||
plugin().log(Level.INFO, "...Unable to register event {0} due to dependency failure", clsInfo.getName());
|
||||
} catch (IllegalPluginAccessException ipae) {
|
||||
plugin().log(Level.INFO, "...Unable to register event {0}; abstract or uninstanced event", clsInfo.getName());
|
||||
} catch (Exception e) {
|
||||
plugin().log(Level.WARNING, "...Failed to complete event registration for " + clsInfo.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
plugin().log(Level.WARNING, "Failed to register event classes", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BukkitTask remap = null;
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void postPluginLoad(PluginEnableEvent e) {
|
||||
if (!config.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (remap != null) {
|
||||
remap.cancel();
|
||||
}
|
||||
|
||||
remap = Bukkit.getScheduler().runTaskLater(plugin(), new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
//event.block
|
||||
rebounders.forEach(e -> reboundHandlers(e));
|
||||
}
|
||||
}, 60l); // 3 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* For tracked events we hijack priority ordering insertions
|
||||
* We to grab the registeredlistener array, remove all listeners
|
||||
* then readd, forcing _this_ plugin for _that_ event to be first(s) and last(s) in the list.
|
||||
*
|
||||
* @param clazz The Event class whose handlers we want to reorder
|
||||
*/
|
||||
private <T extends Event> void reboundHandlers(Class<T> clazz) {
|
||||
try {
|
||||
plugin().log(Level.INFO, "Rebound handlers for {0}", clazz.getName());
|
||||
Method method = clazz.getDeclaredMethod("getHandlerList");
|
||||
HandlerList handler = (HandlerList) method.invoke(null);
|
||||
RegisteredListener[] listeners = handler.getRegisteredListeners();
|
||||
ArrayList<RegisteredListener> newOrdering = new ArrayList<RegisteredListener>(listeners.length);
|
||||
ArrayList<RegisteredListener> thisLow = new ArrayList<RegisteredListener>();
|
||||
ArrayList<RegisteredListener> thisHigh = new ArrayList<RegisteredListener>();
|
||||
for (RegisteredListener l : listeners) {
|
||||
handler.unregister(l);
|
||||
if (l.getPriority().equals(EventPriority.LOWEST)) {
|
||||
if (l.getListener().equals(this)) {
|
||||
thisLow.add(l);
|
||||
continue;
|
||||
}
|
||||
} else if (l.getPriority().equals(EventPriority.MONITOR)) {
|
||||
if (l.getListener().equals(this)) {
|
||||
thisHigh.add(l);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newOrdering.add(l);
|
||||
}
|
||||
if (thisLow.isEmpty()) {
|
||||
plugin().log("Failed to find low-order handler!");
|
||||
}
|
||||
thisLow.addAll(newOrdering);
|
||||
if (thisHigh.isEmpty()) {
|
||||
plugin().log("Failed to find high-order handler!");
|
||||
} else {
|
||||
thisLow.addAll(thisHigh);
|
||||
}
|
||||
try {
|
||||
handler.registerAll(thisLow);
|
||||
} catch (Exception e) {
|
||||
plugin().log(Level.SEVERE, "reboundHandler failure to reassert listeners for " + clazz.getName(), e);
|
||||
handler.registerAll(Arrays.asList(listeners));
|
||||
}
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
plugin().log(Level.WARNING, "This event does not have independent handlers, skipping " + clazz.getName());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException m) {
|
||||
plugin().log(Level.SEVERE, "Unable to retrieve handlers for " + clazz.getName(), m);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering Insight's insight command");
|
||||
plugin().registerCommand("insight", this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
if (config.isEnabled()) {
|
||||
this.tracking = new ConcurrentHashMap<String, InsightStat>();
|
||||
this.rebounders = new ArrayList<Class<? extends Event>>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
if (config.isEnabled() || this.tracking != null) {
|
||||
this.tracking.clear();
|
||||
this.tracking = null;
|
||||
this.rebounders = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
if (config.isEnabled()) {
|
||||
StringBuffer sb = new StringBuffer( "Passively providing insight into events:\n");
|
||||
|
||||
if (this.tracking != null) {
|
||||
sb.append(ChatColor.WHITE).append(String.format(" %10s %10s %10s %7s %7s %7s %7s Event\n", "Count", "Canceled", "Handlers", "Min", "Avg", "Max", "StDev"));
|
||||
this.tracking.forEach((k, v) -> {
|
||||
sb.append(String.format(" %s%10d %10d %10d %s%7d %s%7.0f %s%7d %s%7.2f ",
|
||||
ChatColor.WHITE, v.getSamples(), v.getCancels(), v.getHandlers(),
|
||||
color(v.getMin()), v.getMin(),
|
||||
color(v.getAverage()), v.getAverage(),
|
||||
color(v.getMax()), v.getMax(),
|
||||
ChatColor.WHITE, v.getStdev()));
|
||||
sb.append(ChatColor.AQUA).append(k).append("\n");
|
||||
});
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
} else {
|
||||
return "Insights fully disabled.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 10% of tick? Green.
|
||||
* 15% of tick? Dark Green.
|
||||
* 20% of tick? Yellow
|
||||
* 30% of tick? Orange
|
||||
* 50% of tick? Dark Red
|
||||
* Above that? Red
|
||||
*
|
||||
* @param microseconds
|
||||
* @return
|
||||
*/
|
||||
private ChatColor color(long microseconds) {
|
||||
if (microseconds <= 5000) {
|
||||
return ChatColor.GREEN;
|
||||
} else if (microseconds <= 7500) {
|
||||
return ChatColor.DARK_GREEN;
|
||||
} else if (microseconds <= 10000) {
|
||||
return ChatColor.YELLOW;
|
||||
} else if (microseconds <= 15000) {
|
||||
return ChatColor.GOLD;
|
||||
} else if (microseconds <= 25000) {
|
||||
return ChatColor.DARK_RED;
|
||||
} else {
|
||||
return ChatColor.RED;
|
||||
}
|
||||
}
|
||||
|
||||
private ChatColor color(double microseconds) {
|
||||
return color((long) microseconds);
|
||||
}
|
||||
|
||||
// -- SUPPORT
|
||||
|
||||
public void start(Event event) {
|
||||
if (config.isEnabled()) {
|
||||
InsightStat insight = this.tracking.compute(event.getEventName(), (key, stat) ->
|
||||
(stat == null) ? new InsightStat() : stat
|
||||
);
|
||||
insight.begin(event.getHandlers().getRegisteredListeners().length);
|
||||
}
|
||||
}
|
||||
|
||||
public void end(Event event) {
|
||||
if (config.isEnabled()) {
|
||||
InsightStat insight = this.tracking.get(event.getEventName());
|
||||
if (insight != null) {
|
||||
if (event instanceof Cancellable) {
|
||||
insight.end(((Cancellable) event).isCancelled());
|
||||
} else {
|
||||
insight.end(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static InsightConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new InsightConfig(plugin, config);
|
||||
}
|
||||
|
||||
private static class InsightStat {
|
||||
// count of events
|
||||
private long count;
|
||||
// count of cancels
|
||||
private long cancels;
|
||||
// avg length of event (based on LOWEST vs. MONITOR)
|
||||
private double avg;
|
||||
// max length of event (based on LOWEST vs. MONITOR)
|
||||
private long max;
|
||||
// min length of event
|
||||
private long min;
|
||||
// variance of lengths
|
||||
private double varsum;
|
||||
private double var;
|
||||
// rolling stdev of lengths
|
||||
private double std;
|
||||
// last execution handler list size
|
||||
private long handlers;
|
||||
|
||||
private long lastStart;
|
||||
|
||||
public InsightStat() {
|
||||
count = 0l;
|
||||
cancels = 0l;
|
||||
avg = 0l;
|
||||
max = 0l;
|
||||
min = 0l;
|
||||
var = 0l;
|
||||
std = 0l;
|
||||
handlers = 0l;
|
||||
lastStart = 0l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a specific capture for this; assumes runs in LOWEST for same-event.
|
||||
* Will safely skip events that had no end() called.
|
||||
*
|
||||
* @param handlers
|
||||
*/
|
||||
public synchronized void begin(long handlers) {
|
||||
this.lastStart = System.nanoTime() / 1000l;
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a specific capture for this; assumes runs in MONITOR for same-event.
|
||||
*/
|
||||
public synchronized void end(boolean cancelled) {
|
||||
long len = (System.nanoTime() / 1000l) - this.lastStart;
|
||||
if (cancelled) {
|
||||
cancels ++;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
avg = len;
|
||||
max = len;
|
||||
min = len;
|
||||
varsum = 0d;
|
||||
var = 0d;
|
||||
std = 0d;
|
||||
} else {
|
||||
double oavg = avg;
|
||||
avg = avg + ( len - avg )/ (double) (count + 1);
|
||||
// estimate...
|
||||
varsum += Math.pow(len - avg, 2);
|
||||
var = varsum / count; // (pre-inc, see Bessel's correction)
|
||||
std = Math.sqrt(var);
|
||||
if (len > max) {
|
||||
max = len;
|
||||
}
|
||||
if (len < min) {
|
||||
min = len;
|
||||
}
|
||||
}
|
||||
count++; // n+1
|
||||
}
|
||||
|
||||
public long getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public double getAverage() {
|
||||
return avg;
|
||||
}
|
||||
|
||||
public long getSamples() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public double getStdev() {
|
||||
return std;
|
||||
}
|
||||
|
||||
public long getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public long getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public long getCancels() {
|
||||
return cancels;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@@ -24,6 +25,7 @@ import org.bukkit.inventory.meta.BookMeta;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.CommandListener;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.IntrobookConfig;
|
||||
|
||||
/**
|
||||
@@ -114,7 +116,7 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
|
||||
|
||||
if (this.hasBook.remove(puid)) {
|
||||
Inventory inv = dead.getInventory();
|
||||
inv.addItem(config.getIntroBook());
|
||||
inv.addItem(config.getIntroBook(dead));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +133,7 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
|
||||
if (!alive.hasPlayedBefore() || this.hasBook.contains(puid)) {
|
||||
this.hasBook.remove(puid);
|
||||
Inventory inv = alive.getInventory();
|
||||
inv.addItem(config.getIntroBook());
|
||||
inv.addItem(config.getIntroBook(alive));
|
||||
Introbook.bookGiftCount ++;
|
||||
plugin().debug("Gave {0} an introbook", alive.getName());
|
||||
}
|
||||
@@ -149,7 +151,7 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
|
||||
sb.append(ChatColor.WHITE).append("\n Introbooks given out: ");
|
||||
sb.append(ChatColor.GREEN).append(Introbook.bookGiftCount);
|
||||
sb.append("\n Current Introbook:");
|
||||
ItemStack book = config.getIntroBook();
|
||||
ItemStack book = config.getIntroBook(null);
|
||||
if (book != null) {
|
||||
BookMeta meta = (BookMeta) book.getItemMeta();
|
||||
if (meta != null) {
|
||||
@@ -194,9 +196,13 @@ public class Introbook extends SimpleHack<IntrobookConfig> implements Listener,
|
||||
plugin().log(Level.INFO, "Sent introbook to {0}", args[0]);
|
||||
p.sendMessage(ChatColor.GREEN + "You've been given an introductory book!");
|
||||
Inventory inv = p.getInventory();
|
||||
inv.addItem(config.getIntroBook());
|
||||
inv.addItem(config.getIntroBook(p));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IntrobookConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new IntrobookConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import vg.civcraft.mc.namelayer.NameAPI;
|
||||
import net.minecraft.server.v1_10_R1.NBTTagCompound;
|
||||
import net.minecraft.server.v1_10_R1.NBTTagList;
|
||||
import net.minecraft.server.v1_10_R1.WorldNBTStorage;
|
||||
import net.minecraft.server.v1_12_R1.NBTTagCompound;
|
||||
import net.minecraft.server.v1_12_R1.NBTTagList;
|
||||
import net.minecraft.server.v1_12_R1.WorldNBTStorage;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
@@ -15,9 +15,10 @@ import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.v1_10_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_10_R1.inventory.CraftInventoryPlayer;
|
||||
import org.bukkit.craftbukkit.v1_10_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftInventoryPlayer;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
@@ -30,6 +31,7 @@ import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.InvControlConfig;
|
||||
|
||||
public class InvControl extends SimpleHack<InvControlConfig> implements CommandExecutor, Listener {
|
||||
@@ -87,7 +89,7 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
|
||||
int food = rawPlayer.getInt("foodLevel");
|
||||
|
||||
// Fun NMS inventory reconstruction from file data.
|
||||
net.minecraft.server.v1_10_R1.PlayerInventory nms_pl_inv = new net.minecraft.server.v1_10_R1.PlayerInventory(null);
|
||||
net.minecraft.server.v1_12_R1.PlayerInventory nms_pl_inv = new net.minecraft.server.v1_12_R1.PlayerInventory(null);
|
||||
NBTTagList inv = rawPlayer.getList("Inventory", rawPlayer.getTypeId());
|
||||
nms_pl_inv.b(inv); // We use this to bypass the Craft code which requires a player object, unlike NMS.
|
||||
PlayerInventory pl_inv = (PlayerInventory) new CraftInventoryPlayer(nms_pl_inv);
|
||||
@@ -144,7 +146,7 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
|
||||
sb.append("\n Feet: ").append(pl_inv.getBoots());
|
||||
for (int slot = 0; slot < 36; slot++) {
|
||||
final ItemStack it = pl_inv.getItem(slot);
|
||||
sb.append("\n ").append(slot).append(":").append(it);
|
||||
sb.append("\n ").append(slot).append(":").append(it).append(ChatColor.RESET);
|
||||
}
|
||||
sender.sendMessage(sb.toString());
|
||||
} else {
|
||||
@@ -222,4 +224,7 @@ public class InvControl extends SimpleHack<InvControlConfig> implements CommandE
|
||||
}
|
||||
}
|
||||
|
||||
public static InvControlConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new InvControlConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.InvisibleFixConfig;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Sometimes when joining, a player is invisible due to some kind of spigot bug that's been
|
||||
* known since 2015 but unfixed.
|
||||
*
|
||||
* This is widely reported as a fix -- hide then show the player to the other players online.
|
||||
* To do this safely w/o crashing all the things, I schedule the hide and show delayed
|
||||
* so if we get a huge influx of joins this doesn't take forever.
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class InvisibleFix extends SimpleHack<InvisibleFixConfig> implements Listener {
|
||||
public static final String NAME = "InvisibleFix";
|
||||
private AtomicInteger activeJoins = new AtomicInteger(0);
|
||||
|
||||
private LinkedTransferQueue<UUID> ltq = new LinkedTransferQueue<UUID>();
|
||||
private BukkitTask recheckTask = null;
|
||||
|
||||
public InvisibleFix(SimpleAdminHacks plugin, InvisibleFixConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void JoinEvent(PlayerJoinEvent event) {
|
||||
if (!config.isEnabled()) return; // ignore if off
|
||||
Player p = event.getPlayer();
|
||||
if (p == null) return;
|
||||
final UUID uuid = p.getUniqueId();
|
||||
ltq.offer(uuid);
|
||||
|
||||
if (p.isOp() && config.getIgnoreOps()) return;
|
||||
String tPerm = config.getIgnorePermission();
|
||||
if (tPerm != null && p.hasPermission(tPerm)) return;
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin().debug("Hopefully fixing any invisibility for {0}", uuid);
|
||||
try {
|
||||
if (uuid == null) return;
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p == null) return;
|
||||
for (Player online : Bukkit.getOnlinePlayers()) {
|
||||
if (online != p) {
|
||||
online.hidePlayer(p);
|
||||
p.hidePlayer(online);
|
||||
online.showPlayer(p);
|
||||
p.showPlayer(online);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (activeJoins.decrementAndGet() < 0) {
|
||||
activeJoins.set(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, activeJoins.incrementAndGet() * 20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering InvisibleFix listener");
|
||||
plugin().registerListener(this);
|
||||
plugin().log("Starting recheck task");
|
||||
this.recheckTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin(),
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!config.isEnabled()) return;
|
||||
doRecheck();
|
||||
}
|
||||
},
|
||||
config.getRecheckInterval(), config.getRecheckInterval());
|
||||
}
|
||||
}
|
||||
|
||||
private void doRecheck() {
|
||||
// unspool, then recheck.
|
||||
plugin().debug("Refixing invisibles...");
|
||||
HashSet<Player> refix = new HashSet<Player>(config.getMaxPlayersPerRecheck());
|
||||
LinkedList<UUID> skiplist = new LinkedList<UUID>();
|
||||
int cnt = 0;
|
||||
// accumulate
|
||||
while (this.ltq.peek() != null && cnt < config.getMaxPlayersPerRecheck()) {
|
||||
UUID fixerUUID = ltq.poll();
|
||||
if (fixerUUID == null) break;
|
||||
Player fixer = Bukkit.getPlayer(fixerUUID);
|
||||
if (fixer != null) {
|
||||
if (fixer.isOp() && config.getIgnoreOps()) {
|
||||
skiplist.add(fixerUUID);
|
||||
continue;
|
||||
}
|
||||
String tPerm = config.getIgnorePermission();
|
||||
if (tPerm != null && fixer.hasPermission(tPerm)) {
|
||||
skiplist.add(fixerUUID);
|
||||
continue;
|
||||
}
|
||||
refix.add(fixer);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
if (cnt > 0) {
|
||||
plugin().debug("Found {0} players to refix", cnt);
|
||||
// unblind
|
||||
for (Player online : Bukkit.getOnlinePlayers()) {
|
||||
for (Player fix : refix) {
|
||||
if (!refix.contains(fix)) {
|
||||
online.hidePlayer(fix);
|
||||
fix.hidePlayer(online);
|
||||
online.showPlayer(fix);
|
||||
fix.hidePlayer(online);
|
||||
}
|
||||
}
|
||||
}
|
||||
// add them back.
|
||||
for (Player fix : refix) {
|
||||
this.ltq.offer(fix.getUniqueId());
|
||||
}
|
||||
}
|
||||
for (UUID skip : skiplist) {
|
||||
this.ltq.offer(skip);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
activeJoins.set(0);
|
||||
ltq.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
if (this.recheckTask != null) {
|
||||
plugin().debug("Stopping InvisibleFix recheck task");
|
||||
this.recheckTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
ltq.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
if (config != null && config.isEnabled()) {
|
||||
return "InvisibleFix active";
|
||||
} else {
|
||||
return "InvisibleFix not active";
|
||||
}
|
||||
}
|
||||
|
||||
public static InvisibleFixConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new InvisibleFixConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.server.v1_12_R1.EntityTracker;
|
||||
import net.minecraft.server.v1_12_R1.EntityTrackerEntry;
|
||||
import net.minecraft.server.v1_12_R1.WorldServer;
|
||||
import net.minecraft.server.v1_12_R1.PacketPlayOutEntityTeleport;
|
||||
|
||||
import org.bukkit.craftbukkit.v1_12_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftEntity;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.event.vehicle.VehicleMoveEvent;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.InvisibleFixTwoConfig;
|
||||
|
||||
/**
|
||||
* DISABLE WITH 1.11 -- This is for 1.10 only. Basically doing what Mojang was too
|
||||
* busy / lazy to do before 1.11. Note this is not a perfect fix, but at least
|
||||
* seems to deal with the largest areas of issue, namely, boats and horses,
|
||||
* by forcing a location packet to be broadcast to other players that are tracking.
|
||||
*
|
||||
* Portions adapted and heavily modified from code by aadnk: https://gist.github.com/aadnk/3773860
|
||||
*
|
||||
* @author ProgrammerDan
|
||||
*/
|
||||
public class InvisibleFixTwo extends SimpleHack<InvisibleFixTwoConfig> implements Listener {
|
||||
public static final String NAME = "InvisibleFixTwo";
|
||||
|
||||
private Server server;
|
||||
private Map<Integer, Long> updateMap;
|
||||
|
||||
public InvisibleFixTwo(SimpleAdminHacks plugin, InvisibleFixTwoConfig config) {
|
||||
super(plugin, config);
|
||||
this.server = plugin.getServer();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event) {
|
||||
if (!config.isEnabled()) return; // ignore if off
|
||||
|
||||
final Player player = event.getPlayer();
|
||||
|
||||
if (player.isOp() && config.getIgnoreOps()) return;
|
||||
String tPerm = config.getIgnorePermission();
|
||||
if (tPerm != null && player.hasPermission(tPerm)) return;
|
||||
|
||||
server.getScheduler().runTaskLater(plugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
forceUpdate(player);
|
||||
} catch (NullPointerException npe) {
|
||||
plugin().debug("Player offline, no forcefix");
|
||||
}
|
||||
}
|
||||
}, config.getTeleportFixDelay());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerVehicleMove(VehicleMoveEvent move) {
|
||||
if (!config.isEnabled()) return; // ignore if off
|
||||
|
||||
Vehicle vehicle = move.getVehicle();
|
||||
if (vehicle == null) return;
|
||||
if (vehicle.getPassengers() == null || vehicle.getPassengers().size() == 0) return;
|
||||
|
||||
for (Entity e : vehicle.getPassengers()) {
|
||||
if (e == null) continue;
|
||||
|
||||
if (e.isOp() && config.getIgnoreOps()) continue;
|
||||
String tPerm = config.getIgnorePermission();
|
||||
if (tPerm != null && e.hasPermission(tPerm)) continue;
|
||||
|
||||
forceUpdate(e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerMoveInVehicle(PlayerMoveEvent move) {
|
||||
if (!config.isEnabled()) return; // ignore if off
|
||||
|
||||
Player p = move.getPlayer();
|
||||
if (p == null) return;
|
||||
|
||||
if (p.isOp() && config.getIgnoreOps()) return;
|
||||
String tPerm = config.getIgnorePermission();
|
||||
if (tPerm != null && p.hasPermission(tPerm)) return;
|
||||
|
||||
Entity vehicle = p.getVehicle();
|
||||
if (vehicle == null) return;
|
||||
forceUpdate(p);
|
||||
}
|
||||
|
||||
private void forceUpdate(Entity entity) {
|
||||
Long last = updateMap.get(entity.getEntityId());
|
||||
long now = System.currentTimeMillis();
|
||||
if (last == null || last < now - config.getFixInterval()) {
|
||||
updateEntities(entity);
|
||||
updateMap.put(entity.getEntityId(), now);
|
||||
//plugin().debug("Forcing packet-based update for {0}", entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateEntities(final Entity entity) {
|
||||
World world = entity.getWorld();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
|
||||
EntityTracker tracker = worldServer.tracker;
|
||||
|
||||
EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities
|
||||
.get(entity.getEntityId());
|
||||
|
||||
net.minecraft.server.v1_12_R1.Entity eEntity = getNmsEntity(entity);
|
||||
|
||||
entry.broadcast(new PacketPlayOutEntityTeleport(eEntity));
|
||||
}
|
||||
|
||||
private net.minecraft.server.v1_12_R1.Entity getNmsEntity(final Entity entity) {
|
||||
CraftEntity craftEntity = (CraftEntity) entity;
|
||||
return craftEntity.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering InvisibleFixTwo listener");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
updateMap = new HashMap<Integer, Long>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
if (updateMap != null) {
|
||||
updateMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
if (config != null && config.isEnabled()) {
|
||||
return "InvisibleFixTwo active";
|
||||
} else {
|
||||
return "InvisibleFixTwo not active";
|
||||
}
|
||||
}
|
||||
|
||||
public static InvisibleFixTwoConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new InvisibleFixTwoConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -7,6 +7,7 @@ import java.util.logging.Level;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@@ -21,6 +22,7 @@ import org.bukkit.ChatColor;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.BroadcastLevel;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.HackBotConfig;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.NewfriendAssistConfig;
|
||||
|
||||
/**
|
||||
@@ -252,5 +254,8 @@ public class NewfriendAssist extends SimpleHack<NewfriendAssistConfig> implement
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static NewfriendAssistConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new NewfriendAssistConfig(plugin, config);
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.ReinforcedChestBreakConfig;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import vg.civcraft.mc.citadel.Citadel;
|
||||
import vg.civcraft.mc.citadel.ReinforcementManager;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Sends every 3 minutes a message to the admins if a chest is broken
|
||||
*/
|
||||
public class ReinforcedChestBreak extends SimpleHack<ReinforcedChestBreakConfig> implements Listener {
|
||||
|
||||
public static String NAME = "ReinforcedChestBreak";
|
||||
|
||||
private ReinforcementManager manager;
|
||||
private Messenger messenger;
|
||||
private Set<String> messages;
|
||||
|
||||
public ReinforcedChestBreak(SimpleAdminHacks plugin, ReinforcedChestBreakConfig config) {
|
||||
super(plugin, config);
|
||||
}
|
||||
|
||||
public static ReinforcedChestBreakConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new ReinforcedChestBreakConfig(plugin, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (!config.isEnabled()) return;
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
if (!config.isEnabled()) return;
|
||||
messages = new HashSet<>();
|
||||
|
||||
manager = Citadel.getReinforcementManager();
|
||||
messenger = new Messenger();
|
||||
|
||||
Bukkit.getScheduler().runTaskTimer(plugin(), messenger, 0, config.getDelay() * 20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
return "Delay: " + config.getDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets fired by the BLockBreakEvent and checks if the block is reinforced
|
||||
* @param eve BLockBreakEvent
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true)
|
||||
public void onBlockBreak(BlockBreakEvent eve) {
|
||||
if (!config.isEnabled()) return;
|
||||
if (eve.getPlayer() == null) return;
|
||||
if (eve.getBlock() == null) return;
|
||||
Material bbe = eve.getBlock().getType();
|
||||
if (bbe == null) return;
|
||||
if (manager == null) manager = Citadel.getReinforcementManager();
|
||||
if (Material.CHEST.equals(bbe) || Material.TRAPPED_CHEST.equals(bbe)
|
||||
|| Material.ENDER_CHEST.equals(bbe) || Material.FURNACE.equals(bbe)
|
||||
|| Material.BURNING_FURNACE.equals(bbe) || Material.DISPENSER.equals(bbe)
|
||||
|| Material.DROPPER.equals(bbe) || Material.HOPPER.equals(bbe)) {
|
||||
if(manager.isReinforced(eve.getBlock())) {
|
||||
String name = eve.getPlayer().getDisplayName();
|
||||
Location loc = eve.getBlock().getLocation();
|
||||
|
||||
String msg = setVars(name,
|
||||
String.valueOf(loc.getBlockX()),
|
||||
String.valueOf(loc.getBlockY()),
|
||||
String.valueOf(loc.getBlockZ()));
|
||||
|
||||
if(messages.add(msg)) {
|
||||
plugin().log(Level.INFO, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the message
|
||||
* @param name the player name
|
||||
* @param x block x
|
||||
* @param y block y
|
||||
* @param z block z
|
||||
* @return returns the builded String
|
||||
*/
|
||||
private String setVars(String name, String x, String y, String z) {
|
||||
return ChatColor.translateAlternateColorCodes('&', config.getMessage()
|
||||
.replace("%player%", name)
|
||||
.replace("%x%", x)
|
||||
.replace("%y%", y)
|
||||
.replace("%z%", z));
|
||||
}
|
||||
|
||||
private class Messenger implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (String message: messages) {
|
||||
plugin().serverOperatorBroadcast(message);
|
||||
}
|
||||
messages.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.programmerdan.minecraft.simpleadminhacks.hacks;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.SimpleHack;
|
||||
import com.programmerdan.minecraft.simpleadminhacks.configs.SanityHackConfig;
|
||||
|
||||
public class SanityHack extends SimpleHack<SanityHackConfig> implements Listener {
|
||||
|
||||
public static final String NAME = "SanityHack";
|
||||
|
||||
private final int trackingLevel;
|
||||
private int caughtPlayers; // Just to give a useful message in status tbh
|
||||
|
||||
public SanityHack(SimpleAdminHacks plugin, SanityHackConfig config) {
|
||||
super(plugin, config);
|
||||
this.trackingLevel = config.getTrackingLevel();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled=true)
|
||||
public void onBlockBreak(BlockBreakEvent event){
|
||||
if(!config.isEnabled()) return;
|
||||
if(!config.isTrackingBreak()) return;
|
||||
|
||||
|
||||
Block block = event.getBlock();
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (player == null) return;
|
||||
if (block == null) return;
|
||||
|
||||
if(block.getY() < trackingLevel){
|
||||
plugin().log(Level.INFO, "Player({0}, {1}) caught breaking a block({2}) below Y {3} at {4}", player.getName(), player.getUniqueId().toString(),
|
||||
block.getType().toString(), trackingLevel, block.getLocation());
|
||||
caughtPlayers++;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled=true)
|
||||
public void onBlockPlace(BlockPlaceEvent event){
|
||||
if(!config.isEnabled()) return;
|
||||
if(!config.isTrackingPlace()) return;
|
||||
|
||||
Block block = event.getBlock();
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (player == null) return;
|
||||
if (block == null) return;
|
||||
|
||||
if(block.getY() < trackingLevel && player != null){
|
||||
plugin().log(Level.INFO, "Player({0}, {1}) caught placing a block({2}) below Y {3} at {4}", player.getName(), player.getUniqueId().toString(),
|
||||
block.getType().toString(), trackingLevel, block.getLocation());
|
||||
caughtPlayers++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (config.isEnabled()) {
|
||||
plugin().log("Registering SanityHack listeners");
|
||||
plugin().registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataBootstrap() {
|
||||
caughtPlayers = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCommands() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataCleanup() {
|
||||
caughtPlayers = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String status() {
|
||||
if (!config.isEnabled()) {
|
||||
return "Sanity Hack listening disabled.";
|
||||
}
|
||||
|
||||
return caughtPlayers + " caught below Y " + trackingLevel;
|
||||
}
|
||||
|
||||
public static SanityHackConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) {
|
||||
return new SanityHackConfig(plugin, config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+132
-36
@@ -1,4 +1,4 @@
|
||||
# Effectively Modern Humbug. For 1.9 ground up.
|
||||
# Effectively Modern Humbug. For 1.10 ground up.
|
||||
|
||||
configuration_file_version: 1
|
||||
|
||||
@@ -6,40 +6,81 @@ debug: true
|
||||
broadcast_permission: simpleadmin.broadcast
|
||||
|
||||
hacks:
|
||||
experimental:
|
||||
name: Experimental
|
||||
Experimental:
|
||||
enabled: true
|
||||
combatspy: true
|
||||
teleportspy: true
|
||||
combatspy: false
|
||||
teleportspy: false
|
||||
postteleport:
|
||||
spy: true
|
||||
spy: false
|
||||
count: 10
|
||||
game_features:
|
||||
name: GameFeatures
|
||||
enabled: false
|
||||
elytra: off
|
||||
chorus_fruit: off
|
||||
game_fixes:
|
||||
name: GameFixes
|
||||
enabled: false
|
||||
nether_bed_placement: off
|
||||
game_tuning:
|
||||
name: GameTuning
|
||||
enabled: false
|
||||
str2: replace_str1ext
|
||||
pearl_gravity: 0.3
|
||||
combattag_announce:
|
||||
name: CombatTagAnnounce
|
||||
GameFeatures:
|
||||
enabled: true
|
||||
disablePotatoXP: false
|
||||
# Villager trading is not like above and true = able to trade and false = not able
|
||||
villagerTrading: false
|
||||
# Wither spawn is also true = enabled and false = disabled
|
||||
witherSpawning: false
|
||||
# Enderchests is also true = enabled and false = disabled
|
||||
enderChestPlacement: true
|
||||
enderChestUse: false
|
||||
# ElytraUse: true = enabled, false = disabled
|
||||
elytraUse: true
|
||||
chorusFruitTeleportation: false
|
||||
# WeepingAngel bans people when they die, the numbers are in minutes.
|
||||
weepingAngel:
|
||||
enabled: false
|
||||
environment: 1
|
||||
playerKill: 5
|
||||
# prevent use of buckets or dispensing water in hell biomes
|
||||
blockWaterInHell: true
|
||||
GameFixes:
|
||||
enabled: true
|
||||
# Does not fully work atm
|
||||
blockElytraBreakBug: false
|
||||
damageOnElytraBreakBug: 1.0
|
||||
canStorageTeleport: false
|
||||
stopHopperDupe: true
|
||||
stopRailDupe: true
|
||||
stopEndPortalDeletion: true
|
||||
stopBedBombingInHellBiomes: true
|
||||
GameTuning:
|
||||
enabled: true
|
||||
chunkLimits:
|
||||
enabled: true
|
||||
exceededMessage: '&9Limit reached for this chunk, you cannot place more of this type of block.'
|
||||
tileEntities:
|
||||
HOPPER: 4
|
||||
PISTON_BASE: 4
|
||||
PISTON_STICKY_BASE: 4
|
||||
DISPENSER: 4
|
||||
DROPPER: 4
|
||||
REDSTONE_COMPARATOR: 4
|
||||
OBSERVER: 2
|
||||
exempt:
|
||||
- Steve
|
||||
- Alex
|
||||
daytimeBed:
|
||||
enabled: true
|
||||
spawnSetMessage: '&7Your spawn has been set.'
|
||||
# Makes nether ratio 1 to 1
|
||||
oneToOneNether: false
|
||||
# Return nether portal, true = enabled false = disabled
|
||||
returnNetherPortal: true
|
||||
# Disables minecart inventories (trying to fix dupe issues)
|
||||
# enabled = able to right click disabled = not able to right click
|
||||
chestedMinecartInventories: true
|
||||
hopperMinecartInventories: true
|
||||
CTAnnounce:
|
||||
enabled: false
|
||||
delay: 10000
|
||||
message: '%Victim% was combat tagged by %Attacker%'
|
||||
broadcast: [OP, CONSOLE]
|
||||
newfriend:
|
||||
name: NewfriendAssist
|
||||
CrazyCratesAnnounce:
|
||||
enabled: false
|
||||
NewfriendAssist:
|
||||
enabled: true
|
||||
announce:
|
||||
message: '%Player% is brand new!'
|
||||
broadcast: [PERM, CONSOLE]
|
||||
announce: '&f%Player% is brand new!'
|
||||
broadcast: [PERM, CONSOLE]
|
||||
helptips: on
|
||||
helptips_end: 20m
|
||||
introkit:
|
||||
@@ -65,17 +106,17 @@ hacks:
|
||||
- "This world is unforgiving."
|
||||
- "Be sure to get a good night's"
|
||||
- "rest soon."
|
||||
introbook:
|
||||
name: Introbook
|
||||
Introbook:
|
||||
enabled: true
|
||||
follow: true
|
||||
contents:
|
||||
title: Welcome to Devoted
|
||||
title: Welcome, ${player}
|
||||
author: Bonkill
|
||||
pages:
|
||||
- " &oWelcome to Devoted, ${player}!"
|
||||
- " &oWhat is Devoted?\n\"A new type of Minecraft server built towards providing a competitive, high stakes, immersive experience controlled by the players.\"&r\n-ShadedJon, server owner"
|
||||
- " &oWhat is Devoted?&r\n&lNOT just survival&r\nAdmins are hands-off and only handle glitches and hackers\n&lNOT just chaos&r\nPlugins allow players and towns to enforce their own rules\n&lNOT just roleplay&r\nNobody pretends -- conflict is genuine and heated"
|
||||
- " &oBasic Mechanics&r\n-The world has many continents\n-They're separated by ocean.\n-Limited chat range\n-Respawn in random area unless you sleep\n-Mobs spawn sparsely\n-Vanilla Nether\n-Ore veins and vanilla ore spawning."
|
||||
- " &oBasic Mechanics&r\n-The world is custom generated\n-Many custom biomes\n-Limited chat range\n-Respawn in random area unless you sleep\n-Mobs spawn sparsely\n-Vanilla Nether\n-Ore veins and custom ore spawning."
|
||||
- " &oDevoted Mods&r\n &lCitadel&r:\n Reinforces things so it takes numerous breaks to destroy them. Locks some things too.\n&lMore Info:&r &oVisual Guide:&r imgur.com/BnlL2 &oWiki Page:&r tinyurl.com/citadelmod\n&oType \"/help citadel\" in chat to get commands"
|
||||
- " &lPrison Pearl&r:\n Ender Pearls trap players in the end. Others can steal back your pearl and free you -- you always know where it is so they cannot hide it\n&lMore Info:&r &oVisual Guide:&r imgur.com/XbhkK &oWiki Page:&r tinyurl.com/prisperl"
|
||||
- " &lJuke Alert&r:\n Creates 'Juke' blocks that record player activity in radius. If you steal, grief, or trespass -- people will know about it & put a bounty for you to be pearled.\n&lMore Info:&r &oWiki Page:&r tinyurl.com/snitchblock"
|
||||
@@ -87,9 +128,8 @@ hacks:
|
||||
- " &lBrewery&r:\n Custom brewing of booze and more.\n&lMore Info:&r &oBukkit Page:&r tinyurl.com/bukkitbrewp"
|
||||
- " &oFurther Info&r\nVisit our subreddit at: &oreddit.com/r/Devoted&r"
|
||||
- "We extend our appreciation to the devs, admins, and players at: &oreddit.com/r/Civcraft"
|
||||
hackbot:
|
||||
name: HackBot
|
||||
enabled: true
|
||||
HackBot:
|
||||
enabled: false
|
||||
bots:
|
||||
Rando:
|
||||
name: Rando4800xXx
|
||||
@@ -111,6 +151,62 @@ hacks:
|
||||
y: 40.0
|
||||
world: world
|
||||
x: 50.0
|
||||
invctrl:
|
||||
name: InvControl
|
||||
InvControl:
|
||||
enabled: true
|
||||
InvisibleFixTwo:
|
||||
enabled: false
|
||||
# in ticks -- ticks to wait after teleport to force update
|
||||
teleportDelay: 5
|
||||
# in milliseconds -- min time between forced updates if active
|
||||
minInterval: 100
|
||||
ignoreOps: true
|
||||
ignorePermission: sah.allowInvisible
|
||||
InvisibleFix:
|
||||
enabled: false
|
||||
ignoreOps: true
|
||||
ignorePermission: sah.allowInvisible
|
||||
# in ticks
|
||||
recheckInterval: 500
|
||||
ReinforcedChestBreak:
|
||||
enabled: true
|
||||
# in seconds
|
||||
delay: 180
|
||||
message: "&4%player% is raiding a chest at %x% %y% %z%."
|
||||
SanityHack:
|
||||
enabled: true
|
||||
trackPlace: true
|
||||
trackBreak: true
|
||||
belowYLevel: 7
|
||||
TimingsHack:
|
||||
enabled: true
|
||||
BadBoyWatch:
|
||||
enabled: true
|
||||
trackingDepth: 16
|
||||
clearOnMatch: true
|
||||
watchedMaterials:
|
||||
- CHEST
|
||||
- FURNACE
|
||||
- DROPPER
|
||||
- DISPENSER
|
||||
- JUKEBOX
|
||||
- NOTE_BLOCK
|
||||
- BEACON
|
||||
- ENDER_CHEST
|
||||
- SPONGE
|
||||
- HOPPER
|
||||
- PISTON_BASE
|
||||
- PISTON_STICKY_BASE
|
||||
- OBSERVER
|
||||
Insight:
|
||||
enabled: false
|
||||
into:
|
||||
- org.bukkit.event.player
|
||||
- org.bukkit.event.entity
|
||||
- org.bukkit.event.block
|
||||
- org.bukkit.event.inventory
|
||||
- org.bukkit.event.enchantment
|
||||
- org.bukkit.event.hanging
|
||||
- org.bukkit.event.painting
|
||||
- org.bukkit.event.vehicle
|
||||
- org.bukkit.event.weather
|
||||
- org.bukkit.event.world
|
||||
@@ -3,7 +3,7 @@ main: com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks
|
||||
author: ProgrammerDan
|
||||
authors: []
|
||||
version: ${project.version}
|
||||
softdepend: [CombatTagPlus, ProtocolLib, NameLayer, NPCLib]
|
||||
softdepend: [CombatTagPlus, ProtocolLib, NameLayer, NPCLib, CrazyCrates]
|
||||
commands:
|
||||
hacks:
|
||||
description: Shows the various hacks and fixes supported by this plugin and controls them
|
||||
@@ -47,6 +47,38 @@ commands:
|
||||
/<command> all spawn
|
||||
/<command> [status|list]
|
||||
permission: simpleadmin.bots
|
||||
showtimings:
|
||||
description: Get an in-game map that shows tick length and distribution over time (2 mins, updated continuously)
|
||||
usage: /<command>
|
||||
permission: simpleadmin.timings
|
||||
bindtimings:
|
||||
description: Get an in-game map that shows relative tick usage by a targetted class
|
||||
usage: /<command> classname
|
||||
permission: simpleadmin.timings
|
||||
thresholdtimings:
|
||||
description: Get periodic reports on misbehaving classes using a lot of tick time, based on a threshold (as a factor * avg tick)
|
||||
usage: /<command> threshold
|
||||
permission: simpleadmin.timings
|
||||
listtimings:
|
||||
description: Show all Classes that can be used with bindtimings; this is a progressive report function that continues until nothing new is seen
|
||||
usage: /<command>
|
||||
permission: simpleadmin.timings
|
||||
stoptimings:
|
||||
description: Stop any advanced timing tracking. Does not stop /showtimings reults.
|
||||
usage: /<command>
|
||||
permission: simpleadmin.timings
|
||||
insight:
|
||||
description: Event insights. Passively tracks stuff, or actively represents events for players.
|
||||
usage: |
|
||||
/<command> on
|
||||
/<command> active - Activates active monitoring of events. For continuous events, pings you with a summary update every 5 seconds. For initiated events, pings you immediately.
|
||||
/<command> off
|
||||
/<command> inactive - Deactivates active monitoring.
|
||||
/<command> list - Shows all monitored events and some stats about each
|
||||
/<command> list <event> - Shows all monitored events that match the passed param <event>
|
||||
/<command> periodic <time> - Shows a color differentiated summary of monitored events and counts that have changed, with <time> as ticks inbetween display
|
||||
/<command> periodic <event> <time> - Shows a color differentiated summary of monitored evenst that match the passed param <event>, with <time> as ticks inbetween display
|
||||
/<command> - Just shows this description
|
||||
permissions:
|
||||
simpleadmin.*:
|
||||
description: Gives access to all SimpleAdminHacks commands
|
||||
@@ -61,6 +93,7 @@ permissions:
|
||||
simpleadmin.book: true
|
||||
simpleadmin.broadcast: true
|
||||
simpleadmin.bots: true
|
||||
simpleadmin.timings: true
|
||||
simpleadmin.hacks:
|
||||
description: Allows control of the hacks and settings which simpleadminhacks supports
|
||||
default: op
|
||||
@@ -88,3 +121,6 @@ permissions:
|
||||
simpleadmin.bots:
|
||||
description: Gives the ability to create NPCs (bots)
|
||||
default: op
|
||||
simpleadmin.timings:
|
||||
description: Gives the ability to view and manage /...timings commands
|
||||
default: op
|
||||
Reference in new issue
Block a user