Skip to content

Instantly share code, notes, and snippets.

@I-Al-Istannen
Created August 14, 2016 17:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save I-Al-Istannen/8e83cfd30e190973fc5261f346d1ecf6 to your computer and use it in GitHub Desktop.
Save I-Al-Istannen/8e83cfd30e190973fc5261f346d1ecf6 to your computer and use it in GitHub Desktop.
MystLinkingBooks beginning. Not THAT nice, but maybe you can use it.
<component name="ArtifactManager">
<artifact type="jar" name="Plugin jar">
<output-path>S:/Minecraft/Bukkit Server/Bukkit 1.8.8/plugins/update</output-path>
<root id="archive" name="MYSTLinkingBooks.jar">
<element id="module-output" name="MYSTLinkingBooks" />
</root>
</artifact>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>
<component name="ProjectDictionaryState">
<dictionary name="Julian">
<words>
<w>itemstack</w>
<w>relto</w>
<w>teleport</w>
<w>teleportation</w>
<w>teleported</w>
<w>teleports</w>
<w>warmup</w>
</words>
</dictionary>
</component>
<component name="libraryTable">
<library name="I_Al_Istannen_Util">
<CLASSES>
<root url="jar://S:/Minecraft/Bukkit Server/Bukkit 1.8.8/plugins/I_Al_Istannen_Util.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://S:/Minecraft/Bukkit Server/Bukkit 1.8.8/plugins/I_Al_Istannen_Util.jar!/" />
</SOURCES>
</library>
</component>
<component name="libraryTable">
<library name="spigot-1.8.8">
<CLASSES>
<root url="jar://S:/Minecraft/Bukkit Server/Bukkit 1.8.8/spigot-1.8.8.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://S:/Eclipse Workspaces/bukkit-1.8.8-R0.1-20150727.122515-1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://S:/Minecraft/Bukkit Server/Bukkit 1.8.8/spigot-1.8.8.jar!/" />
</SOURCES>
</library>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
<list size="1">
<item index="0" class="java.lang.String" itemvalue="org.bukkit.event.EventHandler" />
</list>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/MYSTLinkingBooks.iml" filepath="$PROJECT_DIR$/MYSTLinkingBooks.iml" />
</modules>
</component>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ResourceBundleManager">
<file url="file://$PROJECT_DIR$/src/language/Messages.properties" />
<file url="file://$PROJECT_DIR$/src/language/Messages_en.properties" />
<custom-resource-bundle>
<file value="file://$PROJECT_DIR$/src/language/Messages.properties" />
<file value="file://$PROJECT_DIR$/src/language/Messages_en.properties" />
<base-name>Messages</base-name>
</custom-resource-bundle>
</component>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="PROVIDED" name="spigot-1.8.8" level="project" />
<orderEntry type="library" scope="PROVIDED" name="I_Al_Istannen_Util" level="project" />
</component>
</module>
package com.darkblade12.particleeffect;
import com.darkblade12.particleeffect.ReflectionUtils.PackageType;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* <b>ParticleEffect Library</b>
* <p>
* This library was created by @DarkBlade12 and allows you to display all Minecraft particle effects on a Bukkit server
* <p>
* You are welcome to use it, modify it and redistribute it under the following conditions:
* <ul>
* <li>Don't claim this class as your own
* <li>Don't remove this disclaimer
* </ul>
* <p>
* Special thanks:
* <ul>
* <li>@microgeek (original idea, names and packet parameters)
* <li>@ShadyPotato (1.8 names, ids and packet parameters)
* <li>@RingOfStorms (particle behavior)
* <li>@Cybermaxke (particle behavior)
* <li>@JamieSinn (hosting a jenkins server and documentation for particleeffect)
* </ul>
* <p>
* <i>It would be nice if you provide credit to me if you use this class in a published project</i>
*
* @author DarkBlade12
* @version 1.7
*/
@SuppressWarnings("ALL")
public enum ParticleEffect {
/**
* A particle effect which is displayed by exploding tnt and creepers:
* <ul>
* <li>It looks like a white cloud
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
EXPLOSION_NORMAL("explode", 0, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by exploding ghast fireballs and wither skulls:
* <ul>
* <li>It looks like a gray ball which is fading away
* <li>The speed value slightly influences the size of this particle effect
* </ul>
*/
EXPLOSION_LARGE("largeexplode", 1, -1),
/**
* A particle effect which is displayed by exploding tnt and creepers:
* <ul>
* <li>It looks like a crowd of gray balls which are fading away
* <li>The speed value has no influence on this particle effect
* </ul>
*/
EXPLOSION_HUGE("hugeexplosion", 2, -1),
/**
* A particle effect which is displayed by launching fireworks:
* <ul>
* <li>It looks like a white star which is sparkling
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
FIREWORKS_SPARK("fireworksSpark", 3, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by swimming entities and arrows in water:
* <ul>
* <li>It looks like a bubble
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
WATER_BUBBLE("bubble", 4, -1, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_WATER),
/**
* A particle effect which is displayed by swimming entities and shaking wolves:
* <ul>
* <li>It looks like a blue drop
* <li>The speed value has no influence on this particle effect
* </ul>
*/
WATER_SPLASH("splash", 5, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed on water when fishing:
* <ul>
* <li>It looks like a blue droplet
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
WATER_WAKE("wake", 6, 7, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by water:
* <ul>
* <li>It looks like a tiny blue square
* <li>The speed value has no influence on this particle effect
* </ul>
*/
SUSPENDED("suspended", 7, -1, ParticleProperty.REQUIRES_WATER),
/**
* A particle effect which is displayed by air when close to bedrock and the in the void:
* <ul>
* <li>It looks like a tiny gray square
* <li>The speed value has no influence on this particle effect
* </ul>
*/
SUSPENDED_DEPTH("depthSuspend", 8, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed when landing a critical hit and by arrows:
* <ul>
* <li>It looks like a light brown cross
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
CRIT("crit", 9, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed when landing a hit with an enchanted weapon:
* <ul>
* <li>It looks like a cyan star
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
CRIT_MAGIC("magicCrit", 10, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by primed tnt, torches, droppers, dispensers, end portals, brewing stands and monster spawners:
* <ul>
* <li>It looks like a little gray cloud
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
SMOKE_NORMAL("smoke", 11, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by fire, minecarts with furnace and blazes:
* <ul>
* <li>It looks like a large gray cloud
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
SMOKE_LARGE("largesmoke", 12, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed when splash potions or bottles o' enchanting hit something:
* <ul>
* <li>It looks like a white swirl
* <li>The speed value causes the particle to only move upwards when set to 0
* <li>Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0
* </ul>
*/
SPELL("spell", 13, -1),
/**
* A particle effect which is displayed when instant splash potions hit something:
* <ul>
* <li>It looks like a white cross
* <li>The speed value causes the particle to only move upwards when set to 0
* <li>Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0
* </ul>
*/
SPELL_INSTANT("instantSpell", 14, -1),
/**
* A particle effect which is displayed by entities with active potion effects:
* <ul>
* <li>It looks like a colored swirl
* <li>The speed value causes the particle to be colored black when set to 0
* <li>The particle color gets lighter when increasing the speed and darker when decreasing the speed
* </ul>
*/
SPELL_MOB("mobSpell", 15, -1, ParticleProperty.COLORABLE),
/**
* A particle effect which is displayed by entities with active potion effects applied through a beacon:
* <ul>
* <li>It looks like a transparent colored swirl
* <li>The speed value causes the particle to be always colored black when set to 0
* <li>The particle color gets lighter when increasing the speed and darker when decreasing the speed
* </ul>
*/
SPELL_MOB_AMBIENT("mobSpellAmbient", 16, -1, ParticleProperty.COLORABLE),
/**
* A particle effect which is displayed by witches:
* <ul>
* <li>It looks like a purple cross
* <li>The speed value causes the particle to only move upwards when set to 0
* <li>Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0
* </ul>
*/
SPELL_WITCH("witchMagic", 17, -1),
/**
* A particle effect which is displayed by blocks beneath a water source:
* <ul>
* <li>It looks like a blue drip
* <li>The speed value has no influence on this particle effect
* </ul>
*/
DRIP_WATER("dripWater", 18, -1),
/**
* A particle effect which is displayed by blocks beneath a lava source:
* <ul>
* <li>It looks like an orange drip
* <li>The speed value has no influence on this particle effect
* </ul>
*/
DRIP_LAVA("dripLava", 19, -1),
/**
* A particle effect which is displayed when attacking a villager in a village:
* <ul>
* <li>It looks like a cracked gray heart
* <li>The speed value has no influence on this particle effect
* </ul>
*/
VILLAGER_ANGRY("angryVillager", 20, -1),
/**
* A particle effect which is displayed when using bone meal and trading with a villager in a village:
* <ul>
* <li>It looks like a green star
* <li>The speed value has no influence on this particle effect
* </ul>
*/
VILLAGER_HAPPY("happyVillager", 21, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by mycelium:
* <ul>
* <li>It looks like a tiny gray square
* <li>The speed value has no influence on this particle effect
* </ul>
*/
TOWN_AURA("townaura", 22, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by note blocks:
* <ul>
* <li>It looks like a colored note
* <li>The speed value causes the particle to be colored green when set to 0
* </ul>
*/
NOTE("note", 23, -1, ParticleProperty.COLORABLE),
/**
* A particle effect which is displayed by nether portals, endermen, ender pearls, eyes of ender, ender chests and dragon eggs:
* <ul>
* <li>It looks like a purple cloud
* <li>The speed value influences the spread of this particle effect
* </ul>
*/
PORTAL("portal", 24, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by enchantment tables which are nearby bookshelves:
* <ul>
* <li>It looks like a cryptic white letter
* <li>The speed value influences the spread of this particle effect
* </ul>
*/
ENCHANTMENT_TABLE("enchantmenttable", 25, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by torches, active furnaces, magma cubes and monster spawners:
* <ul>
* <li>It looks like a tiny flame
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
FLAME("flame", 26, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by lava:
* <ul>
* <li>It looks like a spark
* <li>The speed value has no influence on this particle effect
* </ul>
*/
LAVA("lava", 27, -1),
/**
* A particle effect which is currently unused:
* <ul>
* <li>It looks like a transparent gray square
* <li>The speed value has no influence on this particle effect
* </ul>
*/
FOOTSTEP("footstep", 28, -1),
/**
* A particle effect which is displayed when a mob dies:
* <ul>
* <li>It looks like a large white cloud
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
CLOUD("cloud", 29, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by redstone ore, powered redstone, redstone torches and redstone repeaters:
* <ul>
* <li>It looks like a tiny colored cloud
* <li>The speed value causes the particle to be colored red when set to 0
* </ul>
*/
REDSTONE("reddust", 30, -1, ParticleProperty.COLORABLE),
/**
* A particle effect which is displayed when snowballs hit a block:
* <ul>
* <li>It looks like a little piece with the snowball texture
* <li>The speed value has no influence on this particle effect
* </ul>
*/
SNOWBALL("snowballpoof", 31, -1),
/**
* A particle effect which is currently unused:
* <ul>
* <li>It looks like a tiny white cloud
* <li>The speed value influences the velocity at which the particle flies off
* </ul>
*/
SNOW_SHOVEL("snowshovel", 32, -1, ParticleProperty.DIRECTIONAL),
/**
* A particle effect which is displayed by slimes:
* <ul>
* <li>It looks like a tiny part of the slimeball icon
* <li>The speed value has no influence on this particle effect
* </ul>
*/
SLIME("slime", 33, -1),
/**
* A particle effect which is displayed when breeding and taming animals:
* <ul>
* <li>It looks like a red heart
* <li>The speed value has no influence on this particle effect
* </ul>
*/
HEART("heart", 34, -1),
/**
* A particle effect which is displayed by barriers:
* <ul>
* <li>It looks like a red box with a slash through it
* <li>The speed value has no influence on this particle effect
* </ul>
*/
BARRIER("barrier", 35, 8),
/**
* A particle effect which is displayed when breaking a tool or eggs hit a block:
* <ul>
* <li>It looks like a little piece with an item texture
* </ul>
*/
ITEM_CRACK("iconcrack", 36, -1, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_DATA),
/**
* A particle effect which is displayed when breaking blocks or sprinting:
* <ul>
* <li>It looks like a little piece with a block texture
* <li>The speed value has no influence on this particle effect
* </ul>
*/
BLOCK_CRACK("blockcrack", 37, -1, ParticleProperty.REQUIRES_DATA),
/**
* A particle effect which is displayed when falling:
* <ul>
* <li>It looks like a little piece with a block texture
* </ul>
*/
BLOCK_DUST("blockdust", 38, 7, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_DATA),
/**
* A particle effect which is displayed when rain hits the ground:
* <ul>
* <li>It looks like a blue droplet
* <li>The speed value has no influence on this particle effect
* </ul>
*/
WATER_DROP("droplet", 39, 8),
/**
* A particle effect which is currently unused:
* <ul>
* <li>It has no visual effect
* </ul>
*/
ITEM_TAKE("take", 40, 8),
/**
* A particle effect which is displayed by elder guardians:
* <ul>
* <li>It looks like the shape of the elder guardian
* <li>The speed value has no influence on this particle effect
* <li>The offset values have no influence on this particle effect
* </ul>
*/
MOB_APPEARANCE("mobappearance", 41, 8);
private static final Map<String, ParticleEffect> NAME_MAP = new HashMap<String, ParticleEffect>();
private static final Map<Integer, ParticleEffect> ID_MAP = new HashMap<Integer, ParticleEffect>();
private final String name;
private final int id;
private final int requiredVersion;
private final List<ParticleProperty> properties;
// Initialize map for quick name and id lookup
static {
for (ParticleEffect effect : values()) {
NAME_MAP.put(effect.name, effect);
ID_MAP.put(effect.id, effect);
}
}
/**
* Construct a new particle effect
*
* @param name Name of this particle effect
* @param id Id of this particle effect
* @param requiredVersion Version which is required (1.x)
* @param properties Properties of this particle effect
*/
private ParticleEffect(String name, int id, int requiredVersion, ParticleProperty... properties) {
this.name = name;
this.id = id;
this.requiredVersion = requiredVersion;
this.properties = Arrays.asList(properties);
}
/**
* Returns the name of this particle effect
*
* @return The name
*/
public String getName() {
return name;
}
/**
* Returns the id of this particle effect
*
* @return The id
*/
public int getId() {
return id;
}
/**
* Returns the required version for this particle effect (1.x)
*
* @return The required version
*/
public int getRequiredVersion() {
return requiredVersion;
}
/**
* Determine if this particle effect has a specific property
*
* @return Whether it has the property or not
*/
public boolean hasProperty(ParticleProperty property) {
return properties.contains(property);
}
/**
* Determine if this particle effect is supported by your current server version
*
* @return Whether the particle effect is supported or not
*/
public boolean isSupported() {
if (requiredVersion == -1) {
return true;
}
return ParticlePacket.getVersion() >= requiredVersion;
}
/**
* Returns the particle effect with the given name
*
* @param name Name of the particle effect
* @return The particle effect
*/
public static ParticleEffect fromName(String name) {
for (Entry<String, ParticleEffect> entry : NAME_MAP.entrySet()) {
if (!entry.getKey().equalsIgnoreCase(name)) {
continue;
}
return entry.getValue();
}
return null;
}
/**
* Returns the particle effect with the given id
*
* @param id Id of the particle effect
* @return The particle effect
*/
public static ParticleEffect fromId(int id) {
for (Entry<Integer, ParticleEffect> entry : ID_MAP.entrySet()) {
if (entry.getKey() != id) {
continue;
}
return entry.getValue();
}
return null;
}
/**
* Determine if water is at a certain location
*
* @param location Location to check
* @return Whether water is at this location or not
*/
private static boolean isWater(Location location) {
Material material = location.getBlock().getType();
return material == Material.WATER || material == Material.STATIONARY_WATER;
}
/**
* Determine if the distance between @param location and one of the players exceeds 256
*
* @param location Location to check
* @return Whether the distance exceeds 256 or not
*/
private static boolean isLongDistance(Location location, List<Player> players) {
String world = location.getWorld().getName();
for (Player player : players) {
Location playerLocation = player.getLocation();
if (!world.equals(playerLocation.getWorld().getName()) || playerLocation.distanceSquared(location) < 65536) {
continue;
}
return true;
}
return false;
}
/**
* Determine if the data type for a particle effect is correct
*
* @param effect Particle effect
* @param data Particle data
* @return Whether the data type is correct or not
*/
private static boolean isDataCorrect(ParticleEffect effect, ParticleData data) {
return ((effect == BLOCK_CRACK || effect == BLOCK_DUST) && data instanceof BlockData) || (effect == ITEM_CRACK && data instanceof ItemData);
}
/**
* Determine if the color type for a particle effect is correct
*
* @param effect Particle effect
* @param color Particle color
* @return Whether the color type is correct or not
*/
private static boolean isColorCorrect(ParticleEffect effect, ParticleColor color) {
return ((effect == SPELL_MOB || effect == SPELL_MOB_AMBIENT || effect == REDSTONE) && color instanceof OrdinaryColor) || (effect == NOTE && color instanceof NoteColor);
}
/**
* Displays a particle effect which is only visible for all players within a certain range in the world of @param center
*
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param center Center location of the effect
* @param range Range of the visibility
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect requires additional data
* @throws IllegalArgumentException If the particle effect requires water and none is at the center location
* @see ParticlePacket
* @see ParticlePacket#sendTo(Location, double)
*/
public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws ParticleVersionException, ParticleDataException, IllegalArgumentException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect requires additional data");
}
if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) {
throw new IllegalArgumentException("There is no water at the center location");
}
new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, range > 256, null).sendTo(center, range);
}
/**
* Displays a particle effect which is only visible for the specified players
*
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect requires additional data
* @throws IllegalArgumentException If the particle effect requires water and none is at the center location
* @see ParticlePacket
* @see ParticlePacket#sendTo(Location, List)
*/
public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List<Player> players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect requires additional data");
}
if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) {
throw new IllegalArgumentException("There is no water at the center location");
}
new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, isLongDistance(center, players), null).sendTo(center, players);
}
/**
* Displays a particle effect which is only visible for the specified players
*
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect requires additional data
* @throws IllegalArgumentException If the particle effect requires water and none is at the center location
* @see #display(float, float, float, float, int, Location, List)
*/
public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, Player... players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException {
display(offsetX, offsetY, offsetZ, speed, amount, center, Arrays.asList(players));
}
/**
* Displays a single particle which flies into a determined direction and is only visible for all players within a certain range in the world of @param center
*
* @param direction Direction of the particle
* @param speed Display speed of the particle
* @param center Center location of the effect
* @param range Range of the visibility
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect requires additional data
* @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location
* @see ParticlePacket#ParticlePacket(ParticleEffect, Vector, float, boolean, ParticleData)
* @see ParticlePacket#sendTo(Location, double)
*/
public void display(Vector direction, float speed, Location center, double range) throws ParticleVersionException, ParticleDataException, IllegalArgumentException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect requires additional data");
}
if (!hasProperty(ParticleProperty.DIRECTIONAL)) {
throw new IllegalArgumentException("This particle effect is not directional");
}
if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) {
throw new IllegalArgumentException("There is no water at the center location");
}
new ParticlePacket(this, direction, speed, range > 256, null).sendTo(center, range);
}
/**
* Displays a single particle which flies into a determined direction and is only visible for the specified players
*
* @param direction Direction of the particle
* @param speed Display speed of the particle
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect requires additional data
* @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location
* @see ParticlePacket#ParticlePacket(ParticleEffect, Vector, float, boolean, ParticleData)
* @see ParticlePacket#sendTo(Location, List)
*/
public void display(Vector direction, float speed, Location center, List<Player> players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect requires additional data");
}
if (!hasProperty(ParticleProperty.DIRECTIONAL)) {
throw new IllegalArgumentException("This particle effect is not directional");
}
if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) {
throw new IllegalArgumentException("There is no water at the center location");
}
new ParticlePacket(this, direction, speed, isLongDistance(center, players), null).sendTo(center, players);
}
/**
* Displays a single particle which flies into a determined direction and is only visible for the specified players
*
* @param direction Direction of the particle
* @param speed Display speed of the particle
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect requires additional data
* @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location
* @see #display(Vector, float, Location, List)
*/
public void display(Vector direction, float speed, Location center, Player... players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException {
display(direction, speed, center, Arrays.asList(players));
}
/**
* Displays a single particle which is colored and only visible for all players within a certain range in the world of @param center
*
* @param color Color of the particle
* @param center Center location of the effect
* @param range Range of the visibility
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect
* @see ParticlePacket#ParticlePacket(ParticleEffect, ParticleColor, boolean)
* @see ParticlePacket#sendTo(Location, double)
*/
public void display(ParticleColor color, Location center, double range) throws ParticleVersionException, ParticleColorException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (!hasProperty(ParticleProperty.COLORABLE)) {
throw new ParticleColorException("This particle effect is not colorable");
}
if (!isColorCorrect(this, color)) {
throw new ParticleColorException("The particle color type is incorrect");
}
new ParticlePacket(this, color, range > 256).sendTo(center, range);
}
/**
* Displays a single particle which is colored and only visible for the specified players
*
* @param color Color of the particle
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect
* @see ParticlePacket#ParticlePacket(ParticleEffect, ParticleColor, boolean)
* @see ParticlePacket#sendTo(Location, List)
*/
public void display(ParticleColor color, Location center, List<Player> players) throws ParticleVersionException, ParticleColorException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (!hasProperty(ParticleProperty.COLORABLE)) {
throw new ParticleColorException("This particle effect is not colorable");
}
if (!isColorCorrect(this, color)) {
throw new ParticleColorException("The particle color type is incorrect");
}
new ParticlePacket(this, color, isLongDistance(center, players)).sendTo(center, players);
}
/**
* Displays a single particle which is colored and only visible for the specified players
*
* @param color Color of the particle
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect
* @see #display(ParticleColor, Location, List)
*/
public void display(ParticleColor color, Location center, Player... players) throws ParticleVersionException, ParticleColorException {
display(color, center, Arrays.asList(players));
}
/**
* Displays a particle effect which requires additional data and is only visible for all players within a certain range in the world of @param center
*
* @param data Data of the effect
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param center Center location of the effect
* @param range Range of the visibility
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect
* @see ParticlePacket
* @see ParticlePacket#sendTo(Location, double)
*/
public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws ParticleVersionException, ParticleDataException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (!hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect does not require additional data");
}
if (!isDataCorrect(this, data)) {
throw new ParticleDataException("The particle data type is incorrect");
}
new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, range > 256, data).sendTo(center, range);
}
/**
* Displays a particle effect which requires additional data and is only visible for the specified players
*
* @param data Data of the effect
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect
* @see ParticlePacket
* @see ParticlePacket#sendTo(Location, List)
*/
public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List<Player> players) throws ParticleVersionException, ParticleDataException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (!hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect does not require additional data");
}
if (!isDataCorrect(this, data)) {
throw new ParticleDataException("The particle data type is incorrect");
}
new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, isLongDistance(center, players), data).sendTo(center, players);
}
/**
* Displays a particle effect which requires additional data and is only visible for the specified players
*
* @param data Data of the effect
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect
* @see #display(ParticleData, float, float, float, float, int, Location, List)
*/
public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, Player... players) throws ParticleVersionException, ParticleDataException {
display(data, offsetX, offsetY, offsetZ, speed, amount, center, Arrays.asList(players));
}
/**
* Displays a single particle which requires additional data that flies into a determined direction and is only visible for all players within a certain range in the world of @param center
*
* @param data Data of the effect
* @param direction Direction of the particle
* @param speed Display speed of the particles
* @param center Center location of the effect
* @param range Range of the visibility
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect
* @see ParticlePacket
* @see ParticlePacket#sendTo(Location, double)
*/
public void display(ParticleData data, Vector direction, float speed, Location center, double range) throws ParticleVersionException, ParticleDataException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (!hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect does not require additional data");
}
if (!isDataCorrect(this, data)) {
throw new ParticleDataException("The particle data type is incorrect");
}
new ParticlePacket(this, direction, speed, range > 256, data).sendTo(center, range);
}
/**
* Displays a single particle which requires additional data that flies into a determined direction and is only visible for the specified players
*
* @param data Data of the effect
* @param direction Direction of the particle
* @param speed Display speed of the particles
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect
* @see ParticlePacket
* @see ParticlePacket#sendTo(Location, List)
*/
public void display(ParticleData data, Vector direction, float speed, Location center, List<Player> players) throws ParticleVersionException, ParticleDataException {
if (!isSupported()) {
throw new ParticleVersionException("This particle effect is not supported by your server version");
}
if (!hasProperty(ParticleProperty.REQUIRES_DATA)) {
throw new ParticleDataException("This particle effect does not require additional data");
}
if (!isDataCorrect(this, data)) {
throw new ParticleDataException("The particle data type is incorrect");
}
new ParticlePacket(this, direction, speed, isLongDistance(center, players), data).sendTo(center, players);
}
/**
* Displays a single particle which requires additional data that flies into a determined direction and is only visible for the specified players
*
* @param data Data of the effect
* @param direction Direction of the particle
* @param speed Display speed of the particles
* @param center Center location of the effect
* @param players Receivers of the effect
* @throws ParticleVersionException If the particle effect is not supported by the server version
* @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect
* @see #display(ParticleData, Vector, float, Location, List)
*/
public void display(ParticleData data, Vector direction, float speed, Location center, Player... players) throws ParticleVersionException, ParticleDataException {
display(data, direction, speed, center, Arrays.asList(players));
}
/**
* Represents the property of a particle effect
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.7
*/
public static enum ParticleProperty {
/**
* The particle effect requires water to be displayed
*/
REQUIRES_WATER,
/**
* The particle effect requires block or item data to be displayed
*/
REQUIRES_DATA,
/**
* The particle effect uses the offsets as direction values
*/
DIRECTIONAL,
/**
* The particle effect uses the offsets as color values
*/
COLORABLE;
}
/**
* Represents the particle data for effects like {@link ParticleEffect#ITEM_CRACK}, {@link ParticleEffect#BLOCK_CRACK} and {@link ParticleEffect#BLOCK_DUST}
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.6
*/
public static abstract class ParticleData {
private final Material material;
private final byte data;
private final int[] packetData;
/**
* Construct a new particle data
*
* @param material Material of the item/block
* @param data Data value of the item/block
*/
@SuppressWarnings("deprecation")
public ParticleData(Material material, byte data) {
this.material = material;
this.data = data;
this.packetData = new int[] { material.getId(), data };
}
/**
* Returns the material of this data
*
* @return The material
*/
public Material getMaterial() {
return material;
}
/**
* Returns the data value of this data
*
* @return The data value
*/
public byte getData() {
return data;
}
/**
* Returns the data as an int array for packet construction
*
* @return The data for the packet
*/
public int[] getPacketData() {
return packetData;
}
/**
* Returns the data as a string for pre 1.8 versions
*
* @return The data string for the packet
*/
public String getPacketDataString() {
return "_" + packetData[0] + "_" + packetData[1];
}
}
/**
* Represents the item data for the {@link ParticleEffect#ITEM_CRACK} effect
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.6
*/
public static final class ItemData extends ParticleData {
/**
* Construct a new item data
*
* @param material Material of the item
* @param data Data value of the item
* @see ParticleData#ParticleData(Material, byte)
*/
public ItemData(Material material, byte data) {
super(material, data);
}
}
/**
* Represents the block data for the {@link ParticleEffect#BLOCK_CRACK} and {@link ParticleEffect#BLOCK_DUST} effects
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.6
*/
public static final class BlockData extends ParticleData {
/**
* Construct a new block data
*
* @param material Material of the block
* @param data Data value of the block
* @throws IllegalArgumentException If the material is not a block
* @see ParticleData#ParticleData(Material, byte)
*/
public BlockData(Material material, byte data) throws IllegalArgumentException {
super(material, data);
if (!material.isBlock()) {
throw new IllegalArgumentException("The material is not a block");
}
}
}
/**
* Represents the color for effects like {@link ParticleEffect#SPELL_MOB}, {@link ParticleEffect#SPELL_MOB_AMBIENT}, {@link ParticleEffect#REDSTONE} and {@link ParticleEffect#NOTE}
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.7
*/
public static abstract class ParticleColor {
/**
* Returns the value for the offsetX field
*
* @return The offsetX value
*/
public abstract float getValueX();
/**
* Returns the value for the offsetY field
*
* @return The offsetY value
*/
public abstract float getValueY();
/**
* Returns the value for the offsetZ field
*
* @return The offsetZ value
*/
public abstract float getValueZ();
}
/**
* Represents the color for effects like {@link ParticleEffect#SPELL_MOB}, {@link ParticleEffect#SPELL_MOB_AMBIENT} and {@link ParticleEffect#NOTE}
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.7
*/
public static final class OrdinaryColor extends ParticleColor {
private final int red;
private final int green;
private final int blue;
/**
* Construct a new ordinary color
*
* @param red Red value of the RGB format
* @param green Green value of the RGB format
* @param blue Blue value of the RGB format
* @throws IllegalArgumentException If one of the values is lower than 0 or higher than 255
*/
public OrdinaryColor(int red, int green, int blue) throws IllegalArgumentException {
if (red < 0) {
throw new IllegalArgumentException("The red value is lower than 0");
}
if (red > 255) {
throw new IllegalArgumentException("The red value is higher than 255");
}
this.red = red;
if (green < 0) {
throw new IllegalArgumentException("The green value is lower than 0");
}
if (green > 255) {
throw new IllegalArgumentException("The green value is higher than 255");
}
this.green = green;
if (blue < 0) {
throw new IllegalArgumentException("The blue value is lower than 0");
}
if (blue > 255) {
throw new IllegalArgumentException("The blue value is higher than 255");
}
this.blue = blue;
}
/**
* Construct a new ordinary color
*
* @param color Bukkit color
*/
public OrdinaryColor(Color color) {
this(color.getRed(), color.getGreen(), color.getBlue());
}
/**
* Returns the red value of the RGB format
*
* @return The red value
*/
public int getRed() {
return red;
}
/**
* Returns the green value of the RGB format
*
* @return The green value
*/
public int getGreen() {
return green;
}
/**
* Returns the blue value of the RGB format
*
* @return The blue value
*/
public int getBlue() {
return blue;
}
/**
* Returns the red value divided by 255
*
* @return The offsetX value
*/
@Override
public float getValueX() {
return (float) red / 255F;
}
/**
* Returns the green value divided by 255
*
* @return The offsetY value
*/
@Override
public float getValueY() {
return (float) green / 255F;
}
/**
* Returns the blue value divided by 255
*
* @return The offsetZ value
*/
@Override
public float getValueZ() {
return (float) blue / 255F;
}
}
/**
* Represents the color for the {@link ParticleEffect#NOTE} effect
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.7
*/
public static final class NoteColor extends ParticleColor {
private final int note;
/**
* Construct a new note color
*
* @param note Note id which determines color
* @throws IllegalArgumentException If the note value is lower than 0 or higher than 24
*/
public NoteColor(int note) throws IllegalArgumentException {
if (note < 0) {
throw new IllegalArgumentException("The note value is lower than 0");
}
if (note > 24) {
throw new IllegalArgumentException("The note value is higher than 24");
}
this.note = note;
}
/**
* Returns the note value divided by 24
*
* @return The offsetX value
*/
@Override
public float getValueX() {
return (float) note / 24F;
}
/**
* Returns zero because the offsetY value is unused
*
* @return zero
*/
@Override
public float getValueY() {
return 0;
}
/**
* Returns zero because the offsetZ value is unused
*
* @return zero
*/
@Override
public float getValueZ() {
return 0;
}
}
/**
* Represents a runtime exception that is thrown either if the displayed particle effect requires data and has none or vice-versa or if the data type is incorrect
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.6
*/
private static final class ParticleDataException extends RuntimeException {
private static final long serialVersionUID = 3203085387160737484L;
/**
* Construct a new particle data exception
*
* @param message Message that will be logged
*/
public ParticleDataException(String message) {
super(message);
}
}
/**
* Represents a runtime exception that is thrown either if the displayed particle effect is not colorable or if the particle color type is incorrect
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.7
*/
private static final class ParticleColorException extends RuntimeException {
private static final long serialVersionUID = 3203085387160737484L;
/**
* Construct a new particle color exception
*
* @param message Message that will be logged
*/
public ParticleColorException(String message) {
super(message);
}
}
/**
* Represents a runtime exception that is thrown if the displayed particle effect requires a newer version
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.6
*/
private static final class ParticleVersionException extends RuntimeException {
private static final long serialVersionUID = 3203085387160737484L;
/**
* Construct a new particle version exception
*
* @param message Message that will be logged
*/
public ParticleVersionException(String message) {
super(message);
}
}
/**
* Represents a particle effect packet with all attributes which is used for sending packets to the players
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.5
*/
public static final class ParticlePacket {
private static int version;
private static Class<?> enumParticle;
private static Constructor<?> packetConstructor;
private static Method getHandle;
private static Field playerConnection;
private static Method sendPacket;
private static boolean initialized;
private final ParticleEffect effect;
private float offsetX;
private final float offsetY;
private final float offsetZ;
private final float speed;
private final int amount;
private final boolean longDistance;
private final ParticleData data;
private Object packet;
/**
* Construct a new particle packet
*
* @param effect Particle effect
* @param offsetX Maximum distance particles can fly away from the center on the x-axis
* @param offsetY Maximum distance particles can fly away from the center on the y-axis
* @param offsetZ Maximum distance particles can fly away from the center on the z-axis
* @param speed Display speed of the particles
* @param amount Amount of particles
* @param longDistance Indicates whether the maximum distance is increased from 256 to 65536
* @param data Data of the effect
* @throws IllegalArgumentException If the speed or amount is lower than 0
* @see #initialize()
*/
public ParticlePacket(ParticleEffect effect, float offsetX, float offsetY, float offsetZ, float speed, int amount, boolean longDistance, ParticleData data) throws IllegalArgumentException {
initialize();
if (speed < 0) {
throw new IllegalArgumentException("The speed is lower than 0");
}
if (amount < 0) {
throw new IllegalArgumentException("The amount is lower than 0");
}
this.effect = effect;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.offsetZ = offsetZ;
this.speed = speed;
this.amount = amount;
this.longDistance = longDistance;
this.data = data;
}
/**
* Construct a new particle packet of a single particle flying into a determined direction
*
* @param effect Particle effect
* @param direction Direction of the particle
* @param speed Display speed of the particle
* @param longDistance Indicates whether the maximum distance is increased from 256 to 65536
* @param data Data of the effect
* @throws IllegalArgumentException If the speed is lower than 0
* @see #ParticleEffect(ParticleEffect, float, float, float, float, int, boolean, ParticleData)
*/
public ParticlePacket(ParticleEffect effect, Vector direction, float speed, boolean longDistance, ParticleData data) throws IllegalArgumentException {
this(effect, (float) direction.getX(), (float) direction.getY(), (float) direction.getZ(), speed, 0, longDistance, data);
}
/**
* Construct a new particle packet of a single colored particle
*
* @param effect Particle effect
* @param color Color of the particle
* @param longDistance Indicates whether the maximum distance is increased from 256 to 65536
* @see #ParticleEffect(ParticleEffect, float, float, float, float, int, boolean, ParticleData)
*/
public ParticlePacket(ParticleEffect effect, ParticleColor color, boolean longDistance) {
this(effect, color.getValueX(), color.getValueY(), color.getValueZ(), 1, 0, longDistance, null);
if (effect == ParticleEffect.REDSTONE && color instanceof OrdinaryColor && ((OrdinaryColor) color).getRed() == 0) {
offsetX = Float.MIN_NORMAL;
}
}
/**
* Initializes {@link #packetConstructor}, {@link #getHandle}, {@link #playerConnection} and {@link #sendPacket} and sets {@link #initialized} to <code>true</code> if it succeeds
* <p>
* <b>Note:</b> These fields only have to be initialized once, so it will return if {@link #initialized} is already set to <code>true</code>
*
* @throws VersionIncompatibleException if your bukkit version is not supported by this library
*/
public static void initialize() throws VersionIncompatibleException {
if (initialized) {
return;
}
try {
version = Integer.parseInt(Character.toString(PackageType.getServerVersion().charAt(3)));
if (version > 7) {
enumParticle = PackageType.MINECRAFT_SERVER.getClass("EnumParticle");
}
Class<?> packetClass = PackageType.MINECRAFT_SERVER.getClass(version < 7 ? "Packet63WorldParticles" : "PacketPlayOutWorldParticles");
packetConstructor = ReflectionUtils.getConstructor(packetClass);
getHandle = ReflectionUtils.getMethod("CraftPlayer", PackageType.CRAFTBUKKIT_ENTITY, "getHandle");
playerConnection = ReflectionUtils.getField("EntityPlayer", PackageType.MINECRAFT_SERVER, false, "playerConnection");
sendPacket = ReflectionUtils.getMethod(playerConnection.getType(), "sendPacket", PackageType.MINECRAFT_SERVER.getClass("Packet"));
} catch (Exception exception) {
throw new VersionIncompatibleException("Your current bukkit version seems to be incompatible with this library", exception);
}
initialized = true;
}
/**
* Returns the version of your server (1.x)
*
* @return The version number
*/
public static int getVersion() {
if (!initialized) {
initialize();
}
return version;
}
/**
* Determine if {@link #packetConstructor}, {@link #getHandle}, {@link #playerConnection} and {@link #sendPacket} are initialized
*
* @return Whether these fields are initialized or not
* @see #initialize()
*/
public static boolean isInitialized() {
return initialized;
}
/**
* Initializes {@link #packet} with all set values
*
* @param center Center location of the effect
* @throws PacketInstantiationException If instantion fails due to an unknown error
*/
private void initializePacket(Location center) throws PacketInstantiationException {
if (packet != null) {
return;
}
try {
packet = packetConstructor.newInstance();
if (version < 8) {
String name = effect.getName();
if (data != null) {
name += data.getPacketDataString();
}
ReflectionUtils.setValue(packet, true, "a", name);
} else {
ReflectionUtils.setValue(packet, true, "a", enumParticle.getEnumConstants()[effect.getId()]);
ReflectionUtils.setValue(packet, true, "j", longDistance);
if (data != null) {
int[] packetData = data.getPacketData();
ReflectionUtils.setValue(packet, true, "k", effect == ParticleEffect.ITEM_CRACK ? packetData : new int[] { packetData[0] | (packetData[1] << 12) });
}
}
ReflectionUtils.setValue(packet, true, "b", (float) center.getX());
ReflectionUtils.setValue(packet, true, "c", (float) center.getY());
ReflectionUtils.setValue(packet, true, "d", (float) center.getZ());
ReflectionUtils.setValue(packet, true, "e", offsetX);
ReflectionUtils.setValue(packet, true, "f", offsetY);
ReflectionUtils.setValue(packet, true, "g", offsetZ);
ReflectionUtils.setValue(packet, true, "h", speed);
ReflectionUtils.setValue(packet, true, "i", amount);
} catch (Exception exception) {
throw new PacketInstantiationException("Packet instantiation failed", exception);
}
}
/**
* Sends the packet to a single player and caches it
*
* @param center Center location of the effect
* @param player Receiver of the packet
* @throws PacketInstantiationException If instantion fails due to an unknown error
* @throws PacketSendingException If sending fails due to an unknown error
* @see #initializePacket(Location)
*/
public void sendTo(Location center, Player player) throws PacketInstantiationException, PacketSendingException {
initializePacket(center);
try {
sendPacket.invoke(playerConnection.get(getHandle.invoke(player)), packet);
} catch (Exception exception) {
throw new PacketSendingException("Failed to send the packet to player '" + player.getName() + "'", exception);
}
}
/**
* Sends the packet to all players in the list
*
* @param center Center location of the effect
* @param players Receivers of the packet
* @throws IllegalArgumentException If the player list is empty
* @see #sendTo(Location center, Player player)
*/
public void sendTo(Location center, List<Player> players) throws IllegalArgumentException {
if (players.isEmpty()) {
throw new IllegalArgumentException("The player list is empty");
}
for (Player player : players) {
sendTo(center, player);
}
}
/**
* Sends the packet to all players in a certain range
*
* @param center Center location of the effect
* @param range Range in which players will receive the packet (Maximum range for particles is usually 16, but it can differ for some types)
* @throws IllegalArgumentException If the range is lower than 1
* @see #sendTo(Location center, Player player)
*/
public void sendTo(Location center, double range) throws IllegalArgumentException {
if (range < 1) {
throw new IllegalArgumentException("The range is lower than 1");
}
String worldName = center.getWorld().getName();
double squared = range * range;
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.getWorld().getName().equals(worldName) || player.getLocation().distanceSquared(center) > squared) {
continue;
}
sendTo(center, player);
}
}
/**
* Represents a runtime exception that is thrown if a bukkit version is not compatible with this library
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.5
*/
private static final class VersionIncompatibleException extends RuntimeException {
private static final long serialVersionUID = 3203085387160737484L;
/**
* Construct a new version incompatible exception
*
* @param message Message that will be logged
* @param cause Cause of the exception
*/
public VersionIncompatibleException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Represents a runtime exception that is thrown if packet instantiation fails
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.4
*/
private static final class PacketInstantiationException extends RuntimeException {
private static final long serialVersionUID = 3203085387160737484L;
/**
* Construct a new packet instantiation exception
*
* @param message Message that will be logged
* @param cause Cause of the exception
*/
public PacketInstantiationException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Represents a runtime exception that is thrown if packet sending fails
* <p>
* This class is part of the <b>ParticleEffect Library</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.4
*/
private static final class PacketSendingException extends RuntimeException {
private static final long serialVersionUID = 3203085387160737484L;
/**
* Construct a new packet sending exception
*
* @param message Message that will be logged
* @param cause Cause of the exception
*/
public PacketSendingException(String message, Throwable cause) {
super(message, cause);
}
}
}
}
package com.darkblade12.particleeffect;
import org.bukkit.Bukkit;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* <b>ReflectionUtils</b>
* <p>
* This class provides useful methods which makes dealing with reflection much easier, especially when working with Bukkit
* <p>
* You are welcome to use it, modify it and redistribute it under the following conditions:
* <ul>
* <li>Don't claim this class as your own
* <li>Don't remove this disclaimer
* </ul>
* <p>
* <i>It would be nice if you provide credit to me if you use this class in a published project</i>
*
* @author DarkBlade12
* @version 1.1
*/
@SuppressWarnings("ALL")
public final class ReflectionUtils {
// Prevent accidental construction
private ReflectionUtils() {}
/**
* Returns the constructor of a given class with the given parameter types
*
* @param clazz Target class
* @param parameterTypes Parameter types of the desired constructor
* @return The constructor of the target class with the specified parameter types
* @throws NoSuchMethodException If the desired constructor with the specified parameter types cannot be found
* @see DataType
* @see DataType#getPrimitive(Class[])
* @see DataType#compare(Class[], Class[])
*/
public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... parameterTypes) throws NoSuchMethodException {
Class<?>[] primitiveTypes = DataType.getPrimitive(parameterTypes);
for (Constructor<?> constructor : clazz.getConstructors()) {
if (!DataType.compare(DataType.getPrimitive(constructor.getParameterTypes()), primitiveTypes)) {
continue;
}
return constructor;
}
throw new NoSuchMethodException("There is no such constructor in this class with the specified parameter types");
}
/**
* Returns the constructor of a desired class with the given parameter types
*
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param parameterTypes Parameter types of the desired constructor
* @return The constructor of the desired target class with the specified parameter types
* @throws NoSuchMethodException If the desired constructor with the specified parameter types cannot be found
* @throws ClassNotFoundException ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #getClass(String, PackageType)
* @see #getConstructor(Class, Class...)
*/
public static Constructor<?> getConstructor(String className, PackageType packageType, Class<?>... parameterTypes) throws NoSuchMethodException, ClassNotFoundException {
return getConstructor(packageType.getClass(className), parameterTypes);
}
/**
* Returns an instance of a class with the given arguments
*
* @param clazz Target class
* @param arguments Arguments which are used to construct an object of the target class
* @return The instance of the target class with the specified arguments
* @throws InstantiationException If you cannot create an instance of the target class due to certain circumstances
* @throws IllegalAccessException If the desired constructor cannot be accessed due to certain circumstances
* @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the constructor (this should not occur since it searches for a constructor with the types of the arguments)
* @throws InvocationTargetException If the desired constructor cannot be invoked
* @throws NoSuchMethodException If the desired constructor with the specified arguments cannot be found
*/
public static Object instantiateObject(Class<?> clazz, Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
return getConstructor(clazz, DataType.getPrimitive(arguments)).newInstance(arguments);
}
/**
* Returns an instance of a desired class with the given arguments
*
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param arguments Arguments which are used to construct an object of the desired target class
* @return The instance of the desired target class with the specified arguments
* @throws InstantiationException If you cannot create an instance of the desired target class due to certain circumstances
* @throws IllegalAccessException If the desired constructor cannot be accessed due to certain circumstances
* @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the constructor (this should not occur since it searches for a constructor with the types of the arguments)
* @throws InvocationTargetException If the desired constructor cannot be invoked
* @throws NoSuchMethodException If the desired constructor with the specified arguments cannot be found
* @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #getClass(String, PackageType)
* @see #instantiateObject(Class, Object...)
*/
public static Object instantiateObject(String className, PackageType packageType, Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
return instantiateObject(packageType.getClass(className), arguments);
}
/**
* Returns a method of a class with the given parameter types
*
* @param clazz Target class
* @param methodName Name of the desired method
* @param parameterTypes Parameter types of the desired method
* @return The method of the target class with the specified name and parameter types
* @throws NoSuchMethodException If the desired method of the target class with the specified name and parameter types cannot be found
* @see DataType#getPrimitive(Class[])
* @see DataType#compare(Class[], Class[])
*/
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
Class<?>[] primitiveTypes = DataType.getPrimitive(parameterTypes);
for (Method method : clazz.getMethods()) {
if (!method.getName().equals(methodName) || !DataType.compare(DataType.getPrimitive(method.getParameterTypes()), primitiveTypes)) {
continue;
}
return method;
}
throw new NoSuchMethodException("There is no such method in this class with the specified name and parameter types");
}
/**
* Returns a method of a desired class with the given parameter types
*
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param methodName Name of the desired method
* @param parameterTypes Parameter types of the desired method
* @return The method of the desired target class with the specified name and parameter types
* @throws NoSuchMethodException If the desired method of the desired target class with the specified name and parameter types cannot be found
* @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #getClass(String, PackageType)
* @see #getMethod(Class, String, Class...)
*/
public static Method getMethod(String className, PackageType packageType, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException, ClassNotFoundException {
return getMethod(packageType.getClass(className), methodName, parameterTypes);
}
/**
* Invokes a method on an object with the given arguments
*
* @param instance Target object
* @param methodName Name of the desired method
* @param arguments Arguments which are used to invoke the desired method
* @return The result of invoking the desired method on the target object
* @throws IllegalAccessException If the desired method cannot be accessed due to certain circumstances
* @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the method (this should not occur since it searches for a method with the types of the arguments)
* @throws InvocationTargetException If the desired method cannot be invoked on the target object
* @throws NoSuchMethodException If the desired method of the class of the target object with the specified name and arguments cannot be found
* @see #getMethod(Class, String, Class...)
* @see DataType#getPrimitive(Object[])
*/
public static Object invokeMethod(Object instance, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
return getMethod(instance.getClass(), methodName, DataType.getPrimitive(arguments)).invoke(instance, arguments);
}
/**
* Invokes a method of the target class on an object with the given arguments
*
* @param instance Target object
* @param clazz Target class
* @param methodName Name of the desired method
* @param arguments Arguments which are used to invoke the desired method
* @return The result of invoking the desired method on the target object
* @throws IllegalAccessException If the desired method cannot be accessed due to certain circumstances
* @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the method (this should not occur since it searches for a method with the types of the arguments)
* @throws InvocationTargetException If the desired method cannot be invoked on the target object
* @throws NoSuchMethodException If the desired method of the target class with the specified name and arguments cannot be found
* @see #getMethod(Class, String, Class...)
* @see DataType#getPrimitive(Object[])
*/
public static Object invokeMethod(Object instance, Class<?> clazz, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
return getMethod(clazz, methodName, DataType.getPrimitive(arguments)).invoke(instance, arguments);
}
/**
* Invokes a method of a desired class on an object with the given arguments
*
* @param instance Target object
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param methodName Name of the desired method
* @param arguments Arguments which are used to invoke the desired method
* @return The result of invoking the desired method on the target object
* @throws IllegalAccessException If the desired method cannot be accessed due to certain circumstances
* @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the method (this should not occur since it searches for a method with the types of the arguments)
* @throws InvocationTargetException If the desired method cannot be invoked on the target object
* @throws NoSuchMethodException If the desired method of the desired target class with the specified name and arguments cannot be found
* @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #getClass(String, PackageType)
* @see #invokeMethod(Object, Class, String, Object...)
*/
public static Object invokeMethod(Object instance, String className, PackageType packageType, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
return invokeMethod(instance, packageType.getClass(className), methodName, arguments);
}
/**
* Returns a field of the target class with the given name
*
* @param clazz Target class
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @return The field of the target class with the specified name
* @throws NoSuchFieldException If the desired field of the given class cannot be found
* @throws SecurityException If the desired field cannot be made accessible
*/
public static Field getField(Class<?> clazz, boolean declared, String fieldName) throws NoSuchFieldException, SecurityException {
Field field = declared ? clazz.getDeclaredField(fieldName) : clazz.getField(fieldName);
field.setAccessible(true);
return field;
}
/**
* Returns a field of a desired class with the given name
*
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @return The field of the desired target class with the specified name
* @throws NoSuchFieldException If the desired field of the desired class cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #getField(Class, boolean, String)
*/
public static Field getField(String className, PackageType packageType, boolean declared, String fieldName) throws NoSuchFieldException, SecurityException, ClassNotFoundException {
return getField(packageType.getClass(className), declared, fieldName);
}
/**
* Returns the value of a field of the given class of an object
*
* @param instance Target object
* @param clazz Target class
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @return The value of field of the target object
* @throws IllegalArgumentException If the target object does not feature the desired field
* @throws IllegalAccessException If the desired field cannot be accessed
* @throws NoSuchFieldException If the desired field of the target class cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @see #getField(Class, boolean, String)
*/
public static Object getValue(Object instance, Class<?> clazz, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
return getField(clazz, declared, fieldName).get(instance);
}
/**
* Returns the value of a field of a desired class of an object
*
* @param instance Target object
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @return The value of field of the target object
* @throws IllegalArgumentException If the target object does not feature the desired field
* @throws IllegalAccessException If the desired field cannot be accessed
* @throws NoSuchFieldException If the desired field of the desired class cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #getValue(Object, Class, boolean, String)
*/
public static Object getValue(Object instance, String className, PackageType packageType, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException {
return getValue(instance, packageType.getClass(className), declared, fieldName);
}
/**
* Returns the value of a field with the given name of an object
*
* @param instance Target object
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @return The value of field of the target object
* @throws IllegalArgumentException If the target object does not feature the desired field (should not occur since it searches for a field with the given name in the class of the object)
* @throws IllegalAccessException If the desired field cannot be accessed
* @throws NoSuchFieldException If the desired field of the target object cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @see #getValue(Object, Class, boolean, String)
*/
public static Object getValue(Object instance, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
return getValue(instance, instance.getClass(), declared, fieldName);
}
/**
* Sets the value of a field of the given class of an object
*
* @param instance Target object
* @param clazz Target class
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @param value New value
* @throws IllegalArgumentException If the type of the value does not match the type of the desired field
* @throws IllegalAccessException If the desired field cannot be accessed
* @throws NoSuchFieldException If the desired field of the target class cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @see #getField(Class, boolean, String)
*/
public static void setValue(Object instance, Class<?> clazz, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
getField(clazz, declared, fieldName).set(instance, value);
}
/**
* Sets the value of a field of a desired class of an object
*
* @param instance Target object
* @param className Name of the desired target class
* @param packageType Package where the desired target class is located
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @param value New value
* @throws IllegalArgumentException If the type of the value does not match the type of the desired field
* @throws IllegalAccessException If the desired field cannot be accessed
* @throws NoSuchFieldException If the desired field of the desired class cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found
* @see #setValue(Object, Class, boolean, String, Object)
*/
public static void setValue(Object instance, String className, PackageType packageType, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException {
setValue(instance, packageType.getClass(className), declared, fieldName, value);
}
/**
* Sets the value of a field with the given name of an object
*
* @param instance Target object
* @param declared Whether the desired field is declared or not
* @param fieldName Name of the desired field
* @param value New value
* @throws IllegalArgumentException If the type of the value does not match the type of the desired field
* @throws IllegalAccessException If the desired field cannot be accessed
* @throws NoSuchFieldException If the desired field of the target object cannot be found
* @throws SecurityException If the desired field cannot be made accessible
* @see #setValue(Object, Class, boolean, String, Object)
*/
public static void setValue(Object instance, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
setValue(instance, instance.getClass(), declared, fieldName, value);
}
/**
* Represents an enumeration of dynamic packages of NMS and CraftBukkit
* <p>
* This class is part of the <b>ReflectionUtils</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.0
*/
public enum PackageType {
MINECRAFT_SERVER("net.minecraft.server." + getServerVersion()),
CRAFTBUKKIT("org.bukkit.craftbukkit." + getServerVersion()),
CRAFTBUKKIT_BLOCK(CRAFTBUKKIT, "block"),
CRAFTBUKKIT_CHUNKIO(CRAFTBUKKIT, "chunkio"),
CRAFTBUKKIT_COMMAND(CRAFTBUKKIT, "command"),
CRAFTBUKKIT_CONVERSATIONS(CRAFTBUKKIT, "conversations"),
CRAFTBUKKIT_ENCHANTMENS(CRAFTBUKKIT, "enchantments"),
CRAFTBUKKIT_ENTITY(CRAFTBUKKIT, "entity"),
CRAFTBUKKIT_EVENT(CRAFTBUKKIT, "event"),
CRAFTBUKKIT_GENERATOR(CRAFTBUKKIT, "generator"),
CRAFTBUKKIT_HELP(CRAFTBUKKIT, "help"),
CRAFTBUKKIT_INVENTORY(CRAFTBUKKIT, "inventory"),
CRAFTBUKKIT_MAP(CRAFTBUKKIT, "map"),
CRAFTBUKKIT_METADATA(CRAFTBUKKIT, "metadata"),
CRAFTBUKKIT_POTION(CRAFTBUKKIT, "potion"),
CRAFTBUKKIT_PROJECTILES(CRAFTBUKKIT, "projectiles"),
CRAFTBUKKIT_SCHEDULER(CRAFTBUKKIT, "scheduler"),
CRAFTBUKKIT_SCOREBOARD(CRAFTBUKKIT, "scoreboard"),
CRAFTBUKKIT_UPDATER(CRAFTBUKKIT, "updater"),
CRAFTBUKKIT_UTIL(CRAFTBUKKIT, "util");
private final String path;
/**
* Construct a new package type
*
* @param path Path of the package
*/
private PackageType(String path) {
this.path = path;
}
/**
* Construct a new package type
*
* @param parent Parent package of the package
* @param path Path of the package
*/
private PackageType(PackageType parent, String path) {
this(parent + "." + path);
}
/**
* Returns the path of this package type
*
* @return The path
*/
public String getPath() {
return path;
}
/**
* Returns the class with the given name
*
* @param className Name of the desired class
* @return The class with the specified name
* @throws ClassNotFoundException If the desired class with the specified name and package cannot be found
*/
public Class<?> getClass(String className) throws ClassNotFoundException {
return Class.forName(this + "." + className);
}
// Override for convenience
@Override
public String toString() {
return path;
}
/**
* Returns the version of your server
*
* @return The server version
*/
public static String getServerVersion() {
return Bukkit.getServer().getClass().getPackage().getName().substring(23);
}
}
/**
* Represents an enumeration of Java data types with corresponding classes
* <p>
* This class is part of the <b>ReflectionUtils</b> and follows the same usage conditions
*
* @author DarkBlade12
* @since 1.0
*/
public enum DataType {
BYTE(byte.class, Byte.class),
SHORT(short.class, Short.class),
INTEGER(int.class, Integer.class),
LONG(long.class, Long.class),
CHARACTER(char.class, Character.class),
FLOAT(float.class, Float.class),
DOUBLE(double.class, Double.class),
BOOLEAN(boolean.class, Boolean.class);
private static final Map<Class<?>, DataType> CLASS_MAP = new HashMap<Class<?>, DataType>();
private final Class<?> primitive;
private final Class<?> reference;
// Initialize map for quick class lookup
static {
for (DataType type : values()) {
CLASS_MAP.put(type.primitive, type);
CLASS_MAP.put(type.reference, type);
}
}
/**
* Construct a new data type
*
* @param primitive Primitive class of this data type
* @param reference Reference class of this data type
*/
private DataType(Class<?> primitive, Class<?> reference) {
this.primitive = primitive;
this.reference = reference;
}
/**
* Returns the primitive class of this data type
*
* @return The primitive class
*/
public Class<?> getPrimitive() {
return primitive;
}
/**
* Returns the reference class of this data type
*
* @return The reference class
*/
public Class<?> getReference() {
return reference;
}
/**
* Returns the data type with the given primitive/reference class
*
* @param clazz Primitive/Reference class of the data type
* @return The data type
*/
public static DataType fromClass(Class<?> clazz) {
return CLASS_MAP.get(clazz);
}
/**
* Returns the primitive class of the data type with the given reference class
*
* @param clazz Reference class of the data type
* @return The primitive class
*/
public static Class<?> getPrimitive(Class<?> clazz) {
DataType type = fromClass(clazz);
return type == null ? clazz : type.getPrimitive();
}
/**
* Returns the reference class of the data type with the given primitive class
*
* @param clazz Primitive class of the data type
* @return The reference class
*/
public static Class<?> getReference(Class<?> clazz) {
DataType type = fromClass(clazz);
return type == null ? clazz : type.getReference();
}
/**
* Returns the primitive class array of the given class array
*
* @param classes Given class array
* @return The primitive class array
*/
public static Class<?>[] getPrimitive(Class<?>[] classes) {
int length = classes == null ? 0 : classes.length;
Class<?>[] types = new Class<?>[length];
for (int index = 0; index < length; index++) {
types[index] = getPrimitive(classes[index]);
}
return types;
}
/**
* Returns the reference class array of the given class array
*
* @param classes Given class array
* @return The reference class array
*/
public static Class<?>[] getReference(Class<?>[] classes) {
int length = classes == null ? 0 : classes.length;
Class<?>[] types = new Class<?>[length];
for (int index = 0; index < length; index++) {
types[index] = getReference(classes[index]);
}
return types;
}
/**
* Returns the primitive class array of the given object array
*
* @param object Given object array
* @return The primitive class array
*/
public static Class<?>[] getPrimitive(Object[] objects) {
int length = objects == null ? 0 : objects.length;
Class<?>[] types = new Class<?>[length];
for (int index = 0; index < length; index++) {
types[index] = getPrimitive(objects[index].getClass());
}
return types;
}
/**
* Returns the reference class array of the given object array
*
* @param object Given object array
* @return The reference class array
*/
public static Class<?>[] getReference(Object[] objects) {
int length = objects == null ? 0 : objects.length;
Class<?>[] types = new Class<?>[length];
for (int index = 0; index < length; index++) {
types[index] = getReference(objects[index].getClass());
}
return types;
}
/**
* Compares two class arrays on equivalence
*
* @param primary Primary class array
* @param secondary Class array which is compared to the primary array
* @return Whether these arrays are equal or not
*/
public static boolean compare(Class<?>[] primary, Class<?>[] secondary) {
if (primary == null || secondary == null || primary.length != secondary.length) {
return false;
}
for (int index = 0; index < primary.length; index++) {
Class<?> primaryClass = primary[index];
Class<?> secondaryClass = secondary[index];
if (primaryClass.equals(secondaryClass) || primaryClass.isAssignableFrom(secondaryClass)) {
continue;
}
return false;
}
return true;
}
}
}
# == Anything requiring a duration or time: ==
# Format is quite loose. Place anything anywhere, in any order.
# <number> ::= Any valid integer
# "<number>t" ==> The time in ticks
# "<number>S" ==> The time in milliseconds
# "<number>s" ==> The time in seconds
# "<number>m" ==> The time in minutes
# "<number>h" ==> The time in hours
# "<number>d" ==> The time in days
# The time before you can teleport with a linking book.
# Before you will be frozen in the air
# Is a duration (see top of file)
linking book warmup: 5s
# The warmup for the relto book
relto book warmup: 5s
# The time a relto invitation is valid
relto_invitation_timeout: 20s
# The invulnerability after a teleport.
# Is a duration (see top of file)
no_damage_ticks_after_teleport: 5s
# The material for the descriptive book
descriptive_book_item_material: enchanting_table
# The material for the finished linking book
finished_linking_book_item_material: book
# The material for an unfinished linking book (one where the user hasn't right-clicked a location yet)
unfinished_linking_book_item_material: book
# The material to use to right click on a descriptive book
linking_book_base_material: book_and_quill
# The material to use for an unset Relto book
unset_relto_book_item_material: book_and_quill
# The material to use for a Relto book
relto_book_item_material: book
# suppress inspection "UnusedProperty" for whole file
# The lore of the descriptive book item. Use "<newline>" for a new line
# Placeholders: {0} ==> The x coordinate, {1} ==> y, {1} ==> z, {3} ==> The world name
descriptive_book_item_lore=&a{0, number,#} {1, number,#} {2, number,#}<newline>&2{3}
# The title of the descriptive book item
descriptive_book_item_name=&3&lDescriptive book
# The lore of the finished linking book item. Use "<newline>" for a new line
# Placeholders: {0} ==> The x coordinate, {1} ==> y, {1} ==> z, {3} ==> The world name
finished_linking_book_item_lore=&a{0, number,#} {1, number,#} {2, number,#}<newline>&2{3}
# The title of the linking book item
finished_linking_book_item_name=&3&lLinking book
# The lore of the unfinished linking book item. Use "<newline>" for a new line
# Placeholders: {0} ==> The x coordinate, {1} ==> y, {1} ==> z, {3} ==> The world name
unfinished_linking_book_item_lore=&6Click anywhere in world &a{3}<newline>&6to finish the creation.
# The title of the linking book item
unfinished_linking_book_item_name=&3&lUnfinished Linking book
# The lore of the unset Relto book item. Use "<newline>" for a new line
unset_relto_book_item_lore=&6Have this in your inventory<newline>&6and you can assign a home<newline>&6location to it using commands.
# The title of the unset Relto book item
unset_relto_book_item_name=&c&lUnset &3&lRelto
# The lore of the unset Relto book item. Use "<newline>" for a new line
relto_book_item_lore=&6Allows you to teleport home.
# The title of the unset Relto book item
relto_book_item_name=&3&lRelto Book
# ==== COMMANDS ====
command_age_name= &2Get &aDescriptive Book&2
command_age_keyword= age
command_age_pattern= age
command_age_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_age_usage= &cUsage: /kor new age &8[<x> <y> <z> <world name>]
# Placeholders: {0} ==> the command name
command_age_description= &3&l{0}&8: &7Gives you a descriptive book.
command_relto_relay_name= &2Relto
command_relto_relay_keyword= relto
command_relto_relay_pattern= relto
command_relto_relay_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_relay_usage= &cUsage: /kor relto &6<clear | set>
# Placeholders: {0} ==> the command name
command_relto_relay_description= &3&l{0}&8: &7The main relto command
command_relto_clear_name= &2Relto clear
command_relto_clear_keyword= clear
command_relto_clear_pattern= clear|remove
command_relto_clear_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_clear_usage= &cUsage: /kor relto &6clear
# Placeholders: {0} ==> the command name
command_relto_clear_description= &3&l{0}&8: &7Clear your home location.
command_relto_set_name= &2Relto Set
command_relto_set_keyword= set
command_relto_set_pattern= set
command_relto_set_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_set_usage= &cUsage: /kor relto &6set
# Placeholders: {0} ==> the command name
command_relto_set_description= &3&l{0}&8: &7Sets your home location.
command_relto_accept_name= &2Relto Accept
command_relto_accept_keyword= accept
command_relto_accept_pattern= accept
command_relto_accept_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_accept_usage= &cUsage: /kor relto &6accept
# Placeholders: {0} ==> the command name
command_relto_accept_description= &3&l{0}&8: &7Accepts an invitation to teleport to a home.
placed_descriptive_book=&2You placed a descriptive book at &6{0} {1} {2} &2in world &a{3}
destroyed_descriptive_book=&cYou destroyed a descriptive book at &6{0} {1} {2} &cin world &a{3}
world_unknown=&cThe world ''&4{0}&c'' is not known.
number_not_a_decimal_value=&c''&4{0}&c'' is not a decimal number.
gave_you_a_linking_book=&2Gave you a linking book for &a{0, number,#} {1, number,#} {2, number,#} &2in world &a{3}&2.
gave_you_a_descriptive_book=&2Gave you a descriptive book for &a{0, number,#} {1, number,#} {2, number,#} &2in world &a{3}&2.
already_a_warmup_running=&cSorry, but you must wait for the current warmup to finish.
warmup_started=&6Warmup started. Teleportation in &a{0}&6.
linking_book_teleported_you=&6You were successfully teleported to &a{0, number,#} {1, number,#} {2, number,#} &6in world &a{3}&6.
gave_unfinished_linking_book=&6Gave you an unfinished linking book. Click any block in the world &a{3}&6 to finish the creation.
world_switch_removed_unfinished_book=&cRemoved all unfinished linking books as you travelled from &4{0} &cto &a{1}&c.
descriptive_book_teleported_you=&6You were successfully teleported to &a{0, number,#} {1, number,#} {2, number,#} &6in world &a{3}&6.
no_home_set=&cYou have not set your home.
home_reset=&6Your home location was reset. All relto books in your inventory were converted.
home_already_set=&cYour home is already set.
no_unset_relto_book_in_inventory=&cYou don't have an &aunset relto book &cin your inventory.
gave_you_a_relto_book=&6Gave you a &arelto book&6.
relto_book_teleported_you=&6Your relto book teleported you to &a{0, number,#} {1, number,#} {2, number,#} &6in world &a{3}&6.
relto_invitation_for_other_timed_out=&cYour relto invitation for &a{0} &ctimed out.
relto_accept_no_pending_invitation=&cYou have no pending invitation.
invitation_sender_deleted_home=&cThe sender deleted his home.
invitation_for_you_cancelled=&cThe invitation from &a{0} &cfor you was cancelled.
cancelled_invitation_due_to_new=&cThe invitation &cwas cancelled, as you made a new.
sent_invitation=&6You sent an invitation to &a{0}&6.
got_invitation=&6You got an invitation from &a{0}&6.
invitation_timed_out=&cThe invitation timed out.
your_invitation_was_accepted=&a{0} &2accepted your invitation.
# suppress inspection "UnusedProperty" for whole file
# The lore of the descriptive book item. Use "<newline>" for a new line
# Placeholders: {0} ==> The x coordinate, {1} ==> y, {1} ==> z, {3} ==> The world name
descriptive_book_item_lore=&a{0, number,#} {1, number,#} {2, number,#}<newline>&2{3}
# The title of the descriptive book item
descriptive_book_item_name=&3&lDescriptive book
# The lore of the finished linking book item. Use "<newline>" for a new line
# Placeholders: {0} ==> The x coordinate, {1} ==> y, {1} ==> z, {3} ==> The world name
finished_linking_book_item_lore=&a{0, number,#} {1, number,#} {2, number,#}<newline>&2{3}
# The title of the linking book item
finished_linking_book_item_name=&3&lLinking book
# The lore of the unfinished linking book item. Use "<newline>" for a new line
# Placeholders: {0} ==> The x coordinate, {1} ==> y, {1} ==> z, {3} ==> The world name
unfinished_linking_book_item_lore=&6Click anywhere in world &a{3}<newline>&6to finish the creation.
# The title of the linking book item
unfinished_linking_book_item_name=&3&lUnfinished Linking book
# The lore of the unset Relto book item. Use "<newline>" for a new line
unset_relto_book_item_lore=&6Have this in your inventory<newline>&6and you can assign a home<newline>&6location to it using commands.
# The title of the unset Relto book item
unset_relto_book_item_name=&c&lUnset &3&lRelto
# The lore of the unset Relto book item. Use "<newline>" for a new line
relto_book_item_lore=&6Allows you to teleport home.
# The title of the unset Relto book item
relto_book_item_name=&3&lRelto Book
# ==== COMMANDS ====
command_age_name= &2Get &aDescriptive Book&2
command_age_keyword= age
command_age_pattern= age
command_age_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_age_usage= &cUsage: /kor new age &8[<x> <y> <z> <world name>]
# Placeholders: {0} ==> the command name
command_age_description= &3&l{0}&8: &7Gives you a descriptive book.
command_relto_relay_name= &2Relto
command_relto_relay_keyword= relto
command_relto_relay_pattern= relto
command_relto_relay_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_relay_usage= &cUsage: /kor relto &6<clear | set>
# Placeholders: {0} ==> the command name
command_relto_relay_description= &3&l{0}&8: &7The main relto command
command_relto_clear_name= &2Relto clear
command_relto_clear_keyword= clear
command_relto_clear_pattern= clear|remove
command_relto_clear_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_clear_usage= &cUsage: /kor relto &6clear
# Placeholders: {0} ==> the command name
command_relto_clear_description= &3&l{0}&8: &7Clear your home location.
command_relto_set_name= &2Relto Set
command_relto_set_keyword= set
command_relto_set_pattern= set
command_relto_set_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_set_usage= &cUsage: /kor relto &6set
# Placeholders: {0} ==> the command name
command_relto_set_description= &3&l{0}&8: &7Sets your home location.
command_relto_accept_name= &2Relto Accept
command_relto_accept_keyword= accept
command_relto_accept_pattern= accept
command_relto_accept_permission= kortee.admin
# Placeholders: {0} ==> the command name
command_relto_accept_usage= &cUsage: /kor relto &6accept
# Placeholders: {0} ==> the command name
command_relto_accept_description= &3&l{0}&8: &7Accepts an invitation to teleport to a home.
placed_descriptive_book=&2You placed a descriptive book at &6{0} {1} {2} &2in world &a{3}
destroyed_descriptive_book=&cYou destroyed a descriptive book at &6{0} {1} {2} &cin world &a{3}
world_unknown=&cThe world ''&4{0}&c'' is not known.
number_not_a_decimal_value=&c''&4{0}&c'' is not a decimal number.
gave_you_a_linking_book=&2Gave you a linking book for &a{0, number,#} {1, number,#} {2, number,#} &2in world &a{3}&2.
gave_you_a_descriptive_book=&2Gave you a descriptive book for &a{0, number,#} {1, number,#} {2, number,#} &2in world &a{3}&2.
already_a_warmup_running=&cSorry, but you must wait for the current warmup to finish.
warmup_started=&6Warmup started. Teleportation in &a{0}&6.
linking_book_teleported_you=&6You were successfully teleported to &a{0, number,#} {1, number,#} {2, number,#} &6in world &a{3}&6.
gave_unfinished_linking_book=&6Gave you an unfinished linking book. Click any block in the world &a{3}&6 to finish the creation.
world_switch_removed_unfinished_book=&cRemoved all unfinished linking books as you travelled from &4{0} &cto &a{1}&c.
descriptive_book_teleported_you=&6You were successfully teleported to &a{0, number,#} {1, number,#} {2, number,#} &6in world &a{3}&6.
no_home_set=&cYou have not set your home.
home_reset=&6Your home location was reset. All relto books in your inventory were converted.
home_already_set=&cYour home is already set.
no_unset_relto_book_in_inventory=&cYou don't have an &aunset relto book &cin your inventory.
gave_you_a_relto_book=&6Gave you a &arelto book&6.
relto_book_teleported_you=&6Your relto book teleported you to &a{0, number,#} {1, number,#} {2, number,#} &6in world &a{3}&6.
relto_invitation_for_other_timed_out=&cYour relto invitation for &a{0} &ctimed out.
relto_accept_no_pending_invitation=&cYou have no pending invitation.
invitation_sender_deleted_home=&cThe sender deleted his home.
invitation_for_you_cancelled=&cThe invitation from &a{0} &cfor you was cancelled.
cancelled_invitation_due_to_new=&cThe invitation &cwas cancelled, as you made a new.
sent_invitation=&6You sent an invitation to &a{0}&6.
got_invitation=&6You got an invitation from &a{0}&6.
invitation_timed_out=&cThe invitation timed out.
your_invitation_was_accepted=&a{0} &2accepted your invitation.
package me.ialistannen.mystlinkingbooks.commands;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.data.BookItemFactory;
import me.ialistannen.mystlinkingbooks.data.BookType;
import me.ialistannen.mystlinkingbooks.util.Util;
import me.ialistannen.tree_command_system.TranslatedPlayerCommandNode;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static me.ialistannen.mystlinkingbooks.util.Util.tr;
/**
* The base command for the age class
*/
public class CommandAge extends TranslatedPlayerCommandNode {
/**
* Adds the children too.
*/
public CommandAge() {
super(MystLinkingBooks.getInstance().getLanguage(), "command_age");
}
/**
* The tab completions for this particular command
*
* @param input The current word the user wrote. May be empty
* @param wholeUserChat All the words the user wrote.
* @param player The {@link Player} who initiated the tab complete. May be queried for permission checks or
* similar.
*
* @return A list with all valid tab completions
*/
@Override
protected List<String> getTabCompletions(String input, List<String> wholeUserChat, Player player) {
List<String> completions = new ArrayList<>();
if (wholeUserChat.size() == 3) {
completions.addAll(Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList()));
}
return completions;
}
/**
* @param player The player who executed the command
* @param args The arguments he passed
*
* @return False if the usage should be send
*/
@Override
protected boolean execute(Player player, String... args) {
if (args.length != 0 && args.length != 4) {
return false;
}
Location location;
if (args.length == 0) {
location = player.getLocation();
} else {
World world = Bukkit.getWorld(args[0]);
if (world == null) {
player.sendMessage(tr("world_unknown", args[0]));
return true;
}
double[] coords = new double[3];
for (int i = 1; i < 4; i++) {
if (!Util.isDouble(args[i])) {
player.sendMessage(tr("number_not_a_decimal_value", args[i]));
return true;
}
coords[i - 1] = Double.parseDouble(args[i]);
}
location = new Location(world, coords[0], coords[1], coords[2]);
}
ItemStack item = BookItemFactory.create(location, BookType.DESCRIPTIVE);
player.getInventory().addItem(item);
player.sendMessage(tr("gave_you_a_descriptive_book",
location.getX(), location.getY(), location.getZ(), location.getWorld().getName()));
return true;
}
}
package me.ialistannen.mystlinkingbooks.commands;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.tree_command_system.TranslatedPlayerCommandNode;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
/**
* A translated player command node
*/
public class CommandPrisonBook extends TranslatedPlayerCommandNode {
public CommandPrisonBook() {
super(MystLinkingBooks.getInstance().getLanguage(), "command_prison_book");
}
@Override
protected List<String> getTabCompletions(String input, List<String> wholeUserChat, Player player) {
return Collections.emptyList();
}
@Override
protected boolean execute(Player player, String... args) {
return true;
}
}
package me.ialistannen.mystlinkingbooks.commands;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.tree_command_system.TranslatedRelayCommandNode;
import org.bukkit.entity.Player;
/**
* Relays the commands
*/
public class CommandRelto extends TranslatedRelayCommandNode {
public CommandRelto() {
super(MystLinkingBooks.getInstance().getLanguage(), "command_relto_relay", sender -> sender instanceof Player);
addChild(new CommandReltoClear());
addChild(new CommandReltoSet());
addChild(new CommandReltoAccept());
}
}
package me.ialistannen.mystlinkingbooks.commands;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.util.InvitationManager;
import me.ialistannen.mystlinkingbooks.util.Util;
import me.ialistannen.tree_command_system.TranslatedPlayerCommandNode;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static me.ialistannen.mystlinkingbooks.util.Util.tr;
/**
* Accepts a relto invitation
*/
public class CommandReltoAccept extends TranslatedPlayerCommandNode {
public CommandReltoAccept() {
super(MystLinkingBooks.getInstance().getLanguage(), "command_relto_accept");;
}
/**
* The tab completions for this particular command
*
* @param input The current word the user wrote. May be empty
* @param wholeUserChat All the words the user wrote.
* @param player The {@link Player} who initiated the tab complete. May be queried for permission checks or
* similar.
*
* @return A list with all valid tab completions
*/
@Override
protected List<String> getTabCompletions(String input, List<String> wholeUserChat, Player player) {
return Collections.emptyList();
}
/**
* @param player The player who executed the command
* @param args The arguments he passed
*
* @return False if the usage should be send
*/
@Override
protected boolean execute(Player player, String... args) {
InvitationManager invitationManager = MystLinkingBooks.getInstance().getInvitationManager();
Optional<UUID> invitationSender = invitationManager.getInvitationSender(player.getUniqueId());
if(!invitationSender.isPresent()) {
player.sendMessage(tr("relto_accept_no_pending_invitation"));
return true;
}
UUID senderUuid = invitationSender.get();
Optional<Location> senderHome = MystLinkingBooks.getInstance().getReltoBookManager().getHome(senderUuid);
if(!senderHome.isPresent()) {
player.sendMessage(Util.tr("invitation_sender_deleted_home"));
invitationManager.removeInvitation(player.getUniqueId());
return true;
}
Player senderPlayer = Bukkit.getPlayer(senderUuid);
if(senderPlayer != null) {
senderPlayer.sendMessage(Util.tr("your_invitation_was_accepted", player.getDisplayName()));
}
Util.startReltoTeleportWithWarmup(player, senderHome.get());
invitationManager.removeInvitation(player.getUniqueId());
return true;
}
}
package me.ialistannen.mystlinkingbooks.commands;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.data.BookItemFactory;
import me.ialistannen.mystlinkingbooks.data.BookType;
import me.ialistannen.mystlinkingbooks.data.ReltoBookManager;
import me.ialistannen.mystlinkingbooks.util.BookUtil;
import me.ialistannen.tree_command_system.TranslatedPlayerCommandNode;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static me.ialistannen.mystlinkingbooks.util.Util.tr;
/**
* Clears the home location and un-sets the relto books
*/
public class CommandReltoClear extends TranslatedPlayerCommandNode {
public CommandReltoClear() {
super(MystLinkingBooks.getInstance().getLanguage(), "command_relto_clear");
}
/**
* The tab completions for this particular command
*
* @param input The current word the user wrote. May be empty
* @param wholeUserChat All the words the user wrote.
* @param player The {@link Player} who initiated the tab complete. May be queried for permission checks or
* similar.
*
* @return A list with all valid tab completions
*/
@Override
protected List<String> getTabCompletions(String input, List<String> wholeUserChat, Player player) {
return Collections.emptyList();
}
/**
* @param player The player who executed the command
* @param args The arguments he passed
*
* @return False if the usage should be send
*/
@Override
protected boolean execute(Player player, String... args) {
ReltoBookManager reltoBookManager = MystLinkingBooks.getInstance().getReltoBookManager();
if(!reltoBookManager.containsUUID(player.getUniqueId())) {
player.sendMessage(tr("no_home_set"));
return true;
}
reltoBookManager.removeHome(player.getUniqueId());
List<Integer> indices = new ArrayList<>();
ItemStack[] contents = player.getInventory().getContents();
for (int i = 0; i < contents.length; i++) {
ItemStack itemStack = contents[i];
if (BookUtil.isReltoBook(itemStack)) {
indices.add(i);
}
}
if(!indices.isEmpty()) {
ItemStack bookItem = BookItemFactory.create(null, BookType.UNSET_RELTO);
for (Integer index : indices) {
player.getInventory().setItem(index, bookItem);
}
}
player.sendMessage(tr("home_reset"));
return true;
}
}
package me.ialistannen.mystlinkingbooks.commands;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.data.BookItemFactory;
import me.ialistannen.mystlinkingbooks.data.BookType;
import me.ialistannen.mystlinkingbooks.data.ReltoBookManager;
import me.ialistannen.mystlinkingbooks.util.BookUtil;
import me.ialistannen.mystlinkingbooks.util.Util;
import me.ialistannen.tree_command_system.TranslatedPlayerCommandNode;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Sets the home location
*/
public class CommandReltoSet extends TranslatedPlayerCommandNode {
public CommandReltoSet() {
super(MystLinkingBooks.getInstance().getLanguage(), "command_relto_set");
}
/**
* The tab completions for this particular command
*
* @param input The current word the user wrote. May be empty
* @param wholeUserChat All the words the user wrote.
* @param player The {@link Player} who initiated the tab complete. May be queried for permission checks or
* similar.
*
* @return A list with all valid tab completions
*/
@Override
protected List<String> getTabCompletions(String input, List<String> wholeUserChat, Player player) {
return Collections.emptyList();
}
/**
* @param player The player who executed the command
* @param args The arguments he passed
*
* @return False if the usage should be send
*/
@Override
protected boolean execute(Player player, String... args) {
ReltoBookManager reltoBookManager = MystLinkingBooks.getInstance().getReltoBookManager();
if(reltoBookManager.containsUUID(player.getUniqueId())) {
player.sendMessage(Util.tr("home_already_set"));
return true;
}
List<Integer> indices = new ArrayList<>();
ItemStack[] contents = player.getInventory().getContents();
for (int i = 0; i < contents.length; i++) {
ItemStack itemStack = contents[i];
if (BookUtil.isUnsetReltoBook(itemStack)) {
indices.add(i);
}
}
if(indices.isEmpty()) {
player.sendMessage(Util.tr("no_unset_relto_book_in_inventory"));
return true;
}
ItemStack bookItem = BookItemFactory.create(null, BookType.RELTO);
for (Integer index : indices) {
player.getInventory().setItem(index, bookItem);
}
reltoBookManager.setHome(player.getUniqueId(), player.getLocation());
player.sendMessage(Util.tr("gave_you_a_relto_book"));
return true;
}
}
package me.ialistannen.mystlinkingbooks.data;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
/**
* Creates the items
*/
public class BookItemFactory {
/**
* Creates a book for a location
*
* @param location The creation location
* @param bookType The book type
*
* @return The itemstack for the book
*/
public static ItemStack create(Location location, BookType bookType) {
return bookType.create(location);
}
}
package me.ialistannen.mystlinkingbooks.data;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.util.BookUtil;
import me.ialistannen.mystlinkingbooks.util.ItemStackBuilder;
import me.ialistannen.mystlinkingbooks.util.Util;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
import java.util.function.Function;
import static me.ialistannen.mystlinkingbooks.util.Util.tr;
/**
* The book types
*/
public enum BookType {
/**
* A book (or normally enchanting table) that can be placed in the world.
* <br>Other books can link to this.
*/
DESCRIPTIVE(location -> {
Material material;
{
String materialName = MystLinkingBooks.getInstance()
.getConfig().getString("descriptive_book_item_material");
Optional<Material> materialOptional = Util.matchMaterial(materialName);
if (!materialOptional.isPresent()) {
MystLinkingBooks.getInstance().getLogger().severe(
"The material '" + materialName + "' can't be found. (descriptive book material)");
Bukkit.getPluginManager().disablePlugin(MystLinkingBooks.getInstance());
return null;
}
material = materialOptional.get();
if (!material.isBlock()) {
MystLinkingBooks.getInstance().getLogger().severe(
"The material '" + materialName + "' must be a block. (descriptive book material)");
Bukkit.getPluginManager().disablePlugin(MystLinkingBooks.getInstance());
return null;
}
}
String name = tr("descriptive_book_item_name");
String lore = tr("descriptive_book_item_lore",
location.getX(), location.getY(), location.getZ(), location.getWorld().getName());
ItemStack book = ItemStackBuilder
.builder(material)
.setLore(lore.split("<newline>"))
.setName(name)
.build();
book = BookUtil.makeDescriptiveBook(location, book);
return book;
}),
/**
* A book that can be linked to a {@link #DESCRIPTIVE} book and allows teleportation
*/
FINISHED_LINKING(location -> {
Material material;
{
String materialName = MystLinkingBooks.getInstance()
.getConfig().getString("finished_linking_book_item_material");
Optional<Material> materialOptional = Util.matchMaterial(materialName);
if (!materialOptional.isPresent()) {
MystLinkingBooks.getInstance().getLogger().severe(
"The material '" + materialName + "' can't be found. (finished linking book material)");
Bukkit.getPluginManager().disablePlugin(MystLinkingBooks.getInstance());
return null;
}
material = materialOptional.get();
}
String name = tr("finished_linking_book_item_name");
String lore = tr("finished_linking_book_item_lore",
location.getX(), location.getY(), location.getZ(), location.getWorld().getName());
ItemStack book = ItemStackBuilder
.builder(material)
.setLore(lore.split("<newline>"))
.setName(name)
.build();
book = BookUtil.makeFinishedLinkingBook(location, book);
return book;
}),
/**
* A linking book, that still needs to be bound to a location in a world
*/
UNFINISHED_LINKING_BOOK(location -> {
Material material;
{
String materialName = MystLinkingBooks.getInstance()
.getConfig().getString("unfinished_linking_book_item_material");
Optional<Material> materialOptional = Util.matchMaterial(materialName);
if (!materialOptional.isPresent()) {
MystLinkingBooks.getInstance().getLogger().severe(
"The material '" + materialName + "' can't be found. (unfinished linking book material)");
Bukkit.getPluginManager().disablePlugin(MystLinkingBooks.getInstance());
return null;
}
material = materialOptional.get();
}
String name = tr("unfinished_linking_book_item_name");
String lore = tr("unfinished_linking_book_item_lore",
location.getX(), location.getY(), location.getZ(), location.getWorld().getName());
ItemStack book = ItemStackBuilder
.builder(material)
.setLore(lore.split("<newline>"))
.setName(name)
.build();
book = BookUtil.makeUnfinishedLinkingBook(location, book);
return book;
}),
/**
* A book to bring you home.
*/
RELTO(location -> {
Material material;
{
String materialName = MystLinkingBooks.getInstance()
.getConfig().getString("relto_book_item_material");
Optional<Material> materialOptional = Util.matchMaterial(materialName);
if (!materialOptional.isPresent()) {
MystLinkingBooks.getInstance().getLogger().severe(
"The material '" + materialName + "' can't be found. (unfinished linking book material)");
Bukkit.getPluginManager().disablePlugin(MystLinkingBooks.getInstance());
return null;
}
material = materialOptional.get();
}
String name = tr("relto_book_item_name");
String lore = tr("relto_book_item_lore");
ItemStack book = ItemStackBuilder
.builder(material)
.setLore(lore.split("<newline>"))
.setName(name)
.build();
book = BookUtil.makeReltoBook(book);
return book;
}),
UNSET_RELTO(location -> {
Material material;
{
String materialName = MystLinkingBooks.getInstance()
.getConfig().getString("unset_relto_book_item_material");
Optional<Material> materialOptional = Util.matchMaterial(materialName);
if (!materialOptional.isPresent()) {
MystLinkingBooks.getInstance().getLogger().severe(
"The material '" + materialName + "' can't be found. (relto book material)");
Bukkit.getPluginManager().disablePlugin(MystLinkingBooks.getInstance());
return null;
}
material = materialOptional.get();
}
String name = tr("unset_relto_book_item_name");
String lore = tr("unset_relto_book_item_lore");
ItemStack book = ItemStackBuilder
.builder(material)
.setLore(lore.split("<newline>"))
.setName(name)
.build();
book = BookUtil.makeUnsetReltoBook(book);
return book;
});
private Function<Location, ItemStack> createBookFunction;
/**
* @param createBookFunction The function to create a book itemstack for a location
*/
BookType(Function<Location, ItemStack> createBookFunction) {
this.createBookFunction = createBookFunction;
}
/**
* Creates a book item
*
* @param location The location of the book
*
* @return The book item
*/
ItemStack create(Location location) {
return createBookFunction.apply(location);
}
}
package me.ialistannen.mystlinkingbooks.data;
import com.darkblade12.particleeffect.ParticleEffect;
import me.ialistannen.mystlinkingbooks.util.LocationSerializable;
import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* A linking book
*/
public class DescriptiveBookBlock implements ConfigurationSerializable {
private Location linkingLocation;
/**
* @param map The map for {@link ConfigurationSerializable}
*/
@SuppressWarnings("unused") // it isn't. Just not called by my code, but by the serialization.
public DescriptiveBookBlock(Map<String, Object> map) {
this(((LocationSerializable) map.get("linkedLocation")).toLocation());
}
/**
* A book that links to a location
*
* @param linkingLocation The location to link to
*/
public DescriptiveBookBlock(Location linkingLocation) {
this.linkingLocation = linkingLocation;
}
/**
* The place this book links to
*
* @return The linked location
*/
public Location getLinkingLocation() {
return linkingLocation.clone();
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("linkedLocation", new LocationSerializable(linkingLocation));
return map;
}
/**
* Shows the particles to distinguish it from a normal block
*
* @param location The location to play them at
*/
static void showCustomParticles(Location location) {
location.add(0.5, 0, 0.5);
//showOneCustomParticle(location);
double height = 0;
for (; height < 2.3; height += 0.2) {
for (double theta = 0; theta < (2 * Math.PI) * 1; theta += Math.PI / 4) {
double x = Math.cos(theta) * 1.2;
double z = Math.sin(theta) * 1.2;
Location center = location.clone().add(x, height, z);
ParticleEffect.FIREWORKS_SPARK.display(0, 0, 0, 0.0f, 1, center, 50);
}
}
for (double rad = 1.2; rad < 2 && rad >= 0; rad -= 0.2) {
for (double theta = 0; theta < (2 * Math.PI) * 1; theta += Math.PI / 4) {
double x = Math.cos(theta) * rad;
double z = Math.sin(theta) * rad;
Location center = location.clone().add(x, height, z);
ParticleEffect.WATER_WAKE.display(0, 0, 0, 0.0f, 1, center, 50);
}
}
}
private static void showOneCustomParticle(Location location) {
Random rand = ThreadLocalRandom.current();
if (rand.nextDouble() < 0.2) {
return;
}
for (int i = 0; i < 3; ++i) {
int xRandom = rand.nextInt(2) * 2 - 1;
int zRandom = rand.nextInt(2) * 2 - 1;
double xOff = 0.25D * xRandom;
double yOff = rand.nextFloat() * 2;
double zOff = 0.25D * zRandom;
float offsetX = (rand.nextFloat() * xRandom);
float offsetY = (rand.nextFloat() - 0.5F) * 0.125F;
float offsetZ = rand.nextFloat() * zRandom;
Location center = location.clone().add(xOff, yOff, zOff);
ParticleEffect.PORTAL.display(
offsetX, offsetY, offsetZ,
rand.nextFloat(), rand.nextInt(3) + 1,
center, 50);
}
}
}
package me.ialistannen.mystlinkingbooks.data;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.util.LocationSerializable;
import org.bukkit.Location;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* A manager for all the placed descriptive books
*/
public class PlacedDescriptiveBookManager {
private Map<Location, DescriptiveBookBlock> bookBlockMap = new HashMap<>();
private Path saveFile;
/**
* The path to save it to
*
* @param saveFile The path to save it to
*/
private PlacedDescriptiveBookManager(Path saveFile) {
this.saveFile = saveFile;
new BukkitRunnable() {
@Override
public void run() {
bookBlockMap.keySet().forEach(location -> DescriptiveBookBlock.showCustomParticles(location.clone()));
}
}.runTaskTimer(MystLinkingBooks.getInstance(), 0, 10);
}
/**
* Adds a {@link DescriptiveBookBlock}
*
* @param location The location the block is at
* @param bookBlock The {@link DescriptiveBookBlock} to add
*/
public void setBook(Location location, DescriptiveBookBlock bookBlock) {
bookBlockMap.put(location, bookBlock);
}
/**
* Removes the descriptive book at the given location
*
* @param location The location at which the book is
*/
public void removeBook(Location location) {
bookBlockMap.remove(location);
}
/**
* Checks if there is a {@link DescriptiveBookBlock} at a location
*
* @param location The {@link Location} to check
*
* @return True if there is a {@link DescriptiveBookBlock} at the location
*/
public boolean hasBook(Location location) {
return bookBlockMap.containsKey(location);
}
/**
* Returns the {@link DescriptiveBookBlock} at a given location
*
* @param location The Location to check
*
* @return The {@link DescriptiveBookBlock} or an empty Optional
*/
public Optional<DescriptiveBookBlock> getBook(Location location) {
return Optional.ofNullable(bookBlockMap.get(location));
}
/**
* Saves the manager
*
* @return True if it saved successfully
*/
public boolean save() {
YamlConfiguration config = new YamlConfiguration();
List<LocationSerializable> locations = new ArrayList<>();
List<DescriptiveBookBlock> books = new ArrayList<>();
for (Map.Entry<Location, DescriptiveBookBlock> entry : bookBlockMap.entrySet()) {
locations.add(new LocationSerializable(entry.getKey()));
books.add(entry.getValue());
}
config.set("books", books);
config.set("locations", locations);
try {
config.save(saveFile.toFile());
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static PlacedDescriptiveBookManager loadOrGetNew(Path file) {
PlacedDescriptiveBookManager bookManager = new PlacedDescriptiveBookManager(file);
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file.toFile());
if (!configuration.contains("books") || !configuration.contains("locations")) {
return bookManager;
}
@SuppressWarnings("unchecked")
List<DescriptiveBookBlock> books = (List<DescriptiveBookBlock>) configuration.getList("books");
@SuppressWarnings("unchecked")
List<Location> locations = ((List<LocationSerializable>) configuration.getList("locations"))
.stream()
.map(LocationSerializable::toLocation)
.collect(Collectors.toList());
for (int i = 0, booksSize = books.size(); i < booksSize; i++) {
DescriptiveBookBlock book = books.get(i);
bookManager.setBook(locations.get(i), book);
}
return bookManager;
}
}
package me.ialistannen.mystlinkingbooks.data;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.util.LocationSerializable;
import org.bukkit.Location;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
* Manages the Relto (home) books
*/
public class ReltoBookManager {
private Map<UUID, Location> uuidHomeMap = new HashMap<>();
private Path saveFile;
/**
* @param saveFile The file to save it under
*/
public ReltoBookManager(Path saveFile) {
this.saveFile = saveFile;
}
/**
* Sets the home for the given UUID to the given location
*
* @param uuid The UUID
* @param location The home location
*
* @return True if the home as added. False if it was already in the manager
*/
public boolean setHome(UUID uuid, Location location) {
if (containsUUID(uuid)) {
return false;
}
uuidHomeMap.put(uuid, location);
return true;
}
/**
* Checks whether the given uuid has already set a home
*
* @param id The UUID to check
*
* @return True if the given UUID has already registered a home
*/
public boolean containsUUID(UUID id) {
return uuidHomeMap.containsKey(id);
}
/**
* Returns the home for the given UUID
*
* @param uuid The UUID to get the home for
*
* @return The home for the given UUID
*/
public Optional<Location> getHome(UUID uuid) {
return Optional.ofNullable(uuidHomeMap.get(uuid));
}
/**
* Removes a UUID
*
* @param uuid The UUID to remove
*/
public void removeHome(UUID uuid) {
uuidHomeMap.remove(uuid);
}
/**
* Saves all the homes
*
* @return True if the save was successful
*/
public boolean save() {
List<String> uuids = new ArrayList<>();
List<LocationSerializable> locations = new ArrayList<>();
for (Map.Entry<UUID, Location> entry : uuidHomeMap.entrySet()) {
uuids.add(entry.getKey().toString());
locations.add(new LocationSerializable(entry.getValue()));
}
YamlConfiguration configuration = new YamlConfiguration();
configuration.set("UUIDs", uuids);
configuration.set("Locations", locations);
try {
configuration.save(saveFile.toFile());
return true;
} catch (IOException e) {
MystLinkingBooks.getInstance().getLogger().log(Level.SEVERE, "Couldn't save homes", e);
return false;
}
}
/**
* Loads the file, or if not found, creates a new {@link ReltoBookManager}
*
* @param saveFile The file to load from and save to
*
* @return The loaded {@link ReltoBookManager} or a new one
*/
public static ReltoBookManager loadOrGetNew(Path saveFile) {
ReltoBookManager bookManager = new ReltoBookManager(saveFile);
if (Files.notExists(saveFile)) {
return bookManager;
}
YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(saveFile.toFile());
if (!yamlConfiguration.contains("UUIDs") || !yamlConfiguration.contains("Locations")) {
return bookManager;
}
List<UUID> uuids = ((List<String>) yamlConfiguration.getList("UUIDs"))
.stream()
.map(UUID::fromString)
.collect(Collectors.toList());
List<Location> locations = ((List<LocationSerializable>) yamlConfiguration.getList("Locations"))
.stream()
.map(LocationSerializable::toLocation)
.collect(Collectors.toList());
for (int i = 0, uuidsSize = uuids.size(); i < uuidsSize; i++) {
UUID uuid = uuids.get(i);
bookManager.setHome(uuid, locations.get(i));
}
return bookManager;
}
}
package me.ialistannen.mystlinkingbooks.event;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.data.BookItemFactory;
import me.ialistannen.mystlinkingbooks.data.BookType;
import me.ialistannen.mystlinkingbooks.data.DescriptiveBookBlock;
import me.ialistannen.mystlinkingbooks.util.BookUtil;
import me.ialistannen.mystlinkingbooks.util.DurationParser;
import me.ialistannen.mystlinkingbooks.util.Util;
import me.ialistannen.mystlinkingbooks.util.WarmupUtil;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.ItemStack;
import java.time.Duration;
import java.util.Optional;
import static me.ialistannen.mystlinkingbooks.util.Util.tr;
/**
* A book listener, to listen for placement and book usage
*/
public class BookListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onDescriptiveBookPlace(BlockPlaceEvent event) {
if (!event.canBuild()) {
return;
}
if (!BookUtil.isDescriptiveBook(event.getItemInHand())) {
return;
}
MystLinkingBooks.getInstance().getPlacedDescriptiveBookManager().setBook(event.getBlock().getLocation(),
BookUtil.descriptiveBookToBlock(event.getItemInHand()));
Location blockLoc = event.getBlock().getLocation();
event.getPlayer().sendMessage(
tr(
"placed_descriptive_book",
blockLoc.getX(), blockLoc.getY(), blockLoc.getZ(), blockLoc.getWorld().getName()
)
);
}
@EventHandler(ignoreCancelled = true)
public void onDescriptiveBookBreak(BlockBreakEvent event) {
if (MystLinkingBooks.getInstance().getPlacedDescriptiveBookManager().hasBook(event.getBlock().getLocation())) {
Location blockLoc = event.getBlock().getLocation();
event.getPlayer().sendMessage(
tr(
"destroyed_descriptive_book",
blockLoc.getX(), blockLoc.getY(), blockLoc.getZ(), blockLoc.getWorld().getName()
)
);
MystLinkingBooks.getInstance().getPlacedDescriptiveBookManager().removeBook(blockLoc);
}
}
@EventHandler
public void onCreateUnfinishedLinkingBook(PlayerInteractEvent e) {
if (!e.hasBlock() || e.getAction() == Action.LEFT_CLICK_BLOCK) {
return;
}
Optional<DescriptiveBookBlock> descriptiveBookBlock = MystLinkingBooks.getInstance()
.getPlacedDescriptiveBookManager()
.getBook(e.getClickedBlock().getLocation());
if (!descriptiveBookBlock.isPresent()) {
return;
}
Location linkedWorldLocation = descriptiveBookBlock.get().getLinkingLocation();
// Now, separate the creating of an unfinished linking book and just travelling
if (!e.hasItem()) {
teleportPlayerByDescriptiveBook(e.getPlayer(), linkedWorldLocation);
return;
}
Optional<Material> baseMaterialOptional =
Util.matchMaterial(MystLinkingBooks.getInstance().getConfig().getString("linking_book_base_material"));
if (!baseMaterialOptional.isPresent() || (baseMaterialOptional.get() != e.getMaterial())) {
return;
}
ItemStack book = BookItemFactory.create(linkedWorldLocation, BookType.UNFINISHED_LINKING_BOOK);
Util.setItemInHand(e.getPlayer(), book);
e.getPlayer().sendMessage(tr("gave_unfinished_linking_book",
linkedWorldLocation.getX(), linkedWorldLocation.getY(), linkedWorldLocation.getZ(),
linkedWorldLocation.getWorld().getName()));
e.setCancelled(true);
teleportPlayerByDescriptiveBook(e.getPlayer(), linkedWorldLocation);
}
private void teleportPlayerByDescriptiveBook(Player player, Location destination) {
WarmupUtil warmupUtil = MystLinkingBooks.getInstance().getWarmupUtil();
if (warmupUtil.containsUUID(player.getUniqueId())) {
player.sendMessage(tr("already_a_warmup_running"));
return;
}
String warmupString = MystLinkingBooks.getInstance().getConfig().getString("linking book warmup");
long warmupMillis = DurationParser.parseDuration(warmupString);
player.sendMessage(tr("warmup_started",
DurationFormatUtils.formatDurationWords(warmupMillis, true, true)));
warmupUtil.add(Duration.ofMillis(warmupMillis), () -> {
player.teleport(destination);
player.sendMessage(tr("descriptive_book_teleported_you",
destination.getX(), destination.getY(), destination.getZ(), destination.getWorld().getName()));
}, player.getUniqueId());
}
@EventHandler
public void onUseUnfinishedLinkingBooks(PlayerInteractEvent e) {
if (!e.hasItem()) {
return;
}
// was cancelled, probably by the warmup
// RIGHT_CLICK_AIR is cancelled always, so special handling
if(e.isCancelled() && e.getAction() != Action.RIGHT_CLICK_AIR) {
return;
}
if (!BookUtil.isUnfinishedLinkingBook(e.getItem())) {
return;
}
Location location = e.getPlayer().getLocation();
ItemStack book = BookItemFactory.create(location, BookType.FINISHED_LINKING);
Util.setItemInHand(e.getPlayer(), book);
e.getPlayer().sendMessage(tr("gave_you_a_linking_book",
location.getX(), location.getY(), location.getZ(), location.getWorld().getName()));
}
@EventHandler
public void onUseLinkingBook(PlayerInteractEvent e) {
if (!e.hasItem()
|| (e.getAction() != Action.RIGHT_CLICK_AIR && e.getAction() != Action.RIGHT_CLICK_BLOCK)) {
return;
}
if (!BookUtil.isFinishedLinkingBook(e.getItem())) {
return;
}
Location linkingBooksLocation = BookUtil.getFinishedLinkingBooksLocation(e.getItem());
WarmupUtil warmupUtil = MystLinkingBooks.getInstance().getWarmupUtil();
if (warmupUtil.containsUUID(e.getPlayer().getUniqueId())) {
e.getPlayer().sendMessage(tr("already_a_warmup_running"));
return;
}
String warmupString = MystLinkingBooks.getInstance().getConfig().getString("linking book warmup");
long warmupMillis = DurationParser.parseDuration(warmupString);
e.getPlayer().sendMessage(tr("warmup_started",
DurationFormatUtils.formatDurationWords(warmupMillis, true, true)));
warmupUtil.add(Duration.ofMillis(warmupMillis),
() -> teleportPlayerLinkingBook(e.getPlayer(), linkingBooksLocation,
e.getItem()),
e.getPlayer().getUniqueId());
}
/**
* Teleports a player and drops the linking book
*
* @param player The player to teleport
* @param targetLocation The place to teleport him to
* @param itemStack The linking book he used
*/
private void teleportPlayerLinkingBook(Player player, Location targetLocation,
ItemStack itemStack) {
if (player.getInventory().contains(itemStack)) {
player.getInventory().remove(itemStack);
player.getWorld().dropItem(player.getLocation(), itemStack);
}
player.teleport(targetLocation);
player.sendMessage(tr("linking_book_teleported_you",
targetLocation.getX(), targetLocation.getY(), targetLocation.getZ(),
targetLocation.getWorld().getName()));
player.setNoDamageTicks((int) DurationParser.parseDurationToTicks(
MystLinkingBooks.getInstance().getConfig().getString("no_damage_ticks_after_teleport")
));
}
@EventHandler(ignoreCancelled = true)
public void onSwitchingWorldsClearUnfinishedBooks(PlayerTeleportEvent e) {
// just if he switches worlds
if (e.getFrom().getWorld().equals(e.getTo().getWorld())) {
return;
}
boolean removedUnfinishedbook = false;
for (ItemStack itemStack : e.getPlayer().getInventory()) {
if (BookUtil.isUnfinishedLinkingBook(itemStack)) {
Location unfinishedLinkingBooksLocation = BookUtil.getUnfinishedLinkingBooksLocation(itemStack);
// he is just arriving in the world for the book
if(unfinishedLinkingBooksLocation.getWorld().equals(e.getTo().getWorld())) {
continue;
}
e.getPlayer().getInventory().remove(itemStack);
removedUnfinishedbook = true;
}
}
if (removedUnfinishedbook) {
e.getPlayer().sendMessage(tr("world_switch_removed_unfinished_book",
e.getFrom().getWorld().getName(), e.getTo().getWorld().getName()));
}
}
@EventHandler(ignoreCancelled = true)
public void onOpeningDescriptiveBookInventory(PlayerInteractEvent e) {
if (e.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (MystLinkingBooks.getInstance().getPlacedDescriptiveBookManager()
.hasBook(e.getClickedBlock().getLocation())) {
e.setCancelled(true);
}
}
}
package me.ialistannen.mystlinkingbooks.event;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import me.ialistannen.mystlinkingbooks.util.BookUtil;
import me.ialistannen.mystlinkingbooks.util.DurationParser;
import me.ialistannen.mystlinkingbooks.util.InvitationManager;
import me.ialistannen.mystlinkingbooks.util.Util;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import java.time.Duration;
import java.util.Optional;
/**
* A listener for Relto books
*/
public class ReltoListener implements Listener {
@EventHandler
public void onUse(PlayerInteractEvent e) {
if (!e.hasItem()) {
return;
}
if (e.getPlayer().isSneaking()) {
return;
}
if (!BookUtil.isReltoBook(e.getItem())) {
return;
}
Player player = e.getPlayer();
Optional<Location> home = MystLinkingBooks.getInstance().getReltoBookManager().getHome(player.getUniqueId());
if (!home.isPresent()) {
player.sendMessage(Util.tr("no_home_set"));
return;
}
Location destination = home.get();
Util.startReltoTeleportWithWarmup(player, destination);
}
@EventHandler(priority = EventPriority.LOW)
public void onRightClickOther(PlayerInteractEntityEvent event) {
if (!(event.getRightClicked() instanceof Player)) {
return;
}
// prevent accidentally inviting others
if (!event.getPlayer().isSneaking()) {
return;
}
ItemStack itemInHand = Util.getItemInHand(event.getPlayer());
if (itemInHand == null || itemInHand.getType() == Material.AIR) {
return;
}
if (!BookUtil.isReltoBook(itemInHand)) {
return;
}
Optional<Location> home = MystLinkingBooks.getInstance().getReltoBookManager()
.getHome(event.getPlayer().getUniqueId());
if (!home.isPresent()) {
event.getPlayer().sendMessage(Util.tr("no_home_set"));
return;
}
InvitationManager invitationManager = MystLinkingBooks.getInstance().getInvitationManager();
// remove any previous invitations from the sender
if (invitationManager.containsSender(event.getPlayer().getUniqueId())) {
invitationManager.getInvitationRecipient(event.getPlayer().getUniqueId()).ifPresent(uuid -> {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
player.sendMessage(Util.tr("invitation_for_you_cancelled", event.getPlayer().getDisplayName()));
}
});
invitationManager.removeInvitationFromSender(event.getPlayer().getUniqueId());
event.getPlayer().sendMessage(Util.tr("cancelled_invitation_due_to_new"));
}
invitationManager.addInvitation(event.getPlayer().getUniqueId(),
event.getRightClicked().getUniqueId(),
Duration.ofMillis(DurationParser.parseDuration(
MystLinkingBooks.getInstance().getConfig().getString("relto_invitation_timeout"))
));
event.getPlayer().sendMessage(
Util.tr("sent_invitation", ((Player) event.getRightClicked()).getDisplayName()));
event.getRightClicked().sendMessage(Util.tr("got_invitation", event.getPlayer().getDisplayName()));
}
}
package me.ialistannen.mystlinkingbooks;
import me.ialistannen.languageSystem.I18N;
import me.ialistannen.mystlinkingbooks.commands.CommandAge;
import me.ialistannen.mystlinkingbooks.commands.CommandRelto;
import me.ialistannen.mystlinkingbooks.data.BookItemFactory;
import me.ialistannen.mystlinkingbooks.data.BookType;
import me.ialistannen.mystlinkingbooks.data.DescriptiveBookBlock;
import me.ialistannen.mystlinkingbooks.data.PlacedDescriptiveBookManager;
import me.ialistannen.mystlinkingbooks.data.ReltoBookManager;
import me.ialistannen.mystlinkingbooks.event.BookListener;
import me.ialistannen.mystlinkingbooks.event.ReltoListener;
import me.ialistannen.mystlinkingbooks.util.InvitationManager;
import me.ialistannen.mystlinkingbooks.util.LocationSerializable;
import me.ialistannen.mystlinkingbooks.util.WarmupUtil;
import me.ialistannen.tree_command_system.CommandTreeCommandListener;
import me.ialistannen.tree_command_system.CommandTreeManager;
import me.ialistannen.tree_command_system.CommandTreeTabCompleteListener;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.logging.Level;
/**
* The main class for this plugin
*/
public class MystLinkingBooks extends JavaPlugin {
private static MystLinkingBooks instance;
private I18N language;
private CommandTreeManager treeManager;
private PlacedDescriptiveBookManager placedDescriptiveBookManager;
private WarmupUtil warmupUtil;
private ReltoBookManager reltoBookManager;
private InvitationManager invitationManager;
public void onEnable() {
instance = this;
initializeConfigSerializableClasses();
placedDescriptiveBookManager = PlacedDescriptiveBookManager
.loadOrGetNew(getDataFolder().toPath().resolve("placed_books.yml"));
reltoBookManager = ReltoBookManager.loadOrGetNew(getDataFolder().toPath().resolve("relto_books.yml"));
warmupUtil = new WarmupUtil();
invitationManager = new InvitationManager();
{
Path directory = getDataFolder().toPath().resolve("language");
if (Files.notExists(directory)) {
try {
Files.createDirectories(directory);
} catch (IOException e) {
getLogger().log(Level.WARNING, "Can't create language folder", e);
}
}
I18N.copyDefaultFiles("language", directory, false, getClass());
language = new I18N("language", directory, Locale.ENGLISH,
getLogger(), getClassLoader(), "Messages");
initializeCommands();
}
Bukkit.getPluginManager().registerEvents(new BookListener(), this);
Bukkit.getPluginManager().registerEvents(new ReltoListener(), this);
Bukkit.getPluginManager().registerEvents(warmupUtil, this);
{
ShapelessRecipe reltoBook = new ShapelessRecipe(BookItemFactory.create(null, BookType.UNSET_RELTO));
reltoBook.addIngredient(1, Material.GLOWSTONE_DUST);
reltoBook.addIngredient(1, Material.BOOK_AND_QUILL);
Bukkit.addRecipe(reltoBook);
}
}
private void initializeCommands() {
treeManager = new CommandTreeManager();
treeManager.registerChild(treeManager.getRoot(), new CommandAge());
treeManager.registerChild(treeManager.getRoot(), new CommandRelto());
getCommand("kor").setExecutor(new CommandTreeCommandListener(treeManager, language));
getCommand("kor").setTabCompleter(new CommandTreeTabCompleteListener(treeManager, true, true));
}
@Override
public void onDisable() {
getPlacedDescriptiveBookManager().save();
getReltoBookManager().save();
// prevent the old instance from still being around.
instance = null;
}
private void initializeConfigSerializableClasses() {
ConfigurationSerialization.registerClass(DescriptiveBookBlock.class);
ConfigurationSerialization.registerClass(LocationSerializable.class);
}
/**
* Returns the invitation manager
*
* @return The {@link InvitationManager}
*/
public InvitationManager getInvitationManager() {
return invitationManager;
}
/**
* Returns the manager for the Relto (Home) books
*
* @return The {@link ReltoBookManager}
*/
public ReltoBookManager getReltoBookManager() {
return reltoBookManager;
}
/**
* Returns the warmup util
*
* @return The {@link WarmupUtil}
*/
public WarmupUtil getWarmupUtil() {
return warmupUtil;
}
/**
* The {@link PlacedDescriptiveBookManager} with all the placed books
*
* @return The {@link PlacedDescriptiveBookManager}
*/
public PlacedDescriptiveBookManager getPlacedDescriptiveBookManager() {
return placedDescriptiveBookManager;
}
/**
* @return The {@link I18N}
*/
public I18N getLanguage() {
return language;
}
/**
* Returns the plugins instance
*
* @return The plugin instance
*/
public static MystLinkingBooks getInstance() {
return instance;
}
}
package me.ialistannen.mystlinkingbooks.util;
import me.ialistannen.mystlinkingbooks.data.BookItemFactory;
import me.ialistannen.mystlinkingbooks.data.BookType;
import me.ialistannen.mystlinkingbooks.data.DescriptiveBookBlock;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
/**
* A class to check item stacks whether they are book ones
*/
public class BookUtil {
private static final String NBT_KEY_DESCRIPTIVE_BOOK = "descriptiveBook";
private static final String NBT_KEY_FINISHED_LINKING_BOOK = "linkingBook";
private static final String NBT_KEY_UNFINISHED_LINKING_BOOK = "unfinished linkingBook";
private static final String NBT_KEY_UNSET_RELTO_BOOK = "unset reltoBook";
private static final String NBT_KEY_RELTO_BOOK = "reltoBook";
/**
* Checks if this is a DescriptiveBook
*
* @param itemStack The itemStack to check
*
* @return True if this item is a descriptive book
*/
public static boolean isDescriptiveBook(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) {
return false;
}
Object nbtTag = NbtUtil.getNBTTagCompound(itemStack);
return NbtUtil.hasKey(nbtTag, NBT_KEY_DESCRIPTIVE_BOOK);
}
/**
* Checks if this is a linking book
*
* @param itemStack The itemStack to check
*
* @return True if this item is a linking book
*/
public static boolean isFinishedLinkingBook(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) {
return false;
}
Object nbtTag = NbtUtil.getNBTTagCompound(itemStack);
return NbtUtil.hasKey(nbtTag, NBT_KEY_FINISHED_LINKING_BOOK);
}
/**
* Checks if this is an unfinished linking book
*
* @param itemStack The itemStack to check
*
* @return True if this item is an unfinished linking book
*/
public static boolean isUnfinishedLinkingBook(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) {
return false;
}
Object nbtTag = NbtUtil.getNBTTagCompound(itemStack);
return NbtUtil.hasKey(nbtTag, NBT_KEY_UNFINISHED_LINKING_BOOK);
}
/**
* Checks if this is an unset relto book
*
* @param itemStack The itemStack to check
*
* @return True if this item is an unset relto book
*/
public static boolean isUnsetReltoBook(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) {
return false;
}
Object nbtTag = NbtUtil.getNBTTagCompound(itemStack);
return NbtUtil.hasKey(nbtTag, NBT_KEY_UNSET_RELTO_BOOK);
}
/**
* Checks if this is a relto book
*
* @param itemStack The itemStack to check
*
* @return True if this item is a relto book
*/
public static boolean isReltoBook(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) {
return false;
}
Object nbtTag = NbtUtil.getNBTTagCompound(itemStack);
return NbtUtil.hasKey(nbtTag, NBT_KEY_RELTO_BOOK);
}
/**
* <b>You shouldn't call this method. {@link BookType#create(Location)} will.</b>
* <br>
* Makes the ItemStack a linking book by adding the Location to it's NBT
*
* @param location The Location
* @param baseItem The BaseItem to modify. Should be obtained from
* {@link BookItemFactory#create(Location, BookType)}
*
* @return The modified {@link ItemStack}
*/
public static ItemStack makeFinishedLinkingBook(Location location, ItemStack baseItem) {
return saveLocationUnderKey(location, baseItem, NBT_KEY_FINISHED_LINKING_BOOK);
}
/**
* <b>You shouldn't call this method. {@link BookType#create(Location)} will.</b>
* <br>
* Makes the ItemStack an unfinished linking book by adding the Location to it's NBT
*
* @param location The Location
* @param baseItem The BaseItem to modify. Should be obtained from
* {@link BookItemFactory#create(Location, BookType)}
*
* @return The modified {@link ItemStack}
*/
public static ItemStack makeUnfinishedLinkingBook(Location location, ItemStack baseItem) {
return saveLocationUnderKey(location, baseItem, NBT_KEY_UNFINISHED_LINKING_BOOK);
}
/**
* <b>You shouldn't call this method. {@link BookType#create(Location)} will.</b>
* <br>
* Makes the ItemStack a descriptive book by adding the Location to it's NBT
*
* @param location The Location
* @param baseItem The BaseItem to modify. Should be obtained from
* {@link BookItemFactory#create(Location, BookType)}
*
* @return The modified {@link ItemStack}
*/
public static ItemStack makeDescriptiveBook(Location location, ItemStack baseItem) {
return saveLocationUnderKey(location, baseItem, NBT_KEY_DESCRIPTIVE_BOOK);
}
/**
* <b>You shouldn't call this method. {@link BookType#create(Location)} will.</b>
* <br>
* Makes the ItemStack a unset relto book by adding a marker NBT tag
*
* @param baseItem The BaseItem to modify. Should be obtained from
* {@link BookItemFactory#create(Location, BookType)}
*
* @return The modified {@link ItemStack}
*/
public static ItemStack makeUnsetReltoBook(ItemStack baseItem) {
return addEmptyTag(baseItem, NBT_KEY_UNSET_RELTO_BOOK);
}
/**
* <b>You shouldn't call this method. {@link BookType#create(Location)} will.</b>
* <br>
* Makes the ItemStack a relto book by adding the Location to it's NBT
*
* @param baseItem The BaseItem to modify. Should be obtained from
* {@link BookItemFactory#create(Location, BookType)}
*
* @return The modified {@link ItemStack}
*/
public static ItemStack makeReltoBook(ItemStack baseItem) {
return addEmptyTag(baseItem, NBT_KEY_RELTO_BOOK);
}
/**
* Adds an empty NBT tag to the item
*
* @param item The item to add it to
* @param key The key to save it under
*
* @return The modified {@link ItemStack}
*/
private static ItemStack addEmptyTag(ItemStack item, String key) {
Object baseNbtTag = NbtUtil.getNBTTagCompound(item);
Object newNbtTag = NbtUtil.newNbtTagCompound();
NbtUtil.setTag(baseNbtTag, key, newNbtTag);
return NbtUtil.applyNbtTag(baseNbtTag, item);
}
/**
* Adds the location to the items NBT
*
* @param location The Location to save
* @param baseItem The item to modify
* @param nbtKeyDescriptiveBook The key to save the NBT data under
*
* @return The modified itemstack
*/
private static ItemStack saveLocationUnderKey(Location location, ItemStack baseItem,
String nbtKeyDescriptiveBook) {
Object baseNbtTag = NbtUtil.getNBTTagCompound(baseItem);
Object newNbtTag = NbtUtil.newNbtTagCompound();
NbtUtil.setTag(newNbtTag, "setDouble", "x", double.class, location.getX());
NbtUtil.setTag(newNbtTag, "setDouble", "y", double.class, location.getY());
NbtUtil.setTag(newNbtTag, "setDouble", "z", double.class, location.getZ());
NbtUtil.setTag(newNbtTag, "setFloat", "yaw", float.class, location.getYaw());
NbtUtil.setTag(newNbtTag, "setFloat", "pitch", float.class, location.getPitch());
NbtUtil.setTag(newNbtTag, "setString", "world", String.class, location.getWorld().getName());
NbtUtil.setTag(baseNbtTag, nbtKeyDescriptiveBook, newNbtTag);
return NbtUtil.applyNbtTag(baseNbtTag, baseItem);
}
/**
* Converts an ItemStack DescriptiveBook to a Block one
*
* @param itemStack The ItemStack to convert
*
* @return The {@link DescriptiveBookBlock} for the given {@link ItemStack}
*/
public static DescriptiveBookBlock descriptiveBookToBlock(ItemStack itemStack) {
if (!isDescriptiveBook(itemStack)) {
throw new IllegalArgumentException("The provided book is no descriptive book.");
}
return new DescriptiveBookBlock(getLocationFromNBT(itemStack, NBT_KEY_DESCRIPTIVE_BOOK));
}
/**
* Gets the linking location from a linking book
*
* @param itemStack The ItemStack to convert
*
* @return The {@link Location} for the given {@link ItemStack}
*/
public static Location getFinishedLinkingBooksLocation(ItemStack itemStack) {
if (!isFinishedLinkingBook(itemStack)) {
throw new IllegalArgumentException("The provided book is no finished linking book.");
}
return getLocationFromNBT(itemStack, NBT_KEY_FINISHED_LINKING_BOOK);
}
/**
* Gets the linking location (=> The descriptive book location) from an unfinished linking book
*
* @param itemStack The ItemStack to convert
*
* @return The {@link Location} for the given {@link ItemStack}
*/
public static Location getUnfinishedLinkingBooksLocation(ItemStack itemStack) {
if (!isUnfinishedLinkingBook(itemStack)) {
throw new IllegalArgumentException("The provided book is no unfinished linking book.");
}
return getLocationFromNBT(itemStack, NBT_KEY_UNFINISHED_LINKING_BOOK);
}
/**
* Reads the location from the item
*
* @param itemStack The {@link ItemStack} to read from
*
* @return The read Location
*/
private static Location getLocationFromNBT(ItemStack itemStack, String nbtKey) {
Object nbtTag = NbtUtil.getNBTTagCompound(itemStack);
nbtTag = NbtUtil.getTag(nbtTag, nbtKey);
double x = (double) NbtUtil.getTag(nbtTag, "getDouble", "x"),
y = (double) NbtUtil.getTag(nbtTag, "getDouble", "y"),
z = (double) NbtUtil.getTag(nbtTag, "getDouble", "z");
float yaw = (float) NbtUtil.getTag(nbtTag, "getFloat", "yaw"),
pitch = (float) NbtUtil.getTag(nbtTag, "getFloat", "pitch");
String world = (String) NbtUtil.getTag(nbtTag, "getString", "world");
return new Location(Bukkit.getWorld(world), x, y, z, yaw, pitch);
}
}
package me.ialistannen.mystlinkingbooks.util;
import java.util.concurrent.TimeUnit;
/**
* Allows the parsing of a duration
*/
public class DurationParser {
/**
* Small recursive parser by I Al Istannen. Bug me about it, I know it is bad!
*
* Format:
* <br>"xxS" ==> milliseconds
* <br>"xxt" ==> ticks
* <br>"xxs" ==> seconds
* <br>"xxm" ==> minutes
* <br>"xxh" ==> hours
* <br>"xxd" ==> days
*
* @param input The input string
* @return The time in milliseconds
* @throws RuntimeException If an error occurred while parsing.
*/
public static long parseDurationToTicks(String input) throws RuntimeException {
return parseDuration(input) / 50;
}
/**
* Small recursive parser by I Al Istannen. Bug me about it, I know it is bad!
*
* Format:
* <br>"xxS" ==> milliseconds
* <br>"xxt" ==> ticks
* <br>"xxs" ==> seconds
* <br>"xxm" ==> minutes
* <br>"xxh" ==> hours
* <br>"xxd" ==> days
*
* @param input The input string
* @return The time in milliseconds
* @throws RuntimeException If an error occurred while parsing.
*/
public static long parseDuration(String input) throws RuntimeException {
return new Object() {
private int pos = -1, ch;
/**
* Goes to the next char
*/
private void nextChar() {
ch = ++pos < input.length() ? input.charAt(pos) : -1;
}
/**
* Eats a char
*
* @param charToEat The chat to eat
* @return True if the char was found and eaten
*/
private boolean eat(int charToEat) {
while(ch == ' ') {
nextChar();
}
if(ch == charToEat) {
nextChar();
return true;
}
return false;
}
public long parse() {
nextChar();
return parsePart();
}
private long parsePart() {
long number = parseNumber();
while(ch != -1) {
number += parseNumber();
}
return number;
}
private long parseNumber() {
while(ch == ' ') {
nextChar();
}
long number;
int start = pos;
if(ch >= '0' && ch <= '9') {
while(ch >= '0' && ch <= '9') {
nextChar();
}
number = Long.parseLong(input.substring(start, pos));
//noinspection StatementWithEmptyBody
if(eat('S')) {
// well, it is already in ms
}
else if(eat('s')) {
number *= 1000;
}
else if(eat('m')) {
number = TimeUnit.MINUTES.toMillis(number);
}
else if(eat('h')) {
number = TimeUnit.HOURS.toMillis(number);
}
else if(eat('d')) {
number = TimeUnit.DAYS.toMillis(number);
}
else if(eat('t')) {
number *= 50;
}
else {
throw new RuntimeException("No unit given near pos " + pos + " starting at " + start);
}
}
else {
throw new RuntimeException("Unexpected char at pos " + pos + " " + ch + " '" + (char) ch + "'");
}
return number;
}
}.parse();
}
}
package me.ialistannen.mystlinkingbooks.util;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* Manages the relto invitations
*/
public class InvitationManager {
private Map<UUID, InvitationObject> recipientInvitationMap = new HashMap<>();
private BukkitRunnable runner;
private void suggestStartingRunner() {
if (runner == null) {
runner = new BukkitRunnable() {
@Override
public void run() {
for (Iterator<InvitationObject> iterator = recipientInvitationMap.values().iterator(); iterator
.hasNext(); ) {
InvitationObject invitationObject = iterator.next();
if(invitationObject.isTimedOut()) {
getPlayer(invitationObject.getRecipient()).ifPresent(player -> {
player.sendMessage(Util.tr("invitation_timed_out"));
});
getPlayer(invitationObject.getSender()).ifPresent(player -> {
player.sendMessage(Util.tr("invitation_timed_out"));
});
iterator.remove();
}
}
}
};
runner.runTaskTimer(MystLinkingBooks.getInstance(), 0, 10);
}
}
private Optional<Player> getPlayer(UUID uuid) {
return Optional.ofNullable(Bukkit.getPlayer(uuid));
}
private void suggestStoppingRunner() {
if(runner == null) {
return;
}
if(!recipientInvitationMap.isEmpty()) {
return;
}
if (Bukkit.getScheduler().isQueued(runner.getTaskId())
|| Bukkit.getScheduler().isCurrentlyRunning(runner.getTaskId())) {
runner.cancel();
runner = null;
}
}
/**
* Adds the invitation, if possible
*
* @param sender The sender
* @param recipient The recipient
* @param duration The duration of the invitation
*
* @return True if the invitation was added
*/
public boolean addInvitation(UUID sender, UUID recipient, Duration duration) {
if (containsRecipient(recipient) || containsSender(sender)) {
return false;
}
InvitationObject invitationObject = new InvitationObject(sender, recipient, LocalDateTime.now().plus
(duration));
recipientInvitationMap.put(recipient, invitationObject);
suggestStartingRunner();
return true;
}
/**
* Removes the invitation for the given recipient
*
* @param recipient The recipient
*/
public void removeInvitation(UUID recipient) {
recipientInvitationMap.remove(recipient);
suggestStoppingRunner();
}
/**
* Removes the invitation from the given sender
*
* @param sender The sender
*/
public void removeInvitationFromSender(UUID sender) {
recipientInvitationMap.values().removeIf(invitationObject -> invitationObject.isSender(sender));
suggestStoppingRunner();
}
/**
* Returns the sender of the initiation
*
* @param recipient The recipient to get the sender for
*
* @return The UUID of the sender
*/
public Optional<UUID> getInvitationSender(UUID recipient) {
if (!containsRecipient(recipient)) {
return Optional.empty();
}
return Optional.ofNullable(recipientInvitationMap.get(recipient).getSender());
}
/**
* Returns the recipient of the initiation
*
* @param sender The sender to get the recipient for
*
* @return The UUID of the recipient
*/
public Optional<UUID> getInvitationRecipient(UUID sender) {
return recipientInvitationMap.values()
.stream()
.filter(invitationObject -> invitationObject.isSender(sender))
.map(InvitationObject::getRecipient)
.findAny();
}
/**
* @param recipient The recipient to check
*
* @return True if the given recipient is already in the map
*/
public boolean containsRecipient(UUID recipient) {
return recipientInvitationMap.containsKey(recipient);
}
/**
* @param sender The UUID to check
*
* @return True if it contains the sender
*/
public boolean containsSender(UUID sender) {
return recipientInvitationMap.values().stream()
.anyMatch(invitationObject -> invitationObject.isSender(sender));
}
/**
* An invitation
*/
private class InvitationObject {
private UUID sender, recipient;
private LocalDateTime timeoutTime;
/**
* @param sender The player who sent the invitation
* @param recipient The recipient of the invitation
* @param timeoutTime The timeout time
*/
public InvitationObject(UUID sender, UUID recipient, LocalDateTime timeoutTime) {
this.sender = sender;
this.recipient = recipient;
this.timeoutTime = timeoutTime;
}
/**
* @return True if this invitation is timed out
*/
private boolean isTimedOut() {
return LocalDateTime.now().isAfter(timeoutTime);
}
/**
* @return The player who send the request
*/
public UUID getSender() {
return sender;
}
/**
* @param uuid The UUId to check
*
* @return True if the given UUID is the sender
*/
public boolean isSender(UUID uuid) {
return uuid.equals(sender);
}
/**
* @return The recipient of the invitation
*/
public UUID getRecipient() {
return recipient;
}
}
}
package me.ialistannen.mystlinkingbooks.util;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* An itemstack builder
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public class ItemStackBuilder {
/**
* Colorable materials
*/
private static final Set<Material> COLORABLE = EnumSet.of(Material.WOOL, Material.STAINED_CLAY,
Material.STAINED_GLASS, Material.STAINED_GLASS_PANE, Material.CARPET);
private ItemStack item;
private ItemMeta itemMeta;
private ItemStackBuilder(Material material) {
item = new ItemStack(material);
itemMeta = item.getItemMeta();
}
private ItemStackBuilder(ItemStack item) {
this.item = item.clone();
itemMeta = item.getItemMeta();
}
/**
* @param mat The Material of the itemstack
* @return This builder
*/
public ItemStackBuilder setType(Material mat) {
item.setItemMeta(itemMeta);
item.setType(mat);
itemMeta = item.getItemMeta();
return this;
}
/**
* @param amount The amount of the item
* @return This builder
*/
public ItemStackBuilder setAmount(int amount) {
item.setAmount(amount);
return this;
}
/**
* @param lore The Lore
* @return This builder
*/
public ItemStackBuilder setLore(List<String> lore) {
itemMeta.setLore(lore.stream().map(Util::color).collect(Collectors.toList()));
item.setItemMeta(itemMeta);
return this;
}
/**
* @param lore The Lore
* @return This builder
*/
public ItemStackBuilder setLore(String... lore) {
return setLore(Arrays.asList(lore));
}
/**
* @param loreLine The next line of the lore.
* @return This builder
*/
public ItemStackBuilder addLore(String loreLine) {
List<String> lore = itemMeta.hasLore() ? itemMeta.getLore() : new ArrayList<>();
lore.add(loreLine);
return setLore(lore);
}
/**
* @param name The name of the item
* @return This builder
*/
public ItemStackBuilder setName(String name) {
itemMeta.setDisplayName(Util.color(name));
item.setItemMeta(itemMeta);
return this;
}
/**
* Only colors if this item is colorable. else does nothing
*
* @param color The Color of the item
* @return This builder
*/
@SuppressWarnings("deprecation")
public ItemStackBuilder setColor(DyeColor color) {
if(!COLORABLE.contains(item.getType())) {
return this;
}
item.setDurability(color.getData());
return this;
}
/**
* @param durability The durability
* @return This builder
*/
public ItemStackBuilder setDurability(short durability) {
item.setDurability(durability);
return this;
}
/**
* Automatically uses addUnsafeEnchantment if needed.
*
* @param enchantment The enchantment to add
* @param level The level of the enchantment
* @return This builder
*/
public ItemStackBuilder addEnchantment(Enchantment enchantment, int level) {
if(level <= 0) {
return this;
}
if(level <= enchantment.getMaxLevel() && enchantment.canEnchantItem(item)) {
item.addEnchantment(enchantment, level);
}
else {
item.addUnsafeEnchantment(enchantment, level);
}
itemMeta = item.getItemMeta().clone();
return this;
}
/**
* Only sets the owner if this Material is a skull
*
* @param name The Skull owner
* @return This builder
*/
public ItemStackBuilder setSkullOwner(String name) {
if(item.getType() != Material.SKULL_ITEM) {
return this;
}
SkullMeta meta = (SkullMeta) item.getItemMeta();
meta.setOwner(name);
item.setItemMeta(meta);
itemMeta = item.getItemMeta();
return this;
}
/**
* @return The resulting ItemStack
*/
public ItemStack build() {
item.setItemMeta(itemMeta);
return item;
}
/**
* @param material The Material to use for the stack
* @return A new {@link ItemStackBuilder}
*/
public static ItemStackBuilder builder(Material material) {
return new ItemStackBuilder(material);
}
/**
* @param itemStack The Item to build upon
* @return A new {@link ItemStackBuilder}
*/
public static ItemStackBuilder builder(ItemStack itemStack) {
return new ItemStackBuilder(itemStack);
}
}
package me.ialistannen.mystlinkingbooks.util;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import java.util.HashMap;
import java.util.Map;
/**
* A serializable Location (needed for < 1.8)
*/
public class LocationSerializable implements ConfigurationSerializable {
private double x, y, z;
private float yaw, pitch;
private String world;
/**
* @param x The x coordinate
* @param y The y coordinate
* @param z The z coordinate
* @param yaw The yaw
* @param pitch The pitch
* @param world The world
*/
public LocationSerializable(double x, double y, double z, float yaw, float pitch, String world) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.world = world;
}
/**
* @param location The location to mirror
*/
public LocationSerializable(Location location) {
this(location.getX(), location.getY(), location.getZ(),
location.getYaw(), location.getPitch(),
location.getWorld().getName());
}
/**
* @param map The map for {@link ConfigurationSerializable}
*/
public LocationSerializable(Map<String, Object> map) {
this(((Number) map.get("x")).doubleValue(),
((Number) map.get("y")).doubleValue(),
((Number) map.get("z")).doubleValue(),
((Number) map.get("yaw")).floatValue(),
((Number) map.get("pitch")).floatValue(),
(String) map.get("world"));
}
/**
* Converts this to a location
*
* @return The location as a {@link Location}
*/
public Location toLocation() {
return new Location(Bukkit.getWorld(world),
x, y, z, yaw, pitch);
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("x", x);
map.put("y", y);
map.put("z", z);
map.put("yaw", yaw);
map.put("pitch", pitch);
map.put("world", world);
return map;
}
}
package me.ialistannen.mystlinkingbooks.util;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor;
import java.util.Optional;
/**
* Handles NBT lookups
*/
public class NbtUtil {
private static final Class<?> CRAFT_ITEM_STACK_CLASS = ReflectionUtil
.getCraftbukkitClass("CraftItemStack", "inventory");
private static final Class<?> NBT_TAG_COMPOUND_CLASS = ReflectionUtil.getNMSClass("NBTTagCompound");
private static final Class<?> NBT_TAG_BASE_CLASS = ReflectionUtil.getNMSClass("NBTBase");
/**
* Returns the NBT tag of an item or creates a new one
*
* @param itemStack The ItemStack to get it from
*
* @return The NbtTagCompound
*/
public static Object getNBTTagCompound(ItemStack itemStack) {
Object nmsItem = getNmsItem(itemStack);
Object tag = ReflectionUtil.invokeMethod(nmsItem, "getTag", new Class[0]);
if (tag == null) {
tag = newNbtTagCompound();
}
return tag;
}
private static Object getNmsItem(ItemStack itemStack) {
return ReflectionUtil
.invokeMethod(CRAFT_ITEM_STACK_CLASS, null, "asNMSCopy", new Class[]{ItemStack.class}, itemStack);
}
/**
* Applies the NBTTagCompound to the given ItemStack
*
* @param nbtTag The NBTTagCompound
* @param itemStack The {@link ItemStack}
*
* @return The resulting {@link ItemStack}
*/
public static ItemStack applyNbtTag(Object nbtTag, ItemStack itemStack) {
Object nmsItem = getNmsItem(itemStack);
ReflectionUtil.invokeMethod(nmsItem, "setTag", new Class[]{NBT_TAG_COMPOUND_CLASS}, nbtTag);
return (ItemStack) ReflectionUtil
.invokeMethod(CRAFT_ITEM_STACK_CLASS, null, "asBukkitCopy", new Class[]{nmsItem.getClass()}, nmsItem);
}
/**
* Creates a new NBTTagCompound
*
* @return A new NBTTagCompound
*/
public static Object newNbtTagCompound() {
Optional<Constructor> constructor = ReflectionUtil.getConstructor(NBT_TAG_COMPOUND_CLASS);
if (!constructor.isPresent()) {
throw new NoSuchMethodError("The constructor for NBTTagCompound can't be found.");
}
return ReflectionUtil.instantiate(constructor.get());
}
/**
* Gets the NBTTag with the given name
*
* @param nbtTag The NBTTag to perform upon
* @param name The name of the NBTTag to get
*
* @return The NBTBase
*/
public static Object getTag(Object nbtTag, String name) {
return getTag(nbtTag, "get", name);
}
/**
* Gets the NBTTag with the given name
*
* @param nbtTag The NBTTag to perform upon
* @param methodName The name of the method to invoke
* @param key The name of the NBTTag to get
*
* @return The result of the invocation
*/
public static Object getTag(Object nbtTag, String methodName, String key) {
return ReflectionUtil.invokeMethod(nbtTag, methodName, new Class[]{String.class}, key);
}
/**
* Sets the NBTTag
*
* @param handle The NBTTag to perform upon
* @param name The name of the NBTTag to get
* @param newNbtTag The new NNTTag to set
*/
public static void setTag(Object handle, String name, Object newNbtTag) {
ReflectionUtil.invokeMethod(handle, "set",
new Class[]{String.class, NBT_TAG_BASE_CLASS}, name, newNbtTag);
}
/**
* Sets the tag. Lets you specify the method though.
*
* @param handle The handle to perform upon
* @param methodName The name of the method to invoke
* @param key The key to set
* @param valueClass The class of the value
* @param value The value
*/
public static void setTag(Object handle, String methodName, String key, Class<?> valueClass, Object value) {
if (!methodName.startsWith("set")) {
throw new IllegalArgumentException("Method name must start with 'set'!");
}
ReflectionUtil.invokeMethod(handle, methodName, new Class[]{String.class, valueClass}, key, value);
}
/**
* Same as {@link #setTag(Object, String, String, Class, Object)}
* <br>But the valueClass will be value#getClass()
*
* @param handle The handle to perform upon
* @param methodName The name of the method to invoke
* @param key The key to set
* @param value The value
*
* @see #setTag(Object, String, String, Class, Object)
*/
public static void setTag(Object handle, String methodName, String key, Object value) {
setTag(handle, methodName, key, value.getClass(), value);
}
/**
* Checks if the NBTTag has a given key
*
* @param nbtTag The NBTTag
* @param key The key to check
*
* @return True if it has the key, false otherwise
*/
public static boolean hasKey(Object nbtTag, String key) {
return (boolean) ReflectionUtil.invokeMethod(nbtTag, "hasKey", new Class[]{String.class}, key);
}
}
package me.ialistannen.mystlinkingbooks.util;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import org.bukkit.Bukkit;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.logging.Level;
/**
* A small help with reflection
*/
public class ReflectionUtil {
private static final String SERVER_VERSION;
static {
String name = Bukkit.getServer().getClass().getName();
name = name.substring(name.indexOf("craftbukkit.") + "craftbukkit.".length());
name = name.substring(0, name.indexOf("."));
SERVER_VERSION = name;
}
/**
* Returns the NMS class.
*
* @param name The name of the class
*
* @return The NMS class or null if an error occurred
*/
public static Class<?> getNMSClass(String name) {
try {
return Class.forName("net.minecraft.server." + SERVER_VERSION + "." + name);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Returns the CraftBukkit class.
*
* @param name The name of the class
*
* @return The CraftBukkit class or null if an error occurred
*/
public static Class<?> getCraftbukkitClass(String name, String packageName) {
try {
return Class.forName("org.bukkit.craftbukkit." + SERVER_VERSION + "." + packageName + "." + name);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Invokes the method
*
* @param handle The handle to invoke it on
* @param methodName The name of the method
* @param parameterClasses The parameter types
* @param args The arguments
*
* @return The resulting object or null if an error occurred / the method didn't return a thing
*/
public static Object invokeMethod(Object handle, String methodName, Class[] parameterClasses, Object... args) {
return invokeMethod(handle.getClass(), handle, methodName, parameterClasses, args);
}
/**
* Invokes the method
*
* @param clazz The class to invoke it from
* @param handle The handle to invoke it on
* @param methodName The name of the method
* @param parameterClasses The parameter types
* @param args The arguments
*
* @return The resulting object or null if an error occurred / the method didn't return a thing
*/
public static Object invokeMethod(Class<?> clazz, Object handle, String methodName, Class[] parameterClasses,
Object... args) {
Optional<Method> methodOptional = getMethod(clazz, methodName, parameterClasses);
if (!methodOptional.isPresent()) {
return null;
}
Method method = methodOptional.get();
try {
return method.invoke(handle, args);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* Sets the value of an instance field
*
* @param handle The handle to invoke it on
* @param name The name of the field
* @param value The new value of the field
*/
@SuppressWarnings("SameParameterValue")
public static void setInstanceField(Object handle, String name, Object value) {
Class<?> clazz = handle.getClass();
Optional<Field> fieldOptional = getField(clazz, name);
if (!fieldOptional.isPresent()) {
return;
}
Field field = fieldOptional.get();
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
field.set(handle, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Returns the constructor
*
* @param clazz The class
* @param params The Constructor parameters
*
* @return The Constructor or an empty Optional if there is none with these parameters
*/
public static Optional<Constructor> getConstructor(Class<?> clazz, Class<?>... params) {
try {
return Optional.of(clazz.getConstructor(params));
} catch (NoSuchMethodException e) {
return Optional.empty();
}
}
/**
* Instantiates the class. Will print the errors it gets
*
* @param constructor The constructor
* @param arguments The initial arguments
*
* @return The resulting object, or null if an error occurred.
*/
public static Object instantiate(Constructor<?> constructor, Object... arguments) {
try {
return constructor.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
MystLinkingBooks.getInstance().getLogger()
.log(Level.WARNING, "Can't instantiate class " + constructor.getDeclaringClass(), e);
}
return null;
}
private static Optional<Method> getMethod(Class<?> clazz, String name, Class<?>... params) {
try {
return Optional.of(clazz.getMethod(name, params));
} catch (NoSuchMethodException ignored) {
}
try {
return Optional.of(clazz.getDeclaredMethod(name, params));
} catch (NoSuchMethodException ignored) {
}
return Optional.empty();
}
private static Optional<Field> getField(Class<?> clazz, String name) {
try {
return Optional.of(clazz.getField(name));
} catch (NoSuchFieldException ignored) {
}
try {
return Optional.of(clazz.getDeclaredField(name));
} catch (NoSuchFieldException ignored) {
}
return Optional.empty();
}
}
package me.ialistannen.mystlinkingbooks.util;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.time.Duration;
import java.util.Optional;
/**
* Some static Utility functions
*/
public class Util {
/**
* Returns the material for the string
*
* @param name The name of the material
*
* @return The material if any found
*/
public static Optional<Material> matchMaterial(String name) {
Material material = Material.matchMaterial(name);
// try some more unsafe things...
if (material == null) {
material = Bukkit.getUnsafe().getMaterialFromInternalName(name);
}
return Optional.ofNullable(material);
}
/**
* Colors the string
*
* @param string The string to color
*
* @return The colored String
*/
public static String color(String string) {
return ChatColor.translateAlternateColorCodes('&', string);
}
/**
* Translates a String
*
* @param key The key
* @param objects The formatting objects
*
* @return The translated string, already colored
*/
public static String tr(String key, Object... objects) {
return color(trUncolored(key, objects));
}
/**
* Translates a String
*
* @param key The key
* @param objects The formatting objects
*
* @return The translated string, not colored
*/
public static String trUncolored(String key, Object... objects) {
return MystLinkingBooks.getInstance().getLanguage().tr(key, objects);
}
/**
* Checks if the string is a double.
* <br>Yes, exception for the program flow.
*
* @param string The string to check
*
* @return True if it is a double
*/
public static boolean isDouble(String string) {
try {
Double.parseDouble(string);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* Sets the item in hand, trying to respect the server version
*
* @param player The player to give it to
* @param itemStack The item to give
*/
public static void setItemInHand(Player player, ItemStack itemStack) {
if (isOneHanded()) {
player.setItemInHand(itemStack);
} else {
ReflectionUtil.invokeMethod(
player.getInventory(),
"setItemInMainHand",
new Class[]{ItemStack.class},
itemStack);
}
}
private static boolean isOneHanded() {
String version = Bukkit.getVersion();
version = version.substring(version.indexOf("MC: ") + "MC: ".length());
version = version.replaceAll("[^0-9.]", "");
String[] split = version.split("\\.");
if (split.length >= 2) {
int gameVersion = Integer.parseInt(split[0]);
int major = Integer.parseInt(split[1]);
// use the setItemInMainHand method to not rely on the legacy one
if (gameVersion > 1 || major >= 9) {
return false;
}
}
return true;
}
/**
* The item in the (main) hand
*
* @param player The player
*
* @return The item in the (main) hand
*/
public static ItemStack getItemInHand(Player player) {
if (isOneHanded()) {
return player.getItemInHand();
} else {
return (ItemStack) ReflectionUtil.invokeMethod(
player.getInventory(),
"getItemInMainHand",
new Class[]{});
}
}
/**
* Teleports by a relto book
*
* @param player The player to teleport
* @param destination The destination to teleport him to
*/
public static void startReltoTeleportWithWarmup(Player player, Location destination) {
WarmupUtil warmupUtil = MystLinkingBooks.getInstance().getWarmupUtil();
if (warmupUtil.containsUUID(player.getUniqueId())) {
player.sendMessage(tr("already_a_warmup_running"));
return;
}
String warmupString = MystLinkingBooks.getInstance().getConfig().getString("relto book warmup");
long warmupMillis = DurationParser.parseDuration(warmupString);
player.sendMessage(tr("warmup_started",
DurationFormatUtils.formatDurationWords(warmupMillis, true, true)));
warmupUtil.add(Duration.ofMillis(warmupMillis), () -> {
player.teleport(destination);
player.sendMessage(tr("relto_book_teleported_you",
destination.getX(), destination.getY(), destination.getZ(), destination.getWorld().getName()));
}, player.getUniqueId());
}
}
package me.ialistannen.mystlinkingbooks.util;
import me.ialistannen.mystlinkingbooks.MystLinkingBooks;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitRunnable;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
/**
* Allows a warm up to take place, until something is executed
*/
public class WarmupUtil implements Listener {
private Map<UUID, WarmupObject> warmupObjects = new HashMap<>();
private BukkitRunnable ticker;
/**
* Adds the runnable at the giVen time
*
* @param time The time to execute it
* @param runnable The runnable to add
* @param uuid The UUID to use as a key. The Player one normally
*
* @return True if the warmup was added
*/
public boolean add(LocalDateTime time, Runnable runnable, UUID uuid) {
if (containsUUID(uuid)) {
return false;
}
warmupObjects.put(uuid, new WarmupObject(time, runnable));
considerStartingTicking();
return true;
}
/**
* Adds the runnable at the giVen time
*
* @param timeWait The time to wait before executing it
* @param runnable The runnable to add
* @param uuid The UUID to use as a key. The Player one normally
*
* @return True if the warmup was added
*/
public boolean add(Duration timeWait, Runnable runnable, UUID uuid) {
return add(LocalDateTime.now().plus(timeWait), runnable, uuid);
}
/**
* Removes the the runnable scheduled for the given UUID
*
* @param key The UUID it was registered under
*/
public void remove(UUID key) {
warmupObjects.remove(key);
considerStoppingTicking();
}
/**
* Checks whether the given UUID is already registered
*
* @param uuid The UUID to check
*
* @return True if this manager contains the UUID
*/
public boolean containsUUID(UUID uuid) {
return warmupObjects.containsKey(uuid);
}
/**
* Start the ticker if it is needed
*/
private void considerStartingTicking() {
if (ticker == null) {
ticker = new BukkitRunnable() {
@Override
public void run() {
tick();
}
};
ticker.runTaskTimer(MystLinkingBooks.getInstance(), 0, 5);
}
}
/**
* Stop the ticker if there is no cooldown anymore
*/
private void considerStoppingTicking() {
if (warmupObjects.isEmpty() && ticker != null) {
if (Bukkit.getScheduler().isCurrentlyRunning(ticker.getTaskId())
|| Bukkit.getScheduler().isQueued(ticker.getTaskId())) {
ticker.cancel();
}
ticker = null;
}
}
/**
* Ticks.
*/
private void tick() {
for (Iterator<WarmupObject> iterator = warmupObjects.values().iterator(); iterator.hasNext(); ) {
WarmupObject warmupObject = iterator.next();
if (warmupObject.shouldFire()) {
iterator.remove();
warmupObject.run();
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerMove(PlayerMoveEvent e) {
if (!containsUUID(e.getPlayer().getUniqueId())) {
return;
}
// no moving for you, but looking round is okay.
Location newToLocation = e.getFrom().setDirection(e.getTo().getDirection());
e.setTo(newToLocation);
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerPickupItem(PlayerPickupItemEvent e) {
if (!containsUUID(e.getPlayer().getUniqueId())) {
return;
}
e.setCancelled(true);
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent e) {
if (!containsUUID(e.getPlayer().getUniqueId())) {
return;
}
e.setCancelled(true);
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerQuit(PlayerQuitEvent e) {
remove(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerGettingDamage(EntityDamageEvent e) {
if (!containsUUID(e.getEntity().getUniqueId())) {
return;
}
e.setCancelled(true);
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerClickInventory(InventoryClickEvent e) {
if (!containsUUID(e.getWhoClicked().getUniqueId())) {
return;
}
e.setCancelled(true);
}
/**
* A warmup object
*/
private class WarmupObject {
private LocalDateTime runTime;
private Runnable runnable;
/**
* @param runTime The time it should run at
* @param runnable The runnable to run
*/
WarmupObject(LocalDateTime runTime, Runnable runnable) {
this.runTime = runTime;
this.runnable = runnable;
}
/**
* Runs the runnable
*/
void run() {
runnable.run();
}
/**
* @return True if this runnable should fire now
*/
boolean shouldFire() {
return LocalDateTime.now().isAfter(runTime);
}
}
}
name: "MYSTLinkingBooks"
version: 0.1
main: me.ialistannen.mystlinkingbooks.MystLinkingBooks
author: "I Al Istannen"
commands:
kor:
usage: "Usage"
description: "Description"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment