Compare commits
91 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7c9bf21ced | ||
![]() |
f4cb5f58e5 | ||
![]() |
3fde89e1ca | ||
![]() |
5701a20cc8 | ||
![]() |
97e17cba3f | ||
![]() |
8acef93d7d | ||
![]() |
353f97a5e8 | ||
![]() |
a3f0fcf5aa | ||
![]() |
cb10fa6720 | ||
![]() |
1c2d966791 | ||
![]() |
092aa33303 | ||
![]() |
a740b34e4e | ||
![]() |
b5d1079a55 | ||
![]() |
7b1c03ff69 | ||
![]() |
fcc025a1a2 | ||
![]() |
9620b230e2 | ||
![]() |
67b2488976 | ||
![]() |
6ac5c1de0e | ||
![]() |
eb5b2beb41 | ||
![]() |
4082e51aa2 | ||
![]() |
d912715741 | ||
![]() |
3b25897490 | ||
![]() |
b058f5f658 | ||
![]() |
7feda4b517 | ||
![]() |
6295fa609c | ||
![]() |
a1cd3a476b | ||
![]() |
6bcecb61ef | ||
![]() |
3cf7ffd1a2 | ||
![]() |
382b2370ea | ||
![]() |
3a0adad1f0 | ||
![]() |
f7d9b7f30d | ||
![]() |
a22b4c715d | ||
![]() |
7eb271c764 | ||
![]() |
80704ba595 | ||
![]() |
0d37534eed | ||
![]() |
4a602b42f6 | ||
![]() |
454c83c7f0 | ||
![]() |
8b2947e312 | ||
![]() |
c94f56f0ac | ||
![]() |
f0c89e897b | ||
![]() |
0a124dc16a | ||
![]() |
318fe7f052 | ||
![]() |
9405176e09 | ||
![]() |
5e2e3c16bb | ||
![]() |
711bcb1c70 | ||
![]() |
d22aa9f189 | ||
![]() |
96d519f84a | ||
![]() |
4eb5892517 | ||
![]() |
5540b4332d | ||
![]() |
4dc4835758 | ||
![]() |
0270890c03 | ||
![]() |
cb5a0a07ca | ||
![]() |
5acc20f485 | ||
![]() |
257662d198 | ||
![]() |
424bc4f706 | ||
![]() |
5c380cccc3 | ||
![]() |
261932393d | ||
![]() |
6c85283344 | ||
![]() |
791698adf9 | ||
![]() |
ec2f4f1e27 | ||
![]() |
abd49226db | ||
![]() |
bd3fa2d4f8 | ||
![]() |
258f44879a | ||
![]() |
39947d460f | ||
![]() |
ca0d62b85f | ||
![]() |
9add7134e5 | ||
![]() |
69e827dc08 | ||
![]() |
3ab058adfb | ||
![]() |
0e8ab30672 | ||
![]() |
76aa22ec27 | ||
![]() |
f3a061a942 | ||
![]() |
5e8a0ebc54 | ||
![]() |
17bd4950d8 | ||
![]() |
1ce8ed4d82 | ||
![]() |
8750db052f | ||
![]() |
77ffae3d09 | ||
![]() |
4ae4a834cd | ||
![]() |
b0bf3ed601 | ||
![]() |
f545ce86c8 | ||
![]() |
05640e87b2 | ||
![]() |
fd7b1552b9 | ||
![]() |
610b73a6c8 | ||
![]() |
c47b5c4376 | ||
![]() |
80c77797bd | ||
![]() |
511e689e0c | ||
![]() |
ad00a02261 | ||
![]() |
dfdd01bfaf | ||
![]() |
e704545487 | ||
![]() |
235052d60b | ||
![]() |
0765550d78 | ||
![]() |
fbc350b025 |
59
.github/workflows/ci.yml
vendored
Normal file
59
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the master branch
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v2.0.0
|
||||
with:
|
||||
# The Java version to set up. Takes a whole or semver Java version. See examples of supported syntax in README file
|
||||
java-version: '16'
|
||||
# Java distribution. See the list of supported distributions in README file
|
||||
distribution: 'adopt'
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Compile
|
||||
run: |
|
||||
mkdir -p build
|
||||
jar_url=$(curl https://hub.spigotmc.org/nexus/content/repositories/snapshots/org/spigotmc/spigot-api/1.17-R0.1-SNAPSHOT/ | grep "shaded.jar\"" | tail -1 | sed "s/.*href=\"\(.*\)\".*/\1/g")
|
||||
curl $jar_url > spigot-shaded.jar
|
||||
find -name "*.java" > sources
|
||||
curl -s https://github.com/GabrielTofvesson/SpigotWizCompat/releases | grep "latest/SpigotWizCompat-.*jar" | cut -d : -f 2,3 | sed "s/[^\"]*\"\\([^\"]*\\)\".*/https:\\/\\/github.com\\/\\1/g" | wget -O SpigotWizCompat.jar -qi -
|
||||
javac -Xlint:deprecation -classpath "spigot-shaded.jar:SpigotWizCompat.jar" @sources -d build
|
||||
cp res/* build
|
||||
rm sources
|
||||
cd build
|
||||
jar cvf build.jar *
|
||||
|
||||
- name: Rename Artifacts
|
||||
run: |
|
||||
cd build
|
||||
mv build.jar $(sha1sum build.jar | sed "s/\([^ ]*\) .*/SpigotInvTweaks-\\1.jar/g")
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "latest"
|
||||
prerelease: true
|
||||
title: "CI Build"
|
||||
files: |
|
||||
build/SpigotInvTweaks-*.jar
|
1
.idea/artifacts/SpigotInvTweaks_jar.xml
generated
1
.idea/artifacts/SpigotInvTweaks_jar.xml
generated
@ -3,6 +3,7 @@
|
||||
<output-path>$PROJECT_DIR$/out/artifacts/SpigotInvTweaks_jar</output-path>
|
||||
<root id="archive" name="SpigotInvTweaks.jar">
|
||||
<element id="module-output" name="SpigotInvTweaks" />
|
||||
<element id="dir-copy" path="$PROJECT_DIR$/res" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
9
.idea/libraries/SpigotWizCompat.xml
generated
Normal file
9
.idea/libraries/SpigotWizCompat.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="SpigotWizCompat">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/lib/SpigotWizCompat.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_15" project-jdk-name="15" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="16" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -3,10 +3,13 @@
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="15" jdkType="JavaSDK" />
|
||||
<orderEntry type="jdk" jdkName="16" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="spigot-1.15.5-R0.1" level="project" />
|
||||
<orderEntry type="library" name="SpigotWizCompat" level="project" />
|
||||
<orderEntry type="library" name="SpigotWizCompat" level="project" />
|
||||
</component>
|
||||
</module>
|
14
res/config.yml
Normal file
14
res/config.yml
Normal file
@ -0,0 +1,14 @@
|
||||
magnet:
|
||||
==: dev.w1zzrd.invtweaks.serialization.MagnetConfig
|
||||
radius: 8.0
|
||||
interval: 5
|
||||
subdivide: 1
|
||||
search:
|
||||
==: dev.w1zzrd.invtweaks.serialization.SearchConfig
|
||||
searchRadiusX: 8
|
||||
searchRadiusY: 8
|
||||
searchRadiusZ: 8
|
||||
ghostClick: true
|
||||
capitator: true
|
||||
capitatorHungerPerBlock: 0.03125
|
||||
capitatorMinHunger: 3
|
40
res/plugin.yml
Normal file
40
res/plugin.yml
Normal file
@ -0,0 +1,40 @@
|
||||
name: InventoryTweaks
|
||||
version: 1.4.1
|
||||
author: IKEA_Jesus
|
||||
main: dev.w1zzrd.invtweaks.InvTweaksPlugin
|
||||
api-version: 1.13
|
||||
depend:
|
||||
- WizCompat
|
||||
commands:
|
||||
sort:
|
||||
description: Sort chest you are looking at
|
||||
usage: /<command>
|
||||
permission: invtweaks.sort
|
||||
magnet:
|
||||
description: Toggle item magnet mode for player
|
||||
usage: /<command>
|
||||
permission: invtweaks.magnet
|
||||
search:
|
||||
description: Search for a given item in all nearby inventories
|
||||
usage: /<command> {item type}
|
||||
permission: invtweaks.search
|
||||
find:
|
||||
description: Show all chests that contain a given item
|
||||
usage: /<command> {item type}
|
||||
permission: invtweaks.find
|
||||
capitator:
|
||||
description: Toggle tree capitation for an axe in your main hand
|
||||
usage: /<command>
|
||||
permission: invtweaks.capitator
|
||||
chestname:
|
||||
description: Add a floating nametag to a chest
|
||||
usage: /<command> {name}
|
||||
permission: invtweaks.spawnfake
|
||||
growup:
|
||||
description: Age baby animals
|
||||
usage: /<command> [radius]
|
||||
permission: invtweaks.growup
|
||||
rewool:
|
||||
description: Put the wool back on sheep
|
||||
usage: /<command> [radius]
|
||||
permission: invtweaks.rewool
|
@ -1,10 +1,19 @@
|
||||
package dev.w1zzrd.invtweaks;
|
||||
|
||||
import dev.w1zzrd.invtweaks.command.MagnetCommandExecutor;
|
||||
import dev.w1zzrd.invtweaks.command.SortCommandExecutor;
|
||||
import dev.w1zzrd.invtweaks.listener.SortListener;
|
||||
import dev.w1zzrd.invtweaks.listener.StackReplaceListener;
|
||||
import dev.w1zzrd.invtweaks.command.*;
|
||||
import dev.w1zzrd.invtweaks.enchantment.CapitatorEnchantment;
|
||||
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
|
||||
import dev.w1zzrd.invtweaks.listener.*;
|
||||
import dev.w1zzrd.invtweaks.serialization.ChestNameConfig;
|
||||
import dev.w1zzrd.invtweaks.serialization.MagnetConfig;
|
||||
import dev.w1zzrd.invtweaks.serialization.MagnetData;
|
||||
import dev.w1zzrd.invtweaks.serialization.SearchConfig;
|
||||
import dev.w1zzrd.spigot.wizcompat.enchantment.EnchantmentRegistryEntry;
|
||||
import dev.w1zzrd.spigot.wizcompat.enchantment.ServerEnchantmentRegistry;
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
@ -21,22 +30,31 @@ public final class InvTweaksPlugin extends JavaPlugin {
|
||||
* Plugin logging tag. This should be prepended to any log messages sent by this plugin
|
||||
*/
|
||||
public static final String LOG_PLUGIN_NAME = "[InventoryTweaks]";
|
||||
private static final String PERSISTENT_DATA_NAME = "data";
|
||||
|
||||
// TODO: Magic values: make a config
|
||||
private static final double MAGNET_DISTANCE = 8.0;
|
||||
private static final long MAGNET_INTERVAL = 5;
|
||||
private static final int MAGNET_SUBDIVIDE = 2;
|
||||
private static final String ENCHANTMENT_CAPITATOR_NAME = "Capitator";
|
||||
|
||||
private final Logger logger = Bukkit.getLogger();
|
||||
|
||||
// Command executor references in case I need them or something idk
|
||||
private SortCommandExecutor sortCommandExecutor;
|
||||
private MagnetCommandExecutor magnetCommandExecutor;
|
||||
private SearchCommandExecutor searchCommandExecutor;
|
||||
private NamedChestCommand namedChestCommandExecutor;
|
||||
private FindCommandExecutor findCommandExecutor;
|
||||
private CapitatorCommand capitatorCommand;
|
||||
private GrowUpCommand growUpCommand;
|
||||
private ReWoolCommand reWoolCommand;
|
||||
private PersistentData data;
|
||||
private NamedChestManager chestManager;
|
||||
private EnchantmentRegistryEntry<CapitatorEnchantment> capitatorEnchantment = null;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
logger.fine(LOG_PLUGIN_NAME + " Plugin enabled");
|
||||
|
||||
enablePersistentData();
|
||||
initEnchantments();
|
||||
initCommands();
|
||||
initEvents();
|
||||
}
|
||||
@ -47,38 +65,72 @@ public final class InvTweaksPlugin extends JavaPlugin {
|
||||
|
||||
disableEvents();
|
||||
disableCommands();
|
||||
disableEnchantments();
|
||||
disablePersistentData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
|
||||
getConfig().options().copyDefaults(true);
|
||||
|
||||
if (magnetCommandExecutor != null)
|
||||
magnetCommandExecutor.reloadConfig();
|
||||
|
||||
if (searchCommandExecutor != null)
|
||||
searchCommandExecutor.reloadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize commands registered by this plugin
|
||||
* Get a reference to the persistent data store object for this plugin
|
||||
* @return An instance of {@link PersistentData} for this plugin
|
||||
*/
|
||||
private void initCommands() {
|
||||
sortCommandExecutor = new SortCommandExecutor();
|
||||
magnetCommandExecutor = new MagnetCommandExecutor(this, MAGNET_DISTANCE, MAGNET_INTERVAL, MAGNET_SUBDIVIDE);
|
||||
public PersistentData getPersistentData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// TODO: Bind command by annotation
|
||||
Objects.requireNonNull(getCommand("sort")).setExecutor(sortCommandExecutor);
|
||||
Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor);
|
||||
private void initEnchantments() {
|
||||
if (getConfig().getBoolean("capitator", true))
|
||||
capitatorEnchantment = ServerEnchantmentRegistry.registerEnchantment(
|
||||
this,
|
||||
new CapitatorEnchantment(
|
||||
ENCHANTMENT_CAPITATOR_NAME,
|
||||
new NamespacedKey(this, ENCHANTMENT_CAPITATOR_NAME)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void disableEnchantments() {
|
||||
if (getConfig().getBoolean("capitator", true))
|
||||
ServerEnchantmentRegistry.unRegisterEnchantment(this, capitatorEnchantment);
|
||||
|
||||
capitatorEnchantment = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize event listeners for this plugin
|
||||
*/
|
||||
private void initEvents() {
|
||||
final boolean activateCapitator = getConfig().getBoolean("capitator", true);
|
||||
final boolean activateGhostClick = getConfig().getBoolean("ghostClick", true);
|
||||
|
||||
final PluginManager pluginManager = getServer().getPluginManager();
|
||||
|
||||
// TODO: Register listeners by annotation
|
||||
pluginManager.registerEvents(new StackReplaceListener(), this);
|
||||
if (activateGhostClick)
|
||||
pluginManager.registerEvents(new GhostClickListener(), this);
|
||||
pluginManager.registerEvents(new StackReplaceListener(this), this);
|
||||
pluginManager.registerEvents(new SortListener(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do whatever is necessary to disable commands and their execution
|
||||
*/
|
||||
private void disableCommands() {
|
||||
sortCommandExecutor = null;
|
||||
magnetCommandExecutor = null;
|
||||
pluginManager.registerEvents(new MagnetismListener(magnetCommandExecutor), this);
|
||||
pluginManager.registerEvents(new TabCompletionListener(), this);
|
||||
pluginManager.registerEvents(new TreeCapitatorListener(
|
||||
activateCapitator ? capitatorEnchantment.getEnchantment() : null,
|
||||
getConfig().getDouble("capitatorHungerPerBlock"),
|
||||
getConfig().getInt("capitatorMinHunger")
|
||||
), this);
|
||||
pluginManager.registerEvents(new PlayerMoveRenderListener(chestManager), this);
|
||||
pluginManager.registerEvents(new ChestBreakListener(chestManager), this);
|
||||
pluginManager.registerEvents(new SignEditListener(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,4 +140,108 @@ public final class InvTweaksPlugin extends JavaPlugin {
|
||||
// Un-register all listeners
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize commands registered by this plugin
|
||||
*/
|
||||
private void initCommands() {
|
||||
final boolean activateCapitator = getConfig().getBoolean("capitator", true);
|
||||
|
||||
sortCommandExecutor = new SortCommandExecutor();
|
||||
magnetCommandExecutor = new MagnetCommandExecutor(this, "magnet", getPersistentData());
|
||||
searchCommandExecutor = new SearchCommandExecutor(this, "search");
|
||||
namedChestCommandExecutor = new NamedChestCommand(chestManager);
|
||||
findCommandExecutor = new FindCommandExecutor(this);
|
||||
growUpCommand = new GrowUpCommand();
|
||||
reWoolCommand = new ReWoolCommand();
|
||||
|
||||
if (activateCapitator)
|
||||
capitatorCommand = new CapitatorCommand(capitatorEnchantment.getEnchantment());
|
||||
|
||||
// TODO: Bind command by annotation
|
||||
Objects.requireNonNull(getCommand("sort")).setExecutor(sortCommandExecutor);
|
||||
Objects.requireNonNull(getCommand("magnet")).setExecutor(magnetCommandExecutor);
|
||||
Objects.requireNonNull(getCommand("search")).setExecutor(searchCommandExecutor);
|
||||
Objects.requireNonNull(getCommand("chestname")).setExecutor(namedChestCommandExecutor);
|
||||
Objects.requireNonNull(getCommand("find")).setExecutor(findCommandExecutor);
|
||||
Objects.requireNonNull(getCommand("growup")).setExecutor(growUpCommand);
|
||||
Objects.requireNonNull(getCommand("rewool")).setExecutor(reWoolCommand);
|
||||
|
||||
if (activateCapitator)
|
||||
Objects.requireNonNull(getCommand("capitator")).setExecutor(capitatorCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do whatever is necessary to disable commands and their execution
|
||||
*/
|
||||
private void disableCommands() {
|
||||
magnetCommandExecutor.onDisable();
|
||||
|
||||
capitatorCommand = null;
|
||||
findCommandExecutor = null;
|
||||
namedChestCommandExecutor = null;
|
||||
searchCommandExecutor = null;
|
||||
magnetCommandExecutor = null;
|
||||
sortCommandExecutor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register type serializers/deserializers for configurations and YAML files
|
||||
*
|
||||
* @see #unregisterSerializers()
|
||||
*/
|
||||
private void registerSerializers() {
|
||||
ConfigurationSerialization.registerClass(MagnetConfig.class);
|
||||
ConfigurationSerialization.registerClass(MagnetData.class);
|
||||
ConfigurationSerialization.registerClass(SearchConfig.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister type serializers/deserializers for configurations and YAML files
|
||||
*
|
||||
* @see #registerSerializers()
|
||||
*/
|
||||
private void unregisterSerializers() {
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.class);
|
||||
ConfigurationSerialization.registerClass(ChestNameConfig.class);
|
||||
ConfigurationSerialization.unregisterClass(MagnetConfig.class);
|
||||
ConfigurationSerialization.unregisterClass(MagnetData.class);
|
||||
ConfigurationSerialization.unregisterClass(SearchConfig.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize persistent data storage sources and handlers
|
||||
*/
|
||||
private void enablePersistentData() {
|
||||
registerSerializers();
|
||||
|
||||
getConfig().options().copyDefaults(true);
|
||||
|
||||
saveConfig();
|
||||
|
||||
// Implicit load
|
||||
data = new PersistentData(PERSISTENT_DATA_NAME, this);
|
||||
|
||||
chestManager = new NamedChestManager(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* De-activate and finalize persistent data storage sources and handlers
|
||||
*/
|
||||
private void disablePersistentData() {
|
||||
chestManager = null;
|
||||
|
||||
data.saveData();
|
||||
data = null;
|
||||
|
||||
//saveConfig();
|
||||
|
||||
unregisterSerializers();
|
||||
}
|
||||
}
|
||||
|
43
src/dev/w1zzrd/invtweaks/command/CapitatorCommand.java
Normal file
43
src/dev/w1zzrd/invtweaks/command/CapitatorCommand.java
Normal file
@ -0,0 +1,43 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class CapitatorCommand implements CommandExecutor {
|
||||
private final Enchantment capitatorEnchantment;
|
||||
|
||||
public CapitatorCommand(final Enchantment capitatorEnchantment) {
|
||||
this.capitatorEnchantment = capitatorEnchantment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (capitatorEnchantment == null) {
|
||||
sender.spigot().sendMessage(CommandUtils.errorMessage("Tree capitation is disabled!"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sender instanceof final Player caller) {
|
||||
final ItemStack stack = caller.getInventory().getItemInMainHand();
|
||||
|
||||
if (stack.getType().name().endsWith("_AXE")) {
|
||||
if (stack.containsEnchantment(capitatorEnchantment)) {
|
||||
stack.removeEnchantment(capitatorEnchantment);
|
||||
sender.spigot().sendMessage(CommandUtils.successMessage("Item is now a regular axe"));
|
||||
} else {
|
||||
stack.addEnchantment(capitatorEnchantment, 1);
|
||||
sender.spigot().sendMessage(CommandUtils.successMessage("Item is now a capitator axe"));
|
||||
}
|
||||
} else
|
||||
sender.spigot().sendMessage(CommandUtils.errorMessage("Only axes can be tree capitators!"));
|
||||
} else
|
||||
sender.spigot().sendMessage(CommandUtils.errorMessage("Only players can create tree capitators!"));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
43
src/dev/w1zzrd/invtweaks/command/Commands.java
Normal file
43
src/dev/w1zzrd/invtweaks/command/Commands.java
Normal file
@ -0,0 +1,43 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.errorMessage;
|
||||
|
||||
public final class Commands {
|
||||
private Commands() { throw new UnsupportedOperationException("Functional class"); }
|
||||
|
||||
public static double getCommandRadius(final CommandSender sender, final String[] args, final int radiusIndex) {
|
||||
try {
|
||||
if (args.length > radiusIndex)
|
||||
return Double.parseDouble(args[radiusIndex]);
|
||||
} catch (NumberFormatException e) {
|
||||
sender.spigot().sendMessage(errorMessage(String.format("\"%s\" is not a number", args[radiusIndex])));
|
||||
}
|
||||
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
public static <T extends Entity> Stream<T> getCommandEntityRadius(
|
||||
final CommandSender sender,
|
||||
final String[] args,
|
||||
final int radiusIndex,
|
||||
final Class<T> entityType
|
||||
) {
|
||||
final double radius = Commands.getCommandRadius(sender, args, 0);
|
||||
if (Double.compare(radius, Double.NaN) == 0)
|
||||
return null;
|
||||
|
||||
return
|
||||
(radius >= 0 ?
|
||||
((Player) sender).getNearbyEntities(radius, radius, radius).stream() :
|
||||
Bukkit.getWorlds().stream().flatMap(it -> it.getEntities().stream()))
|
||||
.filter(it -> entityType.isAssignableFrom(it.getClass()))
|
||||
.map(it -> (T) it);
|
||||
}
|
||||
}
|
98
src/dev/w1zzrd/invtweaks/command/FindCommandExecutor.java
Normal file
98
src/dev/w1zzrd/invtweaks/command/FindCommandExecutor.java
Normal file
@ -0,0 +1,98 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Container;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching;
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
||||
import static dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*;
|
||||
|
||||
public final class FindCommandExecutor implements CommandExecutor {
|
||||
private static final int SEARCH_RADIUS = 3;
|
||||
private static final long DESPAWN_WAIT = 20 * 10;
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
public FindCommandExecutor(final Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
Material targetMaterial;
|
||||
|
||||
if (assertTrue(sender instanceof Player, "Command can only be run by players", sender) ||
|
||||
assertTrue(args.length == 1, "Exactly one argument is expected", sender) ||
|
||||
assertTrue((targetMaterial = getMaterialMatching(args[0])) != null, String.format("Unknown item/block: %s", args[0]), sender))
|
||||
return true;
|
||||
|
||||
final Player player = (Player) sender;
|
||||
|
||||
final List<BlockState> matches = searchChunks(
|
||||
player.getLocation().getChunk(),
|
||||
SEARCH_RADIUS,
|
||||
Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX
|
||||
);
|
||||
|
||||
final List<Integer> entities = matches.stream()
|
||||
.filter(it -> Arrays.stream(((Container)it).getSnapshotInventory().getContents()).filter(Objects::nonNull).map(ItemStack::getType).anyMatch(targetMaterial::equals))
|
||||
.map(state -> spawnMarker(player, state.getLocation().add(0.5, 0.2, 0.5)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (assertTrue(entities.size() > 0, "No containers neardby contain that item/block", sender))
|
||||
return true;
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
entities.forEach(it -> sendEntityDespawnPacket(player, it));
|
||||
}, DESPAWN_WAIT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int spawnMarker(final Player target, final Location location) {
|
||||
final Object entity = createFakeSlime(target);
|
||||
setSlimeSize(entity, 1);
|
||||
setEntityCollision(entity, false);
|
||||
setEntityInvisible(entity, true);
|
||||
setEntityInvulnerable(entity, true);
|
||||
setEntityGlowing(entity, true);
|
||||
setEntityLocation(entity, location.getX(), location.getY(), location.getZ(), 0f, 0f);
|
||||
|
||||
sendEntitySpawnPacket(target, entity);
|
||||
sendEntityMetadataPacket(target, entity);
|
||||
|
||||
return getEntityID(entity);
|
||||
}
|
||||
|
||||
private static List<BlockState> searchChunks(final Chunk middle, final int radius, final Material... targets) {
|
||||
final int xMin = middle.getX() - radius;
|
||||
final int xMax = middle.getX() + radius;
|
||||
final int zMin = middle.getZ() - radius;
|
||||
final int zMax = middle.getZ() + radius;
|
||||
|
||||
final World sourceWorld = middle.getWorld();
|
||||
|
||||
final List<Material> targetMaterials = Arrays.asList(targets);
|
||||
final ArrayList<BlockState> collect = new ArrayList<>();
|
||||
for (int x = xMin; x <= xMax; ++x)
|
||||
for (int z = zMin; z <= zMax; ++z)
|
||||
Arrays.stream(sourceWorld.getChunkAt(x, z).getTileEntities()).filter(it -> targetMaterials.contains(it.getType())).forEach(collect::add);
|
||||
|
||||
return collect;
|
||||
}
|
||||
}
|
28
src/dev/w1zzrd/invtweaks/command/GrowUpCommand.java
Normal file
28
src/dev/w1zzrd/invtweaks/command/GrowUpCommand.java
Normal file
@ -0,0 +1,28 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Ageable;
|
||||
import org.bukkit.entity.Breedable;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
||||
|
||||
public class GrowUpCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (assertTrue(args.length == 0 || sender instanceof Player, "Only players can run this command", sender))
|
||||
return true;
|
||||
|
||||
final Stream<Breedable> breedables = Commands.getCommandEntityRadius(sender, args, 0, Breedable.class);
|
||||
if (breedables == null)
|
||||
return true;
|
||||
|
||||
breedables.filter(it -> !it.getAgeLock() && !it.isAdult()).forEach(Ageable::setAdult);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import dev.w1zzrd.invtweaks.serialization.MagnetConfig;
|
||||
import dev.w1zzrd.invtweaks.serialization.MagnetData;
|
||||
import dev.w1zzrd.spigot.wizcompat.command.ConfigurableCommandExecutor;
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -12,7 +15,6 @@ import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
@ -22,22 +24,12 @@ import static dev.w1zzrd.invtweaks.InvTweaksPlugin.LOG_PLUGIN_NAME;
|
||||
/**
|
||||
* Handler for executions of /magnet command
|
||||
*/
|
||||
public class MagnetCommandExecutor implements CommandExecutor {
|
||||
public class MagnetCommandExecutor extends ConfigurableCommandExecutor<MagnetConfig> {
|
||||
|
||||
private static final Logger logger = Bukkit.getLogger();
|
||||
|
||||
/**
|
||||
* List of players with magnet mode active
|
||||
*/
|
||||
private final List<UUID> activeMagnets = new ArrayList<>();
|
||||
private final List<UUID> activeMagnetsView = Collections.unmodifiableList(activeMagnets);
|
||||
|
||||
private final Plugin plugin;
|
||||
private final long interval;
|
||||
private final int subdivide;
|
||||
|
||||
private final double sqRadius;
|
||||
|
||||
private final PersistentData data;
|
||||
private final MagnetData magnetData;
|
||||
|
||||
private int divIndex = 0;
|
||||
|
||||
@ -46,23 +38,17 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
/**
|
||||
* Initialize the magnet executor and manger
|
||||
* @param plugin Owner plugin for this executor
|
||||
* @param sqRadius Radius of the cube to search for items in (half side length)
|
||||
* @param interval Interval (in ticks) between magnetism checks for active magnets
|
||||
* @param subdivide What fraction of the list of active magnets should be iterated over during a magnetism check.
|
||||
* Set to 1 to check the whole list each iteration
|
||||
*/
|
||||
public MagnetCommandExecutor(final Plugin plugin, final double sqRadius, final long interval, final int subdivide) {
|
||||
this.plugin = plugin;
|
||||
this.sqRadius = sqRadius;
|
||||
this.interval = interval;
|
||||
this.subdivide = subdivide;
|
||||
public MagnetCommandExecutor(final Plugin plugin, final String path, final PersistentData data) {
|
||||
super(plugin, path);
|
||||
this.data = data;
|
||||
this.magnetData = data.loadData("magnets", MagnetData::blank);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
final boolean isMagnetActive = toggleMagnet((Player) sender);
|
||||
|
||||
@ -87,19 +73,12 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
* @return True if, after this method call, the UUID is part of the list of active magnets, else false.
|
||||
*/
|
||||
public boolean toggleMagnet(final UUID uuid) {
|
||||
boolean result = false;
|
||||
try {
|
||||
final int index = Collections.binarySearch(activeMagnets, uuid);
|
||||
|
||||
// See JavaDoc: Collections.binarySearch
|
||||
if (index < 0) {
|
||||
activeMagnets.add(-(index + 1), uuid);
|
||||
return true;
|
||||
} else {
|
||||
activeMagnets.remove(index);
|
||||
return false;
|
||||
}
|
||||
result = magnetData.toggleMagnet(uuid);
|
||||
return result;
|
||||
} finally {
|
||||
updateMagnetismTask();
|
||||
updateMagnetismTask(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +88,7 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
* @return Unmodifiable list view of the active magnets
|
||||
*/
|
||||
public List<UUID> getActiveMagnets() {
|
||||
return activeMagnetsView;
|
||||
return magnetData.getActiveMagnetsView();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,14 +107,9 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
*/
|
||||
public boolean removeMagnet(final UUID uuid) {
|
||||
try {
|
||||
final int index = Collections.binarySearch(activeMagnets, uuid);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
activeMagnets.remove(index);
|
||||
return true;
|
||||
return magnetData.removeMagnet(uuid);
|
||||
} finally {
|
||||
updateMagnetismTask();
|
||||
updateMagnetismTask(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,18 +120,9 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
*/
|
||||
public boolean removeMagnets(final Iterable<UUID> uuids) {
|
||||
try {
|
||||
boolean changed = false;
|
||||
for(final UUID uuid : uuids) {
|
||||
final int index = Collections.binarySearch(activeMagnets, uuid);
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
activeMagnets.remove(index);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
return magnetData.removeMagnets(uuids);
|
||||
} finally {
|
||||
updateMagnetismTask();
|
||||
updateMagnetismTask(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,16 +142,9 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
*/
|
||||
public boolean addMagnet(final UUID uuid) {
|
||||
try {
|
||||
final int index = Collections.binarySearch(activeMagnets, uuid);
|
||||
|
||||
if (index < 0) {
|
||||
activeMagnets.add(-(index + 1), uuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return magnetData.addMagnet(uuid);
|
||||
} finally {
|
||||
updateMagnetismTask();
|
||||
updateMagnetismTask(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +163,7 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
* @return True if the given UUID is marked as a magnet
|
||||
*/
|
||||
public boolean isMagnet(final UUID uuid) {
|
||||
return Collections.binarySearch(activeMagnets, uuid) >= 0;
|
||||
return magnetData.isMagnet(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,15 +173,17 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
* enabled. The refresh task is cancelled and state reset if there are no active magnet players, or the plugin is
|
||||
* disabled.
|
||||
*/
|
||||
private void updateMagnetismTask() {
|
||||
if (refreshTask == null && activeMagnets.size() > 0 && plugin.isEnabled()) {
|
||||
refreshTask = Bukkit.getScheduler().runTaskTimer(plugin, this::taskApplyMagnetism, 0, interval);
|
||||
public void updateMagnetismTask(final boolean checkOnline) {
|
||||
final MagnetConfig config = getConfig();
|
||||
final Plugin plugin = getPlugin();
|
||||
|
||||
if (refreshTask == null && (!checkOnline || magnetData.onlineMagnets() > 0) && plugin.isEnabled()) {
|
||||
refreshTask = Bukkit.getScheduler().runTaskTimer(plugin, this::taskApplyMagnetism, 0, config.getInterval());
|
||||
logger.info(LOG_PLUGIN_NAME + " Activated magnetism check task");
|
||||
}
|
||||
else if (refreshTask != null && (activeMagnets.size() == 0 || !plugin.isEnabled())) {
|
||||
else if (refreshTask != null && ((checkOnline && magnetData.onlineMagnets() == 0) || !plugin.isEnabled())) {
|
||||
Bukkit.getScheduler().cancelTask(refreshTask.getTaskId());
|
||||
refreshTask = null;
|
||||
activeMagnets.clear();
|
||||
divIndex = 0;
|
||||
logger.info(LOG_PLUGIN_NAME + " De-activated magnetism check task");
|
||||
}
|
||||
@ -231,13 +191,17 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
|
||||
/**
|
||||
* Task for teleporting items to magnet players
|
||||
*
|
||||
* @see MagnetCommandExecutor#updateMagnetismTask()
|
||||
*/
|
||||
private void taskApplyMagnetism() {
|
||||
final List<UUID> toRemove = new ArrayList<>();
|
||||
|
||||
final List<UUID> activeMagnets = magnetData.getOnlineMagnetsView();
|
||||
final int size = activeMagnets.size();
|
||||
|
||||
final List<UUID> toRemove = new ArrayList<>();
|
||||
final MagnetConfig config = getConfig();
|
||||
|
||||
final int subdivide = config.getSubdivide();
|
||||
final double sqRadius = config.getRadius();
|
||||
|
||||
// Iterate over a subdivision of the active magnets
|
||||
for (int index = divIndex; index < size; index += subdivide) {
|
||||
@ -265,4 +229,29 @@ public class MagnetCommandExecutor implements CommandExecutor {
|
||||
// Update subdivision to check next iteration
|
||||
divIndex = (divIndex + 1) % subdivide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a potential magnet player logs out
|
||||
* @param magnet Player to check magnet state for
|
||||
*/
|
||||
public void onMagnetLogout(final UUID magnet) {
|
||||
if (magnetData.removeLogoutOnline(magnet))
|
||||
updateMagnetismTask(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a potential magnet player logs in
|
||||
* @param magnet Player to check magnet state for
|
||||
*/
|
||||
public void onMagnetLogin(final UUID magnet) {
|
||||
if (magnetData.addLoginOnline(magnet))
|
||||
updateMagnetismTask(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for saving magnet data to persistent data store
|
||||
*/
|
||||
public void onDisable() {
|
||||
data.storeData("magnets", magnetData);
|
||||
}
|
||||
}
|
||||
|
52
src/dev/w1zzrd/invtweaks/command/NamedChestCommand.java
Normal file
52
src/dev/w1zzrd/invtweaks/command/NamedChestCommand.java
Normal file
@ -0,0 +1,52 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Chest;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static dev.w1zzrd.invtweaks.listener.PlayerMoveRenderListener.RENDER_RADIUS;
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
||||
|
||||
public final class NamedChestCommand implements CommandExecutor {
|
||||
|
||||
private final NamedChestManager manager;
|
||||
|
||||
public NamedChestCommand(final NamedChestManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (assertTrue(sender instanceof Player && ((Player) sender).isOnline(), "Command can only be run by a player!", sender))
|
||||
return true;
|
||||
|
||||
if (assertTrue(args.length <= 1, "Too many arguments for command", sender))
|
||||
return true;
|
||||
|
||||
final Player player = (Player) sender;
|
||||
|
||||
final Block block = player.getTargetBlockExact(10);
|
||||
|
||||
if (assertTrue(block != null && (block.getType() == Material.CHEST || block.getType() == Material.TRAPPED_CHEST), "You must be targeting a chest", sender))
|
||||
return true;
|
||||
|
||||
final Chest chest = (Chest) block.getState();
|
||||
|
||||
if (args.length == 0) {
|
||||
manager.removeTag(chest);
|
||||
} else {
|
||||
if (manager.hasNamedChest(chest))
|
||||
manager.removeTag(chest);
|
||||
|
||||
manager.addTag(chest, args[0]);
|
||||
}
|
||||
|
||||
manager.renderTags(chest.getChunk(), RENDER_RADIUS);
|
||||
return true;
|
||||
}
|
||||
}
|
27
src/dev/w1zzrd/invtweaks/command/ReWoolCommand.java
Normal file
27
src/dev/w1zzrd/invtweaks/command/ReWoolCommand.java
Normal file
@ -0,0 +1,27 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Sheep;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
||||
|
||||
public class ReWoolCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (assertTrue(args.length == 0 || sender instanceof Player, "Only players can run this command", sender))
|
||||
return true;
|
||||
|
||||
final Stream<Sheep> sheep = Commands.getCommandEntityRadius(sender, args, 0, Sheep.class);
|
||||
if (sheep == null)
|
||||
return true;
|
||||
|
||||
sheep.forEach(it -> it.setSheared(false));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
195
src/dev/w1zzrd/invtweaks/command/SearchCommandExecutor.java
Normal file
195
src/dev/w1zzrd/invtweaks/command/SearchCommandExecutor.java
Normal file
@ -0,0 +1,195 @@
|
||||
package dev.w1zzrd.invtweaks.command;
|
||||
|
||||
import dev.w1zzrd.invtweaks.InvTweaksPlugin;
|
||||
import dev.w1zzrd.invtweaks.serialization.SearchConfig;
|
||||
import dev.w1zzrd.spigot.wizcompat.command.ConfigurableCommandExecutor;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.*;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static dev.w1zzrd.invtweaks.listener.TabCompletionListener.getMaterialMatching;
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
||||
|
||||
/**
|
||||
* Handler for executions of /search command
|
||||
*/
|
||||
public class SearchCommandExecutor extends ConfigurableCommandExecutor<SearchConfig> {
|
||||
|
||||
private static final Logger logger = Bukkit.getLogger();
|
||||
|
||||
// TODO: Move to config (magic values)
|
||||
private static final String
|
||||
ERR_NOT_PLAYER = "Command must be run by an in-game player",
|
||||
ERR_NO_ARG = "Command expects an item or block name as an argument",
|
||||
ERR_UNKNOWN = "Unknown item/block name \"%s\"",
|
||||
ERR_NO_INVENTORIES = "No inventories could be found";
|
||||
|
||||
|
||||
public SearchCommandExecutor(final Plugin plugin, final String path) {
|
||||
super(plugin, path);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
final Material targetMaterial;
|
||||
if (assertTrue(sender instanceof Player, ERR_NOT_PLAYER, sender) ||
|
||||
assertTrue(args.length == 1, ERR_NO_ARG, sender) ||
|
||||
assertTrue((targetMaterial = getMaterialMatching(args[0])) != null, String.format(ERR_UNKNOWN, args[0]), sender)
|
||||
) return true;
|
||||
|
||||
final Player player = (Player) sender;
|
||||
final Location playerLocation = player.getLocation();
|
||||
|
||||
final SearchConfig config = getConfig();
|
||||
|
||||
final List<BlockState> matches = searchChunks(
|
||||
playerLocation.getChunk(),
|
||||
config.getSearchRadiusX(),
|
||||
Material.CHEST, Material.TRAPPED_CHEST, Material.SHULKER_BOX
|
||||
);
|
||||
|
||||
// Ensure we found inventory-holding blocks
|
||||
if (assertTrue(matches.size() != 0, ERR_NO_INVENTORIES, sender))
|
||||
return true;
|
||||
|
||||
matches.sort(Comparator.comparingDouble(state -> state.getLocation().distanceSquared(playerLocation)));
|
||||
|
||||
final InventoryHolder result;
|
||||
|
||||
FIND_RESULT:
|
||||
{
|
||||
for (final BlockState check : matches) {
|
||||
final InventoryHolder holder;
|
||||
if (check instanceof Chest)
|
||||
holder = Objects.requireNonNull(((Chest) check).getBlockInventory().getHolder()).getInventory().getHolder();
|
||||
else if (check instanceof ShulkerBox)
|
||||
holder = ((ShulkerBox) check).getInventory().getHolder();
|
||||
else {
|
||||
logger.info(InvTweaksPlugin.LOG_PLUGIN_NAME + " Found non-matching block");
|
||||
continue;
|
||||
}
|
||||
|
||||
assert holder != null;
|
||||
|
||||
if (holder instanceof DoubleChest) {
|
||||
final InventoryHolder left = Objects.requireNonNull(((DoubleChest) holder).getLeftSide());
|
||||
final InventoryHolder right = Objects.requireNonNull(((DoubleChest) holder).getRightSide());
|
||||
|
||||
if (left.getInventory().contains(targetMaterial) || right.getInventory().contains(targetMaterial)) {
|
||||
result = holder;
|
||||
break FIND_RESULT;
|
||||
}
|
||||
} else if (holder.getInventory().contains(targetMaterial)) {
|
||||
result = holder;
|
||||
break FIND_RESULT;
|
||||
}
|
||||
}
|
||||
assertTrue(false, "Could not find inventory with target item/block", sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result instanceof DoubleChest) {
|
||||
final DoubleChest dChest = (DoubleChest) result;
|
||||
|
||||
// Black magic to make chest lid animation behave for double chests
|
||||
try {
|
||||
final Field tileField = dChest.getInventory().getClass().getDeclaredField("tile");
|
||||
tileField.setAccessible(true);
|
||||
|
||||
final Object tile = tileField.get(dChest.getInventory());
|
||||
|
||||
final Field tecField = tile.getClass().getDeclaredField("tileentitychest");
|
||||
tecField.setAccessible(true);
|
||||
|
||||
final Field entityField = player.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("entity");
|
||||
entityField.setAccessible(true);
|
||||
|
||||
final Object entity = entityField.get(player);
|
||||
|
||||
final Method openContainerMethod = entity.getClass().getDeclaredMethod("openContainer", tile.getClass().getInterfaces()[0]);
|
||||
openContainerMethod.setAccessible(true);
|
||||
|
||||
openContainerMethod.invoke(entity, tile);
|
||||
|
||||
final Field activeContainerField = entity.getClass().getSuperclass().getDeclaredField("activeContainer");
|
||||
activeContainerField.setAccessible(true);
|
||||
|
||||
final Object activeContainer = activeContainerField.get(entity);
|
||||
|
||||
final Field checkReachableField = activeContainer.getClass().getSuperclass().getDeclaredField("checkReachable");
|
||||
checkReachableField.setAccessible(true);
|
||||
|
||||
// Disable reach checks for container while it is open
|
||||
checkReachableField.set(activeContainer, false);
|
||||
|
||||
return true;
|
||||
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||
logger.fine(InvTweaksPlugin.LOG_PLUGIN_NAME + " Could not use internal openContainer method; chest lids may stay open");
|
||||
}
|
||||
}
|
||||
|
||||
// Default behaviour for non-double-chest inventories
|
||||
// (plus fallback for double-chests on "unsupported" versions)
|
||||
player.openInventory(result.getInventory());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<BlockState> searchChunks(final Chunk middle, final int radius, final Material... targets) {
|
||||
final int xMin = middle.getX() - radius;
|
||||
final int xMax = middle.getX() + radius;
|
||||
final int zMin = middle.getZ() - radius;
|
||||
final int zMax = middle.getZ() + radius;
|
||||
|
||||
final World sourceWorld = middle.getWorld();
|
||||
|
||||
final List<Material> targetMaterials = Arrays.asList(targets);
|
||||
final ArrayList<BlockState> collect = new ArrayList<>();
|
||||
for (int x = xMin; x <= xMax; ++x)
|
||||
for (int z = zMin; z <= zMax; ++z)
|
||||
Arrays.stream(sourceWorld.getChunkAt(x, z).getTileEntities()).filter(it -> targetMaterials.contains(it.getType())).forEach(collect::add);
|
||||
|
||||
return collect;
|
||||
}
|
||||
|
||||
private static List<BlockState> searchBlocks(
|
||||
final Location centre,
|
||||
final World world,
|
||||
final int rx,
|
||||
final int ry,
|
||||
final int rz,
|
||||
final Material... targets
|
||||
) {
|
||||
if (targets.length == 0)
|
||||
return Collections.emptyList();
|
||||
|
||||
final int x = centre.getBlockX(), y = centre.getBlockY(), z = centre.getBlockZ();
|
||||
final ArrayList<BlockState> matches = new ArrayList<>();
|
||||
for (int dx = -rx; dx < rx; ++dx)
|
||||
for (int dy = -ry; dy < ry; ++dy)
|
||||
CHECK_Z:
|
||||
for (int dz = -rz; dz < rz; ++dz) {
|
||||
final Block check = world.getBlockAt(x + dx, y + dy, z + dz);
|
||||
final Material checkMaterial = check.getType();
|
||||
for (Material target : targets)
|
||||
if (target == checkMaterial) {
|
||||
matches.add(check.getState());
|
||||
continue CHECK_Z;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static dev.w1zzrd.invtweaks.InvTweaksPlugin.LOG_PLUGIN_NAME;
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
|
||||
import static org.bukkit.Material.*;
|
||||
|
||||
/**
|
||||
@ -32,37 +33,34 @@ public class SortCommandExecutor implements CommandExecutor {
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
// Since we rely on targeting an inventory either by what we're looking at or by whom it was called,
|
||||
// there is an implicit dependency on that the command is called by (at the very least) an entity
|
||||
if (!(sender instanceof Player)) {
|
||||
logger.info(LOG_PLUGIN_NAME + " Sort command triggered by non-player");
|
||||
sender.sendMessage("Command must be run by a player");
|
||||
return false;
|
||||
}
|
||||
if (assertTrue(sender instanceof Player, "Command must be run by a player", sender))
|
||||
return true;
|
||||
|
||||
assert sender instanceof Player;
|
||||
final Player player = (Player) sender;
|
||||
|
||||
logger.fine(LOG_PLUGIN_NAME + " Sort triggered by player " + player.getName());
|
||||
|
||||
// The block the player is currently looking at (if applicable)
|
||||
final Block targetBlock = player.getTargetBlockExact(6);
|
||||
if (targetBlock == null)
|
||||
return false;
|
||||
if (targetBlock != null) {
|
||||
final BlockState target = targetBlock.getState();
|
||||
|
||||
final BlockState target = targetBlock.getState();
|
||||
|
||||
// Sort appropriate inventory holder
|
||||
if (target instanceof Chest) {
|
||||
sortChest((Chest) target);
|
||||
player.spigot().sendMessage(new TextComponent("Sorted chest"));
|
||||
}
|
||||
else if (target instanceof ShulkerBox) {
|
||||
sortShulkerBox((ShulkerBox) target);
|
||||
player.spigot().sendMessage(new TextComponent("Sorted shulker box"));
|
||||
}
|
||||
else {
|
||||
sortPlayer(player);
|
||||
player.spigot().sendMessage(new TextComponent("Sorted inventory"));
|
||||
// Sort appropriate inventory holder
|
||||
if (target instanceof Chest) {
|
||||
sortChest((Chest) target);
|
||||
player.spigot().sendMessage(new TextComponent("Sorted chest"));
|
||||
return true;
|
||||
}
|
||||
else if (target instanceof ShulkerBox) {
|
||||
sortShulkerBox((ShulkerBox) target);
|
||||
player.spigot().sendMessage(new TextComponent("Sorted shulker box"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
sortPlayer(player);
|
||||
player.spigot().sendMessage(new TextComponent("Sorted inventory"));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
package dev.w1zzrd.invtweaks.enchantment;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.enchantments.EnchantmentTarget;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public final class CapitatorEnchantment extends Enchantment {
|
||||
private final String name;
|
||||
|
||||
public CapitatorEnchantment(String name, NamespacedKey key) {
|
||||
super(key);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLevel() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartLevel() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnchantmentTarget getItemTarget() {
|
||||
return EnchantmentTarget.TOOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTreasure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCursed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean conflictsWith(Enchantment other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEnchantItem(ItemStack item) {
|
||||
return item.getType().name().endsWith("_AXE");
|
||||
}
|
||||
}
|
471
src/dev/w1zzrd/invtweaks/feature/NamedChestManager.java
Normal file
471
src/dev/w1zzrd/invtweaks/feature/NamedChestManager.java
Normal file
@ -0,0 +1,471 @@
|
||||
package dev.w1zzrd.invtweaks.feature;
|
||||
|
||||
import dev.w1zzrd.invtweaks.serialization.ChestNameConfig;
|
||||
import dev.w1zzrd.spigot.wizcompat.packet.EntityCreator;
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Chest;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static dev.w1zzrd.spigot.wizcompat.block.Chests.*;
|
||||
import static dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*;
|
||||
|
||||
public final class NamedChestManager {
|
||||
private static final String PATH_NAMED_CHESTS = "namedChests";
|
||||
|
||||
private final RenderRegistry renders = new RenderRegistry();
|
||||
|
||||
private final ChestNameConfig config;
|
||||
|
||||
public NamedChestManager(final PersistentData data) {
|
||||
config = data.loadData(PATH_NAMED_CHESTS, ChestNameConfig::new);
|
||||
}
|
||||
|
||||
public boolean hasNamedChest(final World world, final Location loc) {
|
||||
return getChestNameAt(world, loc) != null;
|
||||
}
|
||||
|
||||
public boolean hasNamedChest(final Location loc) {
|
||||
return hasNamedChest(Objects.requireNonNull(loc.getWorld()), loc);
|
||||
}
|
||||
|
||||
public boolean hasNamedChest(final Chest chest) {
|
||||
final Block left = getLeftChest(chest);
|
||||
|
||||
return hasNamedChest(left.getWorld(), left.getLocation());
|
||||
}
|
||||
|
||||
private String getChestName(final Block block) {
|
||||
return getChestNameAt(block.getWorld(), getLeftChest((Chest)block.getState()).getLocation());
|
||||
}
|
||||
|
||||
private void setChestName(final Block block, final String name) {
|
||||
addChestName(block.getWorld(), block.getLocation(), name);
|
||||
}
|
||||
|
||||
private void addChestName(final World world, final Location location, final String name) {
|
||||
config.getEntry(world.getUID()).add(location, name);
|
||||
}
|
||||
|
||||
public String getChestNameAt(final World world, final Location location) {
|
||||
return config.getEntry(world.getUID()).getName(location);
|
||||
}
|
||||
|
||||
public String getChestNameAt(final Location location) {
|
||||
return getChestNameAt(Objects.requireNonNull(location.getWorld()), location);
|
||||
}
|
||||
|
||||
public void untrackPlayer(final Player player) {
|
||||
renders.removeRender(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void renderTags(final Chunk chunk, final int chunkRadius) {
|
||||
chunk.getWorld()
|
||||
.getPlayers()
|
||||
.stream()
|
||||
.filter(it -> {
|
||||
final Chunk playerChunk = it.getLocation().getChunk();
|
||||
|
||||
return Math.abs(playerChunk.getX() - chunk.getX()) <= chunkRadius && Math.abs(playerChunk.getZ() - chunk.getZ()) <= chunkRadius;
|
||||
})
|
||||
.forEach(player -> renderTags(player, chunkRadius));
|
||||
|
||||
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(
|
||||
chunk.getWorld().getUID(),
|
||||
chunk.getX(),
|
||||
chunk.getZ()
|
||||
);
|
||||
|
||||
if (chestChunk != null)
|
||||
chestChunk.setDirty(false);
|
||||
}
|
||||
|
||||
public void renderTags(final Player target, final int chunkRadius) {
|
||||
final UUID worldID = target.getWorld().getUID();
|
||||
|
||||
renders.updateRenders(
|
||||
target,
|
||||
chunkRadius,
|
||||
addedChunk -> {
|
||||
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chunk = config.getChunkEntry(worldID, addedChunk.getRender().x(), addedChunk.getRender().z());
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
final int baseX = chunk.getX() << 4;
|
||||
final int baseZ = chunk.getZ() << 4;
|
||||
|
||||
chunk.streamEntries().forEach(entry -> {
|
||||
final Object entity = entry.getEntity(() -> {
|
||||
final Location loc = getCenterChestLocation(target.getWorld().getBlockAt(baseX + entry.getChunkX(), entry.getY(), baseZ + entry.getChunkZ()));
|
||||
|
||||
final Object newEntity = createFakeSlime(target);
|
||||
setEntityCollision(newEntity, false);
|
||||
setEntityInvulnerable(newEntity, true);
|
||||
setEntityLocation(newEntity, loc.getX(), loc.getY(), loc.getZ(), 0f, 0f);
|
||||
setEntityCustomName(newEntity, entry.getName());
|
||||
setEntityCustomNameVisible(newEntity, true);
|
||||
|
||||
return newEntity;
|
||||
});
|
||||
|
||||
sendEntitySpawnPacket(target, entity);
|
||||
sendEntityMetadataPacket(target, entity);
|
||||
});
|
||||
},
|
||||
removedChunk -> {
|
||||
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chunk = config.getChunkEntry(worldID, removedChunk.getRender().x(), removedChunk.getRender().z());
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
sendEntityDespawnPackets(
|
||||
target,
|
||||
chunk.streamEntries()
|
||||
.map(entry -> entry.getEntity(() -> null))
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(EntityCreator::getEntityID).toArray()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public void removeTag(final World world, final Location location) {
|
||||
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry entry = config.getEntryAt(world.getUID(), location);
|
||||
|
||||
if (entry != null) {
|
||||
final ChunkRenderEntry chunk = renders.getChunk(location.getChunk().getX(), location.getChunk().getZ(), false);
|
||||
|
||||
if (chunk != null) {
|
||||
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(world.getUID(), chunk.getRender().x(), chunk.getRender().z());
|
||||
|
||||
if (chestChunk != null)
|
||||
chestChunk.removeEntry(entry);
|
||||
|
||||
chunk.streamRenders().forEach(it -> {
|
||||
final Player player = Bukkit.getPlayer(it.getRender());
|
||||
|
||||
if (player == null)
|
||||
renders.removeRender(it.getRender());
|
||||
else
|
||||
sendEntityDespawnPacket(player, getEntityID(entry.getEntity(() -> null)));
|
||||
});
|
||||
|
||||
if (chestChunk != null) {
|
||||
chestChunk.setDirty(false);
|
||||
|
||||
// Make sure we don't leave blank data in persistent data file
|
||||
final ChestNameConfig.ChestNameWorldEntry worldEntry = config.getEntry(world.getUID());
|
||||
worldEntry.deleteEmptyChunk(chestChunk);
|
||||
config.deleteEmptyWorld(worldEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeTag(final Location location) {
|
||||
removeTag(Objects.requireNonNull(location.getWorld()), location);
|
||||
}
|
||||
|
||||
public void addTag(final World world, final Location location, final String name) {
|
||||
config.getEntry(world.getUID(), true).add(location, name);
|
||||
}
|
||||
|
||||
public void addTag(final Chest chest, final String name) {
|
||||
final Block left = getLeftChest(chest);
|
||||
|
||||
addTag(left.getWorld(), left.getLocation(), name);
|
||||
}
|
||||
|
||||
public void removeTag(final Chest chest) {
|
||||
final Block left = getLeftChest(chest);
|
||||
|
||||
removeTag(left.getWorld(), left.getLocation());
|
||||
}
|
||||
|
||||
private static Location getCenterChestLocation(final Block chestBlock) {
|
||||
if (isDoubleChest(chestBlock)) {
|
||||
final Location left = getBlockCenter(getLeftChest((Chest) chestBlock.getState()));
|
||||
final Location right = getBlockCenter(getRightChest((Chest) chestBlock.getState()));
|
||||
|
||||
return new Location(left.getWorld(), (left.getX() + right.getX()) / 2.0, left.getY() + 0.2, (left.getZ() + right.getZ()) / 2.0);
|
||||
} else {
|
||||
return getBlockCenter(chestBlock).add(0.0, 0.2, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
private static Location getBlockCenter(final Block block) {
|
||||
return block.getLocation().add(0.5, 0, 0.5);
|
||||
}
|
||||
|
||||
private final class RenderRegistry {
|
||||
private final List<PlayerRenderEntry> playerRegistry = new ArrayList<>();
|
||||
private final List<ChunkRenderEntry> chunkRegistry = new ArrayList<>();
|
||||
|
||||
public void addRender(final Player target, final int chunkX, final int chunkZ) {
|
||||
final PlayerRenderEntry player = getPlayer(target.getUniqueId(), true);
|
||||
final ChunkRenderEntry chunk = getChunk(chunkX, chunkZ, true);
|
||||
|
||||
assert player != null;
|
||||
player.addRender(chunk);
|
||||
|
||||
assert chunk != null;
|
||||
chunk.addRender(player);
|
||||
}
|
||||
|
||||
public void removeRender(final Player target) {
|
||||
removeRender(target.getUniqueId());
|
||||
}
|
||||
|
||||
void removeRender(final UUID offlinePlayer) {
|
||||
final PlayerRenderEntry player = getPlayer(offlinePlayer, false);
|
||||
|
||||
if (player != null) {
|
||||
player.streamRenders().forEach(this::doRemoveChunk);
|
||||
doRemovePlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeRender(final int chunkX, final int chunkZ) {
|
||||
final ChunkRenderEntry chunk = getChunk(chunkX, chunkZ, false);
|
||||
|
||||
if (chunk != null) {
|
||||
chunk.streamRenders().forEach(this::doRemovePlayer);
|
||||
doRemoveChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void doRemoveChunk(final ChunkRenderEntry chunk) {
|
||||
final int index = Collections.binarySearch(chunkRegistry, chunk);
|
||||
|
||||
if (index >= 0)
|
||||
chunkRegistry.remove(index);
|
||||
}
|
||||
|
||||
private void doRemovePlayer(final PlayerRenderEntry player) {
|
||||
final int index = Collections.binarySearch(playerRegistry, player);
|
||||
|
||||
if (index >= 0)
|
||||
playerRegistry.remove(index);
|
||||
}
|
||||
|
||||
public void updateRenders(final Player target, final int chunkRadius, final ChunkEntryChangeHandler onAdd, final ChunkEntryChangeHandler onRemove) {
|
||||
final PlayerRenderEntry player = getPlayer(target.getUniqueId(), true);
|
||||
final UUID worldID = target.getWorld().getUID();
|
||||
final int chunkX = target.getLocation().getBlockX() >> 4;
|
||||
final int chunkZ = target.getLocation().getBlockZ() >> 4;
|
||||
|
||||
final int xMax = chunkX + chunkRadius;
|
||||
final int xMin = chunkX - chunkRadius;
|
||||
final int zMax = chunkZ + chunkRadius;
|
||||
final int zMin = chunkZ - chunkRadius;
|
||||
|
||||
assert player != null;
|
||||
final List<ChunkRenderEntry> toRemove = new ArrayList<>();
|
||||
player.streamRenders()
|
||||
.filter(chunk -> {
|
||||
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(worldID, chunk.getRender().x(), chunk.getRender().z());
|
||||
|
||||
return chunk.getRender().x() > xMax ||
|
||||
chunk.getRender().x() < xMin ||
|
||||
chunk.getRender().z() > zMax ||
|
||||
chunk.getRender().z() < zMin ||
|
||||
(chestChunk != null && chestChunk.isDirty());
|
||||
}
|
||||
)
|
||||
.forEach(chunk -> {
|
||||
toRemove.add(chunk);
|
||||
onRemove.onChange(chunk);
|
||||
});
|
||||
toRemove.forEach(player::removeRender);
|
||||
|
||||
for (int x = xMin; x <= xMax; ++x)
|
||||
for (int z = zMin; z <= zMax; ++z) {
|
||||
final ChunkRenderEntry chunk = getChunk(x, z, true);
|
||||
|
||||
assert chunk != null;
|
||||
if (player.addRender(chunk))
|
||||
onAdd.onChange(chunk);
|
||||
|
||||
chunk.addRender(player);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<PlayerRenderEntry> streamPlayers() {
|
||||
return playerRegistry.stream();
|
||||
}
|
||||
|
||||
public Stream<ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry> streamEntries(final Player target) {
|
||||
final PlayerRenderEntry player = getPlayer(target.getUniqueId(), false);
|
||||
final UUID worldID = target.getWorld().getUID();
|
||||
|
||||
if (player == null)
|
||||
return null;
|
||||
else
|
||||
return player.streamRenders()
|
||||
.map(chunkEntry -> config.getChunkEntry(worldID, chunkEntry.getRender().x(), chunkEntry.getRender().z()));
|
||||
}
|
||||
|
||||
private ChunkRenderEntry getChunk(final int chunkX, final int chunkZ, final boolean addIfMissing) {
|
||||
final ChunkRenderEntry find = new ChunkRenderEntry(chunkX, chunkZ);
|
||||
|
||||
final int index = Collections.binarySearch(chunkRegistry, find);
|
||||
|
||||
if (index >= 0)
|
||||
return chunkRegistry.get(index);
|
||||
else if (addIfMissing) {
|
||||
chunkRegistry.add(-(index + 1), find);
|
||||
return find;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private PlayerRenderEntry getPlayer(final UUID player, final boolean addIfMissing) {
|
||||
final PlayerRenderEntry find = new PlayerRenderEntry(player);
|
||||
|
||||
final int index = Collections.binarySearch(playerRegistry, find);
|
||||
|
||||
if (index >= 0)
|
||||
return playerRegistry.get(index);
|
||||
else if (addIfMissing) {
|
||||
playerRegistry.add(-(index + 1), find);
|
||||
return find;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private interface RenderEntry<T extends Comparable<T>, R> extends Comparable<RenderEntry<T, R>> {
|
||||
T getRender();
|
||||
boolean addRender(final R r);
|
||||
boolean removeRender(final R r);
|
||||
boolean containsRender(final R r);
|
||||
Stream<R> streamRenders();
|
||||
|
||||
@Override
|
||||
default int compareTo(final RenderEntry<T, R> o) {
|
||||
return getRender().compareTo(o.getRender());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PlayerRenderEntry implements RenderEntry<UUID, ChunkRenderEntry> {
|
||||
private final UUID player;
|
||||
private final List<ChunkRenderEntry> chunks = new ArrayList<>();
|
||||
|
||||
public PlayerRenderEntry(final UUID player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getRender() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addRender(final ChunkRenderEntry chunk) {
|
||||
final int index = Collections.binarySearch(chunks, chunk);
|
||||
|
||||
if (index < 0) {
|
||||
chunks.add(-(index + 1), chunk);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRender(final ChunkRenderEntry chunk) {
|
||||
final int index = Collections.binarySearch(chunks, chunk);
|
||||
|
||||
if (index >= 0) {
|
||||
chunks.remove(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsRender(final ChunkRenderEntry chunkRenderEntry) {
|
||||
return Collections.binarySearch(chunks, chunkRenderEntry) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ChunkRenderEntry> streamRenders() {
|
||||
return chunks.stream();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class ChunkRenderEntry implements RenderEntry<ChunkRenderEntry.ChunkCoordinate, PlayerRenderEntry> {
|
||||
private final ChunkCoordinate coords;
|
||||
private final List<PlayerRenderEntry> players = new ArrayList<>();
|
||||
|
||||
public ChunkRenderEntry(final int chunkX, final int chunkZ) {
|
||||
coords = new ChunkCoordinate(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ChunkCoordinate getRender() {
|
||||
return coords;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addRender(final PlayerRenderEntry player) {
|
||||
final int index = Collections.binarySearch(players, player);
|
||||
|
||||
if (index < 0) {
|
||||
players.add(-(index + 1), player);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRender(final PlayerRenderEntry player) {
|
||||
final int index = Collections.binarySearch(players, player);
|
||||
|
||||
if (index >= 0) {
|
||||
players.remove(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsRender(final PlayerRenderEntry chunkRenderEntry) {
|
||||
return Collections.binarySearch(players, chunkRenderEntry) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<PlayerRenderEntry> streamRenders() {
|
||||
return players.stream();
|
||||
}
|
||||
|
||||
public record ChunkCoordinate(int x, int z) implements Comparable<ChunkCoordinate> {
|
||||
@Override
|
||||
public int compareTo(final ChunkCoordinate o) {
|
||||
final int compX = Integer.compare(x, o.x);
|
||||
|
||||
if (compX == 0)
|
||||
return Integer.compare(z, o.z);
|
||||
|
||||
return compX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface ChunkEntryChangeHandler {
|
||||
void onChange(final ChunkRenderEntry chunk);
|
||||
}
|
||||
}
|
43
src/dev/w1zzrd/invtweaks/listener/ChestBreakListener.java
Normal file
43
src/dev/w1zzrd/invtweaks/listener/ChestBreakListener.java
Normal file
@ -0,0 +1,43 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Chest;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
|
||||
import static dev.w1zzrd.invtweaks.listener.PlayerMoveRenderListener.RENDER_RADIUS;
|
||||
import static dev.w1zzrd.spigot.wizcompat.block.Chests.getLeftChest;
|
||||
|
||||
public final class ChestBreakListener implements Listener {
|
||||
|
||||
private final NamedChestManager manager;
|
||||
|
||||
public ChestBreakListener(final NamedChestManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChestBreak(final BlockBreakEvent event) {
|
||||
if (!event.isCancelled())
|
||||
processBlockEvent(event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChestPlace(final BlockPlaceEvent event) {
|
||||
if (!event.isCancelled())
|
||||
processBlockEvent(event);
|
||||
}
|
||||
|
||||
private void processBlockEvent(final BlockEvent event) {
|
||||
if (event.getBlock().getType() == Material.CHEST || event.getBlock().getType() == Material.TRAPPED_CHEST) {
|
||||
final Chest chest = (Chest) event.getBlock().getState();
|
||||
manager.removeTag(chest);
|
||||
manager.renderTags(chest.getChunk(), RENDER_RADIUS);
|
||||
}
|
||||
}
|
||||
}
|
133
src/dev/w1zzrd/invtweaks/listener/GhostClickListener.java
Normal file
133
src/dev/w1zzrd/invtweaks/listener/GhostClickListener.java
Normal file
@ -0,0 +1,133 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import dev.w1zzrd.invtweaks.InvTweaksPlugin;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class GhostClickListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onEntityClick(final PlayerInteractEntityEvent clickEvent) {
|
||||
if (clickEvent.getPlayer().getInventory().getItemInMainHand().getType() == Material.AIR &&
|
||||
clickEvent.getPlayer().getInventory().getItemInOffHand().getType() == Material.AIR &&
|
||||
clickEvent.getHand() == EquipmentSlot.HAND) {
|
||||
final Block b = clickEvent.getPlayer().getTargetBlockExact(6);
|
||||
if (b != null) {
|
||||
try {
|
||||
final Method getNMS = findDeclaredMethod(b.getClass(), "getNMS");
|
||||
if (getNMS == null) {
|
||||
Bukkit.getLogger().warning(InvTweaksPlugin.LOG_PLUGIN_NAME + " Cannot find NMS getter for blocks: ghost click will not function!");
|
||||
return;
|
||||
}
|
||||
getNMS.setAccessible(true);
|
||||
|
||||
final Object nmsBlock = getNMS.invoke(b);
|
||||
|
||||
final Method interact = findDeclaredMethod(nmsBlock.getClass(), "interact");
|
||||
if (interact == null) {
|
||||
Bukkit.getLogger().warning(InvTweaksPlugin.LOG_PLUGIN_NAME + " Cannot find interact method for blocks: ghost click will not function!");
|
||||
return;
|
||||
}
|
||||
interact.setAccessible(true);
|
||||
|
||||
final Field world = findDeclaredField(b.getClass(), "world");
|
||||
if (world == null) {
|
||||
Bukkit.getLogger().warning(InvTweaksPlugin.LOG_PLUGIN_NAME + " Cannot find world field for blocks: ghost click will not function!");
|
||||
return;
|
||||
}
|
||||
world.setAccessible(true);
|
||||
|
||||
final Field entity = findDeclaredField(clickEvent.getPlayer().getClass(), "entity");
|
||||
if (entity == null) {
|
||||
Bukkit.getLogger().warning(InvTweaksPlugin.LOG_PLUGIN_NAME + " Cannot find entity field for players: ghost click will not function!");
|
||||
return;
|
||||
}
|
||||
entity.setAccessible(true);
|
||||
|
||||
final Class<?> enumHand = interact.getParameterTypes()[2];
|
||||
|
||||
final Field enumHandA = enumHand.getDeclaredField("a");
|
||||
enumHandA.setAccessible(true);
|
||||
|
||||
final Class<?> movingObjectPositionBlock = interact.getParameterTypes()[3];
|
||||
final Method mopb_a = findDeclaredMethod(movingObjectPositionBlock, "a", 3);
|
||||
if (mopb_a == null) {
|
||||
Bukkit.getLogger().warning(InvTweaksPlugin.LOG_PLUGIN_NAME + " Cannot find movingObjectPositionBlock function: ghost click will not function!");
|
||||
return;
|
||||
}
|
||||
mopb_a.setAccessible(true);
|
||||
|
||||
final Constructor<?> newVec3D = mopb_a.getParameterTypes()[0].getConstructor(double.class, double.class, double.class);
|
||||
newVec3D.setAccessible(true);
|
||||
|
||||
final Field enumDirection = findDeclaredField(mopb_a.getParameterTypes()[1], "e");
|
||||
if (enumDirection == null) {
|
||||
Bukkit.getLogger().warning(InvTweaksPlugin.LOG_PLUGIN_NAME + " Cannot find EnumDirection value 'e': ghost click will not function!");
|
||||
return;
|
||||
}
|
||||
enumDirection.setAccessible(true);
|
||||
|
||||
final Constructor<?> newBlockPosition = mopb_a.getParameterTypes()[2].getDeclaredConstructor(int.class, int.class, int.class);
|
||||
newBlockPosition.setAccessible(true);
|
||||
|
||||
interact.invoke(
|
||||
nmsBlock,
|
||||
world.get(b),
|
||||
entity.get(clickEvent.getPlayer()),
|
||||
enumHandA.get(null),
|
||||
mopb_a.invoke(
|
||||
null,
|
||||
newVec3D.newInstance(
|
||||
clickEvent.getPlayer().getLocation().getX(),
|
||||
clickEvent.getPlayer().getLocation().getY(),
|
||||
clickEvent.getPlayer().getLocation().getZ()
|
||||
),
|
||||
enumDirection.get(null),
|
||||
newBlockPosition.newInstance(b.getX(), b.getY(), b.getZ())
|
||||
)
|
||||
);
|
||||
} catch (IllegalAccessException | InvocationTargetException | NoSuchFieldException | NoSuchMethodException | InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Method findDeclaredMethod(final Class<?> in, final String name, final int paramLen) {
|
||||
for (final Method check : in.getDeclaredMethods())
|
||||
if (check.getParameterTypes().length == paramLen && check.getName().equals(name))
|
||||
return check;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Method findDeclaredMethod(Class<?> start, final String name) {
|
||||
do {
|
||||
for (final Method check : start.getDeclaredMethods())
|
||||
if (check.getName().equals(name))
|
||||
return check;
|
||||
start = start.getSuperclass();
|
||||
} while(start != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Field findDeclaredField(Class<?> start, final String name) {
|
||||
do {
|
||||
for (final Field check : start.getDeclaredFields())
|
||||
if (check.getName().equals(name))
|
||||
return check;
|
||||
start = start.getSuperclass();
|
||||
} while(start != null);
|
||||
return null;
|
||||
}
|
||||
}
|
48
src/dev/w1zzrd/invtweaks/listener/MagnetismListener.java
Normal file
48
src/dev/w1zzrd/invtweaks/listener/MagnetismListener.java
Normal file
@ -0,0 +1,48 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import dev.w1zzrd.invtweaks.command.MagnetCommandExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerKickEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
/**
|
||||
* Event listener for enabling/disabling magnet task state
|
||||
*/
|
||||
public class MagnetismListener implements Listener {
|
||||
|
||||
private final MagnetCommandExecutor magnetCommandExecutor;
|
||||
|
||||
/**
|
||||
* Create a new listener for the given executor
|
||||
* @param magnetCommandExecutor Command handler to track events for
|
||||
*/
|
||||
public MagnetismListener(final MagnetCommandExecutor magnetCommandExecutor) {
|
||||
this.magnetCommandExecutor = magnetCommandExecutor;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoinEvent(final PlayerJoinEvent event) {
|
||||
magnetCommandExecutor.onMagnetLogin(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDisconnectEvent(final PlayerQuitEvent event) {
|
||||
onLeave(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerKickedEvent(final PlayerKickEvent event) {
|
||||
onLeave(event.getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified handler for when a player disconnects from the server
|
||||
* @param player Player that has disconnected
|
||||
*/
|
||||
private void onLeave(final Player player) {
|
||||
magnetCommandExecutor.onMagnetLogout(player.getUniqueId());
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PlayerMoveRenderListener implements Listener {
|
||||
public static final int RENDER_RADIUS = 3;
|
||||
|
||||
private final List<Chunk> trackers = new ArrayList<>();
|
||||
private final List<UUID> tracked = new ArrayList<>();
|
||||
private final NamedChestManager manager;
|
||||
|
||||
public PlayerMoveRenderListener(final NamedChestManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerMove(final PlayerMoveEvent event) {
|
||||
final int index = Collections.binarySearch(tracked, event.getPlayer().getUniqueId());
|
||||
final Player who = event.getPlayer();
|
||||
final Chunk chunk = who.getLocation().getChunk();
|
||||
|
||||
if (index < 0) {
|
||||
final int actualIndex = -(index + 1);
|
||||
trackers.add(actualIndex, chunk);
|
||||
tracked.add(actualIndex, who.getUniqueId());
|
||||
triggerRender(who);
|
||||
}
|
||||
else if (!trackers.get(index).equals(event.getPlayer().getLocation().getChunk())) {
|
||||
trackers.set(index, chunk);
|
||||
triggerRender(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(final PlayerJoinEvent event) {
|
||||
final int index = Collections.binarySearch(tracked, event.getPlayer().getUniqueId());
|
||||
|
||||
// Should always be true
|
||||
if (index < 0) {
|
||||
trackers.add(-(index + 1), event.getPlayer().getLocation().getChunk());
|
||||
tracked.add(-(index + 1), event.getPlayer().getUniqueId());
|
||||
triggerRender(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerLeave(final PlayerQuitEvent event) {
|
||||
final int index = Collections.binarySearch(tracked, event.getPlayer().getUniqueId());
|
||||
|
||||
// Should always be true
|
||||
if (index >= 0) {
|
||||
trackers.remove(index);
|
||||
tracked.remove(index);
|
||||
manager.untrackPlayer(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerRender(final Player player) {
|
||||
manager.renderTags(player, RENDER_RADIUS);
|
||||
}
|
||||
}
|
21
src/dev/w1zzrd/invtweaks/listener/SignEditListener.java
Normal file
21
src/dev/w1zzrd/invtweaks/listener/SignEditListener.java
Normal file
@ -0,0 +1,21 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.packet.Players;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SignEditListener implements Listener {
|
||||
@EventHandler
|
||||
public void onSignClick(final PlayerInteractEvent event) {
|
||||
if(event.getAction() == Action.RIGHT_CLICK_BLOCK &&
|
||||
Objects.requireNonNull(event.getClickedBlock()).getState() instanceof Sign &&
|
||||
event.getPlayer().isSneaking()) { // Sneak-right-click to edit sign
|
||||
Players.openSignEditor(event.getPlayer(), event.getClickedBlock().getLocation());
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,17 @@ package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.ThrowableProjectile;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||||
import org.bukkit.event.player.PlayerItemBreakEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -17,6 +23,15 @@ import static dev.w1zzrd.invtweaks.InvTweaksPlugin.LOG_PLUGIN_NAME;
|
||||
*/
|
||||
public class StackReplaceListener implements Listener {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
public StackReplaceListener(final Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Max index for main player inventory
|
||||
*/
|
||||
private static final int MAX_MAIN_INV = 35;
|
||||
|
||||
|
||||
@ -24,21 +39,114 @@ public class StackReplaceListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onBlockPlacedEvent(final BlockPlaceEvent event) {
|
||||
final ItemStack usedItemStack = event.getItemInHand();
|
||||
if (findAndMoveSimilarStack(event.getItemInHand(), event.getHand(), event.getPlayer().getInventory(), CompareFunc.defaultFunc()))
|
||||
logger.fine(LOG_PLUGIN_NAME + " Moved stack into empty hand for player " + event.getPlayer().getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerItemBreakEvent(final PlayerItemBreakEvent event) {
|
||||
final PlayerInventory inv = event.getPlayer().getInventory();
|
||||
final ItemStack used = event.getBrokenItem();
|
||||
|
||||
final EquipmentSlot slot;
|
||||
if (used.equals(inv.getBoots())) slot = EquipmentSlot.FEET;
|
||||
else if (used.equals(inv.getLeggings())) slot = EquipmentSlot.LEGS;
|
||||
else if (used.equals(inv.getChestplate())) slot = EquipmentSlot.CHEST;
|
||||
else if (used.equals(inv.getHelmet())) slot = EquipmentSlot.HEAD;
|
||||
else if (used.equals(inv.getItemInOffHand())) slot = EquipmentSlot.OFF_HAND;
|
||||
else if (used.equals(inv.getItemInMainHand())) slot = EquipmentSlot.HAND;
|
||||
else return; // No clue what broke
|
||||
|
||||
if (findAndMoveSimilarStack(used, slot, inv, CompareFunc.toolFunc()))
|
||||
logger.fine(LOG_PLUGIN_NAME + " Moved tool into empty hand for player " + event.getPlayer().getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerThrowSnowballEvent(final ProjectileLaunchEvent event) {
|
||||
if (event.getEntity() instanceof ThrowableProjectile &&
|
||||
event.getEntity().getShooter() instanceof Player) {
|
||||
|
||||
final ThrowableProjectile projectile = (ThrowableProjectile) event.getEntity();
|
||||
final Player thrower = (Player) event.getEntity().getShooter();
|
||||
|
||||
assert thrower != null;
|
||||
final PlayerInventory inventory = thrower.getInventory();
|
||||
final ItemStack stack = projectile.getItem();
|
||||
|
||||
final EquipmentSlot slot;
|
||||
if (inventory.getItemInMainHand().getType() == stack.getType())
|
||||
slot = EquipmentSlot.HAND;
|
||||
else if (inventory.getItemInOffHand().getType() == stack.getType())
|
||||
slot = EquipmentSlot.OFF_HAND;
|
||||
else return;
|
||||
|
||||
if (inventory.getItem(slot).getAmount() == 1)
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
plugin,
|
||||
() -> {
|
||||
if (findAndMoveSimilarStack(stack, slot, inventory, CompareFunc.defaultFunc()))
|
||||
logger.fine(LOG_PLUGIN_NAME + " Moved snowball into empty hand for player " + thrower.getName());
|
||||
},
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find and move a similar stack in the inventory to the one given, using the supplied comparison
|
||||
* function
|
||||
* @param usedItemStack Stack to find a replacement for
|
||||
* @param target Inventory slot to move stack to
|
||||
* @param inventory Inventory to search for similar items in
|
||||
* @param compareFunc Function to use when determining similarity between stacks
|
||||
* @return True if a similar stack was found and moved, else false
|
||||
*/
|
||||
private boolean findAndMoveSimilarStack(
|
||||
final ItemStack usedItemStack,
|
||||
final EquipmentSlot target,
|
||||
final PlayerInventory inventory,
|
||||
final CompareFunc compareFunc) {
|
||||
if (usedItemStack.getAmount() == 1) {
|
||||
final PlayerInventory inv = event.getPlayer().getInventory();
|
||||
for (int i = Math.min(inv.getSize() - 1, MAX_MAIN_INV); i >= 0 ; --i) {
|
||||
final ItemStack checkStack = inv.getItem(i);
|
||||
if (i != inv.getHeldItemSlot() && usedItemStack.isSimilar(checkStack)) {
|
||||
for (int i = Math.min(inventory.getSize() - 1, MAX_MAIN_INV); i >= 0 ; --i) {
|
||||
final ItemStack checkStack = inventory.getItem(i);
|
||||
if (i != inventory.getHeldItemSlot() && compareFunc.isSimilar(usedItemStack, checkStack)) {
|
||||
// To prevent race-condition dupes, remove item before putting it in players hand
|
||||
inv.setItem(i, new ItemStack(Material.AIR, 0));
|
||||
inv.setItem(event.getHand(), checkStack);
|
||||
inventory.setItem(i, new ItemStack(Material.AIR, 0));
|
||||
inventory.setItem(target, checkStack);
|
||||
|
||||
logger.fine(LOG_PLUGIN_NAME + " Moved stack into empty hand for player " + event.getPlayer().getName());
|
||||
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Functional interface for determining when two item stacks are equivalent
|
||||
*
|
||||
* Note: The comparison should represent an equivalence relation (especially, it should be symmetric)
|
||||
*/
|
||||
private interface CompareFunc {
|
||||
boolean isSimilar(final ItemStack i1, final ItemStack i2);
|
||||
|
||||
static CompareFunc defaultFunc() { return ItemStack::isSimilar; }
|
||||
static CompareFunc toolFunc() {
|
||||
return (i1, i2) -> {
|
||||
final boolean isNull1 = i1 == null;
|
||||
final boolean isNull2 = i2 == null;
|
||||
|
||||
if (isNull1 || isNull2)
|
||||
return isNull1 == isNull2; // This should really never return true
|
||||
|
||||
final Material m1 = i1.getType();
|
||||
final Material m2 = i2.getType();
|
||||
|
||||
// If the material name has an underscore, it probably has material type alternatives
|
||||
if (m1.name().contains("_"))
|
||||
return m2.name().endsWith(m1.name().substring(i1.getType().name().lastIndexOf('_')));
|
||||
else
|
||||
return m2.equals(m1);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
127
src/dev/w1zzrd/invtweaks/listener/TabCompletionListener.java
Normal file
127
src/dev/w1zzrd/invtweaks/listener/TabCompletionListener.java
Normal file
@ -0,0 +1,127 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.server.TabCompleteEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Listener for providing tab completions for all commands in this plugin
|
||||
*/
|
||||
public class TabCompletionListener implements Listener {
|
||||
|
||||
private static final List<NamespacedKey> materialTypes = Arrays.stream(Material.values())
|
||||
.map(Material::getKey)
|
||||
.sorted(Comparator.comparing(NamespacedKey::toString))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
/**
|
||||
* Whether or not there are namespaces other than the default "minecraft"
|
||||
*/
|
||||
private static final boolean multiNS;
|
||||
|
||||
|
||||
static {
|
||||
String ns = null;
|
||||
for (final Material mat : Material.values()) {
|
||||
if (ns == null) ns = mat.getKey().getNamespace();
|
||||
else if (!ns.equals(mat.getKey().getNamespace())) {
|
||||
ns = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
multiNS = ns == null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onTabCompleteEvent(final TabCompleteEvent event) {
|
||||
if (event.getSender() instanceof Player) {
|
||||
final String buffer = event.getBuffer();
|
||||
final List<String> completions = event.getCompletions();
|
||||
|
||||
if (buffer.startsWith("/search ")) {
|
||||
final String[] split = buffer.split(" ");
|
||||
|
||||
if (split.length > 2) {
|
||||
completions.clear();
|
||||
event.setCancelled(true);
|
||||
} else if (split.length == 2) {
|
||||
completions.addAll(getMatching(split[1]).collect(Collectors.toList()));
|
||||
} else {
|
||||
completions.clear();
|
||||
completions.addAll(materialTypes.stream().map(TabCompletionListener::toUniqueNSString).collect(Collectors.toList()));
|
||||
}
|
||||
} else if (buffer.startsWith("/magnet ")) {
|
||||
completions.clear();
|
||||
} else if (buffer.startsWith("/sort ")) {
|
||||
completions.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String toUniqueNSString(final NamespacedKey key) {
|
||||
return multiNS ? key.toString() : key.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all material namespace names which fuzzy-match the given string
|
||||
* @param arg String to match to materials
|
||||
* @return Stream of namespace strings associated with matched materials
|
||||
*/
|
||||
public static Stream<String> getMatching(final String arg) {
|
||||
final String[] key = arg.split(":", 2);
|
||||
|
||||
return Arrays.stream(Material.values())
|
||||
.filter(it -> (key.length == 1 || it.getKey().getNamespace().equals(key[0])) &&
|
||||
it.getKey().getKey().contains(key[key.length - 1]))
|
||||
.map(Material::getKey)
|
||||
.map(it -> key.length == 1 ? it.getKey() : it.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all materials which fuzzy-match the given string
|
||||
* @param arg String to match to materials
|
||||
* @return Stream of matched materials
|
||||
*/
|
||||
public static Stream<Material> getAllMaterialsMatching(final String arg) {
|
||||
final String[] key = arg.split(":", 2);
|
||||
|
||||
return Arrays.stream(Material.values())
|
||||
.filter(it -> (key.length == 1 || it.getKey().getNamespace().equals(key[0])) &&
|
||||
it.getKey().getKey().contains(key[key.length - 1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a {@link Material} which is uniquely identifiable by the given string
|
||||
* @param arg Part of namespaced key which uniquely identifies the material
|
||||
* @return The identified material, if any, else null
|
||||
*/
|
||||
public static Material getMaterialMatching(final String arg) {
|
||||
final List<Material> mats = getAllMaterialsMatching(arg).collect(Collectors.toList());
|
||||
|
||||
if (mats.size() == 0)
|
||||
return null;
|
||||
|
||||
if (mats.size() == 1)
|
||||
return mats.get(0);
|
||||
|
||||
return mats.stream()
|
||||
.filter(it -> multiNS || arg.contains(":") ? arg.equals(it.getKey().toString()) : arg.equals(it.getKey().getKey()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
}
|
||||
}
|
257
src/dev/w1zzrd/invtweaks/listener/TreeCapitatorListener.java
Normal file
257
src/dev/w1zzrd/invtweaks/listener/TreeCapitatorListener.java
Normal file
@ -0,0 +1,257 @@
|
||||
package dev.w1zzrd.invtweaks.listener;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.Damageable;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.errorMessage;
|
||||
import static org.bukkit.Material.*;
|
||||
|
||||
public class TreeCapitatorListener implements Listener {
|
||||
|
||||
private static final int MAX_SEARCH_BLOCKS = 69420;
|
||||
private static final List<Material> targetMaterials = Arrays.asList(
|
||||
ACACIA_LOG,
|
||||
OAK_LOG,
|
||||
BIRCH_LOG,
|
||||
JUNGLE_LOG,
|
||||
DARK_OAK_LOG,
|
||||
SPRUCE_LOG,
|
||||
CRIMSON_STEM,
|
||||
WARPED_STEM,
|
||||
ACACIA_WOOD,
|
||||
OAK_WOOD,
|
||||
BIRCH_WOOD,
|
||||
JUNGLE_WOOD,
|
||||
DARK_OAK_WOOD,
|
||||
SPRUCE_WOOD,
|
||||
CRIMSON_STEM,
|
||||
WARPED_STEM,
|
||||
CRIMSON_HYPHAE,
|
||||
WARPED_HYPHAE,
|
||||
STRIPPED_ACACIA_LOG,
|
||||
STRIPPED_OAK_LOG,
|
||||
STRIPPED_BIRCH_LOG,
|
||||
STRIPPED_JUNGLE_LOG,
|
||||
STRIPPED_DARK_OAK_LOG,
|
||||
STRIPPED_SPRUCE_LOG,
|
||||
STRIPPED_CRIMSON_STEM,
|
||||
STRIPPED_WARPED_STEM,
|
||||
STRIPPED_CRIMSON_HYPHAE,
|
||||
STRIPPED_WARPED_HYPHAE,
|
||||
|
||||
ACACIA_LEAVES,
|
||||
OAK_LEAVES,
|
||||
BIRCH_LEAVES,
|
||||
JUNGLE_LEAVES,
|
||||
DARK_OAK_LEAVES,
|
||||
SPRUCE_LEAVES,
|
||||
NETHER_WART_BLOCK,
|
||||
WARPED_WART_BLOCK,
|
||||
SHROOMLIGHT
|
||||
);
|
||||
|
||||
private static final List<Material> leaves = Arrays.asList(
|
||||
ACACIA_LEAVES,
|
||||
OAK_LEAVES,
|
||||
BIRCH_LEAVES,
|
||||
JUNGLE_LEAVES,
|
||||
DARK_OAK_LEAVES,
|
||||
SPRUCE_LEAVES,
|
||||
NETHER_WART_BLOCK,
|
||||
WARPED_WART_BLOCK,
|
||||
SHROOMLIGHT
|
||||
);
|
||||
|
||||
private final Enchantment capitatorEnchantment;
|
||||
private final double hungerPerBlock;
|
||||
private final int minHunger;
|
||||
|
||||
public TreeCapitatorListener(final Enchantment capitatorEnchantment, final double hungerPerBlock, final int minHunger) {
|
||||
this.capitatorEnchantment = capitatorEnchantment;
|
||||
this.hungerPerBlock = hungerPerBlock;
|
||||
this.minHunger = minHunger;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockBreak(final BlockBreakEvent event) {
|
||||
final ItemStack handTool = event.getPlayer().getInventory().getItemInMainHand();
|
||||
if (event.isCancelled() || !handTool.containsEnchantment(capitatorEnchantment))
|
||||
return;
|
||||
|
||||
// Check if capitator functionality is prevented by hunger
|
||||
if (event.getPlayer().getFoodLevel() < minHunger) {
|
||||
event.getPlayer().spigot().sendMessage(errorMessage("You are too tired to fell the tree"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (handTool.getItemMeta() instanceof final Damageable tool) {
|
||||
if (!leaves.contains(event.getBlock().getType()) && targetMaterials.contains(event.getBlock().getType())) {
|
||||
int logBreakCount = 0;
|
||||
|
||||
for (final Block found : findAdjacent(event.getBlock(), getMaxUses(handTool, tool)))
|
||||
if (event.getPlayer().getGameMode() == GameMode.CREATIVE)
|
||||
found.setType(AIR);
|
||||
else {
|
||||
if (!leaves.contains(found.getType()))
|
||||
++logBreakCount;
|
||||
|
||||
found.breakNaturally(handTool);
|
||||
}
|
||||
|
||||
if (event.getPlayer().getGameMode() != GameMode.CREATIVE && !applyDamage(handTool, logBreakCount)) {
|
||||
event.getPlayer().getInventory().setItemInMainHand(new ItemStack(AIR));
|
||||
event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (event.getPlayer().getGameMode() == GameMode.ADVENTURE || event.getPlayer().getGameMode() == GameMode.SURVIVAL) {
|
||||
final int hunger = (int) Math.round(hungerPerBlock * logBreakCount);
|
||||
event.getPlayer().setFoodLevel(Math.max(0, event.getPlayer().getFoodLevel() - hunger));
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getMaxUses(final ItemStack stack, final Damageable tool) {
|
||||
return ((stack.getEnchantmentLevel(Enchantment.DURABILITY) + 1) * (stack.getType().getMaxDurability()) - tool.getDamage()) + 1;
|
||||
}
|
||||
|
||||
private static boolean applyDamage(final ItemStack stack, final int brokenBlocks) {
|
||||
final ItemMeta meta = stack.getItemMeta();
|
||||
|
||||
if (meta instanceof final Damageable toolMeta) {
|
||||
|
||||
final int unbreakingDivider = stack.getEnchantmentLevel(Enchantment.DURABILITY) + 1;
|
||||
final int round = brokenBlocks % unbreakingDivider;
|
||||
final int dmg = (brokenBlocks / unbreakingDivider) + (round != 0 ? 1 : 0);
|
||||
|
||||
if (dmg > (stack.getType().getMaxDurability() - toolMeta.getDamage()))
|
||||
return false;
|
||||
|
||||
toolMeta.setDamage(toolMeta.getDamage() + dmg);
|
||||
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Block> findAdjacent(final Block start, final int softMax) {
|
||||
List<Block> frontier = new ArrayList<>();
|
||||
final List<Block> matches = new ArrayList<>();
|
||||
|
||||
frontier.add(start);
|
||||
matches.add(start);
|
||||
|
||||
int total = 1;
|
||||
int softMaxCount = 1;
|
||||
|
||||
// Keep finding blocks until we have no new matches
|
||||
while (frontier.size() > 0 && total < MAX_SEARCH_BLOCKS && softMaxCount < softMax) {
|
||||
final long result = addAdjacents(frontier, matches, total, softMaxCount, softMax);
|
||||
total = (int) (result >>> 32);
|
||||
softMaxCount = (int) (result & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
private static long addAdjacents(final List<Block> frontier, final List<Block> collect, int total, int softMax, final int softMaxCap) {
|
||||
final List<Block> newFrontier = new ArrayList<>();
|
||||
|
||||
OUTER:
|
||||
for (final Block check : frontier)
|
||||
for (int x = -1; x <= 1; ++x)
|
||||
for (int y = -1; y <= 1; ++y)
|
||||
for (int z = -1; z <= 1; ++z)
|
||||
if ((x | y | z) != 0) {
|
||||
final Block offset = offset(collect, check, x, y, z);
|
||||
|
||||
if (offset != null && !binaryContains(newFrontier, offset)) {
|
||||
binaryInsert(collect, offset);
|
||||
binaryInsert(newFrontier, offset);
|
||||
|
||||
if (!leaves.contains(offset.getType())) {
|
||||
++softMax;
|
||||
|
||||
if (softMax >= softMaxCap)
|
||||
break OUTER;
|
||||
}
|
||||
}
|
||||
|
||||
// Short-circuit for max search
|
||||
++total;
|
||||
if (total == MAX_SEARCH_BLOCKS)
|
||||
break OUTER;
|
||||
}
|
||||
|
||||
frontier.clear();
|
||||
frontier.addAll(newFrontier);
|
||||
|
||||
return (((long)total) << 32) | (((long) softMax) & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private static Block offset(final List<Block> checked, final Block source, int x, int y, int z) {
|
||||
final Block offset = source.getWorld().getBlockAt(source.getLocation().add(x, y, z));
|
||||
|
||||
if (targetMaterials.contains(offset.getType()) && (!leaves.contains(source.getType()) || !leaves.contains(offset.getType())) && !binaryContains(checked, offset))
|
||||
return offset;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean binaryContains(final List<Block> collection, final Block find) {
|
||||
return binarySearchBlock(collection, find) >= 0;
|
||||
}
|
||||
|
||||
private static void binaryInsert(final List<Block> collection, final Block insert) {
|
||||
final int index = binarySearchBlock(collection, insert);
|
||||
|
||||
if (index >= 0)
|
||||
return;
|
||||
|
||||
collection.add(-(index + 1), insert);
|
||||
}
|
||||
|
||||
private static void binaryRemove(final List<Block> collection, final Block remove) {
|
||||
final int index = binarySearchBlock(collection, remove);
|
||||
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
collection.remove(index);
|
||||
}
|
||||
|
||||
private static int binarySearchBlock(final List<Block> collection, final Block find) {
|
||||
return Collections.binarySearch(collection, find, TreeCapitatorListener::blockCompare);
|
||||
}
|
||||
|
||||
private static int blockCompare(final Block b1, final Block b2) {
|
||||
return coordinateCompare(b1.getX(), b1.getY(), b1.getZ(), b2.getX(), b2.getY(), b2.getZ());
|
||||
}
|
||||
private static int coordinateCompare(final int x1, final int y1, final int z1, final int x2, final int y2, final int z2) {
|
||||
final int x = Integer.compare(x1, x2);
|
||||
if (x != 0)
|
||||
return x;
|
||||
|
||||
final int y = Integer.compare(y1, y2);
|
||||
if (y != 0)
|
||||
return y;
|
||||
|
||||
return Integer.compare(z1, z2);
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@
|
||||
* /magnet Toggle magnetism for the sender. When magnetism is enabled, all
|
||||
* items within a 16x16x16 cube around the player that can be picked
|
||||
* up are teleported to the player relatively frequently
|
||||
* /search Find a given item type in any nearby chest and open the chest for
|
||||
* the player
|
||||
* </pre>
|
||||
* <h3>Features</h3>
|
||||
* <ul>
|
||||
|
367
src/dev/w1zzrd/invtweaks/serialization/ChestNameConfig.java
Normal file
367
src/dev/w1zzrd/invtweaks/serialization/ChestNameConfig.java
Normal file
@ -0,0 +1,367 @@
|
||||
package dev.w1zzrd.invtweaks.serialization;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ChestNameConfig extends SimpleReflectiveConfigItem {
|
||||
|
||||
private List<ChestNameWorldEntry> worldEntries;
|
||||
|
||||
public ChestNameConfig(Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
}
|
||||
|
||||
public ChestNameConfig() {
|
||||
this(Collections.emptyMap());
|
||||
worldEntries = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ChestNameWorldEntry getEntry(final UUID worldID, final boolean addIfMissing) {
|
||||
final int index = indexOf(worldID);
|
||||
|
||||
if (index >= 0)
|
||||
return worldEntries.get(index);
|
||||
else if (addIfMissing) {
|
||||
final ChestNameWorldEntry entry = new ChestNameWorldEntry(worldID);
|
||||
worldEntries.add(-(index + 1), entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChestNameWorldEntry getEntry(final UUID worldID) {
|
||||
return getEntry(worldID, true);
|
||||
}
|
||||
|
||||
public ChestNameWorldEntry.ChestNameChunkEntry getChunkEntry(final UUID worldID, final int chunkX, final int chunkZ) {
|
||||
final int index = indexOf(worldID);
|
||||
|
||||
if (index >= 0)
|
||||
return worldEntries.get(index).getChunk(chunkX, chunkZ);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry getEntryAt(final UUID worldID, final Location location) {
|
||||
final ChestNameWorldEntry.ChestNameChunkEntry chunk = getChunkEntry(worldID, location.getBlockX() >> 4, location.getBlockZ() >> 4);
|
||||
|
||||
if (chunk != null) {
|
||||
return chunk.getEntry(location.getBlockX(), location.getBlockY(), location.getBlockZ(), false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean contains(final UUID worldID) {
|
||||
return getEntry(worldID) != null;
|
||||
}
|
||||
|
||||
public void add(final UUID worldID) {
|
||||
getEntry(worldID);
|
||||
}
|
||||
|
||||
public void remove(final UUID worldID) {
|
||||
final int index = indexOf(worldID);
|
||||
|
||||
if (index >= 0)
|
||||
worldEntries.remove(index);
|
||||
}
|
||||
|
||||
private int indexOf(final UUID worldID) {
|
||||
return Collections.binarySearch(worldEntries, new ChestNameWorldEntry(worldID));
|
||||
}
|
||||
|
||||
public void deleteEmptyWorld(final ChestNameWorldEntry world) {
|
||||
if (!world.hasEntries()) {
|
||||
final int index = Collections.binarySearch(worldEntries, world);
|
||||
|
||||
if (index >= 0)
|
||||
worldEntries.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final class ChestNameWorldEntry extends SimpleReflectiveConfigItem implements Comparable<ChestNameWorldEntry> {
|
||||
private String worldIDStr;
|
||||
private final transient UUID worldID;
|
||||
private List<ChestNameChunkEntry> chunks;
|
||||
|
||||
/**
|
||||
* Required constructor for deserializing data
|
||||
*
|
||||
* @param mappings Data to deserialize
|
||||
*/
|
||||
public ChestNameWorldEntry(Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
worldID = UUID.fromString(worldIDStr);
|
||||
}
|
||||
|
||||
ChestNameWorldEntry(final UUID worldID) {
|
||||
super(Collections.emptyMap());
|
||||
this.worldID = worldID;
|
||||
worldIDStr = worldID.toString();
|
||||
chunks = new ArrayList<>();
|
||||
}
|
||||
|
||||
public UUID getWorldID() {
|
||||
return worldID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final ChestNameWorldEntry o) {
|
||||
return getWorldID().compareTo(o.getWorldID());
|
||||
}
|
||||
|
||||
public String getName(final Location location) {
|
||||
final ChestNameChunkEntry chunk = getChunk(location, false);
|
||||
|
||||
if (chunk == null)
|
||||
return null;
|
||||
else
|
||||
return chunk.getName(location);
|
||||
}
|
||||
|
||||
public boolean contains(final Location location) {
|
||||
return getName(location) != null;
|
||||
}
|
||||
|
||||
public boolean hasEntries() {
|
||||
return chunks.size() > 0;
|
||||
}
|
||||
|
||||
public void add(final Location location, final String name) {
|
||||
Objects.requireNonNull(getChunk(location, true)).add(location, name);
|
||||
}
|
||||
|
||||
public void remove(final Location location) {
|
||||
final int index = indexOf(location);
|
||||
|
||||
if (index >= 0)
|
||||
chunks.remove(index);
|
||||
}
|
||||
|
||||
private int indexOf(final Location location) {
|
||||
return Collections.binarySearch(chunks, new ChestNameChunkEntry(location));
|
||||
}
|
||||
|
||||
private int indexOf(final int chunkX, final int chunkZ) {
|
||||
return Collections.binarySearch(chunks, new ChestNameChunkEntry(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
private ChestNameChunkEntry getChunk(final Location location, final boolean addIfMissing) {
|
||||
final int index = indexOf(location);
|
||||
|
||||
if (index >= 0)
|
||||
return chunks.get(index);
|
||||
else if (addIfMissing) {
|
||||
final ChestNameChunkEntry entry = new ChestNameChunkEntry(location);
|
||||
chunks.add(-(index + 1), entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ChestNameChunkEntry getChunk(final int chunkX, final int chunkZ) {
|
||||
final int index = indexOf(chunkX, chunkZ);
|
||||
|
||||
if (index >= 0)
|
||||
return chunks.get(index);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private void clearChunk(final Location location) {
|
||||
final int index = indexOf(location);
|
||||
|
||||
if (index >= 0)
|
||||
chunks.remove(index);
|
||||
}
|
||||
|
||||
public void deleteEmptyChunk(final ChestNameChunkEntry chunk) {
|
||||
if (!chunk.hasEntries()) {
|
||||
final int index = Collections.binarySearch(chunks, chunk);
|
||||
|
||||
if (index >= 0)
|
||||
chunks.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ChestNameChunkEntry extends SimpleReflectiveConfigItem implements Comparable<ChestNameChunkEntry> {
|
||||
private int x, z;
|
||||
private List<ChestNameConfigEntry> entries;
|
||||
private transient boolean dirty = false;
|
||||
|
||||
public ChestNameChunkEntry(Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
}
|
||||
|
||||
ChestNameChunkEntry(final int x, final int z) {
|
||||
super(Collections.emptyMap());
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
entries = new ArrayList<>();
|
||||
}
|
||||
|
||||
ChestNameChunkEntry(final Location location) {
|
||||
// Convert world coordinates to chunk coordinates
|
||||
this(location.getBlockX() >> 4, location.getBlockZ() >> 4);
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public void setDirty(final boolean dirty) {
|
||||
this.dirty = dirty;
|
||||
}
|
||||
|
||||
public boolean hasEntries() {
|
||||
return entries.size() > 0;
|
||||
}
|
||||
|
||||
public void add(final int x, final int y, final int z, final String name) {
|
||||
final ChestNameConfigEntry check = getEntry(x, y, z, true);
|
||||
|
||||
assert check != null;
|
||||
check.setName(name);
|
||||
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
public void add(final Location location, final String name) {
|
||||
add(location.getBlockX(), location.getBlockY(), location.getBlockZ(), name);
|
||||
}
|
||||
|
||||
public String getName(final Location location) {
|
||||
final ChestNameConfigEntry entry = getEntry(location.getBlockX(), location.getBlockY(), location.getBlockZ(), false);
|
||||
|
||||
if (entry == null)
|
||||
return null;
|
||||
else
|
||||
return entry.getName();
|
||||
}
|
||||
|
||||
private ChestNameConfigEntry getEntry(final int x, final int y, final int z, final boolean createIfMissing) {
|
||||
final ChestNameConfigEntry find = new ChestNameConfigEntry(x, y, z);
|
||||
|
||||
final int index = indexOf(find);
|
||||
|
||||
if (index >= 0)
|
||||
return entries.get(index);
|
||||
else if (createIfMissing) {
|
||||
entries.add(-(index + 1), find);
|
||||
return find;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeEntry(final ChestNameConfigEntry entry) {
|
||||
final int index = indexOf(entry);
|
||||
|
||||
if (index >= 0) {
|
||||
entries.remove(index);
|
||||
setDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<ChestNameConfigEntry> streamEntries() {
|
||||
return entries.stream();
|
||||
}
|
||||
|
||||
private int indexOf(final ChestNameConfigEntry find) {
|
||||
return Collections.binarySearch(entries, find);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final ChestNameChunkEntry o) {
|
||||
final int compX = Integer.compare(x, o.x);
|
||||
|
||||
if (compX == 0)
|
||||
return Integer.compare(z, o.z);
|
||||
|
||||
return compX;
|
||||
}
|
||||
|
||||
public static final class ChestNameConfigEntry extends SimpleReflectiveConfigItem implements Comparable<ChestNameConfigEntry> {
|
||||
private transient Object entity;
|
||||
private transient int locInt;
|
||||
private String loc;
|
||||
private String name;
|
||||
|
||||
public ChestNameConfigEntry(Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
locInt = Integer.parseInt(loc, 16);
|
||||
}
|
||||
|
||||
ChestNameConfigEntry(final int x, final int y, final int z, final String name) {
|
||||
super(Collections.emptyMap());
|
||||
locInt = ((y & 0xFFF) << 8) | ((x & 0xF) << 4) | (z & 0xF);
|
||||
loc = Integer.toString(locInt, 16);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
ChestNameConfigEntry(final int x, final int y, final int z) {
|
||||
this(x, y, z, null);
|
||||
}
|
||||
|
||||
ChestNameConfigEntry(final Location location, final String name) {
|
||||
this(location.getBlockX() & 0xF, location.getBlockY(), location.getBlockZ() & 0xF, name);
|
||||
}
|
||||
|
||||
ChestNameConfigEntry(final Location location) {
|
||||
this(location, null);
|
||||
}
|
||||
|
||||
public Object getEntity(final EntityCreator creator) {
|
||||
if (entity == null)
|
||||
entity = creator.createFakeEntity();
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public int getChunkX() {
|
||||
return (locInt >>> 4) & 0xF;
|
||||
}
|
||||
|
||||
public int getChunkZ() {
|
||||
return locInt & 0xF;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return (locInt >>> 8) & 0xFFF;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final ChestNameConfigEntry o) {
|
||||
return Integer.compare(locInt, o.locInt);
|
||||
}
|
||||
|
||||
public interface EntityCreator {
|
||||
Object createFakeEntity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
89
src/dev/w1zzrd/invtweaks/serialization/MagnetConfig.java
Normal file
89
src/dev/w1zzrd/invtweaks/serialization/MagnetConfig.java
Normal file
@ -0,0 +1,89 @@
|
||||
package dev.w1zzrd.invtweaks.serialization;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Type representation of persistent configuration for /magnet
|
||||
*/
|
||||
public class MagnetConfig extends SimpleReflectiveConfigItem {
|
||||
/**
|
||||
* Radius from player to check for items
|
||||
*/
|
||||
private double radius;
|
||||
|
||||
/**
|
||||
* Interval (in ticks) to check for items around magnet players
|
||||
*/
|
||||
private int interval;
|
||||
|
||||
/**
|
||||
* How many subsets to divide the active player list into when running magnetism check (for performance)
|
||||
*/
|
||||
private int subdivide;
|
||||
|
||||
public MagnetConfig(final Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item search radius
|
||||
* @return Item search radius
|
||||
*/
|
||||
public double getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item search interval (in ticks)
|
||||
* @return Item search interval
|
||||
*/
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item search list subdivisions
|
||||
* @return Item search list subdivisions
|
||||
*/
|
||||
public int getSubdivide() {
|
||||
return subdivide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item search radius
|
||||
* @param radius Radius
|
||||
*/
|
||||
public void setRadius(double radius) {
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item search interval (in ticks)
|
||||
* @param interval Interval
|
||||
*/
|
||||
public void setInterval(int interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item search list subdivisions
|
||||
* @param subdivide Subdivisions
|
||||
*/
|
||||
public void setSubdivide(int subdivide) {
|
||||
this.subdivide = subdivide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load default configuration values from the given plugin
|
||||
* @param plugin Plugin to load defaults for
|
||||
* @param path Path in default configuration to load values from
|
||||
* @return Instance containing default configuration values
|
||||
*/
|
||||
public static MagnetConfig getDefault(final Plugin plugin, final String path) {
|
||||
return (MagnetConfig) Objects.requireNonNull(plugin.getConfig().getDefaults()).get(path);
|
||||
}
|
||||
}
|
270
src/dev/w1zzrd/invtweaks/serialization/MagnetData.java
Normal file
270
src/dev/w1zzrd/invtweaks/serialization/MagnetData.java
Normal file
@ -0,0 +1,270 @@
|
||||
package dev.w1zzrd.invtweaks.serialization;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem;
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.UUIDList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Persistent data pertaining to /magnet command
|
||||
*/
|
||||
public class MagnetData extends SimpleReflectiveConfigItem {
|
||||
|
||||
/**
|
||||
* A sorted list of all active magnets
|
||||
*/
|
||||
private volatile UUIDList activeMagnetsUUIDS; // Serializer
|
||||
private final transient List<UUID> activeMagnets;
|
||||
private final transient List<UUID> activeMagnetsView;
|
||||
|
||||
// Online magnets are a subset of active magnets. The redundancy in the collections is to preserve performance
|
||||
private final transient List<UUID> onlineMagnets = new ArrayList<>();
|
||||
private final transient List<UUID> onlineMagnetsView = Collections.unmodifiableList(onlineMagnets);
|
||||
|
||||
/**
|
||||
* Construct persistent magnet data from serialized data
|
||||
* @param mappings Mappings to deserialize
|
||||
*/
|
||||
public MagnetData(final Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
|
||||
activeMagnets = activeMagnetsUUIDS.uuids;
|
||||
activeMagnetsView = Collections.unmodifiableList(activeMagnets);
|
||||
|
||||
ensureListIntegrity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle magnet state for a given player
|
||||
* @param magnet Player to toggle state for
|
||||
* @return True if player is a magnet after this method call, else false
|
||||
*/
|
||||
public boolean toggleMagnet(final UUID magnet) {
|
||||
final int index = Collections.binarySearch(activeMagnets, magnet);
|
||||
|
||||
// See JavaDoc: Collections.binarySearch
|
||||
if (index < 0) {
|
||||
activeMagnets.add(-(index + 1), magnet);
|
||||
addEnabledOnline(magnet);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
activeMagnets.remove(index);
|
||||
removeDisabledOnline(magnet);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a player as a magnet
|
||||
* @param magnet Player to activate magnetism for
|
||||
* @return True if list of magnets was modified, else false
|
||||
*/
|
||||
public boolean addMagnet(final UUID magnet) {
|
||||
final int index = Collections.binarySearch(activeMagnets, magnet);
|
||||
|
||||
// Insert magnet at correct place in list to keep it sorted
|
||||
if (index < 0) {
|
||||
activeMagnets.add(-(index + 1), magnet);
|
||||
addMagnet(magnet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a player from magnet list
|
||||
* @param magnet Player to disable magnetism for
|
||||
* @return True if list of magnets was modified, else false
|
||||
*/
|
||||
public boolean removeMagnet(final UUID magnet) {
|
||||
final int index = Collections.binarySearch(activeMagnets, magnet);
|
||||
|
||||
if (index >= 0) {
|
||||
activeMagnets.remove(index);
|
||||
removeDisabledOnline(magnet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove many players from magnet list
|
||||
* @param magnets Players to disable magnetism for
|
||||
* @return True if list of magnets was modified, else false
|
||||
*/
|
||||
public boolean removeMagnets(final Iterable<UUID> magnets) {
|
||||
boolean changed = false;
|
||||
for(final UUID uuid : magnets) {
|
||||
final int index = Collections.binarySearch(activeMagnets, uuid);
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
activeMagnets.remove(index);
|
||||
removeDisabledOnline(uuid);
|
||||
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player is a magnet
|
||||
* @param check Player to check
|
||||
* @return True if player is a magnet, else false
|
||||
* @see #isOnlineMagnet(UUID)
|
||||
*/
|
||||
public boolean isMagnet(final UUID check) {
|
||||
return Collections.binarySearch(activeMagnets, check) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if player is a magnet <em>and</em> is online
|
||||
* @param check Player to check
|
||||
* @return True if player is online and a magnet
|
||||
* @see #isMagnet(UUID)
|
||||
*/
|
||||
public boolean isOnlineMagnet(final UUID check) {
|
||||
return Collections.binarySearch(onlineMagnets, check) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view of players with magnetism enabled
|
||||
* @return Unmodifiable list of magnets
|
||||
*/
|
||||
public List<UUID> getActiveMagnetsView() {
|
||||
return activeMagnetsView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view of online players with magnetism enabled
|
||||
* @return Unmodifiable list of online magnets
|
||||
*/
|
||||
public List<UUID> getOnlineMagnetsView() {
|
||||
return onlineMagnetsView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get amount of magnets
|
||||
* @return Number of total magnets
|
||||
*/
|
||||
public int activeMagnets() {
|
||||
return activeMagnets.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get amount of online magnets
|
||||
* @return Number of online magnets
|
||||
*/
|
||||
public int onlineMagnets() {
|
||||
return onlineMagnets.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add player to online magnet list if player is online
|
||||
* @param magnet Player to potentially add
|
||||
*/
|
||||
private void addEnabledOnline(final UUID magnet) {
|
||||
if (isPlayerOnline(magnet))
|
||||
addOnline(magnet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add player to online magnet list if player is a magnet
|
||||
* @param magnet Player to potentially add
|
||||
* @return True if player is a magnet, else false
|
||||
*/
|
||||
public boolean addLoginOnline(final UUID magnet) {
|
||||
if (isMagnet(magnet)) {
|
||||
addOnline(magnet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add player to online magnet list
|
||||
* @param magnet Player to add
|
||||
*/
|
||||
private void addOnline(final UUID magnet) {
|
||||
onlineMagnets.add(-(Collections.binarySearch(onlineMagnets, magnet) + 1), magnet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove player from online magnet list if they are online
|
||||
* @param magnet Player to remove from list
|
||||
*/
|
||||
private void removeDisabledOnline(final UUID magnet) {
|
||||
if (isPlayerOnline(magnet))
|
||||
removeOnline(magnet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove player from online magnet list on logout
|
||||
* @param magnet Player to remove
|
||||
* @return True if player was an online magnet, else false
|
||||
*/
|
||||
public boolean removeLogoutOnline(final UUID magnet) {
|
||||
if (isOnlineMagnet(magnet)) {
|
||||
removeOnline(magnet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a player from the online magnet list
|
||||
* @param magnet Player to remove
|
||||
*/
|
||||
private void removeOnline(final UUID magnet) {
|
||||
onlineMagnets.remove(Collections.binarySearch(onlineMagnets, magnet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially expensive method for checking and correcting out-of-order elements in the sorted magnet list.
|
||||
* This exists because I do not doubt that some idiot will try and manually edit the data files (probably me),
|
||||
* without taking into consideration the fact that the list *must* be sorted for binary search to work (kind of).
|
||||
* <br><br>
|
||||
* This method has O(n) complexity if the list is sorted, else (probably) O(n log n). Timsort (default
|
||||
* {@link Arrays#sort} implementation) is used, as it is assumed that the list is only out of order if a
|
||||
* small total number of elements are out of order
|
||||
*/
|
||||
private void ensureListIntegrity() {
|
||||
CHECK_SORTED:{
|
||||
for (int i = activeMagnets.size() - 2; i >= 0; --i)
|
||||
if (activeMagnets.get(i).compareTo(activeMagnets.get(i + 1)) >= 0)
|
||||
break CHECK_SORTED;
|
||||
|
||||
// All elements were in order
|
||||
return;
|
||||
}
|
||||
|
||||
// Someone probably manually added a UUID to the beginning (or end) of the list or something...
|
||||
activeMagnets.sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given player is online
|
||||
* @param check Player to check
|
||||
* @return True if player is online, else false
|
||||
*/
|
||||
private static boolean isPlayerOnline(final UUID check) {
|
||||
final Player player = Bukkit.getPlayer(check);
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty {@link MagnetData} configuration object
|
||||
* @return Blank (valid) object
|
||||
*/
|
||||
public static MagnetData blank() {
|
||||
return new MagnetData(Collections.singletonMap("activeMagnetsUUIDS", new UUIDList()));
|
||||
}
|
||||
}
|
31
src/dev/w1zzrd/invtweaks/serialization/SearchConfig.java
Normal file
31
src/dev/w1zzrd/invtweaks/serialization/SearchConfig.java
Normal file
@ -0,0 +1,31 @@
|
||||
package dev.w1zzrd.invtweaks.serialization;
|
||||
|
||||
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SearchConfig extends SimpleReflectiveConfigItem {
|
||||
|
||||
private int searchRadiusX, searchRadiusY, searchRadiusZ;
|
||||
|
||||
/**
|
||||
* Required constructor for deserializing data
|
||||
*
|
||||
* @param mappings Data to deserialize
|
||||
*/
|
||||
public SearchConfig(Map<String, Object> mappings) {
|
||||
super(mappings);
|
||||
}
|
||||
|
||||
public int getSearchRadiusX() {
|
||||
return searchRadiusX;
|
||||
}
|
||||
|
||||
public int getSearchRadiusY() {
|
||||
return searchRadiusY;
|
||||
}
|
||||
|
||||
public int getSearchRadiusZ() {
|
||||
return searchRadiusZ;
|
||||
}
|
||||
}
|
4
src/dev/w1zzrd/invtweaks/serialization/package-info.java
Normal file
4
src/dev/w1zzrd/invtweaks/serialization/package-info.java
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Types used for serializing and deserializing persistent data
|
||||
*/
|
||||
package dev.w1zzrd.invtweaks.serialization;
|
@ -1,14 +0,0 @@
|
||||
name: InventoryTweaks
|
||||
version: 1.1.0
|
||||
author: IKEA_Jesus
|
||||
main: dev.w1zzrd.invtweaks.InvTweaksPlugin
|
||||
api-version: 1.13
|
||||
commands:
|
||||
sort:
|
||||
description: Sort chest you are looking at
|
||||
usage: /<command>
|
||||
permission: invtweaks.sort
|
||||
magnet:
|
||||
description: Toggle item magnet mode for player
|
||||
usage: /<command>
|
||||
permission: invtweaks.magnet
|
Loading…
x
Reference in New Issue
Block a user