Initial commit
This commit is contained in:
commit
14ef4c9e13
66 files changed
+6705
No files matched your search
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
target/
|
||||
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
.flattened-pom.xml
|
||||
|
||||
# Common working directory
|
||||
run/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Eisi05
|
||||
|
||||
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,173 @@
|
||||
# NpcAPI
|
||||
|
||||
A powerful and easy-to-use NPC (Non-Player Character) API for Minecraft Spigot plugins that allows you to create, manage, and customize NPCs with
|
||||
advanced features.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎭 Create custom NPCs with ease
|
||||
- 🎨 Customize NPC appearance (skins, glowing effects, etc.)
|
||||
- 👆 Handle click events and interactions
|
||||
- 🎬 Play animations and control NPC behavior
|
||||
- 💾 Save and load NPCs persistently
|
||||
- 👥 Show/hide NPCs for specific players
|
||||
- 🔍 Comprehensive NPC management system
|
||||
|
||||
## Installation
|
||||
|
||||
### Maven
|
||||
|
||||
Add the following repository and dependency to your `pom.xml`:
|
||||
|
||||
```xml
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.Eisi05</groupId>
|
||||
<artifactId>NpcApi</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
|
||||
Add the following to your `build.gradle`:
|
||||
|
||||
```gradle
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.github.Eisi05:NpcApi:Tag'
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Plugin Setup
|
||||
|
||||
Initialize the NpcAPI in your plugin's main class:
|
||||
|
||||
```java
|
||||
|
||||
@Override
|
||||
public void onEnable()
|
||||
{
|
||||
// Initialize NpcAPI with default configuration
|
||||
NpcApi.createInstance(this, new NpcConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable()
|
||||
{
|
||||
// Properly disable NpcAPI
|
||||
NpcApi.disable();
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Creating a Basic NPC
|
||||
|
||||
```java
|
||||
// Create a location where the NPC should spawn
|
||||
Location location = new Location(world, x, y, z);
|
||||
|
||||
// Create a new NPC with a name
|
||||
NPC npc = new NPC(location, WrappedComponent.create("Test"));
|
||||
|
||||
// Enable the NPC to make it visible to all players
|
||||
npc.setEnabled(true);
|
||||
```
|
||||
|
||||
### Customizing NPC Appearance
|
||||
|
||||
```java
|
||||
// Make the NPC glow with a red color
|
||||
npc.setOption(NpcOption.GLOWING, ChatColor.RED);
|
||||
|
||||
// Set a custom skin from a player
|
||||
npc.setOption(NpcOption.SKIN, Skin.fromPlayer(player));
|
||||
```
|
||||
|
||||
### Handling Click Events
|
||||
|
||||
```java
|
||||
// Set up a click event handler
|
||||
npc.setClickEvent(event -> {
|
||||
Player player = event.getPlayer();
|
||||
NPC clickedNpc = event.getNpc();
|
||||
player.sendMessage("You clicked " + clickedNpc.getName().toLegacy());
|
||||
});
|
||||
```
|
||||
|
||||
### Managing NPC State
|
||||
|
||||
```java
|
||||
npc.save();
|
||||
npc.setName(WrappedComponent.create("New Name"));
|
||||
npc.setLocation(newLocation);
|
||||
npc.reload();
|
||||
```
|
||||
|
||||
### Advanced NPC Control
|
||||
|
||||
```java
|
||||
npc.playAnimation(/* animation parameters */);
|
||||
npc.showNPCToPlayer(player);
|
||||
npc.hideNpcFromPlayer(player);
|
||||
npc.lookAtPlayer(player);
|
||||
npc.delete();
|
||||
```
|
||||
|
||||
## NPC Management
|
||||
|
||||
### Getting All NPCs
|
||||
|
||||
```java
|
||||
// Get a list of all available NPCs
|
||||
List<NPC> allNpcs = NpcManager.getList();
|
||||
```
|
||||
|
||||
### Finding NPCs by UUID
|
||||
|
||||
```java
|
||||
// Get a specific NPC by its UUID
|
||||
UUID npcUuid = /* your NPC's UUID */;
|
||||
NPC npc = NpcManager.fromUUID(npcUuid);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### NPC Class
|
||||
|
||||
| Method | Description |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| `setOption(NpcOption, Object)` | Set NPC options like glowing, skin, etc. |
|
||||
| `setClickEvent(Consumer<ClickEvent>)` | Set the click event handler |
|
||||
| `setEnabled(boolean)` | Enable/disable NPC visibility |
|
||||
| `save()` | Save NPC to persistent storage |
|
||||
| `reload()` | Reload NPC data |
|
||||
| `setName(WrappedComponent)` | Update NPC display name |
|
||||
| `setLocation(Location)` | Move NPC to new location |
|
||||
| `playAnimation(...)` | Play NPC animation |
|
||||
| `showNPCToPlayer(Player)` | Show NPC to specific player |
|
||||
| `hideNpcFromPlayer(Player)` | Hide NPC from specific player |
|
||||
| `lookAtPlayer(Player)` | Make NPC look at player |
|
||||
| `delete()` | Remove NPC permanently |
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java 17+
|
||||
- Spigot 1.17+ (compatible with newer versions)
|
||||
- Minecraft server with NPC support
|
||||
|
||||
[](https://jitpack.io/#Eisi05/NpcApi)
|
||||
@@ -0,0 +1,3 @@
|
||||
jdk:
|
||||
- openjdk17
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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>
|
||||
|
||||
<groupId>de.eisi05</groupId>
|
||||
<artifactId>NpcApi</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>NpcApi</name>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean package</defaultGoal>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigotmc-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype</id>
|
||||
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>codemc-releases</id>
|
||||
<url>https://repo.codemc.io/repository/maven-releases/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>codemc-snapshots</id>
|
||||
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>mojang</id>
|
||||
<url>https://libraries.minecraft.net/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.17-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>4.2.1.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>datafixerupper</artifactId>
|
||||
<version>6.0.8</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>authlib</artifactId>
|
||||
<version>3.13.56</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>brigadier</artifactId>
|
||||
<version>1.0.18</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,102 @@
|
||||
package de.eisi05.npc.api;
|
||||
|
||||
import de.eisi05.npc.api.listeners.ChangeWorldListener;
|
||||
import de.eisi05.npc.api.listeners.ConnectionListener;
|
||||
import de.eisi05.npc.api.listeners.NpcInteractListener;
|
||||
import de.eisi05.npc.api.manager.NpcManager;
|
||||
import de.eisi05.npc.api.objects.NPC;
|
||||
import de.eisi05.npc.api.objects.NpcConfig;
|
||||
import de.eisi05.npc.api.objects.Tasks;
|
||||
import de.eisi05.npc.api.utils.PacketReader;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The main entry point and singleton class for the NPC API.
|
||||
* This class handles the initialization, configuration, and shutdown
|
||||
* of the NPC functionality within a Bukkit plugin.
|
||||
*/
|
||||
public final class NpcApi
|
||||
{
|
||||
/**
|
||||
* A static reference to the Bukkit plugin instance that is using this API.
|
||||
* This is set during the API's initialization.
|
||||
*/
|
||||
public static Plugin plugin;
|
||||
|
||||
/**
|
||||
* The configuration object for the NPC API, containing various settings
|
||||
* like the look-at timer.
|
||||
*/
|
||||
public static NpcConfig config;
|
||||
|
||||
private static NpcApi npcApi;
|
||||
|
||||
/**
|
||||
* Private constructor to enforce the singleton pattern.
|
||||
* Initializes the API by registering listeners, loading existing NPCs,
|
||||
* injecting packet readers, and starting recurring tasks.
|
||||
*
|
||||
* @param plugin The {@link JavaPlugin} instance using this API. Must not be {@code null}.
|
||||
* @param config The {@link NpcConfig} object for the API. Must not be {@code null}.
|
||||
*/
|
||||
private NpcApi(@NotNull JavaPlugin plugin, @NotNull NpcConfig config)
|
||||
{
|
||||
NpcApi.plugin = plugin;
|
||||
NpcApi.config = config;
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(new ChangeWorldListener(), plugin);
|
||||
Bukkit.getPluginManager().registerEvents(new ConnectionListener(), plugin);
|
||||
Bukkit.getPluginManager().registerEvents(new NpcInteractListener(), plugin);
|
||||
|
||||
NpcManager.loadNPCs();
|
||||
PacketReader.injectAll();
|
||||
|
||||
Tasks.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or retrieves the singleton instance of the {@code NpcApi} with a default configuration.
|
||||
* If the API instance does not exist or the provided plugin is null, a new instance is created.
|
||||
*
|
||||
* @param plugin The {@link JavaPlugin} instance using this API. Must not be {@code null}.
|
||||
* @return The singleton {@link NpcApi} instance. Must not be {@code null}.
|
||||
*/
|
||||
public static @NotNull NpcApi createInstance(@NotNull JavaPlugin plugin)
|
||||
{
|
||||
return createInstance(plugin, new NpcConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or retrieves the singleton instance of the {@code NpcApi} with a custom configuration.
|
||||
* If the API instance does not exist or the provided plugin is null, a new instance is created.
|
||||
*
|
||||
* @param plugin The {@link JavaPlugin} instance using this API. Must not be {@code null}.
|
||||
* @param config The {@link NpcConfig} object to use for the API. Must not be {@code null}.
|
||||
* @return The singleton {@link NpcApi} instance. Must not be {@code null}.
|
||||
*/
|
||||
public static @NotNull NpcApi createInstance(@NotNull JavaPlugin plugin, @NotNull NpcConfig config)
|
||||
{
|
||||
if(npcApi == null || plugin == null)
|
||||
npcApi = new NpcApi(plugin, config);
|
||||
|
||||
return npcApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the NPC API, performing the necessary cleanup.
|
||||
* This includes hiding all active NPCs from players, clearing the NPC manager,
|
||||
* and uninjecting packet readers. It also nullifies the static references.
|
||||
*/
|
||||
public static void disable()
|
||||
{
|
||||
NpcManager.getList().forEach(NPC::hideNpcFromAllPlayers);
|
||||
NpcManager.clear();
|
||||
PacketReader.uninjectAll();
|
||||
|
||||
npcApi = null;
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package de.eisi05.npc.api.enums;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Represents the type of click action that can be performed on an NPC.
|
||||
* This includes left click, right click, or both.
|
||||
*/
|
||||
public enum ClickActionType implements Serializable
|
||||
{
|
||||
/**
|
||||
* Represents a left-click action.
|
||||
*/
|
||||
LEFT("Left"),
|
||||
|
||||
/**
|
||||
* Represents a right-click action.
|
||||
*/
|
||||
RIGHT("Right"),
|
||||
/**
|
||||
* Represents both left and right-click actions.
|
||||
*/
|
||||
|
||||
BOTH("Left & Right");
|
||||
|
||||
public final @NotNull String title;
|
||||
|
||||
/**
|
||||
* Constructs a ClickActionType with the given title.
|
||||
*
|
||||
* @param title The display name for this click action type.
|
||||
*/
|
||||
ClickActionType(@NotNull String title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.eisi05.npc.api.enums;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Represents the different customizable skin parts of an NPC,
|
||||
* each associated with a unique byte value and an icon material.
|
||||
*/
|
||||
public enum SkinParts implements Serializable
|
||||
{
|
||||
CAPE((byte) 0x01, Material.ELYTRA),
|
||||
JACKET((byte) 0x02, Material.LEATHER_CHESTPLATE),
|
||||
LEFT_SLEEVE((byte) 0x04, Material.SHIELD),
|
||||
RIGHT_SLEEVE((byte) 0x08, Material.DIAMOND_SWORD),
|
||||
LEFT_PANTS_LEG((byte) 0x10, Material.LEATHER_LEGGINGS),
|
||||
RIGHT_PANTS_LEG((byte) 0x20, Material.LEATHER_LEGGINGS),
|
||||
HAT((byte) 0x40, Material.TURTLE_HELMET);
|
||||
|
||||
private final byte value;
|
||||
private final Material icon;
|
||||
|
||||
SkinParts(byte value, @NotNull Material icon)
|
||||
{
|
||||
this.value = value;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte value representing this skin part.
|
||||
*
|
||||
* @return the bitmask value
|
||||
*/
|
||||
public byte getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icon material associated with this skin part.
|
||||
*
|
||||
* @return the icon Material, never null
|
||||
*/
|
||||
public @NotNull Material getIcon()
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package de.eisi05.npc.api.events;
|
||||
|
||||
import de.eisi05.npc.api.enums.ClickActionType;
|
||||
import de.eisi05.npc.api.objects.NPC;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Event triggered when a player interacts with an NPC.
|
||||
* Contains information about the player, the NPC, and the type of click action.
|
||||
*/
|
||||
public class NpcInteractEvent extends Event implements Serializable, Cancellable
|
||||
{
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
private final Player player;
|
||||
private final NPC npc;
|
||||
private final ClickActionType action;
|
||||
private boolean cancelled;
|
||||
|
||||
/**
|
||||
* Creates a new NpcInteractEvent.
|
||||
*
|
||||
* @param player the player who interacted with the NPC
|
||||
* @param npc the NPC that was interacted with
|
||||
* @param action the type of click action performed
|
||||
*/
|
||||
public NpcInteractEvent(@NotNull Player player, @NotNull NPC npc, @NotNull ClickActionType action)
|
||||
{
|
||||
this.player = player;
|
||||
this.npc = npc;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HandlerList for this event.
|
||||
*
|
||||
* @return the static HandlerList instance
|
||||
*/
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the player who triggered this event.
|
||||
*
|
||||
* @return the interacting player, never null
|
||||
*/
|
||||
public @NotNull Player getPlayer()
|
||||
{
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the NPC involved in this event.
|
||||
*
|
||||
* @return the interacted NPC, never null
|
||||
*/
|
||||
public @NotNull NPC getNpc()
|
||||
{
|
||||
return npc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the click action type of this interaction.
|
||||
*
|
||||
* @return the ClickActionType, never null
|
||||
*/
|
||||
public @NotNull ClickActionType getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers()
|
||||
{
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.eisi05.npc.api.interfaces;
|
||||
|
||||
import de.eisi05.npc.api.events.NpcInteractEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Functional interface representing an action to be performed
|
||||
* when an NPC is clicked.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NpcClickAction extends Serializable
|
||||
{
|
||||
@Serial
|
||||
long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Called when an NPC click event occurs.
|
||||
*
|
||||
* @param event the NpcInteractEvent containing interaction details
|
||||
*/
|
||||
void call(@NotNull NpcInteractEvent event);
|
||||
|
||||
/**
|
||||
* Returns a copy of this NpcClickAction.
|
||||
* The default implementation returns the same instance.
|
||||
*
|
||||
* @return a copy of this action
|
||||
*/
|
||||
default NpcClickAction copy() {return this;}
|
||||
|
||||
/**
|
||||
* Initializes this {@link NpcClickAction}.
|
||||
* This method can be used for any setup or configuration that needs to occur
|
||||
* after the action is created or loaded.
|
||||
* The default implementation simply returns the current instance,
|
||||
* indicating no specific initialization is required by default.
|
||||
*
|
||||
* @return The initialized {@link NpcClickAction} instance. By default, it returns {@code this}.
|
||||
*/
|
||||
default NpcClickAction initialize() {return this;}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.eisi05.npc.api.listeners;
|
||||
|
||||
import de.eisi05.npc.api.manager.NpcManager;
|
||||
import de.eisi05.npc.api.objects.NPC;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
|
||||
public class ChangeWorldListener implements Listener
|
||||
{
|
||||
@EventHandler
|
||||
public void onChange(PlayerChangedWorldEvent event)
|
||||
{
|
||||
for(NPC npc : NpcManager.getList())
|
||||
npc.showNPCToPlayer(event.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.eisi05.npc.api.listeners;
|
||||
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.manager.NpcManager;
|
||||
import de.eisi05.npc.api.objects.NPC;
|
||||
import de.eisi05.npc.api.utils.PacketReader;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public class ConnectionListener implements Listener
|
||||
{
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event)
|
||||
{
|
||||
PacketReader.inject(event.getPlayer());
|
||||
|
||||
new BukkitRunnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
NpcManager.getList().forEach(npc -> npc.showNPCToPlayer(event.getPlayer()));
|
||||
}
|
||||
}.runTaskLater(NpcApi.plugin, 10L);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLeave(PlayerQuitEvent event)
|
||||
{
|
||||
PacketReader.uninject(event.getPlayer());
|
||||
|
||||
for(NPC npc : NpcManager.getList())
|
||||
npc.hideNpcFromPlayer(event.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.eisi05.npc.api.listeners;
|
||||
|
||||
import de.eisi05.npc.api.events.NpcInteractEvent;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class NpcInteractListener implements Listener
|
||||
{
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onClick(NpcInteractEvent event)
|
||||
{
|
||||
if(event.getNpc().getClickEvent() != null)
|
||||
event.getNpc().getClickEvent().call(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package de.eisi05.npc.api.manager;
|
||||
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.objects.NPC;
|
||||
import de.eisi05.npc.api.utils.ObjectSaver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Manages the collection and lifecycle of NPC instances.
|
||||
*/
|
||||
public class NpcManager
|
||||
{
|
||||
private static final List<NPC> listNPC = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Adds an NPC to the manager's list.
|
||||
*
|
||||
* @param npc the NPC to add
|
||||
*/
|
||||
public static void addNPC(@NotNull NPC npc)
|
||||
{
|
||||
listNPC.add(npc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all managed NPCs.
|
||||
*
|
||||
* @return the list of NPCs
|
||||
*/
|
||||
public static @NotNull List<NPC> getList()
|
||||
{
|
||||
return listNPC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an NPC from the manager's list.
|
||||
*
|
||||
* @param npc the NPC to remove
|
||||
*/
|
||||
public static void removeNPC(@NotNull NPC npc)
|
||||
{
|
||||
listNPC.remove(npc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all NPCs from the manager.
|
||||
*/
|
||||
public static void clear()
|
||||
{
|
||||
listNPC.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an NPC by its UUID.
|
||||
*
|
||||
* @param uuid the UUID to search for
|
||||
* @return an Optional containing the NPC if found, empty otherwise
|
||||
*/
|
||||
public static @NotNull Optional<NPC> fromUUID(@NotNull UUID uuid)
|
||||
{
|
||||
return listNPC.stream().filter(npc -> npc.getUUID().equals(uuid)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads NPCs from disk files in the plugin data folder.
|
||||
* Logs the count of successfully and unsuccessfully loaded NPCs.
|
||||
*/
|
||||
public static void loadNPCs()
|
||||
{
|
||||
File file = new File(NpcApi.plugin.getDataFolder(), "NPC");
|
||||
|
||||
File[] files = file.listFiles();
|
||||
if(files == null)
|
||||
return;
|
||||
|
||||
long failCounter = 0;
|
||||
long successCounter = 0;
|
||||
|
||||
for(File file1 : files)
|
||||
{
|
||||
if(!file1.getName().endsWith(".npc"))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
NPC.SerializedNPC serializedNPC = new ObjectSaver(file1).read();
|
||||
serializedNPC.deserializedNPC().showNpcToAllPlayers();
|
||||
successCounter++;
|
||||
} catch(Exception e)
|
||||
{
|
||||
failCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
if(successCounter == 1)
|
||||
NpcApi.plugin.getLogger().info("Successfully loaded " + successCounter + " NPC");
|
||||
else if(successCounter > 1)
|
||||
NpcApi.plugin.getLogger().info("Successfully loaded " + successCounter + " NPC's");
|
||||
|
||||
if(failCounter == 1)
|
||||
NpcApi.plugin.getLogger().warning("Failed to load " + failCounter + " NPC");
|
||||
else if(failCounter > 1)
|
||||
NpcApi.plugin.getLogger().warning("Failed to load " + failCounter + " NPC's");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
package de.eisi05.npc.api.objects;
|
||||
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.interfaces.NpcClickAction;
|
||||
import de.eisi05.npc.api.manager.NpcManager;
|
||||
import de.eisi05.npc.api.utils.ObjectSaver;
|
||||
import de.eisi05.npc.api.utils.Var;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedArmorStand;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedComponent;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedPlayerTeam;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedServerPlayer;
|
||||
import de.eisi05.npc.api.wrapper.packets.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Represents a Non-Player Character (NPC) with location, appearance, options, and interaction logic.
|
||||
*/
|
||||
public class NPC extends NpcHolder
|
||||
{
|
||||
final List<Integer> toDeleteEntities = new ArrayList<>();
|
||||
private final WrappedServerPlayer serverPlayer;
|
||||
private final List<UUID> viewers = new ArrayList<>();
|
||||
private final Map<NpcOption<?, ?>, Object> options;
|
||||
private WrappedComponent name;
|
||||
private Location location;
|
||||
private NpcClickAction clickEvent;
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
/**
|
||||
* Creates an NPC at the specified location with a random UUID and default name.
|
||||
* The default name is an empty component.
|
||||
*
|
||||
* @param location the location to spawn the NPC. Must not be null.
|
||||
*/
|
||||
public NPC(@NotNull Location location)
|
||||
{
|
||||
this(location, UUID.randomUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NPC at the specified location with a random UUID and given name.
|
||||
*
|
||||
* @param location the location to spawn the NPC. Must not be null.
|
||||
* @param name the display name of the NPC. Must not be null.
|
||||
*/
|
||||
public NPC(@NotNull Location location, @NotNull WrappedComponent name)
|
||||
{
|
||||
this(location, UUID.randomUUID(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NPC at the specified location with the given UUID and default name.
|
||||
* The default name is an empty component.
|
||||
*
|
||||
* @param location the location to spawn the NPC. Must not be null.
|
||||
* @param uuid the UUID of the NPC. Must not be null.
|
||||
*/
|
||||
public NPC(@NotNull Location location, @NotNull UUID uuid)
|
||||
{
|
||||
this(location, uuid, WrappedComponent.create(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NPC at the specified location with the given UUID and name.
|
||||
* This is the primary constructor that initializes the NPC's core properties.
|
||||
*
|
||||
* @param location the location to spawn the NPC. Must not be null.
|
||||
* @param uuid the UUID of the NPC. Must not be null.
|
||||
* @param name the display name of the NPC. Must not be null.
|
||||
*/
|
||||
public NPC(@NotNull Location location, @NotNull UUID uuid, @NotNull WrappedComponent name)
|
||||
{
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
this.serverPlayer = WrappedServerPlayer.create(location, uuid, name);
|
||||
serverPlayer.moveTo(location);
|
||||
|
||||
this.options = new HashMap<>();
|
||||
for(NpcOption<?, ?> value : NpcOption.values())
|
||||
setOption(value, Var.unsafeCast(value.getDefaultValue()));
|
||||
|
||||
NpcManager.addNPC(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used for creating a copy of an NPC.
|
||||
*
|
||||
* @param location The new location for the NPC. Must not be null.
|
||||
* @param name The name for the NPC. Must not be null.
|
||||
* @param options The options map for the NPC. Must not be null.
|
||||
* @param clickEvent The click event for the NPC. Can be null.
|
||||
*/
|
||||
private NPC(@NotNull Location location, @NotNull WrappedComponent name, @NotNull Map<NpcOption<?, ?>, Object> options,
|
||||
@Nullable NpcClickAction clickEvent)
|
||||
{
|
||||
this(location, UUID.randomUUID(), name);
|
||||
this.options.putAll(options);
|
||||
this.clickEvent = clickEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this NPC at a new location.
|
||||
* The copied NPC will have a new UUID but will retain the original NPC's name, options, and click event.
|
||||
*
|
||||
* @param newLocation the location for the copied NPC. Must not be null.
|
||||
* @return the new NPC instance. Will not be null.
|
||||
*/
|
||||
public @NotNull NPC copy(@NotNull Location newLocation)
|
||||
{
|
||||
return new NPC(newLocation, name.copy(), new HashMap<>(options), clickEvent == null ? null : clickEvent.copy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this NPC has been saved to a file.
|
||||
*
|
||||
* @return {@code true} if the NPC's data file exists, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isSaved()
|
||||
{
|
||||
return new File(NpcApi.plugin.getDataFolder(), "NPC\\" + getUUID() + ".npc").exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the NPC's data to a file.
|
||||
* This method serializes the NPC's current state and writes it to a .npc file.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs during saving.
|
||||
*/
|
||||
@Override
|
||||
public void save() throws IOException
|
||||
{
|
||||
new File(NpcApi.plugin.getDataFolder(), "NPC").mkdirs();
|
||||
new ObjectSaver(new File(NpcApi.plugin.getDataFolder(), "NPC\\" + getUUID() + ".npc")).write(SerializedNPC.serializedNPC(this),
|
||||
false);
|
||||
super.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying server player representation for this NPC.
|
||||
*
|
||||
* @return the {@link WrappedServerPlayer} instance for this NPC. Will not be null.
|
||||
*/
|
||||
public @NotNull WrappedServerPlayer getServerPlayer()
|
||||
{
|
||||
return serverPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the click action associated with this NPC.
|
||||
*
|
||||
* @return the {@link NpcClickAction} for this NPC, or {@code null} if no action is set.
|
||||
*/
|
||||
public @Nullable NpcClickAction getClickEvent()
|
||||
{
|
||||
return clickEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the click action for this NPC.
|
||||
*
|
||||
* @param event the {@link NpcClickAction} to set, or {@code null} to remove the current action.
|
||||
* @return this NPC instance for method chaining. Will not be null.
|
||||
*/
|
||||
public @NotNull NPC setClickEvent(@Nullable NpcClickAction event)
|
||||
{
|
||||
this.clickEvent = event;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the NPC is currently enabled.
|
||||
* An enabled NPC is visible and interactable (unless overridden by player permissions).
|
||||
*
|
||||
* @return {@code true} if the NPC is enabled, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return getOption(NpcOption.ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state of the NPC.
|
||||
* Changing this state will trigger a reload of the NPC for all viewers.
|
||||
*
|
||||
* @param enabled {@code true} to enable the NPC, {@code false} to disable it.
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
setOption(NpcOption.ENABLED, enabled);
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific option for this NPC.
|
||||
*
|
||||
* @param option the {@link NpcOption} to set. Must not be null.
|
||||
* @param value the value for the option. If {@code null}, the option will be removed (reverting to default).
|
||||
* @param <T> the type of the option's value.
|
||||
*/
|
||||
public <T> void setOption(@NotNull NpcOption<T, ?> option, @Nullable T value)
|
||||
{
|
||||
if(value == null)
|
||||
options.remove(option);
|
||||
else
|
||||
options.put(option, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a specific option for this NPC.
|
||||
* If the option has not been explicitly set, its default value will be returned.
|
||||
*
|
||||
* @param option the {@link NpcOption} to get. Must not be null.
|
||||
* @param <T> the type of the option's value.
|
||||
* @return the value of the option. Will not be null (guaranteed by NpcOption default values).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> @NotNull T getOption(@NotNull NpcOption<T, ?> option)
|
||||
{
|
||||
return (T) options.getOrDefault(option, option.getDefaultValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays an animation for this NPC, visible to the specified player.
|
||||
*
|
||||
* @param player the player who will see the animation. Must not be null.
|
||||
* @param animation the {@link AnimatePacket.Animation} to play. Must not be null.
|
||||
*/
|
||||
public void playAnimation(@NotNull Player player, @NotNull AnimatePacket.Animation animation)
|
||||
{
|
||||
WrappedServerPlayer.fromPlayer(player).sendPacket(AnimatePacket.create(getServerPlayer(), animation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the NPC for all current viewers.
|
||||
* This typically involves hiding and then re-showing the NPC to apply any changes.
|
||||
*/
|
||||
public void reload()
|
||||
{
|
||||
final List<UUID> viewers = new ArrayList<>(this.viewers);
|
||||
hideNpcFromAllPlayers();
|
||||
viewers.stream().filter(uuid -> Bukkit.getPlayer(uuid) != null).forEach(uuid -> showNPCToPlayer(Bukkit.getPlayer(uuid)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current location of the NPC.
|
||||
*
|
||||
* @return the {@link Location} of the NPC. Will not be null.
|
||||
*/
|
||||
public @NotNull Location getLocation()
|
||||
{
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location of the NPC.
|
||||
* This will also update the underlying server player's position.
|
||||
*
|
||||
* @param location the new {@link Location} for the NPC. Must not be null.
|
||||
*/
|
||||
public void setLocation(@NotNull Location location)
|
||||
{
|
||||
this.location = location;
|
||||
this.serverPlayer.moveTo(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique identifier (UUID) of this NPC.
|
||||
*
|
||||
* @return the {@link UUID} of the NPC. Will not be null.
|
||||
*/
|
||||
public @NotNull UUID getUUID()
|
||||
{
|
||||
return serverPlayer.getUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display name of this NPC.
|
||||
*
|
||||
* @return the {@link WrappedComponent} representing the NPC's name. Will not be null.
|
||||
*/
|
||||
public @NotNull WrappedComponent getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name of this NPC.
|
||||
* This also updates the name for the underlying server player and its list name.
|
||||
*
|
||||
* @param name the new {@link WrappedComponent} name for the NPC. Must not be null.
|
||||
*/
|
||||
public void setName(@NotNull WrappedComponent name)
|
||||
{
|
||||
this.name = name;
|
||||
serverPlayer.setName(name.toLegacyNoColor());
|
||||
serverPlayer.setListName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp when this NPC was created.
|
||||
*
|
||||
* @return the {@link Instant} of creation. Will not be null.
|
||||
*/
|
||||
public Instant getCreatedAt()
|
||||
{
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the NPC visible to all currently online players.
|
||||
* This respects the NPC's enabled state and player permissions.
|
||||
*/
|
||||
public void showNpcToAllPlayers()
|
||||
{
|
||||
Bukkit.getOnlinePlayers().forEach(this::showNPCToPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the NPC visible to a specific player.
|
||||
* If the NPC is disabled and the player is not an operator, the NPC will not be shown.
|
||||
* This method handles sending all necessary packets to display the NPC correctly.
|
||||
*
|
||||
* @param player the player to show the NPC to. Must not be null.
|
||||
*/
|
||||
public void showNPCToPlayer(@NotNull Player player)
|
||||
{
|
||||
if(!getOption(NpcOption.ENABLED) && !player.isOp())
|
||||
return;
|
||||
|
||||
if(!player.getWorld().getName().equals(serverPlayer.getWorld().getName()))
|
||||
{
|
||||
hideNpcFromPlayer(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!viewers.contains(player.getUniqueId()))
|
||||
viewers.add(player.getUniqueId());
|
||||
|
||||
List<PacketWrapper> packets = new ArrayList<>();
|
||||
|
||||
packets.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.ADD_PLAYER, serverPlayer));
|
||||
packets.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, serverPlayer));
|
||||
|
||||
if(!Versions.isCurrentVersionSmallerThan(Versions.V1_19_3))
|
||||
packets.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LISTED, serverPlayer));
|
||||
|
||||
if(!Versions.isCurrentVersionSmallerThan(Versions.V1_21_2))
|
||||
packets.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER, serverPlayer));
|
||||
|
||||
packets.add(serverPlayer.getAddEntityPacket());
|
||||
|
||||
WrappedPlayerTeam wrappedPlayerTeam = WrappedPlayerTeam.create(player, getServerPlayer().getName());
|
||||
wrappedPlayerTeam.setNameTagVisibility(WrappedPlayerTeam.Visibility.NEVER);
|
||||
|
||||
packets.add(SetPlayerTeamPacket.createAddOrModifyPacket(wrappedPlayerTeam, true));
|
||||
packets.add(SetPlayerTeamPacket.createPlayerPacket(wrappedPlayerTeam, getServerPlayer().getName(), SetPlayerTeamPacket.Action.ADD));
|
||||
|
||||
packets.add(new RotateHeadPacket(serverPlayer, (byte) ((location.getYaw() % 360) * 256 / 360)));
|
||||
packets.add(new MoveEntityPacket.Rot(serverPlayer.getId(), (byte) location.getYaw(), (byte) location.getPitch(), serverPlayer.isOnGround()));
|
||||
|
||||
Arrays.stream(NpcOption.values()).filter(npcOption -> !npcOption.equals(NpcOption.ENABLED))
|
||||
.forEach(npcOption -> npcOption.getPacket(getOption(npcOption), this, player).ifPresent(packets::add));
|
||||
|
||||
NpcOption.ENABLED.getPacket(isEnabled(), this, player).ifPresent(packets::add);
|
||||
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_21))
|
||||
serverPlayer.getNameTag()
|
||||
.moveTo(getLocation().clone().add(0, (serverPlayer.getBoundingBox().getYSize() * getOption(NpcOption.SCALE)), 0));
|
||||
|
||||
packets.add(serverPlayer.getNameTag().getAddEntityPacket());
|
||||
packets.add(
|
||||
SetEntityDataPacket.create(serverPlayer.getNameTag().getId(), WrappedArmorStand.applyData(serverPlayer.getNameTag(), name), true));
|
||||
|
||||
if(!Versions.isCurrentVersionSmallerThan(Versions.V1_21))
|
||||
packets.add(new SetPassengerPacket(serverPlayer));
|
||||
|
||||
WrappedServerPlayer wrappedServerPlayer = WrappedServerPlayer.fromPlayer(player);
|
||||
packets.forEach(wrappedServerPlayer::sendPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the NPC from all currently online players.
|
||||
*/
|
||||
public void hideNpcFromAllPlayers()
|
||||
{
|
||||
Bukkit.getOnlinePlayers().forEach(this::hideNpcFromPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the NPC from a specific player.
|
||||
* This method sends packets to remove the NPC and its associated entities from the player's view.
|
||||
*
|
||||
* @param player the player to hide the NPC from. Must not be null.
|
||||
*/
|
||||
public void hideNpcFromPlayer(@NotNull Player player)
|
||||
{
|
||||
WrappedServerPlayer wrappedServerPlayer = WrappedServerPlayer.fromPlayer(player);
|
||||
wrappedServerPlayer.sendPacket(new RemoveEntityPacket(serverPlayer.getId()));
|
||||
wrappedServerPlayer.sendPacket(new RemoveEntityPacket(serverPlayer.getNameTag().getId()));
|
||||
|
||||
toDeleteEntities.forEach(integer -> wrappedServerPlayer.sendPacket(new RemoveEntityPacket(integer)));
|
||||
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_19_3))
|
||||
wrappedServerPlayer.sendPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.REMOVE_PLAYER, serverPlayer));
|
||||
else
|
||||
wrappedServerPlayer.sendPacket(new PlayerInfoRemovePacket(List.of(getUUID())));
|
||||
|
||||
viewers.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the NPC.
|
||||
* This hides the NPC from all players, removes it from the NPC manager, and deletes its saved data file.
|
||||
*/
|
||||
public void delete()
|
||||
{
|
||||
hideNpcFromAllPlayers();
|
||||
NpcManager.removeNPC(this);
|
||||
|
||||
new File(NpcApi.plugin.getDataFolder(), "NPC").mkdirs();
|
||||
File file = new File(NpcApi.plugin.getDataFolder(), "NPC\\" + getUUID() + ".npc");
|
||||
if(file.exists())
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the NPC look at a specific player.
|
||||
* This calculates the required yaw and pitch and sends update packets to the viewing player.
|
||||
*
|
||||
* @param viewer the player the NPC should look at. Must not be null.
|
||||
*/
|
||||
public void lookAtPlayer(@NotNull Player viewer)
|
||||
{
|
||||
Location npcLoc = serverPlayer.getBukkitPlayer().getLocation();
|
||||
Location playerLoc = viewer.getLocation();
|
||||
|
||||
if(npcLoc.getWorld() != playerLoc.getWorld())
|
||||
return;
|
||||
|
||||
double dx = playerLoc.getX() - npcLoc.getX();
|
||||
double dy = ((playerLoc.getY() + viewer.getEyeHeight())) -
|
||||
((npcLoc.getY() + serverPlayer.getBukkitPlayer().getEyeHeight() * getOption(NpcOption.SCALE)));
|
||||
double dz = playerLoc.getZ() - npcLoc.getZ();
|
||||
|
||||
double distanceXZ = Math.sqrt(dx * dx + dz * dz);
|
||||
float yaw = (float) Math.toDegrees(Math.atan2(-dx, dz));
|
||||
float pitch = (float) Math.toDegrees(-Math.atan2(dy, distanceXZ));
|
||||
|
||||
byte yawByte = (byte) (yaw * 256 / 360);
|
||||
byte pitchByte = (byte) (pitch * 256 / 360);
|
||||
|
||||
WrappedServerPlayer player = WrappedServerPlayer.fromPlayer(viewer);
|
||||
|
||||
player.sendPacket(new RotateHeadPacket(serverPlayer, yawByte));
|
||||
player.sendPacket(new MoveEntityPacket.Rot(serverPlayer.getId(), yawByte, pitchByte, serverPlayer.isOnGround()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A serializable representation of an NPC, used for saving and loading NPC data.
|
||||
* This record stores all essential properties of an NPC that need to be persisted.
|
||||
*
|
||||
* @param world The UUID of the world where the NPC is located.
|
||||
* @param x The x-coordinate of the NPC's location.
|
||||
* @param y The y-coordinate of the NPC's location.
|
||||
* @param z The z-coordinate of the NPC's location.
|
||||
* @param yaw The yaw (horizontal rotation) of the NPC.
|
||||
* @param pitch The pitch (vertical rotation) of the NPC.
|
||||
* @param id The unique identifier (UUID) of the NPC.
|
||||
* @param name The serialized representation of the NPC's display name.
|
||||
* @param options A map of NPC options, where keys are option paths (strings) and values are serializable option values.
|
||||
* @param clickEvent The click action associated with the NPC. Can be null.
|
||||
* @param createdAt The timestamp when the NPC was originally created.
|
||||
*/
|
||||
public record SerializedNPC(@NotNull UUID world, double x, double y, double z, float yaw, float pitch, @NotNull UUID id,
|
||||
@NotNull WrappedComponent.SerializedComponent name, @NotNull Map<String, ? extends Serializable> options,
|
||||
@Nullable NpcClickAction clickEvent, @NotNull Instant createdAt) implements Serializable
|
||||
{
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a {@link SerializedNPC} instance from an existing {@link NPC} object.
|
||||
*
|
||||
* @param npc The NPC to serialize. Must not be null.
|
||||
* @return A new {@link SerializedNPC} instance representing the given NPC. Will not be null.
|
||||
*/
|
||||
public static @NotNull SerializedNPC serializedNPC(@NotNull NPC npc)
|
||||
{
|
||||
return new SerializedNPC(npc.getLocation().getWorld().getUID(), npc.getLocation().getX(), npc.getLocation().getY(),
|
||||
npc.getLocation().getZ(), npc.getLocation().getYaw(), npc.getLocation().getPitch(), npc.getUUID(),
|
||||
npc.getName().serialize(), npc.options.keySet().stream().collect(
|
||||
Collectors.toMap(NpcOption::getPath, npcOption -> npcOption.serialize(npc.getOption((NpcOption<?, ?>) npcOption)))),
|
||||
npc.clickEvent, npc.createdAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes this {@link SerializedNPC} object back into a fully functional {@link NPC} instance.
|
||||
*
|
||||
* @param <T> The type of the NpcOption value.
|
||||
* @param <S> The serializable type of the NpcOption value.
|
||||
* @return A new {@link NPC} instance reconstructed from the serialized data. Will not be null.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, S extends Serializable> @NotNull NPC deserializedNPC()
|
||||
{
|
||||
NPC npc = new NPC(new Location(Bukkit.getWorld(world), x, y, z, yaw, pitch), id, name.deserialize()).setClickEvent(
|
||||
clickEvent == null ? clickEvent : clickEvent.initialize());
|
||||
options.forEach((string, serializable) -> NpcOption.getOption(string)
|
||||
.ifPresent(npcOption -> npc.setOption((NpcOption<T, S>) npcOption, (T) npcOption.deserialize(Var.unsafeCast(serializable)))));
|
||||
npc.createdAt = createdAt == null ? Instant.now() : createdAt;
|
||||
return npc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.eisi05.npc.api.objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Configuration settings for NPC behavior.
|
||||
* This class allows for customizing various aspects of an NPC, such as interaction timers.
|
||||
*/
|
||||
public class NpcConfig
|
||||
{
|
||||
/**
|
||||
* The time in ticks an NPC will look at a player after interaction.
|
||||
* The default value is 5 ticks.
|
||||
*/
|
||||
private long lookAtTimer = 5;
|
||||
|
||||
/**
|
||||
* Sets the duration an NPC will look at a player after an interaction.
|
||||
*
|
||||
* @param time The time in ticks. For example, 20 ticks = 1 second.
|
||||
* @return This {@link NpcConfig} instance for method chaining. Will not be null.
|
||||
*/
|
||||
public @NotNull NpcConfig lookAtTimer(long time)
|
||||
{
|
||||
lookAtTimer = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured duration an NPC will look at a player.
|
||||
*
|
||||
* @return The time in ticks.
|
||||
*/
|
||||
public long getLookAtTimer()
|
||||
{
|
||||
return lookAtTimer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.eisi05.npc.api.objects;
|
||||
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Abstract class representing an entity that can hold NPC-related data
|
||||
* and has a concept of unsaved changes.
|
||||
* It implements {@link InventoryHolder} but onlay as placeholder.
|
||||
*/
|
||||
public abstract class NpcHolder implements InventoryHolder
|
||||
{
|
||||
/**
|
||||
* Flag indicating whether there are unsaved changes to this holder.
|
||||
* Defaults to {@code false}.
|
||||
*/
|
||||
private boolean unsavedChanges = false;
|
||||
|
||||
/**
|
||||
* Checks if there are any unsaved changes for this NPC holder.
|
||||
*
|
||||
* @return {@code true} if there are unsaved changes, {@code false} otherwise.
|
||||
*/
|
||||
|
||||
public boolean hasUnsavedChanges()
|
||||
{
|
||||
return unsavedChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks that there are unsaved changes to this NPC holder.
|
||||
* This should be called whenever a modifiable property of the holder is changed.
|
||||
*/
|
||||
public void markChange()
|
||||
{
|
||||
unsavedChanges = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current state of the NPC holder.
|
||||
* This method is intended to persist any changes. After successful execution,
|
||||
* the {@code unsavedChanges} flag is reset to {@code false}.
|
||||
*
|
||||
* @throws IOException if an error occurs during the saving process.
|
||||
*/
|
||||
public void save() throws IOException
|
||||
{
|
||||
unsavedChanges = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnsupportedOperationException always, as this inventory is not used.
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Inventory getInventory()
|
||||
{
|
||||
throw new UnsupportedOperationException("This inventory is not used!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package de.eisi05.npc.api.objects;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.enums.SkinParts;
|
||||
import de.eisi05.npc.api.utils.ItemSerializer;
|
||||
import de.eisi05.npc.api.utils.TriFunction;
|
||||
import de.eisi05.npc.api.utils.Var;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.enums.ChatFormat;
|
||||
import de.eisi05.npc.api.wrapper.objects.*;
|
||||
import de.eisi05.npc.api.wrapper.packets.*;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Represents a configurable option for an NPC.
|
||||
* Each option has a path, a default value, serialization/deserialization logic,
|
||||
* and a function to generate a network packet for applying the option.
|
||||
*
|
||||
* @param <T> The type of the option's value in its usable form.
|
||||
* @param <S> The type of the option's value in its serialized form.
|
||||
*/
|
||||
public class NpcOption<T, S extends Serializable>
|
||||
{
|
||||
/**
|
||||
* NPC option to determine if the NPC should use the skin of the viewing player.
|
||||
* If true, the NPC's skin will be dynamically set to the skin of the player looking at it.
|
||||
*/
|
||||
public static final NpcOption<Boolean, Boolean> USE_PLAYER_SKIN = new NpcOption<>("use-player-skin", false,
|
||||
aBoolean -> aBoolean, aBoolean -> aBoolean,
|
||||
(skin, npc, player) ->
|
||||
{
|
||||
if(!skin)
|
||||
return null;
|
||||
|
||||
var textureProperties = WrappedServerPlayer.fromPlayer(player).getGameProfile().getProperties().get("textures").iterator();
|
||||
npc.getServerPlayer().getGameProfile().getProperties().removeAll("textures");
|
||||
|
||||
if(!textureProperties.hasNext())
|
||||
return null;
|
||||
|
||||
var textureProperty = textureProperties.next();
|
||||
npc.getServerPlayer().getGameProfile().getProperties().put("textures", textureProperty);
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to set a specific skin using a value and signature.
|
||||
* This is ignored if {@link #USE_PLAYER_SKIN} is true.
|
||||
*/
|
||||
public static final NpcOption<Skin, Skin> SKIN = new NpcOption<>("skin", null,
|
||||
skin -> skin, skin -> skin,
|
||||
(skin, npc, player) ->
|
||||
{
|
||||
if(npc.getOption(USE_PLAYER_SKIN))
|
||||
return null;
|
||||
|
||||
npc.getServerPlayer().getGameProfile().getProperties().removeAll("textures");
|
||||
|
||||
if(skin == null)
|
||||
return null;
|
||||
|
||||
npc.getServerPlayer().getGameProfile().getProperties()
|
||||
.put("textures", new Property("textures", skin.value(), skin.signature()));
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to control whether the NPC is shown in the player tab list.
|
||||
* If false, the NPC will be removed from the tab list for the viewing player after a short delay.
|
||||
*/
|
||||
public static final NpcOption<Boolean, Boolean> SHOW_TAB_LIST = new NpcOption<>("show-tab-list", true,
|
||||
aBoolean -> aBoolean, aBoolean -> aBoolean,
|
||||
(show, npc, player) ->
|
||||
{
|
||||
if(show)
|
||||
return null;
|
||||
|
||||
new BukkitRunnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_19_3))
|
||||
WrappedServerPlayer.fromPlayer(player).sendPacket(
|
||||
new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.REMOVE_PLAYER, npc.getServerPlayer()));
|
||||
else
|
||||
WrappedServerPlayer.fromPlayer(player)
|
||||
.sendPacket(new PlayerInfoRemovePacket(List.of(npc.getUUID())));
|
||||
}
|
||||
}.runTaskLater(NpcApi.plugin, 50);
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to set the simulated latency (ping) of the NPC in the tab list.
|
||||
*/
|
||||
public static final NpcOption<Integer, Integer> LATENCY = new NpcOption<>("latency", 0,
|
||||
aInteger -> aInteger, aInteger -> aInteger,
|
||||
(latency, npc, player) ->
|
||||
{
|
||||
npc.getServerPlayer().setLatency(latency);
|
||||
|
||||
return new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, npc.getServerPlayer());
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to control the visibility of the NPC's nametag.
|
||||
*/
|
||||
public static final NpcOption<Boolean, Boolean> HIDE_NAMETAG = new NpcOption<>("hide-nametag", false,
|
||||
aBoolean -> aBoolean, aBoolean -> aBoolean,
|
||||
(hide, npc, player) ->
|
||||
{
|
||||
WrappedEntityData data = npc.getServerPlayer().getNameTag().getEntityData();
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BOOLEAN.create(3), !hide);
|
||||
return SetEntityDataPacket.create(npc.getServerPlayer().getNameTag().getId(), data, false);
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to set the pose of the NPC (e.g., standing, sleeping, swimming).
|
||||
* For a full list look at {@link Pose}.
|
||||
*/
|
||||
public static final NpcOption<Pose, Pose> POSE = new NpcOption<>("pose", Pose.STANDING,
|
||||
pose -> pose, pose -> pose,
|
||||
(pose, npc, player) ->
|
||||
{
|
||||
de.eisi05.npc.api.wrapper.enums.Pose nmsPose = de.eisi05.npc.api.wrapper.enums.Pose.fromBukkit(pose);
|
||||
|
||||
if(nmsPose == null)
|
||||
throw new RuntimeException("Pose (" + pose.name() + ") not found");
|
||||
|
||||
npc.getServerPlayer().setPose(nmsPose);
|
||||
|
||||
WrappedEntityData data = npc.getServerPlayer().getEntityData();
|
||||
data.set(WrappedEntityData.EntityDataSerializers.ENTITY_POSE.create(6), nmsPose);
|
||||
|
||||
if(pose == Pose.SPIN_ATTACK)
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(8), (byte) 0x04);
|
||||
else
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(8), (byte) 0x01);
|
||||
|
||||
return SetEntityDataPacket.create(npc.getServerPlayer().getId(), data, false);
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to set the equipment worn by the NPC (armor, items in hand).
|
||||
* The map uses {@link EquipmentSlot} as keys and {@link ItemStack} as values.
|
||||
* Serialized form uses item base64 strings.
|
||||
*/
|
||||
public static final NpcOption<Map<EquipmentSlot, ItemStack>, HashMap<EquipmentSlot, String>> EQUIPMENT = new NpcOption<>("equipment", Map.of(),
|
||||
map -> map.entrySet().stream().collect(
|
||||
Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> ItemSerializer.itemStackToBase64(entry.getValue()), (a, b) -> b, HashMap::new)),
|
||||
serializedMap -> serializedMap.entrySet().stream().collect(
|
||||
Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> ItemSerializer.itemStackFromBase64(entry.getValue()), (a, b) -> b, HashMap::new)),
|
||||
(map, npc, player) ->
|
||||
{
|
||||
if(map.isEmpty())
|
||||
return null;
|
||||
|
||||
List<Pair<?, ?>> list = new ArrayList<>();
|
||||
|
||||
map.forEach((slot, item) -> list.add(
|
||||
new Pair<>(de.eisi05.npc.api.wrapper.enums.EquipmentSlot.values()[slot.ordinal()].getHandle(),
|
||||
Var.toNmsItemStack(item))));
|
||||
|
||||
return new SetEquipmentPacket(npc.getServerPlayer().getId(), list);
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to control which parts of the NPC's skin are visible (e.g., hat, jacket).
|
||||
* For a full list look at {@link SkinParts}.
|
||||
*/
|
||||
public static final NpcOption<SkinParts[], SkinParts[]> SKIN_PARTS = new NpcOption<>("skin-parts", SkinParts.values(),
|
||||
skinParts -> skinParts, skinParts -> skinParts,
|
||||
(skinParts, npc, player) ->
|
||||
{
|
||||
WrappedEntityData data = npc.getServerPlayer().getEntityData();
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(17),
|
||||
(byte) Arrays.stream(skinParts).mapToInt(SkinParts::getValue).sum());
|
||||
return SetEntityDataPacket.create(npc.getServerPlayer().getId(), data, true);
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to make the NPC look at the player if they are within a certain distance.
|
||||
* The value is the maximum distance in blocks. A value of 0 or less disables this.
|
||||
* The actual looking logic is handled by {@link Tasks#lookAtTask()}.
|
||||
*/
|
||||
public static final NpcOption<Double, Double> LOOK_AT_PLAYER = new NpcOption<>("look-at-player", 0.0,
|
||||
distance -> distance, distance -> distance,
|
||||
(distance, npc, player) -> null);
|
||||
|
||||
/**
|
||||
* NPC option to make the NPC glow with a specific color.
|
||||
* If null, the glowing effect is removed.
|
||||
*/
|
||||
public static final NpcOption<ChatColor, ChatColor> GLOWING = new NpcOption<>("glowing", null,
|
||||
color -> color, color -> color,
|
||||
(color, npc, player) ->
|
||||
{
|
||||
if(color == null)
|
||||
{
|
||||
WrappedEntityData entityData = npc.getServerPlayer().getEntityData();
|
||||
entityData.set(WrappedEntityData.EntityDataSerializers.BYTE.create(0), (byte) 0);
|
||||
return SetEntityDataPacket.create(npc.getServerPlayer().getId(), entityData, false);
|
||||
}
|
||||
|
||||
String teamName = npc.getServerPlayer().getName();
|
||||
boolean modified = WrappedPlayerTeam.exists(player, teamName);
|
||||
WrappedPlayerTeam wrappedPlayerTeam = WrappedPlayerTeam.create(player, teamName);
|
||||
|
||||
wrappedPlayerTeam.setColor(ChatFormat.fromChatColor(color));
|
||||
|
||||
var teamPacket = SetPlayerTeamPacket.createAddOrModifyPacket(wrappedPlayerTeam, !modified);
|
||||
|
||||
WrappedEntityData entityData = npc.getServerPlayer().getEntityData();
|
||||
entityData.set(WrappedEntityData.EntityDataSerializers.BYTE.create(0), (byte) 0x40);
|
||||
|
||||
return new BundlePacket(teamPacket, SetEntityDataPacket.create(npc.getServerPlayer().getId(), entityData, true));
|
||||
});
|
||||
|
||||
/**
|
||||
* NPC option to set the scale (size) of the NPC.
|
||||
* A value of 1.0 is normal size. Requires Minecraft 1.20.6 or newer.
|
||||
*/
|
||||
public static final NpcOption<Double, Double> SCALE = new NpcOption<>("scale", 1.0,
|
||||
scale -> scale, scale -> scale,
|
||||
(scale, npc, player) ->
|
||||
{
|
||||
WrappedAttributeInstance instance = npc.getServerPlayer().getAttribute(WrappedAttributeInstance.Attributes.SCALE_HOLDER);
|
||||
instance.setBaseValue(scale);
|
||||
|
||||
return new UpdateAttributesPacket(npc.getServerPlayer().getId(), instance);
|
||||
}).since(Versions.V1_20_6);
|
||||
|
||||
/**
|
||||
* NPC option to control if the NPC is enabled (visible and interactable).
|
||||
* If false, a "DISABLED" marker may be shown.
|
||||
* This is an internal option, typically not directly set by users but controlled by {@link NPC#setEnabled(boolean)}.
|
||||
*/
|
||||
static final NpcOption<Boolean, Boolean> ENABLED = new NpcOption<>("enabled", false,
|
||||
aBoolean -> aBoolean, aBoolean -> aBoolean,
|
||||
(enabled, npc, player) ->
|
||||
{
|
||||
if(enabled)
|
||||
return null;
|
||||
|
||||
WrappedArmorStand armorStand = WrappedArmorStand.create(npc.getLocation().getWorld());
|
||||
armorStand.moveTo(
|
||||
npc.getLocation().clone().add(0, (npc.getServerPlayer().getBoundingBox().getYSize() * npc.getOption(SCALE)) + 0.3, 0));
|
||||
|
||||
PacketWrapper addPacket = armorStand.getAddEntityPacket();
|
||||
|
||||
WrappedEntityData data = armorStand.getEntityData();
|
||||
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(0), (byte) 0x20);
|
||||
data.set(WrappedEntityData.EntityDataSerializers.OPTIONAL_CHAT_COMPONENT.create(2),
|
||||
Optional.of(WrappedComponent.create("DISABLED").setFormats(ChatFormat.RED).getHandle()));
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BOOLEAN.create(3), true);
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BOOLEAN.create(4), true);
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(15), (byte) 0x10);
|
||||
|
||||
npc.toDeleteEntities.add(armorStand.getId());
|
||||
|
||||
return new BundlePacket(addPacket, SetEntityDataPacket.create(armorStand.getId(), data, true));
|
||||
});
|
||||
|
||||
private final String path;
|
||||
private final T defaultValue;
|
||||
private final Function<T, S> serializer;
|
||||
private final Function<S, T> deserializer;
|
||||
private final TriFunction<T, NPC, Player, PacketWrapper> packet;
|
||||
private Versions since = Versions.V1_17;
|
||||
|
||||
/**
|
||||
* Private constructor to create a new NpcOption.
|
||||
*
|
||||
* @param path The configuration path string. Must not be null.
|
||||
* @param defaultValue The default value for the option. Can be null.
|
||||
* @param serializer The serialization function. Must not be null.
|
||||
* @param deserializer The deserialization function. Must not be null.
|
||||
* @param packet The packet generation function. Must not be null.
|
||||
*/
|
||||
private NpcOption(@NotNull String path, @Nullable T defaultValue, @NotNull Function<T, S> serializer, @NotNull Function<S, T> deserializer,
|
||||
@NotNull TriFunction<T, NPC, Player, PacketWrapper> packet)
|
||||
{
|
||||
this.path = path;
|
||||
this.defaultValue = defaultValue;
|
||||
this.serializer = serializer;
|
||||
this.deserializer = deserializer;
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all declared {@link NpcOption} constants within this class using reflection.
|
||||
*
|
||||
* @return An array of {@link NpcOption} instances. Will not be null.
|
||||
*/
|
||||
public static @NotNull NpcOption<?, ?>[] values()
|
||||
{
|
||||
List<Field> fields = Arrays.stream(NpcOption.class.getDeclaredFields()).filter(field -> field.getType().equals(NpcOption.class)).toList();
|
||||
|
||||
NpcOption<?, ?>[] values = new NpcOption[fields.size()];
|
||||
|
||||
for(int i = 0; i < fields.size(); i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
values[i] = (NpcOption<?, ?>) fields.get(i).get(null);
|
||||
} catch(IllegalAccessException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an {@link NpcOption} instance by its configuration path.
|
||||
*
|
||||
* @param path The configuration path string to search for. Must not be null.
|
||||
* @return An {@link Optional} containing the found {@link NpcOption}, or an empty Optional if no option matches the path.
|
||||
*/
|
||||
public static @NotNull Optional<NpcOption<?, ?>> getOption(@NotNull String path)
|
||||
{
|
||||
return Arrays.stream(values()).filter(npcOption -> npcOption.getPath().equals(path)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum Minecraft version required for this option.
|
||||
* Used for options that are only available in newer versions of the game.
|
||||
*
|
||||
* @param since The minimum {@link Versions} required. Must not be null.
|
||||
* @return This {@link NpcOption} instance for method chaining.
|
||||
*/
|
||||
public @NotNull NpcOption<T, S> since(@NotNull Versions since)
|
||||
{
|
||||
this.since = since;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this NPC option is compatible with the current server version.
|
||||
* An option is compatible if the current server version is greater than or equal to
|
||||
* the version specified by {@link #since()}.
|
||||
*
|
||||
* @return {@code true} if the option is compatible, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isCompatible()
|
||||
{
|
||||
return !Versions.isCurrentVersionSmallerThan(since);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum Minecraft version required for this option.
|
||||
*
|
||||
* @return The {@link Versions} instance.
|
||||
*/
|
||||
public Versions since()
|
||||
{
|
||||
return since;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value for this option.
|
||||
*
|
||||
* @return The default value, which can be null if defined as such.
|
||||
*/
|
||||
public @Nullable T getDefaultValue()
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration path string for this option.
|
||||
*
|
||||
* @return The path string. Will not be null.
|
||||
*/
|
||||
public @NotNull String getPath()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given value (of type T) into its serializable form (type S).
|
||||
*
|
||||
* @param var1 The value to serialize. Can be null.
|
||||
* @return The serialized value. Can be null if the input or serializer result is null.
|
||||
* @throws RuntimeException if a {@link ClassCastException} occurs during serialization,
|
||||
* indicating an incorrect type was passed.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public @Nullable S serialize(@Nullable Object var1)
|
||||
{
|
||||
try
|
||||
{
|
||||
return serializer.apply((T) var1);
|
||||
} catch(ClassCastException e)
|
||||
{
|
||||
throw new RuntimeException(path + " -> " + var1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the given value (of type S) back into its usable form (type T).
|
||||
*
|
||||
* @param var1 The serialized value to deserialize. Can be null.
|
||||
* @return The deserialized value. Can be null if the input or deserializer result is null.
|
||||
*/
|
||||
public @Nullable T deserialize(@Nullable S var1)
|
||||
{
|
||||
return deserializer.apply(var1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the network packet(s) needed to apply this option's value to an NPC for a specific player.
|
||||
* The method checks for version compatibility before generating the packet.
|
||||
*
|
||||
* @param object The value of the option to apply. Can be null.
|
||||
* @param npc The {@link NPC} to apply the option to. Must not be null.
|
||||
* @param player The {@link Player} who will receive the update. Must not be null.
|
||||
* @return An {@link Optional} containing the {@link PacketWrapper} if one is generated and the option is compatible,
|
||||
* otherwise an empty Optional.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public @NotNull Optional<PacketWrapper> getPacket(@Nullable Object object, @NotNull NPC npc, @NotNull Player player)
|
||||
{
|
||||
if(packet == null || !isCompatible())
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.ofNullable(packet.apply((T) object, npc, player));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package de.eisi05.npc.api.objects;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import de.eisi05.npc.api.utils.Reflections;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.utils.exceptions.VersionNotFound;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedServerPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Represents a player's skin, containing its name, value, and signature.
|
||||
* This record is immutable and implements {@link Serializable} for easy persistence.
|
||||
* The skin value and signature are typically obtained from Mojang's session servers
|
||||
* and are used to display the correct player texture.
|
||||
*
|
||||
* @param name The name associated with the skin (usually the player's username). Can be {@code null}.
|
||||
* @param value The base64 encoded string representing the skin data (texture URL, model, etc.).
|
||||
* @param signature The signature used to verify the authenticity of the skin data.
|
||||
*/
|
||||
public record Skin(@Nullable String name, @NotNull String value, @NotNull String signature) implements Serializable
|
||||
{
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* A static cache to store fetched skins, mapping UUIDs to Skin objects.
|
||||
* This helps reduce redundant API calls to Mojang's servers.
|
||||
*/
|
||||
private static final Map<UUID, Skin> skinCache = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Retrieves the skin data directly from a currently online Bukkit player.
|
||||
* This method uses reflection to access the player's game profile properties.
|
||||
*
|
||||
* @param player The Bukkit player from whom to retrieve the skin. Must not be {@code null}.
|
||||
* @return A {@link Skin} object representing the player's current skin, or {@code null} if no skin properties are found.
|
||||
* @throws VersionNotFound If the API does not support the current server version.
|
||||
*/
|
||||
public static @Nullable Skin fromPlayer(@NotNull Player player)
|
||||
{
|
||||
var properties = WrappedServerPlayer.fromPlayer(player).getGameProfile().getProperties().get("textures").iterator();
|
||||
|
||||
if(!properties.hasNext())
|
||||
return null;
|
||||
|
||||
var property = properties.next();
|
||||
|
||||
if(Versions.getVersion() == Versions.NONE)
|
||||
throw new VersionNotFound();
|
||||
|
||||
return Versions.isCurrentVersionSmallerThan(Versions.V1_20_2) ?
|
||||
new Skin(player.getName(), Reflections.<String>invokeMethod(property, "getValue").get(),
|
||||
Reflections.<String>invokeMethod(property, "getSignature").get()) :
|
||||
new Skin(player.getName(), Reflections.<String>invokeMethod(property, "value").get(),
|
||||
Reflections.<String>invokeMethod(property, "signature").get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a player's skin from Mojang's session server using their UUID.
|
||||
* This method first checks the local cache (`skinCache`) before making an HTTP request.
|
||||
* The fetched skin is added to the cache for future use.
|
||||
*
|
||||
* @param uuid The UUID of the player whose skin is to be fetched. Must not be {@code null}.
|
||||
* @return A {@link Skin} object if the skin is successfully fetched or found in cache, otherwise {@code null}.
|
||||
* @throws VersionNotFound If the API does not support the current server version (though this check might be redundant here).
|
||||
*/
|
||||
private static @Nullable Skin fetchSkin(@NotNull UUID uuid)
|
||||
{
|
||||
|
||||
if(skinCache.containsKey(uuid))
|
||||
return skinCache.get(uuid);
|
||||
|
||||
try
|
||||
{
|
||||
URL url = URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false").toURL();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setReadTimeout(10000);
|
||||
|
||||
try(InputStream is = connection.getInputStream(); Scanner scanner = new Scanner(is))
|
||||
{
|
||||
String response = scanner.useDelimiter("\\A").next();
|
||||
|
||||
JsonObject json = new JsonParser().parse(response).getAsJsonObject();
|
||||
String name = json.get("name").getAsString();
|
||||
|
||||
JsonArray properties = json.getAsJsonArray("properties");
|
||||
for(JsonElement prop : properties)
|
||||
{
|
||||
JsonObject obj = prop.getAsJsonObject();
|
||||
String value = obj.get("value").getAsString();
|
||||
String signature = obj.has("signature") ? obj.get("signature").getAsString() : null;
|
||||
if(Versions.getVersion() == Versions.NONE)
|
||||
throw new VersionNotFound();
|
||||
|
||||
Skin skin = new Skin(name, value, signature);
|
||||
skinCache.put(uuid, skin);
|
||||
return skin;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a player's skin by their username.
|
||||
* This method first calls Mojang's API to get the player's UUID from their name
|
||||
* and then uses the UUID to fetch the skin data.
|
||||
*
|
||||
* @param name The username of the player whose skin is to be fetched. Must not be {@code null}.
|
||||
* @return A {@link Skin} object if the skin is successfully fetched, otherwise {@code null}.
|
||||
*/
|
||||
private static Skin fetchSkin(@NotNull String name)
|
||||
{
|
||||
try
|
||||
{
|
||||
URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setReadTimeout(10000);
|
||||
|
||||
try(InputStream is = conn.getInputStream(); Scanner scanner = new Scanner(is))
|
||||
{
|
||||
String response = scanner.useDelimiter("\\A").next();
|
||||
JsonObject json = new JsonParser().parse(response).getAsJsonObject();
|
||||
String id = json.get("id").getAsString();
|
||||
return fetchSkin(UUID.fromString(id.replaceFirst(
|
||||
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
|
||||
"$1-$2-$3-$4-$5")));
|
||||
}
|
||||
} catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches a player's skin using their UUID.
|
||||
* This method wraps the synchronous {@link #fetchSkin(UUID)} call in a {@link CompletableFuture}
|
||||
* to prevent blocking the main thread.
|
||||
*
|
||||
* @param uuid The UUID of the player whose skin is to be fetched. Must not be {@code null}.
|
||||
* @return A {@link CompletableFuture} that will complete with the {@link Skin} object, or {@code null} if fetching fails.
|
||||
*/
|
||||
public static CompletableFuture<Skin> fetchSkinAsync(@NotNull UUID uuid)
|
||||
{
|
||||
return CompletableFuture.supplyAsync(() -> fetchSkin(uuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches a player's skin using their username.
|
||||
* This method wraps the synchronous {@link #fetchSkin(String)} call in a {@link CompletableFuture}
|
||||
* to prevent blocking the main thread.
|
||||
*
|
||||
* @param name The username of the player whose skin is to be fetched. Must not be {@code null}.
|
||||
* @return A {@link CompletableFuture} that will complete with the {@link Skin} object, or {@code null} if fetching fails.
|
||||
*/
|
||||
public static CompletableFuture<Skin> fetchSkinAsync(@NotNull String name)
|
||||
{
|
||||
return CompletableFuture.supplyAsync(() -> fetchSkin(name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.eisi05.npc.api.objects;
|
||||
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.manager.NpcManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
/**
|
||||
* The {@link Tasks} class manages and starts various recurring tasks
|
||||
* related to Non-Player Characters (NPCs) within the Bukkit environment.
|
||||
* These tasks often involve NPC behavior such as looking at nearby players.
|
||||
*/
|
||||
public class Tasks
|
||||
{
|
||||
/**
|
||||
* Starts all defined NPC-related tasks.
|
||||
* This method should be called when the plugin is enabled to ensure
|
||||
* that NPC behaviors are active.
|
||||
*/
|
||||
public static void start()
|
||||
{
|
||||
lookAtTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a recurring task that makes NPCs look at nearby players.
|
||||
* The task runs on a timer defined by {@code NpcApi.config.getLookAtTimer()}.
|
||||
* NPCs will only look at players within a specified range, which is
|
||||
* configured via {@link NpcOption#LOOK_AT_PLAYER}.
|
||||
*/
|
||||
private static void lookAtTask()
|
||||
{
|
||||
new BukkitRunnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
NpcManager.getList().forEach(npc ->
|
||||
{
|
||||
double range = npc.getOption(NpcOption.LOOK_AT_PLAYER);
|
||||
|
||||
if(range <= 0)
|
||||
return;
|
||||
|
||||
npc.getServerPlayer().getBukkitPlayer().getNearbyEntities(range, range, range)
|
||||
.stream().filter(entity -> entity instanceof Player)
|
||||
.forEach(entity -> npc.lookAtPlayer((Player) entity));
|
||||
});
|
||||
}
|
||||
}.runTaskTimer(NpcApi.plugin, 0, NpcApi.config.getLookAtTimer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* The {@link CallerUtils} class provides utility methods for retrieving information
|
||||
* about the calling class and method in the current stack trace.
|
||||
* It leverages {@link StackWalker} for efficient stack traversal and
|
||||
* caches {@link MethodHandles.Lookup} instances for performance.
|
||||
*/
|
||||
public class CallerUtils
|
||||
{
|
||||
private static final Map<Class<?>, MethodHandles.Lookup> lookupCache = new ConcurrentHashMap<>();
|
||||
private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
|
||||
|
||||
/**
|
||||
* Retrieves the {@link Class} of the immediate caller of the method that invokes this utility method.
|
||||
* It skips the current method and the direct caller of this utility method to find the actual caller.
|
||||
*
|
||||
* @return The {@link Class} object of the caller, or {@code null} if it cannot be determined.
|
||||
*/
|
||||
public static @Nullable Class<?> getCallerClass()
|
||||
{
|
||||
return WALKER.walk(frames -> frames.skip(2).findFirst().map(StackWalker.StackFrame::getDeclaringClass).orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the immediate caller method of the method that invokes this utility method.
|
||||
* It skips the current method and the direct caller of this utility method to find the actual caller's method name.
|
||||
*
|
||||
* @return The name of the caller method as a {@link String}, or {@code null} if it cannot be determined.
|
||||
*/
|
||||
public static @Nullable String getCallerMethodName()
|
||||
{
|
||||
return WALKER.walk(frames -> frames.skip(2).findFirst().map(StackWalker.StackFrame::getMethodName).orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a {@link MethodHandles.Lookup} instance for the given class.
|
||||
* This method uses a cache to return existing lookup instances for already processed classes,
|
||||
* or creates and caches a new one if it's the first time the class is requested.
|
||||
*
|
||||
* @param clazz The {@link Class} for which to get the {@link MethodHandles.Lookup} instance. Must not be {@code null}.
|
||||
* @return A {@link MethodHandles.Lookup} instance associated with the provided class.
|
||||
*/
|
||||
public static @NotNull MethodHandles.Lookup getLookup(@NotNull Class<?> clazz)
|
||||
{
|
||||
return lookupCache.computeIfAbsent(clazz, c -> MethodHandles.lookup());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* The {@link ItemSerializer} class provides utility methods for serializing and deserializing
|
||||
* Bukkit {@link ItemStack} arrays, {@link PlayerInventory} contents, and generic {@link Inventory}
|
||||
* contents to and from Base64 encoded strings. This is useful for storing inventory data
|
||||
* in configurations or databases.
|
||||
*/
|
||||
public class ItemSerializer
|
||||
{
|
||||
/**
|
||||
* Serializes the contents of a {@link PlayerInventory} into a Base64 encoded string array.
|
||||
* The array will contain two elements: the first for the main inventory storage contents,
|
||||
* and the second for the armor contents plus the off-hand item.
|
||||
*
|
||||
* @param playerInventory The {@link PlayerInventory} to serialize. Must not be {@code null}.
|
||||
* @return A {@code String[]} array containing two Base64 encoded strings:
|
||||
* index 0 is the main inventory, index 1 is armor and off-hand.
|
||||
* @throws IllegalStateException If an error occurs during serialization (e.g., I/O error).
|
||||
*/
|
||||
public static @NotNull String[] playerInventoryToBase64(@NotNull PlayerInventory playerInventory) throws IllegalStateException
|
||||
{
|
||||
String content = itemStackArrayToBase64(playerInventory.getStorageContents());
|
||||
ItemStack[] items = new ItemStack[playerInventory.getArmorContents().length + 1];
|
||||
|
||||
for(int i = 0; i < playerInventory.getArmorContents().length; ++i)
|
||||
items[i] = playerInventory.getArmorContents()[i];
|
||||
|
||||
items[items.length - 1] = playerInventory.getItemInOffHand();
|
||||
String armor = itemStackArrayToBase64(items);
|
||||
return new String[]{content, armor};
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a single {@link ItemStack} into a Base64 encoded string.
|
||||
* This is a convenience method that wraps the item in an array and calls {@link #itemStackArrayToBase64(ItemStack[])}.
|
||||
*
|
||||
* @param item The {@link ItemStack} to serialize. Must not be {@code null}.
|
||||
* @return A Base64 encoded {@link String} representing the item.
|
||||
* Returns {@code null} if an error occurs during serialization.
|
||||
*/
|
||||
public static @NotNull String itemStackToBase64(@NotNull ItemStack item)
|
||||
{
|
||||
return itemStackArrayToBase64(new ItemStack[]{item});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a single {@link ItemStack} from a Base64 encoded string.
|
||||
* This method expects the Base64 string to represent a single item.
|
||||
*
|
||||
* @param data The Base64 encoded {@link String} representing the item. Must not be {@code null}.
|
||||
* @return The deserialized {@link ItemStack}, or {@code null} if an error occurs during deserialization
|
||||
* or if the input data does not represent a valid item.
|
||||
*/
|
||||
public static @Nullable ItemStack itemStackFromBase64(@NotNull String data)
|
||||
{
|
||||
try
|
||||
{
|
||||
return itemStackArrayFromBase64(data)[0];
|
||||
} catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an array of {@link ItemStack} objects into a Base64 encoded string.
|
||||
* This method uses Bukkit's object serialization for {@link ItemStack}s.
|
||||
*
|
||||
* @param items An array of {@link ItemStack} objects to serialize. Must not be {@code null}.
|
||||
* @return A Base64 encoded {@link String} representing the array of items.
|
||||
* Returns {@code null} if an error occurs during serialization.
|
||||
*/
|
||||
public static @Nullable String itemStackArrayToBase64(@NotNull ItemStack[] items)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
dataOutput.writeInt(items.length);
|
||||
|
||||
for(ItemStack item : items)
|
||||
dataOutput.writeObject(item);
|
||||
|
||||
dataOutput.close();
|
||||
return Base64Coder.encodeLines(outputStream.toByteArray());
|
||||
} catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the contents of a generic {@link Inventory} into a Base64 encoded string.
|
||||
* This method is suitable for any type of inventory (e.g., chests, custom inventories).
|
||||
*
|
||||
* @param inventory The {@link Inventory} to serialize. Must not be {@code null}.
|
||||
* @return A Base64 encoded {@link String} representing the inventory contents.
|
||||
* Returns an empty string if an error occurs during serialization.
|
||||
*/
|
||||
public static @NotNull String toBase64(@NotNull Inventory inventory)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
dataOutput.writeInt(inventory.getSize());
|
||||
|
||||
for(ItemStack item : inventory.getContents())
|
||||
dataOutput.writeObject(item);
|
||||
|
||||
dataOutput.close();
|
||||
return Base64Coder.encodeLines(outputStream.toByteArray());
|
||||
} catch(Exception e)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes two Base64 encoded strings back into two {@link ItemStack} arrays.
|
||||
* This is typically used for deserializing player inventory content and armor/off-hand.
|
||||
*
|
||||
* @param s A {@code String[]} array containing two Base64 encoded strings,
|
||||
* where {@code s[0]} is the main inventory and {@code s[1]} is armor/off-hand. Must not be {@code null}.
|
||||
* @return A two-dimensional {@code ItemStack[][]} array, where the first array
|
||||
* contains the deserialized main inventory items and the second array
|
||||
* contains the deserialized armor and off-hand items.
|
||||
*/
|
||||
public static @NotNull ItemStack[][] doubleInventoryFromBase64(@NotNull String[] s)
|
||||
{
|
||||
return new ItemStack[][]{itemStackArrayFromBase64(s[0]), itemStackArrayFromBase64(s[1])};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a Base64 encoded string back into a Bukkit {@link Inventory} object.
|
||||
* The inventory's size and contents are restored from the provided data.
|
||||
*
|
||||
* @param data The Base64 encoded {@link String} representing the inventory. Must not be {@code null}.
|
||||
* @return The deserialized {@link Inventory} object, or {@code null} if an error occurs
|
||||
* during deserialization or if the input data is invalid.
|
||||
*/
|
||||
public static @Nullable Inventory fromBase64(@NotNull String data)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
|
||||
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
|
||||
Inventory inventory = Bukkit.getServer().createInventory(null, dataInput.readInt());
|
||||
|
||||
for(int i = 0; i < inventory.getSize(); ++i)
|
||||
inventory.setItem(i, (ItemStack) dataInput.readObject());
|
||||
|
||||
dataInput.close();
|
||||
return inventory;
|
||||
} catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a Base64 encoded string back into an array of {@link ItemStack} objects.
|
||||
*
|
||||
* @param data The Base64 encoded {@link String} representing the array of items. Must not be {@code null}.
|
||||
* @return An array of deserialized {@link ItemStack} objects.
|
||||
* Returns an empty {@code ItemStack[]} array if an error occurs during deserialization.
|
||||
*/
|
||||
public static @NotNull ItemStack[] itemStackArrayFromBase64(@NotNull String data)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
|
||||
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
|
||||
ItemStack[] items = new ItemStack[dataInput.readInt()];
|
||||
|
||||
for(int i = 0; i < items.length; ++i)
|
||||
items[i] = (ItemStack) dataInput.readObject();
|
||||
|
||||
dataInput.close();
|
||||
return items;
|
||||
} catch(Exception e)
|
||||
{
|
||||
return new ItemStack[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ObjectSaver} class provides utility methods for serializing and deserializing
|
||||
* Java objects and lists of objects to and from a file. It uses standard Java
|
||||
* object serialization.
|
||||
*/
|
||||
public class ObjectSaver
|
||||
{
|
||||
private final File file;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ObjectSaver} instance, creating a {@link File} object
|
||||
* from the given file path string. It ensures the parent directories exist
|
||||
* and creates the file if it doesn't already exist.
|
||||
*
|
||||
* @param file The path to the file where objects will be saved/loaded. Must not be {@code null}.
|
||||
* @throws RuntimeException If an {@link IOException} occurs while creating the file.
|
||||
*/
|
||||
public ObjectSaver(@NotNull String file)
|
||||
{
|
||||
this(new File(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ObjectSaver} instance with the specified {@link File} object.
|
||||
* It ensures the parent directories of the file exist and creates the file itself
|
||||
* if it does not already exist.
|
||||
*
|
||||
* @param file The {@link File} object where objects will be saved/loaded. Must not be {@code null}.
|
||||
* @throws RuntimeException If an {@link IOException} occurs while creating the file.
|
||||
*/
|
||||
public ObjectSaver(@NotNull File file)
|
||||
{
|
||||
file.getParentFile().mkdirs();
|
||||
this.file = file;
|
||||
if(!file.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
file.createNewFile();
|
||||
} catch(IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single serializable object to the file, appending to the file if it already exists.
|
||||
* This is a convenience method that calls {@link #write(Serializable, boolean)} with {@code append} set to {@code true}.
|
||||
*
|
||||
* @param object The object to write to the file. Must implement {@link Serializable}.
|
||||
* @param <T> The type of the object, which must extend {@link Serializable}.
|
||||
* @throws IOException If an I/O error occurs during writing.
|
||||
*/
|
||||
public <T extends Serializable> void write(T object) throws IOException
|
||||
{
|
||||
this.write(object, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single serializable object to the file.
|
||||
*
|
||||
* @param object The object to write to the file. Must implement {@link Serializable}.
|
||||
* @param append If {@code true}, the object will be appended to the end of the file.
|
||||
* If {@code false}, the file will be truncated (its contents deleted)
|
||||
* before writing the new object.
|
||||
* @param <T> The type of the object, which must extend {@link Serializable}.
|
||||
* @throws IOException If an I/O error occurs during writing.
|
||||
*/
|
||||
public <T extends Serializable> void write(T object, boolean append) throws IOException
|
||||
{
|
||||
FileOutputStream fileOut = new FileOutputStream(this.file, append);
|
||||
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
|
||||
objectOut.writeObject(object);
|
||||
objectOut.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a list of serializable objects to the file, appending to the file if it already exists.
|
||||
* This is a convenience method that calls {@link #writeList(List, boolean)} with {@code append} set to {@code true}.
|
||||
*
|
||||
* @param object The list of objects to write to the file. Each object in the list must implement {@link Serializable}. Must not be {@code null}.
|
||||
* @param <T> The type of the objects in the list, which must extend {@link Serializable}.
|
||||
* @throws IOException If an I/O error occurs during writing.
|
||||
*/
|
||||
public <T extends Serializable> void writeList(@NotNull List<T> object) throws IOException
|
||||
{
|
||||
this.writeList(object, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a list of serializable objects to the file.
|
||||
*
|
||||
* @param object The list of objects to write to the file. Each object in the list must implement {@link Serializable}. Must not be {@code null}.
|
||||
* @param append If {@code true}, the list will be appended to the end of the file.
|
||||
* If {@code false}, the file will be truncated (its contents deleted)
|
||||
* before writing the new list.
|
||||
* @param <T> The type of the objects in the list, which must extend {@link Serializable}.
|
||||
* @throws IOException If an I/O error occurs during writing.
|
||||
*/
|
||||
public <T extends Serializable> void writeList(@NotNull List<T> object, boolean append) throws IOException
|
||||
{
|
||||
FileOutputStream fileOut = new FileOutputStream(this.file, append);
|
||||
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
|
||||
objectOut.writeObject(object);
|
||||
objectOut.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single serializable object from the file.
|
||||
*
|
||||
* @param <T> The expected type of the object, which must extend {@link Serializable}.
|
||||
* @return The deserialized object, or {@code null} if an error occurs during reading
|
||||
* (e.g., file not found, EOF, class not found, or I/O error).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Serializable> @Nullable T read()
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInputStream fileIn = new FileInputStream(this.file);
|
||||
ObjectInputStream objectOut = new ObjectInputStream(fileIn);
|
||||
Object object = objectOut.readObject();
|
||||
objectOut.close();
|
||||
return (T) object;
|
||||
} catch(Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a list of serializable objects from the file.
|
||||
*
|
||||
* @param <T> The expected type of the objects in the list, which must extend {@link Serializable}.
|
||||
* @return The deserialized list of objects. Returns an empty {@link ArrayList} if an error occurs
|
||||
* during reading (e.g., file not found, EOF, class not found, or I/O error), or if the file is empty.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Serializable> @NotNull List<T> readList()
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInputStream fileIn = new FileInputStream(this.file);
|
||||
ObjectInputStream objectOut = new ObjectInputStream(fileIn);
|
||||
Object object = objectOut.readObject();
|
||||
objectOut.close();
|
||||
return (List<T>) object;
|
||||
} catch(Exception var4)
|
||||
{
|
||||
return new ArrayList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.enums.ClickActionType;
|
||||
import de.eisi05.npc.api.events.NpcInteractEvent;
|
||||
import de.eisi05.npc.api.manager.NpcManager;
|
||||
import de.eisi05.npc.api.objects.NPC;
|
||||
import de.eisi05.npc.api.wrapper.enums.InteractionHand;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedServerPlayer;
|
||||
import de.eisi05.npc.api.wrapper.packets.PacketWrapper;
|
||||
import de.eisi05.npc.api.wrapper.packets.UseEntityPacketWrapper;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* The {@link PacketReader} class is responsible for injecting a custom Netty
|
||||
* channel handler into a player's network pipeline to intercept incoming packets.
|
||||
* It specifically listens for packets related to entity interaction (e.g., attacking or interacting with NPCs)
|
||||
* and dispatches custom events based on these interactions. It also allows for
|
||||
* custom packet readers to be added.
|
||||
*/
|
||||
public class PacketReader
|
||||
{
|
||||
private static final Map<UUID, Channel> channels = new HashMap<>();
|
||||
private static final List<BiConsumer<Player, Object>> readers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Adds a custom packet reader to the list of readers.
|
||||
* This reader will be called for every incoming packet processed by the injected handler.
|
||||
*
|
||||
* @param reader The {@link BiConsumer} to add. It accepts the {@link Player}
|
||||
* and the raw packet {@link Object}. Must not be {@code null}.
|
||||
*/
|
||||
public static void addReader(@NotNull BiConsumer<Player, Object> reader)
|
||||
{
|
||||
readers.add(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a custom {@link ChannelDuplexHandler} into the specified player's Netty pipeline.
|
||||
* This handler intercepts incoming packets to check for NPC interactions.
|
||||
* The handler is named after the plugin's name to avoid conflicts and ensure proper removal.
|
||||
*
|
||||
* @param player The {@link Player} whose pipeline is to be injected. Must not be {@code null}.
|
||||
*/
|
||||
public static void inject(@NotNull Player player)
|
||||
{
|
||||
WrappedServerPlayer serverPlayer = WrappedServerPlayer.fromPlayer(player);
|
||||
|
||||
Channel channel = serverPlayer.playerConnection().networkManager().channel();
|
||||
channels.put(player.getUniqueId(), channel);
|
||||
|
||||
if(channel.pipeline().get(NpcApi.plugin.getName()) != null)
|
||||
return;
|
||||
|
||||
ChannelDuplexHandler duplexHandler = new ChannelDuplexHandler()
|
||||
{
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
|
||||
{
|
||||
checkForPacket(msg, player);
|
||||
|
||||
readers.forEach(consumer -> consumer.accept(player, msg));
|
||||
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
};
|
||||
|
||||
if(channel.pipeline().get("packet_handler") == null)
|
||||
channel.pipeline().addLast(NpcApi.plugin.getName(), duplexHandler);
|
||||
else
|
||||
channel.pipeline().addBefore("packet_handler", NpcApi.plugin.getName(), duplexHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given packet is a {@link UseEntityPacketWrapper} and, if so,
|
||||
* processes the interaction to dispatch a {@link NpcInteractEvent}.
|
||||
* This method is responsible for determining if a player has clicked or attacked an NPC.
|
||||
*
|
||||
* @param packet The raw packet object received from the Netty pipeline. Must not be {@code null}.
|
||||
* @param player The {@link Player} who sent the packet. Must not be {@code null}.
|
||||
*/
|
||||
private static void checkForPacket(@NotNull Object packet, @NotNull Player player)
|
||||
{
|
||||
if(!PacketWrapper.PacketHolder.is(packet, PacketWrapper.PacketHolder.class))
|
||||
return;
|
||||
|
||||
if(!PacketWrapper.PacketHolder.is(packet, UseEntityPacketWrapper.class))
|
||||
return;
|
||||
|
||||
UseEntityPacketWrapper useEntityPacketWrapper = PacketWrapper.PacketHolder.wrap(packet, UseEntityPacketWrapper.class);
|
||||
|
||||
int id = useEntityPacketWrapper.getId();
|
||||
|
||||
NPC npc = NpcManager.getList().stream().filter(npc1 -> npc1.getServerPlayer().getId() == id).findFirst().orElse(null);
|
||||
|
||||
if(npc == null)
|
||||
return;
|
||||
|
||||
UseEntityPacketWrapper.Action action = useEntityPacketWrapper.getAction();
|
||||
|
||||
if(action.getActionType() == UseEntityPacketWrapper.ActionType.ATTACK)
|
||||
{
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(NpcApi.plugin,
|
||||
() -> Bukkit.getPluginManager().callEvent(new NpcInteractEvent(player, npc, ClickActionType.LEFT)), 0);
|
||||
}
|
||||
else if(action.getActionType() == UseEntityPacketWrapper.ActionType.INTERACT && action.getHand() == InteractionHand.MAIN_HAND)
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(NpcApi.plugin,
|
||||
() -> Bukkit.getPluginManager().callEvent(new NpcInteractEvent(player, npc, ClickActionType.RIGHT)), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninjects the custom {@link ChannelDuplexHandler} from the specified player's Netty pipeline.
|
||||
* This stops the interception of packets for that player.
|
||||
*
|
||||
* @param player The {@link Player} whose pipeline is to be uninject. Must not be {@code null}.
|
||||
*/
|
||||
public static void uninject(@NotNull Player player)
|
||||
{
|
||||
Channel channel = channels.get(player.getUniqueId());
|
||||
|
||||
if(channel == null)
|
||||
return;
|
||||
|
||||
if(channel.pipeline().get(NpcApi.plugin.getName()) != null)
|
||||
channel.pipeline().remove(NpcApi.plugin.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninjects the custom packet handler from all currently online players.
|
||||
* This is typically called during plugin shutdown or reload.
|
||||
*/
|
||||
public static void uninjectAll()
|
||||
{
|
||||
for(Player player : Bukkit.getOnlinePlayers())
|
||||
uninject(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the custom packet handler into all currently online players.
|
||||
* This is typically called during plugin startup or after a reload.
|
||||
*/
|
||||
public static void injectAll()
|
||||
{
|
||||
for(Player player : Bukkit.getOnlinePlayers())
|
||||
inject(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import com.google.common.primitives.Primitives;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Utility class providing methods for reflective operations such as loading classes,
|
||||
* invoking methods, and accessing fields dynamically at runtime.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Reflections
|
||||
{
|
||||
/**
|
||||
* Loads a class by its fully qualified name.
|
||||
*
|
||||
* @param path fully qualified class name
|
||||
* @param <T> type of the class
|
||||
* @return Optional containing the Class if found, empty otherwise
|
||||
*/
|
||||
public static <T> @NotNull Optional<Class<T>> getClass(@NotNull String path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Optional.of((Class<T>) Class.forName(path));
|
||||
} catch(ClassNotFoundException e)
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates an object of the given class using the constructor that matches the argument types.
|
||||
*
|
||||
* @param path fully qualified class name
|
||||
* @param args constructor arguments
|
||||
* @param <T> type of the instance
|
||||
* @return Optional containing the instance if created successfully, empty otherwise
|
||||
*/
|
||||
public static <T> @NotNull Optional<T> getInstance(@NotNull String path, @Nullable Object... args)
|
||||
{
|
||||
return getClass(path).flatMap(objectClass -> getInstance((Class<T>) objectClass, args));
|
||||
}
|
||||
|
||||
public static <T> @NotNull Optional<T> getInstance(@NotNull Class<T> clazz, @Nullable Object... args)
|
||||
{
|
||||
final Object[] newArgs = Wrapper.checkArgs(args);
|
||||
|
||||
try
|
||||
{
|
||||
Class<?>[] argTypes = Arrays.stream(newArgs)
|
||||
.map(Object::getClass)
|
||||
.toArray(Class[]::new);
|
||||
Constructor<?> ctor = clazz.getDeclaredConstructor(argTypes);
|
||||
ctor.setAccessible(true);
|
||||
return Optional.of((T) ctor.newInstance(newArgs));
|
||||
} catch(Exception e)
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a declared method in the specified class that matches the given name and parameter types.
|
||||
*
|
||||
* @param clazz the class to search in
|
||||
* @param name the name of the method
|
||||
* @param args the arguments whose types will be used to find the method
|
||||
* @return the matching Method object
|
||||
* @throws NoSuchMethodException if no matching method is found
|
||||
*/
|
||||
private static @NotNull Method findMethod(@NotNull Class<?> clazz, @NotNull String name, @Nullable Object[] args) throws NoSuchMethodException
|
||||
{
|
||||
Class<?> current = clazz;
|
||||
Class<?>[] argTypes = Arrays.stream(args)
|
||||
.map(Object::getClass)
|
||||
.toArray(Class<?>[]::new);
|
||||
|
||||
while(current != null)
|
||||
{
|
||||
for(Method method : current.getDeclaredMethods())
|
||||
{
|
||||
if(!method.getName().equals(name))
|
||||
continue;
|
||||
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
boolean isVarArgs = method.isVarArgs();
|
||||
|
||||
if(isCompatible(argTypes, paramTypes, isVarArgs))
|
||||
{
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
|
||||
throw new NoSuchMethodException("No compatible method " + name + " found in class " + clazz.getName() + "(" + Arrays.toString(args) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given set of argument types is compatible with the parameter types of a method.
|
||||
* <p>
|
||||
* This method supports both regular and varargs methods. It also handles primitive-to-wrapper
|
||||
* type conversions (e.g., int to Integer).
|
||||
*
|
||||
* @param args The types of the provided arguments.
|
||||
* @param params The types of the method's parameters.
|
||||
* @param isVarArgs Whether the method accepts a variable number of arguments (varargs).
|
||||
* @return {@code true} if the argument types are compatible with the parameter types; {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isCompatible(@NotNull Class<?>[] args, @NotNull Class<?>[] params, boolean isVarArgs)
|
||||
{
|
||||
if(!isVarArgs)
|
||||
{
|
||||
if(args.length != params.length)
|
||||
return false;
|
||||
for(int i = 0; i < args.length; i++)
|
||||
{
|
||||
if(!Primitives.wrap(params[i]).isAssignableFrom(Primitives.wrap(args[i])))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(args.length < params.length - 1)
|
||||
return false;
|
||||
for(int i = 0; i < params.length - 1; i++)
|
||||
{
|
||||
if(!Primitives.wrap(params[i]).isAssignableFrom(Primitives.wrap(args[i])))
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> varArgType = Primitives.wrap(params[params.length - 1].getComponentType());
|
||||
for(int i = params.length - 1; i < args.length; i++)
|
||||
{
|
||||
if(!varArgType.isAssignableFrom(Primitives.wrap(args[i])))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes an instance method on the given object with specified arguments.
|
||||
*
|
||||
* @param object the target object
|
||||
* @param methodName name of the method to invoke
|
||||
* @param args method arguments
|
||||
* @param <V> return type of the method
|
||||
* @return a ReflectionChain wrapping the method's return value
|
||||
*/
|
||||
public static <V> @NotNull ReflectionChain<V> invokeMethod(@NotNull Object object, @NotNull String methodName, @Nullable Object... args)
|
||||
{
|
||||
args = Wrapper.checkArgs(args);
|
||||
try
|
||||
{
|
||||
Method method = findMethod(object.getClass(), methodName, args);
|
||||
method.setAccessible(true);
|
||||
return new ReflectionChain<>((V) method.invoke(object, args));
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a static method of a class given its fully qualified name.
|
||||
*
|
||||
* @param classPath fully qualified class name
|
||||
* @param methodName name of the static method
|
||||
* @param args method arguments
|
||||
* @param <V> return type of the method
|
||||
* @return a ReflectionChain wrapping the method's return value
|
||||
*/
|
||||
public static <V> @NotNull ReflectionChain<V> invokeStaticMethod(@NotNull String classPath, @NotNull String methodName, @Nullable Object... args)
|
||||
{
|
||||
args = Wrapper.checkArgs(args);
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(classPath);
|
||||
Method method = findMethod(clazz, methodName, args);
|
||||
method.setAccessible(true);
|
||||
return new ReflectionChain<>((V) method.invoke(null, args));
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a declared field in the specified class by its name and makes it accessible.
|
||||
*
|
||||
* @param clazz the class to search in
|
||||
* @param fieldName the name of the field
|
||||
* @return the Field object with accessibility set to true
|
||||
* @throws NoSuchFieldException if the field is not found
|
||||
*/
|
||||
private static @NotNull Field findField(@NotNull Class<?> clazz, @NotNull String fieldName) throws NoSuchFieldException
|
||||
{
|
||||
while(clazz != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch(NoSuchFieldException ignored)
|
||||
{
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a field from an object.
|
||||
*
|
||||
* @param object the target object
|
||||
* @param fieldName name of the field
|
||||
* @param <T> type of the field value
|
||||
* @return a ReflectionChain wrapping the field's value
|
||||
*/
|
||||
public static <T> @NotNull ReflectionChain<T> getField(@NotNull Object object, @NotNull String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = findField(object.getClass(), fieldName);
|
||||
return new ReflectionChain<>((T) field.get(object));
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a static field from a class.
|
||||
*
|
||||
* @param clazz the target class
|
||||
* @param fieldName name of the static field
|
||||
* @param <T> class type
|
||||
* @param <V> type of the field value
|
||||
* @return the value of the static field, or null if inaccessible
|
||||
*/
|
||||
public static <T, V> @Nullable V getStaticField(@NotNull Class<T> clazz, @Nullable String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = findField(clazz, fieldName);
|
||||
return (V) field.get(null);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a static field by class name.
|
||||
*
|
||||
* @param classPath fully qualified class name
|
||||
* @param fieldName name of the static field
|
||||
* @param <T> type of the field value
|
||||
* @return the value of the static field, or null if inaccessible
|
||||
*/
|
||||
public static <T> @Nullable T getStaticField(@NotNull String classPath, @NotNull String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(classPath);
|
||||
Field field = findField(clazz, fieldName);
|
||||
return (T) field.get(null);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a field on an object.
|
||||
*
|
||||
* @param object target object
|
||||
* @param fieldName name of the field
|
||||
* @param value new value to set
|
||||
*/
|
||||
public static void setField(@NotNull Object object, @NotNull String fieldName, @Nullable Object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = findField(object.getClass(), fieldName);
|
||||
field.set(object, value);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a static field in a class by class name.
|
||||
*
|
||||
* @param classPath fully qualified class name
|
||||
* @param fieldName name of the static field
|
||||
* @param value new value to set
|
||||
*/
|
||||
public static void setStaticField(@NotNull String classPath, @NotNull String fieldName, @Nullable Object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(classPath);
|
||||
Field field = findField(clazz, fieldName);
|
||||
field.set(null, value);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to chain reflection calls on the result of previous reflective operations.
|
||||
*
|
||||
* @param <V> the wrapped value type
|
||||
*/
|
||||
public static class ReflectionChain<V>
|
||||
{
|
||||
private final @Nullable V value;
|
||||
|
||||
/**
|
||||
* Creates a new ReflectionChain wrapping the given value.
|
||||
*
|
||||
* @param value the wrapped value may be null
|
||||
*/
|
||||
public ReflectionChain(@Nullable V value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a method on the wrapped object.
|
||||
*
|
||||
* @param methodName name of the method
|
||||
* @param args method arguments
|
||||
* @return new ReflectionChain wrapping the method's result, or null if error
|
||||
*/
|
||||
public @NotNull ReflectionChain<V> thanInvoke(@NotNull String methodName, @Nullable Object... args)
|
||||
{
|
||||
if(value == null)
|
||||
return new ReflectionChain<>(null);
|
||||
|
||||
args = Wrapper.checkArgs(args);
|
||||
try
|
||||
{
|
||||
Method method = findMethod(value.getClass(), methodName, args);
|
||||
method.setAccessible(true);
|
||||
return new ReflectionChain<>((V) method.invoke(value, args));
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field value from the wrapped object.
|
||||
*
|
||||
* @param fieldName name of the field
|
||||
* @return new ReflectionChain wrapping the field's value, or null if error
|
||||
*/
|
||||
public @NotNull ReflectionChain<V> thanGetField(@NotNull String fieldName)
|
||||
{
|
||||
if(value == null)
|
||||
return new ReflectionChain<>(null);
|
||||
try
|
||||
{
|
||||
Field field = findField(value.getClass(), fieldName);
|
||||
return new ReflectionChain<>((V) field.get(value));
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped value.
|
||||
*
|
||||
* @return wrapped value may be null
|
||||
*/
|
||||
public @Nullable V get()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
/**
|
||||
* Represents a function that accepts three arguments and produces a result.
|
||||
* This is a functional interface whose functional method is {@link #apply(Object, Object, Object)}.
|
||||
*
|
||||
* @param <T> the type of the first argument to the function
|
||||
* @param <U> the type of the second argument to the function
|
||||
* @param <V> the type of the third argument to the function
|
||||
* @param <R> the type of the result of the function
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TriFunction<T, U, V, R>
|
||||
{
|
||||
/**
|
||||
* Applies this function to the given arguments.
|
||||
*
|
||||
* @param t the first function argument
|
||||
* @param u the second function argument
|
||||
* @param v the third function argument
|
||||
* @return the function result
|
||||
*/
|
||||
R apply(T t, U u, V v);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedComponent;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Var} class provides utility methods for converting between Bukkit API objects
|
||||
* and their corresponding NMS (Net Minecraft Server) counterparts, as well as handling
|
||||
* component conversions and unsafe casting. These methods often rely on reflection
|
||||
* due to version-specific NMS paths.
|
||||
*/
|
||||
public class Var
|
||||
{
|
||||
/**
|
||||
* Converts a Bukkit {@link ItemStack} to its NMS (Net Minecraft Server) equivalent.
|
||||
* This method uses reflection to call the appropriate {@code asNMSCopy} method
|
||||
* from the CraftBukkit library, which varies by server version.
|
||||
*
|
||||
* @param itemStack The Bukkit {@link ItemStack} to convert. Must not be {@code null}.
|
||||
* @return The NMS {@link Object} representing the {@code ItemStack}.
|
||||
* @throws java.util.NoSuchElementException If the reflection method is not found or cannot be invoked.
|
||||
*/
|
||||
public static @NotNull Object toNmsItemStack(@NotNull ItemStack itemStack)
|
||||
{
|
||||
return Reflections.invokeStaticMethod("org.bukkit.craftbukkit." + Versions.getVersion().getPath() + ".inventory.CraftItemStack",
|
||||
"asNMSCopy", itemStack).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the NMS (Net Minecraft Server) equivalent of a Bukkit {@link World} object.
|
||||
* This typically involves calling the {@code getHandle()} method on the Bukkit World object
|
||||
* via reflection to access the underlying NMS world instance.
|
||||
*
|
||||
* @param world The Bukkit {@link World} to convert. Must not be {@code null}.
|
||||
* @return The NMS {@link Object} representing the {@code World} (e.g., {@code net.minecraft.server.level.WorldServer}).
|
||||
* @throws java.util.NoSuchElementException If the reflection method is not found or cannot be invoked.
|
||||
*/
|
||||
public static @NotNull Object getNmsLevel(@NotNull World world)
|
||||
{
|
||||
return Reflections.invokeMethod(world, "getHandle").get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a plain text {@link String} into a {@link WrappedComponent} (NMS chat component equivalent).
|
||||
* This method uses reflection to call the appropriate {@code fromStringOrNull} method
|
||||
* from CraftBukkit's CraftChatMessage utility, which handles parsing text into components.
|
||||
*
|
||||
* @param text The plain text {@link String} to convert. Must not be {@code null}.
|
||||
* @return A {@link WrappedComponent} representing the given text.
|
||||
* @throws java.util.NoSuchElementException If the reflection method is not found or cannot be invoked.
|
||||
*/
|
||||
public static @NotNull WrappedComponent fromString(@NotNull String text)
|
||||
{
|
||||
return WrappedComponent.fromHandle(
|
||||
Reflections.invokeStaticMethod("org.bukkit.craftbukkit." + Versions.getVersion().getPath() + ".util.CraftChatMessage",
|
||||
"fromStringOrNull", text).get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link WrappedComponent} (NMS chat component equivalent) back into a plain text {@link String}.
|
||||
* This method uses reflection to call the appropriate {@code fromComponent} method
|
||||
* from CraftBukkit's CraftChatMessage utility.
|
||||
*
|
||||
* @param component The {@link WrappedComponent} to convert to a string. Must not be {@code null}.
|
||||
* @return A plain text {@link String} representation of the component.
|
||||
* @throws java.util.NoSuchElementException If the reflection method is not found or cannot be invoked.
|
||||
*/
|
||||
public static @NotNull String toString(@NotNull WrappedComponent component)
|
||||
{
|
||||
return (String) Reflections.invokeStaticMethod("org.bukkit.craftbukkit." + Versions.getVersion().getPath() + ".util.CraftChatMessage",
|
||||
"fromComponent", component.getHandle()).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an unchecked cast of an object to a specified type.
|
||||
* This method can be used to bypass Java's type checking at compile time,
|
||||
* but it comes with the risk of {@link ClassCastException} at runtime if the
|
||||
* object is not an instance of the target type.
|
||||
*
|
||||
* @param o The object to cast. Can be {@code null}.
|
||||
* @param <T> The target type to which the object will be cast.
|
||||
* @return The object cast to the specified type, or {@code null} if the input object was {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> @Nullable T unsafeCast(@Nullable Object o)
|
||||
{
|
||||
return (T) o;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package de.eisi05.npc.api.utils;
|
||||
|
||||
import de.eisi05.npc.api.NpcApi;
|
||||
import de.eisi05.npc.api.utils.exceptions.VersionNotFound;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The {@link Versions} enum represents the supported Minecraft server versions
|
||||
* and provides utility methods for working with version-specific paths and comparisons.
|
||||
* It helps in adapting the plugin's functionality to different server environments.
|
||||
*/
|
||||
public enum Versions
|
||||
{
|
||||
/**
|
||||
* Represents an unknown or unsupported version.
|
||||
*/
|
||||
NONE(""),
|
||||
/**
|
||||
* Minecraft 1.17 version.
|
||||
*/
|
||||
V1_17("v1_17_R1"),
|
||||
/**
|
||||
* Minecraft 1.18 version.
|
||||
*/
|
||||
V1_18("v1_18_R1"),
|
||||
/**
|
||||
* Minecraft 1.18.2 version.
|
||||
*/
|
||||
V1_18_2("v1_18_R2"),
|
||||
/**
|
||||
* Minecraft 1.19 version.
|
||||
*/
|
||||
V1_19("v1_19_R1"),
|
||||
/**
|
||||
* Minecraft 1.19.1 version.
|
||||
*/
|
||||
V1_19_1("v1_19_R1"),
|
||||
/**
|
||||
* Minecraft 1.19.3 version.
|
||||
*/
|
||||
V1_19_3("v1_19_R2"),
|
||||
/**
|
||||
* Minecraft 1.19.4 version.
|
||||
*/
|
||||
V1_19_4("v1_19_R3"),
|
||||
/**
|
||||
* Minecraft 1.20 version.
|
||||
*/
|
||||
V1_20("v1_20_R1"),
|
||||
/**
|
||||
* Minecraft 1.20.2 version.
|
||||
*/
|
||||
V1_20_2("v1_20_R2"),
|
||||
/**
|
||||
* Minecraft 1.20.4 version.
|
||||
*/
|
||||
V1_20_4("v1_20_R3"),
|
||||
/**
|
||||
* Minecraft 1.20.6 version.
|
||||
*/
|
||||
V1_20_6("v1_20_R4"),
|
||||
/**
|
||||
* Minecraft 1.21 version.
|
||||
*/
|
||||
V1_21("v1_21_R1"),
|
||||
/**
|
||||
* Minecraft 1.21.2 version.
|
||||
*/
|
||||
V1_21_2("v1_21_R2"),
|
||||
/**
|
||||
* Minecraft 1.21.4 version.
|
||||
*/
|
||||
V1_21_4("v1_21_R3"),
|
||||
/**
|
||||
* Minecraft 1.21.5 version.
|
||||
*/
|
||||
V1_21_5("v1_21_R4");
|
||||
|
||||
/**
|
||||
* Caches the determined current server version to avoid repeated lookups.
|
||||
*/
|
||||
private static Versions VERSION;
|
||||
|
||||
/**
|
||||
* The NMS (Net Minecraft Server) path component corresponding to this version.
|
||||
* For example, "v1_17_R1" for Minecraft 1.17.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Constructs a {@code Versions} enum entry with the specified NMS path.
|
||||
*
|
||||
* @param path The NMS path string for this version. Must not be {@code null}.
|
||||
*/
|
||||
Versions(@NotNull String path)
|
||||
{
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines and returns the current Minecraft server version based on the Bukkit server's package name.
|
||||
* The determined version is cached for later calls. If the version is not recognized,
|
||||
* the plugin will be disabled and a {@link VersionNotFound} exception will be thrown.
|
||||
*
|
||||
* @return The {@link Versions} enum entry corresponding to the current server version. Must not be {@code null}.
|
||||
* @throws VersionNotFound If the current server version cannot be determined or is not supported.
|
||||
*/
|
||||
public static @NotNull Versions getVersion()
|
||||
{
|
||||
if(VERSION != null)
|
||||
return VERSION;
|
||||
|
||||
String nmsVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
return VERSION = Arrays.stream(Versions.values())
|
||||
.filter(v -> v.path.equalsIgnoreCase(nmsVersion))
|
||||
.reduce((a, b) -> Bukkit.getBukkitVersion().startsWith("1.19.1") ? Versions.V1_19_1 : Versions.V1_19)
|
||||
.orElseThrow(() ->
|
||||
{
|
||||
Bukkit.getPluginManager().disablePlugin(NpcApi.plugin);
|
||||
return new VersionNotFound(nmsVersion);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of {@link Versions} enum entries that fall inclusively between
|
||||
* two specified versions (based on their ordinal values).
|
||||
* The {@code NONE} version is excluded from the result.
|
||||
*
|
||||
* @param versions1 The starting version (inclusive). Must not be {@code null}.
|
||||
* @param versions2 The ending version (inclusive). Must not be {@code null}.
|
||||
* @return An array of {@link Versions} enum entries within the specified range. Must not be {@code null}.
|
||||
*/
|
||||
private static @NotNull Versions[] getVersionBetween(@NotNull Versions versions1, @NotNull Versions versions2)
|
||||
{
|
||||
return Arrays.stream(values())
|
||||
.filter(v -> v != Versions.NONE)
|
||||
.filter(v -> v.ordinal() >= versions1.ordinal() && v.ordinal() <= versions2.ordinal())
|
||||
.toArray(Versions[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current server version is numerically smaller than a specified version.
|
||||
* This comparison is based on the ordinal value of the enum entries.
|
||||
*
|
||||
* @param versions The version to compare against. Must not be {@code null}.
|
||||
* @return {@code true} if the current version is smaller, {@code false} otherwise.
|
||||
*/
|
||||
public static boolean isCurrentVersionSmallerThan(@NotNull Versions versions)
|
||||
{
|
||||
return getVersion().ordinal() < versions.ordinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current server version is contained within a given {@link Mapping}.
|
||||
* A mapping can specify either a fixed version or a range of versions.
|
||||
*
|
||||
* @param mapping The {@link Mapping} to check against. Must not be {@code null}.
|
||||
* @return {@code true} if the current version matches the mapping, {@code false} otherwise.
|
||||
*/
|
||||
public static boolean containsCurrentVersion(@NotNull Mapping mapping)
|
||||
{
|
||||
if(mapping.fixed().value() != NONE)
|
||||
return mapping.fixed().value() == getVersion();
|
||||
|
||||
return Arrays.stream(getVersionBetween(mapping.range().from(), mapping.range().to())).anyMatch(v -> v == getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the NMS (Net Minecraft Server) path component associated with this version.
|
||||
*
|
||||
* @return The NMS path as a {@link String}. Must not be {@code null}.
|
||||
*/
|
||||
public @NotNull String getPath()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the version, with underscores replaced by dots for better readability.
|
||||
* For example, {@code V1_17} becomes "V1.17".
|
||||
*
|
||||
* @return The formatted name of the version as a {@link String}. Must not be {@code null}.
|
||||
*/
|
||||
public @NotNull String getName()
|
||||
{
|
||||
return name().replace("_", ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.eisi05.npc.api.utils.exceptions;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* An exception thrown when a specific version of the server is not found or supported
|
||||
* for a particular method or class, indicating incompatibility with the current
|
||||
* Bukkit/Minecraft version. This is a {@link RuntimeException}, meaning it is
|
||||
* an unchecked exception and does not need to be explicitly caught.
|
||||
*/
|
||||
public class VersionNotFound extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Constructs a new {@code VersionNotFound} exception with a detail message
|
||||
* indicating that the current server version is not found for a specific method.
|
||||
*
|
||||
* @param method The {@link Method} for which the version was not found. Must not be {@code null}.
|
||||
*/
|
||||
public VersionNotFound(@NotNull Method method)
|
||||
{
|
||||
super("Version " + Versions.getVersion().name() + " not found for " + method.getName() + " in " + method.getDeclaringClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code VersionNotFound} exception with a detail message
|
||||
* indicating that the current server version is not found for a specific class.
|
||||
*
|
||||
* @param clazz The {@link Class} for which the version was not found. Must not be {@code null}.
|
||||
*/
|
||||
public VersionNotFound(@NotNull Class<?> clazz)
|
||||
{
|
||||
super("Version " + Versions.getVersion().name() + " not found for " + clazz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code VersionNotFound} exception with no detail message.
|
||||
* This constructor can be used when the specific context of the version not found
|
||||
* is not immediately available or required.
|
||||
*/
|
||||
public VersionNotFound()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code VersionNotFound} exception with the specified detail message.
|
||||
*
|
||||
* @param message The detail message (which is saved for later retrieval by the {@link Throwable#getMessage()} method). Must not be {@code null}.
|
||||
*/
|
||||
public VersionNotFound(@NotNull String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.eisi05.npc.api.wrapper;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The {@link HandleHolder} interface defines a contract for classes that wrap
|
||||
* an underlying "handle" object, typically a Net Minecraft Server (NMS) object.
|
||||
* This interface allows for a standardized way to access the NMS instance
|
||||
* that a wrapper object represents.
|
||||
*/
|
||||
public interface HandleHolder
|
||||
{
|
||||
/**
|
||||
* Retrieves the underlying "handle" object.
|
||||
* This method is used to access the NMS (Net Minecraft Server) instance
|
||||
* that this object wraps.
|
||||
*
|
||||
* @return The underlying handle object. Must not be {@code null}.
|
||||
*/
|
||||
@NotNull Object getHandle();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package de.eisi05.npc.api.wrapper;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* The {@link Mapping} annotation is used to define version-specific paths or behaviors
|
||||
* for methods, fields, constructors, or types. It allows specifying either a fixed
|
||||
* version or a range of versions for which a particular mapping is valid.
|
||||
* This annotation is {@link Repeatable}, meaning multiple {@code @Mapping} annotations
|
||||
* can be applied to the same element.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
|
||||
@Repeatable(Mapping.WrapList.class)
|
||||
public @interface Mapping
|
||||
{
|
||||
/**
|
||||
* Specifies a fixed {@link Versions} enum entry for which this mapping is valid.
|
||||
* If set to {@link Versions#NONE}, the mapping is considered to be defined by a range instead.
|
||||
*
|
||||
* @return The fixed version for this mapping. Defaults to {@link Versions#NONE}.
|
||||
*/
|
||||
@NotNull Fixed fixed() default @Fixed(Versions.NONE);
|
||||
|
||||
/**
|
||||
* Specifies a range of {@link Versions} enum entries for which this mapping is valid.
|
||||
* This is used when the mapping applies to a continuous set of versions.
|
||||
* If both {@code fixed()} and {@code range()} are specified, {@code fixed()} takes precedence
|
||||
* if its value is not {@link Versions#NONE}.
|
||||
*
|
||||
* @return The version range for this mapping. Defaults to a range from {@link Versions#NONE} to {@link Versions#NONE}.
|
||||
*/
|
||||
@NotNull Range range() default @Range(from = Versions.NONE, to = Versions.NONE);
|
||||
|
||||
/**
|
||||
* The version-specific path or string associated with this mapping.
|
||||
* This could be a class name, a method name, a field name, or any other string
|
||||
* that changes across different Minecraft versions.
|
||||
*
|
||||
* @return The path string for this mapping. Must not be {@code null}.
|
||||
*/
|
||||
@NotNull String path();
|
||||
|
||||
/**
|
||||
* A container annotation for repeatable {@link Mapping} annotations.
|
||||
* Java automatically uses this when multiple {@code @Mapping} annotations
|
||||
* are applied to the same program element.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
|
||||
@interface WrapList
|
||||
{
|
||||
/**
|
||||
* The array of {@link Mapping} annotations.
|
||||
*
|
||||
* @return An array of {@link Mapping} instances. Must not be {@code null}.
|
||||
*/
|
||||
@NotNull Mapping[] value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a fixed {@link Versions} enum entry for a {@link Mapping} annotation.
|
||||
* This nested annotation is used to specify that a mapping is valid for one particular version.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
|
||||
@interface Fixed
|
||||
{
|
||||
/**
|
||||
* The specific {@link Versions} enum entry for which the mapping is valid.
|
||||
*
|
||||
* @return The fixed version. Must not be {@code null}.
|
||||
*/
|
||||
@NotNull Versions value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a range of {@link Versions} enum entries for a {@link Mapping} annotation.
|
||||
* This nested annotation is used to specify that a mapping is valid for a continuous
|
||||
* set of versions.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
|
||||
@interface Range
|
||||
{
|
||||
/**
|
||||
* The starting {@link Versions} enum entry of the range (inclusive).
|
||||
*
|
||||
* @return The starting version of the range. Must not be {@code null}.
|
||||
*/
|
||||
@NotNull Versions from();
|
||||
|
||||
/**
|
||||
* The ending {@link Versions} enum entry of the range (inclusive).
|
||||
*
|
||||
* @return The ending version of the range. Must not be {@code null}.
|
||||
*/
|
||||
@NotNull Versions to();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
package de.eisi05.npc.api.wrapper;
|
||||
|
||||
import com.google.common.primitives.Primitives;
|
||||
import de.eisi05.npc.api.utils.CallerUtils;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.utils.exceptions.VersionNotFound;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public abstract class Wrapper implements HandleHolder
|
||||
{
|
||||
private static final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Constructor<?>> wrapperConstructorCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Constructor<?>> instanceConstructorCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, MethodHandle> methodCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Field> fieldCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, MethodHandle> fieldGetterCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, MethodHandle> fieldSetterCache = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Map<String, Field>> enumFieldCache = new ConcurrentHashMap<>();
|
||||
|
||||
protected final Object handle;
|
||||
|
||||
public Wrapper(Object handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public static Object[] checkArgs(Object... args)
|
||||
{
|
||||
return Arrays.stream(args)
|
||||
.map(arg -> (arg instanceof HandleHolder h) ? h.getHandle() : arg)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
protected static Class<?> getTargetClass(Mapping mapping)
|
||||
{
|
||||
return classCache.computeIfAbsent(mapping.path(), name ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return Class.forName(name);
|
||||
} catch(ClassNotFoundException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static Constructor<?> findConstructor(Class<?> targetClass, Object[] args)
|
||||
{
|
||||
return Arrays.stream(targetClass.getDeclaredConstructors())
|
||||
.filter(ctor ->
|
||||
{
|
||||
Class<?>[] params = ctor.getParameterTypes();
|
||||
boolean varArgs = ctor.isVarArgs();
|
||||
|
||||
if((!varArgs && params.length != args.length) ||
|
||||
(varArgs && args.length < params.length - 1))
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < params.length; i++)
|
||||
{
|
||||
if(i >= args.length)
|
||||
return false;
|
||||
|
||||
Object arg = args[i];
|
||||
if(arg == null)
|
||||
continue;
|
||||
|
||||
Class<?> actual = arg.getClass();
|
||||
if(actual.isAnonymousClass() || actual.isSynthetic())
|
||||
{
|
||||
Class<?>[] interfaces = actual.getInterfaces();
|
||||
actual = interfaces.length > 0 ? interfaces[0] : actual.getSuperclass();
|
||||
}
|
||||
|
||||
if(varArgs && i == params.length - 1)
|
||||
{
|
||||
Class<?> comp = params[i].getComponentType();
|
||||
if(!Primitives.wrap(comp).isAssignableFrom(Primitives.wrap(actual)) && !actual.isArray())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!Primitives.wrap(params[i]).isAssignableFrom(Primitives.wrap(actual)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected static Object createInstance(Class<? extends Wrapper> wrapperClass, Object... args)
|
||||
{
|
||||
Object[] checkedArgs = checkArgs(args);
|
||||
try
|
||||
{
|
||||
Mapping[] annotations = wrapperClass.getAnnotationsByType(Mapping.class);
|
||||
|
||||
for(Mapping mapping : annotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
Class<?> targetClass = getTargetClass(mapping);
|
||||
|
||||
String cacheKey = targetClass.getName() +
|
||||
Arrays.toString(Arrays.stream(checkedArgs)
|
||||
.map(a -> a == null ? "null" : a.getClass().getName())
|
||||
.toArray());
|
||||
|
||||
Constructor<?> constructor = instanceConstructorCache.computeIfAbsent(cacheKey, k -> findConstructor(targetClass, checkedArgs));
|
||||
|
||||
if(constructor == null)
|
||||
throw new NoSuchMethodException("No matching constructor found for: " + targetClass.getName() + "(" +
|
||||
Arrays.toString(checkedArgs) + ")");
|
||||
|
||||
Object[] realArgs;
|
||||
|
||||
if(constructor.isVarArgs())
|
||||
{
|
||||
int fixedCount = constructor.getParameterCount() - 1;
|
||||
Class<?> varArgType = constructor.getParameterTypes()[fixedCount].getComponentType();
|
||||
Object varArgsArray = Array.newInstance(varArgType, args.length - fixedCount);
|
||||
|
||||
for(int i = 0; i < Array.getLength(varArgsArray); i++)
|
||||
Array.set(varArgsArray, i, checkedArgs[fixedCount + i]);
|
||||
|
||||
realArgs = new Object[constructor.getParameterCount()];
|
||||
System.arraycopy(checkedArgs, 0, realArgs, 0, fixedCount);
|
||||
realArgs[fixedCount] = varArgsArray;
|
||||
}
|
||||
else
|
||||
realArgs = checkedArgs;
|
||||
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(realArgs);
|
||||
}
|
||||
|
||||
throw new VersionNotFound(wrapperClass);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected static <T extends Wrapper> T createWrappedInstance(Class<T> wrapperClass, Object... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor<?> cached = wrapperConstructorCache.computeIfAbsent(wrapperClass, clazz ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor<?> cons = clazz.getDeclaredConstructor(Object.class);
|
||||
cons.setAccessible(true);
|
||||
return cons;
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return wrapperClass.cast(cached.newInstance(createInstance(wrapperClass, args)));
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static <T> Optional<T> getStaticWrappedFieldValue(String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> callerClass = CallerUtils.getCallerClass();
|
||||
Field callerField = callerClass.getDeclaredField(fieldName);
|
||||
|
||||
Mapping[] fieldAnnotations = callerField.getAnnotationsByType(Mapping.class);
|
||||
|
||||
Mapping[] classAnnotations = callerClass.getAnnotationsByType(Mapping.class);
|
||||
if(classAnnotations.length == 0)
|
||||
throw new IllegalStateException("Missing @WrapData on class: " + callerClass.getName());
|
||||
|
||||
Mapping fieldAnnotation = Arrays.stream(fieldAnnotations).filter(Versions::containsCurrentVersion).findFirst()
|
||||
.orElseThrow(() -> new VersionNotFound(callerClass.getName() + " -> " + callerField.getName()));
|
||||
|
||||
for(Mapping classData : classAnnotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(classData))
|
||||
continue;
|
||||
|
||||
String key = classData.path() + "#" + fieldAnnotation.path();
|
||||
|
||||
Field targetField = fieldCache.computeIfAbsent(key, k ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> targetClass = getTargetClass(classData);
|
||||
Field f = targetClass.getDeclaredField(fieldAnnotation.path());
|
||||
f.setAccessible(true);
|
||||
return f;
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return Optional.ofNullable((T) targetField.get(null));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
} catch(Exception e)
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T invokeStaticWrappedMethod(Object... args)
|
||||
{
|
||||
final Object[] newArgs = checkArgs(args);
|
||||
try
|
||||
{
|
||||
String callingMethodName = CallerUtils.getCallerMethodName();
|
||||
Class<?> callerClass = CallerUtils.getCallerClass();
|
||||
|
||||
Class<?> targetClass = getTargetClass(callerClass.getAnnotation(Mapping.class));
|
||||
|
||||
Method callingMethod = null;
|
||||
Class<?> clazz = callerClass;
|
||||
while(callingMethod == null && clazz != null)
|
||||
{
|
||||
callingMethod = Arrays.stream(clazz.getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(callingMethodName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
if(callingMethod == null)
|
||||
throw new NoSuchElementException("No value present -> " + callingMethodName);
|
||||
|
||||
Mapping[] annotations = callingMethod.getAnnotationsByType(Mapping.class);
|
||||
|
||||
MethodHandles.Lookup lookup = CallerUtils.getLookup(targetClass);
|
||||
|
||||
for(Mapping mapping : annotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
String methodPath = mapping.path();
|
||||
String key = targetClass.getName() + "#" + methodPath +
|
||||
Arrays.toString(Arrays.stream(newArgs)
|
||||
.map(a -> a == null ? "null" : a.getClass().getName()).toArray());
|
||||
|
||||
MethodHandle handle = methodCache.computeIfAbsent(key, k ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Method method = Arrays.stream(targetClass.getMethods())
|
||||
.filter(m -> m.getName().equals(methodPath))
|
||||
.filter(m ->
|
||||
{
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if(params.length != newArgs.length)
|
||||
return false;
|
||||
for(int i = 0; i < params.length; i++)
|
||||
{
|
||||
if(newArgs[i] == null)
|
||||
continue;
|
||||
if(!Primitives.wrap(params[i]).isAssignableFrom(Primitives.wrap(newArgs[i].getClass())))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new NoSuchMethodException(methodPath));
|
||||
|
||||
method.setAccessible(true);
|
||||
return lookup.unreflect(method);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return (T) handle.invokeWithArguments(newArgs);
|
||||
}
|
||||
|
||||
throw new VersionNotFound(callingMethod);
|
||||
} catch(Throwable e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getPath()
|
||||
{
|
||||
String callingMethodName = CallerUtils.getCallerMethodName();
|
||||
|
||||
Method callingMethod = Arrays.stream(getClass().getMethods())
|
||||
.filter(m -> m.getName().equals(callingMethodName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException(callingMethodName + " -> " + getClass()));
|
||||
|
||||
Mapping[] annotations = callingMethod.getAnnotationsByType(Mapping.class);
|
||||
|
||||
return Arrays.stream(annotations).filter(Versions::containsCurrentVersion).findFirst()
|
||||
.orElseThrow(() -> new VersionNotFound(callingMethod)).path();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T invokeWrappedMethod(Object... args)
|
||||
{
|
||||
final Object[] newArgs = checkArgs(args);
|
||||
try
|
||||
{
|
||||
String callingMethodName = CallerUtils.getCallerMethodName();
|
||||
|
||||
Method callingMethod = null;
|
||||
Class<?> clazz = getClass();
|
||||
while(callingMethod == null && clazz != null)
|
||||
{
|
||||
callingMethod = Arrays.stream(clazz.getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(callingMethodName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
if(callingMethod == null)
|
||||
throw new NoSuchElementException("No value present -> " + callingMethodName);
|
||||
|
||||
Mapping[] annotations = callingMethod.getAnnotationsByType(Mapping.class);
|
||||
|
||||
MethodHandles.Lookup lookup = CallerUtils.getLookup(getHandle().getClass());
|
||||
|
||||
for(Mapping mapping : annotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
String methodPath = mapping.path();
|
||||
String key = getHandle().getClass().getName() + "#" + methodPath +
|
||||
Arrays.toString(Arrays.stream(newArgs).map(a -> a == null ? "null" : a.getClass().getName()).toArray());
|
||||
|
||||
MethodHandle unboundHandle = methodCache.computeIfAbsent(key, k ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Method method = Arrays.stream(getHandle().getClass().getMethods())
|
||||
.filter(m -> m.getName().equals(methodPath))
|
||||
.filter(m ->
|
||||
{
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if(params.length != newArgs.length)
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < params.length; i++)
|
||||
{
|
||||
if(newArgs[i] == null)
|
||||
continue;
|
||||
|
||||
if(!Primitives.wrap(params[i]).isAssignableFrom(Primitives.wrap(newArgs[i].getClass())))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException(new NoSuchMethodException(
|
||||
methodPath + "(" + Arrays.stream(newArgs).map(o -> o.getClass().toString()).toList() + ")")));
|
||||
|
||||
method.setAccessible(true);
|
||||
return lookup.unreflect(method);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
MethodHandle boundHandle = unboundHandle.bindTo(getHandle());
|
||||
return (T) boundHandle.invokeWithArguments(newArgs);
|
||||
}
|
||||
|
||||
throw new VersionNotFound(callingMethod);
|
||||
} catch(Throwable e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T getWrappedFieldValue()
|
||||
{
|
||||
try
|
||||
{
|
||||
String callingMethodName = CallerUtils.getCallerMethodName();
|
||||
|
||||
Method callingMethod = null;
|
||||
Class<?> clazz = getClass();
|
||||
while(callingMethod == null && clazz != null)
|
||||
{
|
||||
callingMethod = Arrays.stream(clazz.getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(callingMethodName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
if(callingMethod == null)
|
||||
throw new NoSuchElementException("No value present -> " + callingMethodName);
|
||||
|
||||
Mapping[] annotations = callingMethod.getAnnotationsByType(Mapping.class);
|
||||
|
||||
for(Mapping mapping : annotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
String fieldName = mapping.path();
|
||||
String key = getHandle().getClass().getName() + "#" + fieldName;
|
||||
|
||||
MethodHandle getter = fieldGetterCache.computeIfAbsent(key, k ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Field f = null;
|
||||
Class<?> c = getHandle().getClass();
|
||||
|
||||
while(c != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
f = c.getDeclaredField(fieldName);
|
||||
if(!Modifier.isStatic(f.getModifiers()))
|
||||
break;
|
||||
f = null;
|
||||
} catch(NoSuchFieldException ignored) {}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
|
||||
if(f == null)
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
|
||||
f.setAccessible(true);
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(f.getDeclaringClass(), MethodHandles.lookup());
|
||||
return lookup.findGetter(f.getDeclaringClass(), fieldName, f.getType());
|
||||
} catch(NoSuchFieldException | IllegalAccessException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return (T) getter.invoke(getHandle());
|
||||
}
|
||||
|
||||
throw new VersionNotFound(callingMethod);
|
||||
} catch(Throwable e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> void setWrappedFieldValue(T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
String callingMethodName = CallerUtils.getCallerMethodName();
|
||||
|
||||
Method callingMethod = null;
|
||||
Class<?> clazz = getClass();
|
||||
while(callingMethod == null && clazz != null)
|
||||
{
|
||||
callingMethod = Arrays.stream(clazz.getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(callingMethodName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
if(callingMethod == null)
|
||||
throw new NoSuchElementException("No value present -> " + callingMethodName);
|
||||
|
||||
Mapping[] annotations = callingMethod.getAnnotationsByType(Mapping.class);
|
||||
|
||||
for(Mapping mapping : annotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
String fieldName = mapping.path();
|
||||
String key = getHandle().getClass().getName() + "#" + fieldName;
|
||||
|
||||
MethodHandle setter = fieldSetterCache.computeIfAbsent(key, k ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Field f = null;
|
||||
Class<?> c = getHandle().getClass();
|
||||
|
||||
while(c != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
f = c.getDeclaredField(fieldName);
|
||||
|
||||
if(!Modifier.isStatic(f.getModifiers()))
|
||||
break;
|
||||
f = null;
|
||||
} catch(NoSuchFieldException ignored) {}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
|
||||
if(f == null)
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
f.setAccessible(true);
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(f.getDeclaringClass(), MethodHandles.lookup());
|
||||
return lookup.findSetter(getHandle().getClass(), fieldName, f.getType());
|
||||
} catch(NoSuchFieldException | IllegalAccessException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
setter.invoke(getHandle(), value);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new VersionNotFound(callingMethod);
|
||||
} catch(Throwable e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
public interface EnumWrapper extends HandleHolder
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends Enum<T> & EnumWrapper, V extends Enum<?>> V cast(T object)
|
||||
{
|
||||
Class<?> enumClass = object.getClass();
|
||||
|
||||
Mapping[] classData = enumClass.getAnnotationsByType(Mapping.class);
|
||||
String classPath = Arrays.stream(classData)
|
||||
.filter(Versions::containsCurrentVersion)
|
||||
.map(Mapping::path)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new VersionNotFound(enumClass));
|
||||
|
||||
Class<?> targetEnum;
|
||||
try
|
||||
{
|
||||
targetEnum = Class.forName(classPath);
|
||||
} catch(ClassNotFoundException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Map<String, Field> fields = enumFieldCache.computeIfAbsent(enumClass, cls ->
|
||||
{
|
||||
Map<String, Field> map = new ConcurrentHashMap<>();
|
||||
for(Field f : cls.getDeclaredFields())
|
||||
{
|
||||
for(Mapping ann : f.getAnnotationsByType(Mapping.class))
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(ann))
|
||||
continue;
|
||||
|
||||
f.setAccessible(true);
|
||||
map.put(f.getName(), f);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
Field sourceField = fields.get(object.name());
|
||||
if(sourceField == null)
|
||||
throw new VersionNotFound(enumClass);
|
||||
|
||||
Mapping[] fieldData = sourceField.getAnnotationsByType(Mapping.class);
|
||||
String fieldName = Arrays.stream(fieldData)
|
||||
.filter(Versions::containsCurrentVersion)
|
||||
.map(Mapping::path)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new VersionNotFound(enumClass));
|
||||
|
||||
Field targetField = targetEnum.getDeclaredField(fieldName);
|
||||
targetField.setAccessible(true);
|
||||
return (V) targetField.get(null);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package de.eisi05.npc.api.wrapper.enums;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.EnumChatFormat")
|
||||
public enum ChatFormat implements Wrapper.EnumWrapper, Serializable
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
BLACK(ChatColor.BLACK),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
DARK_BLUE(ChatColor.DARK_BLUE),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
DARK_GREEN(ChatColor.DARK_GREEN),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "d")
|
||||
DARK_AQUA(ChatColor.DARK_AQUA),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "e")
|
||||
DARK_RED(ChatColor.DARK_RED),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "f")
|
||||
DARK_PURPLE(ChatColor.DARK_PURPLE),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "g")
|
||||
GOLD(ChatColor.GOLD),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "h")
|
||||
GRAY(ChatColor.GRAY),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "i")
|
||||
DARK_GRAY(ChatColor.DARK_GRAY),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "j")
|
||||
BLUE(ChatColor.BLUE),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "k")
|
||||
GREEN(ChatColor.GREEN),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "l")
|
||||
AQUA(ChatColor.AQUA),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "m")
|
||||
RED(ChatColor.RED),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "n")
|
||||
LIGHT_PURPLE(ChatColor.LIGHT_PURPLE),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "o")
|
||||
YELLOW(ChatColor.YELLOW),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "p")
|
||||
WHITE(ChatColor.WHITE),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "q")
|
||||
OBFUSCATED(ChatColor.MAGIC),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "r")
|
||||
BOLD(ChatColor.BOLD),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "s")
|
||||
STRIKETHROUGH(ChatColor.STRIKETHROUGH),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "t")
|
||||
UNDERLINE(ChatColor.UNDERLINE),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "u")
|
||||
ITALIC(ChatColor.ITALIC),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "v")
|
||||
RESET(ChatColor.RESET);
|
||||
|
||||
private final ChatColor color;
|
||||
|
||||
ChatFormat(@NotNull ChatColor color)
|
||||
{
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public static @Nullable ChatFormat fromChatColor(@NotNull ChatColor color)
|
||||
{
|
||||
for(ChatFormat format : ChatFormat.values())
|
||||
{
|
||||
if(format.color.equals(color))
|
||||
return format;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChatColor getBukkitColor()
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.eisi05.npc.api.wrapper.enums;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.entity.EnumItemSlot")
|
||||
public enum EquipmentSlot implements Wrapper.EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
MAINHAND,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
OFFHAND,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
FEET,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "d")
|
||||
LEGS,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "e")
|
||||
CHEST,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "f")
|
||||
HEAD,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "g")
|
||||
BODY;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.eisi05.npc.api.wrapper.enums;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.EnumHand")
|
||||
public enum InteractionHand implements Wrapper.EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
MAIN_HAND,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
OFF_HAND;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package de.eisi05.npc.api.wrapper.enums;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.entity.EntityPose")
|
||||
public enum Pose implements Wrapper.EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
STANDING(getBukkit("STANDING"), Material.ARMOR_STAND),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
FALL_FLYING(getBukkit("FALL_FLYING"), Material.FEATHER),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
SLEEPING(getBukkit("SLEEPING"), Material.RED_BED),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "d")
|
||||
SWIMMING(getBukkit("SWIMMING"), Material.WATER_BUCKET),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "e")
|
||||
SPIN_ATTACK(getBukkit("SPIN_ATTACK"), Material.TRIDENT),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "f")
|
||||
CROUCHING(getBukkit("SNEAKING"), Material.LEATHER_BOOTS),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "g")
|
||||
LONG_JUMPING(getBukkit("LONG_JUMPING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "h")
|
||||
DYING(getBukkit("DYING"), Material.ROTTEN_FLESH),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_21_5), path = "i")
|
||||
CROAKING(getBukkit("CROAKING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_21_5), path = "j")
|
||||
USING_TONGUE(getBukkit("USING_TONGUE"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "k")
|
||||
SITTING(getBukkit("SITTING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "l")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "k")
|
||||
ROARING(getBukkit("ROARING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "m")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "l")
|
||||
SNIFFING(getBukkit("SNIFFING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "n")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "m")
|
||||
EMERGING(getBukkit("EMERGING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "o")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "n")
|
||||
DIGGING(getBukkit("DIGGING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_4, to = Versions.V1_21_5), path = "p")
|
||||
SLIDING(getBukkit("SLIDING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_4, to = Versions.V1_21_5), path = "q")
|
||||
SHOOTING(getBukkit("SHOOTING"), null),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_4, to = Versions.V1_21_5), path = "r")
|
||||
INHALING(getBukkit("INHALING"), null);
|
||||
|
||||
private final org.bukkit.entity.Pose bukkitPose;
|
||||
private final Material icon;
|
||||
|
||||
Pose(@Nullable org.bukkit.entity.Pose bukkitPose, Material icon)
|
||||
{
|
||||
this.bukkitPose = bukkitPose;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public static @Nullable Pose fromBukkit(@NotNull org.bukkit.entity.Pose bukkitPose)
|
||||
{
|
||||
for(Pose pose : Pose.values())
|
||||
{
|
||||
if(pose.bukkitPose == null)
|
||||
continue;
|
||||
|
||||
if(pose.bukkitPose == bukkitPose)
|
||||
return pose;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable org.bukkit.entity.Pose getBukkit(@NotNull String pose)
|
||||
{
|
||||
try
|
||||
{
|
||||
return org.bukkit.entity.Pose.valueOf(pose);
|
||||
} catch(IllegalArgumentException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull Pose[] getValues()
|
||||
{
|
||||
return Arrays.stream(values()).filter(pose -> pose.bukkitPose != null).toArray(Pose[]::new);
|
||||
}
|
||||
|
||||
public org.bukkit.entity.Pose toBukkit()
|
||||
{
|
||||
return bukkitPose;
|
||||
}
|
||||
|
||||
public boolean isForPlayer()
|
||||
{
|
||||
return icon != null;
|
||||
}
|
||||
|
||||
public Material getIcon()
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Reflections;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.entity.decoration.EntityArmorStand")
|
||||
public class WrappedArmorStand extends WrappedEntity<ArmorStand>
|
||||
{
|
||||
private WrappedArmorStand(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static WrappedArmorStand create(World world)
|
||||
{
|
||||
return new WrappedArmorStand(
|
||||
createInstance(WrappedArmorStand.class, EntityTypes.ARMOR_STAND, Reflections.invokeMethod(world, "getHandle").get()));
|
||||
}
|
||||
|
||||
public static WrappedEntityData applyData(WrappedEntity<?> entity, WrappedComponent component)
|
||||
{
|
||||
WrappedEntityData data = entity.getEntityData();
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(0), (byte) 0x20);
|
||||
data.set(WrappedEntityData.EntityDataSerializers.OPTIONAL_CHAT_COMPONENT.create(2), Optional.of(component.getHandle()));
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BOOLEAN.create(4), true);
|
||||
data.set(WrappedEntityData.EntityDataSerializers.BYTE.create(15), (byte) 0x10);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.world.entity.ai.attributes.AttributeModifiable")
|
||||
public class WrappedAttributeInstance extends Wrapper
|
||||
{
|
||||
WrappedAttributeInstance(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public WrappedAttributeInstance(Object holder, Consumer<?> consumer)
|
||||
{
|
||||
super(createInstance(WrappedAttributeInstance.class, holder, consumer));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "a")
|
||||
public void setBaseValue(double value)
|
||||
{
|
||||
invokeWrappedMethod(value);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.world.entity.ai.attributes.GenericAttributes")
|
||||
public static class Attributes extends Wrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "y")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_6), path = "t")
|
||||
public static final Object SCALE_HOLDER = getStaticWrappedFieldValue("SCALE_HOLDER").orElse(null);
|
||||
|
||||
public Attributes()
|
||||
{
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "net.minecraft.server.level.ClientInformation")
|
||||
public class WrappedClientInformation extends Wrapper
|
||||
{
|
||||
private WrappedClientInformation(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "a")
|
||||
public static @NotNull WrappedClientInformation createDefault()
|
||||
{
|
||||
return new WrappedClientInformation(invokeStaticWrappedMethod());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.commands.CommandListenerWrapper")
|
||||
public class WrappedCommandSourceStack extends Wrapper
|
||||
{
|
||||
public WrappedCommandSourceStack(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "getBukkitSender")
|
||||
public CommandSender getBukkitSender()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public @NotNull WrappedCommandSourceStack withMaximumPermission()
|
||||
{
|
||||
return new WrappedCommandSourceStack(invokeWrappedMethod(4));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public @NotNull WrappedCommandSourceStack withSuppressedOutput()
|
||||
{
|
||||
return new WrappedCommandSourceStack(invokeWrappedMethod());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Var;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.enums.ChatFormat;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.chat.IChatBaseComponent")
|
||||
public class WrappedComponent extends Wrapper
|
||||
{
|
||||
private WrappedComponent(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static WrappedComponent fromHandle(@Nullable Object handle)
|
||||
{
|
||||
return new WrappedComponent(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public static @NotNull WrappedComponent create(@Nullable String text)
|
||||
{
|
||||
return new WrappedComponent(text == null ? CommonComponents.EMPTY : invokeStaticWrappedMethod(text));
|
||||
}
|
||||
|
||||
public static @NotNull WrappedComponent parseFromLegacy(@Nullable String legacy)
|
||||
{
|
||||
if(legacy == null || legacy.isEmpty())
|
||||
return create(null);
|
||||
|
||||
return Var.fromString(legacy);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public @NotNull WrappedComponent setFormats(@NotNull ChatFormat... format)
|
||||
{
|
||||
if(format.length == 0)
|
||||
return this;
|
||||
|
||||
try
|
||||
{
|
||||
Method[] methods = getHandle().getClass().getMethods();
|
||||
|
||||
String path = getPath();
|
||||
|
||||
Method target = Arrays.stream(methods)
|
||||
.filter(m -> m.getName().equals(path))
|
||||
.filter(Method::isVarArgs)
|
||||
.filter(method -> method.getParameterTypes()[0].isArray())
|
||||
.filter(method -> method.getParameterTypes()[0].componentType().equals(format[0].getHandle().getClass()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
target.invoke(getHandle(), (Object) Arrays.stream(format)
|
||||
.map(ChatFormat::getHandle)
|
||||
.toArray(i -> (Object[]) Array.newInstance(format[0].getHandle().getClass(), i)));
|
||||
} catch(IllegalAccessException | InvocationTargetException | NoSuchElementException e)
|
||||
{
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "format")
|
||||
public @NotNull WrappedComponent withClickEvent(@NotNull ClickEvent clickEvent)
|
||||
{
|
||||
invokeWrappedMethod((UnaryOperator<Object>) o -> new Style(o).withClickEvent(clickEvent).getHandle());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "format")
|
||||
public @NotNull WrappedComponent withHoverEvent(@NotNull HoverEvent hoverEvent)
|
||||
{
|
||||
invokeWrappedMethod((UnaryOperator<Object>) o -> new Style(o).withHoverEvent(hoverEvent).getHandle());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_1, to = Versions.V1_21_5), path = "b")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_19), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "addSibling")
|
||||
public @NotNull WrappedComponent append(WrappedComponent wrappedComponent)
|
||||
{
|
||||
invokeWrappedMethod(wrappedComponent);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_4, to = Versions.V1_21_5), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_20_2), path = "e")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "mutableCopy")
|
||||
public @NotNull WrappedComponent copy()
|
||||
{
|
||||
return new WrappedComponent(invokeWrappedMethod());
|
||||
}
|
||||
|
||||
public @NotNull String toLegacy()
|
||||
{
|
||||
return Var.toString(this);
|
||||
}
|
||||
|
||||
public @NotNull String toLegacyNoColor()
|
||||
{
|
||||
return ChatColor.stripColor(toLegacy());
|
||||
}
|
||||
|
||||
public @NotNull SerializedComponent serialize()
|
||||
{
|
||||
return new SerializedComponent(toLegacy());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_21_5), path = "net.minecraft.network.chat.CommonComponents")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "net.minecraft.network.chat.ChatComponentText")
|
||||
private static class CommonComponents extends Wrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "d")
|
||||
private static final Object EMPTY = getStaticWrappedFieldValue("EMPTY").orElse(null);
|
||||
|
||||
private CommonComponents()
|
||||
{
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.chat.ChatClickable")
|
||||
public static class ClickEvent extends Wrapper
|
||||
{
|
||||
private ClickEvent(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static @NotNull ClickEvent create(@NotNull ClickAction action, @NotNull Object value)
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_21_5))
|
||||
return createWrappedInstance(ClickEvent.class, action, value);
|
||||
return action.function.apply(value);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$EnumClickAction")
|
||||
public enum ClickAction implements EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
OPEN_URL(o -> new OpenUrl((URI) o)),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
OPEN_FILE(o -> new OpenFile((String) o)),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
RUN_COMMAND(o -> new RunCommand((String) o)),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "d")
|
||||
SUGGEST_COMMAND(o -> new SuggestCommand((String) o)),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "e")
|
||||
CHANGE_PAGE(o -> new ChangePage((int) o)),
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "f")
|
||||
COPY_TO_CLIPBOARD(o -> new CopyToClipboard((String) o));
|
||||
|
||||
private final Function<Object, ClickEvent> function;
|
||||
|
||||
ClickAction(Function<Object, ClickEvent> function)
|
||||
{
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$OpenUrl")
|
||||
private static class OpenUrl extends WrappedComponent.ClickEvent
|
||||
{
|
||||
private OpenUrl(URI url)
|
||||
{
|
||||
super(createInstance(OpenUrl.class, url));
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$OpenFile")
|
||||
private static class OpenFile extends WrappedComponent.ClickEvent
|
||||
{
|
||||
private OpenFile(String file)
|
||||
{
|
||||
super(createInstance(OpenFile.class, file));
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$RunCommand")
|
||||
private static class RunCommand extends WrappedComponent.ClickEvent
|
||||
{
|
||||
private RunCommand(String command)
|
||||
{
|
||||
super(createInstance(RunCommand.class, command));
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$SuggestCommand")
|
||||
private static class SuggestCommand extends WrappedComponent.ClickEvent
|
||||
{
|
||||
private SuggestCommand(String command)
|
||||
{
|
||||
super(createInstance(SuggestCommand.class, command));
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$ChangePage")
|
||||
private static class ChangePage extends WrappedComponent.ClickEvent
|
||||
{
|
||||
private ChangePage(int page)
|
||||
{
|
||||
super(createInstance(ChangePage.class, page));
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatClickable$CopyToClipboard")
|
||||
private static class CopyToClipboard extends WrappedComponent.ClickEvent
|
||||
{
|
||||
private CopyToClipboard(String string)
|
||||
{
|
||||
super(createInstance(CopyToClipboard.class, string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.chat.ChatHoverable")
|
||||
public static class HoverEvent extends Wrapper
|
||||
{
|
||||
|
||||
private HoverEvent(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static HoverEvent create(@NotNull WrappedComponent component)
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_21_5))
|
||||
return createWrappedInstance(HoverEvent.class, HoverAction.SHOW_TEXT, component);
|
||||
return new ShowText(component);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "net.minecraft.network.chat.ChatHoverable$e")
|
||||
private static class ShowText extends HoverEvent
|
||||
{
|
||||
|
||||
private ShowText(@NotNull WrappedComponent component)
|
||||
{
|
||||
super(createInstance(ShowText.class, component));
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.chat.ChatHoverable$EnumHoverAction")
|
||||
private static class HoverAction extends Wrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_4), path = "a")
|
||||
private static final Object SHOW_TEXT = getStaticWrappedFieldValue("SHOW_TEXT").orElse(null);
|
||||
|
||||
private HoverAction()
|
||||
{
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SerializedComponent implements Serializable
|
||||
{
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String text;
|
||||
|
||||
public SerializedComponent(@Nullable String text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public @NotNull WrappedComponent deserialize()
|
||||
{
|
||||
return WrappedComponent.parseFromLegacy(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.chat.ChatModifier")
|
||||
public static class Style extends Wrapper
|
||||
{
|
||||
private Style(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setChatClickable")
|
||||
public @NotNull Style withClickEvent(@NotNull ClickEvent clickEvent)
|
||||
{
|
||||
return new Style(invokeWrappedMethod(clickEvent));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setChatHoverable")
|
||||
public @NotNull Style withHoverEvent(@NotNull HoverEvent hoverEvent)
|
||||
{
|
||||
return new Style(invokeWrappedMethod(hoverEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.packets.PacketWrapper;
|
||||
import io.netty.channel.Channel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "net.minecraft.server.network.PlayerConnection")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "net.minecraft.network.PlayerConnection")
|
||||
public class WrappedConnection extends Wrapper
|
||||
{
|
||||
WrappedConnection(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
WrappedConnection(@NotNull WrappedMinecraftServer server, @NotNull WrappedNetworkManager networkManager, @NotNull WrappedServerPlayer player,
|
||||
@NotNull CommonListenerCookie commonListenerCookie)
|
||||
{
|
||||
super(createInstance(WrappedConnection.class, server, networkManager, player, commonListenerCookie));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "b")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_20), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "sendPacket")
|
||||
public void sendPacket(@NotNull PacketWrapper packet)
|
||||
{
|
||||
invokeWrappedMethod(packet);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "e")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_20_4), path = "c")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20), path = "h")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_3), path = "b")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "a")
|
||||
public @NotNull WrappedNetworkManager networkManager()
|
||||
{
|
||||
return new WrappedNetworkManager(getWrappedFieldValue());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.NetworkManager")
|
||||
public static class WrappedNetworkManager extends Wrapper
|
||||
{
|
||||
private WrappedNetworkManager(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static @NotNull WrappedNetworkManager create(@NotNull PacketFlow packetFlow)
|
||||
{
|
||||
return createWrappedInstance(WrappedNetworkManager.class, packetFlow);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "n")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18_2, to = Versions.V1_20), path = "m")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18), path = "k")
|
||||
public @NotNull Channel channel()
|
||||
{
|
||||
return getWrappedFieldValue();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.protocol.EnumProtocolDirection")
|
||||
public enum PacketFlow implements EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "a")
|
||||
SERVERBOUND,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "b")
|
||||
CLIENTBOUND;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_21_5), path = "net.minecraft.server.network.CommonListenerCookie")
|
||||
public static class CommonListenerCookie extends Wrapper
|
||||
{
|
||||
public CommonListenerCookie(@NotNull GameProfile profile, int latency, boolean transferred)
|
||||
{
|
||||
super(createInstance(CommonListenerCookie.class, profile, latency, WrappedClientInformation.createDefault(), transferred));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import de.eisi05.npc.api.utils.Reflections;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.packets.CustomPacket;
|
||||
import de.eisi05.npc.api.wrapper.packets.PacketWrapper;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class WrappedEntity<T extends Entity> extends Wrapper
|
||||
{
|
||||
protected WrappedEntity(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static <T extends Entity, V extends WrappedEntity<T>> @NotNull V fromEntity(@NotNull T entity, @NotNull Class<V> clazz)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor<V> constructor = clazz.getDeclaredConstructor(Object.class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(Reflections.invokeMethod(entity, "getHandle").get());
|
||||
} catch(NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends WrappedEntity<? extends Entity>> boolean is(WrappedEntity<?> entity, Class<T> clazz)
|
||||
{
|
||||
Mapping[] mappings = clazz.getAnnotationsByType(Mapping.class);
|
||||
|
||||
for(Mapping mapping : mappings)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
return getTargetClass(mapping).equals(entity.getHandle().getClass());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "getBukkitEntity")
|
||||
public @NotNull T getBukkitPlayer()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setLocation")
|
||||
public void moveTo(@NotNull Location location)
|
||||
{
|
||||
invokeWrappedMethod(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "ar")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_4), path = "au")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_21), path = "ar")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_6), path = "ap")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "an")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "al")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20), path = "aj")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_3), path = "al")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_19_1), path = "ai")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getDataWatcher")
|
||||
public @NotNull WrappedEntityData getEntityData()
|
||||
{
|
||||
return new WrappedEntityData(invokeWrappedMethod());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "u")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_4), path = "q")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "p")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "r")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_19_3), path = "au")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "at")
|
||||
protected void setPassengers(WrappedEntity<?>... entities)
|
||||
{
|
||||
setWrappedFieldValue(ImmutableList.copyOf(Arrays.stream(entities).map(wrappedEntity -> wrappedEntity.handle).toList()));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "cU")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "cN")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "cK")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "cJ")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20), path = "cH")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_4), path = "cG")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_3), path = "cH")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_1), path = "cC")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19), path = "cD")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_18_2), path = "cA")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getWorld")
|
||||
public @NotNull Object getServer()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_6), path = "dl")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "dj")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "di")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20), path = "S")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_3), path = "T")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_19_1), path = "S")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getPacket")
|
||||
public @NotNull PacketWrapper getAddEntityPacket()
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_21))
|
||||
return new CustomPacket(invokeWrappedMethod());
|
||||
return new CustomPacket(invokeWrappedMethod(new WrappedEntityTrackerEntry(this)));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "cR")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "cK")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "cH")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "cG")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20), path = "cE")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_19_4), path = "cD")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "cz")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_18_2), path = "cw")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getBoundingBox")
|
||||
public BoundingBox getBoundingBox()
|
||||
{
|
||||
return new BoundingBox(invokeWrappedMethod());
|
||||
}
|
||||
|
||||
public int getId()
|
||||
{
|
||||
return getBukkitPlayer().getEntityId();
|
||||
}
|
||||
|
||||
public @NotNull World getWorld()
|
||||
{
|
||||
return getBukkitPlayer().getWorld();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.entity.EntityTypes")
|
||||
public static class EntityTypes extends Wrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "g")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_4), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_21), path = "d")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "c")
|
||||
public static final Object ARMOR_STAND = getStaticWrappedFieldValue("ARMOR_STAND").orElse(null);
|
||||
|
||||
private EntityTypes()
|
||||
{
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.phys.AxisAlignedBB")
|
||||
public static class BoundingBox extends Wrapper
|
||||
{
|
||||
private BoundingBox(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
public double getYSize()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.enums.Pose;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.syncher.DataWatcher")
|
||||
public class WrappedEntityData extends Wrapper
|
||||
{
|
||||
WrappedEntityData(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_20_4), path = "b")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "set")
|
||||
public <T> void set(@NotNull EntityDataAccessor<T> accessor, @Nullable T value)
|
||||
{
|
||||
invokeWrappedMethod(accessor, value);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
public @NotNull List<?> packDirty()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "c")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getAll")
|
||||
public List<?> getAll()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.syncher.DataWatcherObject")
|
||||
public static class EntityDataAccessor<T> extends Wrapper
|
||||
{
|
||||
private EntityDataAccessor(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static <T> @NotNull EntityDataAccessor<T> create(int i, @NotNull Object serializer)
|
||||
{
|
||||
return createWrappedInstance(EntityDataAccessor.class, i, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.syncher.DataWatcherSerializer")
|
||||
public static class EntityDataSerializer<T> extends Wrapper
|
||||
{
|
||||
private EntityDataSerializer(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public @NotNull EntityDataAccessor<T> create(int i)
|
||||
{
|
||||
return EntityDataAccessor.create(i, this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.syncher.DataWatcherRegistry")
|
||||
public static class EntityDataSerializers extends Wrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public static final EntityDataSerializer<Byte> BYTE = new EntityDataSerializer<>(getStaticWrappedFieldValue("BYTE").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
public static final EntityDataSerializer<Integer> INT = new EntityDataSerializer<>(getStaticWrappedFieldValue("INT").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "c")
|
||||
public static final EntityDataSerializer<Long> LONG = new EntityDataSerializer<>(getStaticWrappedFieldValue("LONG").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "d")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "c")
|
||||
public static final EntityDataSerializer<Float> FLOAT = new EntityDataSerializer<>(getStaticWrappedFieldValue("FLOAT").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "e")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "d")
|
||||
public static final EntityDataSerializer<String> STRING = new EntityDataSerializer<>(getStaticWrappedFieldValue("STRING").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "e")
|
||||
public static final EntityDataSerializer<?> CHAT_COMPONENT = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("CHAT_COMPONENT").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "g")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "f")
|
||||
public static final EntityDataSerializer<Optional<?>> OPTIONAL_CHAT_COMPONENT = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_CHAT_COMPONENT").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "h")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "g")
|
||||
public static final EntityDataSerializer<?> ITEM_STACK = new EntityDataSerializer<>(getStaticWrappedFieldValue("ITEM_STACK").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_21_5), path = "i")
|
||||
public static final EntityDataSerializer<?> BLOCK_DATA = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("BLOCK_DATA").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_21_5), path = "j")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "i")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "h")
|
||||
public static final EntityDataSerializer<Optional<?>> OPTIONAL_BLOCK_DATA = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_BLOCK_DATA").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_21_5), path = "k")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "j")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "i")
|
||||
public static final EntityDataSerializer<Boolean> BOOLEAN = new EntityDataSerializer<>(getStaticWrappedFieldValue("BOOLEAN").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_21_5), path = "l")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "k")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "j")
|
||||
public static final EntityDataSerializer<?> PARTICLE_PARAM = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("PARTICLE_PARAM").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "m")
|
||||
public static final EntityDataSerializer<List<?>> PARTICLE_PARAM_LIST = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("PARTICLE_PARAM_LIST").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "n")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "m")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "l")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "k")
|
||||
public static final EntityDataSerializer<?> VECTOR = new EntityDataSerializer<>(getStaticWrappedFieldValue("VECTOR").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "o")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "n")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "m")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "l")
|
||||
public static final EntityDataSerializer<?> BLOCK_POSITION = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("BLOCK_POSITION").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "p")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "o")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "n")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "m")
|
||||
public static final EntityDataSerializer<?> OPTIONAL_BLOCK_POSITION = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_BLOCK_POSITION").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "q")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "p")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "o")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "n")
|
||||
public static final EntityDataSerializer<?> ENUM_DIRECTION = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("ENUM_DIRECTION").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "r")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "q")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "p")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "o")
|
||||
public static final EntityDataSerializer<Optional<UUID>> OPTIONAL_UUID = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_UUID").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "r")
|
||||
public static final EntityDataSerializer<Optional<?>> OPTIONAL_ENTITY_REFERENCE = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_ENTITY_REFERENCE").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "s")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "r")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "q")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "p")
|
||||
public static final EntityDataSerializer<Optional<?>> OPTIONAL_GLOBAL_POS = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_GLOBAL_POS").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "t")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "s")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "r")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "q")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "p")
|
||||
public static final EntityDataSerializer<?> NBT_TAG = new EntityDataSerializer<>(getStaticWrappedFieldValue("NBT_TAG").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "u")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "t")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "s")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "r")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "q")
|
||||
public static final EntityDataSerializer<?> VILLAGER_DATA = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("VILLAGER_DATA").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "v")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "u")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "t")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "s")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "r")
|
||||
public static final EntityDataSerializer<OptionalInt> OPTIONAL_INT = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("OPTIONAL_INT").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "w")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "v")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "u")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "t")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "s")
|
||||
public static final EntityDataSerializer<Pose> ENTITY_POSE = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("ENTITY_POSE").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5), path = "x")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "w")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "v")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "u")
|
||||
public static final EntityDataSerializer<?> CAT_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("CAT_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "y")
|
||||
public static final EntityDataSerializer<?> CHICKEN_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("CHICKEN_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "z")
|
||||
public static final EntityDataSerializer<?> COW_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("COW_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "A")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "y")
|
||||
public static final EntityDataSerializer<?> WOLF_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("WOLF_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "B")
|
||||
public static final EntityDataSerializer<?> WOLF_SOUND_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("WOLF_SOUND_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "C")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "z")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "x")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "w")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "v")
|
||||
public static final EntityDataSerializer<?> FROG_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("FROG_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "D")
|
||||
public static final EntityDataSerializer<?> PIG_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("PIG_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "E")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "A")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "y")
|
||||
@Mapping(fixed = @Mapping.Fixed(value = Versions.V1_19_3), path = "x")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "w")
|
||||
public static final EntityDataSerializer<?> PAINTING_VARIANT_HOLDER = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("PAINTING_VARIANT_HOLDER").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "F")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "B")
|
||||
public static final EntityDataSerializer<?> ARMADILLO_STATE = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("ARMADILLO_STATE").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "G")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "C")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "z")
|
||||
public static final EntityDataSerializer<?> SNIFFER_STATE = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("SNIFFER_STATE").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "H")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "D")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "A")
|
||||
public static final EntityDataSerializer<?> VECTOR3 = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("VECTOR3").orElse(null));
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "I")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_4), path = "E")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_20_4), path = "B")
|
||||
public static final EntityDataSerializer<?> QUARTERNION = new EntityDataSerializer<>(
|
||||
getStaticWrappedFieldValue("QUARTERNION").orElse(null));
|
||||
|
||||
private EntityDataSerializers()
|
||||
{
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "net.minecraft.server.level.EntityTrackerEntry")
|
||||
public class WrappedEntityTrackerEntry extends Wrapper
|
||||
{
|
||||
public WrappedEntityTrackerEntry(@NotNull WrappedEntity<?> entity)
|
||||
{
|
||||
super(createHandle(entity));
|
||||
}
|
||||
|
||||
private static @NotNull Object createHandle(@NotNull WrappedEntity<?> entity)
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_21_5))
|
||||
return createInstance(WrappedEntityTrackerEntry.class, entity.getServer(), entity, 0, false, (Consumer<Object>) o ->
|
||||
{
|
||||
}, Set.of());
|
||||
else
|
||||
return createInstance(WrappedEntityTrackerEntry.class, entity.getServer(), entity, 0, false, (Consumer<Object>) o ->
|
||||
{
|
||||
}, (BiConsumer<Object, List<UUID>>) (o, uuids) ->
|
||||
{
|
||||
}, Set.of());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import de.eisi05.npc.api.utils.Reflections;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.server.MinecraftServer")
|
||||
public class WrappedMinecraftServer extends Wrapper
|
||||
{
|
||||
public static final WrappedMinecraftServer INSTANCE = new WrappedMinecraftServer(Reflections.invokeMethod(Bukkit.getServer(), "getServer").get());
|
||||
|
||||
private WrappedMinecraftServer(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "vanillaCommandDispatcher")
|
||||
public @NotNull WrappedMinecraftServer commands()
|
||||
{
|
||||
return new WrappedMinecraftServer(getWrappedFieldValue());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getDedicatedServerProperties")
|
||||
public WrappedServerProperties getProperties()
|
||||
{
|
||||
return new WrappedServerProperties(invokeWrappedMethod());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "s")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "r")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "s")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_20_2), path = "u")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_18_2), path = "y")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_18), path = "z")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "x")
|
||||
public WrappedServerSettings settings()
|
||||
{
|
||||
return new WrappedServerSettings(getWrappedFieldValue());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "options")
|
||||
public Object options()
|
||||
{
|
||||
return getWrappedFieldValue();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "ba")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "bc")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "aZ")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "aU")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20), path = "aV")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_4), path = "aX")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_3), path = "aW")
|
||||
public Object registryAccess()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
@SuppressWarnings("unchecked")
|
||||
public @NotNull CommandDispatcher<Object> getCommandDispatcher()
|
||||
{
|
||||
return (CommandDispatcher<Object>) Reflections.invokeMethod(commands().getHandle(), getPath()).get();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.server.dedicated.DedicatedServerProperties")
|
||||
public static class WrappedServerProperties extends Wrapper
|
||||
{
|
||||
private WrappedServerProperties(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public WrappedServerProperties(Properties properties, Object options)
|
||||
{
|
||||
super(createInstance(WrappedServerProperties.class, properties, options));
|
||||
}
|
||||
|
||||
public static WrappedServerProperties fromHandle(Object handle)
|
||||
{
|
||||
return new WrappedServerProperties(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "X")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_21), path = "Y")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_6), path = "X")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_20_4), path = "W")
|
||||
public boolean enforceSecureProfile()
|
||||
{
|
||||
return getWrappedFieldValue();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "ac")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_6), path = "ab")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_2, to = Versions.V1_20_4), path = "Z")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_20), path = "Y")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_19_1), path = "X")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_18_2), path = "Y")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "X")
|
||||
public Properties properties()
|
||||
{
|
||||
return getWrappedFieldValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.server.dedicated.DedicatedServerSettings")
|
||||
public static class WrappedServerSettings extends Wrapper
|
||||
{
|
||||
private WrappedServerSettings(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setProperty")
|
||||
public WrappedServerSettings update(UnaryOperator<Object> unaryOperator)
|
||||
{
|
||||
invokeWrappedMethod(unaryOperator);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import de.eisi05.npc.api.utils.Reflections;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.enums.ChatFormat;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.scores.ScoreboardTeam")
|
||||
public class WrappedPlayerTeam extends Wrapper
|
||||
{
|
||||
private static final Map<UUID, Map<String, WrappedPlayerTeam>> teams = new HashMap<>();
|
||||
|
||||
private WrappedPlayerTeam(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static @NotNull WrappedPlayerTeam create(@NotNull Player player, @NotNull String name)
|
||||
{
|
||||
if(exists(player, name))
|
||||
return teams.get(player.getUniqueId()).get(name);
|
||||
|
||||
Object scoreboard = Reflections.invokeMethod(player.getScoreboard(), "getHandle").get();
|
||||
|
||||
WrappedPlayerTeam wrappedPlayerTeam = createWrappedInstance(WrappedPlayerTeam.class, scoreboard, name);
|
||||
|
||||
var map = teams.getOrDefault(player.getUniqueId(), new HashMap<>());
|
||||
map.put(name, wrappedPlayerTeam);
|
||||
teams.put(player.getUniqueId(), map);
|
||||
|
||||
return wrappedPlayerTeam;
|
||||
}
|
||||
|
||||
public static boolean exists(@NotNull Player player, @NotNull String name)
|
||||
{
|
||||
return teams.getOrDefault(player.getUniqueId(), new HashMap<>()).containsKey(name);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setNameTagVisibility")
|
||||
public void setNameTagVisibility(@NotNull Visibility visibility)
|
||||
{
|
||||
invokeWrappedMethod(visibility.getHandle());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "h")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_4), path = "g")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getPlayerNameSet")
|
||||
public @NotNull Collection<String> getPlayers()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "c")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_4), path = "b")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getName")
|
||||
public @NotNull String getName()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setColor")
|
||||
public void setColor(@NotNull ChatFormat color)
|
||||
{
|
||||
invokeWrappedMethod(color);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "a")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setPrefix")
|
||||
public void setPrefix(@NotNull WrappedComponent prefix)
|
||||
{
|
||||
invokeWrappedMethod(prefix);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.world.scores" +
|
||||
".ScoreboardTeamBase$EnumNameTagVisibility")
|
||||
public enum Visibility implements EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
ALWAYS,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
NEVER,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
HIDE_FOR_OTHER_TEAMS,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "d")
|
||||
HIDE_FOR_OWN_TEAM;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package de.eisi05.npc.api.wrapper.objects;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import de.eisi05.npc.api.utils.Reflections;
|
||||
import de.eisi05.npc.api.utils.Var;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.utils.exceptions.VersionNotFound;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.enums.Pose;
|
||||
import de.eisi05.npc.api.wrapper.packets.BundlePacket;
|
||||
import de.eisi05.npc.api.wrapper.packets.ChatPacket;
|
||||
import de.eisi05.npc.api.wrapper.packets.PacketWrapper;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.server.level.EntityPlayer")
|
||||
public class WrappedServerPlayer extends WrappedEntity<Player>
|
||||
{
|
||||
private WrappedArmorStand armorStand;
|
||||
|
||||
WrappedServerPlayer(Object serverPlayer)
|
||||
{
|
||||
super(serverPlayer);
|
||||
}
|
||||
|
||||
public static @NotNull WrappedServerPlayer fromPlayer(@NotNull Player player)
|
||||
{
|
||||
return fromEntity(player, WrappedServerPlayer.class);
|
||||
}
|
||||
|
||||
public static @NotNull WrappedServerPlayer create(@NotNull Location location, @NotNull UUID uuid, @NotNull WrappedComponent name)
|
||||
{
|
||||
Object mcServer = WrappedMinecraftServer.INSTANCE.getHandle();
|
||||
Object serverLevel = Var.getNmsLevel(location.getWorld());
|
||||
|
||||
GameProfile gameProfile = new GameProfile(uuid, "NPC" + uuid.toString().substring(0, 13));
|
||||
|
||||
Mapping[] annotations = WrappedServerPlayer.class.getAnnotationsByType(Mapping.class);
|
||||
|
||||
Class<?> targetClass = null;
|
||||
for(Mapping mapping : annotations)
|
||||
{
|
||||
if(!Versions.containsCurrentVersion(mapping))
|
||||
continue;
|
||||
|
||||
targetClass = getTargetClass(mapping);
|
||||
break;
|
||||
}
|
||||
|
||||
if(targetClass == null)
|
||||
throw new VersionNotFound(targetClass);
|
||||
|
||||
WrappedServerPlayer wrappedServerPlayer = switch(Versions.getVersion())
|
||||
{
|
||||
case V1_17, V1_18, V1_18_2, V1_19_3, V1_19_4, V1_20 ->
|
||||
createWrappedInstance(WrappedServerPlayer.class, mcServer, serverLevel, gameProfile);
|
||||
case V1_19, V1_19_1 -> createWrappedInstance(WrappedServerPlayer.class, mcServer, serverLevel, gameProfile, null);
|
||||
case V1_20_2, V1_20_4, V1_20_6, V1_21, V1_21_2, V1_21_4, V1_21_5 ->
|
||||
{
|
||||
WrappedServerPlayer serverPlayer = createWrappedInstance(WrappedServerPlayer.class, mcServer, serverLevel, gameProfile,
|
||||
WrappedClientInformation.createDefault().getHandle());
|
||||
|
||||
serverPlayer.setConnection(new WrappedConnection(WrappedMinecraftServer.INSTANCE,
|
||||
WrappedConnection.WrappedNetworkManager.create(WrappedConnection.WrappedNetworkManager.PacketFlow.CLIENTBOUND),
|
||||
serverPlayer, new WrappedConnection.CommonListenerCookie(gameProfile, 0, true)));
|
||||
|
||||
yield serverPlayer;
|
||||
}
|
||||
case NONE -> throw new VersionNotFound();
|
||||
};
|
||||
|
||||
WrappedArmorStand armorStand = WrappedArmorStand.create(location.getWorld());
|
||||
armorStand.moveTo(location.clone().add(0, 0.2, 0));
|
||||
wrappedServerPlayer.setNameTag(armorStand);
|
||||
|
||||
wrappedServerPlayer.setListName(name);
|
||||
return wrappedServerPlayer;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "g")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "f")
|
||||
public @NotNull WrappedAttributeInstance getAttribute(Object attribute)
|
||||
{
|
||||
return new WrappedAttributeInstance(invokeWrappedMethod(attribute));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "gi")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_4), path = "gh")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_21), path = "fX")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_6), path = "gb")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "fR")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "fQ")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20), path = "fM")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_4), path = "fI")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_3), path = "fD")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_1), path = "fy")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19), path = "fz")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_18_2), path = "fq")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_18), path = "fp")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getProfile")
|
||||
public @NotNull GameProfile getGameProfile()
|
||||
{
|
||||
return invokeWrappedMethod();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20, to = Versions.V1_21), path = "c")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_4), path = "b")
|
||||
public @NotNull WrappedConnection playerConnection()
|
||||
{
|
||||
return new WrappedConnection(getWrappedFieldValue());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20, to = Versions.V1_21), path = "c")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_4), path = "b")
|
||||
public void setConnection(@NotNull WrappedConnection newConnection)
|
||||
{
|
||||
setWrappedFieldValue(newConnection.getHandle());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "listName")
|
||||
public void setListName(@Nullable WrappedComponent component)
|
||||
{
|
||||
setWrappedFieldValue(component.getHandle());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "listName")
|
||||
public void setListName(@Nullable String name)
|
||||
{
|
||||
setListName(WrappedComponent.create(name));
|
||||
}
|
||||
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_4), path = "e")
|
||||
public void setLatency(int i)
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_20_2))
|
||||
setWrappedFieldValue(i);
|
||||
else
|
||||
setConnection(new WrappedConnection(WrappedMinecraftServer.INSTANCE, playerConnection().networkManager(), this,
|
||||
new WrappedConnection.CommonListenerCookie(getGameProfile(), i, true)));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_21_5), path = "b")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "setPose")
|
||||
public void setPose(@NotNull Pose pose)
|
||||
{
|
||||
invokeWrappedMethod(pose);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_5, to = Versions.V1_21_5), path = "z")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_4), path = "A")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21), path = "dg")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_4), path = "dd")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20_2), path = "dc")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_20), path = "da")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_4), path = "cZ")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_3), path = "cY")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19_1), path = "cT")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_19), path = "cU")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_18, to = Versions.V1_18_2), path = "cQ")
|
||||
@Mapping(fixed = @Mapping.Fixed(Versions.V1_17), path = "getCommandListener")
|
||||
public WrappedCommandSourceStack createCommandStack()
|
||||
{
|
||||
return new WrappedCommandSourceStack(invokeWrappedMethod());
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19, to = Versions.V1_21_5), path = "a")
|
||||
public void sendRawMessage(@NotNull WrappedComponent component)
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_19))
|
||||
sendPacket(new ChatPacket(component, ChatPacket.ChatType.CHAT));
|
||||
else
|
||||
invokeWrappedMethod(component);
|
||||
}
|
||||
|
||||
public @NotNull String getName()
|
||||
{
|
||||
return getGameProfile().getName();
|
||||
}
|
||||
|
||||
public void setName(@NotNull String name)
|
||||
{
|
||||
Reflections.setField(getGameProfile(), "name", name);
|
||||
}
|
||||
|
||||
public @NotNull UUID getUUID()
|
||||
{
|
||||
return getGameProfile().getId();
|
||||
}
|
||||
|
||||
public boolean isOnGround()
|
||||
{
|
||||
return ((Entity) getBukkitPlayer()).isOnGround();
|
||||
}
|
||||
|
||||
public void sendPacket(@NotNull PacketWrapper packet)
|
||||
{
|
||||
if(packet instanceof BundlePacket bundlePacket)
|
||||
Arrays.stream(bundlePacket.getPackets()).forEach(this::sendPacket);
|
||||
else
|
||||
playerConnection().sendPacket(packet);
|
||||
}
|
||||
|
||||
public WrappedArmorStand getNameTag()
|
||||
{
|
||||
return armorStand;
|
||||
}
|
||||
|
||||
public void setNameTag(WrappedArmorStand armorStand)
|
||||
{
|
||||
setPassengers(armorStand);
|
||||
this.armorStand = armorStand;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game.PacketPlayOutAnimation")
|
||||
public class AnimatePacket extends PacketWrapper
|
||||
{
|
||||
protected AnimatePacket(@NotNull WrappedServerPlayer player, int animationId)
|
||||
{
|
||||
super(AnimatePacket.class, player, animationId);
|
||||
}
|
||||
|
||||
public static PacketWrapper create(@NotNull WrappedServerPlayer player, @NotNull Animation animation)
|
||||
{
|
||||
if(animation != Animation.HURT || Versions.isCurrentVersionSmallerThan(Versions.V1_19_4))
|
||||
return new AnimatePacket(player, animation.ordinal());
|
||||
|
||||
return new HurtAnimationPacket(player);
|
||||
}
|
||||
|
||||
public enum Animation implements Serializable
|
||||
{
|
||||
SWING_MAIN_HAND,
|
||||
HURT,
|
||||
WAKE_UP,
|
||||
SWING_OFF_HAND,
|
||||
CRITICAL_HIT,
|
||||
MAGIC_CRITICAL_HIT
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_4, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket")
|
||||
public static class HurtAnimationPacket extends PacketWrapper
|
||||
{
|
||||
private HurtAnimationPacket(@NotNull WrappedServerPlayer player)
|
||||
{
|
||||
super(HurtAnimationPacket.class, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BundlePacket extends PacketWrapper
|
||||
{
|
||||
private final PacketWrapper[] packets;
|
||||
|
||||
public BundlePacket(@NotNull PacketWrapper... packets)
|
||||
{
|
||||
super(null);
|
||||
this.packets = packets;
|
||||
}
|
||||
|
||||
public @NotNull PacketWrapper[] getPackets()
|
||||
{
|
||||
return packets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "net.minecraft.network.protocol.game.PacketPlayOutChat")
|
||||
public class ChatPacket extends PacketWrapper
|
||||
{
|
||||
private static final UUID NULL_UUID = new UUID(0, 0);
|
||||
|
||||
public ChatPacket(@NotNull WrappedComponent component, @NotNull ChatType type)
|
||||
{
|
||||
super(ChatPacket.class, component, type, NULL_UUID);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "net.minecraft.network.chat.ChatMessageType")
|
||||
public enum ChatType implements EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "a")
|
||||
CHAT,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "b")
|
||||
SYSTEM,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_18_2), path = "c")
|
||||
GAME_INFO;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
public class CustomPacket extends PacketWrapper.PacketHolder
|
||||
{
|
||||
public CustomPacket(Object packet)
|
||||
{
|
||||
super(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game.PacketPlayOutEntity")
|
||||
public class MoveEntityPacket
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutEntity$PacketPlayOutEntityLook")
|
||||
public static class Rot extends PacketWrapper
|
||||
{
|
||||
public Rot(int entityId, byte yRot, byte xRot, boolean onGround)
|
||||
{
|
||||
super(Rot.class, entityId, yRot, xRot, onGround);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class PacketWrapper extends Wrapper
|
||||
{
|
||||
protected PacketWrapper(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public <T extends Wrapper> PacketWrapper(@NotNull Class<T> clazz, Object... args)
|
||||
{
|
||||
super(createInstance(clazz, args));
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.Packet")
|
||||
public static abstract class PacketHolder extends PacketWrapper
|
||||
{
|
||||
public PacketHolder(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
public static boolean is(@NotNull Object packet, @NotNull Class<? extends PacketHolder> clazz)
|
||||
{
|
||||
return Arrays.stream(clazz.getAnnotationsByType(Mapping.class))
|
||||
.filter(Versions::containsCurrentVersion)
|
||||
.findFirst()
|
||||
.filter(data -> getTargetClass(data) != null)
|
||||
.map(data -> getTargetClass(data).isAssignableFrom(packet.getClass())).orElse(false);
|
||||
}
|
||||
|
||||
public static <T extends PacketHolder> @NotNull T wrap(@NotNull Object packet, @NotNull Class<T> clazz)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor<T> constructor = clazz.getDeclaredConstructor(Object.class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(packet);
|
||||
} catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".ClientboundPlayerInfoRemovePacket")
|
||||
public class PlayerInfoRemovePacket extends PacketWrapper
|
||||
{
|
||||
public PlayerInfoRemovePacket(@NotNull List<UUID> list)
|
||||
{
|
||||
super(PlayerInfoRemovePacket.class, list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".ClientboundPlayerInfoUpdatePacket")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo")
|
||||
public class PlayerInfoUpdatePacket extends PacketWrapper
|
||||
{
|
||||
public PlayerInfoUpdatePacket(@NotNull Action action, @NotNull WrappedServerPlayer serverPlayer)
|
||||
{
|
||||
super(PlayerInfoUpdatePacket.class, action, serverPlayer);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".ClientboundPlayerInfoUpdatePacket$a")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutPlayerInfo$EnumPlayerInfoAction")
|
||||
public enum Action implements EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
ADD_PLAYER,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "b")
|
||||
INITIALIZE_CHAT,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "c")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "b")
|
||||
UPDATE_GAME_MODE,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "d")
|
||||
UPDATE_LISTED,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "e")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "c")
|
||||
UPDATE_LATENCY,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "f")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "d")
|
||||
UPDATE_DISPLAY_NAME,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "e")
|
||||
REMOVE_PLAYER,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_2, to = Versions.V1_21_5), path = "g")
|
||||
UPDATE_LIST_ORDER,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21_4, to = Versions.V1_21_5), path = "h")
|
||||
UPDATE_HAT;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutEntityDestroy")
|
||||
public class RemoveEntityPacket extends PacketWrapper
|
||||
{
|
||||
public RemoveEntityPacket(int entityId)
|
||||
{
|
||||
super(RemoveEntityPacket.class, entityId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutEntityHeadRotation")
|
||||
public class RotateHeadPacket extends PacketWrapper
|
||||
{
|
||||
public RotateHeadPacket(@NotNull WrappedServerPlayer serverPlayer, byte yHeadRot)
|
||||
{
|
||||
super(RotateHeadPacket.class, serverPlayer, yHeadRot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedEntityData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutEntityMetadata")
|
||||
public class SetEntityDataPacket extends PacketWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_19_1), path = "")
|
||||
private SetEntityDataPacket(int id, @NotNull WrappedEntityData data, boolean dirty)
|
||||
{
|
||||
super(SetEntityDataPacket.class, id, data, dirty);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_19_3, to = Versions.V1_21_5), path = "")
|
||||
private SetEntityDataPacket(int id, @NotNull List<?> data)
|
||||
{
|
||||
super(SetEntityDataPacket.class, id, data);
|
||||
}
|
||||
|
||||
public static SetEntityDataPacket create(int id, @NotNull WrappedEntityData data, boolean nonDefaults)
|
||||
{
|
||||
if(Versions.isCurrentVersionSmallerThan(Versions.V1_19_3))
|
||||
return new SetEntityDataPacket(id, data, nonDefaults);
|
||||
else
|
||||
{
|
||||
List<?> dataList = nonDefaults ? data.getAll() : data.packDirty();
|
||||
return new SetEntityDataPacket(id, dataList == null ? new ArrayList<>() : dataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutEntityEquipment")
|
||||
public class SetEquipmentPacket extends PacketWrapper
|
||||
{
|
||||
public SetEquipmentPacket(int entity, @NotNull List<Pair<?, ?>> list)
|
||||
{
|
||||
super(SetEquipmentPacket.class, entity, list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedEntity;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game.PacketPlayOutMount")
|
||||
public class SetPassengerPacket extends PacketWrapper
|
||||
{
|
||||
public SetPassengerPacket(WrappedEntity<?> entity)
|
||||
{
|
||||
super(SetPassengerPacket.class, entity.getHandle());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedPlayerTeam;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutScoreboardTeam")
|
||||
public class SetPlayerTeamPacket extends PacketWrapper
|
||||
{
|
||||
private SetPlayerTeamPacket(@NotNull String teamName, int method, @NotNull Optional<Object> parameter, @NotNull Collection<String> players)
|
||||
{
|
||||
super(SetPlayerTeamPacket.class, teamName, method, parameter, players);
|
||||
}
|
||||
|
||||
public static SetPlayerTeamPacket createAddOrModifyPacket(@NotNull WrappedPlayerTeam team, boolean create)
|
||||
{
|
||||
return new SetPlayerTeamPacket(team.getName(), create ? 0 : 2, Optional.of(new Parameters(team).getHandle()),
|
||||
(create ? team.getPlayers() : ImmutableList.of()));
|
||||
}
|
||||
|
||||
public static SetPlayerTeamPacket createRemovePacket(@NotNull WrappedPlayerTeam team)
|
||||
{
|
||||
return new SetPlayerTeamPacket(team.getName(), 1, Optional.empty(), ImmutableList.of());
|
||||
}
|
||||
|
||||
public static SetPlayerTeamPacket createPlayerPacket(@NotNull WrappedPlayerTeam team, @NotNull String playerName, @NotNull Action action)
|
||||
{
|
||||
return new SetPlayerTeamPacket(team.getName(), action == Action.ADD ? 3 : 4, Optional.empty(), ImmutableList.of(playerName));
|
||||
}
|
||||
|
||||
public enum Action
|
||||
{
|
||||
ADD,
|
||||
REMOVE
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayOutScoreboardTeam$b")
|
||||
public static class Parameters extends Wrapper
|
||||
{
|
||||
public Parameters(@NotNull WrappedPlayerTeam team)
|
||||
{
|
||||
super(createInstance(Parameters.class, team.getHandle()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.objects.WrappedAttributeInstance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_20_6, to = Versions.V1_21_5),
|
||||
path = "net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes")
|
||||
public class UpdateAttributesPacket extends PacketWrapper
|
||||
{
|
||||
public UpdateAttributesPacket(int id, WrappedAttributeInstance instance)
|
||||
{
|
||||
super(UpdateAttributesPacket.class, id, List.of(instance.getHandle()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package de.eisi05.npc.api.wrapper.packets;
|
||||
|
||||
import de.eisi05.npc.api.utils.Versions;
|
||||
import de.eisi05.npc.api.wrapper.Mapping;
|
||||
import de.eisi05.npc.api.wrapper.Wrapper;
|
||||
import de.eisi05.npc.api.wrapper.enums.InteractionHand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game.PacketPlayInUseEntity")
|
||||
public class UseEntityPacketWrapper extends PacketWrapper.PacketHolder
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "e")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_20_6), path = "d")
|
||||
public final static Action ATTACK_ACTION = new Action(getStaticWrappedFieldValue("ATTACK_ACTION").orElse(null));
|
||||
|
||||
protected UseEntityPacketWrapper(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "b")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_20_6), path = "a")
|
||||
public int getId()
|
||||
{
|
||||
return getWrappedFieldValue();
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_21, to = Versions.V1_21_5), path = "c")
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_20_6), path = "b")
|
||||
public @NotNull Action getAction()
|
||||
{
|
||||
Object value = getWrappedFieldValue();
|
||||
|
||||
if(value.equals(ATTACK_ACTION.getHandle()))
|
||||
return ATTACK_ACTION;
|
||||
|
||||
return new Action(value);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayInUseEntity$b")
|
||||
public enum ActionType implements EnumWrapper
|
||||
{
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
INTERACT,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "b")
|
||||
ATTACK,
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "c")
|
||||
INTERACT_AT;
|
||||
|
||||
@Override
|
||||
public @NotNull Object getHandle()
|
||||
{
|
||||
return cast(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "net.minecraft.network.protocol.game" +
|
||||
".PacketPlayInUseEntity$EnumEntityUseAction")
|
||||
public static class Action extends Wrapper
|
||||
{
|
||||
public Action(Object handle)
|
||||
{
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public @Nullable ActionType getActionType()
|
||||
{
|
||||
Object enumHandle = invokeWrappedMethod();
|
||||
for(ActionType type : ActionType.values())
|
||||
{
|
||||
if(type.getHandle().equals(enumHandle))
|
||||
return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Mapping(range = @Mapping.Range(from = Versions.V1_17, to = Versions.V1_21_5), path = "a")
|
||||
public @Nullable InteractionHand getHand()
|
||||
{
|
||||
try
|
||||
{
|
||||
Object enumHandle = getWrappedFieldValue();
|
||||
for(InteractionHand type : InteractionHand.values())
|
||||
{
|
||||
if(type.getHandle().equals(enumHandle))
|
||||
return type;
|
||||
}
|
||||
return null;
|
||||
} catch(RuntimeException e)
|
||||
{
|
||||
if(e.getCause() instanceof RuntimeException ex && ex.getCause() instanceof NoSuchFieldException)
|
||||
return null;
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user