Created
August 14, 2016 17:33
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<component name="CopyrightManager"> | |
<settings default="" /> | |
</component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# == 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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