Compare commits

..
20 Commits
Author SHA1 Message Date
Jikoo 1fbaa5b2a9 Reduce redundant saves for ender chests as well
While they don't suffer the same owner-related issues that player inventories did, there's no need to save so many times.
2016-04-11 18:45:23 -04:00
Jikoo eeb28b4210 Reduce duplicate saves and fix for owner coming online for all versions 2016-04-11 18:12:21 -04:00
Jikoo 0ebe6fe132 Properly update PlayerInventory owner when they come online
Closes #5
2016-04-11 17:59:28 -04:00
Jikoo e39ab10797 Don't load from player.dat for online players
Fixes lishid#40 and #5
2016-04-11 15:44:25 -04:00
Jikoo fb48f6a783 Fix up legacy code to use new interfaces
This took way more time than it was worth.
Converted line endings to Unix, some other trivial crap.
Thanks to Phoenix616 for already having a multi-version pom, it would have taken me much longer to figure out without a functional example.

The only legacy code which is not supported is that of the pre-versioned package era. I compiled a copy of Craftbukkit about 15 commits prior to the introduction of versioned packages, and NMS had already been fiddled with. Anyone who needs support for builds in that range can use an older OpenInv build instead, it's not like UUIDs are a big concern.
2016-03-04 05:49:07 -05:00
Jikoo d761b6bfe4 Actually allow for offhand modification, whoops. 2016-03-04 00:53:21 -05:00
Jikoo 5b3ba79b82 Maven, huge refactor, interface changes
Among other things, these interface changes allow for supporting pre-UUID Minecraft versions again. I may eventually backport older versions, but I don't know that I'll be able to lay hands on old enough jars.
Removed static access
Renamed all methods to follow conventions
2016-03-03 23:33:54 -05:00
Jikoo 3549431fbc Removed item wand functionality
Anyone who can use it has to be able to run the command anyway. If they can run the command, they can use tab completion to automatically fill in anyone online's name. There's no need to keep around a bunch of deprecated code for a function I sincerely doubt anyone actually uses.
2016-03-01 12:30:12 -05:00
Jikoo 17b0cb6efe Fix players without permission to modify inventories inserting items 2016-03-01 12:22:26 -05:00
Jikoo 2d7522554c Use AnyChest when an ocelot is on a chest.
Closes #2
2016-03-01 12:09:29 -05:00
Jikoo 131f874329 Added offhand slot display and manipulation 2016-03-01 11:44:38 -05:00
Jikoo a623af5119 1.9.0 2016-03-01 00:52:57 -05:00
Jikoo 8f17bd0237 Remove update checker and bumped version for release
Added myself to the authors list as I've been maintaining this fork for a while
2015-11-16 23:21:34 -05:00
Jikoo 20fb61705d Update to 1.8.8, properly declare permissions in plugin.yml 2015-11-16 23:12:46 -05:00
Jikoo c0f513177a Removed additional async lookup notification, current is pretty fast. 2015-05-19 08:11:53 -04:00
Jikoo b3a3947b03 1.8.4
The easiest NMS update ever.
2015-05-19 08:10:39 -04:00
Jikoo c7bab7d451 Whoops, didn't make sure my autoformatter's settings matched the style. 2015-03-12 16:42:00 -04:00
Jikoo 4058463f7f OpenInv 2.2.7
Updated for Spigot 1.8.3
Dropped support for all other versions
Fixed lookup not working for players on first login/incomplete names
2015-03-11 16:48:21 -04:00
Jikoo 21cd1926ae Don't find username on the main thread. Not friendly with partial names. 2014-12-01 16:11:52 -05:00
Jikoo 2a66b2e81b Updated to Spigot 1.8 2014-12-01 14:10:14 -05:00
255 changed files with 12216 additions and 13520 deletions

No files matched your search

-10
View File
@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "monthly"
@@ -1,55 +0,0 @@
name: Auto-merge Dependabot PRs
on:
workflow_run:
workflows: [ "Pull Request" ]
types: [ completed ]
jobs:
merge-dependabot:
if: "github.actor == 'dependabot[bot]'
&& github.event.workflow_run.event == 'pull_request'
&& github.event.workflow_run.conclusion == 'success'"
runs-on: ubuntu-latest
steps:
# Note: this is directly from GitHub's example for using data from a triggering workflow:
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
- name: 'Download artifact'
uses: actions/github-script@v7
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr_number"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data));
# This might be a useless use of cat, but I'm not sure what shell Actions is going to be running.
- name: Add Pull Number Variable
run: |-
unzip pr_number.zip
echo "PR_NUMBER=$(cat pr_number)" >> "$GITHUB_ENV"
- name: Approve
uses: hmarr/auto-approve-action@v4.0.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
pull-request-number: "${{ env.PR_NUMBER }}"
- name: Merge
uses: pascalgn/automerge-action@v0.16.4
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_LABELS: "dependencies,java"
MERGE_METHOD: "squash"
PULL_REQUEST: "${{ env.PR_NUMBER }}"
-45
View File
@@ -1,45 +0,0 @@
name: OpenInv CI
on:
push:
branches:
- 'master'
tags-ignore:
- '**'
paths-ignore:
- resource-pack/**
# Enable running CI via other Actions, i.e. for drafting releases and handling PRs.
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
# Install Spigot dependencies if necessary.
- name: Install Spigot Dependencies
run: . scripts/install_spigot_dependencies.sh
- name: Build With Maven
run: mvn -e clean package -am -P all
# Upload artifacts
- name: Upload Distributable Jar
id: upload-final
uses: actions/upload-artifact@v4
with:
name: dist
path: ./target/*.jar
- name: Upload API Jar
id: upload-api
uses: actions/upload-artifact@v4
with:
name: api
path: ./api/target/openinvapi*.jar
-45
View File
@@ -1,45 +0,0 @@
name: Draft Github Release
on:
push:
tags:
- '**'
jobs:
run-ci:
uses: Jikoo/OpenInv/.github/workflows/ci.yml@master
draft-release:
needs: [ run-ci ]
runs-on: ubuntu-latest
steps:
# Fetch all history - used to assemble changelog.
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set Release Variables
run: bash ./scripts/set_release_env.sh
- name: Download build
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Package resource pack
run: |-
pushd resource-pack
zip -r ../dist/openinv-legibility-pack.zip .
popd
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ env.VERSIONED_NAME }}
body: ${{ env.GENERATED_CHANGELOG }}
draft: true
prerelease: false
files: ./dist/**
-37
View File
@@ -1,37 +0,0 @@
name: Release to CurseForge
on:
release:
types: [ released ]
jobs:
curseforge_release:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch Github Release Asset
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ github.event.release.id }}
file: OpenInv.jar
- name: Set CurseForge Variables
run: . scripts/set_curseforge_env.sh "${{ github.event.release.body }}"
- name: Create CurseForge Release
uses: itsmeow/curseforge-upload@v3
with:
token: "${{ secrets.CURSEFORGE_TOKEN }}"
project_id: 31432
game_endpoint: minecraft
file_path: ./OpenInv.jar
display_name: "${{ github.event.release.name }}"
game_versions: "${{ env.CURSEFORGE_MINECRAFT_VERSIONS }}"
release_type: release
changelog_type: markdown
changelog: "${{ env.CURSEFORGE_CHANGELOG }}"
-22
View File
@@ -1,22 +0,0 @@
name: Pull Request
on:
pull_request:
jobs:
run-ci:
uses: Jikoo/OpenInv/.github/workflows/ci.yml@master
store-dependabot-pr-data:
if: "github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'"
runs-on: ubuntu-latest
steps:
# Note: this is directly from GitHub's example for using data from a triggering workflow:
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
- name: Store Pull Number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/pr_number
- uses: actions/upload-artifact@v4
with:
name: pr_number
path: pr/
-24
View File
@@ -1,24 +0,0 @@
name: Resource Pack CI
on:
push:
branches:
- 'master'
tags-ignore:
- '**'
paths:
- resource-pack/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build resource pack
id: upload-resource-pack
uses: actions/upload-artifact@v4
with:
name: openinv-legibility-pack
path: ./resource-pack/
compression-level: 9
-10
View File
@@ -1,10 +0,0 @@
**/.settings
**/.project
**/.classpath
**/.idea/
**.iml
**/target/
**/bin/
**/out/
**/dependency-reduced-pom.xml
**/pom.xml.versionsBackup
+19 -50
View File
@@ -1,57 +1,26 @@
## About
OpenInv is a [Bukkit plugin](https://dev.bukkit.org/projects/openinv) which allows users to open and edit anyone's inventory or ender chest - online or not!
## Goals
This fork of OpenInv was created when the lag caused by offline player lookups post-UUID migration became too much to bear. Overall, it does not differ much from the original.
## Features
- **OpenInv**: Open anyone's inventory, even if they're offline.
- Read-only mode! Don't grant edit permission.
- Cross-world support! Allow access only from the same world.
- No duplicate slots! Only armor is accessible when opening self (if allowed at all)!
- Drop items as the player! Place items in the dropper slot in the bottom right. Can be disabled via permission!
- Allow any item in armor slots! Configurable via permission.
- **OpenEnder**: Open anyone's ender chest, even if they're offline.
- Allow access only to own ender chest! Don't grant permission to open others.
- Read-only mode! Don't grant edit permission.
- Cross-world support! Allow access only from the same world.
- **SilentContainer**: Open containers without displaying an animation or making sound.
- **AnyContainer**: Open containers, even if blocked by ocelots or blocks.
### Changes of Note
- Removed updater
- Removed wand
## Commands
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Commands).
## Previous Versions
Any version of Minecraft this fork has been existent for should have a [release](https://github.com/Jikoo/OpenInv/releases) tagged. Features are not backported, however.
## Permissions
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Permissions)
## For Developers
### As a Dependency
The OpenInv API is available via [JitPack](https://jitpack.io/).
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>com.github.jikoo.OpenInv</groupId>
<artifactId>openinvapi</artifactId>
<version>${openinv.version}</version>
</dependency>
</dependencies>
## License
```
Copyright (C) 2011-2014 lishid. All rights reserved.
### Compilation
To compile, the relevant Spigot jars must be installed in the local repository.
As OpenInv is compiled against Mojang's mappings, you must run BuildTools with the `--remapped` argument:
`java -jar BuildTools.jar --remapped --rev $serverVersion`
`$serverVersion` is the version of the server, i.e. `1.18.1`
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.
To compile just the API, execute Maven as usual:
`mvn clean package`
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
To compile the full plugin, use the provided profile with `-P all`:
`mvn clean package -am -P all`
For more information, check out the [official Maven guide](http://maven.apache.org/guides/introduction/introduction-to-profiles.html).
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
-72
View File
@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>5.1.3</version>
</parent>
<artifactId>openinvtogglepersist</artifactId>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>OITogglePersist</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-final-jar</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.parent.build.directory}</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}</directory>
<includes>
<include>${project.build.finalName}.jar</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,143 +0,0 @@
package com.github.jikoo.openinv.togglepersist;
import com.google.errorprone.annotations.Keep;
import com.lishid.openinv.event.PlayerToggledEvent;
import com.lishid.openinv.util.setting.PlayerToggle;
import com.lishid.openinv.util.setting.PlayerToggles;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
public class TogglePersist extends JavaPlugin implements Listener {
private final Map<UUID, Set<String>> enabledToggles = new HashMap<>();
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
File file = new File(getDataFolder(), "toggles.yml");
// If there's no save file, there's nothing to load.
if (!file.exists()) {
return;
}
Configuration loaded = YamlConfiguration.loadConfiguration(file);
// For each toggle, enable loaded players.
for (String toggleName : loaded.getKeys(false)) {
PlayerToggle toggle = PlayerToggles.get(toggleName);
// Ensure toggle exists.
if (toggle == null) {
continue;
}
for (String idString : loaded.getStringList(toggleName)) {
// Ensure valid UUID.
UUID uuid;
try {
uuid = UUID.fromString(idString);
} catch (IllegalArgumentException e) {
continue;
}
// Track that toggle is enabled.
set(uuid, toggleName);
}
}
}
private void set(UUID playerId, String toggleName) {
enabledToggles.compute(playerId, (uuid, toggles) -> {
if (toggles == null) {
toggles = new HashSet<>();
}
toggles.add(toggleName);
return toggles;
});
}
@Override
public void onDisable() {
Map<String, List<String>> converted = getSaveData();
YamlConfiguration data = new YamlConfiguration();
for (Map.Entry<String, List<String>> playerToggle : converted.entrySet()) {
data.set(playerToggle.getKey(), playerToggle.getValue());
}
File file = new File(getDataFolder(), "toggles.yml");
try {
data.save(file);
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Unable to save player toggle states", e);
}
}
private @NotNull Map<String, List<String>> getSaveData() {
Map<String, List<String>> converted = new HashMap<>();
for (Map.Entry<UUID, Set<String>> playerToggles : enabledToggles.entrySet()) {
String idString = playerToggles.getKey().toString();
for (String toggleName : playerToggles.getValue()) {
// Add player ID to listing for each enabled toggle.
converted.compute(toggleName, (name, ids) -> {
if (ids == null) {
ids = new ArrayList<>();
}
ids.add(idString);
return ids;
});
}
}
return converted;
}
@Keep
@EventHandler
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
UUID playerId = event.getPlayer().getUniqueId();
Set<String> toggleNames = enabledToggles.get(playerId);
if (toggleNames == null) {
return;
}
for (String toggleName : toggleNames) {
PlayerToggle toggle = PlayerToggles.get(toggleName);
if (toggle != null) {
toggle.set(playerId, true);
}
}
}
@Keep
@EventHandler
private void onToggleSet(@NotNull PlayerToggledEvent event) {
if (event.isEnabled()) {
set(event.getPlayerId(), event.getToggle().getName());
} else {
enabledToggles.computeIfPresent(event.getPlayerId(), (uuid, toggles) -> {
toggles.remove(event.getToggle().getName());
return toggles.isEmpty() ? null : toggles;
});
}
}
}
@@ -1,7 +0,0 @@
name: OITogglePersist
main: com.github.jikoo.openinv.togglepersist.TogglePersist
version: ${project.version}
author: Jikoo
description: An OpenInv addon allowing /anycontainer and /silentcontainer to persist across sessions.
api-version: "1.20"
depend: [ OpenInv ]
-49
View File
@@ -1,49 +0,0 @@
<!--
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<version>5.1.3</version>
</parent>
<artifactId>openinvapi</artifactId>
<name>OpenInvAPI</name>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,185 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.logging.Logger;
/**
* Interface defining behavior for the OpenInv plugin.
*/
public interface IOpenInv {
/**
* Check if the server version is supported by OpenInv.
*
* @return true if the server version is supported
*/
boolean isSupportedVersion();
/**
* Check the configuration value for whether OpenInv saves player data when unloading players. This is exclusively
* for users who do not allow editing of inventories, only viewing, and wish to prevent any possibility of bugs such
* as lishid#40. If true, OpenInv will not ever save any edits made to players.
*
* @return false unless configured otherwise
*/
boolean disableSaving();
/**
* Check the configuration value for whether OpenInv allows offline access. If true, OpenInv will not load or allow
* modification of players while they are not online. This does not prevent other plugins from using existing loaded
* players who have gone offline.
*
* @return false unless configured otherwise
* @since 4.2.0
*/
boolean disableOfflineAccess();
/**
* Check the configuration value for whether OpenInv uses history for opening commands. If false, OpenInv will use
* the previous parameterized search when no parameters are provided.
*
* @return false unless configured otherwise
* @since 4.3.0
*/
boolean noArgsOpensSelf();
/**
* Get the active {@link IAnySilentContainer} implementation.
*
* @return the active implementation for the server version
* @throws IllegalStateException if the server version is unsupported
*/
@NotNull IAnySilentContainer getAnySilentContainer();
/**
* Get whether a user has AnyContainer mode enabled.
*
* @param offline the user to obtain the state of
* @return true if AnyContainer mode is enabled
*/
boolean getAnyContainerStatus(@NotNull OfflinePlayer offline);
/**
* Set whether a user has AnyContainer mode enabled.
*
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setAnyContainerStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Get whether a user has SilentContainer mode enabled.
*
* @param offline the user to obtain the state of
* @return true if SilentContainer mode is enabled
*/
boolean getSilentContainerStatus(@NotNull OfflinePlayer offline);
/**
* Set whether a user has SilentContainer mode enabled.
*
* @param offline the user to set the state of
* @param status the state of the mode
*/
void setSilentContainerStatus(@NotNull OfflinePlayer offline, boolean status);
/**
* Get an {@link ISpecialEnderChest} for a user.
*
* @param player the {@link Player} owning the inventory
* @param online whether the owner is currently online
* @return the created inventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Get an {@link ISpecialPlayerInventory} for a user.
*
* @param player the {@link Player} owning the inventory
* @param online whether the owner is currently online
* @return the created inventory
* @throws IllegalStateException if the server version is unsupported
* @throws InstantiationException if there was an issue creating the inventory
*/
@NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull Player player, boolean online) throws InstantiationException;
/**
* Open an {@link ISpecialInventory} for a {@link Player}.
*
* @param player the viewer
* @param inventory the inventory to open
* @return the resulting {@link InventoryView}
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
/**
* Check if a {@link Player} is currently loaded by OpenInv.
*
* @param playerUuid the {@link UUID} of the {@code Player}
* @return whether the {@code Player} is loaded
* @since 4.2.0
*/
boolean isPlayerLoaded(@NotNull UUID playerUuid);
/**
* Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for
* the server is not loaded, this will return {@code null}.
*
* @param offline the {@code OfflinePlayer} to load a {@code Player} for
* @return the loaded {@code Player}
* @throws IllegalStateException if the server version is unsupported
*/
@Nullable Player loadPlayer(@NotNull final OfflinePlayer offline);
/**
* Match an existing {@link OfflinePlayer}. If the name is a {@link UUID#toString() UUID string}, this will only
* return the user if they have actually played on the server before, unlike {@link Bukkit#getOfflinePlayer(UUID)}.
*
* <p>This method is potentially very heavily blocking. It should not ever be called on the
* main thread, and if it is, a stack trace will be displayed alerting server owners to the
* call.
*
* @param name the string to match
* @return the user with the closest matching name
*/
@Nullable OfflinePlayer matchPlayer(@NotNull String name);
/**
* Forcibly close inventories of and unload any cached data for a user.
*
* @param offline the {@link OfflinePlayer} to unload
*/
void unload(@NotNull OfflinePlayer offline);
Logger getLogger();
}
@@ -1,56 +0,0 @@
package com.lishid.openinv.event;
import com.google.errorprone.annotations.RestrictedApi;
import com.lishid.openinv.internal.ISpecialInventory;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Event fired before OpenInv saves a player's data when closing an {@link ISpecialInventory}.
*/
public class OpenPlayerSaveEvent extends PlayerSaveEvent {
private static final HandlerList HANDLERS = new HandlerList();
private final ISpecialInventory inventory;
/**
* Construct a new {@code OpenPlayerSaveEvent}.
*
* <p>The constructor is not considered part of the API, and may be subject to change.</p>
*
* @param player the player to be saved
* @param inventory the {@link ISpecialInventory} being closed
*/
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java")
@ApiStatus.Internal
OpenPlayerSaveEvent(@NotNull Player player, @NotNull ISpecialInventory inventory) {
super(player);
this.inventory = inventory;
}
/**
* Get the {@link ISpecialInventory} that triggered the save by being closed.
*
* @return the special inventory
*/
public @NotNull ISpecialInventory getInventory() {
return inventory;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -1,67 +0,0 @@
package com.lishid.openinv.event;
import com.google.errorprone.annotations.RestrictedApi;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Event fired before a {@link Player} loaded via OpenInv is saved.
*/
public class PlayerSaveEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private boolean cancelled = false;
/**
* Construct a new {@code PlayerSaveEvent}.
*
* <p>The constructor is not considered part of the API, and may be subject to change.</p>
*
* @param player the player to be saved
*/
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/(OpenPlayerSaveEvent|OpenEvents).java")
@ApiStatus.Internal
PlayerSaveEvent(@NotNull Player player) {
super(player);
}
/**
* Get whether the event is cancelled.
*
* @return true if the event is cancelled
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Set whether the event is cancelled.
*
* @param cancel whether the event is cancelled
*/
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -1,71 +0,0 @@
package com.lishid.openinv.event;
import com.google.errorprone.annotations.RestrictedApi;
import com.lishid.openinv.util.setting.PlayerToggle;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Event fired after OpenInv modifies a toggleable setting for a player.
*/
public class PlayerToggledEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final @NotNull PlayerToggle toggle;
private final @NotNull UUID uuid;
private final boolean enabled;
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java")
@ApiStatus.Internal
PlayerToggledEvent(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean enabled) {
this.toggle = toggle;
this.uuid = uuid;
this.enabled = enabled;
}
/**
* Get the {@link PlayerToggle} affected.
*
* @return the toggle
*/
public @NotNull PlayerToggle getToggle() {
return toggle;
}
/**
* Get the {@link UUID} of the player whose setting was changed.
*
* @return the player ID
*/
public @NotNull UUID getPlayerId() {
return uuid;
}
/**
* Get whether the toggle is enabled.
*
* @return true if the toggle is enabled
*/
public boolean isEnabled() {
return enabled;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -1,122 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.EnderChest;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.util.BoundingBox;
import org.jetbrains.annotations.NotNull;
public interface IAnySilentContainer {
/**
* Forcibly open the container at the given coordinates for the Player. This will open blocked containers! Be sure
* to check {@link #isAnyContainerNeeded(Block)} first if that is not desirable.
*
* @param player the {@link Player} opening the container
* @param silent whether the container's noise is to be silenced
* @param block the {@link Block} of the container
* @return true if the container can be opened
*/
boolean activateContainer(@NotNull Player player, boolean silent, @NotNull Block block);
/**
* Perform operations required to close the current container silently.
*
* @param player the {@link Player} closing a container
*/
void deactivateContainer(@NotNull Player player);
/**
* Check if the container at the given coordinates is blocked.
*
* @param block the {@link Block} of the container
* @return true if the container is blocked
*/
boolean isAnyContainerNeeded(@NotNull Block block);
/**
* Check if a shulker box block cannot be opened under ordinary circumstances.
*
* @param shulkerBox the shulker box block
* @return whether the container is blocked
*/
default boolean isShulkerBlocked(@NotNull Block shulkerBox) {
Directional directional = (Directional) shulkerBox.getBlockData();
BlockFace facing = directional.getFacing();
// Construct a new 1-block bounding box at the origin.
BoundingBox box = new BoundingBox(0, 0, 0, 1, 1, 1);
// Expand the box in the direction the shulker will open.
box.expand(facing, 0.5);
// Move the box away from the origin by a block so only the expansion intersects with a box around the origin.
box.shift(facing.getOppositeFace().getDirection());
// Check if the relative block's collision shape (which will be at the origin) intersects with the expanded box.
return shulkerBox.getRelative(facing).getCollisionShape().overlaps(box);
}
/**
* Check if a chest cannot be opened under ordinary circumstances.
*
* @param chest the chest block
* @return whether the container is blocked
*/
default boolean isChestBlocked(@NotNull Block chest) {
org.bukkit.block.Block relative = chest.getRelative(0, 1, 0);
return relative.getType().isOccluding()
|| !chest.getWorld().getNearbyEntities(BoundingBox.of(relative), Cat.class::isInstance).isEmpty();
}
/**
* Check if the given {@link Block} is a container which can be unblocked or silenced.
*
* @param block the potential container
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(@NotNull Block block);
/**
* Check if the given {@link BlockState} is a container which can be unblocked or silenced.
*
* @param blockState the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull BlockState blockState) {
return (blockState instanceof InventoryHolder holder && isAnySilentContainer(holder))
|| blockState instanceof EnderChest;
}
/**
* Check if the given {@link InventoryHolder} is a container which can be unblocked or silenced.
*
* @param holder the potential container
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull InventoryHolder holder) {
return holder instanceof org.bukkit.block.EnderChest
|| holder instanceof org.bukkit.block.Chest
|| holder instanceof org.bukkit.block.DoubleChest
|| holder instanceof org.bukkit.block.ShulkerBox
|| holder instanceof org.bukkit.block.Barrel;
}
}
@@ -1,64 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
/**
* Interface defining behavior for special inventories backed by other inventories' content listings.
*/
public interface ISpecialInventory {
/**
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
*
* @return the Bukkit inventory
*/
@NotNull Inventory getBukkitInventory();
/**
* Set the owning {@link Player} instance to a newly-joined user.
*
* @param player the user coming online
*/
void setPlayerOnline(@NotNull Player player);
/**
* Mark the owner of the inventory offline.
*/
void setPlayerOffline();
/**
* Get whether the inventory is being viewed by any users.
*
* @return true if the inventory is being viewed
*/
default boolean isInUse() {
return !getBukkitInventory().getViewers().isEmpty();
}
/**
* Get the {@link Player} who owns the inventory.
*
* @return the {@link HumanEntity} who owns the inventory
*/
@NotNull HumanEntity getPlayer();
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
import java.util.List;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
/**
* An {@link ISpecialInventory} representing a player inventory.
*/
public interface ISpecialPlayerInventory extends ISpecialInventory {
/*
* Player inventory usage varies from all other inventories - as the inventory is technically open at all times,
* if the player is online or has been online while the inventory is open, they are in the viewer list.
*/
@Override
default boolean isInUse() {
Inventory inventory = getBukkitInventory();
List<HumanEntity> viewers = inventory.getViewers();
if (viewers.size() != 1) {
return !viewers.isEmpty();
}
HumanEntity viewer = viewers.get(0);
if (!viewer.getUniqueId().equals(getPlayer().getUniqueId())) {
return true;
}
return viewer.getOpenInventory().getTopInventory().equals(inventory);
}
}
@@ -1,104 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import com.google.errorprone.annotations.RestrictedApi;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.BiFunction;
public final class InventoryAccess {
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider;
public static boolean isUsable() {
return provider != null;
}
/**
* Check if an {@link Inventory} is an {@link ISpecialPlayerInventory} implementation.
*
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
return getPlayerInventory(inventory) != null;
}
/**
* Get the {@link ISpecialPlayerInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is
* not backed by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
return provider == null ? null : (ISpecialPlayerInventory) provider.apply(inventory, ISpecialPlayerInventory.class);
}
/**
* Check if an {@link Inventory} is an {@link ISpecialEnderChest} implementation.
*
* @param inventory the Bukkit inventory
* @return true if backed by the correct implementation
*/
public static boolean isEnderChest(@NotNull Inventory inventory) {
return getEnderChest(inventory) != null;
}
/**
* Get the {@link ISpecialEnderChest} backing an {@link Inventory}. Returns {@code null} if the inventory is
* not backed by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
return provider == null ? null : (ISpecialEnderChest) provider.apply(inventory, ISpecialEnderChest.class);
}
/**
* Get a {@link ISpecialInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is not backed
* by the correct class.
*
* @param inventory the Bukkit inventory
* @return the backing implementation if available
*/
public static @Nullable ISpecialInventory getInventory(@NotNull Inventory inventory) {
return provider == null ? null : provider.apply(inventory, ISpecialInventory.class);
}
@RestrictedApi(
explanation = "Not part of the API.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/util/InternalAccessor.java")
@ApiStatus.Internal
static void setProvider(@Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider) {
InventoryAccess.provider = provider;
}
private InventoryAccess() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,36 +0,0 @@
package com.lishid.openinv.util.setting;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* A per-player setting that may be enabled or disabled.
*/
public interface PlayerToggle {
/**
* Get the name of the setting.
*
* @return the setting name
*/
@NotNull String getName();
/**
* Get the state of the toggle for a particular player ID.
*
* @param uuid the player ID
* @return true if the setting is enabled
*/
boolean is(@NotNull UUID uuid);
/**
* Set the state of the toggle for a particular player ID.
*
* @param uuid the player ID
* @param enabled whether the setting is enabled
* @return true if the setting changed as a result of being set
*/
boolean set(@NotNull UUID uuid, boolean enabled);
}
@@ -1,106 +0,0 @@
package com.lishid.openinv.util.setting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Utility class containing all of OpenInv's {@link PlayerToggle PlayerToggles}.
*/
public final class PlayerToggles {
private static final Map<String, PlayerToggle> TOGGLES = new HashMap<>();
private static final PlayerToggle ANY = add(new MemoryToggle("AnyContainer"));
private static final PlayerToggle SILENT = add(new MemoryToggle("SilentContainer"));
/**
* Get the AnyContainer toggle.
*
* @return the AnyContainer toggle
*/
public static @NotNull PlayerToggle any() {
return ANY;
}
/**
* Get the SilentContainer toggle.
*
* @return the SilentContainer toggle
*/
public static @NotNull PlayerToggle silent() {
return SILENT;
}
/**
* Get a toggle by name.
*
* @param toggleName the name of the toggle
* @return the toggle, or null if no such toggle exists.
*/
public static @Nullable PlayerToggle get(@NotNull String toggleName) {
PlayerToggle toggle = TOGGLES.get(toggleName);
if (toggle == null) {
toggle = TOGGLES.get(toggleName.toLowerCase(Locale.ENGLISH));
}
return toggle;
}
/**
* Get an unmodifable view of all toggles available.
*
* @return a view of all toggles available
*/
public static @UnmodifiableView @NotNull Collection<PlayerToggle> get() {
return Collections.unmodifiableCollection(TOGGLES.values());
}
private static @NotNull PlayerToggle add(@NotNull PlayerToggle toggle) {
TOGGLES.put(toggle.getName().toLowerCase(Locale.ENGLISH), toggle);
return toggle;
}
private PlayerToggles() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
private static class MemoryToggle implements PlayerToggle {
private final @NotNull Set<UUID> enabled;
private final @NotNull String name;
private MemoryToggle(@NotNull String name) {
enabled = new HashSet<>();
this.name = name;
}
@Override
public @NotNull String getName() {
return this.name;
}
@Override
public boolean is(@NotNull UUID uuid) {
return enabled.contains(uuid);
}
@Override
public boolean set(@NotNull UUID uuid, boolean enabled) {
if (enabled) {
return this.enabled.add(uuid);
} else {
return this.enabled.remove(uuid);
}
}
}
}
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>5.1.3</version>
</parent>
<artifactId>openinvcommon</artifactId>
<dependencies>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
</dependencies>
</project>
@@ -1,39 +0,0 @@
package com.lishid.openinv.event;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.setting.PlayerToggle;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Construct and call events.
*/
public final class OpenEvents {
public static boolean saveCancelled(@NotNull Player player) {
return call(new PlayerSaveEvent(player));
}
public static boolean saveCancelled(@NotNull ISpecialInventory inventory) {
return call(new OpenPlayerSaveEvent((Player) inventory.getPlayer(), inventory));
}
public static void notifyPlayerToggle(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
Bukkit.getPluginManager().callEvent(new PlayerToggledEvent(toggle, uuid, state));
}
private static <T extends Event & Cancellable> boolean call(T event) {
Bukkit.getPluginManager().callEvent(event);
return event.isCancelled();
}
private OpenEvents() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,24 +0,0 @@
package com.lishid.openinv.internal;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface Accessor {
@NotNull
PlayerManager getPlayerManager();
@NotNull IAnySilentContainer getAnySilentContainer();
@NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player);
@NotNull ISpecialEnderChest createEnderChest(@NotNull Player player);
<T extends ISpecialInventory> @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class<T> clazz);
void reload(@NotNull ConfigurationSection config);
}
@@ -1,106 +0,0 @@
package com.lishid.openinv.internal;
import org.bukkit.block.Barrel;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.EnderChest;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Chest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
public abstract class AnySilentContainerBase implements IAnySilentContainer {
private static final @Nullable Method BLOCK_GET_STATE_BOOLEAN;
static {
@Nullable Method getState;
try {
//noinspection JavaReflectionMemberAccess
getState = Block.class.getMethod("getState", boolean.class);
} catch (NoSuchMethodException e) {
getState = null;
}
BLOCK_GET_STATE_BOOLEAN = getState;
}
private static BlockState getBlockState(Block block) {
// Paper: Get state without snapshotting.
if (BLOCK_GET_STATE_BOOLEAN != null) {
try {
return (BlockState) BLOCK_GET_STATE_BOOLEAN.invoke(block, false);
} catch (ReflectiveOperationException ignored) {
// If we encounter an issue, fall through to regular snapshotting method.
}
}
return block.getState();
}
@Override
public boolean isAnyContainerNeeded(@NotNull Block block) {
BlockState blockState = getBlockState(block);
// Barrels do not require AnyContainer.
if (blockState instanceof Barrel) {
return false;
}
// Enderchests require a non-occluding block on top to open.
if (blockState instanceof EnderChest) {
return block.getRelative(0, 1, 0).getType().isOccluding();
}
// Shulker boxes require half a block clear in the direction they open.
if (blockState instanceof ShulkerBox) {
return isShulkerBlocked(block);
}
if (!(blockState instanceof org.bukkit.block.Chest)) {
return false;
}
if (isChestBlocked(block)) {
return true;
}
BlockData blockData = block.getBlockData();
if (!(blockData instanceof Chest chest) || chest.getType() == Chest.Type.SINGLE) {
return false;
}
BlockFace relativeFace = switch (chest.getFacing()) {
case NORTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.WEST : BlockFace.EAST;
case EAST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.NORTH : BlockFace.SOUTH;
case SOUTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.EAST : BlockFace.WEST;
case WEST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.SOUTH : BlockFace.NORTH;
default -> BlockFace.SELF;
};
Block relative = block.getRelative(relativeFace);
if (relative.getType() != block.getType()) {
return false;
}
BlockData relativeData = relative.getBlockData();
if (!(relativeData instanceof Chest relativeChest)) {
return false;
}
if (relativeChest.getFacing() != chest.getFacing()
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
return false;
}
return isChestBlocked(relative);
}
@Override
public boolean isAnySilentContainer(@NotNull Block block) {
return isAnySilentContainer(getBlockState(block));
}
}
@@ -1,7 +0,0 @@
package com.lishid.openinv.internal;
public interface InternalOwned<T> {
T getOwnerHandle();
}
@@ -1,48 +0,0 @@
package com.lishid.openinv.internal;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public enum InventoryViewTitle {
PLAYER_INVENTORY("container.player", "'s Inventory"),
ENDER_CHEST("container.enderchest", "'s Ender Chest");
private final String localizationKey;
private final String defaultSuffix;
InventoryViewTitle(String localizationKey, String defaultSuffix) {
this.localizationKey = localizationKey;
this.defaultSuffix = defaultSuffix;
}
public @NotNull String getTitle(
@NotNull LanguageManager lang,
@NotNull Player viewer,
@NotNull ISpecialInventory inventory) {
HumanEntity owner = inventory.getPlayer();
String localTitle = lang.getLocalizedMessage(
viewer,
localizationKey,
new Replacement("%player%", owner.getName()));
return Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + defaultSuffix);
}
public static @Nullable InventoryViewTitle of(@NotNull ISpecialInventory inventory) {
if (inventory instanceof ISpecialPlayerInventory) {
return PLAYER_INVENTORY;
} else if (inventory instanceof ISpecialEnderChest) {
return ENDER_CHEST;
} else {
return null;
}
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface PlayerManager {
/**
* Loads a Player for an OfflinePlayer.
* </p>
* This method is potentially blocking, and should not be called on the main thread.
*
* @param offline the OfflinePlayer
* @return the Player loaded
*/
@Nullable Player loadPlayer(@NotNull OfflinePlayer offline);
/**
* Creates a new Player from an existing one that will function slightly better offline.
*
* @return the Player
*/
@NotNull Player inject(@NotNull Player player);
/**
* Opens an ISpecialInventory for a Player.
*
* @param player the Player opening the ISpecialInventory
* @param inventory the Inventory
*`
* @return the InventoryView opened
*/
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
}
@@ -1,4 +0,0 @@
package com.lishid.openinv.internal;
public interface ViewOnly {
}
@@ -1,60 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import org.bukkit.permissions.Permissible;
import org.jetbrains.annotations.NotNull;
public enum Permissions {
INVENTORY_OPEN_SELF("inventory.open.self"),
INVENTORY_OPEN_OTHER("inventory.open.other"),
INVENTORY_EDIT_SELF("inventory.edit.self"),
INVENTORY_EDIT_OTHER("inventory.edit.other"),
INVENTORY_SLOT_HEAD_ANY("inventory.slot.head.any"),
INVENTORY_SLOT_CHEST_ANY("inventory.slot.chest.any"),
INVENTORY_SLOT_LEGS_ANY("inventory.slot.legs.any"),
INVENTORY_SLOT_FEET_ANY("inventory.slot.feet.any"),
INVENTORY_SLOT_DROP("inventory.slot.drop"),
ENDERCHEST_OPEN_SELF("enderchest.open.self"),
ENDERCHEST_OPEN_OTHER("enderchest.open.other"),
ENDERCHEST_EDIT_SELF("enderchest.edit.self"),
ENDERCHEST_EDIT_OTHER("enderchest.edit.other"),
ACCESS_CROSSWORLD("access.crossworld"),
ACCESS_OFFLINE("access.offline"),
ACCESS_ONLINE("access.online"),
SPECTATE_CLICK("spectate.click"),
CONTAINER_ANY("container.any"),
CONTAINER_SILENT("container.silent"),
SEARCH_INVENTORY("search.inventory"),
SEARCH_CONTAINER("search.container");
private final String permission;
Permissions(String permission) {
this.permission = "openinv." + permission;
}
public boolean hasPermission(@NotNull Permissible permissible) {
return permissible.hasPermission(permission);
}
}
@@ -1,78 +0,0 @@
/*
* Copyright (C) 2011-2021 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
/**
* A utility for making reflection easier.
*/
public final class ReflectionHelper {
/**
* Grab an {@link Object} stored in a {@link Field} of another {@code Object}.
*
* <p>This casts the field to the correct class. Any issues will result in a {@code null} return value.
*
* @param fieldType the {@link Class} of {@code Object} stored in the {@code Field}
* @param holder the containing {@code Object}
* @param <T> the type of stored {@code Object}
* @return the first matching {@code Object} or {@code null} if none match
*/
public static <T> @Nullable T grabObjectByType(final Object holder, final Class<T> fieldType) {
Field field = grabFieldByType(holder.getClass(), fieldType);
if (field != null) {
try {
return fieldType.cast(field.get(holder));
} catch (IllegalAccessException ignored) {
// Ignore issues obtaining field
}
}
return null;
}
/**
* Grab a {@link Field} of an {@link Object}
*
* @param fieldType the {@link Class} of the object
* @param holderType the containing {@code Class}
* @return the first matching object or {@code null} if none match
*/
public static @Nullable Field grabFieldByType(Class<?> holderType, Class<?> fieldType) {
for (Field field : holderType.getDeclaredFields()) {
if (fieldType.isAssignableFrom(field.getType())) {
field.setAccessible(true);
return field;
}
}
if (holderType.getSuperclass() != null) {
return grabFieldByType(fieldType, holderType.getSuperclass());
}
return null;
}
private ReflectionHelper() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,319 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util.lang;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import java.util.logging.Level;
/**
* A simple language manager supporting both custom and bundled languages.
*
* @author Jikoo
*/
public class LanguageManager {
private final Plugin plugin;
private final File folder;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(@NotNull Plugin plugin, @NotNull String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
this.folder = new File(plugin.getDataFolder(), "locale");
if (!folder.exists() && !folder.mkdirs()) {
plugin.getLogger().warning(() -> "Unable to create " + folder.getPath() + "! Languages may not be editable.");
}
reload();
}
public void reload() {
this.locales.clear();
getOrLoadLocale(defaultLocale);
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
LangLocation lang = bestMatch(locale, null);
// If a parent was a better match, check if it is already loaded.
if (!locale.equals(lang.locale)) {
loaded = locales.get(lang.locale);
if (loaded != null) {
locales.put(locale, loaded);
return loaded;
}
}
// Load locale config from disk and bundled locale defaults.
YamlConfiguration localeConfig = loadLocale(lang);
// If the locale is not the default locale, also handle any missing translations from the default locale.
if (!locale.equals(defaultLocale)) {
addTranslationFallthrough(lang, localeConfig);
if (plugin.getConfig().getBoolean("settings.secret.warn-about-guess-section", true)
&& localeConfig.isConfigurationSection("guess")) {
// Warn that guess section exists. This should run once per language per server restart
// when accessed by a user to hint to server owners that they can make UX improvements.
plugin.getLogger().info(() -> "[LanguageManager] Missing translations from " + lang.locale + ".yml! Check the guess section!");
}
}
locales.put(locale, localeConfig);
locales.put(lang.locale, localeConfig);
return localeConfig;
}
private @NotNull LangLocation bestMatch(@NotNull String locale, @Nullable LangLocation initial) {
File file = new File(folder, locale + ".yml");
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
if (file.exists() || bundled != null) {
return new LangLocation(locale, file, bundled);
}
if (initial == null) {
initial = new LangLocation(locale, file, null);
}
int lastSeparator = locale.lastIndexOf('_');
// Must be at least some content before separator.
if (lastSeparator < 1) {
return initial;
}
return bestMatch(locale.substring(0, lastSeparator), initial);
}
private @NotNull YamlConfiguration loadLocale(@NotNull LangLocation lang) {
YamlConfiguration localeConfigDefaults;
if (lang.bundled == null) {
localeConfigDefaults = new YamlConfiguration();
} else {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(lang.bundled, StandardCharsets.UTF_8))) {
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + lang.locale + ".yml");
localeConfigDefaults = new YamlConfiguration();
}
}
if (!lang.file.exists()) {
// If the file does not exist on disk, save bundled defaults.
try {
localeConfigDefaults.save(lang.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
}
// Return loaded bundled locale.
return localeConfigDefaults;
}
// If the file does exist on disk, load it.
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(lang.file);
// Check for missing translations from the bundled file.
List<String> newKeys = getMissingKeys(localeConfigDefaults, localeConfig::isSet);
if (newKeys.isEmpty()) {
return localeConfig;
}
// Get guess section for missing keys.
ConfigurationSection guess = localeConfig.getConfigurationSection("guess");
for (String newKey : newKeys) {
// Set all missing keys to defaults.
localeConfig.set(newKey, localeConfigDefaults.get(newKey));
// Delete relevant guess keys in case this is a new translation.
if (guess != null) {
guess.set(newKey, null);
}
}
// If guess section is empty, delete it.
if (guess != null && guess.getKeys(false).isEmpty()) {
localeConfig.set("guess", null);
}
plugin.getLogger().info(() -> "[LanguageManager] Added new translation keys to " + lang.locale + ".yml: " + String.join(", ", newKeys));
// Write new keys to disk.
try {
localeConfig.save(lang.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
}
return localeConfig;
}
private void addTranslationFallthrough(@NotNull LangLocation location, @NotNull YamlConfiguration localeConfig) {
YamlConfiguration defaultLocaleConfig = locales.get(defaultLocale);
// Get missing keys. Keys that already have a guess value are not new and don't need to trigger another write.
List<String> missingKeys = getMissingKeys(
defaultLocaleConfig,
key -> localeConfig.isSet(key) || localeConfig.isSet("guess." + key));
if (!missingKeys.isEmpty()) {
// Set up guess section for missing keys.
for (String key : missingKeys) {
localeConfig.set("guess." + key, defaultLocaleConfig.get(key));
}
// Write modified guess section to disk.
try {
localeConfig.save(location.file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + location.locale + ".yml");
}
}
// Fall through to default locale.
localeConfig.setDefaults(defaultLocaleConfig);
}
private @NotNull List<String> getMissingKeys(
@NotNull Configuration configurationDefault,
@NotNull Predicate<String> nodeSetPredicate) {
List<String> missingKeys = new ArrayList<>();
for (String key : configurationDefault.getKeys(true)) {
if (!configurationDefault.isConfigurationSection(key) && !nodeSetPredicate.test(key)) {
// Missing keys are non-section keys that fail the predicate.
missingKeys.add(key);
}
}
return missingKeys;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
if (value == null || value.isBlank()) {
return null;
}
value = ChatColor.translateAlternateColorCodes('&', value);
return value;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale, Replacement @NotNull ... replacements) {
String value = getValue(key, locale);
if (value == null) {
return null;
}
for (Replacement replacement : replacements) {
value = value.replace(replacement.placeholder(), replacement.value());
}
return value;
}
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
return getValue(key, getLocale(sender));
}
public @Nullable String getLocalizedMessage(
@NotNull CommandSender sender,
@NotNull String key,
Replacement @NotNull ... replacements) {
return getValue(key, getLocale(sender), replacements);
}
private @NotNull String getLocale(@NotNull CommandSender sender) {
if (sender instanceof Player) {
return ((Player) sender).getLocale();
} else {
return plugin.getConfig().getString("settings.locale", "en");
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
String message = getLocalizedMessage(sender, key);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
String message = getLocalizedMessage(player, key);
if (message == null) {
return;
}
int newline = message.indexOf('\n');
if (newline != -1) {
// No newlines in action bar chat.
message = message.substring(0, newline);
}
if (message.isEmpty()) {
return;
}
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacy(message));
}
private record LangLocation(@NotNull String locale, @NotNull File file, @Nullable InputStream bundled) {}
}
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util.lang;
import org.jetbrains.annotations.NotNull;
/**
* A data holder for string replacement in translations.
*
* @param placeholder the placeholder to be replaced
* @param value the value to insert
*/
public record Replacement(@NotNull String placeholder, @NotNull String value) {
}
View File
File renamed without changes.
-77
View File
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2023 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>5.1.3</version>
</parent>
<artifactId>openinvadapter1_21_R1</artifactId>
<name>OpenInvAdapter1_21_R1</name>
<properties>
<maven.compiler.system>21</maven.compiler.system>
<maven.compiler.release>21</maven.compiler.release>
<spigot.version>1.21.1-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
</dependency>
<dependency>
<artifactId>spigot</artifactId>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>${spigot.version}</version>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>openinvcommon</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,81 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1;
import com.lishid.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.v1_21_R1.container.AnySilentContainer;
import com.lishid.openinv.internal.v1_21_R1.container.OpenEnderChest;
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.world.Container;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class InternalAccessor implements Accessor {
private final @NotNull Logger logger;
private final @NotNull PlayerManager manager;
private final @NotNull AnySilentContainer anySilentContainer;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
manager = new PlayerManager(logger);
anySilentContainer = new AnySilentContainer(logger, lang);
}
@Override
public @NotNull PlayerManager getPlayerManager() {
return manager;
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return anySilentContainer;
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
}
@Override
public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) {
return new OpenEnderChest(player);
}
@Override
public <T extends ISpecialInventory> @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class<T> clazz) {
if (!(bukkitInventory instanceof CraftInventory craftInventory)) {
return null;
}
Container container = craftInventory.getInventory();
if (clazz.isInstance(container)) {
return clazz.cast(container);
}
return null;
}
@Override
public void reload(@NotNull ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
if (placeholders != null) {
try {
Placeholders.load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
}
@@ -1,210 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_21_R1.container;
import com.lishid.openinv.internal.AnySilentContainerBase;
import com.lishid.openinv.internal.v1_21_R1.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
import com.lishid.openinv.util.ReflectionHelper;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.TrappedChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.logging.Logger;
public class AnySilentContainer extends AnySilentContainerBase {
private final @NotNull Logger logger;
private final @NotNull LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
this.lang = lang;
try {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
logger.warning("ServerPlayerGameMode#gameModeForPlayer's obfuscated name has changed!");
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
}
} catch (SecurityException e) {
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
}
}
@Override
public boolean activateContainer(
@NotNull final Player bukkitPlayer,
final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
final net.minecraft.world.level.Level level = player.level();
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
return false;
}
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
enderChest.setActiveChest(enderChestTile);
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
MenuType<?> containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
int rows = enderChest.getContainerSize() / 9;
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
}, Component.translatable("container.enderchest")));
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
if (!(blockEntity instanceof MenuProvider menuProvider)) {
return false;
}
BlockState blockState = level.getBlockState(blockPos);
Block block = blockState.getBlock();
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
if (menuProvider == null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof ShulkerBoxBlock) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
if (block instanceof BarrelBlock) {
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
player.openMenu(menuProvider);
return true;
}
// SilentChest requires access to setting players' game mode directly.
if (this.serverPlayerGameModeGameType == null) {
return false;
}
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
if (lootable.lootTable != null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
}
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
player.openMenu(menuProvider);
this.forceGameType(player, gameType);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
// Regular game mode set calls GameModeChangeEvent and is cancellable.
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
// ServerPlayer#closeContainer cannot be called without entering an
// infinite loop because this method is called during inventory close.
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
// From ServerPlayer#closeContainer
player.doCloseContainer();
// Regular inventory close will handle the rest - packet sending, etc.
// Revert forced game mode.
this.forceGameType(player, gameType);
}
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
if (this.serverPlayerGameModeGameType == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
this.serverPlayerGameModeGameType.setAccessible(true);
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e);
}
}
}
@@ -1,196 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R1.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned<ServerPlayer>,
ISpecialEnderChest {
private CraftInventory inventory;
private @NotNull ServerPlayer owner;
private NonNullList<ItemStack> items;
private int maxStack = 64;
private final List<HumanEntity> transaction = new ArrayList<>();
public OpenEnderChest(@NotNull org.bukkit.entity.Player player) {
this.owner = PlayerManager.getHandle(player);
this.items = owner.getEnderChestInventory().items;
}
@Override
public @NotNull ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
if (inventory == null) {
inventory = new CraftInventory(this) {
@Override
public @NotNull InventoryType getType() {
return InventoryType.ENDER_CHEST;
}
};
}
return inventory;
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
owner = PlayerManager.getHandle(player);
NonNullList<ItemStack> activeItems = owner.getEnderChestInventory().items;
// Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally.
int max = Math.min(items.size(), activeItems.size());
for (int index = 0; index < max; ++index) {
activeItems.set(index, items.get(index));
}
items = activeItems;
}
@Override
public void setPlayerOffline() {}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public int getContainerSize() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int index, int amount) {
ItemStack itemstack = ContainerHelper.removeItem(items, index, amount);
if (!itemstack.isEmpty()) {
setChanged();
}
return itemstack;
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY;
}
@Override
public void setItem(int index, ItemStack itemStack) {
if (index >= 0 && index < items.size()) {
items.set(index, itemStack);
}
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
return items;
}
@Override
public void onOpen(CraftHumanEntity craftHumanEntity) {
transaction.add(craftHumanEntity);
}
@Override
public void onClose(CraftHumanEntity craftHumanEntity) {
transaction.remove(craftHumanEntity);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return getPlayer();
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void clearContent() {
items.clear();
setChanged();
}
@Override
public void fillStackedContents(StackedContents stackedContents) {
for (ItemStack itemstack : items) {
stackedContents.accountStack(itemstack);
}
}
public Component getTitle() {
return Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName())
.append(Component.translatable("container.enderchest"))
.append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName()));
}
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,364 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenPlayerInventory;
import com.lishid.openinv.internal.v1_21_R1.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.v1_21_R1.container.slot.Content;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentCrafting;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentCraftingResult;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentCursor;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentDrop;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentEquipment;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentList;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentOffHand;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentViewOnly;
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
import net.minecraft.ChatFormatting;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class OpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
private final List<Content> slots;
private final int size;
private ServerPlayer owner;
private int maxStackSize = 99;
private CraftInventory bukkitEntity;
public List<HumanEntity> transaction = new ArrayList<>();
public OpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) {
owner = PlayerManager.getHandle(bukkitPlayer);
// Get total size, rounding up to nearest 9 for client compatibility.
int rawSize = owner.getInventory().getContainerSize() + owner.inventoryMenu.getCraftSlots().getContainerSize() + 1;
size = ((int) Math.ceil(rawSize / 9.0)) * 9;
slots = NonNullList.withSize(size, new ContentViewOnly(owner));
setupSlots();
}
private void setupSlots() {
// Top of inventory: Regular contents.
int nextIndex = addMainInventory();
// If inventory is expected size, we can arrange slots to be pretty.
Inventory ownerInv = owner.getInventory();
if (ownerInv.items.size() == 36
&& ownerInv.armor.size() == 4
&& ownerInv.offhand.size() == 1
&& owner.inventoryMenu.getCraftSlots().getContainerSize() == 4) {
// Armor slots: Bottom left.
addArmor(36);
// Off-hand: Below chestplate.
addOffHand(46);
// Drop slot: Bottom right.
slots.set(53, new ContentDrop(owner));
// Cursor slot: Above drop.
slots.set(44, new ContentCursor(owner));
// Crafting is displayed in the bottom right corner.
// As we're using the pretty view, this is a 3x2.
addCrafting(41, true);
return;
}
// Otherwise we'll just add elements linearly.
nextIndex = addArmor(nextIndex);
nextIndex = addOffHand(nextIndex);
nextIndex = addCrafting(nextIndex, false);
slots.set(nextIndex, new ContentCursor(owner));
// Drop slot last.
slots.set(slots.size() - 1, new ContentDrop(owner));
}
private int addMainInventory() {
int listSize = owner.getInventory().items.size();
// Hotbar slots are 0-8. We want those to appear on the bottom of the inventory like a normal player inventory,
// so everything else needs to move up a row.
int hotbarDiff = listSize - 9;
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
InventoryType.SlotType type;
int invIndex;
if (localIndex < hotbarDiff) {
invIndex = localIndex + 9;
type = InventoryType.SlotType.CONTAINER;
} else {
type = InventoryType.SlotType.QUICKBAR;
invIndex = localIndex - hotbarDiff;
}
slots.set(localIndex, new ContentList(owner, invIndex, type) {
@Override
public void setHolder(@NotNull ServerPlayer holder) {
items = holder.getInventory().items;
}
});
}
return listSize;
}
private int addArmor(int startIndex) {
int listSize = owner.getInventory().armor.size();
for (int i = 0; i < listSize; ++i) {
// Armor slots go bottom to top; boots are slot 0, helmet is slot 3.
// Since we have to display horizontally due to space restrictions,
// making the left side the "top" is more user-friendly.
int armorIndex;
EquipmentSlot slot;
switch (i) {
case 3 -> {
armorIndex = 0;
slot = EquipmentSlot.FEET;
}
case 2 -> {
armorIndex = 1;
slot = EquipmentSlot.LEGS;
}
case 1 -> {
armorIndex = 2;
slot = EquipmentSlot.CHEST;
}
case 0 -> {
armorIndex = 3;
slot = EquipmentSlot.HEAD;
}
default -> {
// In the event that new armor slots are added, they can be placed at the end.
armorIndex = i;
slot = EquipmentSlot.MAINHAND;
}
}
slots.set(startIndex + i, new ContentEquipment(owner, armorIndex, slot));
}
return startIndex + listSize;
}
private int addOffHand(int startIndex) {
int listSize = owner.getInventory().offhand.size();
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
slots.set(startIndex + localIndex, new ContentOffHand(owner, localIndex));
}
return startIndex + listSize;
}
private int addCrafting(int startIndex, boolean pretty) {
int listSize = owner.inventoryMenu.getCraftSlots().getContents().size();
pretty &= listSize == 4;
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
// Pretty display is a 2x2 rather than linear.
// If index is in top row, grid is not 2x2, or pretty is disabled, just use current index.
// Otherwise, subtract 2 and add 9 to start in the same position on the next row.
int modIndex = startIndex + (localIndex < 2 || !pretty ? localIndex : localIndex + 7);
slots.set(modIndex, new ContentCrafting(owner, localIndex));
}
if (pretty) {
slots.set(startIndex + 2, new ContentViewOnly(owner) {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return Placeholders.craftingOutput;
}
};
}
});
slots.set(startIndex + 11, new ContentCraftingResult(owner));
}
return startIndex + listSize;
}
public Slot getMenuSlot(int index, int x, int y) {
return slots.get(index).asSlot(this, index, x, y);
}
public InventoryType.SlotType getSlotType(int index) {
return slots.get(index).getSlotType();
}
public @NotNull Component getTitle(@Nullable ServerPlayer viewer) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
component.append(
Component.translatableWithFallback("openinv.container.inventory.self", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
}
// Normal title: "Inventory - OwnerName"
component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName()))
.append(Component.translatable("container.inventory"))
.append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName()));
return component;
}
@Override
public ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
if (bukkitEntity == null) {
bukkitEntity = new OpenPlayerInventory(this);
}
return bukkitEntity;
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
ServerPlayer newOwner = PlayerManager.getHandle(player);
// Only transfer regular inventory - crafting and cursor slots are transient.
newOwner.getInventory().replaceWith(owner.getInventory());
owner = newOwner;
// Update slots to point to new inventory.
slots.forEach(slot -> slot.setHolder(newOwner));
}
@Override
public void setPlayerOffline() {}
@Override
public boolean isInUse() {
return !transaction.isEmpty();
}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return getOwner();
}
@Override
public int getContainerSize() {
return size;
}
@Override
public boolean isEmpty() {
return slots.stream().map(Content::get).allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return slots.get(index).get();
}
@Override
public ItemStack removeItem(int index, int amount) {
return slots.get(index).removePartial(amount);
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return slots.get(index).remove();
}
@Override
public void setItem(int index, ItemStack itemStack) {
slots.get(index).set(itemStack);
}
@Override
public int getMaxStackSize() {
return maxStackSize;
}
@Override
public void setMaxStackSize(int maxStackSize) {
this.maxStackSize = maxStackSize;
}
@Override
public void setChanged() {}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
NonNullList<ItemStack> contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY);
for (int i = 0; i < getContainerSize(); ++i) {
contents.set(i, getItem(i));
}
return contents;
}
@Override
public void onOpen(CraftHumanEntity viewer) {
transaction.add(viewer);
}
@Override
public void onClose(CraftHumanEntity viewer) {
transaction.remove(viewer);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return owner.getBukkitEntity();
}
@Override
public Location getLocation() {
return owner.getBukkitEntity().getLocation();
}
@Override
public void clearContent() {
owner.getInventory().clearContent();
owner.inventoryMenu.getCraftSlots().clearContent();
owner.inventoryMenu.slotsChanged(owner.inventoryMenu.getCraftSlots());
owner.containerMenu.setCarried(ItemStack.EMPTY);
}
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,183 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.TagParser;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Unit;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.entity.BannerPattern;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BannerPatterns;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.EnumMap;
import java.util.List;
import java.util.Optional;
public final class Placeholders {
private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(9999);
public static final @NotNull EnumMap<GameType, ItemStack> BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class);
public static @NotNull ItemStack craftingOutput = defaultCraftingOutput();
public static @NotNull ItemStack cursor = defaultCursor();
public static @NotNull ItemStack drop = defaultDrop();
public static @NotNull ItemStack emptyHelmet = getEmptyArmor(Items.LEATHER_HELMET);
public static @NotNull ItemStack emptyChestplate = getEmptyArmor(Items.LEATHER_CHESTPLATE);
public static @NotNull ItemStack emptyLeggings = getEmptyArmor(Items.LEATHER_LEGGINGS);
public static @NotNull ItemStack emptyBoots = getEmptyArmor(Items.LEATHER_BOOTS);
public static @NotNull ItemStack emptyOffHand = getEmptyShield();
public static @NotNull ItemStack notSlot = defaultNotSlot();
public static @NotNull ItemStack blockedOffline = defaultBlockedOffline();
static {
for (GameType type : GameType.values()) {
// Barrier: "Not available - Creative" etc.
ItemStack typeItem = new ItemStack(Items.BARRIER);
typeItem.set(
DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName()));
BLOCKED_GAME_TYPE.put(type, typeItem);
}
}
public static void load(@NotNull ConfigurationSection section) throws Exception {
craftingOutput = parse(section, "crafting-output", craftingOutput);
cursor = parse(section, "cursor", cursor);
drop = parse(section, "drop", drop);
emptyHelmet = parse(section, "empty-helmet", emptyHelmet);
emptyChestplate = parse(section, "empty-chestplate", emptyChestplate);
emptyLeggings = parse(section, "empty-leggings", emptyLeggings);
emptyBoots = parse(section, "empty-boots", emptyBoots);
emptyOffHand = parse(section, "empty-off-hand", emptyOffHand);
notSlot = parse(section, "not-a-slot", notSlot);
blockedOffline = parse(section, "blocked.offline", blockedOffline);
BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", BLOCKED_GAME_TYPE.get(GameType.CREATIVE)));
BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", BLOCKED_GAME_TYPE.get(GameType.SPECTATOR)));
}
private static @NotNull ItemStack parse(
@NotNull ConfigurationSection section,
@NotNull String path,
@NotNull ItemStack defaultStack) throws Exception {
String itemText = section.getString(path);
if (itemText == null) {
return defaultStack;
}
CompoundTag compoundTag = TagParser.parseTag(itemText);
Optional<ItemStack> parsed = ItemStack.parse(CraftRegistry.getMinecraftRegistry(), compoundTag);
return parsed.filter(itemStack -> !itemStack.isEmpty()).orElse(defaultStack);
}
public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) {
if (serverPlayer.connection == null || serverPlayer.connection.isDisconnected()) {
return blockedOffline;
}
return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY);
}
private static ItemStack defaultCraftingOutput() {
// Crafting table: "Crafting"
ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE);
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting"));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack defaultCursor() {
// Cursor-like banner with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_BANNER);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.registryOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT);
BannerPattern downRight = bannerPatterns.getOrThrow(BannerPatterns.STRIPE_DOWNRIGHT);
BannerPattern border = bannerPatterns.getOrThrow(BannerPatterns.BORDER);
itemStack.set(DataComponents.BANNER_PATTERNS,
new BannerPatternLayers(List.of(
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY))));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
return itemStack;
}
private static ItemStack defaultDrop() {
// Dropper: "Drop Selected Item"
ItemStack itemStack = new ItemStack(Items.DROPPER);
// Note: translatable component, not keybind component! We want the text identifying the keybind, not the key.
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("key.drop"));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack getEmptyArmor(ItemLike item) {
// Inventory-background-grey-ish leather armor with no tooltip
ItemStack itemStack = new ItemStack(item);
DyedItemColor color = new DyedItemColor(0xC8C8C8, false);
itemStack.set(DataComponents.DYED_COLOR, color);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack getEmptyShield() {
ItemStack itemStack = new ItemStack(Items.SHIELD);
itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.registryOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfLeft = bannerPatterns.getOrThrow(BannerPatterns.HALF_VERTICAL);
BannerPattern topLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_LEFT);
BannerPattern topRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_RIGHT);
BannerPattern bottomLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_LEFT);
BannerPattern bottomRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_RIGHT);
itemStack.set(DataComponents.BANNER_PATTERNS,
new BannerPatternLayers(List.of(
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfLeft), DyeColor.BLACK),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK))));
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack defaultNotSlot() {
// White pane with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_STAINED_GLASS_PANE);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack defaultBlockedOffline() {
// Barrier: "Not available - Offline"
ItemStack itemStack = new ItemStack(Items.BARRIER);
itemStack.set(DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable")
.append(Component.literal(" - "))
.append(Component.translatable("gui.socialInteractions.status_offline")));
return itemStack;
}
private Placeholders() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,163 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.bukkit;
import com.lishid.openinv.internal.ViewOnly;
import net.minecraft.world.Container;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.ListIterator;
/**
* A locked down "empty" inventory that rejects plugin interaction.
*/
public class OpenDummyInventory extends CraftInventory implements ViewOnly {
public OpenDummyInventory(Container inventory) {
super(inventory);
}
@Override
public @Nullable ItemStack getItem(int index) {
return null;
}
@Override
public void setItem(int index, @Nullable ItemStack item) {
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> removeItem(@NotNull ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
private static @NotNull HashMap<Integer, ItemStack> arrayToHashMap(@NotNull ItemStack[] items) {
HashMap<Integer, ItemStack> ignored = new HashMap<>();
for (int index = 0; index < items.length; ++index) {
ignored.put(index, items[index]);
}
return ignored;
}
@Override
public ItemStack[] getContents() {
return new ItemStack[getSize()];
}
@Override
public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
}
@Override
public @NotNull ItemStack[] getStorageContents() {
return new ItemStack[getSize()];
}
@Override
public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
}
@Override
public boolean contains(@NotNull Material material) throws IllegalArgumentException {
return false;
}
@Override
public boolean contains(@Nullable ItemStack item) {
return false;
}
@Override
public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException {
return false;
}
@Override
public boolean contains(@Nullable ItemStack item, int amount) {
return false;
}
@Override
public boolean containsAtLeast(@Nullable ItemStack item, int amount) {
return false;
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(
@NotNull Material material) throws IllegalArgumentException {
return new HashMap<>();
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(@Nullable ItemStack item) {
return new HashMap<>();
}
@Override
public int first(@NotNull Material material) throws IllegalArgumentException {
return -1;
}
@Override
public int first(@NotNull ItemStack item) {
return -1;
}
@Override
public int firstEmpty() {
return -1;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void remove(@NotNull Material material) throws IllegalArgumentException {
}
@Override
public void remove(@NotNull ItemStack item) {
}
@Override
public void clear(int index) {
}
@Override
public void clear() {
}
@Override
public @NotNull ListIterator<ItemStack> iterator() {
return Collections.emptyListIterator();
}
@Override
public @NotNull ListIterator<ItemStack> iterator(int index) {
return Collections.emptyListIterator();
}
}
@@ -1,221 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.bukkit;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenPlayerInventory extends CraftInventory implements PlayerInventory {
public OpenPlayerInventory(@NotNull OpenInventory inventory) {
super(inventory);
}
@Override
public @NotNull OpenInventory getInventory() {
return (OpenInventory) super.getInventory();
}
@Override
public ItemStack[] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
}
@Override
public void setContents(ItemStack[] items) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
int size = internal.getContainerSize();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < size; ++index) {
if (index < items.length) {
internal.setItem(index, CraftItemStack.asNMSCopy(items[index]));
} else {
internal.setItem(index, net.minecraft.world.item.ItemStack.EMPTY);
}
}
}
@Override
public ItemStack[] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().items);
}
@Override
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().items;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public @NotNull InventoryType getType() {
return InventoryType.PLAYER;
}
@Override
public @NotNull Player getHolder() {
return getInventory().getOwner();
}
@Override
public @NotNull ItemStack[] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().armor);
}
@Override
public void setArmorContents(@Nullable ItemStack[] items) {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().armor;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public @NotNull ItemStack[] getExtraContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand);
}
@Override
public void setExtraContents(@Nullable ItemStack[] items) {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().offhand;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public @Nullable ItemStack getHelmet() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.HEAD.getIndex()));
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.HEAD.getIndex(), CraftItemStack.asNMSCopy(helmet));
}
@Override
public @Nullable ItemStack getChestplate() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.HEAD.getIndex()));
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.CHEST.getIndex(), CraftItemStack.asNMSCopy(chestplate));
}
@Override
public @Nullable ItemStack getLeggings() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.LEGS.getIndex()));
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.LEGS.getIndex(), CraftItemStack.asNMSCopy(leggings));
}
@Override
public @Nullable ItemStack getBoots() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.FEET.getIndex()));
}
@Override
public void setBoots(@Nullable ItemStack boots) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.FEET.getIndex(), CraftItemStack.asNMSCopy(boots));
}
@Override
public @NotNull ItemStack getItemInMainHand() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return CraftItemStack.asCraftMirror(internal.getItem(internal.selected));
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
internal.setItem(internal.selected, CraftItemStack.asNMSCopy(item));
}
@Override
public @NotNull ItemStack getItemInOffHand() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand.getFirst());
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
getInventory().getOwnerHandle().getInventory().offhand.set(0, CraftItemStack.asNMSCopy(item));
}
@Override
public @NotNull ItemStack getItemInHand() {
return getItemInMainHand();
}
@Override
public void setItemInHand(@Nullable ItemStack stack) {
setItemInMainHand(stack);
}
@Override
public int getHeldItemSlot() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return internal.items.size() - 9 + internal.selected;
}
@Override
public void setHeldItemSlot(int slot) {
slot %= 9;
getInventory().getOwnerHandle().getInventory().selected = slot;
}
@Override
public @Nullable ItemStack getItem(@NotNull org.bukkit.inventory.EquipmentSlot slot) {
return switch (slot) {
case HAND -> getItemInMainHand();
case OFF_HAND -> getItemInOffHand();
case FEET -> getBoots();
case LEGS -> getLeggings();
case CHEST -> getChestplate();
case HEAD -> getHelmet();
default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot);
};
}
@Override
public void setItem(@NotNull org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) {
switch (slot) {
case HAND -> setItemInMainHand(item);
case OFF_HAND -> setItemInOffHand(item);
case FEET -> setBoots(item);
case LEGS -> setLeggings(item);
case CHEST -> setChestplate(item);
case HEAD -> setHelmet(item);
default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot);
}
}
}
@@ -1,26 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.bukkit;
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public class OpenPlayerInventorySelf extends OpenPlayerInventory {
private final int offset;
public OpenPlayerInventorySelf(@NotNull OpenInventory inventory, int offset) {
super(inventory);
this.offset = offset;
}
@Override
public ItemStack getItem(int index) {
return super.getItem(offset + index);
}
@Override
public void setItem(int index, ItemStack item) {
super.setItem(offset + index, item);
}
}
@@ -1,478 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.menu;
import com.google.common.base.Suppliers;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotPlaceholder;
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.inventory.ContainerSynchronizer;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* An extension of {@link AbstractContainerMenu} that supports {@link SlotPlaceholder placeholders}.
*/
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
extends AbstractContainerMenu {
protected static final int BOTTOM_INVENTORY_SIZE = 36;
protected final T container;
protected final ServerPlayer viewer;
protected final boolean viewOnly;
protected final boolean ownContainer;
protected final int topSize;
private CraftInventoryView<OpenChestMenu<T>, Inventory> bukkitEntity;
// Syncher fields
private @Nullable ContainerSynchronizer synchronizer;
private final List<DataSlot> dataSlots = new ArrayList<>();
private final IntList remoteDataSlots = new IntArrayList();
private final List<ContainerListener> containerListeners = new ArrayList<>();
private ItemStack remoteCarried = ItemStack.EMPTY;
private boolean suppressRemoteUpdates;
protected OpenChestMenu(
@NotNull MenuType<ChestMenu> type,
int containerCounter,
@NotNull T container,
@NotNull ServerPlayer viewer,
boolean viewOnly) {
super(type, containerCounter);
this.container = container;
this.viewer = viewer;
this.viewOnly = viewOnly;
ownContainer = container.getOwnerHandle().equals(viewer);
topSize = getTopSize(viewer);
preSlotSetup();
int upperRows = topSize / 9;
// View's upper inventory - our container
for (int row = 0; row < upperRows; ++row) {
for (int col = 0; col < 9; ++col) {
// x and y for client purposes, but hey, we're thorough here.
// Adapted from net.minecraft.world.inventory.ChestMenu
int x = 8 + col * 18;
int y = 18 + row * 18;
int index = row * 9 + col;
// Guard against weird inventory sizes.
if (index >= container.getContainerSize()) {
addSlot(new SlotViewOnly(container, index, x, y));
continue;
}
Slot slot = getUpperSlot(index, x, y);
addSlot(slot);
}
}
// View's lower inventory - viewer inventory
int playerInvPad = (upperRows - 4) * 18;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + row * 18 + 103;
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
}
}
// Hotbar
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + 161;
addSlot(new Slot(viewer.getInventory(), col, x, y));
}
}
public static @NotNull MenuType<ChestMenu> getChestMenuType(int inventorySize) {
inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9;
return switch (inventorySize) {
case 9 -> MenuType.GENERIC_9x1;
case 18 -> MenuType.GENERIC_9x2;
case 27 -> MenuType.GENERIC_9x3;
case 36 -> MenuType.GENERIC_9x4;
case 45 -> MenuType.GENERIC_9x5;
case 54 -> MenuType.GENERIC_9x6;
default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize);
};
}
protected void preSlotSetup() {}
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
if (viewOnly) {
return new SlotViewOnly(container, index, x, y);
}
return new Slot(container, index, x, y);
}
@Override
public final @NotNull CraftInventoryView<OpenChestMenu<T>, Inventory> getBukkitView() {
if (bukkitEntity == null) {
bukkitEntity = createBukkitEntity();
}
return bukkitEntity;
}
protected @NotNull CraftInventoryView<OpenChestMenu<T>, Inventory> createBukkitEntity() {
Inventory top;
if (viewOnly) {
top = new OpenDummyInventory(container);
} else {
top = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) {
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
return super.getInventory(rawSlot);
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
return super.convertSlot(rawSlot);
}
@Override
public @NotNull InventoryType.SlotType getSlotType(int slot) {
if (viewOnly) {
return InventoryType.SlotType.OUTSIDE;
}
return super.getSlotType(slot);
}
};
}
private int getTopSize(ServerPlayer viewer) {
MenuType<?> menuType = getType();
if (menuType == null) {
throw new IllegalStateException("MenuType cannot be null!");
} else if (menuType == MenuType.GENERIC_9x1) {
return 9;
} else if (menuType == MenuType.GENERIC_9x2) {
return 18;
} else if (menuType == MenuType.GENERIC_9x3) {
return 27;
} else if (menuType == MenuType.GENERIC_9x4) {
return 36;
} else if (menuType == MenuType.GENERIC_9x5) {
return 45;
} else if (menuType == MenuType.GENERIC_9x6) {
return 54;
}
// This is a bit gross, but allows us a safe fallthrough.
return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE;
}
/**
* Reimplementation of {@link AbstractContainerMenu#moveItemStackTo(ItemStack, int, int, boolean)} that ignores fake
* slots and respects {@link Slot#hasItem()}.
*
* @param itemStack the stack to quick-move
* @param rangeLow the start of the range of slots that can be moved to, inclusive
* @param rangeHigh the end of the range of slots that can be moved to, exclusive
* @param topDown whether to start at the top of the range or bottom
* @return whether the stack was modified as a result of being quick-moved
*/
@Override
protected boolean moveItemStackTo(ItemStack itemStack, int rangeLow, int rangeHigh, boolean topDown) {
boolean modified = false;
boolean stackable = itemStack.isStackable();
Slot firstEmpty = null;
for (int index = topDown ? rangeHigh - 1 : rangeLow;
!itemStack.isEmpty() && (topDown ? index >= rangeLow : index < rangeHigh);
index += topDown ? -1 : 1
) {
Slot slot = slots.get(index);
// If the slot cannot be added to, check the next slot.
if (slot.isFake() || !slot.mayPlace(itemStack)) {
continue;
}
if (slot.hasItem()) {
// If the item isn't stackable, check the next slot.
if (!stackable) {
continue;
}
// Otherwise, add as many as we can from our stack to the slot.
modified |= addToExistingStack(itemStack, slot);
} else {
// If this is the first empty slot, keep track of it for later use.
if (firstEmpty == null) {
firstEmpty = slot;
}
// If the item isn't stackable, we've located the slot we're adding it to, so we're done.
if (!stackable) {
break;
}
}
}
// If the item hasn't been fully added yet, add as many as we can to the first open slot.
if (!itemStack.isEmpty() && firstEmpty != null) {
firstEmpty.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), firstEmpty.getMaxStackSize(itemStack))));
firstEmpty.setChanged();
modified = true;
}
return modified;
}
private static boolean addToExistingStack(ItemStack itemStack, Slot slot) {
ItemStack existing = slot.getItem();
// If the items aren't the same, we can't add our item.
if (!ItemStack.isSameItemSameComponents(itemStack, existing)) {
return false;
}
int max = slot.getMaxStackSize(existing);
int existingCount = existing.getCount();
// If the stack is already full, we can't add more.
if (existingCount >= max) {
return false;
}
int total = existingCount + itemStack.getCount();
// If the existing item can accept the entirety of our item, we're done!
if (total <= max) {
itemStack.setCount(0);
existing.setCount(total);
slot.setChanged();
return true;
}
// Otherwise, add as many as we can.
itemStack.shrink(max - existingCount);
existing.setCount(max);
slot.setChanged();
return true;
}
@Override
public void clicked(int i, int j, ClickType clickType, Player player) {
if (viewOnly) {
if (clickType == ClickType.QUICK_CRAFT) {
sendAllDataToRemote();
}
return;
}
super.clicked(i, j, clickType, player);
}
@Override
public boolean stillValid(Player player) {
return true;
}
// Overrides from here on are purely to modify the sync process to send placeholder items.
@Override
protected Slot addSlot(Slot slot) {
slot.index = this.slots.size();
this.slots.add(slot);
this.lastSlots.add(ItemStack.EMPTY);
this.remoteSlots.add(ItemStack.EMPTY);
return slot;
}
@Override
protected DataSlot addDataSlot(DataSlot dataSlot) {
this.dataSlots.add(dataSlot);
this.remoteDataSlots.add(0);
return dataSlot;
}
@Override
protected void addDataSlots(ContainerData containerData) {
for (int i = 0; i < containerData.getCount(); i++) {
this.addDataSlot(DataSlot.forContainer(containerData, i));
}
}
@Override
public void addSlotListener(ContainerListener containerListener) {
if (!this.containerListeners.contains(containerListener)) {
this.containerListeners.add(containerListener);
this.broadcastChanges();
}
}
@Override
public void setSynchronizer(ContainerSynchronizer containerSynchronizer) {
this.synchronizer = containerSynchronizer;
this.sendAllDataToRemote();
}
@Override
public void sendAllDataToRemote() {
for (int index = 0; index < slots.size(); ++index) {
Slot slot = slots.get(index);
this.remoteSlots.set(index, (slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem()).copy());
}
remoteCarried = getCarried().copy();
for (int index = 0; index < this.dataSlots.size(); ++index) {
this.remoteDataSlots.set(index, this.dataSlots.get(index).get());
}
if (this.synchronizer != null) {
this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray());
}
}
@Override
public void broadcastCarriedItem() {
this.remoteCarried = this.getCarried().copy();
if (this.synchronizer != null) {
this.synchronizer.sendCarriedChange(this, this.remoteCarried);
}
}
@Override
public void removeSlotListener(ContainerListener containerListener) {
this.containerListeners.remove(containerListener);
}
@Override
public void broadcastChanges() {
for (int index = 0; index < this.slots.size(); ++index) {
Slot slot = this.slots.get(index);
ItemStack itemstack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem();
Supplier<ItemStack> supplier = Suppliers.memoize(itemstack::copy);
this.triggerSlotListeners(index, itemstack, supplier);
this.synchronizeSlotToRemote(index, itemstack, supplier);
}
this.synchronizeCarriedToRemote();
for (int index = 0; index < this.dataSlots.size(); ++index) {
DataSlot dataSlot = this.dataSlots.get(index);
int j = dataSlot.get();
if (dataSlot.checkAndClearUpdateFlag()) {
this.updateDataSlotListeners(index, j);
}
this.synchronizeDataSlotToRemote(index, j);
}
}
@Override
public void broadcastFullState() {
for (int index = 0; index < this.slots.size(); ++index) {
ItemStack itemstack = this.slots.get(index).getItem();
this.triggerSlotListeners(index, itemstack, itemstack::copy);
}
for (int index = 0; index < this.dataSlots.size(); ++index) {
DataSlot containerproperty = this.dataSlots.get(index);
if (containerproperty.checkAndClearUpdateFlag()) {
this.updateDataSlotListeners(index, containerproperty.get());
}
}
this.sendAllDataToRemote();
}
private void updateDataSlotListeners(int i, int j) {
for (ContainerListener containerListener : this.containerListeners) {
containerListener.dataChanged(this, i, j);
}
}
private void triggerSlotListeners(int index, ItemStack itemStack, Supplier<ItemStack> supplier) {
ItemStack itemStack1 = this.lastSlots.get(index);
if (!ItemStack.matches(itemStack1, itemStack)) {
ItemStack itemStack2 = supplier.get();
this.lastSlots.set(index, itemStack2);
for (ContainerListener containerListener : this.containerListeners) {
containerListener.slotChanged(this, index, itemStack2);
}
}
}
private void synchronizeSlotToRemote(int i, ItemStack itemStack, Supplier<ItemStack> supplier) {
if (!this.suppressRemoteUpdates) {
ItemStack itemStack1 = this.remoteSlots.get(i);
if (!ItemStack.matches(itemStack1, itemStack)) {
ItemStack itemstack2 = supplier.get();
this.remoteSlots.set(i, itemstack2);
if (this.synchronizer != null) {
this.synchronizer.sendSlotChange(this, i, itemstack2);
}
}
}
}
private void synchronizeDataSlotToRemote(int index, int value) {
if (!this.suppressRemoteUpdates) {
int existing = this.remoteDataSlots.getInt(index);
if (existing != value) {
this.remoteDataSlots.set(index, value);
if (this.synchronizer != null) {
this.synchronizer.sendDataChange(this, index, value);
}
}
}
}
private void synchronizeCarriedToRemote() {
if (!this.suppressRemoteUpdates && !ItemStack.matches(this.getCarried(), this.remoteCarried)) {
this.remoteCarried = this.getCarried().copy();
if (this.synchronizer != null) {
this.synchronizer.sendCarriedChange(this, this.remoteCarried);
}
}
}
@Override
public void setRemoteCarried(ItemStack itemstack) {
this.remoteCarried = itemstack.copy();
}
@Override
public void suppressRemoteUpdates() {
this.suppressRemoteUpdates = true;
}
@Override
public void resumeRemoteUpdates() {
this.suppressRemoteUpdates = false;
}
}
@@ -1,53 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.menu;
import com.lishid.openinv.internal.v1_21_R1.container.OpenEnderChest;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
public OpenEnderChestMenu(
@NotNull OpenEnderChest enderChest,
@NotNull ServerPlayer viewer,
int containerId,
boolean viewOnly) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu
Slot slot = this.slots.get(index);
if (slot.isFake() || !slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack original = itemStack.copy();
if (index < topSize) {
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
return ItemStack.EMPTY;
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return original;
}
}
@@ -1,262 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.menu;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenPlayerInventorySelf;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentDrop;
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentEquipment;
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
import com.lishid.openinv.util.Permissions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
private int offset;
public OpenInventoryMenu(OpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly);
}
private static MenuType<ChestMenu> getMenuType(OpenInventory inventory, ServerPlayer viewer) {
int size = inventory.getContainerSize();
// Disallow duplicate access to own main inventory contents.
if (inventory.getOwnerHandle().equals(viewer)) {
size -= viewer.getInventory().items.size();
size = ((int) Math.ceil(size / 9.0)) * 9;
}
return getChestMenuType(size);
}
@Override
protected void preSlotSetup() {
offset = ownContainer ? viewer.getInventory().items.size() : 0;
}
@Override
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
index += offset;
Slot slot = container.getMenuSlot(index, x, y);
// If the slot cannot be interacted with there's nothing to configure.
if (slot.getClass().equals(SlotViewOnly.class)) {
return slot;
}
// Remove drop slot if viewer is not allowed to use it.
if (slot instanceof ContentDrop.SlotDrop
&& (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) {
return new SlotViewOnly(container, index, x, y);
}
if (slot instanceof ContentEquipment.SlotEquipment equipment) {
if (viewOnly) {
return SlotViewOnly.wrap(slot);
}
Permissions perm = switch (equipment.getEquipmentSlot()) {
case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY;
case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY;
case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY;
case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY;
// Off-hand can hold anything, not just equipment.
default -> null;
};
// If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot.
if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) {
equipment.onlyEquipmentFor(container.getOwnerHandle());
}
// Equipment slots are a core part of the inventory, so they will always be shown.
return slot;
}
// When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above).
if (ownContainer && !(slot instanceof ContentDrop.SlotDrop)) {
return new SlotViewOnly(container, index, x, y);
}
if (viewOnly) {
return SlotViewOnly.wrap(slot);
}
return slot;
}
@Override
protected @NotNull CraftInventoryView<OpenChestMenu<OpenInventory>, Inventory> createBukkitEntity() {
org.bukkit.inventory.Inventory bukkitInventory;
if (viewOnly) {
bukkitInventory = new OpenDummyInventory(container);
} else if (ownContainer) {
bukkitInventory = new OpenPlayerInventorySelf(container, offset);
} else {
bukkitInventory = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) {
@Override
public org.bukkit.inventory.ItemStack getItem(int index) {
if (viewOnly || index < 0) {
return null;
}
Slot slot = slots.get(index);
return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
}
@Override
public boolean isInTop(int rawSlot) {
return rawSlot < topSize;
}
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
return null;
}
Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
"Slot %s outside of inventory", rawSlot);
if (rawSlot > topSize) {
return getBottomInventory();
}
Slot slot = slots.get(rawSlot);
if (slot.isFake()) {
return null;
}
return getTopInventory();
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
if (rawSlot < 0) {
return rawSlot;
}
if (rawSlot < topSize) {
Slot slot = slots.get(rawSlot);
if (slot.isFake()) {
return InventoryView.OUTSIDE;
}
return rawSlot;
}
int slot = rawSlot - topSize;
if (slot >= 27) {
slot -= 27;
} else {
slot += 9;
}
return slot;
}
@Override
public @NotNull InventoryType.SlotType getSlotType(int slot) {
if (viewOnly || slot < 0) {
return InventoryType.SlotType.OUTSIDE;
}
if (slot >= topSize) {
slot -= topSize;
if (slot >= 27) {
return InventoryType.SlotType.QUICKBAR;
}
return InventoryType.SlotType.CONTAINER;
}
return OpenInventoryMenu.this.container.getSlotType(offset + slot);
}
@Override
public int countSlots() {
return topSize + BOTTOM_INVENTORY_SIZE;
}
};
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu and InventoryMenu
Slot slot = this.slots.get(index);
if (!slot.hasItem() || slot.isFake()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack originalStack = itemStack.copy();
if (index < topSize) {
// If we're moving top to bottom, do a normal transfer.
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else {
EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack);
boolean movedGear = switch (equipmentSlot) {
// If this is gear, try to move it to the correct slot first.
case OFFHAND, FEET, LEGS, CHEST, HEAD -> {
// Locate the correct slot in the contents following the main inventory.
for (int extra = container.getOwnerHandle().getInventory().items.size() - offset; extra < topSize; ++extra) {
Slot extraSlot = getSlot(extra);
if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
// If we've found a matching slot, try to move to it.
// If this succeeds, even partially, we will not attempt to move to other slots.
// Otherwise, armor is already occupied, so we'll fall through to main inventory.
yield this.moveItemStackTo(itemStack, extra, extra + 1, false);
}
}
yield false;
}
// Non-gear gets no special treatment.
default -> false;
};
// If main inventory is not available, there's nowhere else to move.
if (offset != 0) {
if (!movedGear) {
return ItemStack.EMPTY;
}
} else {
// If we didn't move to a gear slot, try to move to a main inventory slot.
if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().items.size(), true)) {
return ItemStack.EMPTY;
}
}
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return originalStack;
}
}
@@ -1,69 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* An interface defining behaviors for entries in a {@link Container}. Used to reduce duplicate content reordering.
*/
public interface Content {
/**
* Update internal holder.
*
* @param holder the new holder
*/
void setHolder(@NotNull ServerPlayer holder);
/**
* Get the current item.
*
* @return the current item
*/
ItemStack get();
/**
* Remove the current item.
*
* @return the current item
*/
ItemStack remove();
/**
* Remove some of the current item.
*
* @return the current item
*/
ItemStack removePartial(int amount);
/**
* Set the current item. If slot is currently not usable, will drop item instead.
*
* @param itemStack the item to set
*/
void set(ItemStack itemStack);
/**
* Get a {@link Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will
* impose any specific restrictions to insertion or removal.
*
* @param container the backing container
* @param slot the slot of the backing container represented
* @param x clientside x dimension from top left of inventory, not used
* @param y clientside y dimension from top left of inventory, not used
* @return a menu slot
*/
Slot asSlot(Container container, int slot, int x, int y);
/**
* Get a loose Bukkit translation of what this slot stores. For example, any slot that drops items at the owner rather
* than insert them will report itself as being {@link org.bukkit.event.inventory.InventoryType.SlotType#OUTSIDE}.
*
* @return the closes Bukkit slot type
*/
org.bukkit.event.inventory.InventoryType.SlotType getSlotType();
}
@@ -1,131 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A slot in a survival crafting inventory. Unavailable when not online in a survival mode.
*/
public class ContentCrafting implements Content {
private final int index;
private ServerPlayer holder;
private List<ItemStack> items;
public ContentCrafting(@NotNull ServerPlayer holder, int index) {
setHolder(holder);
this.index = index;
}
private boolean isAvailable() {
return isAvailable(holder);
}
static boolean isAvailable(@NotNull ServerPlayer holder) {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival();
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
// Note: CraftingContainer#getItems is immutable! Be careful with updates.
this.items = holder.inventoryMenu.getCraftSlots().getContents();
}
@Override
public ItemStack get() {
return isAvailable() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
if (!this.isAvailable()) {
return ItemStack.EMPTY;
}
ItemStack removed = items.remove(index);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
return removed;
}
@Override
public ItemStack removePartial(int amount) {
if (!this.isAvailable()) {
return ItemStack.EMPTY;
}
ItemStack removed = ContainerHelper.removeItem(items, index, amount);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
return removed;
}
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
items.set(index, itemStack);
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
} else {
holder.drop(itemStack, false);
}
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotCrafting(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return isAvailable() ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE;
}
public class SlotCrafting extends SlotPlaceholder {
private SlotCrafting(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return isAvailable() ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder);
}
@Override
public boolean mayPickup(Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return isAvailable();
}
@Override
public boolean hasItem() {
return isAvailable() && super.hasItem();
}
@Override
public boolean isFake() {
return !isAvailable();
}
}
}
@@ -1,48 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot allowing viewing of the crafting result.
*
* <p>Unmodifiable because I said so. Use your own crafting grid.</p>
*/
public class ContentCraftingResult extends ContentViewOnly {
public ContentCraftingResult(@NotNull ServerPlayer holder) {
super(holder);
}
@Override
public ItemStack get() {
InventoryMenu inventoryMenu = holder.inventoryMenu;
return inventoryMenu.getSlot(inventoryMenu.getResultSlotIndex()).getItem();
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
if (!ContentCrafting.isAvailable(holder)) {
return Placeholders.survivalOnly(holder);
}
InventoryMenu inventoryMenu = holder.inventoryMenu;
return inventoryMenu.getSlot(inventoryMenu.getResultSlotIndex()).getItem();
}
};
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.RESULT;
}
}
@@ -1,117 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot wrapping the active menu's cursor. Unavailable when not online in a survival mode.
*/
public class ContentCursor implements Content {
private @NotNull ServerPlayer holder;
public ContentCursor(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return isAvailable() ? holder.containerMenu.getCarried() : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
ItemStack carried = holder.containerMenu.getCarried();
holder.containerMenu.setCarried(ItemStack.EMPTY);
return carried;
}
@Override
public ItemStack removePartial(int amount) {
ItemStack carried = holder.containerMenu.getCarried();
if (!carried.isEmpty() && carried.getCount() >= amount) {
ItemStack value = carried.split(amount);
if (carried.isEmpty()) {
holder.containerMenu.setCarried(ItemStack.EMPTY);
}
return value;
}
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
holder.containerMenu.setCarried(itemStack);
} else {
holder.drop(itemStack, false);
}
}
private boolean isAvailable() {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival();
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotCursor(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
// As close as possible to "not real"
return InventoryType.SlotType.OUTSIDE;
}
public class SlotCursor extends SlotPlaceholder {
private SlotCursor(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
if (!isAvailable()) {
return Placeholders.survivalOnly(holder);
}
ItemStack carried = holder.containerMenu.getCarried();
return carried.isEmpty() ? Placeholders.cursor : carried;
}
@Override
public boolean mayPickup(Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return isAvailable();
}
@Override
public boolean hasItem() {
return isAvailable() && super.hasItem();
}
@Override
public boolean isFake() {
return true;
}
}
}
@@ -1,88 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A fake slot used to drop items. Unavailable offline.
*/
public class ContentDrop implements Content {
private ServerPlayer holder;
public ContentDrop(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
return ItemStack.EMPTY;
}
@Override
public ItemStack removePartial(int amount) {
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
holder.drop(itemStack, true);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotDrop(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
// Behaves like dropping an item outside the screen, just by the target player.
return InventoryType.SlotType.OUTSIDE;
}
public class SlotDrop extends SlotPlaceholder {
private SlotDrop(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return holder.connection != null && !holder.connection.isDisconnected()
? Placeholders.drop
: Placeholders.blockedOffline;
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return holder.connection != null && !holder.connection.isDisconnected();
}
@Override
public boolean hasItem() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
}
@@ -1,78 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot for equipment that displays placeholders if empty.
*/
public class ContentEquipment extends ContentList {
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
public ContentEquipment(ServerPlayer holder, int index, EquipmentSlot equipmentSlot) {
super(holder, index, InventoryType.SlotType.ARMOR);
placeholder = switch (equipmentSlot) {
case HEAD -> Placeholders.emptyHelmet;
case CHEST -> Placeholders.emptyChestplate;
case LEGS -> Placeholders.emptyLeggings;
case FEET -> Placeholders.emptyBoots;
default -> Placeholders.emptyOffHand;
};
this.equipmentSlot = equipmentSlot;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.items = holder.getInventory().armor;
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y);
}
public class SlotEquipment extends SlotPlaceholder {
private ServerPlayer viewer;
SlotEquipment(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
ItemStack itemStack = getItem();
if (!itemStack.isEmpty()) {
return itemStack;
}
return placeholder;
}
public EquipmentSlot getEquipmentSlot() {
return equipmentSlot;
}
public void onlyEquipmentFor(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public boolean mayPlace(ItemStack var0) {
if (viewer == null) {
return true;
}
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(var0) == equipmentSlot;
}
}
}
@@ -1,58 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import java.util.List;
/**
* A normal slot backed by an item list.
*/
public abstract class ContentList implements Content {
private final int index;
private final InventoryType.SlotType slotType;
protected List<ItemStack> items;
public ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) {
this.index = index;
this.slotType = slotType;
setHolder(holder);
}
@Override
public ItemStack get() {
return items.get(index);
}
@Override
public ItemStack remove() {
ItemStack removed = items.remove(index);
return removed == null || removed.isEmpty() ? ItemStack.EMPTY : removed;
}
@Override
public ItemStack removePartial(int amount) {
return ContainerHelper.removeItem(items, index, amount);
}
@Override
public void set(ItemStack itemStack) {
items.set(index, itemStack);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new Slot(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return slotType;
}
}
@@ -1,46 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot for equipment that updates held items if necessary.
*/
public class ContentOffHand extends ContentEquipment {
private ServerPlayer holder;
public ContentOffHand(ServerPlayer holder, int localIndex) {
super(holder, localIndex, EquipmentSlot.OFFHAND);
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.items = holder.getInventory().offhand;
this.holder = holder;
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.QUICKBAR;
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y) {
@Override
public void setChanged() {
if (holder.connection != null
&& !holder.connection.isDisconnected()
&& holder.containerMenu != holder.inventoryMenu) {
holder.resendItemInHands();
}
}
};
}
}
@@ -1,56 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A view-only slot that can't be interacted with.
*/
public class ContentViewOnly implements Content {
@NotNull ServerPlayer holder;
public ContentViewOnly(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
return ItemStack.EMPTY;
}
@Override
public ItemStack removePartial(int amount) {
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.OUTSIDE;
}
}
@@ -1,20 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
/**
* An implementation of a slot as used by a menu that may have fake placeholder items.
*
* <p>Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories.</p>
*/
public abstract class SlotPlaceholder extends Slot {
public SlotPlaceholder(Container container, int index, int x, int y) {
super(container, index, x, y);
}
public abstract ItemStack getOrDefault();
}
@@ -1,151 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.container.slot;
import com.lishid.openinv.internal.v1_21_R1.container.Placeholders;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content.
*/
public class SlotViewOnly extends SlotPlaceholder {
public static @NotNull SlotViewOnly wrap(@NotNull Slot wrapped) {
SlotViewOnly wrapper;
if (wrapped instanceof SlotPlaceholder placeholder) {
wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return placeholder.getOrDefault();
}
};
} else {
wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return wrapped.getItem();
}
};
}
wrapper.index = wrapped.index;
return wrapper;
}
public SlotViewOnly(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return Placeholders.notSlot;
}
@Override
public void onQuickCraft(ItemStack var0, ItemStack var1) {
}
@Override
public void onTake(Player var0, ItemStack var1) {
}
@Override
public boolean mayPlace(ItemStack var0) {
return false;
}
@Override
public ItemStack getItem() {
return ItemStack.EMPTY;
}
@Override
public boolean hasItem() {
return false;
}
@Override
public void setByPlayer(ItemStack newStack) {
}
@Override
public void setByPlayer(ItemStack newStack, ItemStack oldStack) {
}
@Override
public void set(ItemStack var0) {
}
@Override
public void setChanged() {
}
@Override
public int getMaxStackSize() {
return 0;
}
@Override
public int getMaxStackSize(ItemStack itemStack) {
return 0;
}
@Override
public ItemStack remove(int amount) {
return ItemStack.EMPTY;
}
@Override
public boolean mayPickup(Player var0) {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public Optional<ItemStack> tryRemove(int var0, int var1, Player var2) {
return Optional.empty();
}
@Override
public ItemStack safeTake(int var0, int var1, Player var2) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeInsert(ItemStack itemStack) {
return itemStack;
}
@Override
public ItemStack safeInsert(ItemStack itemStack, int amount) {
return itemStack;
}
@Override
public boolean allowModification(Player var0) {
return false;
}
@Override
public int getContainerSlot() {
return this.slot;
}
@Override
public boolean isHighlightable() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
@@ -1,177 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.player;
import com.lishid.openinv.event.OpenEvents;
import com.mojang.logging.LogUtils;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
public class OpenPlayer extends CraftPlayer {
/**
* List of tags to always reset when saving.
*
* @see net.minecraft.world.entity.Entity#saveWithoutId(CompoundTag)
* @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag)
* @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag)
* @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag)
*/
private static final Set<String> RESET_TAGS = Set.of(
// Entity#saveWithoutId(CompoundTag)
"CustomName",
"CustomNameVisible",
"Silent",
"NoGravity",
"Glowing",
"TicksFrozen",
"HasVisualFire",
"Tags",
"Passengers",
// ServerPlayer#addAdditionalSaveData(CompoundTag)
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
"warden_spawn_tracker",
"enteredNetherPosition",
"SpawnX",
"SpawnY",
"SpawnZ",
"SpawnForced",
"SpawnAngle",
"SpawnDimension",
"raid_omen_position",
// Player#addAdditionalSaveData(CompoundTag)
"ShoulderEntityLeft",
"ShoulderEntityRight",
"LastDeathLocation",
"current_explosion_impact_pos",
// LivingEntity#addAdditionalSaveData(CompoundTag)
"active_effects",
"SleepingX",
"SleepingY",
"SleepingZ",
"Brain"
);
private final PlayerManager manager;
OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
super(server, entity);
this.manager = manager;
}
@Override
public void loadData() {
manager.loadData(getHandle());
}
@Override
public void saveData() {
if (OpenEvents.saveCancelled(this)) {
return;
}
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player.getName().getString(), player.getStringUUID()).orElse(null);
CompoundTag playerData = getWritableTag(oldData);
playerData = player.saveWithoutId(playerData);
setExtraData(playerData);
if (oldData != null) {
// Revert certain special data values when offline.
revertSpecialValues(playerData, oldData);
}
Path playerDataDir = worldNBTStorage.getPlayerDir().toPath();
Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat");
NbtIo.writeCompressed(playerData, tempFile);
Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat");
Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(dataFile, tempFile, backupFile);
} catch (Exception e) {
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
@Contract("null -> new")
private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
if (oldData == null) {
return new CompoundTag();
}
// Copy old data. This is a deep clone, so operating on it should be safe.
oldData = oldData.copy();
// Remove vanilla/server data that is not written every time.
oldData.getAllKeys()
.removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit"));
return oldData;
}
private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) {
// Revert automatic updates to play timestamps.
copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class);
copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class);
copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class);
}
private <T extends Tag> void copyValue(
@NotNull CompoundTag source,
@NotNull CompoundTag target,
@NotNull String container,
@NotNull String key,
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
// New container being null means the server implementation doesn't store this data.
if (newContainer == null) {
return;
}
// If old tag exists, copy it to new location, removing otherwise.
setTag(newContainer, key, getTag(oldContainer, key, tagType));
}
private <T extends Tag> @Nullable T getTag(
@Nullable CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
if (container == null) {
return null;
}
Tag value = container.get(key);
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
return null;
}
return dataType.cast(value);
}
private <T extends Tag> void setTag(
@NotNull CompoundTag container,
@NotNull String key,
@Nullable T data) {
if (data == null) {
container.remove(key);
} else {
container.put(key, data);
}
}
}
@@ -1,265 +0,0 @@
package com.lishid.openinv.internal.v1_21_R1.player;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.v1_21_R1.container.OpenEnderChest;
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
import com.mojang.authlib.GameProfile;
import com.mojang.serialization.Dynamic;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_21_R1.event.CraftEventFactory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.UUID;
import java.util.logging.Logger;
public class PlayerManager implements com.lishid.openinv.internal.PlayerManager {
private static boolean paper;
static {
try {
Class.forName("io.papermc.paper.configuration.Configuration");
paper = true;
} catch (ClassNotFoundException ignored) {
paper = false;
}
}
private final @NotNull Logger logger;
private @Nullable Field bukkitEntity;
public PlayerManager(@NotNull Logger logger) {
this.logger = logger;
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static @NotNull ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}
return nmsPlayer;
}
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
// Create a new ServerPlayer.
ServerPlayer entity = createNewPlayer(server, worldServer, offline);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
entity.getAdvancements().stopListening();
// Try to load the player's data.
if (loadData(entity)) {
// If data is loaded successfully, return the Bukkit entity.
return entity.getBukkitEntity();
}
return null;
}
private @NotNull ServerPlayer createNewPlayer(
@NotNull MinecraftServer server,
@NotNull ServerLevel worldServer,
@NotNull final OfflinePlayer offline) {
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
ClientInformation dummyInfo = new ClientInformation(
"en_us",
1, // Reduce distance just in case.
ChatVisiblity.HIDDEN, // Don't accept chat.
false,
ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION,
ServerPlayer.DEFAULT_MAIN_HAND,
true,
false // Don't list in player list (not that this player is in the list anyway).
);
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
try {
injectPlayer(entity);
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
}
return entity;
}
boolean loadData(@NotNull ServerPlayer player) {
// See CraftPlayer#loadData
CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player).orElse(null);
if (loadedData == null) {
// Exceptions with loading are logged by Mojang.
return false;
}
// Read basic data into the player.
player.load(loadedData);
// Also read "extra" data.
player.readAdditionalSaveData(loadedData);
// Game type settings are also loaded separately.
player.loadGameTypes(loadedData);
if (paper) {
// Paper: world is not loaded by ServerPlayer#load(CompoundTag).
parseWorld(player, loadedData);
}
return true;
}
private void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
// See PlayerList#placeNewPlayer
World bukkitWorld;
if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) {
// Modern Bukkit world.
bukkitWorld = Bukkit.getServer().getWorld(new UUID(loadedData.getLong("WorldUUIDMost"), loadedData.getLong("WorldUUIDLeast")));
} else if (loadedData.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) {
// Legacy Bukkit world.
bukkitWorld = Bukkit.getServer().getWorld(loadedData.getString("world"));
} else {
// Vanilla player data.
DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension")))
.resultOrPartial(logger::warning)
.map(player.server::getLevel)
// If ServerLevel exists, set, otherwise move to spawn.
.ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null));
return;
}
if (bukkitWorld == null) {
player.spawnIn(null);
return;
}
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
}
private void injectPlayer(ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this));
}
@Override
public @NotNull Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
injectPlayer(nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
return player;
}
}
@Override
public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory, boolean viewOnly) {
ServerPlayer player = getHandle(bukkitPlayer);
if (player.connection == null) {
return null;
}
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
AbstractContainerMenu menu;
Component title;
if (inventory instanceof OpenInventory playerInv) {
menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly);
title = playerInv.getTitle(player);
} else if (inventory instanceof OpenEnderChest enderChest) {
menu = enderChest.createMenu(player, player.nextContainerCounter(), viewOnly);
title = enderChest.getTitle();
} else {
return null;
}
// Should never happen, player is a ServerPlayer with an active connection.
if (menu == null) {
return null;
}
// Set up title. Title can only be set once for a menu, and is set during the open process.
// Further title changes are a hack where the client is sent a "new" inventory with the same ID,
// resulting in a title change but no other state modifications (like cursor position).
menu.setTitle(title);
menu = CraftEventFactory.callInventoryOpenEvent(player, menu, false);
// Menu is null if event is cancelled.
if (menu == null) {
return null;
}
player.containerMenu = menu;
player.connection.send(new ClientboundOpenScreenPacket(menu.containerId, menu.getType(), menu.getTitle()));
player.initMenu(menu);
return menu.getBukkitView();
}
}
-77
View File
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2023 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>5.1.3</version>
</parent>
<artifactId>openinvadapter1_21_R2</artifactId>
<name>OpenInvAdapter1_21_R2</name>
<properties>
<maven.compiler.system>21</maven.compiler.system>
<maven.compiler.release>21</maven.compiler.release>
<spigot.version>1.21.3-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
</dependency>
<dependency>
<artifactId>spigot</artifactId>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>${spigot.version}</version>
<classifier>remapped-mojang</classifier>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>openinvcommon</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.md-5</groupId>
<artifactId>specialsource-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,81 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2;
import com.lishid.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.v1_21_R2.container.AnySilentContainer;
import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.world.Container;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class InternalAccessor implements Accessor {
private final @NotNull Logger logger;
private final @NotNull PlayerManager manager;
private final @NotNull AnySilentContainer anySilentContainer;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
manager = new PlayerManager(logger);
anySilentContainer = new AnySilentContainer(logger, lang);
}
@Override
public @NotNull PlayerManager getPlayerManager() {
return manager;
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return anySilentContainer;
}
@Override
public @NotNull ISpecialPlayerInventory createPlayerInventory(@NotNull Player player) {
return new OpenInventory(player);
}
@Override
public @NotNull ISpecialEnderChest createEnderChest(@NotNull Player player) {
return new OpenEnderChest(player);
}
@Override
public <T extends ISpecialInventory> @Nullable T get(@NotNull Inventory bukkitInventory, @NotNull Class<T> clazz) {
if (!(bukkitInventory instanceof CraftInventory craftInventory)) {
return null;
}
Container container = craftInventory.getInventory();
if (clazz.isInstance(container)) {
return clazz.cast(container);
}
return null;
}
@Override
public void reload(@NotNull ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
if (placeholders != null) {
try {
Placeholders.load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
}
@@ -1,210 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_21_R2.container;
import com.lishid.openinv.internal.AnySilentContainerBase;
import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
import com.lishid.openinv.util.ReflectionHelper;
import com.lishid.openinv.util.lang.LanguageManager;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.TrappedChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.logging.Logger;
public class AnySilentContainer extends AnySilentContainerBase {
private final @NotNull Logger logger;
private final @NotNull LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
this.logger = logger;
this.lang = lang;
try {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
logger.warning("ServerPlayerGameMode#gameModeForPlayer's obfuscated name has changed!");
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
}
} catch (SecurityException e) {
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
}
}
@Override
public boolean activateContainer(
@NotNull final Player bukkitPlayer,
final boolean silentchest,
@NotNull final org.bukkit.block.Block bukkitBlock) {
// Silent ender chest is API-only
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
final net.minecraft.world.level.Level level = player.level();
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
return false;
}
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
enderChest.setActiveChest(enderChestTile);
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
MenuType<?> containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
int rows = enderChest.getContainerSize() / 9;
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
}, Component.translatable("container.enderchest")));
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
if (!(blockEntity instanceof MenuProvider menuProvider)) {
return false;
}
BlockState blockState = level.getBlockState(blockPos);
Block block = blockState.getBlock();
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
if (menuProvider == null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
}
}
if (block instanceof ShulkerBoxBlock) {
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
}
if (block instanceof BarrelBlock) {
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
}
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
player.openMenu(menuProvider);
return true;
}
// SilentChest requires access to setting players' game mode directly.
if (this.serverPlayerGameModeGameType == null) {
return false;
}
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
if (lootable.lootTable != null) {
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
}
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
player.openMenu(menuProvider);
this.forceGameType(player, gameType);
return true;
}
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
// Regular game mode set calls GameModeChangeEvent and is cancellable.
GameType gameType = player.gameMode.getGameModeForPlayer();
this.forceGameType(player, GameType.SPECTATOR);
// ServerPlayer#closeContainer cannot be called without entering an
// infinite loop because this method is called during inventory close.
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
// From ServerPlayer#closeContainer
player.doCloseContainer();
// Regular inventory close will handle the rest - packet sending, etc.
// Revert forced game mode.
this.forceGameType(player, gameType);
}
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
if (this.serverPlayerGameModeGameType == null) {
// No need to warn repeatedly, error on startup and lack of function should be enough.
return;
}
try {
this.serverPlayerGameModeGameType.setAccessible(true);
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
} catch (IllegalArgumentException | IllegalAccessException e) {
logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e);
}
}
}
@@ -1,196 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned<ServerPlayer>,
ISpecialEnderChest {
private CraftInventory inventory;
private @NotNull ServerPlayer owner;
private NonNullList<ItemStack> items;
private int maxStack = 64;
private final List<HumanEntity> transaction = new ArrayList<>();
public OpenEnderChest(@NotNull org.bukkit.entity.Player player) {
this.owner = PlayerManager.getHandle(player);
this.items = owner.getEnderChestInventory().items;
}
@Override
public @NotNull ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
if (inventory == null) {
inventory = new CraftInventory(this) {
@Override
public @NotNull InventoryType getType() {
return InventoryType.ENDER_CHEST;
}
};
}
return inventory;
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
owner = PlayerManager.getHandle(player);
NonNullList<ItemStack> activeItems = owner.getEnderChestInventory().items;
// Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally.
int max = Math.min(items.size(), activeItems.size());
for (int index = 0; index < max; ++index) {
activeItems.set(index, items.get(index));
}
items = activeItems;
}
@Override
public void setPlayerOffline() {}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public int getContainerSize() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int index, int amount) {
ItemStack itemstack = ContainerHelper.removeItem(items, index, amount);
if (!itemstack.isEmpty()) {
setChanged();
}
return itemstack;
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY;
}
@Override
public void setItem(int index, ItemStack itemStack) {
if (index >= 0 && index < items.size()) {
items.set(index, itemStack);
}
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
return items;
}
@Override
public void onOpen(CraftHumanEntity craftHumanEntity) {
transaction.add(craftHumanEntity);
}
@Override
public void onClose(CraftHumanEntity craftHumanEntity) {
transaction.remove(craftHumanEntity);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return getPlayer();
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void clearContent() {
items.clear();
setChanged();
}
@Override
public void fillStackedContents(StackedItemContents stackedContents) {
for (ItemStack itemstack : items) {
stackedContents.accountStack(itemstack);
}
}
public Component getTitle() {
return Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName())
.append(Component.translatable("container.enderchest"))
.append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName()));
}
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,364 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenPlayerInventory;
import com.lishid.openinv.internal.v1_21_R2.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.v1_21_R2.container.slot.Content;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCrafting;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCraftingResult;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentCursor;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentDrop;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentEquipment;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentList;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentOffHand;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentViewOnly;
import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly;
import com.lishid.openinv.internal.v1_21_R2.player.PlayerManager;
import net.minecraft.ChatFormatting;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class OpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
private final List<Content> slots;
private final int size;
private ServerPlayer owner;
private int maxStackSize = 99;
private CraftInventory bukkitEntity;
public List<HumanEntity> transaction = new ArrayList<>();
public OpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) {
owner = PlayerManager.getHandle(bukkitPlayer);
// Get total size, rounding up to nearest 9 for client compatibility.
int rawSize = owner.getInventory().getContainerSize() + owner.inventoryMenu.getCraftSlots().getContainerSize() + 1;
size = ((int) Math.ceil(rawSize / 9.0)) * 9;
slots = NonNullList.withSize(size, new ContentViewOnly(owner));
setupSlots();
}
private void setupSlots() {
// Top of inventory: Regular contents.
int nextIndex = addMainInventory();
// If inventory is expected size, we can arrange slots to be pretty.
Inventory ownerInv = owner.getInventory();
if (ownerInv.items.size() == 36
&& ownerInv.armor.size() == 4
&& ownerInv.offhand.size() == 1
&& owner.inventoryMenu.getCraftSlots().getContainerSize() == 4) {
// Armor slots: Bottom left.
addArmor(36);
// Off-hand: Below chestplate.
addOffHand(46);
// Drop slot: Bottom right.
slots.set(53, new ContentDrop(owner));
// Cursor slot: Above drop.
slots.set(44, new ContentCursor(owner));
// Crafting is displayed in the bottom right corner.
// As we're using the pretty view, this is a 3x2.
addCrafting(41, true);
return;
}
// Otherwise we'll just add elements linearly.
nextIndex = addArmor(nextIndex);
nextIndex = addOffHand(nextIndex);
nextIndex = addCrafting(nextIndex, false);
slots.set(nextIndex, new ContentCursor(owner));
// Drop slot last.
slots.set(slots.size() - 1, new ContentDrop(owner));
}
private int addMainInventory() {
int listSize = owner.getInventory().items.size();
// Hotbar slots are 0-8. We want those to appear on the bottom of the inventory like a normal player inventory,
// so everything else needs to move up a row.
int hotbarDiff = listSize - 9;
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
InventoryType.SlotType type;
int invIndex;
if (localIndex < hotbarDiff) {
invIndex = localIndex + 9;
type = InventoryType.SlotType.CONTAINER;
} else {
type = InventoryType.SlotType.QUICKBAR;
invIndex = localIndex - hotbarDiff;
}
slots.set(localIndex, new ContentList(owner, invIndex, type) {
@Override
public void setHolder(@NotNull ServerPlayer holder) {
items = holder.getInventory().items;
}
});
}
return listSize;
}
private int addArmor(int startIndex) {
int listSize = owner.getInventory().armor.size();
for (int i = 0; i < listSize; ++i) {
// Armor slots go bottom to top; boots are slot 0, helmet is slot 3.
// Since we have to display horizontally due to space restrictions,
// making the left side the "top" is more user-friendly.
int armorIndex;
EquipmentSlot slot;
switch (i) {
case 3 -> {
armorIndex = 0;
slot = EquipmentSlot.FEET;
}
case 2 -> {
armorIndex = 1;
slot = EquipmentSlot.LEGS;
}
case 1 -> {
armorIndex = 2;
slot = EquipmentSlot.CHEST;
}
case 0 -> {
armorIndex = 3;
slot = EquipmentSlot.HEAD;
}
default -> {
// In the event that new armor slots are added, they can be placed at the end.
armorIndex = i;
slot = EquipmentSlot.MAINHAND;
}
}
slots.set(startIndex + i, new ContentEquipment(owner, armorIndex, slot));
}
return startIndex + listSize;
}
private int addOffHand(int startIndex) {
int listSize = owner.getInventory().offhand.size();
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
slots.set(startIndex + localIndex, new ContentOffHand(owner, localIndex));
}
return startIndex + listSize;
}
private int addCrafting(int startIndex, boolean pretty) {
int listSize = owner.inventoryMenu.getCraftSlots().getContents().size();
pretty &= listSize == 4;
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
// Pretty display is a 2x2 rather than linear.
// If index is in top row, grid is not 2x2, or pretty is disabled, just use current index.
// Otherwise, subtract 2 and add 9 to start in the same position on the next row.
int modIndex = startIndex + (localIndex < 2 || !pretty ? localIndex : localIndex + 7);
slots.set(modIndex, new ContentCrafting(owner, localIndex));
}
if (pretty) {
slots.set(startIndex + 2, new ContentViewOnly(owner) {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return Placeholders.craftingOutput;
}
};
}
});
slots.set(startIndex + 11, new ContentCraftingResult(owner));
}
return startIndex + listSize;
}
public Slot getMenuSlot(int index, int x, int y) {
return slots.get(index).asSlot(this, index, x, y);
}
public InventoryType.SlotType getSlotType(int index) {
return slots.get(index).getSlotType();
}
public @NotNull Component getTitle(@Nullable ServerPlayer viewer) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
component.append(
Component.translatableWithFallback("openinv.container.inventory.self", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(ResourceLocation.parse("openinv:font/inventory"))
.withColor(ChatFormatting.WHITE)));
}
// Normal title: "Inventory - OwnerName"
component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName()))
.append(Component.translatable("container.inventory"))
.append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName()));
return component;
}
@Override
public ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
if (bukkitEntity == null) {
bukkitEntity = new OpenPlayerInventory(this);
}
return bukkitEntity;
}
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
ServerPlayer newOwner = PlayerManager.getHandle(player);
// Only transfer regular inventory - crafting and cursor slots are transient.
newOwner.getInventory().replaceWith(owner.getInventory());
owner = newOwner;
// Update slots to point to new inventory.
slots.forEach(slot -> slot.setHolder(newOwner));
}
@Override
public void setPlayerOffline() {}
@Override
public boolean isInUse() {
return !transaction.isEmpty();
}
@Override
public @NotNull org.bukkit.entity.Player getPlayer() {
return getOwner();
}
@Override
public int getContainerSize() {
return size;
}
@Override
public boolean isEmpty() {
return slots.stream().map(Content::get).allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return slots.get(index).get();
}
@Override
public ItemStack removeItem(int index, int amount) {
return slots.get(index).removePartial(amount);
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return slots.get(index).remove();
}
@Override
public void setItem(int index, ItemStack itemStack) {
slots.get(index).set(itemStack);
}
@Override
public int getMaxStackSize() {
return maxStackSize;
}
@Override
public void setMaxStackSize(int maxStackSize) {
this.maxStackSize = maxStackSize;
}
@Override
public void setChanged() {}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
NonNullList<ItemStack> contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY);
for (int i = 0; i < getContainerSize(); ++i) {
contents.set(i, getItem(i));
}
return contents;
}
@Override
public void onOpen(CraftHumanEntity viewer) {
transaction.add(viewer);
}
@Override
public void onClose(CraftHumanEntity viewer) {
transaction.remove(viewer);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return owner.getBukkitEntity();
}
@Override
public Location getLocation() {
return owner.getBukkitEntity().getLocation();
}
@Override
public void clearContent() {
owner.getInventory().clearContent();
owner.inventoryMenu.getCraftSlots().clearContent();
owner.inventoryMenu.slotsChanged(owner.inventoryMenu.getCraftSlots());
owner.containerMenu.setCarried(ItemStack.EMPTY);
}
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,183 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.TagParser;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Unit;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.entity.BannerPattern;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BannerPatterns;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.v1_21_R2.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.EnumMap;
import java.util.List;
import java.util.Optional;
public final class Placeholders {
private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(9999);
public static final @NotNull EnumMap<GameType, ItemStack> BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class);
public static @NotNull ItemStack craftingOutput = defaultCraftingOutput();
public static @NotNull ItemStack cursor = defaultCursor();
public static @NotNull ItemStack drop = defaultDrop();
public static @NotNull ItemStack emptyHelmet = getEmptyArmor(Items.LEATHER_HELMET);
public static @NotNull ItemStack emptyChestplate = getEmptyArmor(Items.LEATHER_CHESTPLATE);
public static @NotNull ItemStack emptyLeggings = getEmptyArmor(Items.LEATHER_LEGGINGS);
public static @NotNull ItemStack emptyBoots = getEmptyArmor(Items.LEATHER_BOOTS);
public static @NotNull ItemStack emptyOffHand = getEmptyShield();
public static @NotNull ItemStack notSlot = defaultNotSlot();
public static @NotNull ItemStack blockedOffline = defaultBlockedOffline();
static {
for (GameType type : GameType.values()) {
// Barrier: "Not available - Creative" etc.
ItemStack typeItem = new ItemStack(Items.BARRIER);
typeItem.set(
DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName()));
BLOCKED_GAME_TYPE.put(type, typeItem);
}
}
public static void load(@NotNull ConfigurationSection section) throws Exception {
craftingOutput = parse(section, "crafting-output", craftingOutput);
cursor = parse(section, "cursor", cursor);
drop = parse(section, "drop", drop);
emptyHelmet = parse(section, "empty-helmet", emptyHelmet);
emptyChestplate = parse(section, "empty-chestplate", emptyChestplate);
emptyLeggings = parse(section, "empty-leggings", emptyLeggings);
emptyBoots = parse(section, "empty-boots", emptyBoots);
emptyOffHand = parse(section, "empty-off-hand", emptyOffHand);
notSlot = parse(section, "not-a-slot", notSlot);
blockedOffline = parse(section, "blocked.offline", blockedOffline);
BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", BLOCKED_GAME_TYPE.get(GameType.CREATIVE)));
BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", BLOCKED_GAME_TYPE.get(GameType.SPECTATOR)));
}
private static @NotNull ItemStack parse(
@NotNull ConfigurationSection section,
@NotNull String path,
@NotNull ItemStack defaultStack) throws Exception {
String itemText = section.getString(path);
if (itemText == null) {
return defaultStack;
}
CompoundTag compoundTag = TagParser.parseTag(itemText);
Optional<ItemStack> parsed = ItemStack.parse(CraftRegistry.getMinecraftRegistry(), compoundTag);
return parsed.filter(itemStack -> !itemStack.isEmpty()).orElse(defaultStack);
}
public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) {
if (serverPlayer.connection == null || serverPlayer.connection.isDisconnected()) {
return blockedOffline;
}
return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY);
}
private static ItemStack defaultCraftingOutput() {
// Crafting table: "Crafting"
ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE);
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting"));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack defaultCursor() {
// Cursor-like banner with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_BANNER);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT).value();
BannerPattern downRight = bannerPatterns.getOrThrow(BannerPatterns.STRIPE_DOWNRIGHT).value();
BannerPattern border = bannerPatterns.getOrThrow(BannerPatterns.BORDER).value();
itemStack.set(DataComponents.BANNER_PATTERNS,
new BannerPatternLayers(List.of(
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY))));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
return itemStack;
}
private static ItemStack defaultDrop() {
// Dropper: "Drop Selected Item"
ItemStack itemStack = new ItemStack(Items.DROPPER);
// Note: translatable component, not keybind component! We want the text identifying the keybind, not the key.
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("key.drop"));
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack getEmptyArmor(ItemLike item) {
// Inventory-background-grey-ish leather armor with no tooltip
ItemStack itemStack = new ItemStack(item);
DyedItemColor color = new DyedItemColor(0xC8C8C8, false);
itemStack.set(DataComponents.DYED_COLOR, color);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack getEmptyShield() {
ItemStack itemStack = new ItemStack(Items.SHIELD);
itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfLeft = bannerPatterns.getOrThrow(BannerPatterns.HALF_VERTICAL).value();
BannerPattern topLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_LEFT).value();
BannerPattern topRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_RIGHT).value();
BannerPattern bottomLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_LEFT).value();
BannerPattern bottomRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_RIGHT).value();
itemStack.set(DataComponents.BANNER_PATTERNS,
new BannerPatternLayers(List.of(
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfLeft), DyeColor.BLACK),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK),
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK))));
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack defaultNotSlot() {
// White pane with no tooltip
ItemStack itemStack = new ItemStack(Items.WHITE_STAINED_GLASS_PANE);
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
return itemStack;
}
private static ItemStack defaultBlockedOffline() {
// Barrier: "Not available - Offline"
ItemStack itemStack = new ItemStack(Items.BARRIER);
itemStack.set(DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable")
.append(Component.literal(" - "))
.append(Component.translatable("gui.socialInteractions.status_offline")));
return itemStack;
}
private Placeholders() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,163 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.bukkit;
import com.lishid.openinv.internal.ViewOnly;
import net.minecraft.world.Container;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.ListIterator;
/**
* A locked down "empty" inventory that rejects plugin interaction.
*/
public class OpenDummyInventory extends CraftInventory implements ViewOnly {
public OpenDummyInventory(Container inventory) {
super(inventory);
}
@Override
public @Nullable ItemStack getItem(int index) {
return null;
}
@Override
public void setItem(int index, @Nullable ItemStack item) {
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> removeItem(@NotNull ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
private static @NotNull HashMap<Integer, ItemStack> arrayToHashMap(@NotNull ItemStack[] items) {
HashMap<Integer, ItemStack> ignored = new HashMap<>();
for (int index = 0; index < items.length; ++index) {
ignored.put(index, items[index]);
}
return ignored;
}
@Override
public ItemStack[] getContents() {
return new ItemStack[getSize()];
}
@Override
public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
}
@Override
public @NotNull ItemStack[] getStorageContents() {
return new ItemStack[getSize()];
}
@Override
public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
}
@Override
public boolean contains(@NotNull Material material) throws IllegalArgumentException {
return false;
}
@Override
public boolean contains(@Nullable ItemStack item) {
return false;
}
@Override
public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException {
return false;
}
@Override
public boolean contains(@Nullable ItemStack item, int amount) {
return false;
}
@Override
public boolean containsAtLeast(@Nullable ItemStack item, int amount) {
return false;
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(
@NotNull Material material) throws IllegalArgumentException {
return new HashMap<>();
}
@SuppressWarnings("NonApiType")
@Override
public @NotNull HashMap<Integer, ItemStack> all(@Nullable ItemStack item) {
return new HashMap<>();
}
@Override
public int first(@NotNull Material material) throws IllegalArgumentException {
return -1;
}
@Override
public int first(@NotNull ItemStack item) {
return -1;
}
@Override
public int firstEmpty() {
return -1;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void remove(@NotNull Material material) throws IllegalArgumentException {
}
@Override
public void remove(@NotNull ItemStack item) {
}
@Override
public void clear(int index) {
}
@Override
public void clear() {
}
@Override
public @NotNull ListIterator<ItemStack> iterator() {
return Collections.emptyListIterator();
}
@Override
public @NotNull ListIterator<ItemStack> iterator(int index) {
return Collections.emptyListIterator();
}
}
@@ -1,221 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.bukkit;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenPlayerInventory extends CraftInventory implements PlayerInventory {
public OpenPlayerInventory(@NotNull OpenInventory inventory) {
super(inventory);
}
@Override
public @NotNull OpenInventory getInventory() {
return (OpenInventory) super.getInventory();
}
@Override
public ItemStack[] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
}
@Override
public void setContents(ItemStack[] items) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
int size = internal.getContainerSize();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < size; ++index) {
if (index < items.length) {
internal.setItem(index, CraftItemStack.asNMSCopy(items[index]));
} else {
internal.setItem(index, net.minecraft.world.item.ItemStack.EMPTY);
}
}
}
@Override
public ItemStack[] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().items);
}
@Override
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().items;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public @NotNull InventoryType getType() {
return InventoryType.PLAYER;
}
@Override
public @NotNull Player getHolder() {
return getInventory().getOwner();
}
@Override
public @NotNull ItemStack[] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().armor);
}
@Override
public void setArmorContents(@Nullable ItemStack[] items) {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().armor;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public @NotNull ItemStack[] getExtraContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand);
}
@Override
public void setExtraContents(@Nullable ItemStack[] items) {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().offhand;
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public @Nullable ItemStack getHelmet() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.HEAD.getIndex()));
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.HEAD.getIndex(), CraftItemStack.asNMSCopy(helmet));
}
@Override
public @Nullable ItemStack getChestplate() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.HEAD.getIndex()));
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.CHEST.getIndex(), CraftItemStack.asNMSCopy(chestplate));
}
@Override
public @Nullable ItemStack getLeggings() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.LEGS.getIndex()));
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.LEGS.getIndex(), CraftItemStack.asNMSCopy(leggings));
}
@Override
public @Nullable ItemStack getBoots() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory()
.getArmor(EquipmentSlot.FEET.getIndex()));
}
@Override
public void setBoots(@Nullable ItemStack boots) {
getInventory().getOwnerHandle().getInventory().armor
.set(EquipmentSlot.FEET.getIndex(), CraftItemStack.asNMSCopy(boots));
}
@Override
public @NotNull ItemStack getItemInMainHand() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return CraftItemStack.asCraftMirror(internal.getItem(internal.selected));
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
internal.setItem(internal.selected, CraftItemStack.asNMSCopy(item));
}
@Override
public @NotNull ItemStack getItemInOffHand() {
return CraftItemStack.asCraftMirror(getInventory().getOwnerHandle().getInventory().offhand.getFirst());
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
getInventory().getOwnerHandle().getInventory().offhand.set(0, CraftItemStack.asNMSCopy(item));
}
@Override
public @NotNull ItemStack getItemInHand() {
return getItemInMainHand();
}
@Override
public void setItemInHand(@Nullable ItemStack stack) {
setItemInMainHand(stack);
}
@Override
public int getHeldItemSlot() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return internal.items.size() - 9 + internal.selected;
}
@Override
public void setHeldItemSlot(int slot) {
slot %= 9;
getInventory().getOwnerHandle().getInventory().selected = slot;
}
@Override
public @Nullable ItemStack getItem(@NotNull org.bukkit.inventory.EquipmentSlot slot) {
return switch (slot) {
case HAND -> getItemInMainHand();
case OFF_HAND -> getItemInOffHand();
case FEET -> getBoots();
case LEGS -> getLeggings();
case CHEST -> getChestplate();
case HEAD -> getHelmet();
default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot);
};
}
@Override
public void setItem(@NotNull org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) {
switch (slot) {
case HAND -> setItemInMainHand(item);
case OFF_HAND -> setItemInOffHand(item);
case FEET -> setBoots(item);
case LEGS -> setLeggings(item);
case CHEST -> setChestplate(item);
case HEAD -> setHelmet(item);
default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot);
}
}
}
@@ -1,26 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.bukkit;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public class OpenPlayerInventorySelf extends OpenPlayerInventory {
private final int offset;
public OpenPlayerInventorySelf(@NotNull OpenInventory inventory, int offset) {
super(inventory);
this.offset = offset;
}
@Override
public ItemStack getItem(int index) {
return super.getItem(offset + index);
}
@Override
public void setItem(int index, ItemStack item) {
super.setItem(offset + index, item);
}
}
@@ -1,478 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.menu;
import com.google.common.base.Suppliers;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.InternalOwned;
import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotPlaceholder;
import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.inventory.ContainerSynchronizer;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* An extension of {@link AbstractContainerMenu} that supports {@link SlotPlaceholder placeholders}.
*/
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
extends AbstractContainerMenu {
protected static final int BOTTOM_INVENTORY_SIZE = 36;
protected final T container;
protected final ServerPlayer viewer;
protected final boolean viewOnly;
protected final boolean ownContainer;
protected final int topSize;
private CraftInventoryView<OpenChestMenu<T>, Inventory> bukkitEntity;
// Syncher fields
private @Nullable ContainerSynchronizer synchronizer;
private final List<DataSlot> dataSlots = new ArrayList<>();
private final IntList remoteDataSlots = new IntArrayList();
private final List<ContainerListener> containerListeners = new ArrayList<>();
private ItemStack remoteCarried = ItemStack.EMPTY;
private boolean suppressRemoteUpdates;
protected OpenChestMenu(
@NotNull MenuType<ChestMenu> type,
int containerCounter,
@NotNull T container,
@NotNull ServerPlayer viewer,
boolean viewOnly) {
super(type, containerCounter);
this.container = container;
this.viewer = viewer;
this.viewOnly = viewOnly;
ownContainer = container.getOwnerHandle().equals(viewer);
topSize = getTopSize(viewer);
preSlotSetup();
int upperRows = topSize / 9;
// View's upper inventory - our container
for (int row = 0; row < upperRows; ++row) {
for (int col = 0; col < 9; ++col) {
// x and y for client purposes, but hey, we're thorough here.
// Adapted from net.minecraft.world.inventory.ChestMenu
int x = 8 + col * 18;
int y = 18 + row * 18;
int index = row * 9 + col;
// Guard against weird inventory sizes.
if (index >= container.getContainerSize()) {
addSlot(new SlotViewOnly(container, index, x, y));
continue;
}
Slot slot = getUpperSlot(index, x, y);
addSlot(slot);
}
}
// View's lower inventory - viewer inventory
int playerInvPad = (upperRows - 4) * 18;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + row * 18 + 103;
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
}
}
// Hotbar
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + 161;
addSlot(new Slot(viewer.getInventory(), col, x, y));
}
}
public static @NotNull MenuType<ChestMenu> getChestMenuType(int inventorySize) {
inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9;
return switch (inventorySize) {
case 9 -> MenuType.GENERIC_9x1;
case 18 -> MenuType.GENERIC_9x2;
case 27 -> MenuType.GENERIC_9x3;
case 36 -> MenuType.GENERIC_9x4;
case 45 -> MenuType.GENERIC_9x5;
case 54 -> MenuType.GENERIC_9x6;
default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize);
};
}
protected void preSlotSetup() {}
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
if (viewOnly) {
return new SlotViewOnly(container, index, x, y);
}
return new Slot(container, index, x, y);
}
@Override
public final @NotNull CraftInventoryView<OpenChestMenu<T>, Inventory> getBukkitView() {
if (bukkitEntity == null) {
bukkitEntity = createBukkitEntity();
}
return bukkitEntity;
}
protected @NotNull CraftInventoryView<OpenChestMenu<T>, Inventory> createBukkitEntity() {
Inventory top;
if (viewOnly) {
top = new OpenDummyInventory(container);
} else {
top = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) {
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
return super.getInventory(rawSlot);
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
return super.convertSlot(rawSlot);
}
@Override
public @NotNull InventoryType.SlotType getSlotType(int slot) {
if (viewOnly) {
return InventoryType.SlotType.OUTSIDE;
}
return super.getSlotType(slot);
}
};
}
private int getTopSize(ServerPlayer viewer) {
MenuType<?> menuType = getType();
if (menuType == null) {
throw new IllegalStateException("MenuType cannot be null!");
} else if (menuType == MenuType.GENERIC_9x1) {
return 9;
} else if (menuType == MenuType.GENERIC_9x2) {
return 18;
} else if (menuType == MenuType.GENERIC_9x3) {
return 27;
} else if (menuType == MenuType.GENERIC_9x4) {
return 36;
} else if (menuType == MenuType.GENERIC_9x5) {
return 45;
} else if (menuType == MenuType.GENERIC_9x6) {
return 54;
}
// This is a bit gross, but allows us a safe fallthrough.
return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE;
}
/**
* Reimplementation of {@link AbstractContainerMenu#moveItemStackTo(ItemStack, int, int, boolean)} that ignores fake
* slots and respects {@link Slot#hasItem()}.
*
* @param itemStack the stack to quick-move
* @param rangeLow the start of the range of slots that can be moved to, inclusive
* @param rangeHigh the end of the range of slots that can be moved to, exclusive
* @param topDown whether to start at the top of the range or bottom
* @return whether the stack was modified as a result of being quick-moved
*/
@Override
protected boolean moveItemStackTo(ItemStack itemStack, int rangeLow, int rangeHigh, boolean topDown) {
boolean modified = false;
boolean stackable = itemStack.isStackable();
Slot firstEmpty = null;
for (int index = topDown ? rangeHigh - 1 : rangeLow;
!itemStack.isEmpty() && (topDown ? index >= rangeLow : index < rangeHigh);
index += topDown ? -1 : 1
) {
Slot slot = slots.get(index);
// If the slot cannot be added to, check the next slot.
if (slot.isFake() || !slot.mayPlace(itemStack)) {
continue;
}
if (slot.hasItem()) {
// If the item isn't stackable, check the next slot.
if (!stackable) {
continue;
}
// Otherwise, add as many as we can from our stack to the slot.
modified |= addToExistingStack(itemStack, slot);
} else {
// If this is the first empty slot, keep track of it for later use.
if (firstEmpty == null) {
firstEmpty = slot;
}
// If the item isn't stackable, we've located the slot we're adding it to, so we're done.
if (!stackable) {
break;
}
}
}
// If the item hasn't been fully added yet, add as many as we can to the first open slot.
if (!itemStack.isEmpty() && firstEmpty != null) {
firstEmpty.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), firstEmpty.getMaxStackSize(itemStack))));
firstEmpty.setChanged();
modified = true;
}
return modified;
}
private static boolean addToExistingStack(ItemStack itemStack, Slot slot) {
ItemStack existing = slot.getItem();
// If the items aren't the same, we can't add our item.
if (!ItemStack.isSameItemSameComponents(itemStack, existing)) {
return false;
}
int max = slot.getMaxStackSize(existing);
int existingCount = existing.getCount();
// If the stack is already full, we can't add more.
if (existingCount >= max) {
return false;
}
int total = existingCount + itemStack.getCount();
// If the existing item can accept the entirety of our item, we're done!
if (total <= max) {
itemStack.setCount(0);
existing.setCount(total);
slot.setChanged();
return true;
}
// Otherwise, add as many as we can.
itemStack.shrink(max - existingCount);
existing.setCount(max);
slot.setChanged();
return true;
}
@Override
public void clicked(int i, int j, ClickType clickType, Player player) {
if (viewOnly) {
if (clickType == ClickType.QUICK_CRAFT) {
sendAllDataToRemote();
}
return;
}
super.clicked(i, j, clickType, player);
}
@Override
public boolean stillValid(Player player) {
return true;
}
// Overrides from here on are purely to modify the sync process to send placeholder items.
@Override
protected Slot addSlot(Slot slot) {
slot.index = this.slots.size();
this.slots.add(slot);
this.lastSlots.add(ItemStack.EMPTY);
this.remoteSlots.add(ItemStack.EMPTY);
return slot;
}
@Override
protected DataSlot addDataSlot(DataSlot dataSlot) {
this.dataSlots.add(dataSlot);
this.remoteDataSlots.add(0);
return dataSlot;
}
@Override
protected void addDataSlots(ContainerData containerData) {
for (int i = 0; i < containerData.getCount(); i++) {
this.addDataSlot(DataSlot.forContainer(containerData, i));
}
}
@Override
public void addSlotListener(ContainerListener containerListener) {
if (!this.containerListeners.contains(containerListener)) {
this.containerListeners.add(containerListener);
this.broadcastChanges();
}
}
@Override
public void setSynchronizer(ContainerSynchronizer containerSynchronizer) {
this.synchronizer = containerSynchronizer;
this.sendAllDataToRemote();
}
@Override
public void sendAllDataToRemote() {
for (int index = 0; index < slots.size(); ++index) {
Slot slot = slots.get(index);
this.remoteSlots.set(index, (slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem()).copy());
}
remoteCarried = getCarried().copy();
for (int index = 0; index < this.dataSlots.size(); ++index) {
this.remoteDataSlots.set(index, this.dataSlots.get(index).get());
}
if (this.synchronizer != null) {
this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray());
}
}
@Override
public void broadcastCarriedItem() {
this.remoteCarried = this.getCarried().copy();
if (this.synchronizer != null) {
this.synchronizer.sendCarriedChange(this, this.remoteCarried);
}
}
@Override
public void removeSlotListener(ContainerListener containerListener) {
this.containerListeners.remove(containerListener);
}
@Override
public void broadcastChanges() {
for (int index = 0; index < this.slots.size(); ++index) {
Slot slot = this.slots.get(index);
ItemStack itemstack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem();
Supplier<ItemStack> supplier = Suppliers.memoize(itemstack::copy);
this.triggerSlotListeners(index, itemstack, supplier);
this.synchronizeSlotToRemote(index, itemstack, supplier);
}
this.synchronizeCarriedToRemote();
for (int index = 0; index < this.dataSlots.size(); ++index) {
DataSlot dataSlot = this.dataSlots.get(index);
int j = dataSlot.get();
if (dataSlot.checkAndClearUpdateFlag()) {
this.updateDataSlotListeners(index, j);
}
this.synchronizeDataSlotToRemote(index, j);
}
}
@Override
public void broadcastFullState() {
for (int index = 0; index < this.slots.size(); ++index) {
ItemStack itemstack = this.slots.get(index).getItem();
this.triggerSlotListeners(index, itemstack, itemstack::copy);
}
for (int index = 0; index < this.dataSlots.size(); ++index) {
DataSlot containerproperty = this.dataSlots.get(index);
if (containerproperty.checkAndClearUpdateFlag()) {
this.updateDataSlotListeners(index, containerproperty.get());
}
}
this.sendAllDataToRemote();
}
private void updateDataSlotListeners(int i, int j) {
for (ContainerListener containerListener : this.containerListeners) {
containerListener.dataChanged(this, i, j);
}
}
private void triggerSlotListeners(int index, ItemStack itemStack, Supplier<ItemStack> supplier) {
ItemStack itemStack1 = this.lastSlots.get(index);
if (!ItemStack.matches(itemStack1, itemStack)) {
ItemStack itemStack2 = supplier.get();
this.lastSlots.set(index, itemStack2);
for (ContainerListener containerListener : this.containerListeners) {
containerListener.slotChanged(this, index, itemStack2);
}
}
}
private void synchronizeSlotToRemote(int i, ItemStack itemStack, Supplier<ItemStack> supplier) {
if (!this.suppressRemoteUpdates) {
ItemStack itemStack1 = this.remoteSlots.get(i);
if (!ItemStack.matches(itemStack1, itemStack)) {
ItemStack itemstack2 = supplier.get();
this.remoteSlots.set(i, itemstack2);
if (this.synchronizer != null) {
this.synchronizer.sendSlotChange(this, i, itemstack2);
}
}
}
}
private void synchronizeDataSlotToRemote(int index, int value) {
if (!this.suppressRemoteUpdates) {
int existing = this.remoteDataSlots.getInt(index);
if (existing != value) {
this.remoteDataSlots.set(index, value);
if (this.synchronizer != null) {
this.synchronizer.sendDataChange(this, index, value);
}
}
}
}
private void synchronizeCarriedToRemote() {
if (!this.suppressRemoteUpdates && !ItemStack.matches(this.getCarried(), this.remoteCarried)) {
this.remoteCarried = this.getCarried().copy();
if (this.synchronizer != null) {
this.synchronizer.sendCarriedChange(this, this.remoteCarried);
}
}
}
@Override
public void setRemoteCarried(ItemStack itemstack) {
this.remoteCarried = itemstack.copy();
}
@Override
public void suppressRemoteUpdates() {
this.suppressRemoteUpdates = true;
}
@Override
public void resumeRemoteUpdates() {
this.suppressRemoteUpdates = false;
}
}
@@ -1,53 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.menu;
import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
public OpenEnderChestMenu(
@NotNull OpenEnderChest enderChest,
@NotNull ServerPlayer viewer,
int containerId,
boolean viewOnly) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu
Slot slot = this.slots.get(index);
if (slot.isFake() || !slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack original = itemStack.copy();
if (index < topSize) {
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
return ItemStack.EMPTY;
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return original;
}
}
@@ -1,262 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.menu;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.v1_21_R2.container.bukkit.OpenPlayerInventorySelf;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentDrop;
import com.lishid.openinv.internal.v1_21_R2.container.slot.ContentEquipment;
import com.lishid.openinv.internal.v1_21_R2.container.slot.SlotViewOnly;
import com.lishid.openinv.util.Permissions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView;
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
private int offset;
public OpenInventoryMenu(OpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly);
}
private static MenuType<ChestMenu> getMenuType(OpenInventory inventory, ServerPlayer viewer) {
int size = inventory.getContainerSize();
// Disallow duplicate access to own main inventory contents.
if (inventory.getOwnerHandle().equals(viewer)) {
size -= viewer.getInventory().items.size();
size = ((int) Math.ceil(size / 9.0)) * 9;
}
return getChestMenuType(size);
}
@Override
protected void preSlotSetup() {
offset = ownContainer ? viewer.getInventory().items.size() : 0;
}
@Override
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
index += offset;
Slot slot = container.getMenuSlot(index, x, y);
// If the slot cannot be interacted with there's nothing to configure.
if (slot.getClass().equals(SlotViewOnly.class)) {
return slot;
}
// Remove drop slot if viewer is not allowed to use it.
if (slot instanceof ContentDrop.SlotDrop
&& (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) {
return new SlotViewOnly(container, index, x, y);
}
if (slot instanceof ContentEquipment.SlotEquipment equipment) {
if (viewOnly) {
return SlotViewOnly.wrap(slot);
}
Permissions perm = switch (equipment.getEquipmentSlot()) {
case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY;
case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY;
case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY;
case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY;
// Off-hand can hold anything, not just equipment.
default -> null;
};
// If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot.
if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) {
equipment.onlyEquipmentFor(container.getOwnerHandle());
}
// Equipment slots are a core part of the inventory, so they will always be shown.
return slot;
}
// When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above).
if (ownContainer && !(slot instanceof ContentDrop.SlotDrop)) {
return new SlotViewOnly(container, index, x, y);
}
if (viewOnly) {
return SlotViewOnly.wrap(slot);
}
return slot;
}
@Override
protected @NotNull CraftInventoryView<OpenChestMenu<OpenInventory>, Inventory> createBukkitEntity() {
org.bukkit.inventory.Inventory bukkitInventory;
if (viewOnly) {
bukkitInventory = new OpenDummyInventory(container);
} else if (ownContainer) {
bukkitInventory = new OpenPlayerInventorySelf(container, offset);
} else {
bukkitInventory = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) {
@Override
public org.bukkit.inventory.ItemStack getItem(int index) {
if (viewOnly || index < 0) {
return null;
}
Slot slot = slots.get(index);
return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
}
@Override
public boolean isInTop(int rawSlot) {
return rawSlot < topSize;
}
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
return null;
}
Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
"Slot %s outside of inventory", rawSlot);
if (rawSlot > topSize) {
return getBottomInventory();
}
Slot slot = slots.get(rawSlot);
if (slot.isFake()) {
return null;
}
return getTopInventory();
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
if (rawSlot < 0) {
return rawSlot;
}
if (rawSlot < topSize) {
Slot slot = slots.get(rawSlot);
if (slot.isFake()) {
return InventoryView.OUTSIDE;
}
return rawSlot;
}
int slot = rawSlot - topSize;
if (slot >= 27) {
slot -= 27;
} else {
slot += 9;
}
return slot;
}
@Override
public @NotNull InventoryType.SlotType getSlotType(int slot) {
if (viewOnly || slot < 0) {
return InventoryType.SlotType.OUTSIDE;
}
if (slot >= topSize) {
slot -= topSize;
if (slot >= 27) {
return InventoryType.SlotType.QUICKBAR;
}
return InventoryType.SlotType.CONTAINER;
}
return OpenInventoryMenu.this.container.getSlotType(offset + slot);
}
@Override
public int countSlots() {
return topSize + BOTTOM_INVENTORY_SIZE;
}
};
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu and InventoryMenu
Slot slot = this.slots.get(index);
if (!slot.hasItem() || slot.isFake()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack originalStack = itemStack.copy();
if (index < topSize) {
// If we're moving top to bottom, do a normal transfer.
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else {
EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack);
boolean movedGear = switch (equipmentSlot) {
// If this is gear, try to move it to the correct slot first.
case OFFHAND, FEET, LEGS, CHEST, HEAD -> {
// Locate the correct slot in the contents following the main inventory.
for (int extra = container.getOwnerHandle().getInventory().items.size() - offset; extra < topSize; ++extra) {
Slot extraSlot = getSlot(extra);
if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
// If we've found a matching slot, try to move to it.
// If this succeeds, even partially, we will not attempt to move to other slots.
// Otherwise, armor is already occupied, so we'll fall through to main inventory.
yield this.moveItemStackTo(itemStack, extra, extra + 1, false);
}
}
yield false;
}
// Non-gear gets no special treatment.
default -> false;
};
// If main inventory is not available, there's nowhere else to move.
if (offset != 0) {
if (!movedGear) {
return ItemStack.EMPTY;
}
} else {
// If we didn't move to a gear slot, try to move to a main inventory slot.
if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().items.size(), true)) {
return ItemStack.EMPTY;
}
}
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return originalStack;
}
}
@@ -1,69 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* An interface defining behaviors for entries in a {@link Container}. Used to reduce duplicate content reordering.
*/
public interface Content {
/**
* Update internal holder.
*
* @param holder the new holder
*/
void setHolder(@NotNull ServerPlayer holder);
/**
* Get the current item.
*
* @return the current item
*/
ItemStack get();
/**
* Remove the current item.
*
* @return the current item
*/
ItemStack remove();
/**
* Remove some of the current item.
*
* @return the current item
*/
ItemStack removePartial(int amount);
/**
* Set the current item. If slot is currently not usable, will drop item instead.
*
* @param itemStack the item to set
*/
void set(ItemStack itemStack);
/**
* Get a {@link Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will
* impose any specific restrictions to insertion or removal.
*
* @param container the backing container
* @param slot the slot of the backing container represented
* @param x clientside x dimension from top left of inventory, not used
* @param y clientside y dimension from top left of inventory, not used
* @return a menu slot
*/
Slot asSlot(Container container, int slot, int x, int y);
/**
* Get a loose Bukkit translation of what this slot stores. For example, any slot that drops items at the owner rather
* than insert them will report itself as being {@link org.bukkit.event.inventory.InventoryType.SlotType#OUTSIDE}.
*
* @return the closes Bukkit slot type
*/
org.bukkit.event.inventory.InventoryType.SlotType getSlotType();
}
@@ -1,131 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A slot in a survival crafting inventory. Unavailable when not online in a survival mode.
*/
public class ContentCrafting implements Content {
private final int index;
private ServerPlayer holder;
private List<ItemStack> items;
public ContentCrafting(@NotNull ServerPlayer holder, int index) {
setHolder(holder);
this.index = index;
}
private boolean isAvailable() {
return isAvailable(holder);
}
static boolean isAvailable(@NotNull ServerPlayer holder) {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival();
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
// Note: CraftingContainer#getItems is immutable! Be careful with updates.
this.items = holder.inventoryMenu.getCraftSlots().getContents();
}
@Override
public ItemStack get() {
return isAvailable() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
if (!this.isAvailable()) {
return ItemStack.EMPTY;
}
ItemStack removed = items.remove(index);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
return removed;
}
@Override
public ItemStack removePartial(int amount) {
if (!this.isAvailable()) {
return ItemStack.EMPTY;
}
ItemStack removed = ContainerHelper.removeItem(items, index, amount);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
return removed;
}
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
items.set(index, itemStack);
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
} else {
holder.drop(itemStack, false);
}
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotCrafting(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return isAvailable() ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE;
}
public class SlotCrafting extends SlotPlaceholder {
private SlotCrafting(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return isAvailable() ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder);
}
@Override
public boolean mayPickup(Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return isAvailable();
}
@Override
public boolean hasItem() {
return isAvailable() && super.hasItem();
}
@Override
public boolean isFake() {
return !isAvailable();
}
}
}
@@ -1,48 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot allowing viewing of the crafting result.
*
* <p>Unmodifiable because I said so. Use your own crafting grid.</p>
*/
public class ContentCraftingResult extends ContentViewOnly {
public ContentCraftingResult(@NotNull ServerPlayer holder) {
super(holder);
}
@Override
public ItemStack get() {
InventoryMenu inventoryMenu = holder.inventoryMenu;
return inventoryMenu.getResultSlot().getItem();
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
if (!ContentCrafting.isAvailable(holder)) {
return Placeholders.survivalOnly(holder);
}
InventoryMenu inventoryMenu = holder.inventoryMenu;
return inventoryMenu.getResultSlot().getItem();
}
};
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.RESULT;
}
}
@@ -1,117 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot wrapping the active menu's cursor. Unavailable when not online in a survival mode.
*/
public class ContentCursor implements Content {
private @NotNull ServerPlayer holder;
public ContentCursor(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return isAvailable() ? holder.containerMenu.getCarried() : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
ItemStack carried = holder.containerMenu.getCarried();
holder.containerMenu.setCarried(ItemStack.EMPTY);
return carried;
}
@Override
public ItemStack removePartial(int amount) {
ItemStack carried = holder.containerMenu.getCarried();
if (!carried.isEmpty() && carried.getCount() >= amount) {
ItemStack value = carried.split(amount);
if (carried.isEmpty()) {
holder.containerMenu.setCarried(ItemStack.EMPTY);
}
return value;
}
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
holder.containerMenu.setCarried(itemStack);
} else {
holder.drop(itemStack, false);
}
}
private boolean isAvailable() {
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
// it ignores changes without extra help, and will delete the item as a result.
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
return holder.connection != null && !holder.connection.isDisconnected() && holder.gameMode.isSurvival();
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotCursor(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
// As close as possible to "not real"
return InventoryType.SlotType.OUTSIDE;
}
public class SlotCursor extends SlotPlaceholder {
private SlotCursor(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
if (!isAvailable()) {
return Placeholders.survivalOnly(holder);
}
ItemStack carried = holder.containerMenu.getCarried();
return carried.isEmpty() ? Placeholders.cursor : carried;
}
@Override
public boolean mayPickup(Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return isAvailable();
}
@Override
public boolean hasItem() {
return isAvailable() && super.hasItem();
}
@Override
public boolean isFake() {
return true;
}
}
}
@@ -1,88 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A fake slot used to drop items. Unavailable offline.
*/
public class ContentDrop implements Content {
private ServerPlayer holder;
public ContentDrop(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
return ItemStack.EMPTY;
}
@Override
public ItemStack removePartial(int amount) {
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
holder.drop(itemStack, true);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotDrop(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
// Behaves like dropping an item outside the screen, just by the target player.
return InventoryType.SlotType.OUTSIDE;
}
public class SlotDrop extends SlotPlaceholder {
private SlotDrop(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return holder.connection != null && !holder.connection.isDisconnected()
? Placeholders.drop
: Placeholders.blockedOffline;
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return holder.connection != null && !holder.connection.isDisconnected();
}
@Override
public boolean hasItem() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
}
@@ -1,78 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot for equipment that displays placeholders if empty.
*/
public class ContentEquipment extends ContentList {
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
public ContentEquipment(ServerPlayer holder, int index, EquipmentSlot equipmentSlot) {
super(holder, index, InventoryType.SlotType.ARMOR);
placeholder = switch (equipmentSlot) {
case HEAD -> Placeholders.emptyHelmet;
case CHEST -> Placeholders.emptyChestplate;
case LEGS -> Placeholders.emptyLeggings;
case FEET -> Placeholders.emptyBoots;
default -> Placeholders.emptyOffHand;
};
this.equipmentSlot = equipmentSlot;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.items = holder.getInventory().armor;
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y);
}
public class SlotEquipment extends SlotPlaceholder {
private ServerPlayer viewer;
SlotEquipment(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
ItemStack itemStack = getItem();
if (!itemStack.isEmpty()) {
return itemStack;
}
return placeholder;
}
public EquipmentSlot getEquipmentSlot() {
return equipmentSlot;
}
public void onlyEquipmentFor(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public boolean mayPlace(ItemStack var0) {
if (viewer == null) {
return true;
}
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(var0) == equipmentSlot;
}
}
}
@@ -1,58 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import java.util.List;
/**
* A normal slot backed by an item list.
*/
public abstract class ContentList implements Content {
private final int index;
private final InventoryType.SlotType slotType;
protected List<ItemStack> items;
public ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) {
this.index = index;
this.slotType = slotType;
setHolder(holder);
}
@Override
public ItemStack get() {
return items.get(index);
}
@Override
public ItemStack remove() {
ItemStack removed = items.remove(index);
return removed == null || removed.isEmpty() ? ItemStack.EMPTY : removed;
}
@Override
public ItemStack removePartial(int amount) {
return ContainerHelper.removeItem(items, index, amount);
}
@Override
public void set(ItemStack itemStack) {
items.set(index, itemStack);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new Slot(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return slotType;
}
}
@@ -1,46 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A slot for equipment that updates held items if necessary.
*/
public class ContentOffHand extends ContentEquipment {
private ServerPlayer holder;
public ContentOffHand(ServerPlayer holder, int localIndex) {
super(holder, localIndex, EquipmentSlot.OFFHAND);
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.items = holder.getInventory().offhand;
this.holder = holder;
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.QUICKBAR;
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y) {
@Override
public void setChanged() {
if (holder.connection != null
&& !holder.connection.isDisconnected()
&& holder.containerMenu != holder.inventoryMenu) {
holder.resendItemInHands();
}
}
};
}
}
@@ -1,56 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
/**
* A view-only slot that can't be interacted with.
*/
public class ContentViewOnly implements Content {
@NotNull ServerPlayer holder;
public ContentViewOnly(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(@NotNull ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
return ItemStack.EMPTY;
}
@Override
public ItemStack removePartial(int amount) {
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.OUTSIDE;
}
}
@@ -1,20 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
/**
* An implementation of a slot as used by a menu that may have fake placeholder items.
*
* <p>Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories.</p>
*/
public abstract class SlotPlaceholder extends Slot {
public SlotPlaceholder(Container container, int index, int x, int y) {
super(container, index, x, y);
}
public abstract ItemStack getOrDefault();
}
@@ -1,151 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.container.slot;
import com.lishid.openinv.internal.v1_21_R2.container.Placeholders;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content.
*/
public class SlotViewOnly extends SlotPlaceholder {
public static @NotNull SlotViewOnly wrap(@NotNull Slot wrapped) {
SlotViewOnly wrapper;
if (wrapped instanceof SlotPlaceholder placeholder) {
wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return placeholder.getOrDefault();
}
};
} else {
wrapper = new SlotViewOnly(wrapped.container, wrapped.slot, wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return wrapped.getItem();
}
};
}
wrapper.index = wrapped.index;
return wrapper;
}
public SlotViewOnly(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return Placeholders.notSlot;
}
@Override
public void onQuickCraft(ItemStack var0, ItemStack var1) {
}
@Override
public void onTake(Player var0, ItemStack var1) {
}
@Override
public boolean mayPlace(ItemStack var0) {
return false;
}
@Override
public ItemStack getItem() {
return ItemStack.EMPTY;
}
@Override
public boolean hasItem() {
return false;
}
@Override
public void setByPlayer(ItemStack newStack) {
}
@Override
public void setByPlayer(ItemStack newStack, ItemStack oldStack) {
}
@Override
public void set(ItemStack var0) {
}
@Override
public void setChanged() {
}
@Override
public int getMaxStackSize() {
return 0;
}
@Override
public int getMaxStackSize(ItemStack itemStack) {
return 0;
}
@Override
public ItemStack remove(int amount) {
return ItemStack.EMPTY;
}
@Override
public boolean mayPickup(Player var0) {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public Optional<ItemStack> tryRemove(int var0, int var1, Player var2) {
return Optional.empty();
}
@Override
public ItemStack safeTake(int var0, int var1, Player var2) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeInsert(ItemStack itemStack) {
return itemStack;
}
@Override
public ItemStack safeInsert(ItemStack itemStack, int amount) {
return itemStack;
}
@Override
public boolean allowModification(Player var0) {
return false;
}
@Override
public int getContainerSlot() {
return this.slot;
}
@Override
public boolean isHighlightable() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
@@ -1,178 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.player;
import com.lishid.openinv.event.OpenEvents;
import com.mojang.logging.LogUtils;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.bukkit.craftbukkit.v1_21_R2.CraftServer;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
public class OpenPlayer extends CraftPlayer {
/**
* List of tags to always reset when saving.
*
* @see net.minecraft.world.entity.Entity#saveWithoutId(CompoundTag)
* @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag)
* @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag)
* @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag)
*/
private static final Set<String> RESET_TAGS = Set.of(
// Entity#saveWithoutId(CompoundTag)
"CustomName",
"CustomNameVisible",
"Silent",
"NoGravity",
"Glowing",
"TicksFrozen",
"HasVisualFire",
"Tags",
"Passengers",
// ServerPlayer#addAdditionalSaveData(CompoundTag)
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
"warden_spawn_tracker",
"enteredNetherPosition",
"SpawnX",
"SpawnY",
"SpawnZ",
"SpawnForced",
"SpawnAngle",
"SpawnDimension",
"raid_omen_position",
"ender_pearls",
// Player#addAdditionalSaveData(CompoundTag)
"ShoulderEntityLeft",
"ShoulderEntityRight",
"LastDeathLocation",
"current_explosion_impact_pos",
// LivingEntity#addAdditionalSaveData(CompoundTag)
"active_effects",
"SleepingX",
"SleepingY",
"SleepingZ",
"Brain"
);
private final PlayerManager manager;
OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
super(server, entity);
this.manager = manager;
}
@Override
public void loadData() {
manager.loadData(getHandle());
}
@Override
public void saveData() {
if (OpenEvents.saveCancelled(this)) {
return;
}
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player.getName().getString(), player.getStringUUID()).orElse(null);
CompoundTag playerData = getWritableTag(oldData);
playerData = player.saveWithoutId(playerData);
setExtraData(playerData);
if (oldData != null) {
// Revert certain special data values when offline.
revertSpecialValues(playerData, oldData);
}
Path playerDataDir = worldNBTStorage.getPlayerDir().toPath();
Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat");
NbtIo.writeCompressed(playerData, tempFile);
Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat");
Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(dataFile, tempFile, backupFile);
} catch (Exception e) {
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
@Contract("null -> new")
private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
if (oldData == null) {
return new CompoundTag();
}
// Copy old data. This is a deep clone, so operating on it should be safe.
oldData = oldData.copy();
// Remove vanilla/server data that is not written every time.
oldData.getAllKeys()
.removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit"));
return oldData;
}
private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) {
// Revert automatic updates to play timestamps.
copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class);
copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class);
copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class);
}
private <T extends Tag> void copyValue(
@NotNull CompoundTag source,
@NotNull CompoundTag target,
@NotNull String container,
@NotNull String key,
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
// New container being null means the server implementation doesn't store this data.
if (newContainer == null) {
return;
}
// If old tag exists, copy it to new location, removing otherwise.
setTag(newContainer, key, getTag(oldContainer, key, tagType));
}
private <T extends Tag> @Nullable T getTag(
@Nullable CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
if (container == null) {
return null;
}
Tag value = container.get(key);
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
return null;
}
return dataType.cast(value);
}
private <T extends Tag> void setTag(
@NotNull CompoundTag container,
@NotNull String key,
@Nullable T data) {
if (data == null) {
container.remove(key);
} else {
container.put(key, data);
}
}
}
@@ -1,267 +0,0 @@
package com.lishid.openinv.internal.v1_21_R2.player;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.v1_21_R2.container.OpenEnderChest;
import com.lishid.openinv.internal.v1_21_R2.container.OpenInventory;
import com.mojang.authlib.GameProfile;
import com.mojang.serialization.Dynamic;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ParticleStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R2.CraftServer;
import org.bukkit.craftbukkit.v1_21_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_21_R2.event.CraftEventFactory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.UUID;
import java.util.logging.Logger;
public class PlayerManager implements com.lishid.openinv.internal.PlayerManager {
private static boolean paper;
static {
try {
Class.forName("io.papermc.paper.configuration.Configuration");
paper = true;
} catch (ClassNotFoundException ignored) {
paper = false;
}
}
private final @NotNull Logger logger;
private @Nullable Field bukkitEntity;
public PlayerManager(@NotNull Logger logger) {
this.logger = logger;
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static @NotNull ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer) {
return ((CraftPlayer) player).getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer) {
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}
return nmsPlayer;
}
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
// Create a new ServerPlayer.
ServerPlayer entity = createNewPlayer(server, worldServer, offline);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
entity.getAdvancements().stopListening();
// Try to load the player's data.
if (loadData(entity)) {
// If data is loaded successfully, return the Bukkit entity.
return entity.getBukkitEntity();
}
return null;
}
private @NotNull ServerPlayer createNewPlayer(
@NotNull MinecraftServer server,
@NotNull ServerLevel worldServer,
@NotNull final OfflinePlayer offline) {
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
ClientInformation dummyInfo = new ClientInformation(
"en_us",
1, // Reduce distance just in case.
ChatVisiblity.HIDDEN, // Don't accept chat.
false,
ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION,
ServerPlayer.DEFAULT_MAIN_HAND,
true,
false, // Don't list in player list (not that this player is in the list anyway).
ParticleStatus.MINIMAL
);
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
try {
injectPlayer(entity);
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
}
return entity;
}
boolean loadData(@NotNull ServerPlayer player) {
// See CraftPlayer#loadData
CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player).orElse(null);
if (loadedData == null) {
// Exceptions with loading are logged by Mojang.
return false;
}
// Read basic data into the player.
player.load(loadedData);
// Also read "extra" data.
player.readAdditionalSaveData(loadedData);
// Game type settings are also loaded separately.
player.loadGameTypes(loadedData);
if (paper) {
// Paper: world is not loaded by ServerPlayer#load(CompoundTag).
parseWorld(player, loadedData);
}
return true;
}
private void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
// See PlayerList#placeNewPlayer
World bukkitWorld;
if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) {
// Modern Bukkit world.
bukkitWorld = Bukkit.getServer().getWorld(new UUID(loadedData.getLong("WorldUUIDMost"), loadedData.getLong("WorldUUIDLeast")));
} else if (loadedData.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) {
// Legacy Bukkit world.
bukkitWorld = Bukkit.getServer().getWorld(loadedData.getString("world"));
} else {
// Vanilla player data.
DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension")))
.resultOrPartial(logger::warning)
.map(player.server::getLevel)
// If ServerLevel exists, set, otherwise move to spawn.
.ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null));
return;
}
if (bukkitWorld == null) {
player.spawnIn(null);
return;
}
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
}
private void injectPlayer(ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this));
}
@Override
public @NotNull Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
injectPlayer(nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
logger.log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
return player;
}
}
@Override
public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory, boolean viewOnly) {
ServerPlayer player = getHandle(bukkitPlayer);
if (player.connection == null) {
return null;
}
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
AbstractContainerMenu menu;
Component title;
if (inventory instanceof OpenInventory playerInv) {
menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly);
title = playerInv.getTitle(player);
} else if (inventory instanceof OpenEnderChest enderChest) {
menu = enderChest.createMenu(player, player.nextContainerCounter(), viewOnly);
title = enderChest.getTitle();
} else {
return null;
}
// Should never happen, player is a ServerPlayer with an active connection.
if (menu == null) {
return null;
}
// Set up title. Title can only be set once for a menu, and is set during the open process.
// Further title changes are a hack where the client is sent a "new" inventory with the same ID,
// resulting in a title change but no other state modifications (like cursor position).
menu.setTitle(title);
menu = CraftEventFactory.callInventoryOpenEvent(player, menu, false);
// Menu is null if event is cancelled.
if (menu == null) {
return null;
}
player.containerMenu = menu;
player.connection.send(new ClientboundOpenScreenPacket(menu.containerId, menu.getType(), menu.getTitle()));
player.initMenu(menu);
return menu.getBukkitView();
}
}
-4
View File
@@ -1,4 +0,0 @@
before_install:
- sdk install java 21.0.4-tem
- sdk use java 21.0.4-tem
- mvn wrapper:wrapper -Dmaven=3.9.8
-140
View File
@@ -1,140 +0,0 @@
<!--
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<version>5.1.3</version>
</parent>
<artifactId>openinvplugin</artifactId>
<name>OpenInvPlugin</name>
<dependencies>
<dependency>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
</dependency>
<dependency>
<artifactId>planarwrappers</artifactId>
<groupId>com.github.jikoo</groupId>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<artifactId>openinvcommon</artifactId>
<groupId>com.lishid</groupId>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_21_R2</artifactId>
<version>5.1.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.lishid</groupId>
<artifactId>openinvadapter1_21_R1</artifactId>
<version>5.1.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
</dependency>
</dependencies>
<build>
<finalName>OpenInv</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<filters>
<filter>
<!-- Always shade entirety of required modules. -->
<artifact>com.lishid:openinv*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>*:*</artifact>
<!-- Don't warn about file conflicts that'll be clobbered anyway. -->
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.github.jikoo.planarwrappers</pattern>
<shadedPattern>com.github.jikoo.openinv.planarwrappers</shadedPattern>
</relocation>
</relocations>
<minimizeJar>true</minimizeJar>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-final-jar</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.parent.build.directory}</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}</directory>
<includes>
<include>${project.build.finalName}.jar</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,307 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv;
import com.lishid.openinv.command.ContainerSettingCommand;
import com.lishid.openinv.command.OpenInvCommand;
import com.lishid.openinv.command.SearchContainerCommand;
import com.lishid.openinv.command.SearchEnchantCommand;
import com.lishid.openinv.command.SearchInvCommand;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.listener.ContainerListener;
import com.lishid.openinv.listener.ToggleListener;
import com.lishid.openinv.util.AccessEqualMode;
import com.lishid.openinv.util.InternalAccessor;
import com.lishid.openinv.util.InventoryManager;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.PlayerLoader;
import com.lishid.openinv.util.config.Config;
import com.lishid.openinv.util.config.ConfigUpdater;
import com.lishid.openinv.util.lang.LangMigrator;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.setting.PlayerToggle;
import com.lishid.openinv.util.setting.PlayerToggles;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.Locale;
import java.util.UUID;
import java.util.function.Consumer;
/**
* The main class for OpenInv.
*/
public class OpenInv extends JavaPlugin implements IOpenInv {
private InternalAccessor accessor;
private Config config;
private InventoryManager inventoryManager;
private LanguageManager languageManager;
private PlayerLoader playerLoader;
private boolean isSpigot = false;
@Override
public void reloadConfig() {
super.reloadConfig();
config.reload(getConfig());
languageManager.reload();
if (accessor != null && accessor.isSupported()) {
accessor.reload(getConfig());
}
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!isSpigot || !this.accessor.isSupported()) {
this.sendVersionError(sender::sendMessage);
return true;
}
return false;
}
@Override
public void onDisable() {
inventoryManager.evictAll();
}
@Override
public void onEnable() {
// Save default configuration if not present.
this.saveDefaultConfig();
// Migrate locale files to a subfolder.
Path dataFolder = getDataFolder().toPath();
new LangMigrator(dataFolder, dataFolder.resolve("locale"), getLogger()).migrate();
// Set up configurable features. Note that #reloadConfig is called on the first call to #getConfig!
// Configuration values should not be accessed until after all of these have been set up.
config = new Config();
languageManager = new LanguageManager(this, "en");
accessor = new InternalAccessor(getLogger(), languageManager);
// Perform initial config load.
reloadConfig();
inventoryManager = new InventoryManager(this, config, accessor);
playerLoader = new PlayerLoader(this, config, inventoryManager, accessor, getLogger());
try {
Class.forName("org.bukkit.entity.Player$Spigot");
isSpigot = true;
} catch (ClassNotFoundException e) {
isSpigot = false;
}
// Version check
if (isSpigot && this.accessor.isSupported()) {
reloadConfig();
// Update existing configuration. May require internal access.
new ConfigUpdater(this).checkForUpdates();
// Register relevant event listeners.
registerEvents();
// Register commands to their executors.
registerCommands();
} else {
this.sendVersionError(this.getLogger()::warning);
}
}
private void registerEvents() {
PluginManager pluginManager = this.getServer().getPluginManager();
pluginManager.registerEvents(playerLoader, this);
pluginManager.registerEvents(inventoryManager, this);
pluginManager.registerEvents(new ContainerListener(accessor, languageManager), this);
pluginManager.registerEvents(new ToggleListener(), this);
}
private void registerCommands() {
this.setCommandExecutor(new OpenInvCommand(this, config, inventoryManager, languageManager, playerLoader), "openinv", "openender");
this.setCommandExecutor(new SearchContainerCommand(this, languageManager), "searchcontainer");
this.setCommandExecutor(new SearchInvCommand(languageManager), "searchinv", "searchender");
this.setCommandExecutor(new SearchEnchantCommand(languageManager), "searchenchant");
ContainerSettingCommand settingCommand = new ContainerSettingCommand(languageManager);
for (PlayerToggle toggle : PlayerToggles.get()) {
setCommandExecutor(settingCommand, toggle.getName().toLowerCase(Locale.ENGLISH));
}
}
private void setCommandExecutor(@NotNull CommandExecutor executor, String @NotNull ... commands) {
for (String commandName : commands) {
PluginCommand command = this.getCommand(commandName);
if (command != null) {
command.setExecutor(executor);
}
}
}
private void sendVersionError(@NotNull Consumer<String> messageMethod) {
if (!accessor.isSupported()) {
messageMethod.accept("Your server version (" + accessor.getVersion() + ") is not supported.");
messageMethod.accept("Please download the correct version of OpenInv here: " + accessor.getReleasesLink());
// We check this property late so users can use jars that were remapped by Paper already.
if (Boolean.getBoolean("paper.disable-plugin-rewriting")) {
messageMethod.accept("OpenInv uses Spigot-mapped internals, but you have disabled plugin rewriting in Paper!");
messageMethod.accept("Please set system property 'paper.disable-plugin-rewriting' to false.");
}
}
if (!isSpigot) {
messageMethod.accept("OpenInv requires that you use Spigot or a Spigot fork. Per the 1.14 update thread");
messageMethod.accept("(https://www.spigotmc.org/threads/369724/ \"A Note on CraftBukkit\"), if you are");
messageMethod.accept("encountering an inconsistency with vanilla that prevents you from using Spigot,");
messageMethod.accept("that is considered a Spigot bug and should be reported as such.");
}
}
@Override
public boolean isSupportedVersion() {
return this.accessor != null && this.accessor.isSupported();
}
@Override
public boolean disableSaving() {
return config.isSaveDisabled();
}
@Override
public boolean disableOfflineAccess() {
return config.isOfflineDisabled();
}
@Override
public boolean noArgsOpensSelf() {
return config.doesNoArgsOpenSelf();
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return this.accessor.getAnySilentContainer();
}
@Override
public boolean getAnyContainerStatus(@NotNull final OfflinePlayer offline) {
return PlayerToggles.any().is(offline.getUniqueId());
}
@Override
public void setAnyContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
PlayerToggles.any().set(offline.getUniqueId(), status);
}
@Override
public boolean getSilentContainerStatus(@NotNull final OfflinePlayer offline) {
return PlayerToggles.silent().is(offline.getUniqueId());
}
@Override
public void setSilentContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
PlayerToggles.silent().set(offline.getUniqueId(), status);
}
@Override
public @NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online) {
return inventoryManager.getEnderChest(player);
}
@Override
public @NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online) {
return inventoryManager.getInventory(player);
}
@Override
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
Permissions edit = null;
HumanEntity target = inventory.getPlayer();
boolean ownContainer = player.equals(target);
if (inventory instanceof ISpecialPlayerInventory) {
edit = ownContainer ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER;
} else if (inventory instanceof ISpecialEnderChest) {
edit = ownContainer ? Permissions.ENDERCHEST_EDIT_SELF : Permissions.ENDERCHEST_OPEN_OTHER;
}
boolean viewOnly = edit != null && !edit.hasPermission(player);
if (!ownContainer && !viewOnly) {
for (int level = 4; level > 0; --level) {
String permission = "openinv.access.level." + level;
// If the target doesn't have this access level...
if (!target.hasPermission(permission)) {
// If the viewer does have the access level, all good.
if (player.hasPermission(permission)) {
break;
}
// Otherwise check next access level.
continue;
}
// If the player doesn't have an equal access level or equal access is a denial, deny.
if (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY) {
return null;
}
// Since this is a tie, setting decides view state.
if (config.getAccessEqualMode() == AccessEqualMode.VIEW) {
viewOnly = true;
}
break;
}
}
return this.accessor.openInventory(player, inventory, viewOnly);
}
@Override
public boolean isPlayerLoaded(@NotNull UUID playerUuid) {
return inventoryManager.getLoadedPlayer(playerUuid) != null;
}
@Override
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
return playerLoader.load(offline);
}
@Override
public @Nullable OfflinePlayer matchPlayer(@NotNull String name) {
return playerLoader.match(name);
}
@Override
public void unload(@NotNull final OfflinePlayer offline) {
inventoryManager.unload(offline.getUniqueId());
}
}
@@ -1,107 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.command;
import com.lishid.openinv.event.OpenEvents;
import com.lishid.openinv.util.setting.PlayerToggle;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.setting.PlayerToggles;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
public class ContainerSettingCommand implements TabExecutor {
private final @NotNull LanguageManager lang;
public ContainerSettingCommand(@NotNull LanguageManager lang) {
this.lang = lang;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
lang.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
PlayerToggle toggle = PlayerToggles.get(command.getName());
// Shouldn't be possible.
if (toggle == null) {
JavaPlugin.getProvidingPlugin(getClass()).getLogger().warning("Command /" + command.getName() + " registered with no corresponding toggle!");
return false;
}
UUID playerId = player.getUniqueId();
if (args.length > 0) {
args[0] = args[0].toLowerCase(Locale.ENGLISH);
if (args[0].equals("on")) {
set(toggle, playerId, true);
} else if (args[0].equals("off")) {
set(toggle, playerId, false);
} else if (!args[0].equals("check")) {
// Invalid argument, show usage.
return false;
}
} else {
set(toggle, playerId, !toggle.is(playerId));
}
String onOff = lang.getLocalizedMessage(player, toggle.is(playerId) ? "messages.info.on" : "messages.info.off");
if (onOff == null) {
onOff = String.valueOf(toggle.is(playerId));
}
lang.sendMessage(
sender,
"messages.info.settingState",
new Replacement("%setting%", toggle.getName()),
new Replacement("%state%", onOff));
return true;
}
private void set(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
if (toggle.set(uuid, state)) {
OpenEvents.notifyPlayerToggle(toggle, uuid, state);
}
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.testPermissionSilent(sender) || args.length != 1) {
return Collections.emptyList();
}
return TabCompleter.completeString(args[0], new String[] {"check", "on", "off"});
}
}
@@ -1,249 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.command;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.AccessEqualMode;
import com.lishid.openinv.util.InventoryManager;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.PlayerLoader;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.config.Config;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.StringJoiner;
import java.util.logging.Level;
public class OpenInvCommand implements TabExecutor {
private final @NotNull OpenInv plugin;
private final @NotNull Config config;
private final @NotNull InventoryManager manager;
private final @NotNull LanguageManager lang;
private final @NotNull PlayerLoader playerLoader;
private final HashMap<Player, String> openInvHistory = new HashMap<>();
private final HashMap<Player, String> openEnderHistory = new HashMap<>();
public OpenInvCommand(
@NotNull OpenInv plugin,
@NotNull Config config,
@NotNull InventoryManager manager,
@NotNull LanguageManager lang,
@NotNull PlayerLoader playerLoader) {
this.plugin = plugin;
this.config = config;
this.manager = manager;
this.lang = lang;
this.playerLoader = playerLoader;
}
@Override
public boolean onCommand(@NotNull final CommandSender sender, @NotNull final Command command, @NotNull final String label, @NotNull final String[] args) {
boolean openInv = command.getName().equals("openinv");
if (openInv && args.length > 0 && (args[0].equalsIgnoreCase("help") || args[0].equals("?"))) {
this.showHelp(sender);
return true;
}
if (!(sender instanceof Player player)) {
lang.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
String noArgValue;
if (config.doesNoArgsOpenSelf()) {
noArgValue = player.getUniqueId().toString();
} else {
// History management
noArgValue = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
if (noArgValue == null || noArgValue.isEmpty()) {
noArgValue = player.getUniqueId().toString();
(openInv ? this.openInvHistory : this.openEnderHistory).put(player, noArgValue);
}
}
final String name;
if (args.length < 1) {
name = noArgValue;
} else {
name = args[0];
}
new BukkitRunnable() {
@Override
public void run() {
final OfflinePlayer offlinePlayer = playerLoader.match(name);
if (offlinePlayer == null || (!offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline())) {
lang.sendMessage(player, "messages.error.invalidPlayer");
return;
}
new BukkitRunnable() {
@Override
public void run() {
if (!player.isOnline()) {
return;
}
OpenInvCommand.this.openInventory(player, offlinePlayer, openInv);
}
}.runTask(OpenInvCommand.this.plugin);
}
}.runTaskAsynchronously(this.plugin);
return true;
}
private void showHelp(final CommandSender sender) {
// Get registered commands
for (String commandName : plugin.getDescription().getCommands().keySet()) {
PluginCommand command = plugin.getCommand(commandName);
// Ensure command is successfully registered and sender can use it
if (command == null || !command.testPermissionSilent(sender)) {
continue;
}
// Send usage
sender.sendMessage(command.getUsage().replace("<command>", commandName));
List<String> aliases = command.getAliases();
if (!aliases.isEmpty()) {
// Assemble alias list
StringJoiner aliasJoiner = new StringJoiner(", ", " (aliases: ", ")");
for (String alias : aliases) {
aliasJoiner.add(alias);
}
// Send all aliases
sender.sendMessage(aliasJoiner.toString());
}
}
}
private void openInventory(final Player player, final OfflinePlayer target, boolean openinv) {
Player onlineTarget;
boolean online = target.isOnline();
if (!online) {
if (!config.isOfflineDisabled() && Permissions.ACCESS_OFFLINE.hasPermission(player)) {
// Try loading the player's data
onlineTarget = playerLoader.load(target);
} else {
lang.sendMessage(player, "messages.error.permissionPlayerOffline");
return;
}
} else {
if (Permissions.ACCESS_ONLINE.hasPermission(player)) {
onlineTarget = target.getPlayer();
} else {
lang.sendMessage(player, "messages.error.permissionPlayerOnline");
return;
}
}
if (onlineTarget == null) {
lang.sendMessage(player, "messages.error.invalidPlayer");
return;
}
// Permissions checks
if (onlineTarget.equals(player)) {
// Permission for opening own inventory.
if (!(openinv ? Permissions.INVENTORY_OPEN_SELF : Permissions.ENDERCHEST_OPEN_SELF).hasPermission(player)) {
lang.sendMessage(player, "messages.error.permissionOpenSelf");
return;
}
} else {
// Permission for opening others' inventories.
if (!(openinv ? Permissions.INVENTORY_OPEN_OTHER : Permissions.ENDERCHEST_OPEN_OTHER).hasPermission(player)) {
lang.sendMessage(player, "messages.error.permissionOpenOther");
return;
}
// Protected check
for (int level = 4; level > 0; --level) {
String permission = "openinv.access.level." + level;
if (onlineTarget.hasPermission(permission)
&& (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY)) {
lang.sendMessage(
player,
"messages.error.permissionExempt",
new Replacement("%target%", onlineTarget.getDisplayName()));
return;
}
}
// Crossworld check
if (!Permissions.ACCESS_CROSSWORLD.hasPermission(player)
&& !onlineTarget.getWorld().equals(player.getWorld())) {
lang.sendMessage(
player,
"messages.error.permissionCrossWorld",
new Replacement("%target%", onlineTarget.getDisplayName()));
return;
}
}
if (!config.doesNoArgsOpenSelf()) {
// Record the target
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
}
// Create the inventory
final ISpecialInventory inv;
try {
inv = openinv ? manager.getInventory(onlineTarget) : manager.getEnderChest(onlineTarget);
} catch (Exception e) {
lang.sendMessage(player, "messages.error.commandException");
plugin.getLogger().log(Level.WARNING, "Unable to create ISpecialInventory", e);
return;
}
// Open the inventory
plugin.openInventory(player, inv);
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.testPermissionSilent(sender) || args.length != 1) {
return Collections.emptyList();
}
return TabCompleter.completeOnlinePlayer(sender, args[0]);
}
}
@@ -1,145 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.command;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Command for searching containers in a radius of chunks.
*/
public class SearchContainerCommand implements TabExecutor {
private final @NotNull Plugin plugin;
private final @NotNull LanguageManager lang;
public SearchContainerCommand(@NotNull Plugin plugin, @NotNull LanguageManager lang) {
this.plugin = plugin;
this.lang = lang;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player senderPlayer)) {
lang.sendMessage(sender, "messages.error.consoleUnsupported");
return true;
}
if (args.length < 1) {
// Must supply material
return false;
}
Material material = Material.matchMaterial(args[0]);
if (material == null) {
lang.sendMessage(
sender,
"messages.error.invalidMaterial",
new Replacement("%target%", args[0]));
return false;
}
int radius = 5;
if (args.length > 1) {
try {
radius = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
// Invalid radius supplied
return false;
}
}
// Clamp radius.
int configMax = plugin.getConfig().getInt("settings.command.searchcontainer.max-radius", 10);
radius = Math.max(0, Math.min(radius, configMax));
World world = senderPlayer.getWorld();
Chunk centerChunk = senderPlayer.getLocation().getChunk();
StringBuilder locations = new StringBuilder();
for (int dX = -radius; dX <= radius; ++dX) {
for (int dZ = -radius; dZ <= radius; ++dZ) {
if (!world.loadChunk(centerChunk.getX() + dX, centerChunk.getZ() + dZ, false)) {
continue;
}
Chunk chunk = world.getChunkAt(centerChunk.getX() + dX, centerChunk.getZ() + dZ);
for (BlockState tileEntity : chunk.getTileEntities()) {
if (!(tileEntity instanceof InventoryHolder holder)) {
continue;
}
if (!holder.getInventory().contains(material)) {
continue;
}
locations.append(holder.getInventory().getType().name().toLowerCase(Locale.ENGLISH)).append(" (")
.append(tileEntity.getX()).append(',').append(tileEntity.getY()).append(',')
.append(tileEntity.getZ()).append("), ");
}
}
}
// Matches found, delete trailing comma and space
if (!locations.isEmpty()) {
locations.delete(locations.length() - 2, locations.length());
} else {
lang.sendMessage(
sender,
"messages.info.container.noMatches",
new Replacement("%target%", material.name()));
return true;
}
lang.sendMessage(
sender,
"messages.info.container.matches",
new Replacement("%target%", material.name()),
new Replacement("%detail%", locations.toString()));
return true;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
return Collections.emptyList();
}
String argument = args[args.length - 1];
if (args.length == 1) {
return TabCompleter.completeEnum(argument, Material.class);
} else {
return TabCompleter.completeInteger(argument);
}
}
}
@@ -1,172 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.command;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Command adding the ability to search online players' inventories for enchantments of a specific
* type at or above the level specified.
*
* @author Jikoo
*/
public class SearchEnchantCommand implements TabExecutor {
private final @NotNull LanguageManager lang;
public SearchEnchantCommand(@NotNull LanguageManager lang) {
this.lang = lang;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 0) {
return false;
}
Enchantment enchant = null;
int level = 0;
for (String argument : args) {
try {
level = Integer.parseInt(argument);
continue;
} catch (NumberFormatException ignored) {
// Not a level being specified.
}
argument = argument.toLowerCase(Locale.ENGLISH);
NamespacedKey key = NamespacedKey.fromString(argument);
if (key == null) {
continue;
}
Enchantment localEnchant = Registry.ENCHANTMENT.get(key);
if (localEnchant != null) {
enchant = localEnchant;
}
}
// Arguments not set correctly
if (level == 0 && enchant == null) {
return false;
}
StringBuilder players = new StringBuilder();
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
boolean flagInventory = containsEnchantment(player.getInventory(), enchant, level);
boolean flagEnder = containsEnchantment(player.getEnderChest(), enchant, level);
// No matches, continue
if (!flagInventory && !flagEnder) {
continue;
}
// Matches, append details
players.append(player.getName()).append(" (");
if (flagInventory) {
players.append("inv");
}
if (flagEnder) {
if (flagInventory) {
players.append(',');
}
players.append("ender");
}
players.append("), ");
}
if (!players.isEmpty()) {
// Matches found, delete trailing comma and space
players.delete(players.length() - 2, players.length());
} else {
lang.sendMessage(
sender,
"messages.info.player.noMatches",
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level));
return true;
}
lang.sendMessage(
sender,
"messages.info.player.matches",
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level),
new Replacement("%detail%", players.toString()));
return true;
}
private boolean containsEnchantment(Inventory inventory, @Nullable Enchantment enchant, int minLevel) {
for (ItemStack item : inventory.getContents()) {
if (item == null || item.getType() == Material.AIR) {
continue;
}
if (enchant != null) {
if (item.containsEnchantment(enchant) && item.getEnchantmentLevel(enchant) >= minLevel) {
return true;
}
} else {
if (!item.hasItemMeta()) {
continue;
}
ItemMeta meta = item.getItemMeta();
if (meta == null || !meta.hasEnchants()) {
continue;
}
for (int enchLevel : meta.getEnchants().values()) {
if (enchLevel >= minLevel) {
return true;
}
}
}
}
return false;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.testPermissionSilent(sender) || args.length < 1 || args.length > 2) {
return Collections.emptyList();
}
if (args.length == 1) {
return TabCompleter.completeObject(args[0], enchantment -> enchantment.getKey().toString(), Registry.ENCHANTMENT.stream().toArray(Enchantment[]::new));
} else {
return TabCompleter.completeInteger(args[1]);
}
}
}
@@ -1,123 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.command;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
public class SearchInvCommand implements TabExecutor {
private final @NotNull LanguageManager lang;
public SearchInvCommand(@NotNull LanguageManager lang) {
this.lang = lang;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
Material material = null;
if (args.length >= 1) {
material = Material.matchMaterial(args[0]);
}
if (material == null) {
lang.sendMessage(
sender,
"messages.error.invalidMaterial",
new Replacement("%target%", args.length > 0 ? args[0] : "null"));
return false;
}
int count = 1;
if (args.length >= 2) {
try {
count = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
lang.sendMessage(
sender,
"messages.error.invalidNumber",
new Replacement("%target%", args[1]));
return false;
}
}
StringBuilder players = new StringBuilder();
boolean searchInv = command.getName().equals("searchinv");
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
Inventory inventory = searchInv ? player.getInventory() : player.getEnderChest();
int total = 0;
for (ItemStack itemStack : inventory.getContents()) {
if (itemStack != null && itemStack.getType() == material) {
total += itemStack.getAmount();
if (total >= count) {
players.append(player.getName()).append(", ");
break;
}
}
}
}
// Matches found, delete trailing comma and space
if (!players.isEmpty()) {
players.delete(players.length() - 2, players.length());
} else {
lang.sendMessage(
sender,
"messages.info.player.noMatches",
new Replacement("%target%", material.name()));
return true;
}
lang.sendMessage(
sender,
"messages.info.player.matches",
new Replacement("%target%", material.name()),
new Replacement("%detail%", players.toString()));
return true;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
return Collections.emptyList();
}
String argument = args[args.length - 1];
if (args.length == 1) {
return TabCompleter.completeEnum(argument, Material.class);
} else {
return TabCompleter.completeInteger(argument);
}
}
}
@@ -1,144 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.listener;
import com.google.errorprone.annotations.Keep;
import com.lishid.openinv.internal.ViewOnly;
import com.lishid.openinv.util.InternalAccessor;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.setting.PlayerToggles;
import com.lishid.openinv.util.lang.LanguageManager;
import org.bukkit.GameMode;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event.Result;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryInteractEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* A listener managing AnyContainer, SilentContainer, and more.
*/
public class ContainerListener implements Listener {
private final @NotNull InternalAccessor accessor;
private final @NotNull LanguageManager lang;
public ContainerListener(@NotNull InternalAccessor accessor, @NotNull LanguageManager lang) {
this.accessor = accessor;
this.lang = lang;
}
@Keep
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onPlayerInteract(@NotNull PlayerInteractEvent event) {
// Ignore events from other plugins.
if (!PlayerInteractEvent.class.equals(event.getClass())) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK
|| event.getPlayer().isSneaking()
|| event.useInteractedBlock() == Result.DENY
|| event.getClickedBlock() == null
|| !accessor.getAnySilentContainer().isAnySilentContainer(event.getClickedBlock())) {
return;
}
Player player = event.getPlayer();
UUID playerId = player.getUniqueId();
boolean any = Permissions.CONTAINER_ANY.hasPermission(player) && PlayerToggles.any().is(playerId);
boolean needsAny = accessor.getAnySilentContainer().isAnyContainerNeeded(event.getClickedBlock());
if (!any && needsAny) {
return;
}
boolean silent = Permissions.CONTAINER_SILENT.hasPermission(player) && PlayerToggles.silent().is(playerId);
// If anycontainer or silentcontainer is active
if (any || silent) {
if (accessor.getAnySilentContainer().activateContainer(player, silent, event.getClickedBlock())) {
if (silent && needsAny) {
lang.sendSystemMessage(player, "messages.info.containerBlockedSilent");
} else if (needsAny) {
lang.sendSystemMessage(player, "messages.info.containerBlocked");
} else if (silent) {
lang.sendSystemMessage(player, "messages.info.containerSilent");
}
}
event.setCancelled(true);
}
}
@Keep
@EventHandler
private void onInventoryClose(@NotNull final InventoryCloseEvent event) {
if (!(event.getPlayer() instanceof Player player)) {
return;
}
InventoryHolder holder = event.getInventory().getHolder();
if (PlayerToggles.silent().is(player.getUniqueId())
&& holder != null
&& this.accessor.getAnySilentContainer().isAnySilentContainer(holder)) {
this.accessor.getAnySilentContainer().deactivateContainer(player);
}
}
@Keep
@EventHandler(priority = EventPriority.LOWEST)
private void onInventoryClick(@NotNull final InventoryClickEvent event) {
handleInventoryInteract(event);
}
@Keep
@EventHandler(priority = EventPriority.LOWEST)
private void onInventoryDrag(@NotNull final InventoryDragEvent event) {
handleInventoryInteract(event);
}
private void handleInventoryInteract(@NotNull final InventoryInteractEvent event) {
HumanEntity entity = event.getWhoClicked();
// Un-cancel spectator interactions.
if (entity.getGameMode() == GameMode.SPECTATOR && Permissions.SPECTATE_CLICK.hasPermission(entity)) {
event.setCancelled(false);
}
if (event.isCancelled()) {
return;
}
Inventory inventory = event.getView().getTopInventory();
if (inventory instanceof ViewOnly) {
event.setCancelled(true);
}
}
}
@@ -1,22 +0,0 @@
package com.lishid.openinv.listener;
import com.google.errorprone.annotations.Keep;
import com.lishid.openinv.util.setting.PlayerToggles;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ToggleListener implements Listener {
@Keep
@EventHandler
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
UUID playerId = event.getPlayer().getUniqueId();
PlayerToggles.any().set(playerId, false);
PlayerToggles.silent().set(playerId, false);
}
}
@@ -1,23 +0,0 @@
package com.lishid.openinv.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public enum AccessEqualMode {
DENY, ALLOW, VIEW;
public static @NotNull AccessEqualMode of(@Nullable String value) {
if (value == null) {
return VIEW;
}
return switch (value.toLowerCase(Locale.ENGLISH)) {
case "deny", "false" -> DENY;
case "allow", "true" -> ALLOW;
default -> VIEW;
};
}
}
@@ -1,216 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import com.github.jikoo.planarwrappers.util.version.BukkitVersions;
import com.github.jikoo.planarwrappers.util.version.Version;
import com.lishid.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.PlayerManager;
import com.lishid.openinv.util.lang.LanguageManager;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.logging.Logger;
public class InternalAccessor {
private @Nullable Accessor internal;
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
try {
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 3))) {
internal = new com.lishid.openinv.internal.v1_21_R2.InternalAccessor(logger, lang);
} else if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 1))) {
internal = new com.lishid.openinv.internal.v1_21_R1.InternalAccessor(logger, lang);
}
if (internal != null) {
InventoryAccess.setProvider(internal::get);
}
} catch (NoClassDefFoundError | Exception e) {
internal = null;
InventoryAccess.setProvider(null);
}
}
public String getReleasesLink() {
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 4, 4))) { // Good luck.
return "https://dev.bukkit.org/projects/openinv/files?&sort=datecreated";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 8, 8))) { // 1.8.8
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 13))) { // 1.4.4+ had versioned packages.
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0 (OpenInv-legacy)";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 13))) { // 1.13
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 14))) { // 1.13.1, 1.13.2
return "https://github.com/lishid/OpenInv/releases/tag/4.0.7";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 14))) { // 1.14 to 1.14.1 had no revision bump.
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 14, 1))) { // 1.14.1 to 1.14.2 had no revision bump.
return "https://github.com/lishid/OpenInv/releases/tag/4.0.1";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 15))) { // 1.14.2
return "https://github.com/lishid/OpenInv/releases/tag/4.1.1";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 15, 1))) { // 1.15, 1.15.1
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 16))) { // 1.15.2
return "https://github.com/Jikoo/OpenInv/commit/502f661be39ee85d300851dd571f3da226f12345 (never released)";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 16, 1))) { // 1.16, 1.16.1
return "https://github.com/lishid/OpenInv/releases/tag/4.1.4";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 16, 3))) { // 1.16.2, 1.16.3
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 17))) { // 1.16.4, 1.16.5
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.8";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 18, 1))) { // 1.17, 1.18, 1.18.1
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.10";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 19))) { // 1.18.2
return "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 19))) { // 1.19
return "https://github.com/Jikoo/OpenInv/releases/tag/4.2.0";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 19, 1))) { // 1.19.1
return "https://github.com/Jikoo/OpenInv/releases/tag/4.2.2";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 19, 3))) { // 1.19.2, 1.19.3
return "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
}
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 20))) { // 1.19.4
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 1))) { // 1.20, 1.20.1
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.1";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 3))) { // 1.20.2, 1.20.3
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3";
}
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 5))) { // 1.20.5
return "Unsupported; upgrade to 1.20.6: https://github.com/Jikoo/OpenInv/releases/tag/5.1.2";
}
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21))) { // 1.20.4, 1.20.6, 1.21
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.2";
}
return "https://github.com/Jikoo/OpenInv/releases";
}
/**
* Reload internal features.
*/
public void reload(ConfigurationSection config) {
if (internal != null) {
internal.reload(config);
}
}
/**
* Gets the server implementation version.
*
* @return the version
*/
public @NotNull String getVersion() {
return BukkitVersions.MINECRAFT.toString();
}
/**
* Checks if the server implementation is supported.
*
* @return true if initialized for a supported server version
*/
public boolean isSupported() {
return internal != null;
}
/**
* Get the instance of the IAnySilentContainer implementation for the current server version.
*
* @return the IAnySilentContainer
* @throws IllegalStateException if server version is unsupported
*/
public @NotNull IAnySilentContainer getAnySilentContainer() {
if (internal == null) {
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
}
return internal.getAnySilentContainer();
}
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) {
if (internal == null) {
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
}
return internal.getPlayerManager().openInventory(player, inventory, viewOnly);
}
/**
* Get the instance of the IPlayerDataManager implementation for the current server version.
*
* @return the IPlayerDataManager
* @throws IllegalStateException if server version is unsupported
*/
@NotNull PlayerManager getPlayerDataManager() {
if (internal == null) {
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
}
return internal.getPlayerManager();
}
/**
* Creates an instance of the ISpecialEnderChest implementation for the given Player.
*
* @param player the Player
* @return the ISpecialEnderChest created
*/
ISpecialEnderChest createEnderChest(final Player player) {
if (internal == null) {
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
}
return internal.createEnderChest(player);
}
/**
* Creates an instance of the ISpecialPlayerInventory implementation for the given Player.
*
* @param player the Player
* @return the ISpecialPlayerInventory created
*/
ISpecialPlayerInventory createInventory(final Player player) {
if (internal == null) {
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
}
return internal.createPlayerInventory(player);
}
}
@@ -1,280 +0,0 @@
package com.lishid.openinv.util;
import com.github.jikoo.planarwrappers.util.version.BukkitVersions;
import com.github.jikoo.planarwrappers.util.version.Version;
import com.google.errorprone.annotations.Keep;
import com.lishid.openinv.event.OpenEvents;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.util.config.Config;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Stream;
/**
* A manager for special inventories. Delegates creation and tracks copies in use.
*/
public class InventoryManager implements Listener {
private final Map<UUID, ISpecialPlayerInventory> inventories = new ConcurrentHashMap<>();
private final Map<UUID, ISpecialEnderChest> enderChests = new ConcurrentHashMap<>();
private final Set<UUID> expectedCloses = new HashSet<>();
private final @NotNull Plugin plugin;
private final @NotNull Config config;
private final @NotNull InternalAccessor accessor;
public InventoryManager(@NotNull Plugin plugin, @NotNull Config config, @NotNull InternalAccessor accessor) {
this.plugin = plugin;
this.config = config;
this.accessor = accessor;
}
public void evictAll() {
Stream.concat(inventories.values().stream(), enderChests.values().stream())
.map(inventory -> {
// Rather than iterate twice, evict all viewers during remapping.
for (HumanEntity viewer : List.copyOf(inventory.getBukkitInventory().getViewers())) {
expectedCloses.add(viewer.getUniqueId());
viewer.closeInventory();
}
// If saving is prevented, return a null value for the player to save.
if (config.isSaveDisabled() || OpenEvents.saveCancelled(inventory)) {
return null;
}
if (inventory.getPlayer() instanceof Player player) {
return player;
}
return null;
})
.filter(Objects::nonNull)
.distinct()
.forEach(player -> {
if (!player.isOnline()) {
accessor.getPlayerDataManager().inject(player).saveData();
}
});
inventories.clear();
enderChests.clear();
expectedCloses.clear();
}
public @NotNull ISpecialPlayerInventory getInventory(@NotNull Player player) {
return inventories.computeIfAbsent(player.getUniqueId(), uuid -> accessor.createInventory(player));
}
public @NotNull ISpecialEnderChest getEnderChest(@NotNull Player player) {
return enderChests.computeIfAbsent(player.getUniqueId(), uuid -> accessor.createEnderChest(player));
}
public @Nullable Player getLoadedPlayer(@NotNull UUID uuid) {
ISpecialInventory inUse = inventories.get(uuid);
if (inUse != null) {
return (Player) inUse.getPlayer();
}
inUse = enderChests.get(uuid);
if (inUse != null) {
return (Player) inUse.getPlayer();
}
return null;
}
public void unload(@NotNull UUID uuid) {
inventories.computeIfPresent(uuid, this::remove);
enderChests.computeIfPresent(uuid, this::remove);
}
@Keep
@EventHandler(priority = EventPriority.LOWEST)
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
consumeLoaded(event.getPlayer().getUniqueId(), inventory -> {
inventory.setPlayerOnline(event.getPlayer());
checkViewerAccess(inventory, true);
});
}
@Keep
@EventHandler(priority = EventPriority.MONITOR)
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
consumeLoaded(event.getPlayer().getUniqueId(), inventory -> {
inventory.setPlayerOffline();
checkViewerAccess(inventory, false);
});
}
@Keep
@EventHandler
private void onWorldChanged(@NotNull PlayerChangedWorldEvent event) {
Player player = event.getPlayer();
consumeLoaded(player.getUniqueId(), inventory -> checkViewerAccess(inventory, player.isOnline()));
}
@Keep
@EventHandler
private void onInventoryClose(@NotNull InventoryCloseEvent event) {
ISpecialInventory inventory = InventoryAccess.getInventory(event.getInventory());
// If this is not an ISpecialInventory or the inventory was closed elsewhere internally, don't handle.
if (inventory == null || expectedCloses.remove(event.getPlayer().getUniqueId())) {
return;
}
// Fetch the active ISpecialInventory of this type.
Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
UUID key = inventory.getPlayer().getUniqueId();
ISpecialInventory loaded = map.get(key);
// If there is no loaded inventory, it has already been removed and saved.
if (loaded == null) {
return;
}
// This should only be possible if a plugin is going to extreme lengths to mess with our inventories.
if (loaded != inventory) {
// Immediately remove affected inventory, then dump all viewers. We don't want to risk duplication bugs.
map.remove(key);
remove(key, loaded);
remove(key, inventory);
// The loaded one is "correct" as far as we're concerned, so save that.
save(loaded);
}
// Schedule task to check in use status later this tick. Closing user is still in viewer list.
plugin.getServer().getScheduler().runTask(plugin, () -> {
if (loaded.isInUse()) {
return;
}
// Re-fetch from map to reduce odds of a duplicate save.
ISpecialInventory current = map.remove(key);
if (current != null) {
save(current);
}
});
}
@Keep
@EventHandler(priority = EventPriority.HIGHEST)
private void onInventoryOpen(@NotNull InventoryOpenEvent event) {
ISpecialInventory inventory = InventoryAccess.getInventory(event.getInventory());
if (inventory == null) {
return;
}
Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
UUID key = inventory.getPlayer().getUniqueId();
ISpecialInventory loaded = map.get(key);
if (!inventory.equals(loaded)) {
event.setCancelled(true);
plugin.getLogger().log(
Level.WARNING,
"Prevented a plugin from opening an untracked ISpecialInventory!",
new Throwable("Untracked ISpecialInventory"));
}
}
private <T extends ISpecialInventory> void checkViewerAccess(@NotNull T inventory, boolean online) {
Player owner = (Player) inventory.getPlayer();
Permissions connectedState = online ? Permissions.ACCESS_ONLINE : Permissions.ACCESS_OFFLINE;
boolean alwaysDenied = !online && config.isOfflineDisabled();
// Copy viewers so we don't modify the list we're iterating over when closing inventories.
List<HumanEntity> viewers = new ArrayList<>(inventory.getBukkitInventory().getViewers());
// Legacy: Owner is always a viewer of own inventory.
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21))) {
if (inventory instanceof ISpecialPlayerInventory) {
Inventory active = owner.getOpenInventory().getTopInventory();
if (!active.equals(inventory.getBukkitInventory())) {
viewers.remove(owner);
}
}
}
for (HumanEntity viewer : viewers) {
if (alwaysDenied
|| !connectedState.hasPermission(viewer)
|| (!Objects.equals(owner.getWorld(), viewer.getWorld()) && !Permissions.ACCESS_CROSSWORLD.hasPermission(viewer))) {
expectedCloses.add(viewer.getUniqueId());
viewer.closeInventory();
}
}
}
private void consumeLoaded(@NotNull UUID key, @NotNull Consumer<@NotNull ISpecialInventory> consumer) {
boolean saved = consumeLoaded(inventories, key, false, consumer);
consumeLoaded(enderChests, key, saved, consumer);
}
private <T extends ISpecialInventory> boolean consumeLoaded(
@NotNull Map<UUID, T> map,
@NotNull UUID key,
boolean saved,
@NotNull Consumer<@NotNull ISpecialInventory> consumer) {
T inventory = map.get(key);
if (inventory == null) {
return saved;
}
consumer.accept(inventory);
if (!inventory.isInUse()) {
map.remove(key);
if (!saved) {
save(inventory);
return true;
}
}
return saved;
}
private void save(@NotNull ISpecialInventory inventory) {
if (config.isSaveDisabled()) {
return;
}
Player player = (Player) inventory.getPlayer();
if (!player.isOnline() && !OpenEvents.saveCancelled(inventory)) {
accessor.getPlayerDataManager().inject(player).saveData();
}
}
@Contract("_, _ -> null")
private <T extends ISpecialInventory> @Nullable T remove(@NotNull UUID key, @NotNull T inventory) {
for (HumanEntity viewer : List.copyOf(inventory.getBukkitInventory().getViewers())) {
expectedCloses.add(viewer.getUniqueId());
viewer.closeInventory();
}
return null;
}
}
@@ -1,222 +0,0 @@
package com.lishid.openinv.util;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.errorprone.annotations.Keep;
import com.lishid.openinv.util.config.Config;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.profile.PlayerProfile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A utility for looking up and loading players.
*/
public class PlayerLoader implements Listener {
private final @NotNull Plugin plugin;
private final @NotNull Config config;
private final @NotNull InventoryManager inventoryManager;
private final @NotNull InternalAccessor internalAccessor;
private final @NotNull Logger logger;
private final @NotNull Cache<String, PlayerProfile> lookupCache;
public PlayerLoader(
@NotNull Plugin plugin,
@NotNull Config config,
@NotNull InventoryManager inventoryManager,
@NotNull InternalAccessor internalAccessor,
@NotNull Logger logger) {
this.plugin = plugin;
this.config = config;
this.inventoryManager = inventoryManager;
this.internalAccessor = internalAccessor;
this.logger = logger;
this.lookupCache = CacheBuilder.newBuilder().maximumSize(20).build();
}
/**
* Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for
* the server is not loaded, this will return {@code null}.
*
* @param offline the {@code OfflinePlayer} to load a {@code Player} for
* @return the loaded {@code Player}
* @throws IllegalStateException if the server version is unsupported
*/
public @Nullable Player load(@NotNull OfflinePlayer offline) {
UUID key = offline.getUniqueId();
Player player = offline.getPlayer();
if (player != null) {
return player;
}
player = inventoryManager.getLoadedPlayer(key);
if (player != null) {
return player;
}
if (config.isOfflineDisabled() || !internalAccessor.isSupported()) {
return null;
}
if (Bukkit.isPrimaryThread()) {
return internalAccessor.getPlayerDataManager().loadPlayer(offline);
}
Future<Player> future = Bukkit.getScheduler().callSyncMethod(plugin,
() -> internalAccessor.getPlayerDataManager().loadPlayer(offline));
try {
player = future.get();
} catch (InterruptedException | ExecutionException e) {
logger.log(Level.WARNING, e.getMessage(), e);
return null;
}
return player;
}
public @Nullable OfflinePlayer match(@NotNull String name) {
// Warn if called on the main thread - if we resort to searching offline players, this may take several seconds.
if (Bukkit.getServer().isPrimaryThread()) {
logger.warning("Call to PlayerSearchCache#matchPlayer made on the main thread!");
logger.warning("This can cause the server to hang, potentially severely.");
logger.log(Level.WARNING, "Current stack trace", new Throwable("Current stack trace"));
}
OfflinePlayer player;
try {
UUID uuid = UUID.fromString(name);
player = Bukkit.getOfflinePlayer(uuid);
// Ensure player is an existing player.
if (player.hasPlayedBefore() || player.isOnline()) {
return player;
}
// Return null otherwise.
return null;
} catch (IllegalArgumentException ignored) {
// Not a UUID
}
// Exact online match first.
player = Bukkit.getServer().getPlayerExact(name);
if (player != null) {
return player;
}
// Cached offline match.
PlayerProfile cachedResult = lookupCache.getIfPresent(name);
if (cachedResult != null && cachedResult.getUniqueId() != null) {
player = Bukkit.getOfflinePlayer(cachedResult.getUniqueId());
// Ensure player is an existing player.
if (player.hasPlayedBefore() || player.isOnline()) {
return player;
}
// Return null otherwise.
return null;
}
// Exact offline match second - ensure offline access works when matchable users are online.
player = Bukkit.getServer().getOfflinePlayer(name);
if (player.hasPlayedBefore()) {
lookupCache.put(name, player.getPlayerProfile());
return player;
}
// Inexact online match.
player = Bukkit.getServer().getPlayer(name);
if (player != null) {
return player;
}
// Finally, inexact offline match.
float bestMatch = 0;
for (OfflinePlayer offline : Bukkit.getServer().getOfflinePlayers()) {
if (offline.getName() == null) {
// Loaded by UUID only, name has never been looked up.
continue;
}
float currentMatch = StringMetric.compareJaroWinkler(name, offline.getName());
if (currentMatch == 1.0F) {
return offline;
}
if (currentMatch > bestMatch) {
bestMatch = currentMatch;
player = offline;
}
}
if (player != null) {
// If a match was found, store it.
lookupCache.put(name, player.getPlayerProfile());
return player;
}
// No players have ever joined the server.
return null;
}
@Keep
@EventHandler
private void updateMatches(@NotNull PlayerJoinEvent event) {
// If player is not new, any cached values are valid.
if (event.getPlayer().hasPlayedBefore()) {
return;
}
// New player may have a name that already points to someone else in lookup cache.
String name = event.getPlayer().getName();
lookupCache.invalidate(name);
// If the cache is empty, nothing to do. Don't hit scheduler.
if (lookupCache.size() == 0) {
return;
}
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> {
Iterator<Map.Entry<String, PlayerProfile>> iterator = lookupCache.asMap().entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, PlayerProfile> entry = iterator.next();
String oldMatch = entry.getValue().getName();
// Shouldn't be possible - all profiles should be complete.
if (oldMatch == null) {
iterator.remove();
continue;
}
String lookup = entry.getKey();
float oldMatchScore = StringMetric.compareJaroWinkler(lookup, oldMatch);
float newMatchScore = StringMetric.compareJaroWinkler(lookup, name);
// If new match exceeds old match, delete old match.
if (newMatchScore > oldMatchScore) {
iterator.remove();
}
}
}, 7L);
}
}
Loaded 100 of 255 files, more files were not shown because too many files have changed in this diff. Show more