Merge branch 'development'

This commit is contained in:
Christian Koop 2023-04-13 11:49:07 +02:00
commit 171e4884b0
No known key found for this signature in database
GPG Key ID: 89A8181384E010A3
58 changed files with 1856 additions and 850 deletions

View File

@ -27,7 +27,7 @@ jobs:
# Build remapped Spigot versions
- uses: SpraxDev/Action-SpigotMC@v4
with:
versions: 1.18, 1.18.2, 1.19, 1.19.2, 1.19.3
versions: 1.18, 1.18.2, 1.19, 1.19.2, 1.19.3, 1.19.4
remapped: true
# Build project

View File

@ -38,7 +38,7 @@ jobs:
# Build remapped Spigot versions
- uses: SpraxDev/Action-SpigotMC@v4
with:
versions: 1.18, 1.18.2, 1.19, 1.19.2, 1.19.3
versions: 1.18, 1.18.2, 1.19, 1.19.2, 1.19.3, 1.19.4
remapped: true
- name: Analyze project

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -128,7 +128,7 @@
<dependency>
<groupId>de.tr7zw</groupId>
<artifactId>item-nbt-api</artifactId>
<version>2.11.1</version>
<version>2.11.2</version>
<scope>compile</scope>
</dependency>
@ -267,6 +267,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>SongodaCore-NMS-v1_19_R3</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- End NMS -->
<!-- Start Plugin Hooks -->
@ -376,9 +382,9 @@
</dependency>
<dependency>
<groupId>me.angeschossen</groupId>
<groupId>com.github.Angeschossen</groupId>
<artifactId>LandsAPI</artifactId>
<version>4.12.20</version>
<version>6.28.11</version>
<scope>provided</scope>
</dependency>

View File

@ -55,7 +55,7 @@ public class SongodaCore {
/**
* @since coreRevision 6
*/
private final static String coreVersion = "2.6.18";
private final static String coreVersion = "2.6.19-DEV";
/**
* This is specific to the website api

View File

@ -85,34 +85,41 @@ public abstract class SongodaPlugin extends JavaPlugin {
@Override
public final void onEnable() {
if (emergencyStop) {
if (this.emergencyStop) {
setEnabled(false);
return;
}
//Check plugin access, don't load plugin if user don't have access
// Check plugin access, don't load plugin if user don't have access
if (!SongodaAuth.isAuthorized(true)) {
Thread thread = new Thread(() -> {
console.sendMessage(ChatColor.RED + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
console.sendMessage(ChatColor.RED + "You do not have access to this plugin.");
console.sendMessage(ChatColor.YELLOW + "Please purchase a license at https://sngda.to/marketplace");
console.sendMessage(ChatColor.YELLOW + "or set up your license at https://sngda.to/licenses");
console.sendMessage(ChatColor.YELLOW + "License setup steps:");
console.sendMessage(ChatColor.YELLOW + "Visit the link mentioned above and click the 'Create License button'");
console.sendMessage(ChatColor.YELLOW + "Copy the following ip and uuid and click create.");
console.sendMessage(ChatColor.YELLOW + "IP: " + SongodaAuth.getIP());
console.sendMessage(ChatColor.YELLOW + "UUID: " + SongodaAuth.getUUID());
console.sendMessage(ChatColor.RED + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
});
thread.start();
String pluginName = getDescription().getName();
new Thread(() -> {
String externalIP = SongodaAuth.getIP();
String serverUuid = SongodaAuth.getUUID().toString();
String message = "\n" +
ChatColor.RED + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
ChatColor.RED + "You do not have access to the " + pluginName + " plugin.\n" +
ChatColor.YELLOW + "Please purchase a license at https://sngda.to/marketplace\n" +
ChatColor.YELLOW + "or set up your license at https://sngda.to/licenses\n" +
ChatColor.YELLOW + "License setup steps:\n" +
ChatColor.YELLOW + "Visit the link mentioned above and click the 'Create License' button.\n" +
ChatColor.YELLOW + "Copy the following IP address and UUID and click create.\n" +
ChatColor.YELLOW + "UUID: " + serverUuid + "\n" +
ChatColor.YELLOW + "IP: " + externalIP + "\n" +
ChatColor.RED + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
this.console.sendMessage(message);
}).start();
emergencyStop();
return;
}
console.sendMessage(" "); // blank line to separate chatter
console.sendMessage(ChatColor.GREEN + "=============================");
console.sendMessage(String.format("%s%s %s by %sSongoda <3!", ChatColor.GRAY,
console.sendMessage(String.format("%s%s %s by %sCraftaro <3!", ChatColor.GRAY,
getDescription().getName(), getDescription().getVersion(), ChatColor.DARK_PURPLE));
console.sendMessage(String.format("%sAction: %s%s%s...", ChatColor.GRAY,
ChatColor.GREEN, "Enabling", ChatColor.GRAY));
@ -155,7 +162,7 @@ public abstract class SongodaPlugin extends JavaPlugin {
console.sendMessage(" "); // blank line to separate chatter
console.sendMessage(ChatColor.GREEN + "=============================");
console.sendMessage(String.format("%s%s %s by %sSongoda <3!", ChatColor.GRAY,
console.sendMessage(String.format("%s%s %s by %sCraftaro <3!", ChatColor.GRAY,
getDescription().getName(), getDescription().getVersion(), ChatColor.DARK_PURPLE));
console.sendMessage(String.format("%sAction: %s%s%s...", ChatColor.GRAY,
ChatColor.RED, "Disabling", ChatColor.GRAY));
@ -229,7 +236,7 @@ public abstract class SongodaPlugin extends JavaPlugin {
if (unfinishedTasks > 0) {
getLogger().log(Level.WARNING,
String.format("A DataManager has been forcefully terminated with %d unfinished tasks - " +
"This can be a serious problem, please report it to us (Songoda)!", unfinishedTasks));
"This can be a serious problem, please report it to us (Craftaro / Songoda)!", unfinishedTasks));
}
}
}

View File

@ -372,6 +372,10 @@ public class CommandManager implements CommandExecutor, TabCompleter {
// Set tab complete
commandObject.setTabCompleter(tabManager);
if (command.equalsIgnoreCase("songoda")) {
commandObject.setAliases(Collections.singletonList("craftaro"));
}
// Register the command
Field fieldKnownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands");
fieldKnownCommands.setAccessible(true);

View File

@ -65,7 +65,7 @@ public class MainCommand extends AbstractCommand {
.sendTo(sender);
}
sender.sendMessage(ChatColor.DARK_GRAY + "- " + ChatColor.YELLOW + "/songoda" + ChatColor.GRAY + " - Opens the Songoda plugin GUI");
sender.sendMessage(ChatColor.DARK_GRAY + "- " + ChatColor.YELLOW + "/craftaro" + ChatColor.GRAY + " - Opens the Craftaro plugin GUI");
sender.sendMessage("");
if (nestedCommands != null) {

View File

@ -12,7 +12,7 @@ public class SongodaCoreCommand extends AbstractCommand {
protected GuiManager guiManager;
public SongodaCoreCommand() {
super(false, "songoda");
super(CommandType.CONSOLE_OK, "craftaro", "songoda");
}
@Override
@ -24,7 +24,9 @@ public class SongodaCoreCommand extends AbstractCommand {
guiManager.showGUI((Player) sender, new SongodaCoreOverviewGUI());
} else {
sender.sendMessage("/songoda diag");
sender.sendMessage("/craftaro diag");
sender.sendMessage("/craftaro myip");
sender.sendMessage("/craftaro uuid");
}
return ReturnType.SUCCESS;
@ -37,7 +39,7 @@ public class SongodaCoreCommand extends AbstractCommand {
@Override
public String getSyntax() {
return "/songoda";
return "/craftaro";
}
@Override

View File

@ -9,66 +9,26 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.util.List;
public class SongodaCoreDiagCommand extends AbstractCommand {
private final DecimalFormat format = new DecimalFormat("##.##");
private final DecimalFormat decimalFormat = new DecimalFormat("##.##");
private Object serverInstance;
private Field tpsField;
private Object nmsServerInstance;
private Field recentTpsOnNmsServer;
public SongodaCoreDiagCommand() {
super(CommandType.CONSOLE_OK, "diag");
try {
serverInstance = ClassMapping.MINECRAFT_SERVER.getClazz().getMethod("getServer").invoke(null);
tpsField = serverInstance.getClass().getField("recentTps");
} catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException ex) {
this.nmsServerInstance = ClassMapping.MINECRAFT_SERVER.getClazz().getMethod("getServer").invoke(null);
this.recentTpsOnNmsServer = this.nmsServerInstance.getClass().getField("recentTps");
} catch (ReflectiveOperationException | SecurityException | IllegalArgumentException ex) {
ex.printStackTrace();
}
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
sender.sendMessage("");
sender.sendMessage("Songoda Diagnostics Information");
sender.sendMessage("");
sender.sendMessage("Plugins:");
for (PluginInfo plugin : SongodaCore.getPlugins()) {
sender.sendMessage(plugin.getJavaPlugin().getName()
+ " (" + plugin.getJavaPlugin().getDescription().getVersion() + " Core " + plugin.getCoreLibraryVersion() + ")");
}
sender.sendMessage("");
sender.sendMessage("Server Version: " + Bukkit.getVersion());
sender.sendMessage("NMS: " + ServerProject.getServerVersion() + " " + ServerVersion.getServerVersionString());
sender.sendMessage("Operating System: " + System.getProperty("os.name"));
sender.sendMessage("Allocated Memory: " + format.format(Runtime.getRuntime().maxMemory() / (1024 * 1024)) + "Mb");
sender.sendMessage("Online Players: " + Bukkit.getOnlinePlayers().size());
if (tpsField != null) {
try {
double[] tps = ((double[]) tpsField.get(serverInstance));
sender.sendMessage("TPS from last 1m, 5m, 15m: " + format.format(tps[0]) + ", "
+ format.format(tps[1]) + ", " + format.format(tps[2]));
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
}
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}
@Override
public String getPermissionNode() {
return "songoda.admin";
@ -76,11 +36,60 @@ public class SongodaCoreDiagCommand extends AbstractCommand {
@Override
public String getSyntax() {
return "/songoda diag";
return "/craftaro diag";
}
@Override
public String getDescription() {
return "Display diagnostics information.";
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
sender.sendMessage("");
sender.sendMessage("Craftaro Diagnostics Information");
sender.sendMessage("");
sender.sendMessage("Plugins:");
for (PluginInfo plugin : SongodaCore.getPlugins()) {
sender.sendMessage(String.format("%s v%s (Core v%s)",
plugin.getJavaPlugin().getName(),
plugin.getJavaPlugin().getDescription().getVersion(),
plugin.getCoreLibraryVersion()));
}
sender.sendMessage("");
sender.sendMessage("Server Version: " + Bukkit.getVersion());
sender.sendMessage("NMS: " + ServerProject.getServerVersion() + " " + ServerVersion.getServerVersionString());
sender.sendMessage("Operating System: " + System.getProperty("os.name"));
sender.sendMessage("Allocated Memory: " + getRuntimeMaxMemory());
sender.sendMessage("Online Players: " + Bukkit.getOnlinePlayers().size());
sendCurrentTps(sender);
sender.sendMessage("");
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}
private String getRuntimeMaxMemory() {
return this.decimalFormat.format(Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " MiB";
}
private void sendCurrentTps(CommandSender receiver) {
if (this.recentTpsOnNmsServer == null) {
return;
}
try {
double[] tps = ((double[]) this.recentTpsOnNmsServer.get(this.nmsServerInstance));
receiver.sendMessage(String.format("TPS from last 1m, 5m, 15m: %s, %s, %s", this.decimalFormat.format(tps[0]), this.decimalFormat.format(tps[1]), this.decimalFormat.format(tps[2])));
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
}
}

View File

@ -63,7 +63,7 @@ public class SongodaCoreIPCommand extends AbstractCommand {
@Override
public String getSyntax() {
return "/songoda myip";
return "/craftaro myip";
}
@Override

View File

@ -16,7 +16,7 @@ final class SongodaCoreOverviewGUI extends Gui {
// could do pages, too, but don't think we'll have that many at a time for a while
int max = (int) Math.ceil(plugins.size() / 9.);
setRows(max);
setTitle("Songoda Plugins");
setTitle("Craftaro Plugins");
// TODO: this could use some decorating

View File

@ -40,12 +40,12 @@ public class SongodaCoreUUIDCommand extends AbstractCommand {
@Override
public String getPermissionNode() {
return "songodacore.admin";
return "songoda.admin";
}
@Override
public String getSyntax() {
return "/songodacore uuid";
return "/craftaro uuid";
}
@Override

View File

@ -60,7 +60,7 @@ public class CustomizableGui extends Gui {
"in this GUI.")
.setDefaultComment("overrides",
"For information on how to apply overrides please visit",
"https://wiki.songoda.com/Gui");
"https://wiki.craftaro.com/index.php/Gui");
config.saveChanges();
}

View File

@ -1,8 +1,9 @@
package com.songoda.core.hooks.protection;
import me.angeschossen.lands.api.integration.LandsIntegration;
import me.angeschossen.lands.api.LandsIntegration;
import me.angeschossen.lands.api.flags.type.Flags;
import me.angeschossen.lands.api.flags.type.RoleFlag;
import me.angeschossen.lands.api.land.Area;
import me.angeschossen.lands.api.role.enums.RoleSetting;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
@ -13,32 +14,31 @@ public class LandsProtection extends Protection {
public LandsProtection(Plugin plugin) {
super(plugin);
this.landsIntegration = new LandsIntegration(plugin);
this.landsIntegration = LandsIntegration.of(plugin);
}
@Override
public boolean canPlace(Player player, Location location) {
return hasPerms(player, location, RoleSetting.BLOCK_PLACE);
return hasPerms(player, location, Flags.BLOCK_PLACE);
}
@Override
public boolean canBreak(Player player, Location location) {
return hasPerms(player, location, RoleSetting.BLOCK_BREAK);
return hasPerms(player, location, Flags.BLOCK_BREAK);
}
@Override
public boolean canInteract(Player player, Location location) {
return hasPerms(player, location, RoleSetting.INTERACT_CONTAINER);
return hasPerms(player, location, Flags.INTERACT_CONTAINER);
}
private boolean hasPerms(Player player, Location location, RoleSetting roleSetting) {
Area area = landsIntegration.getAreaByLoc(location);
private boolean hasPerms(Player player, Location location, RoleFlag roleFlag) {
Area area = this.landsIntegration.getArea(location);
if (area == null) {
return true;
}
return area.canSetting(player, roleSetting, false);
return area.getRole(player.getUniqueId()).hasFlag(roleFlag);
}
@Override
@ -48,6 +48,6 @@ public class LandsProtection extends Protection {
@Override
public boolean isEnabled() {
return landsIntegration != null;
return this.landsIntegration != null;
}
}

View File

@ -101,7 +101,7 @@ public class DropUtils {
Bukkit.getScheduler().runTask(SongodaCore.getHijackedPlugin(), () -> {
for (String command : commands) {
if (entity.getKiller() != null) {
command = command.replace("%player%", entity.getKiller().getName());
command = command.replace("%player%", entity.getKiller().getName().replace("%x%", String.valueOf((int)entity.getLocation().getX())).replace("%y%", String.valueOf((int)entity.getLocation().getY())).replace("%z%", String.valueOf((int)entity.getLocation().getZ())));
}
if (!command.contains("%player%")) {

View File

@ -4,9 +4,11 @@ import com.google.gson.annotations.SerializedName;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.utils.ItemUtils;
import com.songoda.core.utils.TextUtils;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import java.util.ArrayList;
import java.util.Arrays;
@ -149,6 +151,25 @@ public class Loot {
return null;
}
//Create enchantment book
if (item.getType().equals(Material.ENCHANTED_BOOK)) {
EnchantmentStorageMeta meta = (EnchantmentStorageMeta) item.getItemMeta();
for (Map.Entry<String, Integer> entry : this.enchants.entrySet()) {
if (entry.getValue() == null) continue;
//TODO add random enchants
// if (entry.getKey().equalsIgnoreCase("RANDOM")) {
// item = ItemUtils.applyRandomEnchants(item, entry.getValue());
//
// continue;
// }
meta.addStoredEnchant(Enchantment.getByName(entry.getKey()), entry.getValue(), true);
}
item.setItemMeta(meta);
return item;
}
Map<Enchantment, Integer> enchants = new HashMap<>();
for (Map.Entry<String, Integer> entry : this.enchants.entrySet()) {
if (entry.getValue() == null) continue;

View File

@ -147,6 +147,12 @@ public class NmsManager {
nbt = new com.songoda.core.nms.v1_19_R2.nbt.NBTCoreImpl();
world = new com.songoda.core.nms.v1_19_R2.world.WorldCoreImpl();
break;
case "v1_19_R3":
player = new com.songoda.core.nms.v1_19_R3.entity.NMSPlayerImpl();
anvil = new com.songoda.core.nms.v1_19_R3.anvil.AnvilCore();
nbt = new com.songoda.core.nms.v1_19_R3.nbt.NBTCoreImpl();
world = new com.songoda.core.nms.v1_19_R3.world.WorldCoreImpl();
break;
default:
Logger.getLogger(NmsManager.class.getName()).log(Level.SEVERE, "Failed to load NMS for this server version: version {0} not found", serverPackageVersion);

View File

@ -1,116 +1,111 @@
package com.songoda.core.utils;
import com.songoda.core.commands.AbstractCommand;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.bukkit.Bukkit;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.Properties;
import java.util.UUID;
public class SongodaAuth {
public static boolean isAuthorized(boolean allowOffline) {
String productId = "%%__PLUGIN__%%";
try {
Integer.parseInt(productId);
} catch (NumberFormatException e) {
//Self compiled, return true
if (isPluginSelfCompiled(productId)) {
return true;
}
UUID uuid = getUUID();
UUID serverUuid = getUUID();
try {
URL url = new URL("https://marketplace.songoda.com/api/v2/products/license/validate");
HttpURLConnection con = (HttpURLConnection)url.openConnection();
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Accept", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\"product_id\":" + productId + ",\"license\":\"" + uuid + "\",\"user_id\":\"%%__USER__%%\"}";
try(OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
String requestBodyJson = "{\"product_id\":" + productId + ",\"license\":\"" + serverUuid + "\",\"user_id\":\"%%__USER__%%\"}";
try (OutputStream os = con.getOutputStream()) {
byte[] requestBody = requestBodyJson.getBytes(StandardCharsets.UTF_8);
os.write(requestBody, 0, requestBody.length);
}
try(BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
JSONObject jsonObject = (JSONObject) new JSONParser().parse(response.toString());
if (jsonObject.get("error") != null) {
//Got an error, return false and print error
Bukkit.getLogger().warning("Error validating license: " + jsonObject.get("error"));
return false;
} else {
return (boolean) jsonObject.get("valid");
}
JSONObject jsonResponse = readHttpResponseJson(con);
if (jsonResponse.containsKey("error")) {
Bukkit.getLogger().warning("Error validating license: " + jsonResponse.get("error"));
return false;
}
} catch (Exception e) {
return (boolean) jsonResponse.get("valid");
} catch (Exception ex) {
return allowOffline;
}
}
public static UUID getUUID() {
File serverProperties = new File(new File("."),"server.properties");
Properties prop = new Properties();
File serverProperties = new File("./server.properties");
try {
Properties prop = new Properties();
prop.load(new FileReader(serverProperties));
String uuid = prop.getProperty("uuid");
if (uuid == null || uuid.isEmpty()) {
UUID newUUID = UUID.randomUUID();
prop.setProperty("uuid", newUUID.toString());
prop.store(new FileWriter(serverProperties), null);
return newUUID;
} else {
if (uuid != null && !uuid.isEmpty()) {
return UUID.fromString(uuid);
}
UUID newUUID = UUID.randomUUID();
prop.setProperty("uuid", newUUID.toString());
prop.store(new FileWriter(serverProperties), null);
return newUUID;
} catch (Exception ex) {
throw new RuntimeException("Could not fetch UUID for server", ex);
throw new RuntimeException("Could not determine UUID for server", ex);
}
}
public static String getIP() {
try {
URL url = new URL("https://marketplace.songoda.com/api/v2/products/license/ip");
HttpURLConnection con = (HttpURLConnection)url.openConnection();
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Accept", "application/json");
con.setDoOutput(true);
try(BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
JSONParser parser = new JSONParser();
JSONObject jsonObject = (JSONObject) parser.parse(response.toString());
return jsonObject.get("ip").toString();
}
JSONObject jsonResponse = readHttpResponseJson(con);
return jsonResponse.get("ip").toString();
} catch (Exception ex) {
throw new RuntimeException("Could not fetch IP address", ex);
}
}
private static JSONObject readHttpResponseJson(HttpURLConnection con) throws IOException, ParseException {
try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
return (JSONObject) new JSONParser().parse(response.toString());
}
}
private static boolean isPluginSelfCompiled(String productId) {
try {
Integer.parseInt(productId);
return false;
} catch (NumberFormatException ignore) {
}
return true;
}
}

1001
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -19,7 +19,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -60,7 +60,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -60,7 +60,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -60,7 +60,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -60,7 +60,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

105
NMS/NMS-v1_19_R3/pom.xml Normal file
View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>${java.release}</release>
</configuration>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
<version>1.2.4</version>
<executions>
<execution>
<id>remap-obf</id>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<configuration>
<srgIn>org.spigotmc:minecraft-server:${nms.ver}:txt:maps-mojang</srgIn>
<reverse>true</reverse>
<remappedDependencies>org.spigotmc:spigot:${nms.ver}:jar:remapped-mojang</remappedDependencies>
<remappedArtifactAttached>true</remappedArtifactAttached>
<remappedClassifierName>remapped-obf</remappedClassifierName>
</configuration>
</execution>
<execution>
<id>remap-spigot</id>
<phase>package</phase>
<goals>
<goal>remap</goal>
</goals>
<configuration>
<inputFile>${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar</inputFile>
<srgIn>org.spigotmc:minecraft-server:${nms.ver}:csrg:maps-spigot</srgIn>
<remappedDependencies>org.spigotmc:spigot:${nms.ver}:jar:remapped-obf</remappedDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
<java.version>17</java.version>
<java.release>17</java.release>
<nms.ver>1.19.4-R0.1-SNAPSHOT</nms.ver>
</properties>
<artifactId>SongodaCore-NMS-v1_19_R3</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${nms.ver}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${nms.ver}</version>
<scope>provided</scope>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>SongodaCore-NMS-API</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>SongodaCore-Compatibility</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,21 @@
package com.songoda.core.nms.v1_19_R3.anvil;
import com.songoda.core.nms.anvil.CustomAnvil;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
public class AnvilCore implements com.songoda.core.nms.anvil.AnvilCore {
@Override
public CustomAnvil createAnvil(Player player) {
ServerPlayer p = ((CraftPlayer) player).getHandle();
return new AnvilView(p.nextContainerCounter(), p, null);
}
@Override
public CustomAnvil createAnvil(Player player, InventoryHolder holder) {
ServerPlayer p = ((CraftPlayer) player).getHandle();
return new AnvilView(p.nextContainerCounter(), p, holder);
}
}

View File

@ -0,0 +1,22 @@
package com.songoda.core.nms.v1_19_R3.anvil;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.AnvilMenu;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryAnvil;
import org.bukkit.inventory.InventoryHolder;
public class AnvilInventoryCustom extends CraftInventoryAnvil {
final InventoryHolder holder;
public AnvilInventoryCustom(InventoryHolder holder, Location location, Container inventory, Container resultInventory, AnvilMenu container) {
super(location, inventory, resultInventory, container);
this.holder = holder;
}
@Override
public InventoryHolder getHolder() {
return holder;
}
}

View File

@ -0,0 +1,216 @@
package com.songoda.core.nms.v1_19_R3.anvil;
import com.songoda.core.nms.anvil.CustomAnvil;
import com.songoda.core.nms.anvil.methods.AnvilTextChange;
import net.md_5.bungee.api.chat.TranslatableComponent;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.AnvilMenu;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.inventory.ItemCombinerMenu;
import net.minecraft.world.inventory.MenuType;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
public class AnvilView extends AnvilMenu implements CustomAnvil {
private final ServerPlayer entity;
private final Inventory inventory;
private String customTitle = "Repairing";
private int cost = -1;
private boolean canUse = true;
private AnvilTextChange textChange;
// used for setting custom inventory
static Field mc_ContainerAnvil_repairInventory; // subcontainer with only the result
static Field mc_ContainerAnvil_resultInventory; // full inventory
static Field mc_ContainerAnvil_bukkitEntity;
static {
try {
mc_ContainerAnvil_repairInventory = ItemCombinerMenu.class.getDeclaredField("q");
mc_ContainerAnvil_repairInventory.setAccessible(true);
mc_ContainerAnvil_resultInventory = ItemCombinerMenu.class.getDeclaredField("r");
mc_ContainerAnvil_resultInventory.setAccessible(true);
mc_ContainerAnvil_bukkitEntity = AnvilMenu.class.getDeclaredField("bukkitEntity");
mc_ContainerAnvil_bukkitEntity.setAccessible(true);
} catch (Exception ex) {
Logger.getLogger(AnvilView.class.getName()).log(Level.SEVERE, "Anvil Setup Error", ex);
}
}
// 1.14 also introduced a title field, also private, which can only be set once and can't be checked
static Field mc_Container_title;
static {
try {
mc_Container_title = AbstractContainerMenu.class.getDeclaredField("title");
mc_Container_title.setAccessible(true);
} catch (Exception ex) {
Logger.getLogger(AnvilView.class.getName()).log(Level.SEVERE, "Anvil Setup Error", ex);
}
}
public AnvilView(int id, ServerPlayer entity, InventoryHolder holder) {
super(entity.nextContainerCounter(), entity.getInventory(), ContainerLevelAccess.create(entity.level, new BlockPos(0, 0, 0)));
this.setTitle(MutableComponent.create(new TranslatableContents(this.customTitle != null ? this.customTitle : "", this.customTitle != null ? this.customTitle : "", new Object[0])));
this.checkReachable = false;
this.entity = entity;
if (holder != null) {
this.inventory = getBukkitView(entity, holder).getTopInventory();
} else {
this.inventory = getBukkitView().getTopInventory();
}
}
public CraftInventoryView getBukkitView(Player player, InventoryHolder holder) {
try {
AnvilInventoryCustom craftInventory = new AnvilInventoryCustom(holder,
new Location(entity.level.getWorld(), 0, 0, 0),
(Container) mc_ContainerAnvil_repairInventory.get(this),
(Container) mc_ContainerAnvil_resultInventory.get(this), this);
CraftInventoryView view = new CraftInventoryView(player.getBukkitEntity(), craftInventory, this);
mc_ContainerAnvil_bukkitEntity.set(this, view);
return view;
} catch (Exception ex) {
Logger.getLogger(AnvilView.class.getName()).log(Level.SEVERE, "Anvil Setup Error", ex);
}
return getBukkitView();
}
@Override
public boolean stillValid(Player entityHuman) {
return canUse;
}
@Override
public void broadcastFullState() {
super.broadcastFullState();
if (cost >= 0) {
this.setLevelCost(cost);
}
textChange.onChange();
}
@Override
public void update() {
broadcastFullState();
}
@Override
public String getRenameText() {
return this.itemName;
}
@Override
public void setRenameText(String text) {
this.setItemName(text);
}
@Override
public void setOnChange(AnvilTextChange handler) {
textChange = handler;
}
@Override
public String getCustomTitle() {
return customTitle;
}
@Override
public void setCustomTitle(String title) {
this.customTitle = title;
try {
mc_Container_title.set(this, MutableComponent.create(new TranslatableContents(this.customTitle != null ? this.customTitle : "", this.customTitle != null ? this.customTitle : "", new Object[0])));
} catch (Exception ex) {
Logger.getLogger(AnvilView.class.getName()).log(Level.SEVERE, "Anvil Error", ex);
}
}
@Override
public void setLevelCost(int cost) {
this.cost = cost;
}
@Override
public int getLevelCost() {
if (cost >= 0) {
return cost;
}
return this.getLevelCost();
}
@Override
public void setCanUse(boolean bool) {
this.canUse = bool;
}
@Override
public ItemStack getLeftInput() {
return inventory.getItem(0);
}
@Override
public ItemStack getRightInput() {
return inventory.getItem(1);
}
@Override
public ItemStack getOutput() {
return inventory.getItem(2);
}
@Override
public void setLeftInput(ItemStack item) {
inventory.setItem(0, item);
}
@Override
public void setRightInput(ItemStack item) {
inventory.setItem(1, item);
}
@Override
public void setOutput(ItemStack item) {
inventory.setItem(2, item);
}
@Override
public Inventory getInventory() {
return inventory;
}
@Override
public void open() {
// Send the packet
entity.connection.send(new ClientboundOpenScreenPacket(super.containerId, MenuType.ANVIL, MutableComponent.create(new TranslatableContents(this.customTitle != null ? this.customTitle : "", this.customTitle != null ? this.customTitle : "", new Object[0]))));
// Set their active container to this anvil
entity.containerMenu = this;
// Add the slot listener
entity.initMenu(entity.containerMenu);
}
}

View File

@ -0,0 +1,13 @@
package com.songoda.core.nms.v1_19_R3.entity;
import com.songoda.core.nms.entity.NMSPlayer;
import net.minecraft.network.protocol.Packet;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.bukkit.entity.Player;
public class NMSPlayerImpl implements NMSPlayer {
@Override
public void sendPacket(Player p, Object packet) {
((CraftPlayer) p).getHandle().connection.send((Packet<?>) packet);
}
}

View File

@ -0,0 +1,208 @@
package com.songoda.core.nms.v1_19_R3.nbt;
import com.songoda.core.nms.nbt.NBTCompound;
import com.songoda.core.nms.nbt.NBTObject;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Set;
import java.util.UUID;
public class NBTCompoundImpl implements NBTCompound {
protected CompoundTag compound;
protected NBTCompoundImpl(CompoundTag compound) {
this.compound = compound;
}
public NBTCompoundImpl() {
this.compound = new CompoundTag();
}
@Override
public NBTCompound set(String tag, String s) {
compound.putString(tag, s);
return this;
}
@Override
public NBTCompound set(String tag, boolean b) {
compound.putBoolean(tag, b);
return this;
}
@Override
public NBTCompound set(String tag, int i) {
compound.putInt(tag, i);
return this;
}
@Override
public NBTCompound set(String tag, double i) {
compound.putDouble(tag, i);
return this;
}
@Override
public NBTCompound set(String tag, long l) {
compound.putLong(tag, l);
return this;
}
@Override
public NBTCompound set(String tag, short s) {
compound.putShort(tag, s);
return this;
}
@Override
public NBTCompound set(String tag, byte b) {
compound.putByte(tag, b);
return this;
}
@Override
public NBTCompound set(String tag, int[] i) {
compound.putIntArray(tag, i);
return this;
}
@Override
public NBTCompound set(String tag, byte[] b) {
compound.putByteArray(tag, b);
return this;
}
@Override
public NBTCompound set(String tag, UUID u) {
compound.putUUID(tag, u);
return this;
}
@Override
public NBTCompound remove(String tag) {
compound.remove(tag);
return this;
}
@Override
public boolean has(String tag) {
return compound.contains(tag);
}
@Override
public NBTObject getNBTObject(String tag) {
return new NBTObjectImpl(compound, tag);
}
@Override
public String getString(String tag) {
return getNBTObject(tag).asString();
}
@Override
public boolean getBoolean(String tag) {
return getNBTObject(tag).asBoolean();
}
@Override
public int getInt(String tag) {
return getNBTObject(tag).asInt();
}
@Override
public double getDouble(String tag) {
return getNBTObject(tag).asDouble();
}
@Override
public long getLong(String tag) {
return getNBTObject(tag).asLong();
}
@Override
public short getShort(String tag) {
return getNBTObject(tag).asShort();
}
@Override
public byte getByte(String tag) {
return getNBTObject(tag).asByte();
}
@Override
public int[] getIntArray(String tag) {
return getNBTObject(tag).asIntArray();
}
@Override
public byte[] getByteArray(String tag) {
return getNBTObject(tag).asByteArray();
}
@Override
public NBTCompound getCompound(String tag) {
if (has(tag)) {
return getNBTObject(tag).asCompound();
}
CompoundTag newCompound = new CompoundTag();
compound.put(tag, newCompound);
return new NBTCompoundImpl(newCompound);
}
@Override
public Set<String> getKeys() {
return compound.getAllKeys();
}
@Override
public Set<String> getKeys(String tag) {
return compound.getCompound(tag).getAllKeys();
}
@Override
public byte[] serialize(String... exclusions) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream dataOutput = new ObjectOutputStream(outputStream)) {
addExtras();
CompoundTag compound = this.compound.copy();
for (String exclusion : exclusions) {
compound.remove(exclusion);
}
NbtIo.writeCompressed(compound, dataOutput);
return outputStream.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
@Override
public void deSerialize(byte[] serialized) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(serialized);
ObjectInputStream dataInput = new ObjectInputStream(inputStream)) {
compound = NbtIo.readCompressed(dataInput);
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void addExtras() {
// None
}
@Override
public String toString() {
return compound.toString();
}
}

View File

@ -0,0 +1,38 @@
package com.songoda.core.nms.v1_19_R3.nbt;
import com.songoda.core.nms.nbt.NBTCore;
import com.songoda.core.nms.nbt.NBTEntity;
import com.songoda.core.nms.nbt.NBTItem;
import net.minecraft.nbt.CompoundTag;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
public class NBTCoreImpl implements NBTCore {
@Deprecated
@Override
public NBTItem of(ItemStack item) {
return new NBTItemImpl(CraftItemStack.asNMSCopy(item));
}
@Deprecated
@Override
public NBTItem newItem() {
return new NBTItemImpl(null);
}
@Override
public NBTEntity of(Entity entity) {
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
CompoundTag nbt = new CompoundTag();
nmsEntity.saveWithoutId(nbt);
return new NBTEntityImpl(nbt, nmsEntity);
}
@Override
public NBTEntity newEntity() {
return new NBTEntityImpl(new CompoundTag(), null);
}
}

View File

@ -0,0 +1,67 @@
package com.songoda.core.nms.v1_19_R3.nbt;
import com.songoda.core.nms.nbt.NBTEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import java.util.Optional;
public class NBTEntityImpl extends NBTCompoundImpl implements NBTEntity {
private Entity nmsEntity;
public NBTEntityImpl(CompoundTag entityNBT, Entity nmsEntity) {
super(entityNBT);
this.nmsEntity = nmsEntity;
}
@Override
public org.bukkit.entity.Entity spawn(Location location) {
String entityType = getNBTObject("entity_type").asString();
getKeys().remove("UUID");
Optional<EntityType<?>> optionalEntity = EntityType.byString(entityType);
if (optionalEntity.isPresent()) {
assert location.getWorld() != null;
Entity spawned = optionalEntity.get().spawn(
((CraftWorld) location.getWorld()).getHandle(),
compound,
null,
new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()),
MobSpawnType.COMMAND,
true,
false
);
if (spawned != null) {
spawned.load(compound);
org.bukkit.entity.Entity entity = spawned.getBukkitEntity();
entity.teleport(location);
nmsEntity = spawned;
return entity;
}
}
return null;
}
@Override
public org.bukkit.entity.Entity reSpawn(Location location) {
nmsEntity.discard();
return spawn(location);
}
@Override
public void addExtras() {
compound.putString("entity_type", BuiltInRegistries.ENTITY_TYPE.getKey(nmsEntity.getType()).toString());
}
}

View File

@ -0,0 +1,24 @@
package com.songoda.core.nms.v1_19_R3.nbt;
import com.songoda.core.nms.nbt.NBTItem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
public class NBTItemImpl extends NBTCompoundImpl implements NBTItem {
private final ItemStack nmsItem;
public NBTItemImpl(ItemStack nmsItem) {
super(nmsItem != null && nmsItem.hasTag() ? nmsItem.getTag() : new CompoundTag());
this.nmsItem = nmsItem;
}
public org.bukkit.inventory.ItemStack finish() {
if (nmsItem == null) {
return CraftItemStack.asBukkitCopy(ItemStack.of(compound));
}
return CraftItemStack.asBukkitCopy(nmsItem);
}
}

View File

@ -0,0 +1,72 @@
package com.songoda.core.nms.v1_19_R3.nbt;
import com.songoda.core.nms.nbt.NBTCompound;
import com.songoda.core.nms.nbt.NBTObject;
import net.minecraft.nbt.CompoundTag;
import java.util.Set;
public class NBTObjectImpl implements NBTObject {
private final CompoundTag compound;
private final String tag;
public NBTObjectImpl(CompoundTag compound, String tag) {
this.compound = compound;
this.tag = tag;
}
@Override
public String asString() {
return compound.getString(tag);
}
@Override
public boolean asBoolean() {
return compound.getBoolean(tag);
}
@Override
public int asInt() {
return compound.getInt(tag);
}
@Override
public double asDouble() {
return compound.getDouble(tag);
}
@Override
public long asLong() {
return compound.getLong(tag);
}
@Override
public short asShort() {
return compound.getShort(tag);
}
@Override
public byte asByte() {
return compound.getByte(tag);
}
@Override
public int[] asIntArray() {
return compound.getIntArray(tag);
}
@Override
public byte[] asByteArray() {
return compound.getByteArray(tag);
}
@Override
public NBTCompound asCompound() {
return new NBTCompoundImpl(compound.getCompound(tag));
}
@Override
public Set<String> getKeys() {
return compound.getAllKeys();
}
}

View File

@ -0,0 +1,39 @@
package com.songoda.core.nms.v1_19_R3.world;
import com.songoda.core.nms.world.SItemStack;
import net.minecraft.core.particles.ItemParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.Vec3;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class SItemStackImpl implements SItemStack {
private final ItemStack item;
public SItemStackImpl(ItemStack item) {
this.item = item;
}
@Override
public void breakItem(Player player, int amount) {
ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle();
for (int i = 0; i < amount; ++i) {
Vec3 vec3d = new Vec3(((double) random.nextFloat() - 0.5D) * 0.1D, Math.random() * 0.1D + 0.1D, 0.0D);
vec3d = vec3d.xRot(-entityPlayer.getXRot() * 0.017453292F);
vec3d = vec3d.yRot(-entityPlayer.getYRot() * 0.017453292F);
double d0 = (double) (-random.nextFloat()) * 0.6D - 0.3D;
Vec3 vec3d1 = new Vec3(((double) random.nextFloat() - 0.5D) * 0.3D, d0, 0.6D);
vec3d1 = vec3d1.xRot(-entityPlayer.getXRot() * 0.017453292F);
vec3d1 = vec3d1.yRot(-entityPlayer.getYRot() * 0.017453292F);
vec3d1 = vec3d1.add(entityPlayer.getX(), entityPlayer.getEyeY(), entityPlayer.getZ());
entityPlayer.level.addParticle(new ItemParticleOption(ParticleTypes.ITEM, CraftItemStack.asNMSCopy(item)), vec3d1.x, vec3d1.y, vec3d1.z, vec3d.x, vec3d.y + 0.05D, vec3d.z);
}
}
}

View File

@ -0,0 +1,132 @@
package com.songoda.core.nms.v1_19_R3.world;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.nms.world.SSpawner;
import com.songoda.core.nms.world.SpawnedEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.level.SpawnData;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.Optional;
import java.util.Set;
public class SSpawnerImpl implements SSpawner {
private final Location spawnerLocation;
public SSpawnerImpl(Location location) {
this.spawnerLocation = location;
}
@Override
public LivingEntity spawnEntity(EntityType type, Location spawnerLocation) {
return spawnEntity(type, "EXPLOSION_NORMAL", null, null);
}
@Override
public LivingEntity spawnEntity(EntityType type, String particleType, SpawnedEntity spawned, Set<CompatibleMaterial> canSpawnOn) {
SpawnData data = new SpawnData();
CompoundTag compound = data.getEntityToSpawn();
String name = type.name().toLowerCase().replace("snowman", "snow_golem")
.replace("mushroom_cow", "mooshroom");
compound.putString("id", "minecraft:" + name);
short spawnRange = 4;
for (int i = 0; i < 50; i++) {
assert spawnerLocation.getWorld() != null;
ServerLevel world = ((CraftWorld) spawnerLocation.getWorld()).getHandle();
RandomSource random = world.getRandom();
double x = spawnerLocation.getX() + (random.nextDouble() - random.nextDouble()) * (double) spawnRange + 0.5D;
double y = spawnerLocation.getY() + random.nextInt(3) - 1;
double z = spawnerLocation.getZ() + (random.nextDouble() - random.nextDouble()) * (double) spawnRange + 0.5D;
Optional<Entity> optionalEntity = net.minecraft.world.entity.EntityType.create(compound, world);
if (optionalEntity.isEmpty()) continue;
Entity entity = optionalEntity.get();
entity.setPos(x, y, z);
BlockPos position = entity.blockPosition();
DifficultyInstance damageScaler = world.getCurrentDifficultyAt(position);
if (!(entity instanceof Mob entityInsentient)) {
continue;
}
Location spot = new Location(spawnerLocation.getWorld(), x, y, z);
if (!canSpawn(world, entityInsentient, spot, canSpawnOn)) {
continue;
}
entityInsentient.finalizeSpawn(world, damageScaler, MobSpawnType.SPAWNER, null, null);
LivingEntity craftEntity = (LivingEntity) entity.getBukkitEntity();
if (spawned != null && !spawned.onSpawn(craftEntity)) {
return null;
}
if (particleType != null) {
float xx = (float) (0 + (Math.random() * 1));
float yy = (float) (0 + (Math.random() * 2));
float zz = (float) (0 + (Math.random() * 1));
CompatibleParticleHandler.spawnParticles(CompatibleParticleHandler.ParticleType.getParticle(particleType), spot, 5, xx, yy, zz, 0);
}
world.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.SPAWNER);
spot.setYaw(random.nextFloat() * 360.0F);
craftEntity.teleport(spot);
return craftEntity;
}
return null;
}
private boolean canSpawn(ServerLevel world, Mob entityInsentient, Location location, Set<CompatibleMaterial> canSpawnOn) {
if (!world.noCollision(entityInsentient, entityInsentient.getBoundingBox())) {
return false;
}
CompatibleMaterial spawnedIn = CompatibleMaterial.getMaterial(location.getBlock());
CompatibleMaterial spawnedOn = CompatibleMaterial.getMaterial(location.getBlock().getRelative(BlockFace.DOWN));
if (spawnedIn == null || spawnedOn == null) {
return false;
}
if (!spawnedIn.isAir() &&
spawnedIn != CompatibleMaterial.WATER &&
!spawnedIn.name().contains("PRESSURE") &&
!spawnedIn.name().contains("SLAB")) {
return false;
}
for (CompatibleMaterial material : canSpawnOn) {
if (material == null) continue;
if (spawnedOn.equals(material) || material.isAir()) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,38 @@
package com.songoda.core.nms.v1_19_R3.world;
import com.songoda.core.nms.world.SWorld;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.entity.LevelEntityGetter;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.entity.LivingEntity;
import java.util.ArrayList;
import java.util.List;
public class SWorldImpl implements SWorld {
private final World world;
public SWorldImpl(World world) {
this.world = world;
}
@Override
public List<LivingEntity> getLivingEntities() {
List<LivingEntity> result = new ArrayList<>();
ServerLevel worldServer = ((CraftWorld) world).getHandle();
LevelEntityGetter<Entity> entities = worldServer.getEntities();
entities.getAll().forEach((mcEnt) -> {
org.bukkit.entity.Entity bukkitEntity = mcEnt.getBukkitEntity();
if (bukkitEntity instanceof LivingEntity && bukkitEntity.isValid()) {
result.add((LivingEntity) bukkitEntity);
}
});
return result;
}
}

View File

@ -0,0 +1,94 @@
package com.songoda.core.nms.v1_19_R3.world;
import com.songoda.core.nms.ReflectionUtils;
import com.songoda.core.nms.v1_19_R3.world.spawner.BBaseSpawnerImpl;
import com.songoda.core.nms.world.BBaseSpawner;
import com.songoda.core.nms.world.SItemStack;
import com.songoda.core.nms.world.SSpawner;
import com.songoda.core.nms.world.SWorld;
import com.songoda.core.nms.world.WorldCore;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.material.FluidState;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
import org.bukkit.inventory.ItemStack;
public class WorldCoreImpl implements WorldCore {
@Override
public SSpawner getSpawner(CreatureSpawner spawner) {
return new SSpawnerImpl(spawner.getLocation());
}
@Override
public SSpawner getSpawner(Location location) {
return new SSpawnerImpl(location);
}
@Override
public SItemStack getItemStack(ItemStack item) {
return new SItemStackImpl(item);
}
@Override
public SWorld getWorld(World world) {
return new SWorldImpl(world);
}
@Override
public BBaseSpawner getBaseSpawner(CreatureSpawner spawner) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException {
Object cTileEntity = ReflectionUtils.getFieldValue(spawner, "tileEntity");
return new BBaseSpawnerImpl(spawner, (BaseSpawner) ReflectionUtils.getFieldValue(cTileEntity, "a"));
}
/**
* Method is based on {@link ServerLevel#tickChunk(LevelChunk, int)}.
*/
@Override
public void randomTickChunk(org.bukkit.Chunk bukkitChunk, int tickAmount) {
LevelChunk chunk = (LevelChunk) ((CraftChunk) bukkitChunk).getHandle(ChunkStatus.FULL);
ServerLevel world = chunk.q;
ProfilerFiller gameProfilerFiller = world.getProfiler();
ChunkPos chunkCoordIntPair = chunk.getPos();
int j = chunkCoordIntPair.getMinBlockX();
int k = chunkCoordIntPair.getMinBlockZ();
gameProfilerFiller.popPush("tickBlocks");
if (tickAmount > 0) {
LevelChunkSection[] aChunkSection = chunk.getSections();
for (LevelChunkSection chunkSection : aChunkSection) {
if (chunkSection.isRandomlyTicking()) {
int j1 = chunkSection.bottomBlockY();
for (int k1 = 0; k1 < tickAmount; ++k1) {
BlockPos blockposition2 = world.getBlockRandomPos(j, j1, k, 15);
gameProfilerFiller.push("randomTick");
BlockState iBlockData1 = chunkSection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
if (iBlockData1.isRandomlyTicking()) {
iBlockData1.randomTick(world, blockposition2, world.random);
}
FluidState fluid = iBlockData1.getFluidState();
if (fluid.isRandomlyTicking()) {
fluid.randomTick(world, blockposition2, world.random);
}
gameProfilerFiller.pop();
}
}
}
}
}
}

View File

@ -0,0 +1,213 @@
package com.songoda.core.nms.v1_19_R3.world.spawner;
import com.songoda.core.nms.world.BBaseSpawner;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.util.random.WeightedEntry;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.SpawnData;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.AABB;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.block.CraftCreatureSpawner;
import org.bukkit.craftbukkit.v1_19_R3.event.CraftEventFactory;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
public class BBaseSpawnerImpl implements BBaseSpawner {
private final CreatureSpawner bukkitSpawner;
private final BaseSpawner spawner;
private final Method setNextSpawnDataMethod;
private final Method getOrCreateNextSpawnDataMethod;
public BBaseSpawnerImpl(CreatureSpawner bukkitSpawner, BaseSpawner spawner) throws NoSuchMethodException {
this.bukkitSpawner = bukkitSpawner;
this.spawner = spawner;
this.setNextSpawnDataMethod = this.spawner.getClass().getDeclaredMethod("a", Level.class, BlockPos.class, SpawnData.class);
if (!this.setNextSpawnDataMethod.canAccess(this.spawner)) {
this.setNextSpawnDataMethod.setAccessible(true);
}
this.getOrCreateNextSpawnDataMethod = this.spawner.getClass().getSuperclass().getDeclaredMethod("b", Level.class, RandomSource.class, BlockPos.class);
if (!this.getOrCreateNextSpawnDataMethod.canAccess(this.spawner)) {
this.getOrCreateNextSpawnDataMethod.setAccessible(true);
}
}
/**
* This method is based on {@link BaseSpawner#isNearPlayer(Level, BlockPos)}.
*/
@SuppressWarnings("JavadocReference")
@Override
public boolean isNearPlayer() {
BlockPos bPos = getBlockPosition();
return getWorld().hasNearbyAlivePlayer(
(double) bPos.getX() + 0.5,
(double) bPos.getY() + 0.5,
(double) bPos.getZ() + 0.5,
this.spawner.requiredPlayerRange);
}
/**
* This method is based on {@link BaseSpawner#serverTick(ServerLevel, BlockPos)}.
*/
@Override
public void tick() throws InvocationTargetException, IllegalAccessException {
ServerLevel worldserver = getWorld();
BlockPos blockposition = getBlockPosition();
if (this.spawner.spawnDelay == -1) {
this.delay(worldserver, blockposition);
}
if (this.spawner.spawnDelay > 0) {
--this.spawner.spawnDelay;
} else {
boolean flag = false;
RandomSource randomsource = worldserver.getRandom();
SpawnData mobspawnerdata = (SpawnData) this.getOrCreateNextSpawnDataMethod.invoke(this.spawner, worldserver, randomsource, blockposition);
int i = 0;
while (true) {
if (i >= this.spawner.spawnCount) {
if (flag) {
this.delay(worldserver, blockposition);
}
break;
}
CompoundTag nbttagcompound = mobspawnerdata.getEntityToSpawn();
Optional<EntityType<?>> optional = EntityType.by(nbttagcompound);
if (optional.isEmpty()) {
this.delay(worldserver, blockposition);
return;
}
ListTag nbttaglist = nbttagcompound.getList("Pos", 6);
int j = nbttaglist.size();
double d0 = j >= 1 ? nbttaglist.getDouble(0) : (double) blockposition.getX() + (randomsource.nextDouble() - randomsource.nextDouble()) * (double) this.spawner.spawnRange + 0.5;
double d1 = j >= 2 ? nbttaglist.getDouble(1) : (double) (blockposition.getY() + randomsource.nextInt(3) - 1);
double d2 = j >= 3 ? nbttaglist.getDouble(2) : (double) blockposition.getZ() + (randomsource.nextDouble() - randomsource.nextDouble()) * (double) this.spawner.spawnRange + 0.5;
if (worldserver.noCollision(optional.get().getAABB(d0, d1, d2))) {
label128:
{
BlockPos blockposition1 = BlockPos.containing(d0, d1, d2);
if (mobspawnerdata.getCustomSpawnRules().isPresent()) {
if (!optional.get().getCategory().isFriendly() && worldserver.getDifficulty() == Difficulty.PEACEFUL) {
break label128;
}
SpawnData.CustomSpawnRules mobspawnerdata_a = mobspawnerdata.getCustomSpawnRules().get();
if (!mobspawnerdata_a.blockLightLimit().isValueInRange(worldserver.getBrightness(LightLayer.BLOCK, blockposition1)) || !mobspawnerdata_a.skyLightLimit().isValueInRange(worldserver.getBrightness(LightLayer.SKY, blockposition1))) {
break label128;
}
} else if (!SpawnPlacements.checkSpawnRules((EntityType<?>) optional.get(), worldserver, MobSpawnType.SPAWNER, blockposition1, worldserver.getRandom())) {
break label128;
}
Entity entity = EntityType.loadEntityRecursive(nbttagcompound, worldserver, (entity1) -> {
entity1.moveTo(d0, d1, d2, entity1.getYRot(), entity1.getXRot());
return entity1;
});
if (entity == null) {
this.delay(worldserver, blockposition);
return;
}
int k = worldserver.getEntitiesOfClass(entity.getClass(), (new AABB(blockposition.getX(), blockposition.getY(), blockposition.getZ(), blockposition.getX() + 1, blockposition.getY() + 1, blockposition.getZ() + 1)).inflate(this.spawner.spawnRange)).size();
if (k >= this.spawner.maxNearbyEntities) {
this.delay(worldserver, blockposition);
return;
}
entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F);
if (entity instanceof Mob entityinsentient) {
if (mobspawnerdata.getCustomSpawnRules().isEmpty() && !entityinsentient.checkSpawnRules(worldserver, MobSpawnType.SPAWNER) || !entityinsentient.checkSpawnObstruction(worldserver)) {
break label128;
}
if (mobspawnerdata.getEntityToSpawn().size() == 1 && mobspawnerdata.getEntityToSpawn().contains("id", 8)) {
((Mob) entity).finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.SPAWNER, null, null);
}
if (entityinsentient.level.spigotConfig.nerfSpawnerMobs) {
entityinsentient.aware = false;
}
}
if (CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) {
Entity vehicle = entity.getVehicle();
if (vehicle != null) {
vehicle.discard();
}
for (Entity passenger : entity.getIndirectPassengers()) {
passenger.discard();
}
} else {
if (!worldserver.tryAddFreshEntityWithPassengers(entity, CreatureSpawnEvent.SpawnReason.SPAWNER)) {
this.delay(worldserver, blockposition);
return;
}
worldserver.levelEvent(2004, blockposition, 0);
worldserver.gameEvent(entity, GameEvent.ENTITY_PLACE, blockposition1);
if (entity instanceof Mob) {
((Mob) entity).spawnAnim();
}
flag = true;
}
}
}
++i;
}
}
}
/**
* This method is based on {@link BaseSpawner#delay(Level, BlockPos)}.
*/
@SuppressWarnings("JavadocReference")
private void delay(ServerLevel world, BlockPos bPos) throws InvocationTargetException, IllegalAccessException {
RandomSource randomsource = world.random;
if (this.spawner.maxSpawnDelay <= this.spawner.minSpawnDelay) {
this.spawner.spawnDelay = this.spawner.minSpawnDelay;
} else {
this.spawner.spawnDelay = this.spawner.minSpawnDelay + randomsource.nextInt(this.spawner.maxSpawnDelay - this.spawner.minSpawnDelay);
}
Optional<WeightedEntry.Wrapper<SpawnData>> weightedEntry = this.spawner.spawnPotentials.getRandom(randomsource);
if (weightedEntry.isPresent()) {
this.setNextSpawnDataMethod.invoke(this.spawner, world, bPos, weightedEntry.get().getData());
}
this.spawner.broadcastEvent(world, bPos, 1);
}
private ServerLevel getWorld() {
return ((CraftWorld) this.bukkitSpawner.getWorld()).getHandle();
}
private BlockPos getBlockPosition() {
return ((CraftCreatureSpawner) this.bukkitSpawner).getPosition();
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<groupId>com.songoda</groupId>
<artifactId>SongodaCore-Modules</artifactId>
<version>2.6.18</version>
<version>2.6.19</version>
<packaging>pom</packaging>
<!-- Run 'mvn versions:set -DgenerateBackupPoms=false -DnewVersion=X.Y.Z' to update version recursively -->
@ -49,6 +49,7 @@
<module>NMS/NMS-v1_19_R1</module>
<module>NMS/NMS-v1_19_R1v2</module>
<module>NMS/NMS-v1_19_R2</module>
<module>NMS/NMS-v1_19_R3</module>
</modules>
<issueManagement>