From bfc96e1ea80fa1811f01f6cc76ffd78d4975a3db Mon Sep 17 00:00:00 2001 From: Weever <109693401+Weever1337@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:09:23 +0300 Subject: [PATCH 1/2] feat: add API for addons, moved our-NMS to "versions" folder, a bit refactor and fixes - add `org.vmstudio.visor.api` (VisorAPI, VisorPlayer/VRPlayer,pose/history/trackers, join/leave events, network channels + NMS). I tried to copy Visor's client-side API namings - move NMS modules to versions/; rename VersionAdapter -> VisorVersionAdapter (for our implementations), etc. - fix default settings to match Visor's one (betterSwinging/roomCrawling/roomClimbing/trackers off, supportedMovement CONTROLLER) - fix vr-only servers kicking VR players before the handshake completes --- README.md | 39 +- build.gradle | 27 +- lombok.config | 2 + settings.gradle | 26 +- .../java/org/vmstudio/visor/api/VisorAPI.java | 143 ++++++++ .../api/event/VisorPlayerJoinedEvent.java | 36 ++ .../visor/api/event/VisorPlayerLeftEvent.java | 36 ++ .../vmstudio/visor/api/network/VisorBuf.java | 340 ++++++++++++++++++ .../visor/api/network/VisorChannel.java | 53 +++ .../api/network/VisorChannelHandler.java | 11 + .../visor/api/network/VisorPacket.java | 6 + .../visor/{ => api}/nms/McVersion.java | 2 +- .../vmstudio/visor/api/nms/VersionedNms.java | 32 ++ .../vmstudio/visor/api/player/HandType.java | 21 ++ .../player}/SupportedMovement.java | 2 +- .../visor/api/player/VRBodyPartType.java | 40 +++ .../vmstudio/visor/api/player/VRPlayer.java | 108 ++++++ .../visor/api/player/VRPlayerPose.java | 134 +++++++ .../org/vmstudio/visor/api/player/VRPose.java | 14 + .../visor/api/player/VRPoseHistory.java | 113 ++++++ .../visor/api/player/VRPoseTrackers.java | 30 ++ .../visor/api/player/VisorPlayer.java | 27 ++ .../visor/bukkit/VisorBukkitPlugin.java | 13 +- .../visor/bukkit/adapter/PaperApiAdapter.java | 5 +- .../adapter/VersionAdapterRegistry.java | 83 ++--- .../adapter/VisorVersionAdapter.java} | 14 +- .../visor/bukkit/api/PoseHistoryImpl.java | 163 +++++++++ .../vmstudio/visor/bukkit/api/PoseMath.java | 40 +++ .../visor/bukkit/api/PoseTrackersImpl.java | 122 +++++++ .../visor/bukkit/api/VRPlayerImpl.java | 133 +++++++ .../visor/bukkit/api/VRPlayerPoseImpl.java | 179 +++++++++ .../visor/bukkit/api/VisorAPIImpl.java | 129 +++++++ .../visor/bukkit/api/VisorChannelImpl.java | 116 ++++++ .../visor/bukkit/api/VisorPlayerImpl.java | 42 +++ .../bukkit/platform/BukkitPlatformPlayer.java | 15 + .../bukkit/platform/BukkitPlatformServer.java | 10 +- .../vmstudio/visor/common/VisorServer.java | 4 +- .../common/broadcast/TrackerBroadcaster.java | 50 +-- .../visor/common/handler/InboundHandler.java | 3 +- .../visor/common/platform/PlatformPlayer.java | 6 + .../common/session/PoseHistoryBuffer.java | 33 ++ .../visor/common/session/SessionManager.java | 16 +- .../visor/common/session/VrState.java | 155 +------- .../visor/common/settings/SettingsLoader.java | 13 +- .../visor/common/settings/VisorSettings.java | 14 +- .../nms-v1_20_R1}/build.gradle | 0 .../nms/v1_20_R1/NmsV1_20_R1Adapter.java | 6 +- .../nms-v1_21_1}/build.gradle | 0 .../visor/nms/v1_21_1/NmsV1_21_1Adapter.java | 7 +- .../nms-v1_21_10}/build.gradle | 0 .../nms/v1_21_10/NmsV1_21_10Adapter.java | 7 +- .../nms-v1_21_11}/build.gradle | 0 .../nms/v1_21_11/NmsV1_21_11Adapter.java | 7 +- .../nms-v1_21_3}/build.gradle | 0 .../visor/nms/v1_21_3/NmsV1_21_3Adapter.java | 7 +- .../nms-v1_21_4}/build.gradle | 0 .../visor/nms/v1_21_4/NmsV1_21_4Adapter.java | 7 +- .../nms-v1_21_5}/build.gradle | 0 .../visor/nms/v1_21_5/NmsV1_21_5Adapter.java | 7 +- .../nms-v1_21_6}/build.gradle | 0 .../visor/nms/v1_21_6/NmsV1_21_6Adapter.java | 7 +- .../nms-v1_21_7}/build.gradle | 0 .../visor/nms/v1_21_7/NmsV1_21_7Adapter.java | 7 +- .../nms-v1_21_8}/build.gradle | 0 .../visor/nms/v1_21_8/NmsV1_21_8Adapter.java | 7 +- .../nms-v1_21_9}/build.gradle | 0 .../visor/nms/v1_21_9/NmsV1_21_9Adapter.java | 7 +- 67 files changed, 2333 insertions(+), 343 deletions(-) create mode 100644 lombok.config create mode 100644 src/main/java/org/vmstudio/visor/api/VisorAPI.java create mode 100644 src/main/java/org/vmstudio/visor/api/event/VisorPlayerJoinedEvent.java create mode 100644 src/main/java/org/vmstudio/visor/api/event/VisorPlayerLeftEvent.java create mode 100644 src/main/java/org/vmstudio/visor/api/network/VisorBuf.java create mode 100644 src/main/java/org/vmstudio/visor/api/network/VisorChannel.java create mode 100644 src/main/java/org/vmstudio/visor/api/network/VisorChannelHandler.java create mode 100644 src/main/java/org/vmstudio/visor/api/network/VisorPacket.java rename src/main/java/org/vmstudio/visor/{ => api}/nms/McVersion.java (96%) create mode 100644 src/main/java/org/vmstudio/visor/api/nms/VersionedNms.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/HandType.java rename src/main/java/org/vmstudio/visor/{common/settings => api/player}/SupportedMovement.java (62%) create mode 100644 src/main/java/org/vmstudio/visor/api/player/VRBodyPartType.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/VRPlayer.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/VRPlayerPose.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/VRPose.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/VRPoseHistory.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/VRPoseTrackers.java create mode 100644 src/main/java/org/vmstudio/visor/api/player/VisorPlayer.java rename src/main/java/org/vmstudio/visor/{nms/VersionAdapter.java => bukkit/adapter/VisorVersionAdapter.java} (69%) create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/PoseHistoryImpl.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/PoseMath.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/PoseTrackersImpl.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerImpl.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerPoseImpl.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/VisorAPIImpl.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/VisorChannelImpl.java create mode 100644 src/main/java/org/vmstudio/visor/bukkit/api/VisorPlayerImpl.java create mode 100644 src/main/java/org/vmstudio/visor/common/session/PoseHistoryBuffer.java rename {nms-v1_20_R1 => versions/nms-v1_20_R1}/build.gradle (100%) rename {nms-v1_20_R1 => versions/nms-v1_20_R1}/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java (98%) rename {nms-v1_21_1 => versions/nms-v1_21_1}/build.gradle (100%) rename {nms-v1_21_1 => versions/nms-v1_21_1}/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java (97%) rename {nms-v1_21_10 => versions/nms-v1_21_10}/build.gradle (100%) rename {nms-v1_21_10 => versions/nms-v1_21_10}/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java (97%) rename {nms-v1_21_11 => versions/nms-v1_21_11}/build.gradle (100%) rename {nms-v1_21_11 => versions/nms-v1_21_11}/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java (97%) rename {nms-v1_21_3 => versions/nms-v1_21_3}/build.gradle (100%) rename {nms-v1_21_3 => versions/nms-v1_21_3}/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java (97%) rename {nms-v1_21_4 => versions/nms-v1_21_4}/build.gradle (100%) rename {nms-v1_21_4 => versions/nms-v1_21_4}/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java (97%) rename {nms-v1_21_5 => versions/nms-v1_21_5}/build.gradle (100%) rename {nms-v1_21_5 => versions/nms-v1_21_5}/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java (97%) rename {nms-v1_21_6 => versions/nms-v1_21_6}/build.gradle (100%) rename {nms-v1_21_6 => versions/nms-v1_21_6}/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java (97%) rename {nms-v1_21_7 => versions/nms-v1_21_7}/build.gradle (100%) rename {nms-v1_21_7 => versions/nms-v1_21_7}/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java (97%) rename {nms-v1_21_8 => versions/nms-v1_21_8}/build.gradle (100%) rename {nms-v1_21_8 => versions/nms-v1_21_8}/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java (97%) rename {nms-v1_21_9 => versions/nms-v1_21_9}/build.gradle (100%) rename {nms-v1_21_9 => versions/nms-v1_21_9}/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java (97%) diff --git a/README.md b/README.md index ea56b19..c3c142a 100644 --- a/README.md +++ b/README.md @@ -41,23 +41,24 @@ Permission: **`visor.admin`** (default: ops) ## Configuration `plugins/VisorPlugin/server_settings.yml`: -| Key | Default | Description | -|-----|---------|-------------------------------------------------------------------------| -| `serverDebug` | `false` | Verbose logging of Visor | -| `vrOnly` | `false` | Kick non-VR players (ops exempt) | -| `twoHandedVR` | `false` | Enable the off-hand slot for two-handed VR | -| `betterSwinging` | `true` | VR-accurate melee attacks and block breaking | -| `swingingRepairDelay` | `400` | Ticks before an unfinished VR block break "heals" | -| `roomCrawlingSupported` | `true` | Allow room-scale crawling (forced crawl/swim pose) | -| `roomClimbingSupported` | `true` | Allow room-scale climbing (resets fall distance) | -| `pvpVRvsVanilla` | `true` | Allow PvP between a VR player and a non-VR player | -| `pvpVRvsVR` | `true` | Allow PvP between two VR players | -| `notifyPvpBlocked` | `false` | Message the attacker when PvP is blocked | -| `creeperSwellDistance` | `1.75` | Creepers swell within this distance of a VR player's head. (`0` disables) | -| `supportedMovement` | `BOTH` | `CONTROLLER`, `TELEPORT`, or `BOTH` | -| `teleportUpLimit` | `1` | Teleport-move vertical-up limit (blocks) | -| `teleportDownLimit` | `4` | Teleport-move vertical-down limit (blocks) | -| `teleportForwardLimit` | `16` | Teleport-move forward limit (blocks) | +| Key | Default | Description | +|-------------------------|--------------|-------------------------------------------------------------------------| +| `serverDebug` | `false` | Verbose logging of Visor | +| `vrOnly` | `false` | Kick non-VR players (ops exempt) | +| `twoHandedVR` | `false` | Enable the off-hand slot for two-handed VR | +| `betterSwinging` | `false` | VR-accurate melee attacks and block breaking | +| `swingingRepairDelay` | `400` | Ticks before an unfinished VR block break "heals" | +| `roomCrawlingSupported` | `false` | Allow room-scale crawling (forced crawl/swim pose) | +| `roomClimbingSupported` | `false` | Allow room-scale climbing (resets fall distance) | +| `pvpVRvsVanilla` | `true` | Allow PvP between a VR player and a non-VR player | +| `pvpVRvsVR` | `true` | Allow PvP between two VR players | +| `notifyPvpBlocked` | `false` | Message the attacker when PvP is blocked | +| `creeperSwellDistance` | `1.75` | Creepers swell within this distance of a VR player's head. (`0` disables) | +| `supportedMovement` | `CONTROLLER` | `CONTROLLER`, `TELEPORT`, or `BOTH` | +| `teleportUpLimit` | `1` | Teleport-move vertical-up limit (blocks) | +| `teleportDownLimit` | `4` | Teleport-move vertical-down limit (blocks) | +| `teleportForwardLimit` | `16` | Teleport-move forward limit (blocks) | +| `trackersSupported` | `false` | Allow to use trackers | ```bash ./gradlew build @@ -66,8 +67,8 @@ Permission: **`visor.admin`** (default: ops) ### Adding support for another Minecraft version 1. Create an `nms-vXXX` module applying `paperweight-userdev` with that version's dev bundle -2. Implement `VersionAdapter` (copy an existing leaf; fix any API differences) -3. Register the class in `VersionAdapterRegistry.NMS_LEAVES` and add `runtimeOnly project(path: ':nms-vXXX', configuration: 'reobf')` to the root build +2. Implement `VisorVersionAdapter` (copy an existing leaf; fix any API differences) +3. Register the class in `VersionAdapterRegistry.NMS_LEAVES` and add `runtimeOnly project(path: ':versions:nms-vXXX', configuration: 'reobf')` to the root build ## Notes - **ViaVersion** is supported for cross-version play: `visor:channel` carries a version-independent byte diff --git a/build.gradle b/build.gradle index 3cfbfdf..4942159 100644 --- a/build.gradle +++ b/build.gradle @@ -10,17 +10,22 @@ dependencies { compileOnly 'io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT' implementation 'org.yaml:snakeyaml:2.3' - runtimeOnly project(path: ':nms-v1_20_R1', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_1', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_3', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_4', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_5', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_6', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_7', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_8', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_9', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_10', configuration: 'reobf') - runtimeOnly project(path: ':nms-v1_21_11', configuration: 'reobf') + compileOnly 'org.projectlombok:lombok:1.18.38' + annotationProcessor 'org.projectlombok:lombok:1.18.38' + testCompileOnly 'org.projectlombok:lombok:1.18.38' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.38' + + runtimeOnly project(path: ':versions:nms-v1_20_R1', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_1', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_3', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_4', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_5', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_6', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_7', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_8', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_9', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_10', configuration: 'reobf') + runtimeOnly project(path: ':versions:nms-v1_21_11', configuration: 'reobf') testImplementation platform('org.junit:junit-bom:5.11.3') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..df71bb6 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/settings.gradle b/settings.gradle index a174f0f..a54becd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,14 +8,18 @@ dependencyResolutionManagement { } } -include 'nms-v1_20_R1' -include 'nms-v1_21_1' -include 'nms-v1_21_3' -include 'nms-v1_21_4' -include 'nms-v1_21_5' -include 'nms-v1_21_6' -include 'nms-v1_21_7' -include 'nms-v1_21_8' -include 'nms-v1_21_9' -include 'nms-v1_21_10' -include 'nms-v1_21_11' +[ + 'nms-v1_20_R1', + 'nms-v1_21_1', + 'nms-v1_21_3', + 'nms-v1_21_4', + 'nms-v1_21_5', + 'nms-v1_21_6', + 'nms-v1_21_7', + 'nms-v1_21_8', + 'nms-v1_21_9', + 'nms-v1_21_10', + 'nms-v1_21_11' +].each { name -> + include "versions:$name" +} diff --git a/src/main/java/org/vmstudio/visor/api/VisorAPI.java b/src/main/java/org/vmstudio/visor/api/VisorAPI.java new file mode 100644 index 0000000..b836d29 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/VisorAPI.java @@ -0,0 +1,143 @@ +package org.vmstudio.visor.api; + +import java.util.Collection; +import java.util.UUID; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.network.VisorChannel; +import org.vmstudio.visor.api.network.VisorChannelHandler; +import org.vmstudio.visor.api.player.VRPlayer; +import org.vmstudio.visor.api.player.VisorPlayer; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.protocol.VisorProtocol; + +/** + * Central access point for all Visor Plugin API functionality + */ +public interface VisorAPI { + /** + * Get Visor API + * + * @return Visor API + */ + static VisorAPI get(){ + if(Instance.api == null){ + throw new IllegalStateException("VisorAPI is not available. Add [VisorPlugin] to depends in plugin.yml"); + } + return Instance.api; + } + + /** + * Visor core network/protocol version + * + * @return Network version from {@link VisorProtocol} + */ + static int networkVersion(){ + return VisorProtocol.CORE_NETWORK_VERSION; + } + + /** + * Minecraft version this server runs + * + * @return Minecraft version + */ + McVersion minecraftVersion(); + + /** + * If there is a VR player with specified uuid + * + * @return true/false + */ + boolean isVRPlayer(UUID uuid); + + /** + * If specified player has VR + * + * @return true/false + */ + boolean isVRPlayer(Player player); + + /** + * Get Visor player with specified uuid. + * + * @return Visor player or null if not found + */ + @Nullable + VisorPlayer getVisorPlayer(UUID uuid); + + /** + * Get Visor player from MC player. + * + * @return Visor player or null if not found + */ + @Nullable + VisorPlayer getVisorPlayer(Player player); + + /** + * Get all Visor players on server. + * @return Visor players + */ + Collection getVisorPlayers(); + + /** + * Get player in VR with specified uuid. + * + * @return VR player or null if not found + */ + @Nullable + VRPlayer getVRPlayer(UUID uuid); + + /** + * Get player in VR from MC player. + * + * @return VR player or null if not found + */ + @Nullable + VRPlayer getVRPlayer(Player player); + + /** + * Get Visor player from MC player. + * + * @return Visor player or null if not found + */ + Collection getVRPlayers(); + + /** + * Register a custom channel for payloads with the matching Visor addon. + * Use this overload when you only send and receive typed + * packets via {@link VisorChannel#on} and {@link VisorChannel#send} + * + * @param owner the plugin that registering channel + * @param channel channel name in {@code namespace:path} form (like {@code "myaddon:hello"}) + * @return registered channel + */ + VisorChannel registerChannel(Plugin owner, String channel); + + /** + * Register a custom channel with a raw handler. + * This is like {@link #registerChannel(Plugin, String)} but any inbound + * message whose first byte does not match a typed packet registered with + * {@link VisorChannel#on} is sending to {@code handler} with raw bytes + * + * @param owner the plugin that registering channel + * @param channel channel name in {@code namespace:path} form (like {@code "myaddon:hello"}) + * @param handler fallback for payloads the typed packet layer does not handle + * @return registered channel + */ + VisorChannel registerChannel(Plugin owner, String channel, VisorChannelHandler handler); + + @ApiStatus.Internal + final class Instance { + private static VisorAPI api; + + private Instance(){} + + @ApiStatus.Internal + public static void set(VisorAPI api){ + Instance.api = api; + } + } +} diff --git a/src/main/java/org/vmstudio/visor/api/event/VisorPlayerJoinedEvent.java b/src/main/java/org/vmstudio/visor/api/event/VisorPlayerJoinedEvent.java new file mode 100644 index 0000000..c1e65c1 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/event/VisorPlayerJoinedEvent.java @@ -0,0 +1,36 @@ +package org.vmstudio.visor.api.event; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.vmstudio.visor.api.player.VisorPlayer; + +/** + * Fired when a player with the Visor mod joins (after the handshake), VR or not - see {@link #isVRPlayer()} + */ +public final class VisorPlayerJoinedEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final VisorPlayer visorPlayer; + + public VisorPlayerJoinedEvent(VisorPlayer visorPlayer){ + this.visorPlayer = visorPlayer; + } + + public VisorPlayer getVisorPlayer(){ + return visorPlayer; + } + + public boolean isVRPlayer(){ + return this.visorPlayer.isVR(); + } + + @Override + public @NotNull HandlerList getHandlers(){ + return handlers; + } + + public static HandlerList getHandlerList(){ + return handlers; + } +} diff --git a/src/main/java/org/vmstudio/visor/api/event/VisorPlayerLeftEvent.java b/src/main/java/org/vmstudio/visor/api/event/VisorPlayerLeftEvent.java new file mode 100644 index 0000000..dffa92b --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/event/VisorPlayerLeftEvent.java @@ -0,0 +1,36 @@ +package org.vmstudio.visor.api.event; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.vmstudio.visor.api.player.VisorPlayer; + +/** + * Fired when a player with the Visor mod disconnects, VR or not - see {@link #isVRPlayer()} + */ +public final class VisorPlayerLeftEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final VisorPlayer visorPlayer; + + public VisorPlayerLeftEvent(VisorPlayer vrPlayer){ + this.visorPlayer = vrPlayer; + } + + public VisorPlayer getVisorPlayer(){ + return visorPlayer; + } + + public boolean isVRPlayer(){ + return this.visorPlayer.isVR(); + } + + @Override + public @NotNull HandlerList getHandlers(){ + return handlers; + } + + public static HandlerList getHandlerList(){ + return handlers; + } +} diff --git a/src/main/java/org/vmstudio/visor/api/network/VisorBuf.java b/src/main/java/org/vmstudio/visor/api/network/VisorBuf.java new file mode 100644 index 0000000..37b89fa --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/network/VisorBuf.java @@ -0,0 +1,340 @@ +package org.vmstudio.visor.api.network; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.UUID; + +/** + * A buffer for reading and writing packet paylods + *

+ * Create a buffer with {@link #create()}, wrap received bytes with + * {@link #wrap(byte[])}, and extract written bytes with {@link #toBytes()} + */ +public final class VisorBuf { + private byte[] buf; + private int writerIndex; + private int readerIndex; + + private VisorBuf(byte[] data, int writerIndex){ + this.buf = data; + this.writerIndex = writerIndex; + } + + /** + * Create an empty buffer ready for writing + * + * @return a new writable buffer + */ + public static VisorBuf create(){ + return new VisorBuf(new byte[64], 0); + } + + /** + * Wrap existing bytes for reading (does not copy) + * + * @param data the bytes to read from + * @return a buffer positioned at the start of {@code data} + */ + public static VisorBuf wrap(byte[] data){ + return new VisorBuf(data, data.length); + } + + private void ensure(int extra){ + int required = writerIndex + extra; + if(required > buf.length){ + buf = Arrays.copyOf(buf, Math.max(buf.length * 2, required)); + } + } + + /** + * Write a single byte (only the low 8 bits of {@code value} are used) + * + * @param value byte value + * @return this buffer + */ + public VisorBuf writeByte(int value){ + ensure(1); + buf[writerIndex++] = (byte) value; + return this; + } + + /** + * Write a boolean as a single byte ({@code 1} or {@code 0}) + * + * @param value boolean value + * @return this buffer + */ + public VisorBuf writeBoolean(boolean value){ + return writeByte(value ? 1 : 0); + } + + /** + * Write a 32-bit int + * + * @param value int value + * @return this buffer + */ + public VisorBuf writeInt(int value){ + ensure(4); + buf[writerIndex++] = (byte) (value >>> 24); + buf[writerIndex++] = (byte) (value >>> 16); + buf[writerIndex++] = (byte) (value >>> 8); + buf[writerIndex++] = (byte) value; + return this; + } + + /** + * Write a variable-length int (7 bits per byte) + * + * @param value int value + * @return this buffer + */ + public VisorBuf writeVarInt(int value){ + while((value & ~0x7F) != 0){ + writeByte((value & 0x7F) | 0x80); + value >>>= 7; + } + return writeByte(value); + } + + /** + * Write a 64-bit long + * + * @param value long value + * @return this buffer + */ + public VisorBuf writeLong(long value){ + ensure(8); + for(int shift = 56; shift >= 0; shift -= 8){ + buf[writerIndex++] = (byte) (value >>> shift); + } + return this; + } + + /** + * Write a 32-bit float + * + * @param value float value + * @return this buffer + */ + public VisorBuf writeFloat(float value){ + return writeInt(Float.floatToIntBits(value)); + } + + /** + * Write a 64-bit double + * + * @param value double value + * @return this buffer + */ + public VisorBuf writeDouble(double value){ + return writeLong(Double.doubleToLongBits(value)); + } + + /** + * Write a UUID as two longs + * + * @param uuid uuid + * @return this buffer + */ + public VisorBuf writeUUID(UUID uuid){ + return writeLong(uuid.getMostSignificantBits()).writeLong(uuid.getLeastSignificantBits()); + } + + /** + * Write a UTF-8 string + * + * @param value string + * @return this buffer + */ + public VisorBuf writeString(String value){ + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + return writeVarInt(bytes.length).writeBytes(bytes); + } + + /** + * Write a byte array + * + * @param data bytes + * @return this buffer + */ + public VisorBuf writeByteArray(byte[] data){ + return writeVarInt(data.length).writeBytes(data); + } + + /** + * Write raw bytes with no length prefix + * + * @param data bytes + * @return this buffer + */ + public VisorBuf writeBytes(byte[] data){ + ensure(data.length); + System.arraycopy(data, 0, buf, writerIndex, data.length); + writerIndex += data.length; + return this; + } + + /** + * @return the number of bytes remaining to read + */ + public int readableBytes(){ + return writerIndex - readerIndex; + } + + private void requireReadable(int n){ + if(readerIndex + n > writerIndex){ + throw new IndexOutOfBoundsException( + "Visor buffer underflow: need " + n + " bytes, have " + readableBytes()); + } + } + + /** + * Read a single signed byte written by {@link #writeByte} + * + * @return the byte value + */ + public byte readByte(){ + requireReadable(1); + return buf[readerIndex++]; + } + + /** + * Read a single byte as an unsigned value + * + * @return the byte value in {@code 0..255} + */ + public int readUnsignedByte(){ + return readByte() & 0xFF; + } + + /** + * Read a boolean (any non-zero byte is {@code true}) written by {@link #writeBoolean} + * + * @return the boolean value + */ + public boolean readBoolean(){ + return readByte() != 0; + } + + /** + * Read a int written by {@link #writeInt} + * + * @return the int value + */ + public int readInt(){ + requireReadable(4); + return ((buf[readerIndex++] & 0xFF) << 24) + | ((buf[readerIndex++] & 0xFF) << 16) + | ((buf[readerIndex++] & 0xFF) << 8) + | (buf[readerIndex++] & 0xFF); + } + + /** + * Read a variable-length int written by {@link #writeVarInt} + * + * @return the int value + * @throws IllegalArgumentException if the encoding spans more than 5 bytes + */ + public int readVarInt(){ + int value = 0; + int position = 0; + byte b; + do { + b = readByte(); + value |= (b & 0x7F) << position; + position += 7; + if(position > 35){ + throw new IllegalArgumentException("VarInt too big"); + } + } while((b & 0x80) != 0); + return value; + } + + /** + * Read a long written by {@link #writeLong} + * + * @return the long value + */ + public long readLong(){ + requireReadable(8); + long result = 0L; + for(int i = 0; i < 8; i++){ + result = (result << 8) | (buf[readerIndex++] & 0xFFL); + } + return result; + } + + /** + * Read a float written by {@link #writeFloat} + * + * @return the float value + */ + public float readFloat(){ + return Float.intBitsToFloat(readInt()); + } + + /** + * Read a double written by {@link #writeDouble} + * + * @return the double value + */ + public double readDouble(){ + return Double.longBitsToDouble(readLong()); + } + + /** + * Read a UUID written by {@link #writeUUID} + * + * @return the uuid + */ + public UUID readUUID(){ + return new UUID(readLong(), readLong()); + } + + /** + * Read a UTF-8 string written by {@link #writeString} + * + * @return the string + */ + public String readString(){ + int len = readVarInt(); + requireReadable(len); + String s = new String(buf, readerIndex, len, StandardCharsets.UTF_8); + readerIndex += len; + return s; + } + + /** + * Read a byte array written by {@link #writeByteArray} + * + * @return a copy of the bytes + */ + public byte[] readByteArray(){ + int len = readVarInt(); + requireReadable(len); + byte[] out = Arrays.copyOfRange(buf, readerIndex, readerIndex + len); + readerIndex += len; + return out; + } + + /** + * Read everything left in the buffer + * + * @return a copy of the remaining bytes + */ + public byte[] readRemainingBytes(){ + byte[] out = Arrays.copyOfRange(buf, readerIndex, writerIndex); + readerIndex = writerIndex; + return out; + } + + /** + * Copy out the written bytes (from index {@code 0} to the writer position) + * + * @return the payload as a fresh array + */ + public byte[] toBytes(){ + return Arrays.copyOf(buf, writerIndex); + } +} diff --git a/src/main/java/org/vmstudio/visor/api/network/VisorChannel.java b/src/main/java/org/vmstudio/visor/api/network/VisorChannel.java new file mode 100644 index 0000000..c2bbc96 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/network/VisorChannel.java @@ -0,0 +1,53 @@ +package org.vmstudio.visor.api.network; + +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.bukkit.entity.Player; + +public interface VisorChannel { + /** + * @return channel name in {@code namespace:path} form + */ + String name(); + + /** + * Registers a typed inbound packet + * + * @param id packet discriminator, 0..255, matching {@link VisorPacket#id()} + * @param reader decodes the payload (id byte already consumed) into a packet + * @param handler receives the sending player and the decoded packet + * @return this channel, for chaining + * @throws IllegalArgumentException if {@code id} is outside 0..255 + */ + VisorChannel on(int id, Function reader, BiConsumer handler); + + /** + * Encodes and sends a typed packet to MC player + */ + void send(Player player, VisorPacket packet); + + /** + * Encodes and sends a typed packet to a player by uuid + * + * @param uuid player's uuid + * @param packet packet to send + */ + void send(UUID uuid, VisorPacket packet); + + /** + * Sends a raw payload + */ + void send(Player player, byte[] payload); + + /** + * Sends a raw payload to a player by uuid + */ + void send(UUID uuid, byte[] payload); + + /** + * Unregister this channel and its listeners + */ + void unregister(); +} diff --git a/src/main/java/org/vmstudio/visor/api/network/VisorChannelHandler.java b/src/main/java/org/vmstudio/visor/api/network/VisorChannelHandler.java new file mode 100644 index 0000000..1fb6303 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/network/VisorChannelHandler.java @@ -0,0 +1,11 @@ +package org.vmstudio.visor.api.network; + +import org.bukkit.entity.Player; + +/** + * Receives raw payloads a client sent on a registered {@link VisorChannel} + */ +@FunctionalInterface +public interface VisorChannelHandler { + void onMessage(Player sender, byte[] payload); +} \ No newline at end of file diff --git a/src/main/java/org/vmstudio/visor/api/network/VisorPacket.java b/src/main/java/org/vmstudio/visor/api/network/VisorPacket.java new file mode 100644 index 0000000..a886c84 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/network/VisorPacket.java @@ -0,0 +1,6 @@ +package org.vmstudio.visor.api.network; + +public interface VisorPacket { + int id(); + void write(VisorBuf buf); +} diff --git a/src/main/java/org/vmstudio/visor/nms/McVersion.java b/src/main/java/org/vmstudio/visor/api/nms/McVersion.java similarity index 96% rename from src/main/java/org/vmstudio/visor/nms/McVersion.java rename to src/main/java/org/vmstudio/visor/api/nms/McVersion.java index a84f1e2..f895c27 100644 --- a/src/main/java/org/vmstudio/visor/nms/McVersion.java +++ b/src/main/java/org/vmstudio/visor/api/nms/McVersion.java @@ -1,4 +1,4 @@ -package org.vmstudio.visor.nms; +package org.vmstudio.visor.api.nms; public record McVersion(int major, int minor, int patch, String raw){ public static McVersion parse(String bukkitVersion){ diff --git a/src/main/java/org/vmstudio/visor/api/nms/VersionedNms.java b/src/main/java/org/vmstudio/visor/api/nms/VersionedNms.java new file mode 100644 index 0000000..a184a9d --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/nms/VersionedNms.java @@ -0,0 +1,32 @@ +package org.vmstudio.visor.api.nms; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +public final class VersionedNms { + private VersionedNms(){} + + public record Target(String className, int major, int minor, int minPatch, int maxPatch){ + public boolean matches(McVersion v){ + return v.major() == major && v.minor() == minor + && v.patch() >= minPatch && v.patch() <= maxPatch; + } + } + + @Nullable + public static T resolve(Class type, McVersion version, List targets){ + for(Target target : targets){ + if(!target.matches(version)){ + continue; + } + try { + Object impl = Class.forName(target.className()).getDeclaredConstructor().newInstance(); + return type.cast(impl); + }catch (Throwable notUsable){ + return null; + } + } + return null; + } +} diff --git a/src/main/java/org/vmstudio/visor/api/player/HandType.java b/src/main/java/org/vmstudio/visor/api/player/HandType.java new file mode 100644 index 0000000..76cafd0 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/HandType.java @@ -0,0 +1,21 @@ +package org.vmstudio.visor.api.player; + +import org.jetbrains.annotations.NotNull; + +/** Which VR controller hand */ +public enum HandType { + MAIN, + OFFHAND; + + public @NotNull HandType opposite(){ + return this == MAIN ? OFFHAND : MAIN; + } + + public @NotNull VRBodyPartType asBodyPart(){ + return this == MAIN ? VRBodyPartType.MAIN_HAND : VRBodyPartType.OFFHAND; + } + + public static @NotNull HandType fromInt(int id){ + return id == 0 ? MAIN : OFFHAND; + } +} diff --git a/src/main/java/org/vmstudio/visor/common/settings/SupportedMovement.java b/src/main/java/org/vmstudio/visor/api/player/SupportedMovement.java similarity index 62% rename from src/main/java/org/vmstudio/visor/common/settings/SupportedMovement.java rename to src/main/java/org/vmstudio/visor/api/player/SupportedMovement.java index 9893145..d7ad27d 100644 --- a/src/main/java/org/vmstudio/visor/common/settings/SupportedMovement.java +++ b/src/main/java/org/vmstudio/visor/api/player/SupportedMovement.java @@ -1,4 +1,4 @@ -package org.vmstudio.visor.common.settings; +package org.vmstudio.visor.api.player; public enum SupportedMovement { CONTROLLER, diff --git a/src/main/java/org/vmstudio/visor/api/player/VRBodyPartType.java b/src/main/java/org/vmstudio/visor/api/player/VRBodyPartType.java new file mode 100644 index 0000000..fb44301 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VRBodyPartType.java @@ -0,0 +1,40 @@ +package org.vmstudio.visor.api.player; + +public enum VRBodyPartType { + /** + * HMD + */ + HEAD, + /** + * Controller Main ( leftHanded false = RIGHT, true = LEFT ) + */ + MAIN_HAND, + /** + * Controller Offhand ( leftHanded false = LEFT, true = RIGHT ) + */ + OFFHAND, + + // Body Main + WAIST, + CHEST, + + // Body Legs + LEFT_FOOT, + RIGHT_FOOT, + + LEFT_ANKLE, + RIGHT_ANKLE, + + LEFT_KNEE, + RIGHT_KNEE, + + // Body Arms + LEFT_WRIST, + RIGHT_WRIST, + + LEFT_ELBOW, + RIGHT_ELBOW, + + LEFT_SHOULDER, + RIGHT_SHOULDER, +} diff --git a/src/main/java/org/vmstudio/visor/api/player/VRPlayer.java b/src/main/java/org/vmstudio/visor/api/player/VRPlayer.java new file mode 100644 index 0000000..cca15e5 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VRPlayer.java @@ -0,0 +1,108 @@ +package org.vmstudio.visor.api.player; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The interface for VR players + * A {@link VisorPlayer} who is currently in VR + */ +public interface VRPlayer extends VisorPlayer { + float DEFAULT_GUN_ANGLE = 60.0F; + float DEFAULT_FULL_HEIGHT = 1.52f; + + @NotNull + VRPlayerPose getPoseDataPrevious(); + + @NotNull + VRPlayerPose getPoseDataRelative(); + + @NotNull + VRPlayerPose getPoseData(); + + /** + * get pose history for relative type + * + * @return pose history + */ + @NotNull + VRPoseHistory getPoseHistoryRelative(); + + /** + * get pose history for tick type + * + * @return pose history + */ + @NotNull + VRPoseHistory getPoseHistoryTick(); + + /** + * The gun angle is used for item pose + * compatibility with different controllers. + * + * @return the gun angle + */ + float getGunAngle(); + + int getOffhandSlot(); + + /** + * If this VR player is left-handed + * + * @return true/false + */ + boolean isLeftHanded(); + + /** + * Get hand type which is currently used + * by player for attack/mining + * + * @return hand type + */ + @NotNull + HandType getActiveHand(); + + /** + * Get full height + * + * @return full height + */ + float getFullHeight(); + + /** + * Get actual height + * + * @return actual height + */ + default float getActualHeight(){ + return (float) getPoseDataRelative().getHeadPivot().getY(); + } + + /** + * Get full height scale. + *

+ * It is the ratio between {@link #getFullHeight()} + * and height of a minecraft player + *

+ * + * @return full height scale + */ + default float getFullHeightScale(){ + return getFullHeight() / DEFAULT_FULL_HEIGHT; + } + + /** + * Whether the player currently has an overlay + * + * @return true when an overlay is active + */ + boolean isOverlayFocused(); + + float getWorldScale(); + + float getRotationY(); + + boolean isCrawling(); + + String getBodyType(); +} diff --git a/src/main/java/org/vmstudio/visor/api/player/VRPlayerPose.java b/src/main/java/org/vmstudio/visor/api/player/VRPlayerPose.java new file mode 100644 index 0000000..750b212 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VRPlayerPose.java @@ -0,0 +1,134 @@ +package org.vmstudio.visor.api.player; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Provides pose data for VR player + */ +public interface VRPlayerPose { + /** + * Get the head-mounted display (HMD) pose. + * + * @return the HMD pose + */ + @NotNull + VRPose getHmd(); + + /** + * Get the main hand pose. + *

+ * This represents the aiming pose of VR controller. + *

+ * + * @return the main hand pose + */ + @NotNull + VRPose getMainHand(); + + /** + * Get the offhand pose. + *

+ * This represents the aiming pose of VR controller. + *

+ * + * @return the offhand pose + */ + @NotNull + VRPose getOffhand(); + + /** + * Get the hand pose for the given hand type. + *

+ * This represents the aiming pose of VR controller + *

+ * + * @param handType the hand type + * @return the hand pose + */ + @NotNull + default VRPose getHand(@NotNull HandType handType){ + return handType == HandType.MAIN ? getMainHand() : getOffhand(); + } + + /** + * Get pose from body part + * + * @param bodyPart the body part + * @return pose + */ + @Nullable + VRPose getPose(@NotNull VRBodyPartType bodyPart); + + /** + * Get trackers pose + * + * @return the trackers pose + */ + @NotNull + VRPoseTrackers getTrackers(); + + /** + * Get the origin to which pose data is relative to. + *

+ * The origin is used by Visor to convert + * pose data in VR room coordinates + * to world coordinates relative to player + *

+ * + * @return the origin + */ + @NotNull + Vector getOrigin(); + + /** + * Get the world scale. + *

+ * This defines how VR units map to world units. + *

+ * + * (Always {@code 1.0} because server-side) + * @return the world scale factor + */ + float getWorldScale(); + + /** + * Get the player rotation Y in radians. + *

+ * This is the general yaw rotation for VR. + * We don't use the yaw from minecraft + * to have a protection layer from motion sickness + * between VR and minecraft logic made for PCs. + * The other reason is to be able to control + * player model/ray tracing without interfering VR + *

+ * + * (Always {@code 0} because server-side) + * @return the player's rotation Y in radians + */ + float getRotationY(); + + /** + * Get the head pivot point. + *

+ * This is an approximate point around which the head rotates, + * i.e. the position of the player's neck. + *

+ * + * @return the head pivot position + */ + @NotNull + Vector getHeadPivot(); + + /** + * Get the body yaw in radians. + *

+ * This is the rotation of the body around the Y axis, + * which may differ from the head rotation. + *

+ * + * @return the body yaw in radians + */ + float getBodyYaw(); +} \ No newline at end of file diff --git a/src/main/java/org/vmstudio/visor/api/player/VRPose.java b/src/main/java/org/vmstudio/visor/api/player/VRPose.java new file mode 100644 index 0000000..04890bb --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VRPose.java @@ -0,0 +1,14 @@ +package org.vmstudio.visor.api.player; + +import org.bukkit.util.Vector; + +/** + * Represents a VR spatial data (controller, hmd, eyes, body parts etc.) + */ +public record VRPose(Vector offset, float qx, float qy, float qz, float qw){ + /** + * An empty element with zero position, zero direction, + * and identity rotation. + */ + public static final VRPose EMPTY = new VRPose(new Vector(0, 0, 0), 0, 0, 0, 1); +} diff --git a/src/main/java/org/vmstudio/visor/api/player/VRPoseHistory.java b/src/main/java/org/vmstudio/visor/api/player/VRPoseHistory.java new file mode 100644 index 0000000..390de99 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VRPoseHistory.java @@ -0,0 +1,113 @@ +package org.vmstudio.visor.api.player; + +import java.util.List; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a bounded history of player poses sampled each tick. + *

+ * Implementations treat index {@code 0} as the most recent pose, + * with increasing indices going further back in time. + */ +public interface VRPoseHistory { + /** + * Maximum number of pose samples that can be stored in the history. + */ + int HISTORY_LIMIT = 100; + + /** + * Returns an immutable snapshot of all stored pose samples. + * + * @return list of player poses ordered from newest to oldest + */ + List getAllHistory(); + + /** + * Returns a pose from the history by its age in ticks. + * + * @param ticksBack how many ticks back to look, where {@code 0} is the newest pose + * @return the pose at the given offset + * @throws IndexOutOfBoundsException if {@code ticksBack} is outside the stored range + */ + VRPlayerPose getEntry(int ticksBack); + + /** + * Returns the number of stored pose samples. + * + * @return current history size + */ + int getHistorySize(); + + /** + * Computes the net movement of a body part over the specified time window. + * + * @param bodyPart body part to measure + * @param maxTicksBack maximum ticks back to include, starting from the newest pose + * @return displacement vector from the older pose to the newest pose; + * a zero vector if there is not enough data + * @throws IllegalArgumentException if {@code maxTicksBack} is negative + * or greater than {@link #HISTORY_LIMIT} + */ + Vector netMovement(VRBodyPartType bodyPart, int maxTicksBack); + + /** + * Computes the net movement of the head pivot over the specified time window. + * + * @param maxTicksBack maximum ticks back to include, starting from the newest pose + * @return displacement vector of the head pivot from the older pose to the newest pose; + * a zero vector if there is not enough data + * @throws IllegalArgumentException if {@code maxTicksBack} is negative + * or greater than {@link #HISTORY_LIMIT} + */ + Vector headPivotNetMovement(int maxTicksBack); + + /** + * Computes the average per-tick movement speed of a body part. + *

+ * Speed is calculated over up to {@code maxTicksBack} most recent ticks. + * + * @param bodyPart body part to measure + * @param maxTicksBack maximum number of ticks to consider + * @return average movement distance per tick; {@code 0} if there is not enough data + * @throws IllegalArgumentException if {@code maxTicksBack} is negative + * or greater than {@link #HISTORY_LIMIT} + */ + double averageSpeed(VRBodyPartType bodyPart, int maxTicksBack); + + /** + * Computes the average per-tick movement speed of the head pivot. + *

+ * Speed is calculated over up to {@code maxTicksBack} most recent ticks. + * + * @param maxTicksBack maximum number of ticks to consider + * @return average head pivot movement distance per tick; {@code 0} if there is not enough data + * @throws IllegalArgumentException if {@code maxTicksBack} is negative + * or greater than {@link #HISTORY_LIMIT} + */ + double headPivotAverageSpeed(int maxTicksBack); + + /** + * Computes the average position of a body part over the specified time window. + * + * @param bodyPart body part to sample + * @param maxTicksBack maximum number of most recent poses to include + * @return average position of the body part, or {@code null} if history is empty + * @throws IllegalArgumentException if {@code maxTicksBack} is negative + * or greater than {@link #HISTORY_LIMIT} + */ + @Nullable + Vector averagePosition(VRBodyPartType bodyPart, int maxTicksBack); + + /** + * Computes the average head pivot position over the specified time window. + * + * @param maxTicksBack maximum number of most recent poses to include + * @return average head pivot position, or {@code null} if history is empty + * @throws IllegalArgumentException if {@code maxTicksBack} is negative + * or greater than {@link #HISTORY_LIMIT} + */ + @Nullable + Vector headPivotAveragePosition(int maxTicksBack); +} diff --git a/src/main/java/org/vmstudio/visor/api/player/VRPoseTrackers.java b/src/main/java/org/vmstudio/visor/api/player/VRPoseTrackers.java new file mode 100644 index 0000000..0ad2d28 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VRPoseTrackers.java @@ -0,0 +1,30 @@ +package org.vmstudio.visor.api.player; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface VRPoseTrackers { + boolean isActive(); + + List getActiveTrackersPose(); + + List getActiveTrackersType(); + + @Nullable VRPose getWaist(); + @Nullable VRPose getChest(); + + @Nullable VRPose getLeftFoot(); + @Nullable VRPose getRightFoot(); + @Nullable VRPose getLeftAnkle(); + @Nullable VRPose getRightAnkle(); + @Nullable VRPose getLeftKnee(); + @Nullable VRPose getRightKnee(); + + @Nullable VRPose getLeftWrist(); + @Nullable VRPose getRightWrist(); + @Nullable VRPose getLeftElbow(); + @Nullable VRPose getRightElbow(); + @Nullable VRPose getLeftShoulder(); + @Nullable VRPose getRightShoulder(); +} diff --git a/src/main/java/org/vmstudio/visor/api/player/VisorPlayer.java b/src/main/java/org/vmstudio/visor/api/player/VisorPlayer.java new file mode 100644 index 0000000..1dc92b7 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/api/player/VisorPlayer.java @@ -0,0 +1,27 @@ +package org.vmstudio.visor.api.player; + +import java.util.UUID; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +/** + * Player with Visor mod installed (VR or NonVR) + */ +public interface VisorPlayer { + UUID getUuid(); + Player getMcPlayer(); + String getClientVersion(); + + default boolean isVR(){ + return asVR() != null; + } + default boolean isNonVR(){ + return asVR() == null; + } + + @Nullable + default VRPlayer asVR(){ + return this instanceof VRPlayer vr ? vr : null; + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/VisorBukkitPlugin.java b/src/main/java/org/vmstudio/visor/bukkit/VisorBukkitPlugin.java index 36abc03..86b7112 100644 --- a/src/main/java/org/vmstudio/visor/bukkit/VisorBukkitPlugin.java +++ b/src/main/java/org/vmstudio/visor/bukkit/VisorBukkitPlugin.java @@ -4,7 +4,9 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; +import org.vmstudio.visor.api.VisorAPI; import org.vmstudio.visor.bukkit.adapter.VersionAdapterRegistry; +import org.vmstudio.visor.bukkit.api.VisorAPIImpl; import org.vmstudio.visor.bukkit.command.VisorCommand; import org.vmstudio.visor.bukkit.listener.PvpListener; import org.vmstudio.visor.bukkit.listener.VisorChannelListener; @@ -15,8 +17,8 @@ import org.vmstudio.visor.common.VisorServer; import org.vmstudio.visor.common.settings.SettingsLoader; import org.vmstudio.visor.common.settings.VisorSettings; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.VisorProtocol; public final class VisorBukkitPlugin extends JavaPlugin { @@ -34,7 +36,7 @@ public void onEnable(){ logger.setDebug(settings.serverDebug()); McVersion mc = McVersion.parse(getServer().getBukkitVersion()); - VersionAdapter adapter = VersionAdapterRegistry.resolve(mc, logger); + VisorVersionAdapter adapter = VersionAdapterRegistry.resolve(mc, logger); adapter.init(this); BukkitPlatformServer platform = new BukkitPlatformServer(this, adapter, mc, core, logger); @@ -51,6 +53,10 @@ public void onEnable(){ getCommand("visor").setExecutor(visorCommand); getCommand("visor").setTabCompleter(visorCommand); + VisorAPIImpl api = new VisorAPIImpl(visor, mc); + visor.sessions().setListener(api); + VisorAPI.Instance.set(api); + visor.start(); logger.info("VisorPlugin enabled - MC {}, {} core, adapter '{}', protocol v{}.", @@ -59,6 +65,7 @@ public void onEnable(){ @Override public void onDisable(){ + VisorAPI.Instance.set(null); if(visor != null){ BukkitPlatformPlayer.removeAllDebug(getServer()); if(prefsFile != null){ diff --git a/src/main/java/org/vmstudio/visor/bukkit/adapter/PaperApiAdapter.java b/src/main/java/org/vmstudio/visor/bukkit/adapter/PaperApiAdapter.java index 9e2e7c2..3976291 100644 --- a/src/main/java/org/vmstudio/visor/bukkit/adapter/PaperApiAdapter.java +++ b/src/main/java/org/vmstudio/visor/bukkit/adapter/PaperApiAdapter.java @@ -4,12 +4,11 @@ import org.bukkit.entity.Player; import org.vmstudio.visor.common.platform.VisorLogger; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class PaperApiAdapter implements VersionAdapter { +public final class PaperApiAdapter implements VisorVersionAdapter { private static final double TRACKER_FALLBACK_RANGE = 256.0; private final VisorLogger logger; diff --git a/src/main/java/org/vmstudio/visor/bukkit/adapter/VersionAdapterRegistry.java b/src/main/java/org/vmstudio/visor/bukkit/adapter/VersionAdapterRegistry.java index 2fe27f0..ead34ae 100644 --- a/src/main/java/org/vmstudio/visor/bukkit/adapter/VersionAdapterRegistry.java +++ b/src/main/java/org/vmstudio/visor/bukkit/adapter/VersionAdapterRegistry.java @@ -1,70 +1,33 @@ package org.vmstudio.visor.bukkit.adapter; -import java.util.ArrayList; -import java.util.Comparator; import java.util.List; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.api.nms.VersionedNms; import org.vmstudio.visor.common.platform.VisorLogger; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; public final class VersionAdapterRegistry { - private final List adapters = new ArrayList<>(); - - public void register(VersionAdapter adapter){ - adapters.add(adapter); - } - - public VersionAdapter select(McVersion version){ - return adapters.stream() - .filter(a -> a.supports(version) && a.selfTest()) - .max(Comparator.comparingInt(VersionAdapter::priority)) - .orElse(null); - } - - private record Leaf(String className, int major, int minor, int minPatch, int maxPatch){ - boolean matches(McVersion v){ - return v.major() == major && v.minor() == minor - && v.patch() >= minPatch && v.patch() <= maxPatch; - } - } - - private static final Leaf[] NMS_LEAVES = { - new Leaf("org.vmstudio.visor.nms.v1_20_R1.NmsV1_20_R1Adapter", 1, 20, 1, 1), - new Leaf("org.vmstudio.visor.nms.v1_21_1.NmsV1_21_1Adapter", 1, 21, 0, 1), - new Leaf("org.vmstudio.visor.nms.v1_21_3.NmsV1_21_3Adapter", 1, 21, 2, 3), - new Leaf("org.vmstudio.visor.nms.v1_21_4.NmsV1_21_4Adapter", 1, 21, 4, 4), - new Leaf("org.vmstudio.visor.nms.v1_21_5.NmsV1_21_5Adapter", 1, 21, 5, 5), - new Leaf("org.vmstudio.visor.nms.v1_21_6.NmsV1_21_6Adapter", 1, 21, 6, 6), - new Leaf("org.vmstudio.visor.nms.v1_21_7.NmsV1_21_7Adapter", 1, 21, 7, 7), - new Leaf("org.vmstudio.visor.nms.v1_21_8.NmsV1_21_8Adapter", 1, 21, 8, 8), - new Leaf("org.vmstudio.visor.nms.v1_21_9.NmsV1_21_9Adapter", 1, 21, 9, 9), - new Leaf("org.vmstudio.visor.nms.v1_21_10.NmsV1_21_10Adapter", 1, 21, 10, 10), - new Leaf("org.vmstudio.visor.nms.v1_21_11.NmsV1_21_11Adapter", 1, 21, 11, 11), - }; - - public static VersionAdapter resolve(McVersion version, VisorLogger logger){ - VersionAdapterRegistry registry = new VersionAdapterRegistry(); - registry.register(new PaperApiAdapter(logger)); - for(Leaf leaf : NMS_LEAVES){ - if(!leaf.matches(version)){ - continue; - } - VersionAdapter adapter = tryLoadLeaf(leaf.className()); - if(adapter != null){ - registry.register(adapter); - } - } - VersionAdapter selected = registry.select(version); - return selected != null ? selected : new PaperApiAdapter(logger); - } - - private static VersionAdapter tryLoadLeaf(String className){ - try { - Class clazz = Class.forName(className); - return (VersionAdapter) clazz.getDeclaredConstructor().newInstance(); - }catch (Throwable notPresent){ - return null; + private VersionAdapterRegistry(){} + + private static final List NMS_LEAVES = List.of( + new VersionedNms.Target("org.vmstudio.visor.nms.v1_20_R1.NmsV1_20_R1Adapter", 1, 20, 1, 1), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_1.NmsV1_21_1Adapter", 1, 21, 0, 1), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_3.NmsV1_21_3Adapter", 1, 21, 2, 3), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_4.NmsV1_21_4Adapter", 1, 21, 4, 4), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_5.NmsV1_21_5Adapter", 1, 21, 5, 5), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_6.NmsV1_21_6Adapter", 1, 21, 6, 6), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_7.NmsV1_21_7Adapter", 1, 21, 7, 7), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_8.NmsV1_21_8Adapter", 1, 21, 8, 8), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_9.NmsV1_21_9Adapter", 1, 21, 9, 9), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_10.NmsV1_21_10Adapter", 1, 21, 10, 10), + new VersionedNms.Target("org.vmstudio.visor.nms.v1_21_11.NmsV1_21_11Adapter", 1, 21, 11, 11) + ); + + public static VisorVersionAdapter resolve(McVersion version, VisorLogger logger){ + VisorVersionAdapter leaf = VersionedNms.resolve(VisorVersionAdapter.class, version, NMS_LEAVES); + if(leaf != null && leaf.selfTest()){ + return leaf; } + return new PaperApiAdapter(logger); } } diff --git a/src/main/java/org/vmstudio/visor/nms/VersionAdapter.java b/src/main/java/org/vmstudio/visor/bukkit/adapter/VisorVersionAdapter.java similarity index 69% rename from src/main/java/org/vmstudio/visor/nms/VersionAdapter.java rename to src/main/java/org/vmstudio/visor/bukkit/adapter/VisorVersionAdapter.java index 60ee8f8..f153f47 100644 --- a/src/main/java/org/vmstudio/visor/nms/VersionAdapter.java +++ b/src/main/java/org/vmstudio/visor/bukkit/adapter/VisorVersionAdapter.java @@ -1,14 +1,18 @@ -package org.vmstudio.visor.nms; +package org.vmstudio.visor.bukkit.adapter; import java.util.Set; import java.util.UUID; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.ApiStatus; +import org.vmstudio.visor.api.nms.McVersion; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.value.VBlockPos; -public interface VersionAdapter { +/** VisorPlugin's SPI for NMS */ +@ApiStatus.Internal +public interface VisorVersionAdapter { String name(); int priority(); @@ -17,11 +21,9 @@ public interface VersionAdapter { boolean selfTest(); - default void init(Plugin plugin){ - } + default void init(Plugin plugin){} - default void tickEffects(Player player){ - } + default void tickEffects(Player player){} boolean forceCrawlPose(Player player, boolean crawling); diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/PoseHistoryImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/PoseHistoryImpl.java new file mode 100644 index 0000000..aa17334 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/PoseHistoryImpl.java @@ -0,0 +1,163 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.List; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.player.VRPlayerPose; +import org.vmstudio.visor.api.player.VRPoseHistory; +import org.vmstudio.visor.api.player.VRBodyPartType; +import org.vmstudio.visor.api.player.VRPose; + +public class PoseHistoryImpl implements VRPoseHistory { + private final List history; + + public PoseHistoryImpl(List history){ + this.history = history; + } + + @Override + public List getAllHistory(){ + return List.copyOf(history); + } + + @Override + public VRPlayerPose getEntry(int ticksBack){ + return history.get(ticksBack); + } + + @Override + public int getHistorySize(){ + return history.size(); + } + + @Override + public Vector netMovement(VRBodyPartType bodyPart, int maxTicksBack){ + checkTicksBack(maxTicksBack); + if(history.size() <= 1){ + return new Vector(); + } + int back = clamp(maxTicksBack); + Vector last = position(history.get(0), bodyPart); + Vector old = position(history.get(back), bodyPart); + if(last == null || old == null){ + return new Vector(); + } + return last.clone().subtract(old); + } + + @Override + public Vector headPivotNetMovement(int maxTicksBack){ + checkTicksBack(maxTicksBack); + if(history.size() <= 1){ + return new Vector(); + } + int back = clamp(maxTicksBack); + return history.get(0).getHeadPivot().subtract(history.get(back).getHeadPivot()); + } + + @Override + public double averageSpeed(VRBodyPartType bodyPart, int maxTicksBack){ + checkTicksBack(maxTicksBack); + if(history.size() <= 1){ + return 0; + } + int back = clamp(maxTicksBack); + double sum = 0; + int n = 0; + for(int i = 0; i < back; i++){ + Vector newer = position(history.get(i), bodyPart); + Vector older = position(history.get(i + 1), bodyPart); + if(newer == null || older == null){ + continue; + } + sum += newer.distance(older); + n++; + } + return n == 0 ? 0 : sum / n; + } + + @Override + public double headPivotAverageSpeed(int maxTicksBack){ + checkTicksBack(maxTicksBack); + if(history.size() <= 1){ + return 0; + } + int back = clamp(maxTicksBack); + double sum = 0; + int n = 0; + for(int i = 0; i < back; i++){ + sum += history.get(i).getHeadPivot().distance(history.get(i + 1).getHeadPivot()); + n++; + } + return n == 0 ? 0 : sum / n; + } + + @Override + public @Nullable Vector averagePosition(VRBodyPartType bodyPart, int maxTicksBack){ + checkTicksBack(maxTicksBack); + if(history.isEmpty()){ + return null; + } + int back = clamp(maxTicksBack); + double sx = 0; + double sy = 0; + double sz = 0; + int n = 0; + int count = 0; + for(VRPlayerPose pose : history){ + Vector p = position(pose, bodyPart); + if(p != null){ + sx += p.getX(); + sy += p.getY(); + sz += p.getZ(); + n++; + } + if(++count >= back){ + break; + } + } + return n == 0 ? null : new Vector(sx / n, sy / n, sz / n); + } + + @Override + public @Nullable Vector headPivotAveragePosition(int maxTicksBack){ + checkTicksBack(maxTicksBack); + if(history.isEmpty()){ + return null; + } + int back = clamp(maxTicksBack); + double sx = 0; + double sy = 0; + double sz = 0; + int n = 0; + int count = 0; + for(VRPlayerPose pose : history){ + Vector p = pose.getHeadPivot(); + sx += p.getX(); + sy += p.getY(); + sz += p.getZ(); + n++; + if(++count >= back){ + break; + } + } + return n == 0 ? null : new Vector(sx / n, sy / n, sz / n); + } + + private void checkTicksBack(int ticksBack){ + if(ticksBack < 0 || ticksBack > HISTORY_LIMIT){ + throw new IllegalArgumentException("Value must be between 0 and " + HISTORY_LIMIT); + } + } + + private int clamp(int maxTicksBack){ + return Math.max(0, Math.min(maxTicksBack, history.size() - 1)); + } + + @Nullable + private static Vector position(VRPlayerPose pose, VRBodyPartType bodyPart){ + VRPose vp = pose.getPose(bodyPart); + return vp == null ? null : vp.offset(); + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/PoseMath.java b/src/main/java/org/vmstudio/visor/bukkit/api/PoseMath.java new file mode 100644 index 0000000..470b29e --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/PoseMath.java @@ -0,0 +1,40 @@ +package org.vmstudio.visor.bukkit.api; + +import org.bukkit.util.Vector; +import org.vmstudio.visor.protocol.value.Quat; + +public final class PoseMath { + private PoseMath(){} + + /** + * rotate vector {@code (vx, vy, vz)} by the quaternion {@code q} + * + * @param q the rotation quaternion + * @param vx vector X + * @param vy vector Y + * @param vz vector Z + * @return rotated vector + */ + public static Vector rotate(Quat q, double vx, double vy, double vz){ + double tx = 2.0 * (q.y() * vz - q.z() * vy); + double ty = 2.0 * (q.z() * vx - q.x() * vz); + double tz = 2.0 * (q.x() * vy - q.y() * vx); + double rx = vx + q.w() * tx + (q.y() * tz - q.z() * ty); + double ry = vy + q.w() * ty + (q.z() * tx - q.x() * tz); + double rz = vz + q.w() * tz + (q.x() * ty - q.y() * tx); + return new Vector(rx, ry, rz); + } + + /** + * rotate a vector around the Y axis (matches joml {@code Vector3f.rotateY}) + * + * @param v the vector to rotate + * @param angle the angle in radians + * @return rotated vector + */ + public static Vector rotateY(Vector v, double angle){ + double s = Math.sin(angle); + double c = Math.cos(angle); + return new Vector(v.getX() * c + v.getZ() * s, v.getY(), -v.getX() * s + v.getZ() * c); + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/PoseTrackersImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/PoseTrackersImpl.java new file mode 100644 index 0000000..c7bf8af --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/PoseTrackersImpl.java @@ -0,0 +1,122 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.player.VRBodyPartType; +import org.vmstudio.visor.api.player.VRPose; +import org.vmstudio.visor.api.player.VRPoseTrackers; + +public final class PoseTrackersImpl implements VRPoseTrackers { + private static final VRBodyPartType[] TRACKER_PARTS = { + VRBodyPartType.WAIST, VRBodyPartType.CHEST, + VRBodyPartType.LEFT_FOOT, VRBodyPartType.RIGHT_FOOT, + VRBodyPartType.LEFT_ANKLE, VRBodyPartType.RIGHT_ANKLE, + VRBodyPartType.LEFT_KNEE, VRBodyPartType.RIGHT_KNEE, + VRBodyPartType.LEFT_WRIST, VRBodyPartType.RIGHT_WRIST, + VRBodyPartType.LEFT_ELBOW, VRBodyPartType.RIGHT_ELBOW, + VRBodyPartType.LEFT_SHOULDER, VRBodyPartType.RIGHT_SHOULDER, + }; + + private final Map poses; + private final List activePose = new ArrayList<>(); + private final List activeType = new ArrayList<>(); + + public PoseTrackersImpl(Map poses){ + this.poses = poses; + for(VRBodyPartType part : TRACKER_PARTS){ + VRPose p = poses.get(part); + if(p != null){ + activePose.add(p); + activeType.add(part); + } + } + } + + @Override + public boolean isActive(){ + return !activePose.isEmpty(); + } + + @Override + public List getActiveTrackersPose(){ + return List.copyOf(activePose); + } + + @Override + public List getActiveTrackersType(){ + return List.copyOf(activeType); + } + + @Override + public @Nullable VRPose getWaist(){ + return poses.get(VRBodyPartType.WAIST); + } + + @Override + public @Nullable VRPose getChest(){ + return poses.get(VRBodyPartType.CHEST); + } + + @Override + public @Nullable VRPose getLeftFoot(){ + return poses.get(VRBodyPartType.LEFT_FOOT); + } + + @Override + public @Nullable VRPose getRightFoot(){ + return poses.get(VRBodyPartType.RIGHT_FOOT); + } + + @Override + public @Nullable VRPose getLeftAnkle(){ + return poses.get(VRBodyPartType.LEFT_ANKLE); + } + + @Override + public @Nullable VRPose getRightAnkle(){ + return poses.get(VRBodyPartType.RIGHT_ANKLE); + } + + @Override + public @Nullable VRPose getLeftKnee(){ + return poses.get(VRBodyPartType.LEFT_KNEE); + } + + @Override + public @Nullable VRPose getRightKnee(){ + return poses.get(VRBodyPartType.RIGHT_KNEE); + } + + @Override + public @Nullable VRPose getLeftWrist(){ + return poses.get(VRBodyPartType.LEFT_WRIST); + } + + @Override + public @Nullable VRPose getRightWrist(){ + return poses.get(VRBodyPartType.RIGHT_WRIST); + } + + @Override + public @Nullable VRPose getLeftElbow(){ + return poses.get(VRBodyPartType.LEFT_ELBOW); + } + + @Override + public @Nullable VRPose getRightElbow(){ + return poses.get(VRBodyPartType.RIGHT_ELBOW); + } + + @Override + public @Nullable VRPose getLeftShoulder(){ + return poses.get(VRBodyPartType.LEFT_SHOULDER); + } + + @Override + public @Nullable VRPose getRightShoulder(){ + return poses.get(VRBodyPartType.RIGHT_SHOULDER); + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerImpl.java new file mode 100644 index 0000000..873497d --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerImpl.java @@ -0,0 +1,133 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.player.HandType; +import org.vmstudio.visor.api.player.VRBodyPartType; +import org.vmstudio.visor.api.player.VRPlayer; +import org.vmstudio.visor.api.player.VRPlayerPose; +import org.vmstudio.visor.api.player.VRPose; +import org.vmstudio.visor.api.player.VRPoseHistory; +import org.vmstudio.visor.bukkit.platform.BukkitPlatformPlayer; +import org.vmstudio.visor.common.session.PoseHistoryBuffer; +import org.vmstudio.visor.common.session.VisorSession; + +public final class VRPlayerImpl extends VisorPlayerImpl implements VRPlayer { + public VRPlayerImpl(VisorSession session, Player player){ + super(session, player); + } + + @Override + public @NotNull VRPlayerPose getPoseData(){ + return poseAt(0, true); + } + + @Override + public @NotNull VRPlayerPose getPoseDataPrevious(){ + List h = session.vr().getPoseHistory(); + return poseFrom(h, h.size() > 1 ? 1 : 0, true); + } + + @Override + public @NotNull VRPlayerPose getPoseDataRelative(){ + return poseAt(0, false); + } + + @Override + public @NotNull VRPoseHistory getPoseHistoryRelative(){ + return history(false); + } + + @Override + public @NotNull VRPoseHistory getPoseHistoryTick(){ + return history(true); + } + + private VRPlayerPose poseAt(int idx, boolean worldSpace){ + return poseFrom(session.vr().getPoseHistory(), idx, worldSpace); + } + + private static VRPlayerPose poseFrom(List h, int idx, boolean worldSpace){ + if(h.isEmpty() || idx >= h.size()){ + return VRPlayerPoseImpl.empty(); + } + PoseHistoryBuffer.Snapshot s = h.get(idx); + return VRPlayerPoseImpl.of(s.pose(), originOf(s, worldSpace)); + } + + private VRPoseHistory history(boolean worldSpace){ + List h = session.vr().getPoseHistory(); + List list = new ArrayList<>(h.size()); + for(PoseHistoryBuffer.Snapshot s : h){ + list.add(VRPlayerPoseImpl.of(s.pose(), originOf(s, worldSpace))); + } + return new PoseHistoryImpl(list); + } + + private static Vector originOf(PoseHistoryBuffer.Snapshot s, boolean worldSpace){ + return worldSpace ? new Vector(s.ox(), s.oy(), s.oz()) : new Vector(0, 0, 0); + } + + @Override + public boolean isLeftHanded(){ + return session.vr().isLeftHanded(); + } + + @Override + public @NotNull HandType getActiveHand(){ + return session.vr().isActiveHandMain() ? HandType.MAIN : HandType.OFFHAND; + } + + @Override + public int getOffhandSlot(){ + return session.vr().getOffhandSlot(); + } + + @Override + public float getWorldScale(){ + return session.vr().getWorldScale(); + } + + @Override + public float getFullHeight(){ + return session.vr().getFullHeight(); + } + + @Override + public float getGunAngle(){ + return session.vr().getGunAngle(); + } + + @Override + public float getRotationY(){ + return session.vr().getRotationY(); + } + + @Override + public boolean isOverlayFocused(){ + return session.vr().isOverlayFocused(); + } + + @Override + public boolean isCrawling(){ + return session.vr().isCrawling(); + } + + @Override + public String getBodyType(){ + return session.vr().getBodyType(); + } + + public static @Nullable VRPlayer fromVisorSession(VisorSession session){ + if(session == null || !session.vrActive()){ + return null; + } + Player player = ((BukkitPlatformPlayer) session.player()).handle(); + return new VRPlayerImpl(session, player); + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerPoseImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerPoseImpl.java new file mode 100644 index 0000000..2835fd1 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/VRPlayerPoseImpl.java @@ -0,0 +1,179 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.EnumMap; +import java.util.Map; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.player.VRPlayerPose; +import org.vmstudio.visor.api.player.VRBodyPartType; +import org.vmstudio.visor.api.player.VRPose; +import org.vmstudio.visor.api.player.VRPoseTrackers; +import org.vmstudio.visor.protocol.value.PoseData; +import org.vmstudio.visor.protocol.value.PoseElement; +import org.vmstudio.visor.protocol.value.Quat; +import org.vmstudio.visor.protocol.value.Vec3f; + +public final class VRPlayerPoseImpl implements VRPlayerPose { + private final Vector origin; + private final Map poses = new EnumMap<>(VRBodyPartType.class); + private final VRPose hmd; + private final VRPose mainHand; + private final VRPose offhand; + private final Vector headPivot; + private final float bodyYaw; + private final VRPoseTrackers trackers; + + private VRPlayerPoseImpl(){ + this.origin = new Vector(0, 0, 0); + this.hmd = VRPose.EMPTY; + this.mainHand = VRPose.EMPTY; + this.offhand = VRPose.EMPTY; + this.headPivot = new Vector(0, 0, 0); + this.bodyYaw = 0f; + this.trackers = new PoseTrackersImpl(poses); + } + + private VRPlayerPoseImpl(PoseData pose, Vector origin){ + this.origin = origin; + VRBodyPartType[] parts = VRBodyPartType.values(); + + VRPose h = element(pose.hmd(), origin); + VRPose m = element(pose.mainHand(), origin); + VRPose o = element(pose.offhand(), origin); + this.hmd = h != null ? h : VRPose.EMPTY; + this.mainHand = m != null ? m : VRPose.EMPTY; + this.offhand = o != null ? o : VRPose.EMPTY; + if(h != null){ + poses.put(VRBodyPartType.HEAD, h); + } + if(m != null){ + poses.put(VRBodyPartType.MAIN_HAND, m); + } + if(o != null){ + poses.put(VRBodyPartType.OFFHAND, o); + } + + PoseElement[] trackers = pose.trackers().toArray(); + for(int i = 0; i < trackers.length; i++){ + VRPose vp = element(trackers[i], origin); + if(vp != null){ + poses.put(parts[i + 3], vp); + } + } + + this.headPivot = computeHeadPivot(pose.hmd(), origin); + this.bodyYaw = computeBodyYaw(pose); + this.trackers = new PoseTrackersImpl(poses); + } + + public static VRPlayerPose of(PoseData pose, Vector origin){ + return new VRPlayerPoseImpl(pose, origin); + } + + public static VRPlayerPose empty(){ + return new VRPlayerPoseImpl(); + } + + @Nullable + private static VRPose element(PoseElement el, Vector origin){ + if(el == null){ + return null; + } + Vec3f p = el.position(); + Quat q = el.orientation(); + return new VRPose( + new Vector(p.x() + origin.getX(), p.y() + origin.getY(), p.z() + origin.getZ()), + q.x(), q.y(), q.z(), q.w()); + } + + private static Vector computeHeadPivot(PoseElement hmd, Vector origin){ + if(hmd == null){ + return origin.clone(); + } + Vec3f p = hmd.position(); + Vector t = PoseMath.rotate(hmd.orientation(), 0.0, -0.1, 0.1); + return new Vector( + p.x() + origin.getX() + t.getX(), + p.y() + origin.getY() + t.getY(), + p.z() + origin.getZ() + t.getZ()); + } + + private static float computeBodyYaw(PoseData pose){ + PoseElement main = pose.mainHand(); + PoseElement off = pose.offhand(); + PoseElement hmd = pose.hmd(); + if(main == null || off == null || hmd == null){ + return 0f; + } + Vector body = new Vector( + off.position().x() - main.position().x(), + off.position().y() - main.position().y(), + off.position().z() - main.position().z()); + if(body.lengthSquared() < 1e-12){ + return 0f; + } + body.normalize(); + body = PoseMath.rotateY(body, -Math.PI / 2.0); + Vector hmdDir = PoseMath.rotate(hmd.orientation(), 0.0, 0.0, -1.0); + if(body.dot(hmdDir) < 0.0){ + body.multiply(-1.0); + } + Vector lerped = new Vector( + hmdDir.getX() + (body.getX() - hmdDir.getX()) * 0.7, + hmdDir.getY() + (body.getY() - hmdDir.getY()) * 0.7, + hmdDir.getZ() + (body.getZ() - hmdDir.getZ()) * 0.7); + return (float) Math.atan2(-lerped.getX(), lerped.getZ()); + } + + @Override + public @NotNull VRPose getHmd(){ + return hmd; + } + + @Override + public @NotNull VRPose getMainHand(){ + return mainHand; + } + + @Override + public @NotNull VRPose getOffhand(){ + return offhand; + } + + @Override + public @Nullable VRPose getPose(@NotNull VRBodyPartType bodyPart){ + return poses.get(bodyPart); + } + + @Override + public @NotNull VRPoseTrackers getTrackers(){ + return trackers; + } + + @Override + public @NotNull Vector getOrigin(){ + return origin.clone(); + } + + @Override + public float getWorldScale(){ + return 1.0f; + } + + @Override + public float getRotationY(){ + return 0f; + } + + @Override + public @NotNull Vector getHeadPivot(){ + return headPivot.clone(); + } + + @Override + public float getBodyYaw(){ + return bodyYaw; + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/VisorAPIImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/VisorAPIImpl.java new file mode 100644 index 0000000..c4615b8 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/VisorAPIImpl.java @@ -0,0 +1,129 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.vmstudio.visor.api.VisorAPI; +import org.vmstudio.visor.api.network.VisorChannel; +import org.vmstudio.visor.api.network.VisorChannelHandler; +import org.vmstudio.visor.api.player.VRPlayer; +import org.vmstudio.visor.api.player.VisorPlayer; +import org.vmstudio.visor.api.event.VisorPlayerJoinedEvent; +import org.vmstudio.visor.api.event.VisorPlayerLeftEvent; +import org.vmstudio.visor.common.VisorServer; +import org.vmstudio.visor.common.session.SessionManager; +import org.vmstudio.visor.common.session.VisorSession; +import org.vmstudio.visor.api.nms.McVersion; + +public final class VisorAPIImpl implements VisorAPI, SessionManager.Listener { + private final VisorServer visor; + private final McVersion mcVersion; + + public VisorAPIImpl(VisorServer visor, McVersion mcVersion){ + this.visor = visor; + this.mcVersion = mcVersion; + } + + @Override + public McVersion minecraftVersion(){ + return mcVersion; + } + + @Override + public boolean isVRPlayer(UUID uuid){ + VisorSession session = visor.sessions().get(uuid); + return session != null && session.vrActive(); + } + + @Override + public boolean isVRPlayer(Player player) { + return isVRPlayer(player.getUniqueId()); + } + + @Override + public VisorPlayer getVisorPlayer(UUID uuid){ + return VisorPlayerImpl.fromVisorSession(visor.sessions().get(uuid)); + } + + @Override + public VisorPlayer getVisorPlayer(Player player){ + return getVisorPlayer(player.getUniqueId()); + } + + @Override + public Collection getVisorPlayers(){ + List out = new ArrayList<>(); + for(VisorSession session : visor.sessions().all()){ + VisorPlayer visorPlayer = VisorPlayerImpl.fromVisorSession(session); + if(visorPlayer != null){ + out.add(visorPlayer); + } + } + return out; + } + + @Override + public VRPlayer getVRPlayer(UUID uuid){ + return VRPlayerImpl.fromVisorSession(visor.sessions().get(uuid)); + } + + @Override + public VRPlayer getVRPlayer(Player player){ + return getVRPlayer(player.getUniqueId()); + } + + @Override + public Collection getVRPlayers(){ + List out = new ArrayList<>(); + for(VisorSession session : visor.sessions().all()){ + VRPlayer vrPlayer = VRPlayerImpl.fromVisorSession(session); + if(vrPlayer != null){ + out.add(vrPlayer); + } + } + return out; + } + + @Override + public VisorChannel registerChannel(Plugin owner, String channel){ + return registerChannel(owner, channel, null); + } + + @Override + public VisorChannel registerChannel(Plugin owner, String channel, VisorChannelHandler handler){ + Messenger messenger = Bukkit.getMessenger(); + VisorChannelImpl impl = new VisorChannelImpl(owner, channel, handler); + PluginMessageListener listener = (ch, player, message) -> { + if(ch.equals(channel)){ + impl.handleIncoming(player, message); + } + }; + impl.attachListener(listener); + messenger.registerOutgoingPluginChannel(owner, channel); + messenger.registerIncomingPluginChannel(owner, channel, listener); + return impl; + } + + @Override + public void onSessionStart(VisorSession session){ + VisorPlayer visorPlayer = VisorPlayerImpl.fromVisorSession(session); + if(visorPlayer != null){ + Bukkit.getPluginManager().callEvent(new VisorPlayerJoinedEvent(visorPlayer)); + } + } + + @Override + public void onSessionEnd(VisorSession session){ + VisorPlayer visorPlayer = VisorPlayerImpl.fromVisorSession(session); + if(visorPlayer != null){ + Bukkit.getPluginManager().callEvent(new VisorPlayerLeftEvent(visorPlayer)); + } + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/VisorChannelImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/VisorChannelImpl.java new file mode 100644 index 0000000..bffbb10 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/VisorChannelImpl.java @@ -0,0 +1,116 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.network.VisorBuf; +import org.vmstudio.visor.api.network.VisorChannel; +import org.vmstudio.visor.api.network.VisorChannelHandler; +import org.vmstudio.visor.api.network.VisorPacket; + +public final class VisorChannelImpl implements VisorChannel { + private final Plugin owner; + private final String channel; + @Nullable + private final VisorChannelHandler rawHandler; + private final Map registry = new HashMap<>(); + private PluginMessageListener listener; + + public VisorChannelImpl(Plugin owner, String channel, @Nullable VisorChannelHandler rawHandler){ + this.owner = owner; + this.channel = channel; + this.rawHandler = rawHandler; + } + + public void attachListener(PluginMessageListener listener){ + this.listener = listener; + } + + public void handleIncoming(Player player, byte[] message){ + if(message != null && message.length > 0){ + VisorBuf buf = VisorBuf.wrap(message); + int id = buf.readUnsignedByte(); + Entry entry = registry.get(id); + if(entry != null){ + dispatch(entry, player, buf); + return; + } + } + if(rawHandler != null){ + rawHandler.onMessage(player, message); + } + } + + @SuppressWarnings("unchecked") + private void dispatch(Entry entry, Player player, VisorBuf buf){ + VisorPacket packet = ((Function) entry.reader).apply(buf); + ((BiConsumer) entry.handler).accept(player, packet); + } + + @Override + public String name(){ + return channel; + } + + @Override + public VisorChannel on(int id, Function reader, BiConsumer handler){ + if(id < 0 || id > 0xFF){ + throw new IllegalArgumentException("Packet id must be 0..255, got " + id); + } + registry.put(id, new Entry(reader, handler)); + return this; + } + + @Override + public void send(Player player, VisorPacket packet){ + VisorBuf buf = VisorBuf.create(); + buf.writeByte(packet.id()); + packet.write(buf); + send(player, buf.toBytes()); + } + + @Override + public void send(UUID uuid, VisorPacket packet){ + Player player = Bukkit.getPlayer(uuid); + if(player != null){ + send(player, packet); + } + } + + @Override + public void send(Player player, byte[] payload){ + if(player.isOnline()){ + player.sendPluginMessage(owner, channel, payload); + } + } + + @Override + public void send(UUID uuid, byte[] payload){ + Player player = Bukkit.getPlayer(uuid); + if(player != null){ + send(player, payload); + } + } + + @Override + public void unregister(){ + Messenger messenger = Bukkit.getMessenger(); + if(listener != null){ + messenger.unregisterIncomingPluginChannel(owner, channel, listener); + } + messenger.unregisterOutgoingPluginChannel(owner, channel); + } + + private record Entry(Function reader, + BiConsumer handler) { + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/api/VisorPlayerImpl.java b/src/main/java/org/vmstudio/visor/bukkit/api/VisorPlayerImpl.java new file mode 100644 index 0000000..840a776 --- /dev/null +++ b/src/main/java/org/vmstudio/visor/bukkit/api/VisorPlayerImpl.java @@ -0,0 +1,42 @@ +package org.vmstudio.visor.bukkit.api; + +import java.util.UUID; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.player.VisorPlayer; +import org.vmstudio.visor.bukkit.platform.BukkitPlatformPlayer; +import org.vmstudio.visor.common.session.VisorSession; + +public class VisorPlayerImpl implements VisorPlayer { + public final VisorSession session; + public final Player player; + + public VisorPlayerImpl(VisorSession session, Player player){ + this.session = session; + this.player = player; + } + + @Override + public UUID getUuid(){ + return player.getUniqueId(); + } + + @Override + public Player getMcPlayer(){ + return player; + } + + @Override + public String getClientVersion(){ + return session.clientVersion(); + } + + public static @Nullable VisorPlayer fromVisorSession(VisorSession session){ + if(session == null){ + return null; + } + Player player = ((BukkitPlatformPlayer) session.player()).handle(); + return session.vrActive() ? new VRPlayerImpl(session, player) : new VisorPlayerImpl(session, player); + } +} diff --git a/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformPlayer.java b/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformPlayer.java index de189b6..df2e022 100644 --- a/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformPlayer.java +++ b/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformPlayer.java @@ -82,6 +82,21 @@ public boolean isOnline(){ return handle.isOnline(); } + @Override + public double x(){ + return handle.getLocation().getX(); + } + + @Override + public double y(){ + return handle.getLocation().getY(); + } + + @Override + public double z(){ + return handle.getLocation().getZ(); + } + @Override public void sendPayload(byte[] channelData){ if(handle.isOnline()){ diff --git a/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformServer.java b/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformServer.java index 6792e99..aa5ed90 100644 --- a/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformServer.java +++ b/src/main/java/org/vmstudio/visor/bukkit/platform/BukkitPlatformServer.java @@ -16,18 +16,18 @@ import org.vmstudio.visor.common.platform.PlatformServer; import org.vmstudio.visor.common.platform.Scheduler; import org.vmstudio.visor.common.platform.VisorLogger; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; public final class BukkitPlatformServer implements PlatformServer { private final Plugin plugin; - private final VersionAdapter adapter; + private final VisorVersionAdapter adapter; private final McVersion mcVersion; private final ServerCore core; private final VisorLogger logger; private final Scheduler scheduler; - public BukkitPlatformServer(Plugin plugin, VersionAdapter adapter, McVersion mcVersion, + public BukkitPlatformServer(Plugin plugin, VisorVersionAdapter adapter, McVersion mcVersion, ServerCore core, VisorLogger logger){ this.plugin = plugin; this.adapter = adapter; @@ -41,7 +41,7 @@ public Plugin plugin(){ return plugin; } - public VersionAdapter adapter(){ + public VisorVersionAdapter adapter(){ return adapter; } diff --git a/src/main/java/org/vmstudio/visor/common/VisorServer.java b/src/main/java/org/vmstudio/visor/common/VisorServer.java index 2f45b50..2bfa629 100644 --- a/src/main/java/org/vmstudio/visor/common/VisorServer.java +++ b/src/main/java/org/vmstudio/visor/common/VisorServer.java @@ -18,8 +18,6 @@ import org.vmstudio.visor.protocol.value.Vec3f; public final class VisorServer { - private static final long VR_ONLY_KICK_DELAY_TICKS = 100L; - private final PlatformServer platform; private final SessionManager sessions = new SessionManager(); private final VrPrefsStore prefs = new VrPrefsStore(); @@ -77,7 +75,7 @@ public void onJoin(PlatformPlayer player){ if(session == null || !session.vrActive()){ player.disconnect("This server is VR-only."); } - }, VR_ONLY_KICK_DELAY_TICKS); + }, 200L); } public void onQuit(UUID uuid){ diff --git a/src/main/java/org/vmstudio/visor/common/broadcast/TrackerBroadcaster.java b/src/main/java/org/vmstudio/visor/common/broadcast/TrackerBroadcaster.java index 8e2c7a0..ab1ad5a 100644 --- a/src/main/java/org/vmstudio/visor/common/broadcast/TrackerBroadcaster.java +++ b/src/main/java/org/vmstudio/visor/common/broadcast/TrackerBroadcaster.java @@ -48,7 +48,7 @@ public void broadcast(VisorSession session){ currentUuids.add(t.uuid()); } Set newUuids = new HashSet<>(currentUuids); - newUuids.removeAll(vr.knownTrackers()); + newUuids.removeAll(vr.getKnownTrackers()); byte[] pose = VisorCodec.encode(new OtherPoseDataOut(uuid, vr.pose())); for(PlatformPlayer t : trackers){ @@ -56,12 +56,12 @@ public void broadcast(VisorSession session){ } if(!newUuids.isEmpty()){ - byte[] body = VisorCodec.encode(new OtherBodyTypeOut(uuid, vr.bodyType())); - byte[] left = VisorCodec.encode(new OtherLeftHandedOut(uuid, vr.leftHanded())); - byte[] scale = VisorCodec.encode(new OtherWorldScaleOut(uuid, vr.worldScale())); - byte[] height = VisorCodec.encode(new OtherFullHeightOut(uuid, vr.fullHeight())); - byte[] gun = VisorCodec.encode(new OtherGunAngleOut(uuid, vr.gunAngle())); - byte[] overlay = VisorCodec.encode(new OtherOverlayFocusedOut(uuid, vr.overlayFocused())); + byte[] body = VisorCodec.encode(new OtherBodyTypeOut(uuid, vr.getBodyType())); + byte[] left = VisorCodec.encode(new OtherLeftHandedOut(uuid, vr.isLeftHanded())); + byte[] scale = VisorCodec.encode(new OtherWorldScaleOut(uuid, vr.getWorldScale())); + byte[] height = VisorCodec.encode(new OtherFullHeightOut(uuid, vr.getFullHeight())); + byte[] gun = VisorCodec.encode(new OtherGunAngleOut(uuid, vr.getGunAngle())); + byte[] overlay = VisorCodec.encode(new OtherOverlayFocusedOut(uuid, vr.isOverlayFocused())); for(PlatformPlayer t : trackers){ if(!newUuids.contains(t.uuid())){ continue; @@ -75,29 +75,29 @@ public void broadcast(VisorSession session){ } } - if(!vr.bodyType().equals(vr.bodyTypeLastSent())){ - sendDelta(trackers, newUuids, new OtherBodyTypeOut(uuid, vr.bodyType())); - vr.setBodyTypeLastSent(vr.bodyType()); + if(!vr.getBodyType().equals(vr.getBodyTypeLastSent())){ + sendDelta(trackers, newUuids, new OtherBodyTypeOut(uuid, vr.getBodyType())); + vr.setBodyTypeLastSent(vr.getBodyType()); } - if(vr.leftHanded() != vr.leftHandedLastSent()){ - sendDelta(trackers, newUuids, new OtherLeftHandedOut(uuid, vr.leftHanded())); - vr.setLeftHandedLastSent(vr.leftHanded()); + if(vr.isLeftHanded() != vr.isLeftHandedLastSent()){ + sendDelta(trackers, newUuids, new OtherLeftHandedOut(uuid, vr.isLeftHanded())); + vr.setLeftHandedLastSent(vr.isLeftHanded()); } - if(vr.worldScale() != vr.worldScaleLastSent()){ - sendDelta(trackers, newUuids, new OtherWorldScaleOut(uuid, vr.worldScale())); - vr.setWorldScaleLastSent(vr.worldScale()); + if(vr.getWorldScale() != vr.getWorldScaleLastSent()){ + sendDelta(trackers, newUuids, new OtherWorldScaleOut(uuid, vr.getWorldScale())); + vr.setWorldScaleLastSent(vr.getWorldScale()); } - if(vr.fullHeight() != vr.fullHeightLastSent()){ - sendDelta(trackers, newUuids, new OtherFullHeightOut(uuid, vr.fullHeight())); - vr.setFullHeightLastSent(vr.fullHeight()); + if(vr.getFullHeight() != vr.getFullHeightLastSent()){ + sendDelta(trackers, newUuids, new OtherFullHeightOut(uuid, vr.getFullHeight())); + vr.setFullHeightLastSent(vr.getFullHeight()); } - if(vr.gunAngle() != vr.gunAngleLastSent()){ - sendDelta(trackers, null, new OtherGunAngleOut(uuid, vr.gunAngle())); - vr.setGunAngleLastSent(vr.gunAngle()); + if(vr.getGunAngle() != vr.getGunAngleLastSent()){ + sendDelta(trackers, null, new OtherGunAngleOut(uuid, vr.getGunAngle())); + vr.setGunAngleLastSent(vr.getGunAngle()); } - if(vr.overlayFocused() != vr.overlayFocusedLastSent()){ - sendDelta(trackers, newUuids, new OtherOverlayFocusedOut(uuid, vr.overlayFocused())); - vr.setOverlayFocusedLastSent(vr.overlayFocused()); + if(vr.isOverlayFocused() != vr.isOverlayFocusedLastSent()){ + sendDelta(trackers, newUuids, new OtherOverlayFocusedOut(uuid, vr.isOverlayFocused())); + vr.setOverlayFocusedLastSent(vr.isOverlayFocused()); } vr.setKnownTrackers(currentUuids); diff --git a/src/main/java/org/vmstudio/visor/common/handler/InboundHandler.java b/src/main/java/org/vmstudio/visor/common/handler/InboundHandler.java index f9d4996..eca9b8d 100644 --- a/src/main/java/org/vmstudio/visor/common/handler/InboundHandler.java +++ b/src/main/java/org/vmstudio/visor/common/handler/InboundHandler.java @@ -3,7 +3,7 @@ import org.vmstudio.visor.common.platform.PlatformPlayer; import org.vmstudio.visor.common.platform.VisorLogger; import org.vmstudio.visor.common.session.*; -import org.vmstudio.visor.common.settings.SupportedMovement; +import org.vmstudio.visor.api.player.SupportedMovement; import org.vmstudio.visor.common.settings.VisorSettings; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorInbound; @@ -51,6 +51,7 @@ public void handle(PlatformPlayer sender, VisorInbound payload){ if (payload instanceof PoseDataIn p) { vr.setPose(p.pose()); + vr.pushPoseSnapshot(p.pose(), sender.x(), sender.y(), sender.z()); } else if (payload instanceof LeftHandedIn p) { vr.setLeftHanded(p.leftHanded()); } else if (payload instanceof ActiveHandIn p) { diff --git a/src/main/java/org/vmstudio/visor/common/platform/PlatformPlayer.java b/src/main/java/org/vmstudio/visor/common/platform/PlatformPlayer.java index 6529305..7c9059b 100644 --- a/src/main/java/org/vmstudio/visor/common/platform/PlatformPlayer.java +++ b/src/main/java/org/vmstudio/visor/common/platform/PlatformPlayer.java @@ -16,6 +16,12 @@ public interface PlatformPlayer { boolean isOnline(); + double x(); + + double y(); + + double z(); + void sendPayload(byte[] channelData); void disconnect(String reason); diff --git a/src/main/java/org/vmstudio/visor/common/session/PoseHistoryBuffer.java b/src/main/java/org/vmstudio/visor/common/session/PoseHistoryBuffer.java new file mode 100644 index 0000000..20e5d9f --- /dev/null +++ b/src/main/java/org/vmstudio/visor/common/session/PoseHistoryBuffer.java @@ -0,0 +1,33 @@ +package org.vmstudio.visor.common.session; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; + +import org.vmstudio.visor.api.player.VRPoseHistory; +import org.vmstudio.visor.protocol.value.PoseData; + +public final class PoseHistoryBuffer { + public record Snapshot(PoseData pose, double ox, double oy, double oz){} + private final Deque history = new ArrayDeque<>(); + + public void push(PoseData pose, double ox, double oy, double oz){ + history.addFirst(new Snapshot(pose, ox, oy, oz)); + while(history.size() > VRPoseHistory.HISTORY_LIMIT){ + history.removeLast(); + } + } + + /** + * get an immutable copy of the snapshot history + * + * @return stored snapshots ordered from newest to oldest + */ + public List snapshot(){ + return List.copyOf(history); + } + + public void clear(){ + history.clear(); + } +} diff --git a/src/main/java/org/vmstudio/visor/common/session/SessionManager.java b/src/main/java/org/vmstudio/visor/common/session/SessionManager.java index d0b699f..e729313 100644 --- a/src/main/java/org/vmstudio/visor/common/session/SessionManager.java +++ b/src/main/java/org/vmstudio/visor/common/session/SessionManager.java @@ -4,14 +4,20 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import lombok.Setter; import org.vmstudio.visor.common.platform.PlatformPlayer; public final class SessionManager { private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + @Setter + private Listener listener; public VisorSession register(PlatformPlayer player, boolean vrActive, String clientVersion){ VisorSession session = new VisorSession(player, vrActive, clientVersion); sessions.put(player.uuid(), session); + if(listener != null){ + listener.onSessionStart(session); + } return session; } @@ -24,10 +30,18 @@ public boolean has(UUID uuid){ } public VisorSession remove(UUID uuid){ - return sessions.remove(uuid); + VisorSession session = sessions.remove(uuid); + if(listener != null && session != null){ + listener.onSessionEnd(session); + } + return session; } public Collection all(){ return sessions.values(); } + public interface Listener { + void onSessionStart(VisorSession session); + void onSessionEnd(VisorSession session); + } } diff --git a/src/main/java/org/vmstudio/visor/common/session/VrState.java b/src/main/java/org/vmstudio/visor/common/session/VrState.java index b128540..0e25252 100644 --- a/src/main/java/org/vmstudio/visor/common/session/VrState.java +++ b/src/main/java/org/vmstudio/visor/common/session/VrState.java @@ -1,14 +1,20 @@ package org.vmstudio.visor.common.session; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.vmstudio.visor.api.player.VRPlayer; import org.vmstudio.visor.protocol.value.PoseData; +@Getter +@Setter public final class VrState { - public static final float DEFAULT_FULL_HEIGHT = 1.52f; - public static final float DEFAULT_GUN_ANGLE = 60.0f; + private final PoseHistoryBuffer poseHistory = new PoseHistoryBuffer(); + private static final String DEFAULT_BODY_TYPE = "null"; private PoseData pose; @@ -17,17 +23,17 @@ public final class VrState { private int offhandSlot; private String bodyType = DEFAULT_BODY_TYPE; private float worldScale = 1.0f; - private float fullHeight = DEFAULT_FULL_HEIGHT; + private float fullHeight = VRPlayer.DEFAULT_FULL_HEIGHT; private float rotationY; - private float gunAngle = DEFAULT_GUN_ANGLE; + private float gunAngle = VRPlayer.DEFAULT_GUN_ANGLE; private boolean overlayFocused; private boolean crawling; private String bodyTypeLastSent; private boolean leftHandedLastSent; private float worldScaleLastSent = 1.0f; - private float fullHeightLastSent = DEFAULT_FULL_HEIGHT; - private float gunAngleLastSent = DEFAULT_GUN_ANGLE; + private float fullHeightLastSent = VRPlayer.DEFAULT_FULL_HEIGHT; + private float gunAngleLastSent = VRPlayer.DEFAULT_GUN_ANGLE; private boolean overlayFocusedLastSent; private Set knownTrackers = new HashSet<>(); @@ -36,143 +42,16 @@ public PoseData pose(){ return pose; } - public void setPose(PoseData pose){ - this.pose = pose; - } - - public boolean leftHanded(){ - return leftHanded; - } - - public void setLeftHanded(boolean v){ - this.leftHanded = v; - } - - public boolean activeHandMain(){ - return activeHandMain; - } - - public void setActiveHandMain(boolean v){ - this.activeHandMain = v; - } - - public int offhandSlot(){ - return offhandSlot; - } - - public void setOffhandSlot(int v){ - this.offhandSlot = v; + public void pushPoseSnapshot(PoseData pose, double ox, double oy, double oz){ + poseHistory.push(pose, ox, oy, oz); } - public String bodyType(){ - return bodyType; + public List getPoseHistory(){ + return poseHistory.snapshot(); } public void setBodyType(String v){ - this.bodyType = v == null ? "" : v; - } - - public float worldScale(){ - return worldScale; - } - - public void setWorldScale(float v){ - this.worldScale = v; - } - - public float fullHeight(){ - return fullHeight; - } - - public void setFullHeight(float v){ - this.fullHeight = v; - } - - public float rotationY(){ - return rotationY; - } - - public void setRotationY(float v){ - this.rotationY = v; - } - - public float gunAngle(){ - return gunAngle; - } - - public void setGunAngle(float v){ - this.gunAngle = v; - } - - public boolean overlayFocused(){ - return overlayFocused; - } - - public void setOverlayFocused(boolean v){ - this.overlayFocused = v; - } - - public boolean crawling(){ - return crawling; - } - - public void setCrawling(boolean v){ - this.crawling = v; - } - - public String bodyTypeLastSent(){ - return bodyTypeLastSent; - } - - public void setBodyTypeLastSent(String v){ - this.bodyTypeLastSent = v; - } - - public boolean leftHandedLastSent(){ - return leftHandedLastSent; - } - - public void setLeftHandedLastSent(boolean v){ - this.leftHandedLastSent = v; - } - - public float worldScaleLastSent(){ - return worldScaleLastSent; - } - - public void setWorldScaleLastSent(float v){ - this.worldScaleLastSent = v; - } - - public float fullHeightLastSent(){ - return fullHeightLastSent; - } - - public void setFullHeightLastSent(float v){ - this.fullHeightLastSent = v; - } - - public float gunAngleLastSent(){ - return gunAngleLastSent; - } - - public void setGunAngleLastSent(float v){ - this.gunAngleLastSent = v; - } - - public boolean overlayFocusedLastSent(){ - return overlayFocusedLastSent; - } - - public void setOverlayFocusedLastSent(boolean v){ - this.overlayFocusedLastSent = v; - } - - public Set knownTrackers(){ - return knownTrackers; + this.bodyType = v == null ? DEFAULT_BODY_TYPE : v; } - public void setKnownTrackers(Set trackers){ - this.knownTrackers = trackers; - } } diff --git a/src/main/java/org/vmstudio/visor/common/settings/SettingsLoader.java b/src/main/java/org/vmstudio/visor/common/settings/SettingsLoader.java index 53e1d49..c9cc8fe 100644 --- a/src/main/java/org/vmstudio/visor/common/settings/SettingsLoader.java +++ b/src/main/java/org/vmstudio/visor/common/settings/SettingsLoader.java @@ -9,6 +9,7 @@ import java.util.Locale; import java.util.Map; +import org.vmstudio.visor.api.player.SupportedMovement; import org.vmstudio.visor.common.platform.VisorLogger; import org.yaml.snakeyaml.Yaml; @@ -97,16 +98,16 @@ private static void writeDefault(Path file) throws IOException { } private static final String DEFAULT_YAML = """ - # Visor server settings + # Visor Plugin serverDebug: false vrOnly: false twoHandedVR: false - betterSwinging: true + betterSwinging: false swingingRepairDelay: 400 - roomCrawlingSupported: true - roomClimbingSupported: true + roomCrawlingSupported: false + roomClimbingSupported: false pvpVRvsVanilla: true pvpVRvsVR: true @@ -115,12 +116,12 @@ private static void writeDefault(Path file) throws IOException { creeperSwellDistance: 1.75 # CONTROLLER | TELEPORT | BOTH - supportedMovement: BOTH + supportedMovement: CONTROLLER teleportUpLimit: 1 teleportDownLimit: 4 teleportForwardLimit: 16 - trackersSupported: true + trackersSupported: false """; } diff --git a/src/main/java/org/vmstudio/visor/common/settings/VisorSettings.java b/src/main/java/org/vmstudio/visor/common/settings/VisorSettings.java index 476c751..130c47b 100644 --- a/src/main/java/org/vmstudio/visor/common/settings/VisorSettings.java +++ b/src/main/java/org/vmstudio/visor/common/settings/VisorSettings.java @@ -1,5 +1,7 @@ package org.vmstudio.visor.common.settings; +import org.vmstudio.visor.api.player.SupportedMovement; + public record VisorSettings( boolean serverDebug, boolean vrOnly, @@ -22,20 +24,20 @@ public static VisorSettings defaults(){ return new VisorSettings( false, false, - true, - true, + false, + false, 400L, - true, - true, + false, + false, true, true, false, 1.75, - SupportedMovement.BOTH, + SupportedMovement.CONTROLLER, 1, 4, 16, - true); + false); } public String toClientYaml(){ diff --git a/nms-v1_20_R1/build.gradle b/versions/nms-v1_20_R1/build.gradle similarity index 100% rename from nms-v1_20_R1/build.gradle rename to versions/nms-v1_20_R1/build.gradle diff --git a/nms-v1_20_R1/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java b/versions/nms-v1_20_R1/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java similarity index 98% rename from nms-v1_20_R1/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java rename to versions/nms-v1_20_R1/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java index 352d6c3..76acfbb 100644 --- a/nms-v1_20_R1/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java +++ b/versions/nms-v1_20_R1/src/main/java/org/vmstudio/visor/nms/v1_20_R1/NmsV1_20_R1Adapter.java @@ -34,15 +34,15 @@ import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_20_R1Adapter implements VersionAdapter { +public final class NmsV1_20_R1Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_20_R1"); private Plugin plugin; diff --git a/nms-v1_21_1/build.gradle b/versions/nms-v1_21_1/build.gradle similarity index 100% rename from nms-v1_21_1/build.gradle rename to versions/nms-v1_21_1/build.gradle diff --git a/nms-v1_21_1/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java b/versions/nms-v1_21_1/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java similarity index 97% rename from nms-v1_21_1/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java rename to versions/nms-v1_21_1/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java index 8a32f5c..ea3f2b0 100644 --- a/nms-v1_21_1/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java +++ b/versions/nms-v1_21_1/src/main/java/org/vmstudio/visor/nms/v1_21_1/NmsV1_21_1Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_1Adapter implements VersionAdapter { +public final class NmsV1_21_1Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_1"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_10/build.gradle b/versions/nms-v1_21_10/build.gradle similarity index 100% rename from nms-v1_21_10/build.gradle rename to versions/nms-v1_21_10/build.gradle diff --git a/nms-v1_21_10/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java b/versions/nms-v1_21_10/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java similarity index 97% rename from nms-v1_21_10/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java rename to versions/nms-v1_21_10/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java index bdebf04..32aff77 100644 --- a/nms-v1_21_10/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java +++ b/versions/nms-v1_21_10/src/main/java/org/vmstudio/visor/nms/v1_21_10/NmsV1_21_10Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_10Adapter implements VersionAdapter { +public final class NmsV1_21_10Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_10"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_11/build.gradle b/versions/nms-v1_21_11/build.gradle similarity index 100% rename from nms-v1_21_11/build.gradle rename to versions/nms-v1_21_11/build.gradle diff --git a/nms-v1_21_11/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java b/versions/nms-v1_21_11/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java similarity index 97% rename from nms-v1_21_11/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java rename to versions/nms-v1_21_11/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java index 1e46a54..cdc122a 100644 --- a/nms-v1_21_11/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java +++ b/versions/nms-v1_21_11/src/main/java/org/vmstudio/visor/nms/v1_21_11/NmsV1_21_11Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_11Adapter implements VersionAdapter { +public final class NmsV1_21_11Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_11"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_3/build.gradle b/versions/nms-v1_21_3/build.gradle similarity index 100% rename from nms-v1_21_3/build.gradle rename to versions/nms-v1_21_3/build.gradle diff --git a/nms-v1_21_3/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java b/versions/nms-v1_21_3/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java similarity index 97% rename from nms-v1_21_3/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java rename to versions/nms-v1_21_3/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java index 3acda5c..009b041 100644 --- a/nms-v1_21_3/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java +++ b/versions/nms-v1_21_3/src/main/java/org/vmstudio/visor/nms/v1_21_3/NmsV1_21_3Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_3Adapter implements VersionAdapter { +public final class NmsV1_21_3Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_3"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_4/build.gradle b/versions/nms-v1_21_4/build.gradle similarity index 100% rename from nms-v1_21_4/build.gradle rename to versions/nms-v1_21_4/build.gradle diff --git a/nms-v1_21_4/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java b/versions/nms-v1_21_4/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java similarity index 97% rename from nms-v1_21_4/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java rename to versions/nms-v1_21_4/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java index 9f46d3d..d47cb44 100644 --- a/nms-v1_21_4/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java +++ b/versions/nms-v1_21_4/src/main/java/org/vmstudio/visor/nms/v1_21_4/NmsV1_21_4Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_4Adapter implements VersionAdapter { +public final class NmsV1_21_4Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_4"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_5/build.gradle b/versions/nms-v1_21_5/build.gradle similarity index 100% rename from nms-v1_21_5/build.gradle rename to versions/nms-v1_21_5/build.gradle diff --git a/nms-v1_21_5/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java b/versions/nms-v1_21_5/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java similarity index 97% rename from nms-v1_21_5/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java rename to versions/nms-v1_21_5/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java index d7c0d48..2d44e1d 100644 --- a/nms-v1_21_5/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java +++ b/versions/nms-v1_21_5/src/main/java/org/vmstudio/visor/nms/v1_21_5/NmsV1_21_5Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_5Adapter implements VersionAdapter { +public final class NmsV1_21_5Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_5"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_6/build.gradle b/versions/nms-v1_21_6/build.gradle similarity index 100% rename from nms-v1_21_6/build.gradle rename to versions/nms-v1_21_6/build.gradle diff --git a/nms-v1_21_6/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java b/versions/nms-v1_21_6/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java similarity index 97% rename from nms-v1_21_6/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java rename to versions/nms-v1_21_6/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java index 1c02b2b..22a15ce 100644 --- a/nms-v1_21_6/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java +++ b/versions/nms-v1_21_6/src/main/java/org/vmstudio/visor/nms/v1_21_6/NmsV1_21_6Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_6Adapter implements VersionAdapter { +public final class NmsV1_21_6Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_6"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_7/build.gradle b/versions/nms-v1_21_7/build.gradle similarity index 100% rename from nms-v1_21_7/build.gradle rename to versions/nms-v1_21_7/build.gradle diff --git a/nms-v1_21_7/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java b/versions/nms-v1_21_7/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java similarity index 97% rename from nms-v1_21_7/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java rename to versions/nms-v1_21_7/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java index 25f1d2d..79af9ab 100644 --- a/nms-v1_21_7/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java +++ b/versions/nms-v1_21_7/src/main/java/org/vmstudio/visor/nms/v1_21_7/NmsV1_21_7Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_7Adapter implements VersionAdapter { +public final class NmsV1_21_7Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_7"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_8/build.gradle b/versions/nms-v1_21_8/build.gradle similarity index 100% rename from nms-v1_21_8/build.gradle rename to versions/nms-v1_21_8/build.gradle diff --git a/nms-v1_21_8/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java b/versions/nms-v1_21_8/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java similarity index 97% rename from nms-v1_21_8/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java rename to versions/nms-v1_21_8/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java index 65b456d..3f42e2b 100644 --- a/nms-v1_21_8/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java +++ b/versions/nms-v1_21_8/src/main/java/org/vmstudio/visor/nms/v1_21_8/NmsV1_21_8Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_8Adapter implements VersionAdapter { +public final class NmsV1_21_8Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_8"); private static final double MAX_REACH_SQ = 36.0; diff --git a/nms-v1_21_9/build.gradle b/versions/nms-v1_21_9/build.gradle similarity index 100% rename from nms-v1_21_9/build.gradle rename to versions/nms-v1_21_9/build.gradle diff --git a/nms-v1_21_9/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java b/versions/nms-v1_21_9/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java similarity index 97% rename from nms-v1_21_9/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java rename to versions/nms-v1_21_9/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java index e0767ca..5fdd01a 100644 --- a/nms-v1_21_9/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java +++ b/versions/nms-v1_21_9/src/main/java/org/vmstudio/visor/nms/v1_21_9/NmsV1_21_9Adapter.java @@ -19,7 +19,6 @@ import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -27,15 +26,15 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.vmstudio.visor.nms.McVersion; -import org.vmstudio.visor.nms.VersionAdapter; +import org.vmstudio.visor.api.nms.McVersion; +import org.vmstudio.visor.bukkit.adapter.VisorVersionAdapter; import org.vmstudio.visor.protocol.DirectionValue; import org.vmstudio.visor.protocol.VisorCodec; import org.vmstudio.visor.protocol.VisorProtocol; import org.vmstudio.visor.protocol.toclient.BlockDamageOut; import org.vmstudio.visor.protocol.value.VBlockPos; -public final class NmsV1_21_9Adapter implements VersionAdapter { +public final class NmsV1_21_9Adapter implements VisorVersionAdapter { private static final Logger LOG = Logger.getLogger("VisorPlugin/nms-v1_21_9"); private static final double MAX_REACH_SQ = 36.0; From 1aadb1565972a5b829069c07cbd57486ea73cc8b Mon Sep 17 00:00:00 2001 From: Weever <109693401+Weever1337@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:31:01 +0300 Subject: [PATCH 2/2] removed lombok.config --- lombok.config | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 lombok.config diff --git a/lombok.config b/lombok.config deleted file mode 100644 index df71bb6..0000000 --- a/lombok.config +++ /dev/null @@ -1,2 +0,0 @@ -config.stopBubbling = true -lombok.addLombokGeneratedAnnotation = true