Compare commits

...
41 Commits
Author SHA1 Message Date
Jikoo 5aa959f1ac Bump version to 4.4.1 for release 2023-11-12 14:29:11 -05:00
Adam bc7e13f245 Expand preserved old data (#166)
* Fix session timestamps being erroneously updated
* Fix vehicle possibly not being re-mounted
2023-11-12 12:34:32 -05:00
Adam 73edebff54 Fix loading world on Paper (#165)
Unify load process to reduce duplicate code and risk of missed cases
2023-11-12 12:24:19 -05:00
dependabot[bot] faf1d387e9 Bump org.apache.maven.plugins:maven-dependency-plugin (#161) 2023-11-01 17:32:32 +00:00
Jikoo deed277376 Bump version to 4.4.1-SNAPSHOT for development 2023-10-04 22:19:02 -04:00
Jikoo 28fc5aea70 Bump version to 4.4.0 for release 2023-10-04 22:18:38 -04:00
Jikoo bfcf0233fa Fix ender chests not being silent 2023-10-04 22:15:35 -04:00
dependabot[bot] 3cf1aae89b Bump actions/checkout from 3 to 4 (#158)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 21:52:28 -04:00
dependabot[bot] db62878681 Bump org.apache.maven.plugins:maven-shade-plugin from 3.5.0 to 3.5.1 (#159) 2023-10-01 17:45:00 +00:00
Jikoo 21cd52c166 Fix probable NPE 2023-09-22 23:46:43 -04:00
dependabot[bot] 9d715b61c0 Bump maven-shade-plugin from 3.4.1 to 3.5.0 (#153)
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.4.1...maven-shade-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-22 23:20:13 -04:00
Jikoo d636cdd839 Add support for 1.20.2 2023-09-22 23:16:22 -04:00
Jikoo 1bebdb5602 Add OpenPlayerSaveEvent
Closes #156
2023-09-22 23:06:05 -04:00
Jikoo 57caf85500 Update Spigot 2023-09-10 11:23:49 -04:00
XPYEX 922586c925 Improve support for non-UTF-8 systems (#151) 2023-06-17 07:35:21 -04:00
Jikoo 9ec7bebecb Bump version to 4.3.2-SNAPSHOT for development 2023-06-10 11:09:43 -04:00
Jikoo 5a7d1399b5 Bump version to 4.3.1 for release 2023-06-10 11:09:30 -04:00
Adam cc7481fa9a Update to Minecraft 1.20 (#148) 2023-06-10 11:06:41 -04:00
dependabot[bot] 70d20aceb6 Bump maven-dependency-plugin from 3.5.0 to 3.6.0 (#146) 2023-06-01 18:11:12 +00:00
dependabot[bot] ac52211ce4 Bump maven-assembly-plugin from 3.5.0 to 3.6.0 (#147) 2023-06-01 18:07:58 +00:00
dependabot[bot] 6f2bdccc89 Bump dsaltares/fetch-gh-release-asset from 1.1.0 to 1.1.1 (#143)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 15:28:40 -04:00
Adam 6f23ed2528 Remove OS declaration from workflow_call (#144) 2023-05-01 15:26:56 -04:00
Adam 2d36249dbb Fix split up actions (#141) 2023-04-25 16:51:21 -04:00
Adam 2f370ad641 Fix loading users in deleted worlds on Paper (#139) 2023-04-17 18:20:31 -04:00
dependabot[bot] 1233f587c5 Bump hmarr/auto-approve-action from 3.1.0 to 3.2.1 (#134)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-01 15:22:44 -04:00
dependabot[bot] 23ebea405e Bump annotations from 24.0.0 to 24.0.1 (#133)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-01 15:22:34 -04:00
Jikoo 1bde75f9b3 Bump version to 4.3.1-SNAPSHOT for development 2023-03-16 16:44:10 -04:00
Jikoo 6ee2546efd Bump version to 4.3.0 for release 2023-03-16 16:43:56 -04:00
Adam 0e6acdff36 Update to 1.19.4 (#130)
* Improve data load/save slightly
* Fix longstanding issue with opening players in deleted worlds on Paper
2023-03-16 16:30:43 -04:00
Adam 3d4bed04d5 Split up actions (#129)
* Refactor conditional dependent jobs into separate actions
* Fix incorrect usage of pull_request_target
* Fix Dependabot not merging due to labels being absent when issue is created
* Prettify supported version listing
2023-03-15 08:13:33 -04:00
dependabot[bot] e885bf9a65 Bump maven-assembly-plugin from 3.4.2 to 3.5.0 (#127)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 18:21:01 -05:00
dependabot[bot] e15c566b82 Bump maven-compiler-plugin from 3.10.1 to 3.11.0 (#128)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 18:20:50 -05:00
dependabot[bot] c1ffd09d50 Bump pascalgn/automerge-action from 0.15.5 to 0.15.6 (#126)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 18:20:31 -05:00
Adam bd5e47760e Fix duplicate closes swapping players to spectate (#125) 2023-02-18 14:10:36 -05:00
dependabot[bot] 2fe0322570 Bump maven-dependency-plugin from 3.4.0 to 3.5.0 (#123)
Bumps [maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/apache/maven-dependency-plugin/releases)
- [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.4.0...maven-dependency-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-dependency-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 09:01:16 -05:00
dependabot[bot] 767476a759 Bump annotations from 23.1.0 to 24.0.0 (#122)
Bumps [annotations](https://github.com/JetBrains/java-annotations) from 23.1.0 to 24.0.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/23.1.0...24.0.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 09:01:06 -05:00
dependabot[bot] dc8e36d11f Bump annotations from 23.0.0 to 23.1.0 (#121)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-01 19:13:14 -05:00
Adam 79b2a97c9e Clamp /searchcontainer radius (#117)
Default max is 10 because that's the default server view distance.
2022-12-20 16:44:49 -05:00
Adam 81eb60f628 Improve language manager (#116)
* Clarify replacements internally to reduce the likelihood of messing up when including new ones
 * Fix missing keys not being written to disk for easy translation
 * Use a guessfile-like system where missing translation keys are inserted into a separate section for easy identification
 * Reduce verbosity of missing keys warning
2022-12-20 15:54:30 -05:00
Adam e39f092f14 Add lookup cache, option to open self with no args (#115)
Add a cache for the last 10 offline lookups done by OpenInv#matchPlayer(String) to reduce the performance hit of repeat opens
Add config option to open self with no-arg command
2022-12-20 11:54:12 -05:00
Jikoo 4816737b90 Bump version to 4.2.3-SNAPSHOT for development 2022-12-11 11:35:36 -05:00
48 changed files with 1534 additions and 871 deletions

No files matched your search

@@ -0,0 +1,55 @@
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@v6
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@v3.2.1
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
pull-request-number: "${{ env.PR_NUMBER }}"
- name: Merge
uses: pascalgn/automerge-action@v0.15.6
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_LABELS: "dependencies,java"
MERGE_METHOD: "squash"
PULL_REQUEST: "${{ env.PR_NUMBER }}"
+7 -53
View File
@@ -2,13 +2,18 @@ name: OpenInv CI
on:
push:
pull_request_target:
branches:
- '**'
tags-ignore:
- '**'
# 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@v3
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
@@ -36,54 +41,3 @@ jobs:
with:
name: api
path: ./api/target/openinvapi*.jar
merge-dependabot:
name: Auto-merge Dependabot PRs
needs: [ build ]
if: "github.event.name == 'pull_request_target'
&& github.actor == 'dependabot[bot]'
&& contains( github.event.pull_request.labels.*.name, 'java')"
runs-on: ubuntu-latest
steps:
- name: Approve
uses: hmarr/auto-approve-action@v3.1.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Merge
uses: pascalgn/automerge-action@v0.15.5
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_LABELS: "dependencies"
MERGE_METHOD: "squash"
release:
name: Create Github Release
needs: [ build ]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
# Fetch all history - used to assemble changelog.
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set Release Variables
run: bash ./scripts/set_release_env.sh
- name: Download Artifact
uses: actions/download-artifact@v3
with:
name: dist
path: dist
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v0.1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ env.VERSIONED_NAME }}
body: ${{ env.GENERATED_CHANGELOG }}
draft: true
prerelease: false
files: ./dist/OpenInv.jar
+39
View File
@@ -0,0 +1,39 @@
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 Artifact
uses: actions/download-artifact@v3
with:
name: dist
path: dist
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v0.1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ env.VERSIONED_NAME }}
body: ${{ env.GENERATED_CHANGELOG }}
draft: true
prerelease: false
files: ./dist/OpenInv.jar
@@ -9,12 +9,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch Github Release Asset
uses: dsaltares/fetch-gh-release-asset@1.1.0
uses: dsaltares/fetch-gh-release-asset@1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ github.event.release.id }}
+22
View File
@@ -0,0 +1,22 @@
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@v3
with:
name: pr_number
path: pr/
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<version>4.2.2</version>
<version>4.4.1</version>
</parent>
<artifactId>openinvapi</artifactId>
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -22,9 +22,7 @@ import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.util.InventoryAccess;
import com.lishid.openinv.util.StringMetric;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@@ -65,6 +63,15 @@ public interface IOpenInv {
*/
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.
*
@@ -154,6 +161,7 @@ public interface IOpenInv {
* @return the identifier
* @throws IllegalStateException if the server version is unsupported
*/
@Deprecated(forRemoval = true)
default @NotNull String getPlayerID(@NotNull OfflinePlayer offline) {
return offline.getUniqueId().toString();
}
@@ -219,74 +227,7 @@ public interface IOpenInv {
* @param name the string to match
* @return the user with the closest matching name
*/
default @Nullable OfflinePlayer matchPlayer(@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()) {
this.getLogger().warning("Call to OpenInv#matchPlayer made on the main thread!");
this.getLogger().warning("This can cause the server to hang, potentially severely.");
this.getLogger().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;
}
// Exact offline match second - ensure offline access works when matchable users are online.
player = Bukkit.getServer().getOfflinePlayer(name);
if (player.hasPlayedBefore()) {
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;
}
}
// Only null if no players have played ever, otherwise even the worst match will do.
return player;
}
@Nullable OfflinePlayer matchPlayer(@NotNull String name);
/**
* @deprecated OpenInv uses action bar chat for notifications. Whether they show is based on language settings.
@@ -0,0 +1,74 @@
package com.lishid.openinv.event;
import com.lishid.openinv.internal.ISpecialInventory;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* Event fired before OpenInv saves a player's data.
*/
public class OpenPlayerSaveEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
private final ISpecialInventory inventory;
private boolean cancelled = false;
public OpenPlayerSaveEvent(@NotNull Player player, @NotNull ISpecialInventory inventory) {
this.player = player;
this.inventory = inventory;
}
/**
* Get the {@link Player} whose data is being saved.
*
* @return player the Player whose data is being saved
*/
public @NotNull Player getPlayer() {
return player;
}
/**
* Get the {@link ISpecialInventory} that triggered the save by being closed.
*
* @return the special inventory
*/
public @NotNull ISpecialInventory getInventory() {
return inventory;
}
/**
* 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,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -16,7 +16,6 @@
package com.lishid.openinv.internal;
import org.bukkit.Material;
import org.bukkit.block.Barrel;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@@ -27,6 +26,7 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Chest;
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;
@@ -148,13 +148,32 @@ public interface IAnySilentContainer {
* @return true if the type is a supported container
*/
default boolean isAnySilentContainer(@NotNull Block block) {
if (block.getType() == Material.ENDER_CHEST) {
return true;
}
BlockState state = block.getState();
return state instanceof org.bukkit.block.Chest
|| state instanceof org.bukkit.block.ShulkerBox
|| state instanceof org.bukkit.block.Barrel;
return isAnySilentContainer(block.getState());
}
/**
* 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 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>com.lishid</groupId>
<artifactId>openinvparent</artifactId>
<version>4.2.2</version>
<version>4.4.1</version>
</parent>
<artifactId>openinvassembly</artifactId>
@@ -1,74 +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.v1_18_R2;
import java.io.File;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.apache.logging.log4j.LogManager;
import org.bukkit.craftbukkit.v1_18_R2.CraftServer;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
readExtraData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Special case: save old vehicle data
CompoundTag oldData = worldNBTStorage.load(player);
if (oldData != null && oldData.contains("RootVehicle", 10)) {
// See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogManager.getLogger().warn("Failed to save player data for {}: {}", player.getName().getString(), e.getMessage());
}
}
}
@@ -1,74 +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.v1_19_R1;
import java.io.File;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.apache.logging.log4j.LogManager;
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
readExtraData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Special case: save old vehicle data
CompoundTag oldData = worldNBTStorage.load(player);
if (oldData != null && oldData.contains("RootVehicle", 10)) {
// See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogManager.getLogger().warn("Failed to save player data for {}: {}", player.getName().getString(), e.getMessage());
}
}
}
@@ -1,74 +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.v1_19_R2;
import java.io.File;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.storage.PlayerDataStorage;
import org.apache.logging.log4j.LogManager;
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
readExtraData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Special case: save old vehicle data
CompoundTag oldData = worldNBTStorage.load(player);
if (oldData != null && oldData.contains("RootVehicle", 10)) {
// See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound)
playerData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogManager.getLogger().warn("Failed to save player data for {}: {}", player.getName().getString(), e.getMessage());
}
}
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~ 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
@@ -23,16 +23,16 @@
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>4.2.2</version>
<version>4.4.1</version>
</parent>
<artifactId>openinvadapter1_19_R1</artifactId>
<name>OpenInvAdapter1_19_R1</name>
<artifactId>openinvadapter1_19_R3</artifactId>
<name>OpenInvAdapter1_19_R3</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spigot.version>1.19.1-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.19.4-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,13 +14,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R1;
package com.lishid.openinv.internal.v1_19_R3;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.util.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.core.BlockPos;
@@ -28,7 +27,6 @@ import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.CompoundContainer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.monster.Shulker;
@@ -39,23 +37,21 @@ 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.DoubleBlockCombiner;
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.ChestBlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.ShulkerBox;
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -66,8 +62,6 @@ public class AnySilentContainer implements IAnySilentContainer {
public AnySilentContainer() {
try {
try {
// IDE warns about field not existing, but SpecialSource does not remap strings used in reflection.
// The warning is not suppressed as a reminder that it must manually be checked on updates.
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
@@ -162,33 +156,13 @@ public class AnySilentContainer implements IAnySilentContainer {
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, true).apply(
// Combiner is a copy of private ChestBlock.MENU_PROVIDER_COMBINER
new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
@Override
public Optional<MenuProvider> acceptDouble(ChestBlockEntity localChest1, ChestBlockEntity localChest2) {
CompoundContainer doubleChest = new CompoundContainer(localChest1, localChest2);
return Optional.of(new ChestBlock.DoubleInventory(localChest1, localChest2, doubleChest));
}
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
@Override
public Optional<MenuProvider> acceptSingle(ChestBlockEntity localChest) {
return Optional.of(localChest);
}
@Override
public Optional<MenuProvider> acceptNone() {
return Optional.empty();
}
});
if (menuOptional.isEmpty()) {
if (menuProvider == null) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
menuProvider = menuOptional.get();
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
@@ -231,21 +205,10 @@ public class AnySilentContainer implements IAnySilentContainer {
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
case BARREL:
break;
default:
return;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
@@ -0,0 +1,143 @@
/*
* 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_19_R3;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtUtils;
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.apache.logging.log4j.LogManager;
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
getHandle().readAdditionalSaveData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Preserve certain data when offline.
CompoundTag oldData = worldNBTStorage.load(player);
revertSpecialValues(playerData, oldData);
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogManager.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
private void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) {
if (oldData == null) {
return;
}
// Prevent vehicle deletion.
if (oldData.contains("RootVehicle", Tag.TAG_COMPOUND)) {
// See net.minecraft.server.players.PlayerList#save(ServerPlayer)
// See net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag)
try {
Tag attach = oldData.get("Attach");
if (attach != null) {
newData.putUUID("Attach", NbtUtils.loadUUID(attach));
}
} catch (IllegalArgumentException ignored) {
// Likely will not re-mount successfully, but at least the mount will not be deleted.
}
newData.put("Entity", oldData.getCompound("Entity"));
newData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
// 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,
@NotNull Class<T> tagType) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
// Container being null means the server implementation doesn't store this data.
if (oldContainer == null || 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(
@NotNull CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
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,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R2;
package com.lishid.openinv.internal.v1_19_R3;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IPlayerDataManager;
@@ -23,6 +23,7 @@ import com.lishid.openinv.internal.OpenInventoryView;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Field;
import java.util.logging.Logger;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
@@ -32,13 +33,15 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftContainer;
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_19_R3.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftContainer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
@@ -100,20 +103,48 @@ public class PlayerDataManager implements IPlayerDataManager {
ServerPlayer entity = new ServerPlayer(server, worldServer, profile);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
entity.getAdvancements().stopListening();
try {
injectPlayer(entity);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
// Load data. This also reads basic data into the player.
// See CraftPlayer#loadData
CompoundTag loadedData = server.getPlayerList().playerIo.load(entity);
if (loadedData == null) {
// Exceptions with loading are logged by Mojang.
return null;
}
// Return the entity
return target;
// Also read "extra" data.
entity.readAdditionalSaveData(loadedData);
if (entity.level == null) {
// Paper: Move player to spawn
// SPIGOT-7340: Cannot call ServerPlayer#spawnIn with a null world
ServerLevel level = null;
Vec3 position = null;
if (entity.getRespawnDimension() != null) {
level = entity.server.getLevel(entity.getRespawnDimension());
if (level != null && entity.getRespawnPosition() != null) {
position = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(level, entity.getRespawnPosition(), entity.getRespawnAngle(), false, false).orElse(null);
}
}
if (level == null || position == null) {
level = ((CraftWorld) server.server.getWorlds().get(0)).getHandle();
position = Vec3.atCenterOf(level.getSharedSpawnPos());
}
entity.level = level;
entity.setPos(position);
}
// Return the Bukkit entity.
return entity.getBukkitEntity();
}
void injectPlayer(ServerPlayer player) throws IllegalAccessException {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_18_R2;
package com.lishid.openinv.internal.v1_19_R3;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
@@ -33,8 +33,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_18_R2;
package com.lishid.openinv.internal.v1_19_R3;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
@@ -33,6 +33,7 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
@@ -46,8 +47,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
@@ -719,7 +720,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
for (int index : armorIndices) {
ItemStack itemstack = this.armor.get(index);
if ((!damagesource.isFire() || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
if ((!damagesource.is(DamageTypeTags.IS_FIRE) || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
itemstack.hurtAndBreak((int) damage, this.player, localPlayer -> localPlayer.broadcastBreakEvent(EquipmentSlot.byTypeAndIndex(EquipmentSlot.Type.ARMOR, index)));
}
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~ 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
@@ -23,16 +23,16 @@
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>4.2.2</version>
<version>4.4.1</version>
</parent>
<artifactId>openinvadapter1_18_R2</artifactId>
<name>OpenInvAdapter1_18_R2</name>
<artifactId>openinvadapter1_20_R1</artifactId>
<name>OpenInvAdapter1_20_R1</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spigot.version>1.18.2-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.20.1-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,21 +14,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_18_R2;
package com.lishid.openinv.internal.v1_20_R1;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.util.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.CompoundContainer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.monster.Shulker;
@@ -39,23 +36,21 @@ 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.DoubleBlockCombiner;
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.ChestBlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.ShulkerBox;
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -66,8 +61,6 @@ public class AnySilentContainer implements IAnySilentContainer {
public AnySilentContainer() {
try {
try {
// IDE warns about field not existing, but SpecialSource does not remap strings used in reflection.
// The warning is not suppressed as a reminder that it must manually be checked on updates.
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
@@ -81,7 +74,7 @@ public class AnySilentContainer implements IAnySilentContainer {
} catch (SecurityException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(Level.WARNING, "Error obtaining GameType field", e);
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
}
}
@@ -94,7 +87,7 @@ public class AnySilentContainer implements IAnySilentContainer {
if (!(bukkitWorld instanceof CraftWorld craftWorld)) {
Exception exception = new IllegalStateException("AnySilentContainer access attempted on an unknown world!");
OpenInv.getPlugin(OpenInv.class).getLogger().log(Level.WARNING, exception.getMessage(), exception);
OpenInv.getPlugin(OpenInv.class).getLogger().log(java.util.logging.Level.WARNING, exception.getMessage(), exception);
return false;
}
@@ -131,7 +124,7 @@ public class AnySilentContainer implements IAnySilentContainer {
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
final ServerLevel level = player.getLevel();
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);
@@ -147,7 +140,7 @@ public class AnySilentContainer implements IAnySilentContainer {
MenuType<?> containers = PlayerDataManager.getContainers(enderChest.getContainerSize());
int rows = enderChest.getContainerSize() / 9;
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
}, new TranslatableComponent("container.enderchest")));
}, Component.translatable(("container.enderchest"))));
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
return true;
}
@@ -162,33 +155,13 @@ public class AnySilentContainer implements IAnySilentContainer {
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, true).apply(
// Combiner is a copy of private ChestBlock.MENU_PROVIDER_COMBINER
new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
@Override
public Optional<MenuProvider> acceptDouble(ChestBlockEntity localChest1, ChestBlockEntity localChest2) {
CompoundContainer doubleChest = new CompoundContainer(localChest1, localChest2);
return Optional.of(new ChestBlock.DoubleInventory(localChest1, localChest2, doubleChest));
}
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
@Override
public Optional<MenuProvider> acceptSingle(ChestBlockEntity localChest) {
return Optional.of(localChest);
}
@Override
public Optional<MenuProvider> acceptNone() {
return Optional.empty();
}
});
if (menuOptional.isEmpty()) {
if (menuProvider == null) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
menuProvider = menuOptional.get();
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
@@ -231,21 +204,10 @@ public class AnySilentContainer implements IAnySilentContainer {
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
case BARREL:
break;
default:
return;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
@@ -0,0 +1,143 @@
/*
* 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_20_R1;
import com.mojang.logging.LogUtils;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtUtils;
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_20_R1.CraftServer;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
getHandle().readAdditionalSaveData(loaded);
}
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Preserve certain data when offline.
CompoundTag oldData = worldNBTStorage.load(player);
revertSpecialValues(playerData, oldData);
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
private void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) {
if (oldData == null) {
return;
}
// Prevent vehicle deletion.
if (oldData.contains("RootVehicle", Tag.TAG_COMPOUND)) {
// See net.minecraft.server.players.PlayerList#save(ServerPlayer)
// See net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag)
try {
Tag attach = oldData.get("Attach");
if (attach != null) {
newData.putUUID("Attach", NbtUtils.loadUUID(attach));
}
} catch (IllegalArgumentException ignored) {
// Likely will not re-mount successfully, but at least the mount will not be deleted.
}
newData.put("Entity", oldData.getCompound("Entity"));
newData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
// 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,
@NotNull Class<T> tagType) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
// Container being null means the server implementation doesn't store this data.
if (oldContainer == null || 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(
@NotNull CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
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,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R1;
package com.lishid.openinv.internal.v1_20_R1;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IPlayerDataManager;
@@ -23,6 +23,7 @@ import com.lishid.openinv.internal.OpenInventoryView;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Field;
import java.util.logging.Logger;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
@@ -35,10 +36,10 @@ import net.minecraft.world.level.Level;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_19_R1.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftContainer;
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_20_R1.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftContainer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
@@ -98,7 +99,7 @@ public class PlayerDataManager implements IPlayerDataManager {
return null;
}
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, null);
ServerPlayer entity = new ServerPlayer(server, worldServer, profile);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
entity.getAdvancements().stopListening();
@@ -109,14 +110,25 @@ public class PlayerDataManager implements IPlayerDataManager {
e.printStackTrace();
}
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
// Load data. This also reads basic data into the player.
// See CraftPlayer#loadData
CompoundTag loadedData = server.getPlayerList().playerIo.load(entity);
if (loadedData == null) {
// Exceptions with loading are logged by Mojang.
return null;
}
// Return the entity
return target;
// Also read "extra" data.
entity.readAdditionalSaveData(loadedData);
if (entity.level() == null) {
// Paper: Move player to spawn
entity.spawnIn(null);
}
// Return the Bukkit entity.
return entity.getBukkitEntity();
}
void injectPlayer(ServerPlayer player) throws IllegalAccessException {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R1;
package com.lishid.openinv.internal.v1_20_R1;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
@@ -33,8 +33,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R1;
package com.lishid.openinv.internal.v1_20_R1;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
@@ -33,6 +33,7 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
@@ -46,8 +47,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
@@ -473,7 +474,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
if (!compartment.get(i).isEmpty()) {
compartment.get(i).inventoryTick(this.player.level, this.player, i, this.selected == i);
compartment.get(i).inventoryTick(this.player.level(), this.player, i, this.selected == i);
}
}
}
@@ -719,7 +720,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
for (int index : armorIndices) {
ItemStack itemstack = this.armor.get(index);
if ((!damagesource.isFire() || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
if ((!damagesource.is(DamageTypeTags.IS_FIRE) || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
itemstack.hurtAndBreak((int) damage, this.player, localPlayer -> localPlayer.broadcastBreakEvent(EquipmentSlot.byTypeAndIndex(EquipmentSlot.Type.ARMOR, index)));
}
}
@@ -756,7 +757,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
@Override
public boolean contains(ItemStack itemstack) {
return contains(itemStack -> itemStack.isEmpty() && itemStack.sameItem(itemstack));
return contains(itemStack -> itemStack.isEmpty() && itemStack.is(itemstack.getItem()));
}
@Override
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~ 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
@@ -23,16 +23,16 @@
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<relativePath>../../pom.xml</relativePath>
<version>4.2.2</version>
<version>4.4.1</version>
</parent>
<artifactId>openinvadapter1_19_R2</artifactId>
<name>OpenInvAdapter1_19_R2</name>
<artifactId>openinvadapter1_20_R2</artifactId>
<name>OpenInvAdapter1_20_R2</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spigot.version>1.19.3-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.20.2-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,21 +14,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R2;
package com.lishid.openinv.internal.v1_20_R2;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.util.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.CompoundContainer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.monster.Shulker;
@@ -39,23 +36,21 @@ 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.DoubleBlockCombiner;
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.ChestBlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.ShulkerBox;
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -65,19 +60,21 @@ public class AnySilentContainer implements IAnySilentContainer {
public AnySilentContainer() {
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
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);
try {
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("b");
this.serverPlayerGameModeGameType.setAccessible(true);
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
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 logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
logger.log(Level.WARNING, "Error obtaining GameType field", e);
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
}
}
@@ -90,7 +87,7 @@ public class AnySilentContainer implements IAnySilentContainer {
if (!(bukkitWorld instanceof CraftWorld craftWorld)) {
Exception exception = new IllegalStateException("AnySilentContainer access attempted on an unknown world!");
OpenInv.getPlugin(OpenInv.class).getLogger().log(Level.WARNING, exception.getMessage(), exception);
OpenInv.getPlugin(OpenInv.class).getLogger().log(java.util.logging.Level.WARNING, exception.getMessage(), exception);
return false;
}
@@ -127,7 +124,7 @@ public class AnySilentContainer implements IAnySilentContainer {
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
final ServerLevel level = player.getLevel();
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);
@@ -158,33 +155,13 @@ public class AnySilentContainer implements IAnySilentContainer {
if (block instanceof ChestBlock chestBlock) {
// boolean flag: do not check if chest is blocked
Optional<MenuProvider> menuOptional = chestBlock.combine(blockState, level, blockPos, true).apply(
// Combiner is a copy of private ChestBlock.MENU_PROVIDER_COMBINER
new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
@Override
public Optional<MenuProvider> acceptDouble(ChestBlockEntity localChest1, ChestBlockEntity localChest2) {
CompoundContainer doubleChest = new CompoundContainer(localChest1, localChest2);
return Optional.of(new ChestBlock.DoubleInventory(localChest1, localChest2, doubleChest));
}
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
@Override
public Optional<MenuProvider> acceptSingle(ChestBlockEntity localChest) {
return Optional.of(localChest);
}
@Override
public Optional<MenuProvider> acceptNone() {
return Optional.empty();
}
});
if (menuOptional.isEmpty()) {
if (menuProvider == null) {
OpenInv.getPlugin(OpenInv.class).sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
return false;
}
menuProvider = menuOptional.get();
if (block instanceof TrappedChestBlock) {
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
} else {
@@ -227,21 +204,10 @@ public class AnySilentContainer implements IAnySilentContainer {
@Override
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
if (this.serverPlayerGameModeGameType == null) {
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
return;
}
InventoryView view = bukkitPlayer.getOpenInventory();
switch (view.getType()) {
case CHEST:
case ENDER_CHEST:
case SHULKER_BOX:
case BARREL:
break;
default:
return;
}
ServerPlayer player = PlayerDataManager.getHandle(bukkitPlayer);
// Force game mode change without informing plugins or players.
@@ -0,0 +1,139 @@
/*
* 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_20_R2;
import com.mojang.logging.LogUtils;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtUtils;
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_20_R2.CraftServer;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class OpenPlayer extends CraftPlayer {
public OpenPlayer(CraftServer server, ServerPlayer entity) {
super(server, entity);
}
@Override
public void loadData() {
PlayerDataManager.loadData(getHandle());
}
@Override
public void saveData() {
ServerPlayer player = this.getHandle();
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
try {
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
CompoundTag playerData = player.saveWithoutId(new CompoundTag());
setExtraData(playerData);
if (!isOnline()) {
// Preserve certain data when offline.
CompoundTag oldData = worldNBTStorage.load(player);
revertSpecialValues(playerData, oldData);
}
File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir());
NbtIo.writeCompressed(playerData, file);
File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat");
File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old");
Util.safeReplaceFile(file1, file, file2);
} catch (Exception e) {
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
private void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) {
if (oldData == null) {
return;
}
// Prevent vehicle deletion.
if (oldData.contains("RootVehicle", Tag.TAG_COMPOUND)) {
// See net.minecraft.server.players.PlayerList#save(ServerPlayer)
// See net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag)
try {
Tag attach = oldData.get("Attach");
if (attach != null) {
newData.putUUID("Attach", NbtUtils.loadUUID(attach));
}
} catch (IllegalArgumentException ignored) {
// Likely will not re-mount successfully, but at least the mount will not be deleted.
}
newData.put("Entity", oldData.getCompound("Entity"));
newData.put("RootVehicle", oldData.getCompound("RootVehicle"));
}
// 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,
@NotNull Class<T> tagType) {
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
// Container being null means the server implementation doesn't store this data.
if (oldContainer == null || 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(
@NotNull CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
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,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,46 +14,68 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_18_R2;
package com.lishid.openinv.internal.v1_20_R2;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.IPlayerDataManager;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.OpenInventoryView;
import com.mojang.authlib.GameProfile;
import java.lang.reflect.Field;
import java.util.logging.Logger;
import net.minecraft.network.chat.TextComponent;
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.inventory.MenuType;
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.craftbukkit.v1_18_R2.CraftServer;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_18_R2.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftContainer;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftContainer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.bukkit.plugin.java.JavaPlugin;
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 PlayerDataManager implements IPlayerDataManager {
private static boolean paper;
static {
try {
Class.forName("io.papermc.paper.configuration.Configuration");
paper = true;
} catch (ClassNotFoundException ignored) {
paper = false;
}
}
private @Nullable Field bukkitEntity;
public PlayerDataManager() {
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
Logger logger = OpenInv.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
Logger logger = JavaPlugin.getPlugin(OpenInv.class).getLogger();
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;
}
@@ -73,7 +95,7 @@ public class PlayerDataManager implements IPlayerDataManager {
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 provided Player implementation");
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}
return nmsPlayer;
@@ -86,10 +108,6 @@ public class PlayerDataManager implements IPlayerDataManager {
return null;
}
// Create a profile and entity to load the player data
// See net.minecraft.server.PlayerList#attemptLogin
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
@@ -97,28 +115,103 @@ public class PlayerDataManager implements IPlayerDataManager {
return null;
}
ServerPlayer entity = new ServerPlayer(server, worldServer, profile);
// 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) {
e.printStackTrace();
JavaPlugin.getPlugin(OpenInv.class).getLogger().log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
}
// Get the bukkit entity
Player target = entity.getBukkitEntity();
if (target != null) {
// Load data
target.loadData();
}
// Return the entity
return target;
return entity;
}
void injectPlayer(ServerPlayer player) throws IllegalAccessException {
static boolean loadData(@NotNull ServerPlayer player) {
// See CraftPlayer#loadData
CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player);
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);
if (paper) {
// Paper: world is not loaded by ServerPlayer#load(CompoundTag).
parseWorld(player, loadedData);
}
return true;
}
private static void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
// See PlayerList#placeNewPlayer
World bukkitWorld;
if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) {
// Modern Bukkit world.
bukkitWorld = org.bukkit.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 = org.bukkit.Bukkit.getServer().getWorld(loadedData.getString("world"));
} else {
// Vanilla player data.
DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension")))
.resultOrPartial(JavaPlugin.getPlugin(OpenInv.class).getLogger()::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;
}
@@ -128,9 +221,8 @@ public class PlayerDataManager implements IPlayerDataManager {
bukkitEntity.set(player, new OpenPlayer(player.server.server, player));
}
@NotNull
@Override
public Player inject(@NotNull Player player) {
public @NotNull Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
@@ -139,14 +231,16 @@ public class PlayerDataManager implements IPlayerDataManager {
injectPlayer(nmsPlayer);
return nmsPlayer.getBukkitEntity();
} catch (IllegalAccessException e) {
e.printStackTrace();
JavaPlugin.getPlugin(OpenInv.class).getLogger().log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
return player;
}
}
@Nullable
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
ServerPlayer nmsPlayer = getHandle(player);
@@ -167,7 +261,7 @@ public class PlayerDataManager implements IPlayerDataManager {
}
};
container.setTitle(new TextComponent(view.getTitle()));
container.setTitle(Component.literal(view.getTitle()));
container = CraftEventFactory.callInventoryOpenEvent(nmsPlayer, container);
if (container == null) {
@@ -175,7 +269,7 @@ public class PlayerDataManager implements IPlayerDataManager {
}
nmsPlayer.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(),
new TextComponent(container.getBukkitView().getTitle())));
Component.literal(container.getBukkitView().getTitle())));
nmsPlayer.containerMenu = container;
nmsPlayer.initMenu(container);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R2;
package com.lishid.openinv.internal.v1_20_R2;
import com.lishid.openinv.internal.ISpecialEnderChest;
import java.util.List;
@@ -33,8 +33,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
@@ -67,18 +67,29 @@ public class SpecialEnderChest extends PlayerEnderChestContainer implements ISpe
@Override
public void setPlayerOnline(@NotNull final org.bukkit.entity.Player player) {
if (!this.playerOnline) {
try {
this.owner = PlayerDataManager.getHandle(player);
PlayerEnderChestContainer enderChest = owner.getEnderChestInventory();
for (int i = 0; i < enderChest.getContainerSize(); ++i) {
enderChest.setItem(i, this.items.get(i));
}
this.items = enderChest.items;
enderChest.transaction.addAll(this.transaction);
} catch (Exception ignored) {}
this.playerOnline = true;
if (this.playerOnline) {
return;
}
ServerPlayer offlinePlayer = this.owner;
ServerPlayer onlinePlayer = PlayerDataManager.getHandle(player);
// Set owner to new player.
this.owner = onlinePlayer;
// Set player's ender chest contents to our modified contents.
PlayerEnderChestContainer onlineEnderChest = onlinePlayer.getEnderChestInventory();
for (int i = 0; i < onlineEnderChest.getContainerSize(); ++i) {
onlineEnderChest.setItem(i, this.items.get(i));
}
// Set our item array to the new inventory's array.
this.items = onlineEnderChest.items;
// Add viewers to new inventory.
onlineEnderChest.transaction.addAll(offlinePlayer.getEnderChestInventory().transaction);
this.playerOnline = true;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.v1_19_R2;
package com.lishid.openinv.internal.v1_20_R2;
import com.google.common.collect.ImmutableList;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
@@ -33,6 +33,7 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
@@ -46,8 +47,8 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftInventory;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
@@ -77,20 +78,37 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
@Override
public void setPlayerOnline(@NotNull org.bukkit.entity.Player player) {
if (!this.playerOnline) {
Player entityPlayer = PlayerDataManager.getHandle(player);
entityPlayer.getInventory().transaction.addAll(this.transaction);
this.player = entityPlayer;
for (int i = 0; i < getContainerSize(); ++i) {
this.player.getInventory().setItem(i, getRawItem(i));
}
this.player.getInventory().selected = this.selected;
this.items = this.player.getInventory().items;
this.armor = this.player.getInventory().armor;
this.offhand = this.player.getInventory().offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand);
this.playerOnline = true;
if (this.playerOnline) {
return;
}
Player offlinePlayer = this.player;
Player onlinePlayer = PlayerDataManager.getHandle(player);
onlinePlayer.getInventory().transaction.addAll(this.transaction);
// Set owner to new player.
this.player = onlinePlayer;
// Set player's inventory contents to our modified contents.
Inventory onlineInventory = onlinePlayer.getInventory();
for (int i = 0; i < getContainerSize(); ++i) {
onlineInventory.setItem(i, getRawItem(i));
}
onlineInventory.selected = this.selected;
// Set our item arrays to the new inventory's arrays.
this.items = onlineInventory.items;
this.armor = onlineInventory.armor;
this.offhand = onlineInventory.offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand);
// Add existing viewers to new viewer list.
Inventory offlineInventory = offlinePlayer.getInventory();
// Remove self from listing - player is always a viewer of their own inventory, prevent duplicates.
offlineInventory.transaction.remove(offlinePlayer.getBukkitEntity());
onlineInventory.transaction.addAll(offlineInventory.transaction);
this.playerOnline = true;
}
@Override
@@ -456,7 +474,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
for (NonNullList<ItemStack> compartment : this.compartments) {
for (int i = 0; i < compartment.size(); ++i) {
if (!compartment.get(i).isEmpty()) {
compartment.get(i).inventoryTick(this.player.level, this.player, i, this.selected == i);
compartment.get(i).inventoryTick(this.player.level(), this.player, i, this.selected == i);
}
}
}
@@ -702,7 +720,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
for (int index : armorIndices) {
ItemStack itemstack = this.armor.get(index);
if ((!damagesource.isFire() || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
if ((!damagesource.is(DamageTypeTags.IS_FIRE) || !itemstack.getItem().isFireResistant()) && itemstack.getItem() instanceof ArmorItem) {
itemstack.hurtAndBreak((int) damage, this.player, localPlayer -> localPlayer.broadcastBreakEvent(EquipmentSlot.byTypeAndIndex(EquipmentSlot.Type.ARMOR, index)));
}
}
@@ -739,7 +757,7 @@ public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerI
@Override
public boolean contains(ItemStack itemstack) {
return contains(itemStack -> itemStack.isEmpty() && itemStack.sameItem(itemstack));
return contains(itemStack -> itemStack.isEmpty() && itemStack.is(itemstack.getItem()));
}
@Override
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>openinvparent</artifactId>
<groupId>com.lishid</groupId>
<version>4.2.2</version>
<version>4.4.1</version>
</parent>
<artifactId>openinvplugincore</artifactId>
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -64,6 +64,8 @@ class InternalAccessor {
case "v1_8_R3", "v1_15_R1", "v1_16_R2" -> "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
case "v1_16_R3" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.1.8";
case "v1_17_R1", "v1_18_R1" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.1.10";
case "v1_19_R1" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.2.2";
case "v1_18_R2", "v1_19_R2" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
default -> "https://github.com/Jikoo/OpenInv/releases";
};
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
* 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
@@ -35,6 +35,7 @@ import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -53,17 +54,20 @@ record InventoryListener(OpenInv plugin) implements Listener {
return;
}
if (this.plugin.getSilentContainerStatus(player)) {
InventoryHolder holder = event.getInventory().getHolder();
if (this.plugin.getSilentContainerStatus(player)
&& holder != null
&& this.plugin.getAnySilentContainer().isAnySilentContainer(holder)) {
this.plugin.getAnySilentContainer().deactivateContainer(player);
}
ISpecialInventory specialInventory = InventoryAccess.getEnderChest(event.getInventory());
if (specialInventory != null) {
this.plugin.handleCloseInventory(event.getPlayer(), specialInventory);
this.plugin.handleCloseInventory(specialInventory);
} else {
specialInventory = InventoryAccess.getPlayerInventory(event.getInventory());
if (specialInventory != null) {
this.plugin.handleCloseInventory(event.getPlayer(), specialInventory);
this.plugin.handleCloseInventory(specialInventory);
}
}
}
@@ -16,19 +16,25 @@
package com.lishid.openinv;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.lishid.openinv.commands.ContainerSettingCommand;
import com.lishid.openinv.commands.OpenInvCommand;
import com.lishid.openinv.commands.SearchContainerCommand;
import com.lishid.openinv.commands.SearchEnchantCommand;
import com.lishid.openinv.commands.SearchInvCommand;
import com.lishid.openinv.event.OpenPlayerSaveEvent;
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.util.ConfigUpdater;
import com.lishid.openinv.util.LanguageManager;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.StringMetric;
import com.lishid.openinv.util.lang.LanguageManager;
import com.lishid.openinv.util.lang.Replacement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -37,6 +43,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Stream;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
@@ -52,6 +59,7 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.profile.PlayerProfile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -62,6 +70,7 @@ import org.jetbrains.annotations.Nullable;
*/
public class OpenInv extends JavaPlugin implements IOpenInv {
private final Cache<String, PlayerProfile> offlineLookUpCache = CacheBuilder.newBuilder().maximumSize(10).build();
private final Map<UUID, ISpecialPlayerInventory> inventories = new ConcurrentHashMap<>();
private final Map<UUID, ISpecialEnderChest> enderChests = new ConcurrentHashMap<>();
@@ -190,6 +199,11 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
return this.getConfig().getBoolean("settings.disable-offline-access", false);
}
@Override
public boolean noArgsOpensSelf() {
return this.getConfig().getBoolean("settings.command.open.no-args-opens-self", false);
}
@Override
public @NotNull IAnySilentContainer getAnySilentContainer() {
return this.accessor.getAnySilentContainer();
@@ -311,6 +325,95 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
return player;
}
@Override
public @Nullable OfflinePlayer matchPlayer(@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()) {
this.getLogger().warning("Call to OpenInv#matchPlayer made on the main thread!");
this.getLogger().warning("This can cause the server to hang, potentially severely.");
this.getLogger().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 = offlineLookUpCache.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()) {
offlineLookUpCache.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.
offlineLookUpCache.put(name, player.getPlayerProfile());
return player;
}
// No players have ever joined the server.
return null;
}
@Override
public void unload(@NotNull final OfflinePlayer offline) {
setPlayerOffline(offline, OfflineHandler.REMOVE_AND_CLOSE);
@@ -362,7 +465,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
public @Nullable String getLocalizedMessage(
@NotNull CommandSender sender,
@NotNull String key,
String @NotNull ... replacements) {
Replacement @NotNull ... replacements) {
return this.languageManager.getValue(key, getLocale(sender), replacements);
}
@@ -382,7 +485,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
}
}
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, String @NotNull... replacements) {
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
if (message != null && !message.isEmpty()) {
@@ -442,7 +545,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
}
}
void handleCloseInventory(@NotNull HumanEntity exViewer, @NotNull ISpecialInventory inventory) {
void handleCloseInventory(@NotNull ISpecialInventory inventory) {
Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
UUID key = inventory.getPlayer().getUniqueId();
@Nullable ISpecialInventory loaded = map.get(key);
@@ -474,11 +577,18 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
// Re-fetch from map - prevents duplicate saves on multi-close.
ISpecialInventory current = map.remove(key);
if (!disableSaving()
&& current != null
&& current.getPlayer() instanceof Player player
&& !player.isOnline()) {
this.accessor.getPlayerDataManager().inject(player).saveData();
if (disableSaving()
|| current == null
|| !(current.getPlayer() instanceof Player player)
|| player.isOnline()) {
return;
}
OpenPlayerSaveEvent event = new OpenPlayerSaveEvent(player, current);
getServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
this.accessor.getPlayerDataManager().inject(player).saveData();
}
});
}
@@ -492,6 +602,45 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
void setPlayerOnline(@NotNull Player player) {
setPlayerOnline(inventories, player, player::updateInventory);
setPlayerOnline(enderChests, player, null);
if (player.hasPlayedBefore()) {
return;
}
// New player may have a name that already points to someone else in lookup cache.
String name = player.getName();
this.offlineLookUpCache.invalidate(name);
// If no offline matches are mapped, don't hit scheduler.
if (this.offlineLookUpCache.size() == 0) {
return;
}
// New player may also be a more exact match than one already in the cache.
// I.e. new player "lava1" is a better match for "lava" than "lava123"
// Player joins are already quite intensive, so this is run on a delay.
this.getServer().getScheduler().runTaskLaterAsynchronously(this, () -> {
Iterator<Map.Entry<String, PlayerProfile>> iterator = this.offlineLookUpCache.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();
}
}
}, 101L); // Odd delay for pseudo load balancing; Player tasks are usually scheduled with full seconds.
}
private void setPlayerOnline(
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.Replacement;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
@@ -69,7 +70,11 @@ public class ContainerSettingCommand implements TabExecutor {
onOff = String.valueOf(getSetting.test(player));
}
plugin.sendMessage(sender, "messages.info.settingState","%setting%", any ? "AnyContainer" : "SilentContainer", "%state%", onOff);
plugin.sendMessage(
sender,
"messages.info.settingState",
new Replacement("%setting%", any ? "AnyContainer" : "SilentContainer"),
new Replacement("%state%", onOff));
return true;
}
@@ -20,6 +20,7 @@ import com.lishid.openinv.OpenInv;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.Permissions;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.Replacement;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -57,19 +58,23 @@ public class OpenInvCommand implements TabExecutor {
return true;
}
// History management
String history = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
String noArgValue;
if (plugin.noArgsOpensSelf()) {
noArgValue = player.getUniqueId().toString();
} else {
// History management
noArgValue = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
if (history == null || history.isEmpty()) {
history = player.getName();
(openInv ? this.openInvHistory : this.openEnderHistory).put(player, history);
if (noArgValue == null || noArgValue.isEmpty()) {
noArgValue = player.getUniqueId().toString();
(openInv ? this.openInvHistory : this.openEnderHistory).put(player, noArgValue);
}
}
final String name;
// Read from history if target is not named
if (args.length < 1) {
name = history;
name = noArgValue;
} else {
name = args[0];
}
@@ -171,22 +176,28 @@ public class OpenInvCommand implements TabExecutor {
// Protected check
if (!Permissions.OVERRIDE.hasPermission(player)
&& Permissions.EXEMPT.hasPermission(onlineTarget)) {
plugin.sendMessage(player, "messages.error.permissionExempt",
"%target%", onlineTarget.getDisplayName());
plugin.sendMessage(
player,
"messages.error.permissionExempt",
new Replacement("%target%", onlineTarget.getDisplayName()));
return;
}
// Crossworld check
if (!Permissions.CROSSWORLD.hasPermission(player)
&& !onlineTarget.getWorld().equals(player.getWorld())) {
plugin.sendMessage(player, "messages.error.permissionCrossWorld",
"%target%", onlineTarget.getDisplayName());
plugin.sendMessage(
player,
"messages.error.permissionCrossWorld",
new Replacement("%target%", onlineTarget.getDisplayName()));
return;
}
}
// Record the target
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
if (!plugin.noArgsOpensSelf()) {
// Record the target
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
}
// Create the inventory
final ISpecialInventory inv;
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.Replacement;
import java.util.Collections;
import java.util.List;
import org.bukkit.Chunk;
@@ -57,7 +58,10 @@ public class SearchContainerCommand implements TabExecutor {
Material material = Material.getMaterial(args[0].toUpperCase());
if (material == null) {
plugin.sendMessage(sender, "messages.error.invalidMaterial", "%target%", args[0]);
plugin.sendMessage(
sender,
"messages.error.invalidMaterial",
new Replacement("%target%", args[0]));
return false;
}
@@ -72,6 +76,10 @@ public class SearchContainerCommand implements TabExecutor {
}
}
// 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();
@@ -100,13 +108,18 @@ public class SearchContainerCommand implements TabExecutor {
if (locations.length() > 0) {
locations.delete(locations.length() - 2, locations.length());
} else {
plugin.sendMessage(sender, "messages.info.container.noMatches",
"%target%", material.name());
plugin.sendMessage(
sender,
"messages.info.container.noMatches",
new Replacement("%target%", material.name()));
return true;
}
plugin.sendMessage(sender, "messages.info.container.matches",
"%target%", material.name(), "%detail%", locations.toString());
plugin.sendMessage(
sender,
"messages.info.container.matches",
new Replacement("%target%", material.name()),
new Replacement("%detail%", locations.toString()));
return true;
}
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.Replacement;
import java.util.Collections;
import java.util.List;
import org.bukkit.Material;
@@ -114,14 +115,18 @@ public class SearchEnchantCommand implements TabExecutor {
// Matches found, delete trailing comma and space
players.delete(players.length() - 2, players.length());
} else {
plugin.sendMessage(sender, "messages.info.player.noMatches",
"%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level);
plugin.sendMessage(
sender,
"messages.info.player.noMatches",
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level));
return true;
}
plugin.sendMessage(sender, "messages.info.player.matches",
"%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level,
"%detail%", players.toString());
plugin.sendMessage(
sender,
"messages.info.player.matches",
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level),
new Replacement("%detail%", players.toString()));
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2021 lishid. All rights reserved.
* 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
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.TabCompleter;
import com.lishid.openinv.util.lang.Replacement;
import java.util.Collections;
import java.util.List;
import org.bukkit.Material;
@@ -46,7 +47,10 @@ public class SearchInvCommand implements TabExecutor {
}
if (material == null) {
plugin.sendMessage(sender, "messages.error.invalidMaterial", "%target%", args.length > 0 ? args[0] : "null");
plugin.sendMessage(
sender,
"messages.error.invalidMaterial",
new Replacement("%target%", args.length > 0 ? args[0] : "null"));
return false;
}
@@ -56,7 +60,10 @@ public class SearchInvCommand implements TabExecutor {
try {
count = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
plugin.sendMessage(sender, "messages.error.invalidNumber", "%target%", args[1]);
plugin.sendMessage(
sender,
"messages.error.invalidNumber",
new Replacement("%target%", args[1]));
return false;
}
}
@@ -74,13 +81,18 @@ public class SearchInvCommand implements TabExecutor {
if (players.length() > 0) {
players.delete(players.length() - 2, players.length());
} else {
plugin.sendMessage(sender, "messages.info.player.noMatches",
"%target%", material.name());
plugin.sendMessage(
sender,
"messages.info.player.noMatches",
new Replacement("%target%", material.name()));
return true;
}
plugin.sendMessage(sender, "messages.info.player.matches",
"%target%", material.name(), "%detail%", players.toString());
plugin.sendMessage(
sender,
"messages.info.player.matches",
new Replacement("%target%", material.name()),
new Replacement("%detail%", players.toString()));
return true;
}
@@ -17,6 +17,7 @@
package com.lishid.openinv.internal;
import com.lishid.openinv.OpenInv;
import com.lishid.openinv.util.lang.Replacement;
import java.util.Objects;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
@@ -73,8 +74,7 @@ public class OpenInventoryView extends InventoryView {
.getLocalizedMessage(
player,
titleKey,
"%player%",
owner.getName());
new Replacement("%player%", owner.getName()));
title = Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + titleDefaultSuffix);
}
@@ -57,6 +57,9 @@ public record ConfigUpdater(OpenInv plugin) {
if (version < 5) {
updateConfig4To5();
}
if (version < 6) {
updateConfig5To6();
}
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.saveConfig();
@@ -65,6 +68,14 @@ public record ConfigUpdater(OpenInv plugin) {
});
}
private void updateConfig5To6() {
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getConfig().set("settings.command.open.no-args-opens-self", false);
plugin.getConfig().set("settings.command.searchcontainer.max-radius", 10);
plugin.getConfig().set("config-version", 6);
});
}
private void updateConfig4To5() {
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getConfig().set("settings.disable-offline-access", false);
@@ -1,168 +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.lishid.openinv.OpenInv;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A simple language manager supporting both custom and bundled languages.
*
* @author Jikoo
*/
public class LanguageManager {
private final OpenInv plugin;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(@NotNull OpenInv plugin, @NotNull String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
getOrLoadLocale(defaultLocale);
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
InputStream resourceStream = plugin.getResource("locale/" + locale + ".yml");
YamlConfiguration localeConfigDefaults;
if (resourceStream == null) {
localeConfigDefaults = new YamlConfiguration();
} else {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream))) {
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to load resource " + locale + ".yml", e);
localeConfigDefaults = new YamlConfiguration();
}
}
File file = new File(plugin.getDataFolder(), locale + ".yml");
YamlConfiguration localeConfig;
if (!file.exists()) {
localeConfig = localeConfigDefaults;
try {
localeConfigDefaults.save(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to save resource " + locale + ".yml", e);
}
} else {
localeConfig = YamlConfiguration.loadConfiguration(file);
// Add new language keys
List<String> newKeys = new ArrayList<>();
for (String key : localeConfigDefaults.getKeys(true)) {
if (localeConfigDefaults.isConfigurationSection(key)) {
continue;
}
if (localeConfig.isSet(key)) {
continue;
}
localeConfig.set(key, localeConfigDefaults.get(key));
newKeys.add(key);
}
if (!newKeys.isEmpty()) {
plugin.getLogger().info("[LanguageManager] Added new language keys: " + String.join(", ", newKeys));
try {
localeConfig.save(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to save resource " + locale + ".yml", e);
}
}
}
if (!locale.equals(defaultLocale)) {
localeConfigDefaults = locales.get(defaultLocale);
// Check for missing keys
List<String> newKeys = new ArrayList<>();
for (String key : localeConfigDefaults.getKeys(true)) {
if (localeConfigDefaults.isConfigurationSection(key)) {
continue;
}
if (localeConfig.isSet(key)) {
continue;
}
newKeys.add(key);
}
if (!newKeys.isEmpty()) {
plugin.getLogger().info("[LanguageManager] Missing translations from " + locale + ".yml: " + String.join(", ", newKeys));
}
// Fall through to default locale
localeConfig.setDefaults(localeConfigDefaults);
}
locales.put(locale, localeConfig);
return localeConfig;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase()).getString(key);
if (value == null || value.isEmpty()) {
return null;
}
value = ChatColor.translateAlternateColorCodes('&', value);
return value;
}
public @Nullable String getValue(@NotNull String key, @Nullable String locale, String @NotNull ... replacements) {
if (replacements.length % 2 != 0) {
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Replacement data is uneven", new Exception());
}
String value = getValue(key, locale);
if (value == null) {
return null;
}
for (int i = 0; i < replacements.length; i += 2) {
value = value.replace(replacements[i], replacements[i + 1]);
}
return value;
}
}
@@ -0,0 +1,218 @@
/*
* 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 com.lishid.openinv.OpenInv;
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.Map;
import java.util.function.Predicate;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A simple language manager supporting both custom and bundled languages.
*
* @author Jikoo
*/
public class LanguageManager {
private final OpenInv plugin;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(@NotNull OpenInv plugin, @NotNull String defaultLocale) {
this.plugin = plugin;
this.defaultLocale = defaultLocale;
this.locales = new HashMap<>();
getOrLoadLocale(defaultLocale);
}
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
File file = new File(plugin.getDataFolder(), locale + ".yml");
// Load locale config from disk and bundled locale defaults.
YamlConfiguration localeConfig = loadLocale(locale, file);
// If the locale is not the default locale, also handle any missing translations from the default locale.
if (!locale.equals(defaultLocale)) {
addTranslationFallthrough(locale, localeConfig, file);
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 " + locale + ".yml! Check the guess section!");
}
}
locales.put(locale, localeConfig);
return localeConfig;
}
private @NotNull YamlConfiguration loadLocale(
@NotNull String locale,
@NotNull File file) {
// Load defaults from the plugin's bundled resources.
InputStream resourceStream = plugin.getResource("locale/" + locale + ".yml");
YamlConfiguration localeConfigDefaults;
if (resourceStream == null) {
localeConfigDefaults = new YamlConfiguration();
} else {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream, StandardCharsets.UTF_8))) {
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + locale + ".yml");
localeConfigDefaults = new YamlConfiguration();
}
}
if (!file.exists()) {
// If the file does not exist on disk, save bundled defaults.
try {
localeConfigDefaults.save(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + locale + ".yml");
}
// Return loaded bundled locale.
return localeConfigDefaults;
}
// If the file does exist on disk, load it.
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(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 " + locale + ".yml: " + String.join(", ", newKeys));
// Write new keys to disk.
try {
localeConfig.save(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + locale + ".yml");
}
return localeConfig;
}
private void addTranslationFallthrough(
@NotNull String locale,
@NotNull YamlConfiguration localeConfig,
@NotNull File file) {
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(file);
} catch (IOException e) {
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + 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()).getString(key);
if (value == null || value.isEmpty()) {
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;
}
}
@@ -0,0 +1,29 @@
/*
* 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) {
}
+6 -1
View File
@@ -1,5 +1,10 @@
config-version: 5
config-version: 6
settings:
command:
open:
no-args-opens-self: false
searchcontainer:
max-radius: 10
disable-offline-access: false
disable-saving: false
locale: 'en_us'
+14 -13
View File
@@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2011-2022 lishid. All rights reserved.
~ 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
@@ -21,7 +21,7 @@
<artifactId>openinvparent</artifactId>
<name>OpenInv</name>
<url>http://dev.bukkit.org/bukkit-plugins/openinv/</url>
<version>4.2.2</version>
<version>4.4.1</version>
<packaging>pom</packaging>
@@ -29,6 +29,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>
<!-- Silence IDE warning - property is declared in individual internal modules. -->
<spigot.version>unknown</spigot.version>
</properties>
@@ -39,9 +40,9 @@
<modules>
<module>api</module>
<module>plugin</module>
<module>internal/v1_18_R2</module>
<module>internal/v1_19_R1</module>
<module>internal/v1_19_R2</module>
<module>internal/v1_19_R3</module>
<module>internal/v1_20_R1</module>
<module>internal/v1_20_R2</module>
<module>assembly</module>
</modules>
</profile>
@@ -72,25 +73,25 @@
<artifactId>annotations</artifactId>
<groupId>org.jetbrains</groupId>
<scope>provided</scope>
<version>23.0.0</version>
<version>24.0.1</version>
</dependency>
<dependency>
<artifactId>spigot-api</artifactId>
<groupId>org.spigotmc</groupId>
<scope>provided</scope>
<version>1.17.1-R0.1-SNAPSHOT</version>
<version>1.18.2-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<artifactId>openinvapi</artifactId>
<groupId>com.lishid</groupId>
<scope>compile</scope>
<version>4.2.2</version>
<version>4.4.1</version>
</dependency>
<dependency>
<artifactId>openinvplugincore</artifactId>
<groupId>com.lishid</groupId>
<scope>compile</scope>
<version>4.2.2</version>
<version>4.4.1</version>
<exclusions>
<exclusion>
<groupId>com.lishid</groupId>
@@ -111,7 +112,7 @@
-->
<artifactId>maven-dependency-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.4.0</version>
<version>3.6.1</version>
</plugin>
<plugin>
@@ -144,19 +145,19 @@
</execution>
</executions>
<groupId>org.apache.maven.plugins</groupId>
<version>3.4.1</version>
<version>3.5.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.10.1</version>
<version>3.11.0</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.4.2</version>
<version>3.6.0</version>
</plugin>
<plugin>
+2 -2
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#
# Copyright (C) 2011-2021 lishid. All rights reserved.
# 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
@@ -36,7 +36,7 @@ function get_minecraft_versions() {
for version in "${versions[@]}"; do
# Append comma if variable is set, then append version
minecraft_versions="${minecraft_versions:+${minecraft_versions},}${version%%-R*}"
minecraft_versions="${minecraft_versions:+${minecraft_versions}, }${version%%-R*}"
done
echo "${minecraft_versions}"