feat: make nicko work again partially
This commit is contained in:
parent
a996858ba9
commit
4f9e334544
14 changed files with 1939 additions and 77 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -29,4 +29,5 @@ bin/
|
||||||
run/
|
run/
|
||||||
|
|
||||||
### Private ###
|
### Private ###
|
||||||
TODO
|
TODO
|
||||||
|
src/main/java/net/
|
|
@ -36,7 +36,7 @@ dependencies {
|
||||||
compileOnly("me.clip:placeholderapi:2.11.5")
|
compileOnly("me.clip:placeholderapi:2.11.5")
|
||||||
compileOnly("net.kyori:adventure-api:4.17.0")
|
compileOnly("net.kyori:adventure-api:4.17.0")
|
||||||
compileOnly("xyz.xenondevs.invui:invui-core:$invuiVersion")
|
compileOnly("xyz.xenondevs.invui:invui-core:$invuiVersion")
|
||||||
compileOnly("net.wesjd:anvilgui:1.10.4-SNAPSHOT")
|
//compileOnly("net.wesjd:anvilgui:1.10.4-SNAPSHOT")
|
||||||
|
|
||||||
//implementation("xyz.xenondevs.invui:inventory-access-r22:$invuiVersion:remapped-mojang")
|
//implementation("xyz.xenondevs.invui:inventory-access-r22:$invuiVersion:remapped-mojang")
|
||||||
|
|
||||||
|
|
13
src/main/java/com/comphenix/protocol/utility/LICENSE
Normal file
13
src/main/java/com/comphenix/protocol/utility/LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright (C) 2012 Kristian S. Stangeland
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
|
the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with this program;
|
||||||
|
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
02111-1307 USA
|
|
@ -0,0 +1,468 @@
|
||||||
|
/*
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
* 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.comphenix.protocol.utility;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the current Minecraft version.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
|
||||||
|
/**
|
||||||
|
* Version 1.21.4 - the garden awakens drop
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion v1_21_4 = new MinecraftVersion("1.21.4");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.21.2 - the bundles of bravery drop
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion v1_21_2 = new MinecraftVersion("1.21.2");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.21.0 - the tricky trials update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion v1_21_0 = new MinecraftVersion("1.21.0");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.20.5 - the cookie and transfer packet update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion v1_20_5 = new MinecraftVersion("1.20.5");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.20.4 - the decorated pot update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion v1_20_4 = new MinecraftVersion("1.20.4");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.20.2 - the update that added the configuration protocol phase.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion CONFIG_PHASE_PROTOCOL_UPDATE = new MinecraftVersion("1.20.2");
|
||||||
|
/**
|
||||||
|
* Version 1.20 - the trails and tails update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion TRAILS_AND_TAILS = new MinecraftVersion("1.20");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.19.4 - the rest of the feature preview
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion FEATURE_PREVIEW_2 = new MinecraftVersion("1.19.4");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version 1.19.3 - introducing feature preview
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion FEATURE_PREVIEW_UPDATE = new MinecraftVersion("1.19.3");
|
||||||
|
/**
|
||||||
|
* Version 1.19 - the wild update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19");
|
||||||
|
/**
|
||||||
|
* Version 1.18 - caves and cliffs part 2
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18");
|
||||||
|
/**
|
||||||
|
* Version 1.17 - caves and cliffs part 1
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17");
|
||||||
|
/**
|
||||||
|
* Version 1.16.4
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion NETHER_UPDATE_4 = new MinecraftVersion("1.16.4");
|
||||||
|
/**
|
||||||
|
* Version 1.16.2 - breaking change to the nether update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2");
|
||||||
|
/**
|
||||||
|
* Version 1.16.0 - the nether update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16");
|
||||||
|
/**
|
||||||
|
* Version 1.15 - the bee update
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15");
|
||||||
|
/**
|
||||||
|
* Version 1.14 - village and pillage update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14");
|
||||||
|
/**
|
||||||
|
* Version 1.13 - update aquatic.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13");
|
||||||
|
/**
|
||||||
|
* Version 1.12 - the world of color update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12");
|
||||||
|
/**
|
||||||
|
* Version 1.11 - the exploration update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11");
|
||||||
|
/**
|
||||||
|
* Version 1.10 - the frostburn update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10");
|
||||||
|
/**
|
||||||
|
* Version 1.9 - the combat update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9");
|
||||||
|
/**
|
||||||
|
* Version 1.8 - the "bountiful" update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8");
|
||||||
|
/**
|
||||||
|
* Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise)
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8");
|
||||||
|
/**
|
||||||
|
* Version 1.7.2 - the update that changed the world.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2");
|
||||||
|
/**
|
||||||
|
* Version 1.6.1 - the horse update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1");
|
||||||
|
/**
|
||||||
|
* Version 1.5.0 - the redstone update.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0");
|
||||||
|
/**
|
||||||
|
* Version 1.4.2 - the scary update (Wither Boss).
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest release version of minecraft.
|
||||||
|
*/
|
||||||
|
public static final MinecraftVersion LATEST = v1_21_4;
|
||||||
|
|
||||||
|
// used when serializing
|
||||||
|
private static final long serialVersionUID = -8695133558996459770L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression used to parse version strings.
|
||||||
|
*/
|
||||||
|
private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion()
|
||||||
|
*/
|
||||||
|
private static MinecraftVersion currentVersion;
|
||||||
|
|
||||||
|
private final int major;
|
||||||
|
private final int minor;
|
||||||
|
private final int build;
|
||||||
|
// The development stage
|
||||||
|
private final String development;
|
||||||
|
|
||||||
|
// Snapshot?
|
||||||
|
private final SnapshotVersion snapshot;
|
||||||
|
private volatile Boolean atCurrentOrAbove;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the current Minecraft version.
|
||||||
|
*
|
||||||
|
* @param server - the Bukkit server that will be used to examine the MC version.
|
||||||
|
*/
|
||||||
|
public MinecraftVersion(Server server) {
|
||||||
|
this(extractVersion(server.getVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a version object from the format major.minor.build, or the snapshot format.
|
||||||
|
*
|
||||||
|
* @param versionOnly - the version in text form.
|
||||||
|
*/
|
||||||
|
public MinecraftVersion(String versionOnly) {
|
||||||
|
this(versionOnly, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a version format from the standard release version or the snapshot verison.
|
||||||
|
*
|
||||||
|
* @param versionOnly - the version.
|
||||||
|
* @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private MinecraftVersion(String versionOnly, boolean parseSnapshot) {
|
||||||
|
String[] section = versionOnly.split("-");
|
||||||
|
SnapshotVersion snapshot = null;
|
||||||
|
int[] numbers = new int[3];
|
||||||
|
|
||||||
|
try {
|
||||||
|
numbers = this.parseVersion(section[0]);
|
||||||
|
} catch (NumberFormatException cause) {
|
||||||
|
// Skip snapshot parsing
|
||||||
|
if (!parseSnapshot) {
|
||||||
|
throw cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine if the snapshot is newer than the current release version
|
||||||
|
snapshot = new SnapshotVersion(section[0]);
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||||
|
|
||||||
|
MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false);
|
||||||
|
boolean newer = snapshot.getSnapshotDate().compareTo(
|
||||||
|
format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0;
|
||||||
|
|
||||||
|
numbers[0] = latest.getMajor();
|
||||||
|
numbers[1] = latest.getMinor() + (newer ? 1 : -1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Cannot parse " + section[0], e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.major = numbers[0];
|
||||||
|
this.minor = numbers[1];
|
||||||
|
this.build = numbers[2];
|
||||||
|
this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null);
|
||||||
|
this.snapshot = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a version object directly.
|
||||||
|
*
|
||||||
|
* @param major - major version number.
|
||||||
|
* @param minor - minor version number.
|
||||||
|
* @param build - build version number.
|
||||||
|
*/
|
||||||
|
public MinecraftVersion(int major, int minor, int build) {
|
||||||
|
this(major, minor, build, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a version object directly.
|
||||||
|
*
|
||||||
|
* @param major - major version number.
|
||||||
|
* @param minor - minor version number.
|
||||||
|
* @param build - build version number.
|
||||||
|
* @param development - development stage.
|
||||||
|
*/
|
||||||
|
public MinecraftVersion(int major, int minor, int build, String development) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.build = build;
|
||||||
|
this.development = development;
|
||||||
|
this.snapshot = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the Minecraft version from CraftBukkit itself.
|
||||||
|
*
|
||||||
|
* @param text - the server version in text form.
|
||||||
|
* @return The underlying MC version.
|
||||||
|
* @throws IllegalStateException If we could not parse the version string.
|
||||||
|
*/
|
||||||
|
public static String extractVersion(String text) {
|
||||||
|
Matcher version = VERSION_PATTERN.matcher(text);
|
||||||
|
|
||||||
|
if (version.matches() && version.group(1) != null) {
|
||||||
|
return version.group(1);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Cannot parse version String '" + text + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given server version into a Minecraft version.
|
||||||
|
*
|
||||||
|
* @param serverVersion - the server version.
|
||||||
|
* @return The resulting Minecraft version.
|
||||||
|
*/
|
||||||
|
public static MinecraftVersion fromServerVersion(String serverVersion) {
|
||||||
|
return new MinecraftVersion(extractVersion(serverVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MinecraftVersion getCurrentVersion() {
|
||||||
|
if (currentVersion == null) {
|
||||||
|
currentVersion = fromServerVersion(Bukkit.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCurrentVersion(MinecraftVersion version) {
|
||||||
|
currentVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean atOrAbove(MinecraftVersion version) {
|
||||||
|
return getCurrentVersion().isAtLeast(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] parseVersion(String version) {
|
||||||
|
String[] elements = version.split("\\.");
|
||||||
|
int[] numbers = new int[3];
|
||||||
|
|
||||||
|
// Make sure it's even a valid version
|
||||||
|
if (elements.length < 1) {
|
||||||
|
throw new IllegalStateException("Corrupt MC version: " + version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively.
|
||||||
|
for (int i = 0; i < Math.min(numbers.length, elements.length); i++) {
|
||||||
|
numbers[i] = Integer.parseInt(elements[i].trim());
|
||||||
|
}
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Major version number
|
||||||
|
*
|
||||||
|
* @return Current major version number.
|
||||||
|
*/
|
||||||
|
public int getMajor() {
|
||||||
|
return this.major;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minor version number
|
||||||
|
*
|
||||||
|
* @return Current minor version number.
|
||||||
|
*/
|
||||||
|
public int getMinor() {
|
||||||
|
return this.minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build version number
|
||||||
|
*
|
||||||
|
* @return Current build version number.
|
||||||
|
*/
|
||||||
|
public int getBuild() {
|
||||||
|
return this.build;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the development stage.
|
||||||
|
*
|
||||||
|
* @return Development stage, or NULL if this is a release.
|
||||||
|
*/
|
||||||
|
public String getDevelopmentStage() {
|
||||||
|
return this.development;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the snapshot version, or NULL if this is a release.
|
||||||
|
*
|
||||||
|
* @return The snapshot version.
|
||||||
|
*/
|
||||||
|
public SnapshotVersion getSnapshot() {
|
||||||
|
return this.snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this version is a snapshot.
|
||||||
|
*
|
||||||
|
* @return The snapshot version.
|
||||||
|
*/
|
||||||
|
public boolean isSnapshot() {
|
||||||
|
return this.snapshot != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this version is at or above the current version the server is running.
|
||||||
|
*
|
||||||
|
* @return true if this version is equal or newer than the server version, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean atOrAbove() {
|
||||||
|
if (this.atCurrentOrAbove == null) {
|
||||||
|
this.atCurrentOrAbove = atOrAbove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.atCurrentOrAbove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the version String (major.minor.build) only.
|
||||||
|
*
|
||||||
|
* @return A normal version string.
|
||||||
|
*/
|
||||||
|
public String getVersion() {
|
||||||
|
if (this.getDevelopmentStage() == null) {
|
||||||
|
return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild());
|
||||||
|
} else {
|
||||||
|
return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(),
|
||||||
|
this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(MinecraftVersion o) {
|
||||||
|
if (o == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComparisonChain.start()
|
||||||
|
.compare(this.getMajor(), o.getMajor())
|
||||||
|
.compare(this.getMinor(), o.getMinor())
|
||||||
|
.compare(this.getBuild(), o.getBuild())
|
||||||
|
.compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast())
|
||||||
|
.compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst())
|
||||||
|
.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAtLeast(MinecraftVersion other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.compareTo(other) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof MinecraftVersion) {
|
||||||
|
MinecraftVersion other = (MinecraftVersion) obj;
|
||||||
|
|
||||||
|
return this.getMajor() == other.getMajor() &&
|
||||||
|
this.getMinor() == other.getMinor() &&
|
||||||
|
this.getBuild() == other.getBuild() &&
|
||||||
|
Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// Convert to a String that we can parse back again
|
||||||
|
return String.format("(MC: %s)", this.getVersion());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
* 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.comphenix.protocol.utility;
|
||||||
|
|
||||||
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to parse a snapshot version.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class SnapshotVersion implements Comparable<SnapshotVersion>, Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 2778655372579322310L;
|
||||||
|
private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(\\d{2}w\\d{2})([a-z])");
|
||||||
|
|
||||||
|
private final Date snapshotDate;
|
||||||
|
private final int snapshotWeekVersion;
|
||||||
|
|
||||||
|
private transient String rawString;
|
||||||
|
|
||||||
|
public SnapshotVersion(String version) {
|
||||||
|
Matcher matcher = SNAPSHOT_PATTERN.matcher(version.trim());
|
||||||
|
|
||||||
|
if (matcher.matches()) {
|
||||||
|
try {
|
||||||
|
this.snapshotDate = getDateFormat().parse(matcher.group(1));
|
||||||
|
this.snapshotWeekVersion = matcher.group(2).charAt(0) - 'a';
|
||||||
|
this.rawString = version;
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException("Date implied by snapshot version is invalid.", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Cannot parse " + version + " as a snapshot version.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the snapshot date parser.
|
||||||
|
* <p>
|
||||||
|
* We have to create a new instance of SimpleDateFormat every time as it is not thread safe.
|
||||||
|
*
|
||||||
|
* @return The date formatter.
|
||||||
|
*/
|
||||||
|
private static SimpleDateFormat getDateFormat() {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yy'w'ww", Locale.US);
|
||||||
|
format.setLenient(false);
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the snapshot version within a week, starting at zero.
|
||||||
|
*
|
||||||
|
* @return The weekly version
|
||||||
|
*/
|
||||||
|
public int getSnapshotWeekVersion() {
|
||||||
|
return this.snapshotWeekVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the week this snapshot was released.
|
||||||
|
*
|
||||||
|
* @return The week.
|
||||||
|
*/
|
||||||
|
public Date getSnapshotDate() {
|
||||||
|
return this.snapshotDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the raw snapshot string (yy'w'ww[a-z]).
|
||||||
|
*
|
||||||
|
* @return The snapshot string.
|
||||||
|
*/
|
||||||
|
public String getSnapshotString() {
|
||||||
|
if (this.rawString == null) {
|
||||||
|
// It's essential that we use the same locale
|
||||||
|
Calendar current = Calendar.getInstance(Locale.US);
|
||||||
|
current.setTime(this.snapshotDate);
|
||||||
|
this.rawString = String.format("%02dw%02d%s",
|
||||||
|
current.get(Calendar.YEAR) % 100,
|
||||||
|
current.get(Calendar.WEEK_OF_YEAR),
|
||||||
|
(char) ('a' + this.snapshotWeekVersion));
|
||||||
|
}
|
||||||
|
return this.rawString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(SnapshotVersion o) {
|
||||||
|
if (o == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComparisonChain.start()
|
||||||
|
.compare(this.snapshotDate, o.getSnapshotDate())
|
||||||
|
.compare(this.snapshotWeekVersion, o.getSnapshotWeekVersion())
|
||||||
|
.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof SnapshotVersion) {
|
||||||
|
SnapshotVersion other = (SnapshotVersion) obj;
|
||||||
|
return Objects.equals(this.snapshotDate, other.getSnapshotDate())
|
||||||
|
&& this.snapshotWeekVersion == other.getSnapshotWeekVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.snapshotDate, this.snapshotWeekVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getSnapshotString();
|
||||||
|
}
|
||||||
|
}
|
889
src/main/java/net/wesjd/anvilgui/AnvilGUI.java
Normal file
889
src/main/java/net/wesjd/anvilgui/AnvilGUI.java
Normal file
|
@ -0,0 +1,889 @@
|
||||||
|
package net.wesjd.anvilgui;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
import net.wesjd.anvilgui.version.VersionWrapper;
|
||||||
|
import net.wesjd.anvilgui.version.VersionMatcher;
|
||||||
|
import org.apache.commons.lang.Validate;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.*;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An anvil gui, used for gathering a user's input
|
||||||
|
*
|
||||||
|
* @author Wesley Smith
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public class AnvilGUI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local {@link VersionWrapper} object for the server's version
|
||||||
|
*/
|
||||||
|
private static final VersionWrapper WRAPPER = new VersionMatcher().match();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variable containing an item with air. Used when the item would be null.
|
||||||
|
* To keep the heap clean, this object only gets iniziaised once
|
||||||
|
*/
|
||||||
|
private static final ItemStack AIR = new ItemStack(Material.AIR);
|
||||||
|
/**
|
||||||
|
* If the given ItemStack is null, return an air ItemStack, otherwise return the given ItemStack
|
||||||
|
*
|
||||||
|
* @param stack The ItemStack to check
|
||||||
|
* @return air or the given ItemStack
|
||||||
|
*/
|
||||||
|
private static ItemStack itemNotNull(ItemStack stack) {
|
||||||
|
return stack == null ? AIR : stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Plugin} that this anvil GUI is associated with
|
||||||
|
*/
|
||||||
|
private final Plugin plugin;
|
||||||
|
/**
|
||||||
|
* The player who has the GUI open
|
||||||
|
*/
|
||||||
|
private final Player player;
|
||||||
|
/**
|
||||||
|
* An {@link Executor} that executes tasks on the main server thread
|
||||||
|
*/
|
||||||
|
private final Executor mainThreadExecutor;
|
||||||
|
/**
|
||||||
|
* The title of the anvil inventory
|
||||||
|
*/
|
||||||
|
private final Object titleComponent;
|
||||||
|
/**
|
||||||
|
* The initial contents of the inventory
|
||||||
|
*/
|
||||||
|
private final ItemStack[] initialContents;
|
||||||
|
/**
|
||||||
|
* A state that decides where the anvil GUI is able to get closed by the user
|
||||||
|
*/
|
||||||
|
private final boolean preventClose;
|
||||||
|
/**
|
||||||
|
* A state that decides whether compatibility with Geyser software is enabled
|
||||||
|
*/
|
||||||
|
private final boolean geyserCompatibility;
|
||||||
|
/**
|
||||||
|
* A set of slot numbers that are permitted to be interacted with by the user. An interactable
|
||||||
|
* slot is one that is able to be minipulated by the player, i.e. clicking and picking up an item,
|
||||||
|
* placing in a new one, etc.
|
||||||
|
*/
|
||||||
|
private final Set<Integer> interactableSlots;
|
||||||
|
|
||||||
|
/** An {@link Consumer} that is called when the anvil GUI is close */
|
||||||
|
private final Consumer<StateSnapshot> closeListener;
|
||||||
|
/** A flag that decides whether the async click handler can be run concurrently */
|
||||||
|
private final boolean concurrentClickHandlerExecution;
|
||||||
|
/** An {@link BiFunction} that is called when a slot is clicked */
|
||||||
|
private final ClickHandler clickHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The container id of the inventory, used for NMS methods
|
||||||
|
*/
|
||||||
|
private int containerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inventory that is used on the Bukkit side of things
|
||||||
|
*/
|
||||||
|
private Inventory inventory;
|
||||||
|
/**
|
||||||
|
* The listener holder class
|
||||||
|
*/
|
||||||
|
private final ListenUp listener = new ListenUp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the state of the inventory being open
|
||||||
|
*/
|
||||||
|
private boolean open;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual container backing the Anvil GUI
|
||||||
|
*/
|
||||||
|
private VersionWrapper.AnvilContainerWrapper container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AnvilGUI
|
||||||
|
*
|
||||||
|
* @param plugin A {@link org.bukkit.plugin.java.JavaPlugin} instance
|
||||||
|
* @param player The {@link Player} to open the inventory for
|
||||||
|
* @param mainThreadExecutor An {@link Executor} that executes on the main server thread
|
||||||
|
* @param titleComponent What to have the text already set to
|
||||||
|
* @param initialContents The initial contents of the inventory
|
||||||
|
* @param preventClose Whether to prevent the inventory from closing
|
||||||
|
* @param geyserCompatibility Whether to enable compatibility with Geyser software
|
||||||
|
* @param closeListener A {@link Consumer} when the inventory closes
|
||||||
|
* @param concurrentClickHandlerExecution Flag to allow concurrent execution of the click handler
|
||||||
|
* @param clickHandler A {@link ClickHandler} that is called when the player clicks a slot
|
||||||
|
*/
|
||||||
|
private AnvilGUI(
|
||||||
|
Plugin plugin,
|
||||||
|
Player player,
|
||||||
|
Executor mainThreadExecutor,
|
||||||
|
Object titleComponent,
|
||||||
|
ItemStack[] initialContents,
|
||||||
|
boolean preventClose,
|
||||||
|
boolean geyserCompatibility,
|
||||||
|
Set<Integer> interactableSlots,
|
||||||
|
Consumer<StateSnapshot> closeListener,
|
||||||
|
boolean concurrentClickHandlerExecution,
|
||||||
|
ClickHandler clickHandler) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.player = player;
|
||||||
|
this.mainThreadExecutor = mainThreadExecutor;
|
||||||
|
this.titleComponent = titleComponent;
|
||||||
|
this.initialContents = initialContents;
|
||||||
|
this.preventClose = preventClose;
|
||||||
|
this.geyserCompatibility = geyserCompatibility;
|
||||||
|
this.interactableSlots = Collections.unmodifiableSet(interactableSlots);
|
||||||
|
this.closeListener = closeListener;
|
||||||
|
this.concurrentClickHandlerExecution = concurrentClickHandlerExecution;
|
||||||
|
this.clickHandler = clickHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the anvil GUI
|
||||||
|
*/
|
||||||
|
private void openInventory() {
|
||||||
|
Bukkit.getPluginManager().registerEvents(listener, plugin);
|
||||||
|
|
||||||
|
container = WRAPPER.newContainerAnvil(player, titleComponent);
|
||||||
|
|
||||||
|
inventory = container.getBukkitInventory();
|
||||||
|
// We need to use setItem instead of setContents because a Minecraft ContainerAnvil
|
||||||
|
// contains two separate inventories: the result inventory and the ingredients inventory.
|
||||||
|
// The setContents method only updates the ingredients inventory unfortunately,
|
||||||
|
// but setItem handles the index going into the result inventory.
|
||||||
|
for (int i = 0; i < initialContents.length; i++) {
|
||||||
|
inventory.setItem(i, initialContents[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
containerId = WRAPPER.getNextContainerId(player, container);
|
||||||
|
WRAPPER.handleInventoryCloseEvent(player);
|
||||||
|
WRAPPER.sendPacketOpenWindow(player, containerId, titleComponent);
|
||||||
|
WRAPPER.setActiveContainer(player, container);
|
||||||
|
WRAPPER.setActiveContainerId(container, containerId);
|
||||||
|
WRAPPER.addActiveContainerSlotListener(container, player);
|
||||||
|
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the inventory if it's open.
|
||||||
|
*/
|
||||||
|
public void closeInventory() {
|
||||||
|
closeInventory(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the inventory if it's open, only sending the close inventory packets if the arg is true
|
||||||
|
*
|
||||||
|
* @param sendClosePacket Whether to send the close inventory event, packet, etc
|
||||||
|
*/
|
||||||
|
private void closeInventory(boolean sendClosePacket) {
|
||||||
|
if (!open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
open = false;
|
||||||
|
|
||||||
|
HandlerList.unregisterAll(listener);
|
||||||
|
|
||||||
|
if (sendClosePacket) {
|
||||||
|
WRAPPER.handleInventoryCloseEvent(player);
|
||||||
|
WRAPPER.setActiveContainerDefault(player);
|
||||||
|
WRAPPER.sendPacketCloseWindow(player, containerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeListener != null) {
|
||||||
|
closeListener.accept(StateSnapshot.fromAnvilGUI(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the title of the AnvilGUI to the new one.
|
||||||
|
*
|
||||||
|
* @param literalTitle The title to use as literal text
|
||||||
|
* @param preserveRenameText Whether to preserve the entered rename text
|
||||||
|
* @throws IllegalArgumentException when literalTitle is null
|
||||||
|
* @see Builder#title(String)
|
||||||
|
*/
|
||||||
|
public void setTitle(String literalTitle, boolean preserveRenameText) {
|
||||||
|
Validate.notNull(literalTitle, "literalTitle cannot be null");
|
||||||
|
setTitle(WRAPPER.literalChatComponent(literalTitle), preserveRenameText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the title of the AnvilGUI to the new one.
|
||||||
|
*
|
||||||
|
* @param json The json used to parse into a rich chat component
|
||||||
|
* @param preserveRenameText Whether to preserve the entered rename text
|
||||||
|
* @throws IllegalArgumentException when json is null
|
||||||
|
* @see Builder#jsonTitle(String)
|
||||||
|
*/
|
||||||
|
public void setJsonTitle(String json, boolean preserveRenameText) {
|
||||||
|
Validate.notNull(json, "json cannot be null");
|
||||||
|
setTitle(WRAPPER.jsonChatComponent(json), preserveRenameText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the title of the AnvilGUI to the new one.
|
||||||
|
*
|
||||||
|
* @param title The title as a NMS ChatComponent
|
||||||
|
* @param preserveRenameText Whether to preserve the entered rename text
|
||||||
|
*/
|
||||||
|
private void setTitle(Object title, boolean preserveRenameText) {
|
||||||
|
if (!WRAPPER.isCustomTitleSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String renameText = container.getRenameText();
|
||||||
|
WRAPPER.sendPacketOpenWindow(player, containerId, title);
|
||||||
|
if (preserveRenameText) {
|
||||||
|
// The renameText field is marked as @Nullable in newer versions
|
||||||
|
container.setRenameText(renameText == null ? "" : renameText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Bukkit inventory for this anvil gui
|
||||||
|
*
|
||||||
|
* @return the {@link Inventory} for this anvil gui
|
||||||
|
*/
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply holds the listeners for the GUI
|
||||||
|
*/
|
||||||
|
private class ListenUp implements Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean storing the running status of the latest click handler to prevent double execution.
|
||||||
|
* All accesses to this boolean will be from the main server thread, except for the rare event
|
||||||
|
* that the plugin is disabled and the mainThreadExecutor throws an exception
|
||||||
|
*/
|
||||||
|
private boolean clickHandlerRunning = false;
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (!event.getInventory().equals(inventory)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int rawSlot = event.getRawSlot();
|
||||||
|
// ignore items dropped outside the window
|
||||||
|
if (rawSlot == -999) return;
|
||||||
|
|
||||||
|
final Player clicker = (Player) event.getWhoClicked();
|
||||||
|
final Inventory clickedInventory = event.getClickedInventory();
|
||||||
|
|
||||||
|
if (clickedInventory != null) {
|
||||||
|
if (clickedInventory.equals(clicker.getInventory())) {
|
||||||
|
// prevent players from merging items from the anvil inventory
|
||||||
|
if (event.getClick().equals(ClickType.DOUBLE_CLICK)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// prevent shift moving items from players inv to the anvil inventory
|
||||||
|
if (event.isShiftClick()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// prevent players from swapping items in the anvil gui
|
||||||
|
if ((event.getCursor() != null && event.getCursor().getType() != Material.AIR)
|
||||||
|
&& !interactableSlots.contains(rawSlot)
|
||||||
|
&& event.getClickedInventory().equals(inventory)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawSlot < 3 && rawSlot >= 0 || event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) {
|
||||||
|
event.setCancelled(!interactableSlots.contains(rawSlot));
|
||||||
|
if (clickHandlerRunning && !concurrentClickHandlerExecution) {
|
||||||
|
// A click handler is running, don't launch another one
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CompletableFuture<List<ResponseAction>> actionsFuture =
|
||||||
|
clickHandler.apply(rawSlot, StateSnapshot.fromAnvilGUI(AnvilGUI.this));
|
||||||
|
|
||||||
|
final Consumer<List<ResponseAction>> actionsConsumer = actions -> {
|
||||||
|
for (final ResponseAction action : actions) {
|
||||||
|
action.accept(AnvilGUI.this, clicker);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (actionsFuture.isDone()) {
|
||||||
|
// Fast-path without scheduling if clickHandler is performed in sync
|
||||||
|
// Because the future is already completed, .join() will not block the server thread
|
||||||
|
actionsFuture.thenAccept(actionsConsumer).join();
|
||||||
|
} else {
|
||||||
|
clickHandlerRunning = true;
|
||||||
|
// If the plugin is disabled and the Executor throws an exception, the exception will be passed to
|
||||||
|
// the .handle method
|
||||||
|
actionsFuture
|
||||||
|
.thenAcceptAsync(actionsConsumer, mainThreadExecutor)
|
||||||
|
.handle((results, exception) -> {
|
||||||
|
if (exception != null) {
|
||||||
|
plugin.getLogger()
|
||||||
|
.log(
|
||||||
|
Level.SEVERE,
|
||||||
|
"An exception occurred in the AnvilGUI clickHandler",
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
// Whether an exception occurred or not, set running to false
|
||||||
|
clickHandlerRunning = false;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryDrag(InventoryDragEvent event) {
|
||||||
|
if (event.getInventory().equals(inventory)) {
|
||||||
|
for (int slot : Slot.values()) {
|
||||||
|
if (event.getRawSlots().contains(slot)) {
|
||||||
|
event.setCancelled(!interactableSlots.contains(slot));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClose(InventoryCloseEvent event) {
|
||||||
|
if (open && event.getInventory().equals(inventory)) {
|
||||||
|
closeInventory(false);
|
||||||
|
if (preventClose) {
|
||||||
|
mainThreadExecutor.execute(AnvilGUI.this::openInventory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A builder class for an {@link AnvilGUI} object */
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
/** An {@link Executor} that executes tasks on the main server thread */
|
||||||
|
private Executor mainThreadExecutor;
|
||||||
|
/** An {@link Consumer} that is called when the anvil GUI is close */
|
||||||
|
private Consumer<StateSnapshot> closeListener;
|
||||||
|
/** A flag that decides whether the async click handler can be run concurrently */
|
||||||
|
private boolean concurrentClickHandlerExecution = false;
|
||||||
|
/** An {@link Function} that is called when a slot in the inventory has been clicked */
|
||||||
|
private ClickHandler clickHandler;
|
||||||
|
/** A state that decides where the anvil GUI is able to be closed by the user */
|
||||||
|
private boolean preventClose = false;
|
||||||
|
/** A state that determines whether support for Geyser software is enabled */
|
||||||
|
private boolean geyserCompatibility = true;
|
||||||
|
/** A set of integers containing the slot numbers that should be modifiable by the user. */
|
||||||
|
private Set<Integer> interactableSlots = Collections.emptySet();
|
||||||
|
/** The {@link Plugin} that this anvil GUI is associated with */
|
||||||
|
private Plugin plugin;
|
||||||
|
/** The text that will be displayed to the user */
|
||||||
|
private Object titleComponent = WRAPPER.literalChatComponent("Repair & Name");
|
||||||
|
/** The starting text on the item */
|
||||||
|
private String itemText;
|
||||||
|
/** An {@link ItemStack} to be put in the left input slot */
|
||||||
|
private ItemStack itemLeft;
|
||||||
|
/** An {@link ItemStack} to be put in the right input slot */
|
||||||
|
private ItemStack itemRight;
|
||||||
|
/** An {@link ItemStack} to be placed in the output slot */
|
||||||
|
private ItemStack itemOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a custom main server thread executor. Useful for plugins targeting Folia.
|
||||||
|
*
|
||||||
|
* @param executor The executor to run tasks on
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException when the executor is null
|
||||||
|
*/
|
||||||
|
public Builder mainThreadExecutor(Executor executor) {
|
||||||
|
Validate.notNull(executor, "Executor cannot be null");
|
||||||
|
this.mainThreadExecutor = executor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents the closing of the anvil GUI by the user
|
||||||
|
*
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder preventClose() {
|
||||||
|
preventClose = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables compatibility with Geyser software
|
||||||
|
*/
|
||||||
|
public Builder disableGeyserCompat() {
|
||||||
|
geyserCompatibility = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permit the user to modify (take items in and out) the slot numbers provided.
|
||||||
|
*
|
||||||
|
* @param slots A varags param for the slot numbers. You can avoid relying on magic constants by using
|
||||||
|
* the {@link AnvilGUI.Slot} class.
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder interactableSlots(int... slots) {
|
||||||
|
final Set<Integer> newValue = new HashSet<>();
|
||||||
|
for (int slot : slots) {
|
||||||
|
newValue.add(slot);
|
||||||
|
}
|
||||||
|
interactableSlots = newValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for when the inventory is closed
|
||||||
|
*
|
||||||
|
* @param closeListener An {@link Consumer} that is called when the anvil GUI is closed
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException when the closeListener is null
|
||||||
|
*/
|
||||||
|
public Builder onClose(Consumer<StateSnapshot> closeListener) {
|
||||||
|
Validate.notNull(closeListener, "closeListener cannot be null");
|
||||||
|
this.closeListener = closeListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do an action when a slot is clicked in the inventory
|
||||||
|
* <p>
|
||||||
|
* The ClickHandler is only called when the previous execution of the ClickHandler has finished.
|
||||||
|
* To alter this behaviour use {@link #allowConcurrentClickHandlerExecution()}
|
||||||
|
*
|
||||||
|
* @param clickHandler A {@link ClickHandler} that is called when the user clicks a slot. The
|
||||||
|
* {@link Integer} is the slot number corresponding to {@link Slot}, the
|
||||||
|
* {@link StateSnapshot} contains information about the current state of the anvil,
|
||||||
|
* and the response is a {@link CompletableFuture} that will eventually return a
|
||||||
|
* list of {@link ResponseAction} to execute in the order that they are supplied.
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException when the function supplied is null
|
||||||
|
*/
|
||||||
|
public Builder onClickAsync(ClickHandler clickHandler) {
|
||||||
|
Validate.notNull(clickHandler, "click function cannot be null");
|
||||||
|
this.clickHandler = clickHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, the {@link #onClickAsync(ClickHandler) async click handler} will not run concurrently
|
||||||
|
* and instead wait for the previous {@link CompletableFuture} to finish before executing it again.
|
||||||
|
* <p>
|
||||||
|
* If this trait is desired, it can be enabled by calling this method but may lead to inconsistent
|
||||||
|
* behaviour if not handled properly.
|
||||||
|
*
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder allowConcurrentClickHandlerExecution() {
|
||||||
|
this.concurrentClickHandlerExecution = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do an action when a slot is clicked in the inventory
|
||||||
|
*
|
||||||
|
* @param clickHandler A {@link BiFunction} that is called when the user clicks a slot. The
|
||||||
|
* {@link Integer} is the slot number corresponding to {@link Slot}, the
|
||||||
|
* {@link StateSnapshot} contains information about the current state of the anvil,
|
||||||
|
* and the response is a list of {@link ResponseAction} to execute in the order
|
||||||
|
* that they are supplied.
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException when the function supplied is null
|
||||||
|
*/
|
||||||
|
public Builder onClick(BiFunction<Integer, StateSnapshot, List<ResponseAction>> clickHandler) {
|
||||||
|
Validate.notNull(clickHandler, "click function cannot be null");
|
||||||
|
this.clickHandler =
|
||||||
|
(slot, stateSnapshot) -> CompletableFuture.completedFuture(clickHandler.apply(slot, stateSnapshot));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the plugin for the {@link AnvilGUI}
|
||||||
|
*
|
||||||
|
* @param plugin The {@link Plugin} this anvil GUI is associated with
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException if the plugin is null
|
||||||
|
*/
|
||||||
|
public Builder plugin(Plugin plugin) {
|
||||||
|
Validate.notNull(plugin, "Plugin cannot be null");
|
||||||
|
this.plugin = plugin;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the initial item-text that is displayed to the user.
|
||||||
|
* <br><br>
|
||||||
|
* If the usage of Adventure Components is desired, you must create an item, set the displayname of it
|
||||||
|
* and put it into the AnvilGUI via {@link #itemLeft(ItemStack)} manually.
|
||||||
|
*
|
||||||
|
* @param text The initial name of the item in the anvil
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException if the text is null
|
||||||
|
*/
|
||||||
|
public Builder text(String text) {
|
||||||
|
Validate.notNull(text, "Text cannot be null");
|
||||||
|
this.itemText = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the AnvilGUI title that is to be displayed to the user.
|
||||||
|
* <br>
|
||||||
|
* The provided title will be treated as literal text.
|
||||||
|
*
|
||||||
|
* @param title The title that is to be displayed to the user
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException if the title is null
|
||||||
|
*/
|
||||||
|
public Builder title(String title) {
|
||||||
|
Validate.notNull(title, "title cannot be null");
|
||||||
|
this.titleComponent = WRAPPER.literalChatComponent(title);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the AnvilGUI title that is to be displayed to the user.
|
||||||
|
* <br>
|
||||||
|
* The provided json will be parsed into rich chat components.
|
||||||
|
*
|
||||||
|
* @param json The title that is to be displayed to the user
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException if the title is null
|
||||||
|
* @see net.md_5.bungee.chat.ComponentSerializer#toString(BaseComponent)
|
||||||
|
*/
|
||||||
|
public Builder jsonTitle(String json) {
|
||||||
|
Validate.notNull(json, "json cannot be null");
|
||||||
|
this.titleComponent = WRAPPER.jsonChatComponent(json);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link ItemStack} to be put in the first slot
|
||||||
|
*
|
||||||
|
* @param item The {@link ItemStack} to be put in the first slot
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
* @throws IllegalArgumentException if the {@link ItemStack} is null
|
||||||
|
*/
|
||||||
|
public Builder itemLeft(ItemStack item) {
|
||||||
|
Validate.notNull(item, "item cannot be null");
|
||||||
|
this.itemLeft = item;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link ItemStack} to be put in the second slot
|
||||||
|
*
|
||||||
|
* @param item The {@link ItemStack} to be put in the second slot
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder itemRight(ItemStack item) {
|
||||||
|
this.itemRight = item;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link ItemStack} to be put in the output slot
|
||||||
|
*
|
||||||
|
* @param item The {@link ItemStack} to be put in the output slot
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder itemOutput(ItemStack item) {
|
||||||
|
this.itemOutput = item;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the anvil GUI and opens it for the player
|
||||||
|
*
|
||||||
|
* @param player The {@link Player} the anvil GUI should open for
|
||||||
|
* @return The {@link AnvilGUI} instance from this builder
|
||||||
|
* @throws IllegalArgumentException when the onClick function, plugin, or player is null
|
||||||
|
*/
|
||||||
|
public AnvilGUI open(Player player) {
|
||||||
|
Validate.notNull(plugin, "Plugin cannot be null");
|
||||||
|
Validate.notNull(clickHandler, "click handler cannot be null");
|
||||||
|
Validate.notNull(player, "Player cannot be null");
|
||||||
|
|
||||||
|
if (itemText != null) {
|
||||||
|
if (itemLeft == null) {
|
||||||
|
itemLeft = new ItemStack(Material.PAPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemMeta paperMeta = itemLeft.getItemMeta();
|
||||||
|
paperMeta.setDisplayName(itemText);
|
||||||
|
itemLeft.setItemMeta(paperMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no executor is specified, execute all tasks with the BukkitScheduler
|
||||||
|
if (mainThreadExecutor == null) {
|
||||||
|
mainThreadExecutor = task -> Bukkit.getScheduler().runTask(plugin, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AnvilGUI anvilGUI = new AnvilGUI(
|
||||||
|
plugin,
|
||||||
|
player,
|
||||||
|
mainThreadExecutor,
|
||||||
|
titleComponent,
|
||||||
|
new ItemStack[] {itemLeft, itemRight, itemOutput},
|
||||||
|
preventClose,
|
||||||
|
geyserCompatibility,
|
||||||
|
interactableSlots,
|
||||||
|
closeListener,
|
||||||
|
concurrentClickHandlerExecution,
|
||||||
|
clickHandler);
|
||||||
|
anvilGUI.openInventory();
|
||||||
|
return anvilGUI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler that is called when the user clicks a slot. The
|
||||||
|
* {@link Integer} is the slot number corresponding to {@link Slot}, the
|
||||||
|
* {@link StateSnapshot} contains information about the current state of the anvil,
|
||||||
|
* and the response is a {@link CompletableFuture} that will eventually return a
|
||||||
|
* list of {@link ResponseAction} to execute in the order that they are supplied.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ClickHandler extends BiFunction<Integer, StateSnapshot, CompletableFuture<List<ResponseAction>>> {}
|
||||||
|
|
||||||
|
/** An action to run in response to a player clicking the output slot in the GUI. This interface is public
|
||||||
|
* and permits you, the developer, to add additional response features easily to your custom AnvilGUIs. */
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ResponseAction extends BiConsumer<AnvilGUI, Player> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the input text box value with the provided text value.
|
||||||
|
*
|
||||||
|
* Before using this method, it must be verified by the caller that items are either in
|
||||||
|
* {@link Slot#INPUT_LEFT} or {@link Slot#OUTPUT} present.
|
||||||
|
*
|
||||||
|
* @param text The text to write in the input box
|
||||||
|
* @return The {@link ResponseAction} to achieve the text replacement
|
||||||
|
* @throws IllegalArgumentException when the text is null
|
||||||
|
* @throws IllegalStateException when the slots {@link Slot#INPUT_LEFT} and {@link Slot#OUTPUT} are <code>null</code>
|
||||||
|
*/
|
||||||
|
static ResponseAction replaceInputText(String text) {
|
||||||
|
Validate.notNull(text, "text cannot be null");
|
||||||
|
return (anvilgui, player) -> {
|
||||||
|
ItemStack item = anvilgui.getInventory().getItem(Slot.OUTPUT);
|
||||||
|
if (item == null) {
|
||||||
|
// Fallback on left input slot if player hasn't typed anything yet
|
||||||
|
item = anvilgui.getInventory().getItem(Slot.INPUT_LEFT);
|
||||||
|
}
|
||||||
|
if (item == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"replaceInputText can only be used if slots OUTPUT or INPUT_LEFT are not empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
final ItemStack cloned = item.clone();
|
||||||
|
final ItemMeta meta = cloned.getItemMeta();
|
||||||
|
meta.setDisplayName(text);
|
||||||
|
cloned.setItemMeta(meta);
|
||||||
|
anvilgui.getInventory().setItem(Slot.INPUT_LEFT, cloned);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the title of the AnvilGUI to the new one.
|
||||||
|
*
|
||||||
|
* @param literalTitle The title to use as literal text
|
||||||
|
* @param preserveRenameText Whether to preserve the entered rename text
|
||||||
|
* @throws IllegalArgumentException when literalTitle is null
|
||||||
|
* @see Builder#title(String)
|
||||||
|
*/
|
||||||
|
static ResponseAction updateTitle(String literalTitle, boolean preserveRenameText) {
|
||||||
|
Validate.notNull(literalTitle, "literalTitle cannot be null");
|
||||||
|
return (anvilGUI, player) -> anvilGUI.setTitle(literalTitle, preserveRenameText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the title of the AnvilGUI to the new one.
|
||||||
|
*
|
||||||
|
* @param json The json used to parse into a rich chat component
|
||||||
|
* @param preserveRenameText Whether to preserve the entered rename text
|
||||||
|
* @throws IllegalArgumentException when json is null
|
||||||
|
* @see Builder#jsonTitle(String)
|
||||||
|
*/
|
||||||
|
static ResponseAction updateJsonTitle(String json, boolean preserveRenameText) {
|
||||||
|
Validate.notNull(json, "json cannot be null");
|
||||||
|
return (anvilGUI, player) -> anvilGUI.setJsonTitle(json, preserveRenameText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open another inventory
|
||||||
|
* @param otherInventory The inventory to open
|
||||||
|
* @return The {@link ResponseAction} to achieve the inventory open
|
||||||
|
* @throws IllegalArgumentException when the otherInventory is null
|
||||||
|
*/
|
||||||
|
static ResponseAction openInventory(Inventory otherInventory) {
|
||||||
|
Validate.notNull(otherInventory, "otherInventory cannot be null");
|
||||||
|
return (anvilgui, player) -> player.openInventory(otherInventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the AnvilGUI
|
||||||
|
* @return The {@link ResponseAction} to achieve closing the AnvilGUI
|
||||||
|
*/
|
||||||
|
static ResponseAction close() {
|
||||||
|
return (anvilgui, player) -> anvilgui.closeInventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the provided runnable
|
||||||
|
* @param runnable The runnable to run
|
||||||
|
* @return The {@link ResponseAction} to achieve running the runnable
|
||||||
|
* @throws IllegalArgumentException when the runnable is null
|
||||||
|
*/
|
||||||
|
static ResponseAction run(Runnable runnable) {
|
||||||
|
Validate.notNull(runnable, "runnable cannot be null");
|
||||||
|
return (anvilgui, player) -> runnable.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class wrapping the magic constants of slot numbers in an anvil GUI
|
||||||
|
*/
|
||||||
|
public static class Slot {
|
||||||
|
|
||||||
|
private static final int[] values = new int[] {Slot.INPUT_LEFT, Slot.INPUT_RIGHT, Slot.OUTPUT};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The slot on the far left, where the first input is inserted. An {@link ItemStack} is always inserted
|
||||||
|
* here to be renamed
|
||||||
|
*/
|
||||||
|
public static final int INPUT_LEFT = 0;
|
||||||
|
/**
|
||||||
|
* Not used, but in a real anvil you are able to put the second item you want to combine here
|
||||||
|
*/
|
||||||
|
public static final int INPUT_RIGHT = 1;
|
||||||
|
/**
|
||||||
|
* The output slot, where an item is put when two items are combined from {@link #INPUT_LEFT} and
|
||||||
|
* {@link #INPUT_RIGHT} or {@link #INPUT_LEFT} is renamed
|
||||||
|
*/
|
||||||
|
public static final int OUTPUT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all anvil slot values
|
||||||
|
*
|
||||||
|
* @return The array containing all possible anvil slots
|
||||||
|
*/
|
||||||
|
public static int[] values() {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents a snapshot of the state of an AnvilGUI */
|
||||||
|
public static final class StateSnapshot {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an {@link StateSnapshot} from the current state of an {@link AnvilGUI}
|
||||||
|
* @param anvilGUI The instance to take the snapshot of
|
||||||
|
* @return The snapshot
|
||||||
|
*/
|
||||||
|
private static StateSnapshot fromAnvilGUI(AnvilGUI anvilGUI) {
|
||||||
|
final Inventory inventory = anvilGUI.getInventory();
|
||||||
|
return new StateSnapshot(
|
||||||
|
itemNotNull(inventory.getItem(Slot.INPUT_LEFT)).clone(),
|
||||||
|
itemNotNull(inventory.getItem(Slot.INPUT_RIGHT)).clone(),
|
||||||
|
itemNotNull(inventory.getItem(Slot.OUTPUT)).clone(),
|
||||||
|
anvilGUI.player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ItemStack} in the anvilGui slots
|
||||||
|
*/
|
||||||
|
private final ItemStack leftItem, rightItem, outputItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Player} that clicked the output slot
|
||||||
|
*/
|
||||||
|
private final Player player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event parameter constructor
|
||||||
|
* @param leftItem The left item in the combine slot of the anvilGUI
|
||||||
|
* @param rightItem The right item in the combine slot of the anvilGUI
|
||||||
|
* @param outputItem The item that would have been outputted, when the items would have been combined
|
||||||
|
* @param player The player that clicked the output slot
|
||||||
|
*/
|
||||||
|
public StateSnapshot(ItemStack leftItem, ItemStack rightItem, ItemStack outputItem, Player player) {
|
||||||
|
this.leftItem = leftItem;
|
||||||
|
this.rightItem = rightItem;
|
||||||
|
this.outputItem = outputItem;
|
||||||
|
this.player = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns the item in the left combine slot of the gui
|
||||||
|
*
|
||||||
|
* @return The leftItem
|
||||||
|
*/
|
||||||
|
public ItemStack getLeftItem() {
|
||||||
|
return leftItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns the item in the right combine slot of the gui
|
||||||
|
*
|
||||||
|
* @return The rightItem
|
||||||
|
*/
|
||||||
|
public ItemStack getRightItem() {
|
||||||
|
return rightItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns the output item that would have been the result
|
||||||
|
* by combining the left and right one
|
||||||
|
*
|
||||||
|
* @return The outputItem
|
||||||
|
*/
|
||||||
|
public ItemStack getOutputItem() {
|
||||||
|
return outputItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns the player that clicked onto the output slot
|
||||||
|
*
|
||||||
|
* @return The player
|
||||||
|
*/
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns the text the player typed into the rename field
|
||||||
|
*
|
||||||
|
* @return The text of the rename field
|
||||||
|
*/
|
||||||
|
public String getText() {
|
||||||
|
return outputItem.hasItemMeta() ? outputItem.getItemMeta().getDisplayName() : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/main/java/net/wesjd/anvilgui/LICENSE
Normal file
21
src/main/java/net/wesjd/anvilgui/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Wesley Smith
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,135 @@
|
||||||
|
package net.wesjd.anvilgui.version;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.component.DataComponentPatch;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundContainerClosePacket;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.inventory.*;
|
||||||
|
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public final class PaperWrapper1_21_R4 implements VersionWrapper {
|
||||||
|
private int getRealNextContainerId(Player player) {
|
||||||
|
return toNMS(player).nextContainerCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ServerPlayer toNMS(Player player) {
|
||||||
|
return ((CraftPlayer) player).getHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNextContainerId(Player player, AnvilContainerWrapper container) {
|
||||||
|
return ((AnvilContainer) container).containerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleInventoryCloseEvent(Player player) {
|
||||||
|
//CraftEventFactory.handleInventoryCloseEvent(toNMS(player));
|
||||||
|
toNMS(player).doCloseContainer(); // q -> doCloseContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendPacketOpenWindow(Player player, int containerId, Object inventoryTitle) {
|
||||||
|
toNMS(player).connection.send(new ClientboundOpenScreenPacket(containerId, MenuType.ANVIL, Component.literal(inventoryTitle.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendPacketCloseWindow(Player player, int containerId) {
|
||||||
|
toNMS(player).connection.send(new ClientboundContainerClosePacket(containerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendPacketExperienceChange(Player player, int experienceLevel) {
|
||||||
|
toNMS(player).connection.send(new ClientboundSetExperiencePacket(0f, 0, experienceLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActiveContainerDefault(Player player) {
|
||||||
|
toNMS(player).containerMenu = toNMS(player).inventoryMenu; // cd -> containerMenu, cc -> inventoryMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActiveContainer(Player player, AnvilContainerWrapper container) {
|
||||||
|
toNMS(player).containerMenu = (AbstractContainerMenu) container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActiveContainerId(AnvilContainerWrapper container, int containerId) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addActiveContainerSlotListener(AnvilContainerWrapper container, Player player) {
|
||||||
|
toNMS(player).initMenu((AbstractContainerMenu) container);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnvilContainerWrapper newContainerAnvil(Player player, Object title) {
|
||||||
|
return new AnvilContainer(player, getRealNextContainerId(player), Component.literal(title.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object literalChatComponent(String content) {
|
||||||
|
return Component.literal(content); // IChatBaseComponent.b -> Component.literal
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object jsonChatComponent(String json) {
|
||||||
|
return Component.Serializer.toJson(Component.literal(json), RegistryAccess.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AnvilContainer extends AnvilMenu implements AnvilContainerWrapper {
|
||||||
|
public AnvilContainer(Player player, int containerId, Component guiTitle) {
|
||||||
|
super(
|
||||||
|
containerId,
|
||||||
|
((CraftPlayer) player).getHandle().getInventory(),
|
||||||
|
ContainerLevelAccess.create(((CraftPlayer) player).getHandle().level(), BlockPos.ZERO)
|
||||||
|
);
|
||||||
|
this.checkReachable = false;
|
||||||
|
setTitle(guiTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createResult() {
|
||||||
|
// If the output is empty, copy the left input into the output
|
||||||
|
Slot output = getSlot(2); // b -> getSlot
|
||||||
|
if (!output.hasItem()) { // h -> hasItem
|
||||||
|
output.set(getSlot(0).getItem().copy()); // f -> set, g -> getItem, v -> copy
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cost.set(0); // y -> cost, a -> set
|
||||||
|
|
||||||
|
// Sync to the client
|
||||||
|
this.sendAllDataToRemote(); // b -> sendAllDataToRemote
|
||||||
|
this.broadcastChanges(); // d -> broadcastChanges
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setItemName(@NotNull String itemName) {
|
||||||
|
Slot inputLeft = getSlot(0);
|
||||||
|
if (inputLeft.hasItem()) {
|
||||||
|
inputLeft
|
||||||
|
.getItem()
|
||||||
|
.applyComponents(DataComponentPatch
|
||||||
|
.builder()
|
||||||
|
.set(DataComponents.CUSTOM_NAME, Component.literal(itemName))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getBukkitInventory() {
|
||||||
|
return this.getBukkitView().getTopInventory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/main/java/net/wesjd/anvilgui/version/VersionMatcher.java
Normal file
51
src/main/java/net/wesjd/anvilgui/version/VersionMatcher.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package net.wesjd.anvilgui.version;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the server's NMS version to its {@link VersionWrapper}
|
||||||
|
*
|
||||||
|
* @author Wesley Smith
|
||||||
|
* @since 1.2.1
|
||||||
|
*/
|
||||||
|
public class VersionMatcher {
|
||||||
|
/** Maps a Minecraft version string to the corresponding revision string */
|
||||||
|
private static final Map<String, String> VERSION_TO_REVISION = new HashMap<String, String>() {
|
||||||
|
{
|
||||||
|
this.put("1.21.4", "1_21_R4");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/* This needs to be updated to reflect the newest available version wrapper */
|
||||||
|
private static final String FALLBACK_REVISION = "1_21_R4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the server version to it's {@link VersionWrapper}
|
||||||
|
*
|
||||||
|
* @return The {@link VersionWrapper} for this server
|
||||||
|
* @throws IllegalStateException If the version wrapper failed to be instantiated or is unable to be found
|
||||||
|
*/
|
||||||
|
public VersionWrapper match() {
|
||||||
|
String craftBukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
|
|
||||||
|
String rVersion;
|
||||||
|
if (!craftBukkitPackage.contains(".v")) { // cb package not relocated (i.e. paper 1.20.5+)
|
||||||
|
final String version = Bukkit.getBukkitVersion().split("-")[0];
|
||||||
|
rVersion = VERSION_TO_REVISION.getOrDefault(version, FALLBACK_REVISION);
|
||||||
|
} else {
|
||||||
|
rVersion = craftBukkitPackage.split("\\.")[3].substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (VersionWrapper) Class.forName(getClass().getPackage().getName() + ".PaperWrapper" + rVersion)
|
||||||
|
.getDeclaredConstructor()
|
||||||
|
.newInstance();
|
||||||
|
} catch (ClassNotFoundException exception) {
|
||||||
|
throw new IllegalStateException("AnvilGUI does not support server version \"" + rVersion + "\"", exception);
|
||||||
|
} catch (ReflectiveOperationException exception) {
|
||||||
|
throw new IllegalStateException("Failed to instantiate version wrapper for version " + rVersion, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/main/java/net/wesjd/anvilgui/version/VersionWrapper.java
Normal file
150
src/main/java/net/wesjd/anvilgui/version/VersionWrapper.java
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package net.wesjd.anvilgui.version;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps versions to be able to easily use different NMS server versions
|
||||||
|
*
|
||||||
|
* @author Wesley Smith
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public interface VersionWrapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the next available NMS container id for the player
|
||||||
|
*
|
||||||
|
* @param player The player to get the next container id of
|
||||||
|
* @param container The container that a new id is being generated for
|
||||||
|
* @return The next available NMS container id
|
||||||
|
*/
|
||||||
|
int getNextContainerId(Player player, AnvilContainerWrapper container);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current inventory for the player
|
||||||
|
*
|
||||||
|
* @param player The player that needs their current inventory closed
|
||||||
|
*/
|
||||||
|
void handleInventoryCloseEvent(Player player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends PacketPlayOutOpenWindow to the player with the container id and window title
|
||||||
|
*
|
||||||
|
* @param player The player to send the packet to
|
||||||
|
* @param containerId The container id to open
|
||||||
|
* @param inventoryTitle The title of the inventory to be opened (only works in Minecraft 1.14 and above)
|
||||||
|
*/
|
||||||
|
void sendPacketOpenWindow(Player player, int containerId, Object inventoryTitle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends PacketPlayOutCloseWindow to the player with the container id
|
||||||
|
*
|
||||||
|
* @param player The player to send the packet to
|
||||||
|
* @param containerId The container id to close
|
||||||
|
*/
|
||||||
|
void sendPacketCloseWindow(Player player, int containerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends PacketPlayOutExperience to the player with the experience level
|
||||||
|
*
|
||||||
|
* @param player The player to send the packet to
|
||||||
|
* @param experienceLevel The experience level to set
|
||||||
|
*/
|
||||||
|
void sendPacketExperienceChange(Player player, int experienceLevel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the NMS player's active container to the default one
|
||||||
|
*
|
||||||
|
* @param player The player to set the active container of
|
||||||
|
*/
|
||||||
|
void setActiveContainerDefault(Player player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the NMS player's active container to the one supplied
|
||||||
|
*
|
||||||
|
* @param player The player to set the active container of
|
||||||
|
* @param container The container to set as active
|
||||||
|
*/
|
||||||
|
void setActiveContainer(Player player, AnvilContainerWrapper container);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the supplied windowId of the supplied Container
|
||||||
|
*
|
||||||
|
* @param container The container to set the windowId of
|
||||||
|
* @param containerId The new windowId
|
||||||
|
*/
|
||||||
|
void setActiveContainerId(AnvilContainerWrapper container, int containerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a slot listener to the supplied container for the player
|
||||||
|
*
|
||||||
|
* @param container The container to add the slot listener to
|
||||||
|
* @param player The player to have as a listener
|
||||||
|
*/
|
||||||
|
void addActiveContainerSlotListener(AnvilContainerWrapper container, Player player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ContainerAnvil
|
||||||
|
*
|
||||||
|
* @param player The player to get the container of
|
||||||
|
* @param title The title of the anvil inventory
|
||||||
|
* @return The Container instance
|
||||||
|
*/
|
||||||
|
AnvilContainerWrapper newContainerAnvil(Player player, Object title);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current Minecraft version actually supports custom titles
|
||||||
|
*
|
||||||
|
* @return The current supported state
|
||||||
|
*/
|
||||||
|
default boolean isCustomTitleSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new chat component that does not handle the content in any special way
|
||||||
|
*
|
||||||
|
* @param content The content to display
|
||||||
|
* @return Version-specific ChatComponent instance
|
||||||
|
*/
|
||||||
|
Object literalChatComponent(String content);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new rich chat component from the provided json
|
||||||
|
*
|
||||||
|
* @param json The component to parse
|
||||||
|
* @return Version-specific ChatComponent instance
|
||||||
|
*/
|
||||||
|
Object jsonChatComponent(String json);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface implemented by the custom NMS AnvilContainer used to interact with it directly
|
||||||
|
*/
|
||||||
|
interface AnvilContainerWrapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the raw text that has been entered into the Anvil at the moment
|
||||||
|
* <br><br>
|
||||||
|
* This field is marked as public in the Minecraft AnvilContainer only from Minecraft 1.11 and upwards
|
||||||
|
*
|
||||||
|
* @return The raw text in the rename field
|
||||||
|
*/
|
||||||
|
default String getRenameText() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the provided text as the literal hovername of the item in the left input slot
|
||||||
|
*
|
||||||
|
* @param text The text to set
|
||||||
|
*/
|
||||||
|
default void setRenameText(String text) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link Inventory} wrapper of the NMS container
|
||||||
|
*
|
||||||
|
* @return The inventory of the NMS container
|
||||||
|
*/
|
||||||
|
Inventory getBukkitInventory();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package xyz.ineanto.nicko;
|
||||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.command.PluginCommand;
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import xyz.ineanto.nicko.appearance.random.RandomNameFetcher;
|
import xyz.ineanto.nicko.appearance.random.RandomNameFetcher;
|
||||||
import xyz.ineanto.nicko.command.NickoCommand;
|
import xyz.ineanto.nicko.command.NickoCommand;
|
||||||
|
@ -21,6 +20,7 @@ import xyz.ineanto.nicko.storage.PlayerDataStore;
|
||||||
import xyz.ineanto.nicko.storage.json.JSONStorage;
|
import xyz.ineanto.nicko.storage.json.JSONStorage;
|
||||||
import xyz.ineanto.nicko.storage.map.MapCache;
|
import xyz.ineanto.nicko.storage.map.MapCache;
|
||||||
import xyz.ineanto.nicko.storage.name.PlayerNameStore;
|
import xyz.ineanto.nicko.storage.name.PlayerNameStore;
|
||||||
|
import xyz.xenondevs.invui.InvUI;
|
||||||
import xyz.xenondevs.invui.gui.structure.Structure;
|
import xyz.xenondevs.invui.gui.structure.Structure;
|
||||||
import xyz.xenondevs.invui.item.builder.ItemBuilder;
|
import xyz.xenondevs.invui.item.builder.ItemBuilder;
|
||||||
import xyz.xenondevs.invui.item.impl.SimpleItem;
|
import xyz.xenondevs.invui.item.impl.SimpleItem;
|
||||||
|
@ -30,8 +30,6 @@ import java.io.IOException;
|
||||||
public class Nicko extends JavaPlugin {
|
public class Nicko extends JavaPlugin {
|
||||||
private static Nicko plugin;
|
private static Nicko plugin;
|
||||||
|
|
||||||
private final boolean unitTesting;
|
|
||||||
|
|
||||||
private MojangAPI mojangAPI;
|
private MojangAPI mojangAPI;
|
||||||
private PlayerDataStore dataStore;
|
private PlayerDataStore dataStore;
|
||||||
private ConfigurationManager configurationManager;
|
private ConfigurationManager configurationManager;
|
||||||
|
@ -40,19 +38,6 @@ public class Nicko extends JavaPlugin {
|
||||||
private PlayerNameStore nameStore;
|
private PlayerNameStore nameStore;
|
||||||
private RandomNameFetcher nameFetcher;
|
private RandomNameFetcher nameFetcher;
|
||||||
|
|
||||||
public Nicko() {
|
|
||||||
this.unitTesting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by MockBukkit
|
|
||||||
*/
|
|
||||||
protected Nicko(Configuration configuration) {
|
|
||||||
this.unitTesting = true;
|
|
||||||
this.configuration = configuration;
|
|
||||||
getLogger().info("Unit Testing Mode enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
plugin = this;
|
plugin = this;
|
||||||
|
@ -96,41 +81,37 @@ public class Nicko extends JavaPlugin {
|
||||||
dataStore.setCache(cache);
|
dataStore.setCache(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unitTesting) {
|
nameStore = new PlayerNameStore();
|
||||||
nameStore = new PlayerNameStore();
|
mojangAPI = new MojangAPI();
|
||||||
mojangAPI = new MojangAPI();
|
nameFetcher = new RandomNameFetcher(this);
|
||||||
nameFetcher = new RandomNameFetcher(this);
|
|
||||||
|
|
||||||
new ConfigurationMigrator(this).migrate();
|
new ConfigurationMigrator(this).migrate();
|
||||||
|
InvUI.getInstance().setPlugin(this);
|
||||||
|
|
||||||
if (configuration.isCustomLocale()) {
|
if (configuration.isCustomLocale()) {
|
||||||
try {
|
try {
|
||||||
CustomLanguage.dumpIntoFile(Language.ENGLISH);
|
CustomLanguage.dumpIntoFile(Language.ENGLISH);
|
||||||
customLanguage = new CustomLanguage();
|
customLanguage = new CustomLanguage();
|
||||||
new CustomLocaleMigrator(this, customLanguage).migrate();
|
new CustomLocaleMigrator(this, customLanguage).migrate();
|
||||||
getLogger().info("Successfully loaded the custom locale.");
|
getLogger().info("Successfully loaded the custom locale.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
getLogger().severe("Failed to load the custom locale!");
|
getLogger().severe("Failed to load the custom locale!");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final PluginCommand command = getCommand("nicko");
|
|
||||||
if (command != null) {
|
|
||||||
command.setExecutor(new NickoCommand());
|
|
||||||
}
|
|
||||||
|
|
||||||
Structure.addGlobalIngredient('#', new SimpleItem(new ItemBuilder(Material.AIR)));
|
|
||||||
Structure.addGlobalIngredient('%', new SimpleItem(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setDisplayName(" ")));
|
|
||||||
|
|
||||||
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
|
||||||
getLogger().info("Enabling PlaceHolderAPI support...");
|
|
||||||
new NickoExpansion(this).register();
|
|
||||||
}
|
|
||||||
|
|
||||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this);
|
|
||||||
getServer().getPluginManager().registerEvents(new PlayerQuitListener(), this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerCommand("nicko", new NickoCommand());
|
||||||
|
|
||||||
|
Structure.addGlobalIngredient('#', new SimpleItem(new ItemBuilder(Material.AIR)));
|
||||||
|
Structure.addGlobalIngredient('%', new SimpleItem(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setDisplayName(" ")));
|
||||||
|
|
||||||
|
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||||
|
getLogger().info("Enabling PlaceHolderAPI support...");
|
||||||
|
new NickoExpansion(this).register();
|
||||||
|
}
|
||||||
|
|
||||||
|
getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this);
|
||||||
|
getServer().getPluginManager().registerEvents(new PlayerQuitListener(), this);
|
||||||
|
|
||||||
getLogger().info("Nicko has been enabled.");
|
getLogger().info("Nicko has been enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,9 +126,7 @@ public class Nicko extends JavaPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unitTesting) {
|
nameStore.clearStoredNames();
|
||||||
nameStore.clearStoredNames();
|
|
||||||
}
|
|
||||||
getLogger().info("Nicko (Bukkit) has been disabled.");
|
getLogger().info("Nicko (Bukkit) has been disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
package xyz.ineanto.nicko.command;
|
package xyz.ineanto.nicko.command;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jspecify.annotations.Nullable;
|
||||||
import xyz.ineanto.nicko.gui.HomeGUI;
|
import xyz.ineanto.nicko.gui.HomeGUI;
|
||||||
import xyz.ineanto.nicko.language.PlayerLanguage;
|
|
||||||
import xyz.ineanto.nicko.language.LanguageKey;
|
|
||||||
|
|
||||||
public class NickoCommand implements CommandExecutor {
|
public class NickoCommand implements BasicCommand {
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
public void execute(CommandSourceStack stack, String[] strings) {
|
||||||
if (sender instanceof Player player) {
|
final Entity executor = stack.getExecutor();
|
||||||
if (player.isOp() || player.hasPermission("nicko.use") || player.hasPermission("nicko.*")) {
|
final Player player = (Player) executor;
|
||||||
new HomeGUI(player).open();
|
|
||||||
} else {
|
|
||||||
final PlayerLanguage playerLanguage = new PlayerLanguage(player);
|
|
||||||
player.sendMessage(playerLanguage.translate(LanguageKey.Error.PERMISSION, true));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sender.sendMessage("This plugin can only be used in-game. Sorry!");
|
new HomeGUI(player).open();
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUse(CommandSender sender) {
|
||||||
|
return sender instanceof Player && sender.isOp() || sender.hasPermission(permission());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String permission() {
|
||||||
|
return "nicko.use";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,12 @@ public class NickoPluginLoader implements PluginLoader {
|
||||||
@Override
|
@Override
|
||||||
public void classloader(PluginClasspathBuilder pluginClasspathBuilder) {
|
public void classloader(PluginClasspathBuilder pluginClasspathBuilder) {
|
||||||
final MavenLibraryResolver resolver = new MavenLibraryResolver();
|
final MavenLibraryResolver resolver = new MavenLibraryResolver();
|
||||||
|
|
||||||
resolver.addRepository(new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases/").build());
|
resolver.addRepository(new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases/").build());
|
||||||
resolver.addRepository(new RemoteRepository.Builder("wesjd", "default", "https://repo.codemc.io/repository/maven-snapshots/").build());
|
resolver.addRepository(new RemoteRepository.Builder("codemc", "default", "https://repo.codemc.io/repository/maven-snapshots/").build());
|
||||||
resolver.addDependency(new Dependency(new DefaultArtifact("xyz.xenondevs.invui:invui:pom:1.44"), null));
|
resolver.addDependency(new Dependency(new DefaultArtifact("xyz.xenondevs.invui:invui:pom:1.44"), null));
|
||||||
resolver.addDependency(new Dependency(new DefaultArtifact("net:wesjd:anvilgui:1.10.4-SNAPSHOT"), null));
|
//resolver.addDependency(new Dependency(new DefaultArtifact("net.wesjd:anvilgui:1.10.4-SNAPSHOT"), null));
|
||||||
|
|
||||||
pluginClasspathBuilder.addLibrary(resolver);
|
pluginClasspathBuilder.addLibrary(resolver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
name: Nicko
|
name: Nicko
|
||||||
main: xyz.ineanto.nicko.Nicko
|
main: xyz.ineanto.nicko.Nicko
|
||||||
|
loader: xyz.ineanto.nicko.loader.NickoPluginLoader
|
||||||
version: ${version}
|
version: ${version}
|
||||||
author: Ineanto
|
author: Ineanto
|
||||||
description: "The feature packed, next generation disguise plugin for Minecraft."
|
description: "The feature packed, next generation disguise plugin for Minecraft."
|
||||||
api-version: 1.21
|
api-version: "1.21"
|
||||||
softdepend: [ PlaceholderAPI ]
|
softdepend: [ PlaceholderAPI ]
|
||||||
depend:
|
depend:
|
||||||
- ProtocolLib
|
- ProtocolLib
|
||||||
load: POSTWORLD
|
|
||||||
commands:
|
# Suppose we require ProtocolLib to be loaded for our plugin
|
||||||
nicko:
|
AnvilGUI:
|
||||||
description: "Opens Nicko's GUI."
|
join-classpath: true
|
||||||
permission: nicko.use
|
required: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
nicko.*:
|
nicko.*:
|
||||||
default: op
|
default: op
|
Loading…
Add table
Add a link
Reference in a new issue