feat: make nicko work again partially
This commit is contained in:
parent
a996858ba9
commit
4f9e334544
14 changed files with 1939 additions and 77 deletions
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 org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import xyz.ineanto.nicko.appearance.random.RandomNameFetcher;
|
||||
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.map.MapCache;
|
||||
import xyz.ineanto.nicko.storage.name.PlayerNameStore;
|
||||
import xyz.xenondevs.invui.InvUI;
|
||||
import xyz.xenondevs.invui.gui.structure.Structure;
|
||||
import xyz.xenondevs.invui.item.builder.ItemBuilder;
|
||||
import xyz.xenondevs.invui.item.impl.SimpleItem;
|
||||
|
@ -30,8 +30,6 @@ import java.io.IOException;
|
|||
public class Nicko extends JavaPlugin {
|
||||
private static Nicko plugin;
|
||||
|
||||
private final boolean unitTesting;
|
||||
|
||||
private MojangAPI mojangAPI;
|
||||
private PlayerDataStore dataStore;
|
||||
private ConfigurationManager configurationManager;
|
||||
|
@ -40,19 +38,6 @@ public class Nicko extends JavaPlugin {
|
|||
private PlayerNameStore nameStore;
|
||||
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
|
||||
public void onEnable() {
|
||||
plugin = this;
|
||||
|
@ -96,41 +81,37 @@ public class Nicko extends JavaPlugin {
|
|||
dataStore.setCache(cache);
|
||||
}
|
||||
|
||||
if (!unitTesting) {
|
||||
nameStore = new PlayerNameStore();
|
||||
mojangAPI = new MojangAPI();
|
||||
nameFetcher = new RandomNameFetcher(this);
|
||||
nameStore = new PlayerNameStore();
|
||||
mojangAPI = new MojangAPI();
|
||||
nameFetcher = new RandomNameFetcher(this);
|
||||
|
||||
new ConfigurationMigrator(this).migrate();
|
||||
new ConfigurationMigrator(this).migrate();
|
||||
InvUI.getInstance().setPlugin(this);
|
||||
|
||||
if (configuration.isCustomLocale()) {
|
||||
try {
|
||||
CustomLanguage.dumpIntoFile(Language.ENGLISH);
|
||||
customLanguage = new CustomLanguage();
|
||||
new CustomLocaleMigrator(this, customLanguage).migrate();
|
||||
getLogger().info("Successfully loaded the custom locale.");
|
||||
} catch (IOException e) {
|
||||
getLogger().severe("Failed to load the custom locale!");
|
||||
}
|
||||
if (configuration.isCustomLocale()) {
|
||||
try {
|
||||
CustomLanguage.dumpIntoFile(Language.ENGLISH);
|
||||
customLanguage = new CustomLanguage();
|
||||
new CustomLocaleMigrator(this, customLanguage).migrate();
|
||||
getLogger().info("Successfully loaded the custom locale.");
|
||||
} catch (IOException e) {
|
||||
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.");
|
||||
}
|
||||
|
||||
|
@ -145,9 +126,7 @@ public class Nicko extends JavaPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
if (!unitTesting) {
|
||||
nameStore.clearStoredNames();
|
||||
}
|
||||
nameStore.clearStoredNames();
|
||||
getLogger().info("Nicko (Bukkit) has been disabled.");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
package xyz.ineanto.nicko.command;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
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.language.PlayerLanguage;
|
||||
import xyz.ineanto.nicko.language.LanguageKey;
|
||||
|
||||
public class NickoCommand implements CommandExecutor {
|
||||
public class NickoCommand implements BasicCommand {
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (sender instanceof Player player) {
|
||||
if (player.isOp() || player.hasPermission("nicko.use") || player.hasPermission("nicko.*")) {
|
||||
new HomeGUI(player).open();
|
||||
} else {
|
||||
final PlayerLanguage playerLanguage = new PlayerLanguage(player);
|
||||
player.sendMessage(playerLanguage.translate(LanguageKey.Error.PERMISSION, true));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void execute(CommandSourceStack stack, String[] strings) {
|
||||
final Entity executor = stack.getExecutor();
|
||||
final Player player = (Player) executor;
|
||||
|
||||
sender.sendMessage("This plugin can only be used in-game. Sorry!");
|
||||
return false;
|
||||
new HomeGUI(player).open();
|
||||
}
|
||||
|
||||
@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
|
||||
public void classloader(PluginClasspathBuilder pluginClasspathBuilder) {
|
||||
final MavenLibraryResolver resolver = new MavenLibraryResolver();
|
||||
|
||||
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("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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
name: Nicko
|
||||
main: xyz.ineanto.nicko.Nicko
|
||||
loader: xyz.ineanto.nicko.loader.NickoPluginLoader
|
||||
version: ${version}
|
||||
author: Ineanto
|
||||
description: "The feature packed, next generation disguise plugin for Minecraft."
|
||||
api-version: 1.21
|
||||
api-version: "1.21"
|
||||
softdepend: [ PlaceholderAPI ]
|
||||
depend:
|
||||
- ProtocolLib
|
||||
load: POSTWORLD
|
||||
commands:
|
||||
nicko:
|
||||
description: "Opens Nicko's GUI."
|
||||
permission: nicko.use
|
||||
|
||||
# Suppose we require ProtocolLib to be loaded for our plugin
|
||||
AnvilGUI:
|
||||
join-classpath: true
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
nicko.*:
|
||||
default: op
|
Loading…
Add table
Add a link
Reference in a new issue