mirror of
https://github.com/Jikoo/OpenInv.git
synced 2026-11-04 16:50:04 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6d36cc9cf | ||
|
|
54f078724f | ||
|
|
847f22f63f | ||
|
|
f121c113f0 | ||
|
|
8d60cd0726 | ||
|
|
7b504971b1 | ||
|
|
c1a20a4e24 | ||
|
|
5d75640cee | ||
|
|
cace6f6726 | ||
|
|
1967f4ebb1 | ||
|
|
fbdda9637e | ||
|
|
336aee88e2 | ||
|
|
d50b7433c7 | ||
|
|
e64261669b | ||
|
|
e8ab490d95 | ||
|
|
5eaf6243fb | ||
|
|
5e2cf56d6f | ||
|
|
0f0bde3e49 | ||
|
|
4198b7f22a | ||
|
|
8c15b3d078 | ||
|
|
335d849525 | ||
|
|
0fb9e5eed3 | ||
|
|
fe11217a27 | ||
|
|
5fea7f3e02 | ||
|
|
dbceff086a | ||
|
|
64899e5c3d | ||
|
|
bca080be7c | ||
|
|
09842fd168 |
No files matched your search
@@ -36,7 +36,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: ./target/OpenInv.jar
|
||||
path: ./target/*.jar
|
||||
- name: Upload API Jar
|
||||
id: upload-api
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
id: create-release
|
||||
uses: softprops/action-gh-release@v2.0.6
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvtogglepersist</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>annotations</artifactId>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>openinvapi</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>OITogglePersist</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-final-jar</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.parent.build.directory}</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<includes>
|
||||
<include>${project.build.finalName}.jar</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package com.github.jikoo.openinv.togglepersist;
|
||||
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.event.PlayerToggledEvent;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class TogglePersist extends JavaPlugin implements Listener {
|
||||
|
||||
private final Map<UUID, Set<String>> enabledToggles = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
|
||||
File file = new File(getDataFolder(), "toggles.yml");
|
||||
|
||||
// If there's no save file, there's nothing to load.
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Configuration loaded = YamlConfiguration.loadConfiguration(file);
|
||||
|
||||
// For each toggle, enable loaded players.
|
||||
for (String toggleName : loaded.getKeys(false)) {
|
||||
PlayerToggle toggle = PlayerToggles.get(toggleName);
|
||||
// Ensure toggle exists.
|
||||
if (toggle == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String idString : loaded.getStringList(toggleName)) {
|
||||
// Ensure valid UUID.
|
||||
UUID uuid;
|
||||
try {
|
||||
uuid = UUID.fromString(idString);
|
||||
} catch (IllegalArgumentException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track that toggle is enabled.
|
||||
set(uuid, toggleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void set(UUID playerId, String toggleName) {
|
||||
enabledToggles.compute(playerId, (uuid, toggles) -> {
|
||||
if (toggles == null) {
|
||||
toggles = new HashSet<>();
|
||||
}
|
||||
toggles.add(toggleName);
|
||||
return toggles;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
Map<String, List<String>> converted = getSaveData();
|
||||
|
||||
YamlConfiguration data = new YamlConfiguration();
|
||||
for (Map.Entry<String, List<String>> playerToggle : converted.entrySet()) {
|
||||
data.set(playerToggle.getKey(), playerToggle.getValue());
|
||||
}
|
||||
|
||||
File file = new File(getDataFolder(), "toggles.yml");
|
||||
try {
|
||||
data.save(file);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Unable to save player toggle states", e);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Map<String, List<String>> getSaveData() {
|
||||
Map<String, List<String>> converted = new HashMap<>();
|
||||
|
||||
for (Map.Entry<UUID, Set<String>> playerToggles : enabledToggles.entrySet()) {
|
||||
String idString = playerToggles.getKey().toString();
|
||||
for (String toggleName : playerToggles.getValue()) {
|
||||
// Add player ID to listing for each enabled toggle.
|
||||
converted.compute(toggleName, (name, ids) -> {
|
||||
if (ids == null) {
|
||||
ids = new ArrayList<>();
|
||||
}
|
||||
ids.add(idString);
|
||||
return ids;
|
||||
});
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
UUID playerId = event.getPlayer().getUniqueId();
|
||||
Set<String> toggleNames = enabledToggles.get(playerId);
|
||||
|
||||
if (toggleNames == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String toggleName : toggleNames) {
|
||||
PlayerToggle toggle = PlayerToggles.get(toggleName);
|
||||
if (toggle != null) {
|
||||
toggle.set(playerId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onToggleSet(@NotNull PlayerToggledEvent event) {
|
||||
if (event.isEnabled()) {
|
||||
set(event.getPlayerId(), event.getToggle().getName());
|
||||
} else {
|
||||
enabledToggles.computeIfPresent(event.getPlayerId(), (uuid, toggles) -> {
|
||||
toggles.remove(event.getToggle().getName());
|
||||
return toggles.isEmpty() ? null : toggles;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
name: OITogglePersist
|
||||
main: com.github.jikoo.openinv.togglepersist.TogglePersist
|
||||
version: ${project.version}
|
||||
author: Jikoo
|
||||
description: An OpenInv addon allowing /anycontainer and /silentcontainer to persist across sessions.
|
||||
api-version: "1.20"
|
||||
depend: [ OpenInv ]
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvapi</artifactId>
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.lishid.openinv.event;
|
||||
import com.google.errorprone.annotations.RestrictedApi;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -11,6 +12,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public class OpenPlayerSaveEvent extends PlayerSaveEvent {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final ISpecialInventory inventory;
|
||||
|
||||
/**
|
||||
@@ -40,4 +43,14 @@ public class OpenPlayerSaveEvent extends PlayerSaveEvent {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.lishid.openinv.event;
|
||||
|
||||
import com.google.errorprone.annotations.RestrictedApi;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Event fired after OpenInv modifies a toggleable setting for a player.
|
||||
*/
|
||||
public class PlayerToggledEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final @NotNull PlayerToggle toggle;
|
||||
private final @NotNull UUID uuid;
|
||||
private final boolean enabled;
|
||||
|
||||
@RestrictedApi(
|
||||
explanation = "Constructor is not considered part of the API and may be subject to change.",
|
||||
link = "",
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java")
|
||||
@ApiStatus.Internal
|
||||
PlayerToggledEvent(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean enabled) {
|
||||
this.toggle = toggle;
|
||||
this.uuid = uuid;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link PlayerToggle} affected.
|
||||
*
|
||||
* @return the toggle
|
||||
*/
|
||||
public @NotNull PlayerToggle getToggle() {
|
||||
return toggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link UUID} of the player whose setting was changed.
|
||||
*
|
||||
* @return the player ID
|
||||
*/
|
||||
public @NotNull UUID getPlayerId() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the toggle is enabled.
|
||||
*
|
||||
* @return true if the toggle is enabled
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,15 +16,11 @@
|
||||
|
||||
package com.lishid.openinv.internal;
|
||||
|
||||
import org.bukkit.block.Barrel;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.EnderChest;
|
||||
import org.bukkit.block.ShulkerBox;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Directional;
|
||||
import org.bukkit.block.data.type.Chest;
|
||||
import org.bukkit.entity.Cat;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
@@ -57,62 +53,7 @@ public interface IAnySilentContainer {
|
||||
* @param block the {@link Block} of the container
|
||||
* @return true if the container is blocked
|
||||
*/
|
||||
default boolean isAnyContainerNeeded(@NotNull Block block) {
|
||||
BlockState blockState = block.getState();
|
||||
|
||||
// Barrels do not require AnyContainer.
|
||||
if (blockState instanceof Barrel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enderchests require a non-occluding block on top to open.
|
||||
if (blockState instanceof EnderChest) {
|
||||
return block.getRelative(0, 1, 0).getType().isOccluding();
|
||||
}
|
||||
|
||||
// Shulker boxes require half a block clear in the direction they open.
|
||||
if (blockState instanceof ShulkerBox) {
|
||||
return isShulkerBlocked(block);
|
||||
}
|
||||
|
||||
if (!(blockState instanceof org.bukkit.block.Chest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isChestBlocked(block)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockData blockData = block.getBlockData();
|
||||
if (!(blockData instanceof Chest chest) || chest.getType() == Chest.Type.SINGLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockFace relativeFace = switch (chest.getFacing()) {
|
||||
case NORTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.WEST : BlockFace.EAST;
|
||||
case EAST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.NORTH : BlockFace.SOUTH;
|
||||
case SOUTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.EAST : BlockFace.WEST;
|
||||
case WEST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.SOUTH : BlockFace.NORTH;
|
||||
default -> BlockFace.SELF;
|
||||
};
|
||||
Block relative = block.getRelative(relativeFace);
|
||||
|
||||
if (relative.getType() != block.getType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockData relativeData = relative.getBlockData();
|
||||
if (!(relativeData instanceof Chest relativeChest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (relativeChest.getFacing() != chest.getFacing()
|
||||
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isChestBlocked(relative);
|
||||
}
|
||||
boolean isAnyContainerNeeded(@NotNull Block block);
|
||||
|
||||
/**
|
||||
* Check if a shulker box block cannot be opened under ordinary circumstances.
|
||||
@@ -123,9 +64,13 @@ public interface IAnySilentContainer {
|
||||
default boolean isShulkerBlocked(@NotNull Block shulkerBox) {
|
||||
Directional directional = (Directional) shulkerBox.getBlockData();
|
||||
BlockFace facing = directional.getFacing();
|
||||
// Construct a new 1-block bounding box at the origin.
|
||||
BoundingBox box = new BoundingBox(0, 0, 0, 1, 1, 1);
|
||||
// Expand the box in the direction the shulker will open.
|
||||
box.expand(facing, 0.5);
|
||||
// Move the box away from the origin by a block so only the expansion intersects with a box around the origin.
|
||||
box.shift(facing.getOppositeFace().getDirection());
|
||||
// Check if the relative block's collision shape (which will be at the origin) intersects with the expanded box.
|
||||
return shulkerBox.getRelative(facing).getCollisionShape().overlaps(box);
|
||||
}
|
||||
|
||||
@@ -147,9 +92,7 @@ public interface IAnySilentContainer {
|
||||
* @param block the potential container
|
||||
* @return true if the type is a supported container
|
||||
*/
|
||||
default boolean isAnySilentContainer(@NotNull Block block) {
|
||||
return isAnySilentContainer(block.getState());
|
||||
}
|
||||
boolean isAnySilentContainer(@NotNull Block block);
|
||||
|
||||
/**
|
||||
* Check if the given {@link BlockState} is a container which can be unblocked or silenced.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.lishid.openinv.util.setting;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A per-player setting that may be enabled or disabled.
|
||||
*/
|
||||
public interface PlayerToggle {
|
||||
|
||||
/**
|
||||
* Get the name of the setting.
|
||||
*
|
||||
* @return the setting name
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Get the state of the toggle for a particular player ID.
|
||||
*
|
||||
* @param uuid the player ID
|
||||
* @return true if the setting is enabled
|
||||
*/
|
||||
boolean is(@NotNull UUID uuid);
|
||||
|
||||
/**
|
||||
* Set the state of the toggle for a particular player ID.
|
||||
*
|
||||
* @param uuid the player ID
|
||||
* @param enabled whether the setting is enabled
|
||||
* @return true if the setting changed as a result of being set
|
||||
*/
|
||||
boolean set(@NotNull UUID uuid, boolean enabled);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.lishid.openinv.util.setting;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class containing all of OpenInv's {@link PlayerToggle PlayerToggles}.
|
||||
*/
|
||||
public final class PlayerToggles {
|
||||
|
||||
private static final Map<String, PlayerToggle> TOGGLES = new HashMap<>();
|
||||
private static final PlayerToggle ANY = add(new MemoryToggle("AnyContainer"));
|
||||
private static final PlayerToggle SILENT = add(new MemoryToggle("SilentContainer"));
|
||||
|
||||
/**
|
||||
* Get the AnyContainer toggle.
|
||||
*
|
||||
* @return the AnyContainer toggle
|
||||
*/
|
||||
public static @NotNull PlayerToggle any() {
|
||||
return ANY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SilentContainer toggle.
|
||||
*
|
||||
* @return the SilentContainer toggle
|
||||
*/
|
||||
public static @NotNull PlayerToggle silent() {
|
||||
return SILENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a toggle by name.
|
||||
*
|
||||
* @param toggleName the name of the toggle
|
||||
* @return the toggle, or null if no such toggle exists.
|
||||
*/
|
||||
public static @Nullable PlayerToggle get(@NotNull String toggleName) {
|
||||
PlayerToggle toggle = TOGGLES.get(toggleName);
|
||||
if (toggle == null) {
|
||||
toggle = TOGGLES.get(toggleName.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
return toggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unmodifable view of all toggles available.
|
||||
*
|
||||
* @return a view of all toggles available
|
||||
*/
|
||||
public static @UnmodifiableView @NotNull Collection<PlayerToggle> get() {
|
||||
return Collections.unmodifiableCollection(TOGGLES.values());
|
||||
}
|
||||
|
||||
private static @NotNull PlayerToggle add(@NotNull PlayerToggle toggle) {
|
||||
TOGGLES.put(toggle.getName().toLowerCase(Locale.ENGLISH), toggle);
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private PlayerToggles() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
}
|
||||
|
||||
private static class MemoryToggle implements PlayerToggle {
|
||||
|
||||
private final @NotNull Set<UUID> enabled;
|
||||
private final @NotNull String name;
|
||||
|
||||
private MemoryToggle(@NotNull String name) {
|
||||
enabled = new HashSet<>();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean is(@NotNull UUID uuid) {
|
||||
return enabled.contains(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean set(@NotNull UUID uuid, boolean enabled) {
|
||||
if (enabled) {
|
||||
return this.enabled.add(uuid);
|
||||
} else {
|
||||
return this.enabled.remove(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lishid</groupId>
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvcommon</artifactId>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.lishid.openinv.event;
|
||||
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Construct and call events.
|
||||
*/
|
||||
@@ -16,8 +19,12 @@ public final class OpenEvents {
|
||||
return call(new PlayerSaveEvent(player));
|
||||
}
|
||||
|
||||
public static boolean saveCancelled(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
return call(new OpenPlayerSaveEvent(player, inventory));
|
||||
public static boolean saveCancelled(@NotNull ISpecialInventory inventory) {
|
||||
return call(new OpenPlayerSaveEvent((Player) inventory.getPlayer(), inventory));
|
||||
}
|
||||
|
||||
public static void notifyPlayerToggle(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
|
||||
Bukkit.getPluginManager().callEvent(new PlayerToggledEvent(toggle, uuid, state));
|
||||
}
|
||||
|
||||
private static <T extends Event & Cancellable> boolean call(T event) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.lishid.openinv.internal;
|
||||
|
||||
import org.bukkit.block.Barrel;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.EnderChest;
|
||||
import org.bukkit.block.ShulkerBox;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Chest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public abstract class AnySilentContainerBase implements IAnySilentContainer {
|
||||
|
||||
private static final @Nullable Method BLOCK_GET_STATE_BOOLEAN;
|
||||
|
||||
static {
|
||||
@Nullable Method getState;
|
||||
try {
|
||||
//noinspection JavaReflectionMemberAccess
|
||||
getState = Block.class.getMethod("getState", boolean.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
getState = null;
|
||||
}
|
||||
BLOCK_GET_STATE_BOOLEAN = getState;
|
||||
}
|
||||
|
||||
private static BlockState getBlockState(Block block) {
|
||||
// Paper: Get state without snapshotting.
|
||||
if (BLOCK_GET_STATE_BOOLEAN != null) {
|
||||
try {
|
||||
return (BlockState) BLOCK_GET_STATE_BOOLEAN.invoke(block, false);
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
// If we encounter an issue, fall through to regular snapshotting method.
|
||||
}
|
||||
}
|
||||
return block.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnyContainerNeeded(@NotNull Block block) {
|
||||
BlockState blockState = getBlockState(block);
|
||||
|
||||
// Barrels do not require AnyContainer.
|
||||
if (blockState instanceof Barrel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enderchests require a non-occluding block on top to open.
|
||||
if (blockState instanceof EnderChest) {
|
||||
return block.getRelative(0, 1, 0).getType().isOccluding();
|
||||
}
|
||||
|
||||
// Shulker boxes require half a block clear in the direction they open.
|
||||
if (blockState instanceof ShulkerBox) {
|
||||
return isShulkerBlocked(block);
|
||||
}
|
||||
|
||||
if (!(blockState instanceof org.bukkit.block.Chest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isChestBlocked(block)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockData blockData = block.getBlockData();
|
||||
if (!(blockData instanceof Chest chest) || chest.getType() == Chest.Type.SINGLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockFace relativeFace = switch (chest.getFacing()) {
|
||||
case NORTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.WEST : BlockFace.EAST;
|
||||
case EAST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.NORTH : BlockFace.SOUTH;
|
||||
case SOUTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.EAST : BlockFace.WEST;
|
||||
case WEST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.SOUTH : BlockFace.NORTH;
|
||||
default -> BlockFace.SELF;
|
||||
};
|
||||
Block relative = block.getRelative(relativeFace);
|
||||
|
||||
if (relative.getType() != block.getType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockData relativeData = relative.getBlockData();
|
||||
if (!(relativeData instanceof Chest relativeChest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (relativeChest.getFacing() != chest.getFacing()
|
||||
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isChestBlocked(relative);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnySilentContainer(@NotNull Block block) {
|
||||
return isAnySilentContainer(getBlockState(block));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.lishid.openinv.internal;
|
||||
|
||||
public interface InternalOwned<T> {
|
||||
|
||||
T getOwnerHandle();
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.lishid.openinv.internal;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
public interface PlaceholderParser {
|
||||
|
||||
void load(ConfigurationSection section) throws Exception;
|
||||
|
||||
}
|
||||
@@ -49,6 +49,6 @@ public interface PlayerManager {
|
||||
*`
|
||||
* @return the InventoryView opened
|
||||
*/
|
||||
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
|
||||
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.lishid.openinv.internal;
|
||||
|
||||
public interface ViewOnly {
|
||||
}
|
||||
@@ -274,7 +274,7 @@ public class LanguageManager {
|
||||
if (sender instanceof Player) {
|
||||
return ((Player) sender).getLocale();
|
||||
} else {
|
||||
return plugin.getConfig().getString("settings.locale", "en_us");
|
||||
return plugin.getConfig().getString("settings.locale", "en");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvadapter1_20_R3</artifactId>
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.lishid.openinv.internal.v1_20_R3;
|
||||
|
||||
import com.lishid.openinv.internal.IAnySilentContainer;
|
||||
import com.lishid.openinv.internal.AnySilentContainerBase;
|
||||
import com.lishid.openinv.util.ReflectionHelper;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import net.minecraft.core.BlockPos;
|
||||
@@ -48,7 +48,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class AnySilentContainer implements IAnySilentContainer {
|
||||
public class AnySilentContainer extends AnySilentContainerBase {
|
||||
|
||||
private final @NotNull Logger logger;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
+1
-6
@@ -105,11 +105,6 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
// Ensure player has data
|
||||
if (!offline.hasPlayedBefore()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
|
||||
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
|
||||
|
||||
@@ -244,7 +239,7 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
|
||||
ServerPlayer nmsPlayer = getHandle(player);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvadapter1_20_R4</artifactId>
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.lishid.openinv.internal.v1_20_R4;
|
||||
|
||||
import com.lishid.openinv.internal.IAnySilentContainer;
|
||||
import com.lishid.openinv.internal.AnySilentContainerBase;
|
||||
import com.lishid.openinv.util.ReflectionHelper;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import net.minecraft.core.BlockPos;
|
||||
@@ -48,7 +48,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class AnySilentContainer implements IAnySilentContainer {
|
||||
public class AnySilentContainer extends AnySilentContainerBase {
|
||||
|
||||
private final @NotNull Logger logger;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
+1
-6
@@ -105,11 +105,6 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
// Ensure player has data
|
||||
if (!offline.hasPlayedBefore()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
|
||||
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
|
||||
|
||||
@@ -244,7 +239,7 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
|
||||
ServerPlayer nmsPlayer = getHandle(player);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvadapter1_21_R1</artifactId>
|
||||
@@ -32,7 +32,7 @@
|
||||
<properties>
|
||||
<maven.compiler.system>21</maven.compiler.system>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<spigot.version>1.21-R0.1-SNAPSHOT</spigot.version>
|
||||
<spigot.version>1.21.1-R0.1-SNAPSHOT</spigot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+4
-3
@@ -16,7 +16,8 @@
|
||||
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
|
||||
import com.lishid.openinv.internal.IAnySilentContainer;
|
||||
import com.lishid.openinv.internal.AnySilentContainerBase;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.menu.OpenChestMenu;
|
||||
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
|
||||
import com.lishid.openinv.util.ReflectionHelper;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
@@ -49,7 +50,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class AnySilentContainer implements IAnySilentContainer {
|
||||
public class AnySilentContainer extends AnySilentContainerBase {
|
||||
|
||||
private final @NotNull Logger logger;
|
||||
private final @NotNull LanguageManager lang;
|
||||
@@ -103,7 +104,7 @@ public class AnySilentContainer implements IAnySilentContainer {
|
||||
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
|
||||
enderChest.setActiveChest(enderChestTile);
|
||||
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
|
||||
MenuType<?> containers = OpenContainerMenu.getContainers(enderChest.getContainerSize());
|
||||
MenuType<?> containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
|
||||
int rows = enderChest.getContainerSize() / 9;
|
||||
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
|
||||
}, Component.translatable("container.enderchest")));
|
||||
|
||||
+12
-8
@@ -1,14 +1,14 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
|
||||
import com.lishid.openinv.internal.ISpecialEnderChest;
|
||||
import com.lishid.openinv.internal.InternalOwned;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.menu.OpenEnderChestMenu;
|
||||
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.player.StackedContents;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -25,7 +25,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class OpenEnderChest implements Container, StackedContentsCompatible, MenuProvider, ISpecialEnderChest {
|
||||
public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned<ServerPlayer>,
|
||||
ISpecialEnderChest {
|
||||
|
||||
private CraftInventory inventory;
|
||||
private @NotNull ServerPlayer owner;
|
||||
@@ -38,6 +39,11 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Men
|
||||
this.items = owner.getEnderChestInventory().items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ServerPlayer getOwnerHandle() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
|
||||
if (inventory == null) {
|
||||
@@ -174,17 +180,15 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Men
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
public Component getTitle() {
|
||||
return Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName())
|
||||
.append(Component.translatable("container.enderchest"))
|
||||
.append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
|
||||
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
return new OpenEnderChestMenu(this, serverPlayer, i);
|
||||
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView;
|
||||
|
||||
class OpenEnderChestMenu extends OpenContainerMenu {
|
||||
|
||||
private final OpenEnderChest enderChest;
|
||||
private final boolean viewOnly;
|
||||
private final ServerPlayer viewer;
|
||||
private final int topSize;
|
||||
private CraftInventoryView view;
|
||||
|
||||
OpenEnderChestMenu(OpenEnderChest enderChest, ServerPlayer viewer, int containerId) {
|
||||
super(getMenuType(enderChest), containerId);
|
||||
this.enderChest = enderChest;
|
||||
this.viewer = viewer;
|
||||
var bukkitViewer = viewer.getBukkitEntity();
|
||||
viewOnly = !(bukkitViewer.equals(enderChest.getPlayer())
|
||||
? Permissions.ENDERCHEST_EDIT_SELF
|
||||
: Permissions.ENDERCHEST_OPEN_OTHER)
|
||||
.hasPermission(bukkitViewer);
|
||||
int upperRows = (int) Math.ceil(enderChest.getContainerSize() / 9.0);
|
||||
topSize = upperRows * 9;
|
||||
|
||||
// View's upper inventory - our container
|
||||
for (int row = 0; row < upperRows; ++row) {
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
// x and y for client purposes, but hey, we're thorough here.
|
||||
// Adapted from net.minecraft.world.inventory.ChestMenu
|
||||
int x = 8 + col * 18;
|
||||
int y = 18 + row * 18;
|
||||
int index = row * 9 + col;
|
||||
|
||||
// Guard against weird inventory sizes.
|
||||
if (index >= enderChest.getContainerSize()) {
|
||||
addSlot(new SlotViewOnly(enderChest, index, x, y));
|
||||
continue;
|
||||
}
|
||||
|
||||
addSlot(new Slot(enderChest, index, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
// View's lower inventory - viewer inventory
|
||||
int playerInvPad = (upperRows - 4) * 18;
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
int x = 8 + col * 18;
|
||||
int y = playerInvPad + row * 18 + 103;
|
||||
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
|
||||
}
|
||||
}
|
||||
// Hotbar
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
int x = 8 + col * 18;
|
||||
int y = playerInvPad + 161;
|
||||
addSlot(new Slot(viewer.getInventory(), col, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
private static MenuType<?> getMenuType(OpenEnderChest enderChest) {
|
||||
return OpenContainerMenu.getContainers(((int) Math.ceil(enderChest.getContainerSize() / 9.0)) * 9);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CraftInventoryView getBukkitView() {
|
||||
if (view == null) {
|
||||
view = new CraftInventoryView(viewer.getBukkitEntity(), enderChest.getBukkitInventory(), this);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Slot addSlot(Slot slot) {
|
||||
slot = super.addSlot(slot);
|
||||
|
||||
// If view-only and slot is in upper inventory, wrap it.
|
||||
if (viewOnly && slot.index < enderChest.getContainerSize()) {
|
||||
slot = SlotViewOnly.wrap(slot);
|
||||
slots.set(slot.index, slot);
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
// See ChestMenu
|
||||
Slot slot = this.slots.get(index);
|
||||
|
||||
if (slot.isFake() || !slot.hasItem()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
ItemStack itemStack = slot.getItem();
|
||||
ItemStack original = itemStack.copy();
|
||||
|
||||
if (index < topSize) {
|
||||
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
if (itemStack.isEmpty()) {
|
||||
slot.setByPlayer(ItemStack.EMPTY);
|
||||
} else {
|
||||
slot.setChanged();
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player entityhuman) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+12
-15
@@ -1,7 +1,9 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
|
||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
||||
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
|
||||
import com.lishid.openinv.internal.InternalOwned;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenPlayerInventory;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.menu.OpenInventoryMenu;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.Content;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentCrafting;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentCraftingResult;
|
||||
@@ -12,6 +14,7 @@ import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentList;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentOffHand;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentViewOnly;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
|
||||
import com.lishid.openinv.internal.v1_21_R1.player.PlayerManager;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -19,7 +22,6 @@ import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -37,7 +39,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class OpenInventory implements Container, MenuProvider, ISpecialPlayerInventory {
|
||||
public class OpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
|
||||
|
||||
private final List<Content> slots;
|
||||
private final int size;
|
||||
@@ -203,10 +205,6 @@ public class OpenInventory implements Container, MenuProvider, ISpecialPlayerInv
|
||||
return slots.get(index).getSlotType();
|
||||
}
|
||||
|
||||
public ServerPlayer getOwnerHandle() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public @NotNull Component getTitle(@Nullable ServerPlayer viewer) {
|
||||
MutableComponent component = Component.empty();
|
||||
// Prefix for use with custom bitmap image fonts.
|
||||
@@ -230,6 +228,11 @@ public class OpenInventory implements Container, MenuProvider, ISpecialPlayerInv
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerPlayer getOwnerHandle() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
|
||||
if (bukkitEntity == null) {
|
||||
@@ -351,15 +354,9 @@ public class OpenInventory implements Container, MenuProvider, ISpecialPlayerInv
|
||||
owner.containerMenu.setCarried(ItemStack.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return getTitle(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
|
||||
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
return new OpenInventoryMenu(this, serverPlayer, i);
|
||||
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
-295
@@ -1,295 +0,0 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentDrop;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentEquipment;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class OpenInventoryMenu extends OpenContainerMenu {
|
||||
|
||||
private final OpenInventory inventory;
|
||||
private final ServerPlayer viewer;
|
||||
private final int topSize;
|
||||
private final int offset;
|
||||
private final boolean ownInv;
|
||||
private CraftInventoryView bukkitEntity;
|
||||
|
||||
protected OpenInventoryMenu(OpenInventory inventory, ServerPlayer viewer, int i) {
|
||||
super(getMenuType(inventory, viewer), i);
|
||||
this.inventory = inventory;
|
||||
this.viewer = viewer;
|
||||
ownInv = inventory.getOwnerHandle().equals(viewer);
|
||||
|
||||
int upperRows;
|
||||
if (ownInv) {
|
||||
// Disallow duplicate access to own main inventory contents.
|
||||
offset = viewer.getInventory().items.size();
|
||||
upperRows = ((int) Math.ceil((inventory.getContainerSize() - offset) / 9.0));
|
||||
} else {
|
||||
offset = 0;
|
||||
upperRows = inventory.getContainerSize() / 9;
|
||||
}
|
||||
|
||||
boolean viewOnly = !(ownInv ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER)
|
||||
.hasPermission(viewer.getBukkitEntity());
|
||||
|
||||
// View's upper inventory - our container
|
||||
for (int row = 0; row < upperRows; ++row) {
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
// x and y for client purposes, but hey, we're thorough here.
|
||||
// Adapted from net.minecraft.world.inventory.ChestMenu
|
||||
int x = 8 + col * 18;
|
||||
int y = 18 + row * 18;
|
||||
int index = offset + row * 9 + col;
|
||||
|
||||
// Guard against weird inventory sizes.
|
||||
if (index >= inventory.getContainerSize()) {
|
||||
addSlot(new SlotViewOnly(inventory, index, x, y));
|
||||
continue;
|
||||
}
|
||||
|
||||
Slot slot = getUpperSlot(index, x, y, ownInv, viewOnly);
|
||||
|
||||
addSlot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
// View's lower inventory - viewer inventory
|
||||
int playerInvPad = (upperRows - 4) * 18;
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
int x = 8 + col * 18;
|
||||
int y = playerInvPad + row * 18 + 103;
|
||||
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
|
||||
}
|
||||
}
|
||||
// Hotbar
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
int x = 8 + col * 18;
|
||||
int y = playerInvPad + 161;
|
||||
addSlot(new Slot(viewer.getInventory(), col, x, y));
|
||||
}
|
||||
|
||||
this.topSize = slots.size() - 36;
|
||||
}
|
||||
|
||||
private Slot getUpperSlot(int index, int x, int y, boolean ownInv, boolean viewOnly) {
|
||||
Slot slot = inventory.getMenuSlot(index, x, y);
|
||||
|
||||
// If the slot is cannot be interacted with there's nothing to configure.
|
||||
if (slot.getClass().equals(SlotViewOnly.class)) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
// Remove drop slot if viewer is not allowed to use it.
|
||||
if (slot instanceof ContentDrop.SlotDrop
|
||||
&& (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) {
|
||||
return new SlotViewOnly(inventory, index, x, y);
|
||||
}
|
||||
|
||||
if (slot instanceof ContentEquipment.SlotEquipment equipment) {
|
||||
if (viewOnly) {
|
||||
return SlotViewOnly.wrap(slot);
|
||||
}
|
||||
|
||||
Permissions perm = switch (equipment.getEquipmentSlot()) {
|
||||
case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY;
|
||||
case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY;
|
||||
case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY;
|
||||
case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY;
|
||||
// Off-hand can hold anything, not just equipment.
|
||||
default -> null;
|
||||
};
|
||||
|
||||
// If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot.
|
||||
if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) {
|
||||
equipment.onlyEquipmentFor(inventory.getOwnerHandle());
|
||||
}
|
||||
|
||||
// Equipment slots are a core part of the inventory, so they will always be shown.
|
||||
return slot;
|
||||
}
|
||||
|
||||
// When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above).
|
||||
if (ownInv && !(slot instanceof ContentDrop.SlotDrop)) {
|
||||
return new SlotViewOnly(inventory, index, x, y);
|
||||
}
|
||||
|
||||
if (viewOnly) {
|
||||
return SlotViewOnly.wrap(slot);
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static MenuType<?> getMenuType(OpenInventory inventory, ServerPlayer viewer) {
|
||||
int size = inventory.getContainerSize();
|
||||
if (inventory.getOwnerHandle().equals(viewer)) {
|
||||
size -= viewer.getInventory().items.size();
|
||||
size = ((int) Math.ceil(size / 9.0)) * 9;
|
||||
}
|
||||
|
||||
return OpenContainerMenu.getContainers(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CraftInventoryView getBukkitView() {
|
||||
if (bukkitEntity == null) {
|
||||
org.bukkit.inventory.Inventory bukkitInventory;
|
||||
if (ownInv) {
|
||||
bukkitInventory = new OpenPlayerInventorySelf(inventory, offset);
|
||||
} else {
|
||||
bukkitInventory = inventory.getBukkitInventory();
|
||||
}
|
||||
|
||||
bukkitEntity = new CraftInventoryView(viewer.getBukkitEntity(), bukkitInventory, this) {
|
||||
@Override
|
||||
public org.bukkit.inventory.ItemStack getItem(int index) {
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Slot slot = slots.get(index);
|
||||
return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInTop(int rawSlot) {
|
||||
return rawSlot < topSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory(int rawSlot) {
|
||||
if (rawSlot < 0) {
|
||||
return super.getInventory(rawSlot);
|
||||
}
|
||||
if (rawSlot > topSize) {
|
||||
return super.getInventory(offset + rawSlot);
|
||||
}
|
||||
Slot slot = slots.get(rawSlot);
|
||||
if (slot.isFake()) {
|
||||
return null;
|
||||
}
|
||||
return getTopInventory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertSlot(int rawSlot) {
|
||||
if (rawSlot < 0) {
|
||||
return rawSlot;
|
||||
}
|
||||
if (rawSlot < topSize) {
|
||||
Slot slot = slots.get(rawSlot);
|
||||
if (slot.isFake()) {
|
||||
return InventoryView.OUTSIDE;
|
||||
}
|
||||
return rawSlot;
|
||||
}
|
||||
return super.convertSlot(offset + rawSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InventoryType.SlotType getSlotType(int slot) {
|
||||
if (slot < 0) {
|
||||
return InventoryType.SlotType.OUTSIDE;
|
||||
}
|
||||
if (slot >= topSize) {
|
||||
return super.getSlotType(offset + slot);
|
||||
}
|
||||
return inventory.getSlotType(offset + slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countSlots() {
|
||||
return topSize + getBottomInventory().getSize();
|
||||
}
|
||||
};
|
||||
}
|
||||
return bukkitEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
// See ChestMenu and InventoryMenu
|
||||
Slot slot = this.slots.get(index);
|
||||
|
||||
if (!slot.hasItem() || slot.isFake()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
ItemStack itemStack = slot.getItem();
|
||||
ItemStack originalStack = itemStack.copy();
|
||||
|
||||
if (index < topSize) {
|
||||
// If we're moving top to bottom, do a normal transfer.
|
||||
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else {
|
||||
EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack);
|
||||
boolean movedGear = switch (equipmentSlot) {
|
||||
// If this is gear, try to move it to the correct slot first.
|
||||
case OFFHAND, FEET, LEGS, CHEST, HEAD -> {
|
||||
// Locate the correct slot in the contents following the main inventory.
|
||||
for (int extra = inventory.getOwnerHandle().getInventory().items.size() - offset; extra < topSize; ++extra) {
|
||||
Slot extraSlot = getSlot(extra);
|
||||
if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot
|
||||
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
|
||||
// If we've found a matching slot, try to move to it.
|
||||
// If this succeeds, even partially, we will not attempt to move to other slots.
|
||||
// Otherwise, armor is already occupied, so we'll fall through to main inventory.
|
||||
yield this.moveItemStackTo(itemStack, extra, extra + 1, false);
|
||||
}
|
||||
}
|
||||
yield false;
|
||||
}
|
||||
// Non-gear gets no special treatment.
|
||||
default -> false;
|
||||
};
|
||||
|
||||
// If main inventory is not available, there's nowhere else to move.
|
||||
if (offset != 0) {
|
||||
if (!movedGear) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else {
|
||||
// If we didn't move to a gear slot, try to move to a main inventory slot.
|
||||
if (!movedGear && !this.moveItemStackTo(itemStack, 0, inventory.getOwnerHandle().getInventory().items.size(), true)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (itemStack.isEmpty()) {
|
||||
slot.setByPlayer(ItemStack.EMPTY);
|
||||
} else {
|
||||
slot.setChanged();
|
||||
}
|
||||
|
||||
return originalStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDragTo(Slot slot) {
|
||||
return !(slot instanceof ContentDrop.SlotDrop || slot instanceof SlotViewOnly);
|
||||
}
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container.bukkit;
|
||||
|
||||
import com.lishid.openinv.internal.ViewOnly;
|
||||
import net.minecraft.world.Container;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* A locked down "empty" inventory that rejects plugin interaction.
|
||||
*/
|
||||
public class OpenDummyInventory extends CraftInventory implements ViewOnly {
|
||||
|
||||
public OpenDummyInventory(Container inventory) {
|
||||
super(inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack getItem(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(int index, @Nullable ItemStack item) {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonApiType")
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... items) throws IllegalArgumentException {
|
||||
return arrayToHashMap(items);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonApiType")
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> removeItem(@NotNull ItemStack... items) throws IllegalArgumentException {
|
||||
return arrayToHashMap(items);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonApiType")
|
||||
private static @NotNull HashMap<Integer, ItemStack> arrayToHashMap(@NotNull ItemStack[] items) {
|
||||
HashMap<Integer, ItemStack> ignored = new HashMap<>();
|
||||
for (int index = 0; index < items.length; ++index) {
|
||||
ignored.put(index, items[index]);
|
||||
}
|
||||
return ignored;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getContents() {
|
||||
return new ItemStack[getSize()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack[] getStorageContents() {
|
||||
return new ItemStack[getSize()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull Material material) throws IllegalArgumentException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@Nullable ItemStack item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@Nullable ItemStack item, int amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAtLeast(@Nullable ItemStack item, int amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonApiType")
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> all(
|
||||
@NotNull Material material) throws IllegalArgumentException {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonApiType")
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> all(@Nullable ItemStack item) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int first(@NotNull Material material) throws IllegalArgumentException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int first(@NotNull ItemStack item) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstEmpty() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull Material material) throws IllegalArgumentException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull ItemStack item) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int index) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ListIterator<ItemStack> iterator() {
|
||||
return Collections.emptyListIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ListIterator<ItemStack> iterator(int index) {
|
||||
return Collections.emptyListIterator();
|
||||
}
|
||||
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
package com.lishid.openinv.internal.v1_21_R1.container.bukkit;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -20,7 +21,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenInventory getInventory() {
|
||||
public @NotNull OpenInventory getInventory() {
|
||||
return (OpenInventory) super.getInventory();
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
package com.lishid.openinv.internal.v1_21_R1.container.bukkit;
|
||||
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
+188
-17
@@ -1,10 +1,19 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container;
|
||||
package com.lishid.openinv.internal.v1_21_R1.container.menu;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.InternalOwned;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenDummyInventory;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotPlaceholder;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ChestMenu;
|
||||
import net.minecraft.world.inventory.ClickType;
|
||||
import net.minecraft.world.inventory.ContainerData;
|
||||
import net.minecraft.world.inventory.ContainerListener;
|
||||
import net.minecraft.world.inventory.ContainerSynchronizer;
|
||||
@@ -12,6 +21,10 @@ import net.minecraft.world.inventory.DataSlot;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -22,8 +35,18 @@ import java.util.function.Supplier;
|
||||
/**
|
||||
* An extension of {@link AbstractContainerMenu} that supports {@link SlotPlaceholder placeholders}.
|
||||
*/
|
||||
public abstract class OpenContainerMenu extends AbstractContainerMenu {
|
||||
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
|
||||
extends AbstractContainerMenu {
|
||||
|
||||
protected static final int BOTTOM_INVENTORY_SIZE = 36;
|
||||
|
||||
protected final T container;
|
||||
protected final ServerPlayer viewer;
|
||||
protected final boolean viewOnly;
|
||||
protected final boolean ownContainer;
|
||||
protected final int topSize;
|
||||
private CraftInventoryView<OpenChestMenu<T>> bukkitEntity;
|
||||
// Syncher fields
|
||||
private @Nullable ContainerSynchronizer synchronizer;
|
||||
private final List<DataSlot> dataSlots = new ArrayList<>();
|
||||
private final IntList remoteDataSlots = new IntArrayList();
|
||||
@@ -31,20 +54,145 @@ public abstract class OpenContainerMenu extends AbstractContainerMenu {
|
||||
private ItemStack remoteCarried = ItemStack.EMPTY;
|
||||
private boolean suppressRemoteUpdates;
|
||||
|
||||
protected OpenContainerMenu(@Nullable MenuType<?> containers, int containerCounter) {
|
||||
super(containers, containerCounter);
|
||||
protected OpenChestMenu(
|
||||
@NotNull MenuType<ChestMenu> type,
|
||||
int containerCounter,
|
||||
@NotNull T container,
|
||||
@NotNull ServerPlayer viewer,
|
||||
boolean viewOnly) {
|
||||
super(type, containerCounter);
|
||||
this.container = container;
|
||||
this.viewer = viewer;
|
||||
this.viewOnly = viewOnly;
|
||||
ownContainer = container.getOwnerHandle().equals(viewer);
|
||||
topSize = getTopSize(viewer);
|
||||
|
||||
preSlotSetup();
|
||||
|
||||
int upperRows = topSize / 9;
|
||||
// View's upper inventory - our container
|
||||
for (int row = 0; row < upperRows; ++row) {
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
// x and y for client purposes, but hey, we're thorough here.
|
||||
// Adapted from net.minecraft.world.inventory.ChestMenu
|
||||
int x = 8 + col * 18;
|
||||
int y = 18 + row * 18;
|
||||
int index = row * 9 + col;
|
||||
|
||||
// Guard against weird inventory sizes.
|
||||
if (index >= container.getContainerSize()) {
|
||||
addSlot(new SlotViewOnly(container, index, x, y));
|
||||
continue;
|
||||
}
|
||||
|
||||
Slot slot = getUpperSlot(index, x, y);
|
||||
|
||||
addSlot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
// View's lower inventory - viewer inventory
|
||||
int playerInvPad = (upperRows - 4) * 18;
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
int x = 8 + col * 18;
|
||||
int y = playerInvPad + row * 18 + 103;
|
||||
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
|
||||
}
|
||||
}
|
||||
// Hotbar
|
||||
for (int col = 0; col < 9; ++col) {
|
||||
int x = 8 + col * 18;
|
||||
int y = playerInvPad + 161;
|
||||
addSlot(new Slot(viewer.getInventory(), col, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
static @NotNull MenuType<?> getContainers(int inventorySize) {
|
||||
return switch (inventorySize) {
|
||||
case 9 -> MenuType.GENERIC_9x1;
|
||||
case 18 -> MenuType.GENERIC_9x2;
|
||||
case 36 -> MenuType.GENERIC_9x4;
|
||||
case 45 -> MenuType.GENERIC_9x5;
|
||||
case 54 -> MenuType.GENERIC_9x6;
|
||||
// Default to 27-slot inventory
|
||||
default -> MenuType.GENERIC_9x3;
|
||||
};
|
||||
public static @NotNull MenuType<ChestMenu> getChestMenuType(int inventorySize) {
|
||||
inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9;
|
||||
return switch (inventorySize) {
|
||||
case 9 -> MenuType.GENERIC_9x1;
|
||||
case 18 -> MenuType.GENERIC_9x2;
|
||||
case 27 -> MenuType.GENERIC_9x3;
|
||||
case 36 -> MenuType.GENERIC_9x4;
|
||||
case 45 -> MenuType.GENERIC_9x5;
|
||||
case 54 -> MenuType.GENERIC_9x6;
|
||||
default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize);
|
||||
};
|
||||
}
|
||||
|
||||
protected void preSlotSetup() {}
|
||||
|
||||
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
|
||||
if (viewOnly) {
|
||||
return new SlotViewOnly(container, index, x, y);
|
||||
}
|
||||
return new Slot(container, index, x, y);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final @NotNull CraftInventoryView<OpenChestMenu<T>> getBukkitView() {
|
||||
if (bukkitEntity == null) {
|
||||
bukkitEntity = createBukkitEntity();
|
||||
}
|
||||
|
||||
return bukkitEntity;
|
||||
}
|
||||
|
||||
protected @NotNull CraftInventoryView<OpenChestMenu<T>> createBukkitEntity() {
|
||||
Inventory top;
|
||||
if (viewOnly) {
|
||||
top = new OpenDummyInventory(container);
|
||||
} else {
|
||||
top = container.getBukkitInventory();
|
||||
}
|
||||
return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) {
|
||||
@Override
|
||||
public @Nullable Inventory getInventory(int rawSlot) {
|
||||
if (viewOnly) {
|
||||
return null;
|
||||
}
|
||||
return super.getInventory(rawSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertSlot(int rawSlot) {
|
||||
if (viewOnly) {
|
||||
return InventoryView.OUTSIDE;
|
||||
}
|
||||
return super.convertSlot(rawSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InventoryType.SlotType getSlotType(int slot) {
|
||||
if (viewOnly) {
|
||||
return InventoryType.SlotType.OUTSIDE;
|
||||
}
|
||||
return super.getSlotType(slot);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private int getTopSize(ServerPlayer viewer) {
|
||||
MenuType<?> menuType = getType();
|
||||
if (menuType == null) {
|
||||
throw new IllegalStateException("MenuType cannot be null!");
|
||||
} else if (menuType == MenuType.GENERIC_9x1) {
|
||||
return 9;
|
||||
} else if (menuType == MenuType.GENERIC_9x2) {
|
||||
return 18;
|
||||
} else if (menuType == MenuType.GENERIC_9x3) {
|
||||
return 27;
|
||||
} else if (menuType == MenuType.GENERIC_9x4) {
|
||||
return 36;
|
||||
} else if (menuType == MenuType.GENERIC_9x5) {
|
||||
return 45;
|
||||
} else if (menuType == MenuType.GENERIC_9x6) {
|
||||
return 54;
|
||||
}
|
||||
// This is a bit gross, but allows us a safe fallthrough.
|
||||
return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +227,7 @@ public abstract class OpenContainerMenu extends AbstractContainerMenu {
|
||||
continue;
|
||||
}
|
||||
// Otherwise, add as many as we can from our stack to the slot.
|
||||
modified = addToExistingStack(itemStack, slot);
|
||||
modified |= addToExistingStack(itemStack, slot);
|
||||
} else {
|
||||
// If this is the first empty slot, keep track of it for later use.
|
||||
if (firstEmpty == null) {
|
||||
@@ -110,8 +258,15 @@ public abstract class OpenContainerMenu extends AbstractContainerMenu {
|
||||
return false;
|
||||
}
|
||||
|
||||
int total = existing.getCount() + itemStack.getCount();
|
||||
int max = slot.getMaxStackSize(existing);
|
||||
int existingCount = existing.getCount();
|
||||
|
||||
// If the stack is already full, we can't add more.
|
||||
if (existingCount >= max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int total = existingCount + itemStack.getCount();
|
||||
|
||||
// If the existing item can accept the entirety of our item, we're done!
|
||||
if (total <= max) {
|
||||
@@ -122,12 +277,28 @@ public abstract class OpenContainerMenu extends AbstractContainerMenu {
|
||||
}
|
||||
|
||||
// Otherwise, add as many as we can.
|
||||
itemStack.shrink(max - existing.getCount());
|
||||
itemStack.shrink(max - existingCount);
|
||||
existing.setCount(max);
|
||||
slot.setChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clicked(int i, int j, ClickType clickType, Player player) {
|
||||
if (viewOnly) {
|
||||
if (clickType == ClickType.QUICK_CRAFT) {
|
||||
sendAllDataToRemote();
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.clicked(i, j, clickType, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Overrides from here on are purely to modify the sync process to send placeholder items.
|
||||
@Override
|
||||
protected Slot addSlot(Slot slot) {
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container.menu;
|
||||
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.OpenEnderChest;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
|
||||
|
||||
public OpenEnderChestMenu(
|
||||
@NotNull OpenEnderChest enderChest,
|
||||
@NotNull ServerPlayer viewer,
|
||||
int containerId,
|
||||
boolean viewOnly) {
|
||||
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
if (viewOnly) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
// See ChestMenu
|
||||
Slot slot = this.slots.get(index);
|
||||
|
||||
if (slot.isFake() || !slot.hasItem()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
ItemStack itemStack = slot.getItem();
|
||||
ItemStack original = itemStack.copy();
|
||||
|
||||
if (index < topSize) {
|
||||
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
if (itemStack.isEmpty()) {
|
||||
slot.setByPlayer(ItemStack.EMPTY);
|
||||
} else {
|
||||
slot.setChanged();
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
package com.lishid.openinv.internal.v1_21_R1.container.menu;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.OpenInventory;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenDummyInventory;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.bukkit.OpenPlayerInventorySelf;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentDrop;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.ContentEquipment;
|
||||
import com.lishid.openinv.internal.v1_21_R1.container.slot.SlotViewOnly;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.ChestMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
|
||||
|
||||
private int offset;
|
||||
|
||||
public OpenInventoryMenu(OpenInventory inventory, ServerPlayer viewer, int i, boolean viewOnly) {
|
||||
super(getMenuType(inventory, viewer), i, inventory, viewer, viewOnly);
|
||||
}
|
||||
|
||||
private static MenuType<ChestMenu> getMenuType(OpenInventory inventory, ServerPlayer viewer) {
|
||||
int size = inventory.getContainerSize();
|
||||
// Disallow duplicate access to own main inventory contents.
|
||||
if (inventory.getOwnerHandle().equals(viewer)) {
|
||||
size -= viewer.getInventory().items.size();
|
||||
size = ((int) Math.ceil(size / 9.0)) * 9;
|
||||
}
|
||||
|
||||
return getChestMenuType(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preSlotSetup() {
|
||||
offset = ownContainer ? viewer.getInventory().items.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
|
||||
index += offset;
|
||||
Slot slot = container.getMenuSlot(index, x, y);
|
||||
|
||||
// If the slot cannot be interacted with there's nothing to configure.
|
||||
if (slot.getClass().equals(SlotViewOnly.class)) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
// Remove drop slot if viewer is not allowed to use it.
|
||||
if (slot instanceof ContentDrop.SlotDrop
|
||||
&& (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) {
|
||||
return new SlotViewOnly(container, index, x, y);
|
||||
}
|
||||
|
||||
if (slot instanceof ContentEquipment.SlotEquipment equipment) {
|
||||
if (viewOnly) {
|
||||
return SlotViewOnly.wrap(slot);
|
||||
}
|
||||
|
||||
Permissions perm = switch (equipment.getEquipmentSlot()) {
|
||||
case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY;
|
||||
case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY;
|
||||
case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY;
|
||||
case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY;
|
||||
// Off-hand can hold anything, not just equipment.
|
||||
default -> null;
|
||||
};
|
||||
|
||||
// If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot.
|
||||
if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) {
|
||||
equipment.onlyEquipmentFor(container.getOwnerHandle());
|
||||
}
|
||||
|
||||
// Equipment slots are a core part of the inventory, so they will always be shown.
|
||||
return slot;
|
||||
}
|
||||
|
||||
// When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above).
|
||||
if (ownContainer && !(slot instanceof ContentDrop.SlotDrop)) {
|
||||
return new SlotViewOnly(container, index, x, y);
|
||||
}
|
||||
|
||||
if (viewOnly) {
|
||||
return SlotViewOnly.wrap(slot);
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull CraftInventoryView<OpenChestMenu<OpenInventory>> createBukkitEntity() {
|
||||
org.bukkit.inventory.Inventory bukkitInventory;
|
||||
if (viewOnly) {
|
||||
bukkitInventory = new OpenDummyInventory(container);
|
||||
} else if (ownContainer) {
|
||||
bukkitInventory = new OpenPlayerInventorySelf(container, offset);
|
||||
} else {
|
||||
bukkitInventory = container.getBukkitInventory();
|
||||
}
|
||||
|
||||
return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) {
|
||||
@Override
|
||||
public org.bukkit.inventory.ItemStack getItem(int index) {
|
||||
if (viewOnly || index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Slot slot = slots.get(index);
|
||||
return CraftItemStack.asCraftMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInTop(int rawSlot) {
|
||||
return rawSlot < topSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory(int rawSlot) {
|
||||
if (viewOnly) {
|
||||
return null;
|
||||
}
|
||||
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
|
||||
return null;
|
||||
}
|
||||
Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
|
||||
"Slot %s outside of inventory", rawSlot);
|
||||
if (rawSlot > topSize) {
|
||||
return getBottomInventory();
|
||||
}
|
||||
Slot slot = slots.get(rawSlot);
|
||||
if (slot.isFake()) {
|
||||
return null;
|
||||
}
|
||||
return getTopInventory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertSlot(int rawSlot) {
|
||||
if (viewOnly) {
|
||||
return InventoryView.OUTSIDE;
|
||||
}
|
||||
if (rawSlot < 0) {
|
||||
return rawSlot;
|
||||
}
|
||||
if (rawSlot < topSize) {
|
||||
Slot slot = slots.get(rawSlot);
|
||||
if (slot.isFake()) {
|
||||
return InventoryView.OUTSIDE;
|
||||
}
|
||||
return rawSlot;
|
||||
}
|
||||
|
||||
int slot = rawSlot - topSize;
|
||||
|
||||
if (slot >= 27) {
|
||||
slot -= 27;
|
||||
} else {
|
||||
slot += 9;
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InventoryType.SlotType getSlotType(int slot) {
|
||||
if (viewOnly || slot < 0) {
|
||||
return InventoryType.SlotType.OUTSIDE;
|
||||
}
|
||||
if (slot >= topSize) {
|
||||
slot -= topSize;
|
||||
if (slot >= 27) {
|
||||
return InventoryType.SlotType.QUICKBAR;
|
||||
}
|
||||
return InventoryType.SlotType.CONTAINER;
|
||||
}
|
||||
return OpenInventoryMenu.this.container.getSlotType(offset + slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countSlots() {
|
||||
return topSize + BOTTOM_INVENTORY_SIZE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
if (viewOnly) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
// See ChestMenu and InventoryMenu
|
||||
Slot slot = this.slots.get(index);
|
||||
|
||||
if (!slot.hasItem() || slot.isFake()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
ItemStack itemStack = slot.getItem();
|
||||
ItemStack originalStack = itemStack.copy();
|
||||
|
||||
if (index < topSize) {
|
||||
// If we're moving top to bottom, do a normal transfer.
|
||||
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else {
|
||||
EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack);
|
||||
boolean movedGear = switch (equipmentSlot) {
|
||||
// If this is gear, try to move it to the correct slot first.
|
||||
case OFFHAND, FEET, LEGS, CHEST, HEAD -> {
|
||||
// Locate the correct slot in the contents following the main inventory.
|
||||
for (int extra = container.getOwnerHandle().getInventory().items.size() - offset; extra < topSize; ++extra) {
|
||||
Slot extraSlot = getSlot(extra);
|
||||
if (extraSlot instanceof ContentEquipment.SlotEquipment equipSlot
|
||||
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
|
||||
// If we've found a matching slot, try to move to it.
|
||||
// If this succeeds, even partially, we will not attempt to move to other slots.
|
||||
// Otherwise, armor is already occupied, so we'll fall through to main inventory.
|
||||
yield this.moveItemStackTo(itemStack, extra, extra + 1, false);
|
||||
}
|
||||
}
|
||||
yield false;
|
||||
}
|
||||
// Non-gear gets no special treatment.
|
||||
default -> false;
|
||||
};
|
||||
|
||||
// If main inventory is not available, there's nowhere else to move.
|
||||
if (offset != 0) {
|
||||
if (!movedGear) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else {
|
||||
// If we didn't move to a gear slot, try to move to a main inventory slot.
|
||||
if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().items.size(), true)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (itemStack.isEmpty()) {
|
||||
slot.setByPlayer(ItemStack.EMPTY);
|
||||
} else {
|
||||
slot.setChanged();
|
||||
}
|
||||
|
||||
return originalStack;
|
||||
}
|
||||
|
||||
}
|
||||
+9
-16
@@ -13,7 +13,6 @@ 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.MenuProvider;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.ChatVisiblity;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -85,11 +84,6 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
// Ensure player has data
|
||||
if (!offline.hasPlayedBefore()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
|
||||
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
|
||||
|
||||
@@ -224,35 +218,34 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory) {
|
||||
public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
ServerPlayer player = getHandle(bukkitPlayer);
|
||||
|
||||
if (player.connection == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MenuProvider provider;
|
||||
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
|
||||
AbstractContainerMenu menu;
|
||||
Component title;
|
||||
if (inventory instanceof OpenInventory playerInv) {
|
||||
provider = playerInv;
|
||||
menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly);
|
||||
title = playerInv.getTitle(player);
|
||||
} else if (inventory instanceof OpenEnderChest enderChest) {
|
||||
provider = enderChest;
|
||||
title = enderChest.getDisplayName();
|
||||
menu = enderChest.createMenu(player, player.nextContainerCounter(), viewOnly);
|
||||
title = enderChest.getTitle();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
|
||||
AbstractContainerMenu menu = provider.createMenu(player.nextContainerCounter(), player.getInventory(), player);
|
||||
|
||||
// Should never happen, player is a ServerPlayer with an active connection.
|
||||
if (menu == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set up title (the whole reason we're not just using Player#openInventory(Inventory)).
|
||||
// Title can only be set once for a menu, and is set during the open process.
|
||||
// Set up title. Title can only be set once for a menu, and is set during the open process.
|
||||
// Further title changes are a hack where the client is sent a "new" inventory with the same ID,
|
||||
// resulting in a title change but no other state modifications (like cursor position).
|
||||
menu.setTitle(title);
|
||||
|
||||
menu = CraftEventFactory.callInventoryOpenEvent(player, menu, false);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
before_install:
|
||||
- sdk install java 21.0.4-tem
|
||||
- sdk use java 21.0.4-tem
|
||||
- mvn wrapper:wrapper -Dmaven=3.9.8
|
||||
+4
-4
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>openinvparent</artifactId>
|
||||
<groupId>com.lishid</groupId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>openinvplugin</artifactId>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>com.lishid</groupId>
|
||||
<artifactId>openinvadapter1_21_R1</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lishid</groupId>
|
||||
<artifactId>openinvadapter1_20_R4</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lishid</groupId>
|
||||
<artifactId>openinvadapter1_20_R3</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2023 lishid. All rights reserved.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv;
|
||||
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
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.Permissions;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Listener for inventory-related events to prevent modification of inventories where not allowed.
|
||||
*
|
||||
* @author Jikoo
|
||||
*/
|
||||
record InventoryListener(OpenInv plugin) implements Listener {
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onInventoryClose(@NotNull final InventoryCloseEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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(specialInventory);
|
||||
} else {
|
||||
specialInventory = InventoryAccess.getPlayerInventory(event.getInventory());
|
||||
if (specialInventory != null) {
|
||||
this.plugin.handleCloseInventory(specialInventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onInventoryClick(@NotNull final InventoryClickEvent event) {
|
||||
handleInventoryInteract(event);
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onInventoryDrag(@NotNull final InventoryDragEvent event) {
|
||||
handleInventoryInteract(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle common InventoryInteractEvent functions.
|
||||
*
|
||||
* @param event the InventoryInteractEvent
|
||||
*/
|
||||
private void handleInventoryInteract(@NotNull final InventoryInteractEvent event) {
|
||||
HumanEntity entity = event.getWhoClicked();
|
||||
|
||||
// Un-cancel spectator interactions.
|
||||
if (entity.getGameMode() == GameMode.SPECTATOR && Permissions.SPECTATE_CLICK.hasPermission(entity)) {
|
||||
event.setCancelled(false);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory inventory = event.getView().getTopInventory();
|
||||
ISpecialInventory backing = InventoryAccess.getInventory(inventory);
|
||||
|
||||
// Not a special inventory.
|
||||
if (backing == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions editSelf;
|
||||
Permissions editOther;
|
||||
if (backing instanceof ISpecialEnderChest) {
|
||||
editSelf = Permissions.ENDERCHEST_EDIT_SELF;
|
||||
editOther = Permissions.ENDERCHEST_EDIT_OTHER;
|
||||
} else if (backing instanceof ISpecialPlayerInventory) {
|
||||
editSelf = Permissions.INVENTORY_EDIT_SELF;
|
||||
editOther = Permissions.INVENTORY_EDIT_OTHER;
|
||||
} else {
|
||||
// Unknown implementation.
|
||||
return;
|
||||
}
|
||||
|
||||
if (Objects.equals(entity, backing.getPlayer())) {
|
||||
if (!editSelf.hasPermission(entity)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
} else {
|
||||
if (!editOther.hasPermission(entity)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv;
|
||||
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
record OfflineHandler(
|
||||
@NotNull BiFunction<Map<UUID, ? extends ISpecialInventory>, UUID, ISpecialInventory> fetch,
|
||||
@NotNull Consumer<@NotNull ISpecialInventory> handle) {
|
||||
|
||||
static final OfflineHandler REMOVE_AND_CLOSE = new OfflineHandler(
|
||||
Map::remove,
|
||||
inventory -> OpenInv.ejectViewers(inventory, viewer -> true)
|
||||
);
|
||||
|
||||
static final OfflineHandler REQUIRE_PERMISSIONS = new OfflineHandler(
|
||||
Map::get,
|
||||
inventory -> OpenInv.ejectViewers(inventory, viewer -> !Permissions.ACCESS_OFFLINE.hasPermission(viewer))
|
||||
);
|
||||
|
||||
}
|
||||
@@ -18,25 +18,29 @@ package com.lishid.openinv;
|
||||
|
||||
import com.github.jikoo.planarwrappers.util.version.BukkitVersions;
|
||||
import com.github.jikoo.planarwrappers.util.version.Version;
|
||||
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.OpenEvents;
|
||||
import com.lishid.openinv.command.ContainerSettingCommand;
|
||||
import com.lishid.openinv.command.OpenInvCommand;
|
||||
import com.lishid.openinv.command.SearchContainerCommand;
|
||||
import com.lishid.openinv.command.SearchEnchantCommand;
|
||||
import com.lishid.openinv.command.SearchInvCommand;
|
||||
import com.lishid.openinv.internal.IAnySilentContainer;
|
||||
import com.lishid.openinv.internal.ISpecialEnderChest;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
||||
import com.lishid.openinv.util.ConfigUpdater;
|
||||
import com.lishid.openinv.listener.ContainerListener;
|
||||
import com.lishid.openinv.listener.LegacyInventoryListener;
|
||||
import com.lishid.openinv.listener.ToggleListener;
|
||||
import com.lishid.openinv.util.AccessEqualMode;
|
||||
import com.lishid.openinv.util.InternalAccessor;
|
||||
import com.lishid.openinv.util.LangMigrator;
|
||||
import com.lishid.openinv.util.InventoryManager;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.StringMetric;
|
||||
import com.lishid.openinv.util.PlayerLoader;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import com.lishid.openinv.util.config.ConfigUpdater;
|
||||
import com.lishid.openinv.util.lang.LangMigrator;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@@ -44,51 +48,36 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
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;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Open other player's inventory
|
||||
*
|
||||
* @author lishid
|
||||
* The main class for OpenInv.
|
||||
*/
|
||||
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<>();
|
||||
|
||||
private InternalAccessor accessor;
|
||||
private Config config;
|
||||
private InventoryManager inventoryManager;
|
||||
private LanguageManager languageManager;
|
||||
private PlayerLoader playerLoader;
|
||||
private boolean isSpigot = false;
|
||||
private OfflineHandler offlineHandler;
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
config.reload(getConfig());
|
||||
languageManager.reload();
|
||||
this.offlineHandler = disableOfflineAccess() ? OfflineHandler.REMOVE_AND_CLOSE : OfflineHandler.REQUIRE_PERMISSIONS;
|
||||
if (this.accessor != null && this.accessor.isSupported()) {
|
||||
this.accessor.reload(this.getConfig());
|
||||
if (accessor != null && accessor.isSupported()) {
|
||||
accessor.reload(getConfig());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,27 +92,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (this.disableSaving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stream.concat(inventories.values().stream(), enderChests.values().stream())
|
||||
.map(inventory -> {
|
||||
// Cheat a bit - rather than stream twice, evict all viewers during remapping.
|
||||
ejectViewers(inventory, viewer -> true);
|
||||
if (inventory.getPlayer() instanceof Player player) {
|
||||
return player;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.forEach(player -> {
|
||||
if (!player.isOnline()) {
|
||||
player = accessor.getPlayerDataManager().inject(player);
|
||||
}
|
||||
player.saveData();
|
||||
});
|
||||
inventoryManager.evictAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,8 +104,17 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
||||
Path dataFolder = getDataFolder().toPath();
|
||||
new LangMigrator(dataFolder, dataFolder.resolve("locale"), getLogger()).migrate();
|
||||
|
||||
this.languageManager = new LanguageManager(this, "en");
|
||||
this.accessor = new InternalAccessor(getLogger(), languageManager);
|
||||
// Set up configurable features. Note that #reloadConfig is called on the first call to #getConfig!
|
||||
// Configuration values should not be accessed until after all of these have been set up.
|
||||
config = new Config();
|
||||
languageManager = new LanguageManager(this, "en");
|
||||
accessor = new InternalAccessor(getLogger(), languageManager);
|
||||
|
||||
// Perform initial config load.
|
||||
reloadConfig();
|
||||
|
||||
inventoryManager = new InventoryManager(this, config, accessor);
|
||||
playerLoader = new PlayerLoader(this, config, inventoryManager, accessor, getLogger());
|
||||
|
||||
try {
|
||||
Class.forName("org.bukkit.entity.Player$Spigot");
|
||||
@@ -152,22 +130,11 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
||||
// Update existing configuration. May require internal access.
|
||||
new ConfigUpdater(this).checkForUpdates();
|
||||
|
||||
// Get plugin manager
|
||||
PluginManager pm = this.getServer().getPluginManager();
|
||||
// Register listeners
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21))) {
|
||||
pm.registerEvents(new LegacyInventoryListener(this), this);
|
||||
} else {
|
||||
pm.registerEvents(new PlayerListener(this, languageManager), this);
|
||||
}
|
||||
pm.registerEvents(new InventoryListener(this), this);
|
||||
// Register relevant event listeners.
|
||||
registerEvents();
|
||||
|
||||
// Register commands to their executors
|
||||
this.setCommandExecutor(new OpenInvCommand(this, languageManager), "openinv", "openender");
|
||||
this.setCommandExecutor(new SearchContainerCommand(this, languageManager), "searchcontainer");
|
||||
this.setCommandExecutor(new SearchInvCommand(languageManager), "searchinv", "searchender");
|
||||
this.setCommandExecutor(new SearchEnchantCommand(languageManager), "searchenchant");
|
||||
this.setCommandExecutor(new ContainerSettingCommand(this, languageManager), "silentcontainer", "anycontainer");
|
||||
// Register commands to their executors.
|
||||
registerCommands();
|
||||
|
||||
} else {
|
||||
this.sendVersionError(this.getLogger()::warning);
|
||||
@@ -175,6 +142,30 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
||||
|
||||
}
|
||||
|
||||
private void registerEvents() {
|
||||
PluginManager pluginManager = this.getServer().getPluginManager();
|
||||
// Legacy: extra listener for permission handling and self-view issue prevention.
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21))) {
|
||||
pluginManager.registerEvents(new LegacyInventoryListener(this, config), this);
|
||||
}
|
||||
pluginManager.registerEvents(playerLoader, this);
|
||||
pluginManager.registerEvents(inventoryManager, this);
|
||||
pluginManager.registerEvents(new ContainerListener(accessor, languageManager), this);
|
||||
pluginManager.registerEvents(new ToggleListener(), this);
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
this.setCommandExecutor(new OpenInvCommand(this, config, inventoryManager, languageManager, playerLoader), "openinv", "openender");
|
||||
this.setCommandExecutor(new SearchContainerCommand(this, languageManager), "searchcontainer");
|
||||
this.setCommandExecutor(new SearchInvCommand(languageManager), "searchinv", "searchender");
|
||||
this.setCommandExecutor(new SearchEnchantCommand(languageManager), "searchenchant");
|
||||
|
||||
ContainerSettingCommand settingCommand = new ContainerSettingCommand(languageManager);
|
||||
for (PlayerToggle toggle : PlayerToggles.get()) {
|
||||
setCommandExecutor(settingCommand, toggle.getName().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
}
|
||||
|
||||
private void setCommandExecutor(@NotNull CommandExecutor executor, String @NotNull ... commands) {
|
||||
for (String commandName : commands) {
|
||||
PluginCommand command = this.getCommand(commandName);
|
||||
@@ -210,17 +201,17 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
||||
|
||||
@Override
|
||||
public boolean disableSaving() {
|
||||
return this.getConfig().getBoolean("settings.disable-saving", false);
|
||||
return config.isSaveDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableOfflineAccess() {
|
||||
return this.getConfig().getBoolean("settings.disable-offline-access", false);
|
||||
return config.isOfflineDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean noArgsOpensSelf() {
|
||||
return this.getConfig().getBoolean("settings.command.open.no-args-opens-self", false);
|
||||
return config.doesNoArgsOpenSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,379 +221,94 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
||||
|
||||
@Override
|
||||
public boolean getAnyContainerStatus(@NotNull final OfflinePlayer offline) {
|
||||
return this.getConfig().getBoolean("toggles.any-chest." + offline.getUniqueId(), false);
|
||||
return PlayerToggles.any().is(offline.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnyContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
|
||||
this.getConfig().set("toggles.any-chest." + offline.getUniqueId(), status);
|
||||
this.saveConfig();
|
||||
PlayerToggles.any().set(offline.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSilentContainerStatus(@NotNull final OfflinePlayer offline) {
|
||||
return this.getConfig().getBoolean("toggles.silent-chest." + offline.getUniqueId(), false);
|
||||
return PlayerToggles.silent().is(offline.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSilentContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
|
||||
this.getConfig().set("toggles.silent-chest." + offline.getUniqueId(), status);
|
||||
this.saveConfig();
|
||||
PlayerToggles.silent().set(offline.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online) {
|
||||
UUID key = player.getUniqueId();
|
||||
|
||||
if (this.enderChests.containsKey(key)) {
|
||||
return this.enderChests.get(key);
|
||||
}
|
||||
|
||||
ISpecialEnderChest inv = this.accessor.newSpecialEnderChest(player);
|
||||
this.enderChests.put(key, inv);
|
||||
return inv;
|
||||
return inventoryManager.getEnderChest(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online) {
|
||||
UUID key = player.getUniqueId();
|
||||
|
||||
if (this.inventories.containsKey(key)) {
|
||||
return this.inventories.get(key);
|
||||
}
|
||||
|
||||
ISpecialPlayerInventory inv = this.accessor.newSpecialPlayerInventory(player);
|
||||
this.inventories.put(key, inv);
|
||||
return inv;
|
||||
return inventoryManager.getInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
return this.accessor.getPlayerDataManager().openInventory(player, inventory);
|
||||
Permissions edit = null;
|
||||
HumanEntity target = inventory.getPlayer();
|
||||
boolean ownContainer = player.equals(target);
|
||||
if (inventory instanceof ISpecialPlayerInventory) {
|
||||
edit = ownContainer ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER;
|
||||
} else if (inventory instanceof ISpecialEnderChest) {
|
||||
edit = ownContainer ? Permissions.ENDERCHEST_EDIT_SELF : Permissions.ENDERCHEST_OPEN_OTHER;
|
||||
}
|
||||
|
||||
boolean viewOnly = edit != null && !edit.hasPermission(player);
|
||||
|
||||
if (!ownContainer && !viewOnly) {
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
// If the target doesn't have this access level...
|
||||
if (!target.hasPermission(permission)) {
|
||||
// If the viewer does have the access level, all good.
|
||||
if (player.hasPermission(permission)) {
|
||||
break;
|
||||
}
|
||||
// Otherwise check next access level.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the player doesn't have an equal access level or equal access is a denial, deny.
|
||||
if (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Since this is a tie, setting decides view state.
|
||||
if (config.getAccessEqualMode() == AccessEqualMode.VIEW) {
|
||||
viewOnly = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.accessor.openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerLoaded(@NotNull UUID playerUuid) {
|
||||
return this.inventories.containsKey(playerUuid) || this.enderChests.containsKey(playerUuid);
|
||||
return inventoryManager.getLoadedPlayer(playerUuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
UUID key = offline.getUniqueId();
|
||||
|
||||
if (this.inventories.containsKey(key)) {
|
||||
return (Player) this.inventories.get(key).getPlayer();
|
||||
}
|
||||
|
||||
if (this.enderChests.containsKey(key)) {
|
||||
return (Player) this.enderChests.get(key).getPlayer();
|
||||
}
|
||||
|
||||
Player player = offline.getPlayer();
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
if (disableOfflineAccess() || !this.isSupportedVersion()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
return this.accessor.getPlayerDataManager().loadPlayer(offline);
|
||||
}
|
||||
|
||||
Future<Player> future = Bukkit.getScheduler().callSyncMethod(this,
|
||||
() -> OpenInv.this.accessor.getPlayerDataManager().loadPlayer(offline));
|
||||
|
||||
try {
|
||||
player = future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
getLogger().log(Level.WARNING, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return player;
|
||||
return playerLoader.load(offline);
|
||||
}
|
||||
|
||||
@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;
|
||||
return playerLoader.match(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(@NotNull final OfflinePlayer offline) {
|
||||
setPlayerOffline(offline, OfflineHandler.REMOVE_AND_CLOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict all viewers lacking cross-world permissions when a {@link Player} changes worlds.
|
||||
*
|
||||
* @param player the Player
|
||||
*/
|
||||
void changeWorld(@NotNull Player player) {
|
||||
UUID key = player.getUniqueId();
|
||||
|
||||
if (this.inventories.containsKey(key)) {
|
||||
kickCrossWorldViewers(player, this.inventories.get(key));
|
||||
}
|
||||
|
||||
if (this.enderChests.containsKey(key)) {
|
||||
kickCrossWorldViewers(player, this.enderChests.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
private void kickCrossWorldViewers(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
ejectViewers(
|
||||
inventory,
|
||||
viewer ->
|
||||
!Permissions.ACCESS_CROSSWORLD.hasPermission(viewer)
|
||||
&& !Objects.equals(viewer.getWorld(), player.getWorld()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling a Player going offline.
|
||||
*
|
||||
* @param player the Player
|
||||
*/
|
||||
void setPlayerOffline(@NotNull Player player) {
|
||||
setPlayerOffline(player, offlineHandler);
|
||||
}
|
||||
|
||||
private void setPlayerOffline(@NotNull OfflinePlayer player, @NotNull OfflineHandler handler) {
|
||||
UUID key = player.getUniqueId();
|
||||
|
||||
setPlayerOffline(inventories, key, handler);
|
||||
setPlayerOffline(enderChests, key, handler);
|
||||
}
|
||||
|
||||
private void setPlayerOffline(
|
||||
@NotNull Map<UUID, ? extends ISpecialInventory> map,
|
||||
@NotNull UUID key,
|
||||
@NotNull OfflineHandler handler) {
|
||||
ISpecialInventory inventory = handler.fetch().apply(map, key);
|
||||
if (inventory == null) {
|
||||
return;
|
||||
}
|
||||
inventory.setPlayerOffline();
|
||||
if (!inventory.isInUse()) {
|
||||
map.remove(key);
|
||||
} else {
|
||||
handler.handle().accept(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);
|
||||
|
||||
if (loaded == null) {
|
||||
// Loaded inventory has already been removed. Removal will handle saving if necessary.
|
||||
return;
|
||||
}
|
||||
|
||||
// This should only be possible if a plugin is doing funky things with our inventories.
|
||||
if (loaded != inventory) {
|
||||
Inventory bukkitInventory = inventory.getBukkitInventory();
|
||||
// Just in case, respect contents of the inventory that was just used.
|
||||
loaded.getBukkitInventory().setContents(bukkitInventory.getContents());
|
||||
// We need to close this inventory to reduce risk of duplication bugs if the user is offline.
|
||||
// We don't want to risk recursively closing the same inventory repeatedly, so we schedule dumping viewers.
|
||||
// Worst case we schedule a couple redundant tasks if several people had the inventory open.
|
||||
if (inventory.isInUse()) {
|
||||
getServer().getScheduler().runTask(this, () -> ejectViewers(inventory, viewer -> true));
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule task to check in use status later this tick. Closing user is still in viewer list.
|
||||
getServer().getScheduler().runTask(this, () -> {
|
||||
if (loaded.isInUse()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!OpenEvents.saveCancelled(player, current)) {
|
||||
this.accessor.getPlayerDataManager().inject(player).saveData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling a Player coming online.
|
||||
*
|
||||
* @param player the Player
|
||||
* @throws IllegalStateException if the server version is unsupported
|
||||
*/
|
||||
void setPlayerOnline(@NotNull Player player) {
|
||||
setPlayerOnline(inventories, player);
|
||||
setPlayerOnline(enderChests, player);
|
||||
|
||||
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(
|
||||
@NotNull Map<UUID, ? extends ISpecialInventory> map,
|
||||
@NotNull Player player) {
|
||||
ISpecialInventory inventory = map.get(player.getUniqueId());
|
||||
|
||||
if (inventory == null) {
|
||||
// Inventory not open.
|
||||
return;
|
||||
}
|
||||
|
||||
inventory.setPlayerOnline(player);
|
||||
|
||||
// Eject viewers lacking permission.
|
||||
ejectViewers(
|
||||
inventory,
|
||||
viewer ->
|
||||
!Permissions.ACCESS_ONLINE.hasPermission(viewer)
|
||||
|| (!Permissions.ACCESS_CROSSWORLD.hasPermission(viewer)
|
||||
&& !Objects.equals(viewer.getWorld(), inventory.getPlayer().getWorld())));
|
||||
}
|
||||
|
||||
static void ejectViewers(@NotNull ISpecialInventory inventory, @NotNull Predicate<@NotNull HumanEntity> predicate) {
|
||||
Inventory bukkitInventory = inventory.getBukkitInventory();
|
||||
for (HumanEntity viewer : new ArrayList<>(bukkitInventory.getViewers())) {
|
||||
if (viewer.getUniqueId().equals(inventory.getPlayer().getUniqueId())
|
||||
&& !viewer.getOpenInventory().getTopInventory().equals(bukkitInventory)) {
|
||||
// Skip owner with other inventory open. They aren't actually a viewer.
|
||||
continue;
|
||||
}
|
||||
if (predicate.test(viewer)) {
|
||||
viewer.closeInventory();
|
||||
}
|
||||
}
|
||||
inventoryManager.unload(offline.getUniqueId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv;
|
||||
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event.Result;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class PlayerListener implements Listener {
|
||||
|
||||
private final @NotNull OpenInv plugin;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
PlayerListener(@NotNull OpenInv plugin, @NotNull LanguageManager lang) {
|
||||
this.plugin = plugin;
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
plugin.setPlayerOnline(event.getPlayer());
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
plugin.setPlayerOffline(event.getPlayer());
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onWorldChange(@NotNull PlayerChangedWorldEvent event) {
|
||||
plugin.changeWorld(event.getPlayer());
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
private void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
||||
|
||||
// Do not cancel 3rd party plugins' custom events
|
||||
if (!PlayerInteractEvent.class.equals(event.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getPlayer().isSneaking()
|
||||
|| event.useInteractedBlock() == Result.DENY || event.getClickedBlock() == null
|
||||
|| !plugin.getAnySilentContainer().isAnySilentContainer(event.getClickedBlock())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
boolean any = Permissions.CONTAINER_ANY.hasPermission(player) && plugin.getAnyContainerStatus(player);
|
||||
boolean needsAny = plugin.getAnySilentContainer().isAnyContainerNeeded(event.getClickedBlock());
|
||||
|
||||
if (!any && needsAny) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean silent = Permissions.CONTAINER_SILENT.hasPermission(player) && plugin.getSilentContainerStatus(player);
|
||||
|
||||
// If anycontainer or silentcontainer is active
|
||||
if (any || silent) {
|
||||
if (plugin.getAnySilentContainer().activateContainer(player, silent, event.getClickedBlock())) {
|
||||
if (silent && needsAny) {
|
||||
lang.sendSystemMessage(player, "messages.info.containerBlockedSilent");
|
||||
} else if (needsAny) {
|
||||
lang.sendSystemMessage(player, "messages.info.containerBlocked");
|
||||
} else if (silent) {
|
||||
lang.sendSystemMessage(player, "messages.info.containerSilent");
|
||||
}
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+28
-17
@@ -14,32 +14,31 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv.commands;
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.IOpenInv;
|
||||
import com.lishid.openinv.event.OpenEvents;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ContainerSettingCommand implements TabExecutor {
|
||||
|
||||
private final @NotNull IOpenInv plugin;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public ContainerSettingCommand(@NotNull IOpenInv plugin, @NotNull LanguageManager lang) {
|
||||
this.plugin = plugin;
|
||||
public ContainerSettingCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@@ -50,40 +49,52 @@ public class ContainerSettingCommand implements TabExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean any = command.getName().startsWith("any");
|
||||
Predicate<Player> getSetting = any ? plugin::getAnyContainerStatus : plugin::getSilentContainerStatus;
|
||||
BiConsumer<OfflinePlayer, Boolean> setSetting = any ? plugin::setAnyContainerStatus : plugin::setSilentContainerStatus;
|
||||
PlayerToggle toggle = PlayerToggles.get(command.getName());
|
||||
|
||||
// Shouldn't be possible.
|
||||
if (toggle == null) {
|
||||
JavaPlugin.getProvidingPlugin(getClass()).getLogger().warning("Command /" + command.getName() + " registered with no corresponding toggle!");
|
||||
return false;
|
||||
}
|
||||
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
if (args.length > 0) {
|
||||
args[0] = args[0].toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if (args[0].equals("on")) {
|
||||
setSetting.accept(player, true);
|
||||
set(toggle, playerId, true);
|
||||
} else if (args[0].equals("off")) {
|
||||
setSetting.accept(player, false);
|
||||
set(toggle, playerId, false);
|
||||
} else if (!args[0].equals("check")) {
|
||||
// Invalid argument, show usage.
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
setSetting.accept(player, !getSetting.test(player));
|
||||
set(toggle, playerId, !toggle.is(playerId));
|
||||
}
|
||||
|
||||
String onOff = lang.getLocalizedMessage(player, getSetting.test(player) ? "messages.info.on" : "messages.info.off");
|
||||
String onOff = lang.getLocalizedMessage(player, toggle.is(playerId) ? "messages.info.on" : "messages.info.off");
|
||||
if (onOff == null) {
|
||||
onOff = String.valueOf(getSetting.test(player));
|
||||
onOff = String.valueOf(toggle.is(playerId));
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.settingState",
|
||||
new Replacement("%setting%", any ? "AnyContainer" : "SilentContainer"),
|
||||
new Replacement("%setting%", toggle.getName()),
|
||||
new Replacement("%state%", onOff));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void set(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
|
||||
if (toggle.set(uuid, state)) {
|
||||
OpenEvents.notifyPlayerToggle(toggle, uuid, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!command.testPermissionSilent(sender) || args.length != 1) {
|
||||
+24
-9
@@ -14,12 +14,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv.commands;
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.OpenInv;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.util.AccessEqualMode;
|
||||
import com.lishid.openinv.util.InventoryManager;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.PlayerLoader;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
@@ -40,13 +44,24 @@ import java.util.logging.Level;
|
||||
public class OpenInvCommand implements TabExecutor {
|
||||
|
||||
private final @NotNull OpenInv plugin;
|
||||
private final @NotNull Config config;
|
||||
private final @NotNull InventoryManager manager;
|
||||
private final @NotNull LanguageManager lang;
|
||||
private final @NotNull PlayerLoader playerLoader;
|
||||
private final HashMap<Player, String> openInvHistory = new HashMap<>();
|
||||
private final HashMap<Player, String> openEnderHistory = new HashMap<>();
|
||||
|
||||
public OpenInvCommand(@NotNull OpenInv plugin, @NotNull LanguageManager lang) {
|
||||
public OpenInvCommand(
|
||||
@NotNull OpenInv plugin,
|
||||
@NotNull Config config,
|
||||
@NotNull InventoryManager manager,
|
||||
@NotNull LanguageManager lang,
|
||||
@NotNull PlayerLoader playerLoader) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.manager = manager;
|
||||
this.lang = lang;
|
||||
this.playerLoader = playerLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,7 +79,7 @@ public class OpenInvCommand implements TabExecutor {
|
||||
}
|
||||
|
||||
String noArgValue;
|
||||
if (plugin.noArgsOpensSelf()) {
|
||||
if (config.doesNoArgsOpenSelf()) {
|
||||
noArgValue = player.getUniqueId().toString();
|
||||
} else {
|
||||
// History management
|
||||
@@ -87,7 +102,7 @@ public class OpenInvCommand implements TabExecutor {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final OfflinePlayer offlinePlayer = OpenInvCommand.this.plugin.matchPlayer(name);
|
||||
final OfflinePlayer offlinePlayer = playerLoader.match(name);
|
||||
|
||||
if (offlinePlayer == null || (!offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline())) {
|
||||
lang.sendMessage(player, "messages.error.invalidPlayer");
|
||||
@@ -143,9 +158,9 @@ public class OpenInvCommand implements TabExecutor {
|
||||
boolean online = target.isOnline();
|
||||
|
||||
if (!online) {
|
||||
if (!plugin.disableOfflineAccess() && Permissions.ACCESS_OFFLINE.hasPermission(player)) {
|
||||
if (!config.isOfflineDisabled() && Permissions.ACCESS_OFFLINE.hasPermission(player)) {
|
||||
// Try loading the player's data
|
||||
onlineTarget = this.plugin.loadPlayer(target);
|
||||
onlineTarget = playerLoader.load(target);
|
||||
} else {
|
||||
lang.sendMessage(player, "messages.error.permissionPlayerOffline");
|
||||
return;
|
||||
@@ -183,7 +198,7 @@ public class OpenInvCommand implements TabExecutor {
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
if (onlineTarget.hasPermission(permission)
|
||||
&& !player.hasPermission(permission)) {
|
||||
&& (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY)) {
|
||||
lang.sendMessage(
|
||||
player,
|
||||
"messages.error.permissionExempt",
|
||||
@@ -203,7 +218,7 @@ public class OpenInvCommand implements TabExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.noArgsOpensSelf()) {
|
||||
if (!config.doesNoArgsOpenSelf()) {
|
||||
// Record the target
|
||||
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
|
||||
}
|
||||
@@ -211,7 +226,7 @@ public class OpenInvCommand implements TabExecutor {
|
||||
// Create the inventory
|
||||
final ISpecialInventory inv;
|
||||
try {
|
||||
inv = openinv ? this.plugin.getSpecialInventory(onlineTarget, online) : this.plugin.getSpecialEnderChest(onlineTarget, online);
|
||||
inv = openinv ? manager.getInventory(onlineTarget) : manager.getEnderChest(onlineTarget);
|
||||
} catch (Exception e) {
|
||||
lang.sendMessage(player, "messages.error.commandException");
|
||||
plugin.getLogger().log(Level.WARNING, "Unable to create ISpecialInventory", e);
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv.commands;
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
+3
-3
@@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv.commands;
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
@@ -46,9 +46,9 @@ import java.util.Locale;
|
||||
*/
|
||||
public class SearchEnchantCommand implements TabExecutor {
|
||||
|
||||
private final LanguageManager lang;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public SearchEnchantCommand(LanguageManager lang) {
|
||||
public SearchEnchantCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv.commands;
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
@@ -34,9 +34,9 @@ import java.util.List;
|
||||
|
||||
public class SearchInvCommand implements TabExecutor {
|
||||
|
||||
private final LanguageManager lang;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public SearchInvCommand(LanguageManager lang) {
|
||||
public SearchInvCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.lishid.openinv.listener;
|
||||
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.internal.ViewOnly;
|
||||
import com.lishid.openinv.util.InternalAccessor;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event.Result;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.inventory.InventoryInteractEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A listener managing AnyContainer, SilentContainer, and more.
|
||||
*/
|
||||
public class ContainerListener implements Listener {
|
||||
|
||||
private final @NotNull InternalAccessor accessor;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public ContainerListener(@NotNull InternalAccessor accessor, @NotNull LanguageManager lang) {
|
||||
this.accessor = accessor;
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
private void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
||||
// Ignore events from other plugins.
|
||||
if (!PlayerInteractEvent.class.equals(event.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK
|
||||
|| event.getPlayer().isSneaking()
|
||||
|| event.useInteractedBlock() == Result.DENY
|
||||
|| event.getClickedBlock() == null
|
||||
|| !accessor.getAnySilentContainer().isAnySilentContainer(event.getClickedBlock())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
UUID playerId = player.getUniqueId();
|
||||
boolean any = Permissions.CONTAINER_ANY.hasPermission(player) && PlayerToggles.any().is(playerId);
|
||||
boolean needsAny = accessor.getAnySilentContainer().isAnyContainerNeeded(event.getClickedBlock());
|
||||
|
||||
if (!any && needsAny) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean silent = Permissions.CONTAINER_SILENT.hasPermission(player) && PlayerToggles.silent().is(playerId);
|
||||
|
||||
// If anycontainer or silentcontainer is active
|
||||
if (any || silent) {
|
||||
if (accessor.getAnySilentContainer().activateContainer(player, silent, event.getClickedBlock())) {
|
||||
if (silent && needsAny) {
|
||||
lang.sendSystemMessage(player, "messages.info.containerBlockedSilent");
|
||||
} else if (needsAny) {
|
||||
lang.sendSystemMessage(player, "messages.info.containerBlocked");
|
||||
} else if (silent) {
|
||||
lang.sendSystemMessage(player, "messages.info.containerSilent");
|
||||
}
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onInventoryClose(@NotNull final InventoryCloseEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryHolder holder = event.getInventory().getHolder();
|
||||
if (PlayerToggles.silent().is(player.getUniqueId())
|
||||
&& holder != null
|
||||
&& this.accessor.getAnySilentContainer().isAnySilentContainer(holder)) {
|
||||
this.accessor.getAnySilentContainer().deactivateContainer(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onInventoryClick(@NotNull final InventoryClickEvent event) {
|
||||
handleInventoryInteract(event);
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onInventoryDrag(@NotNull final InventoryDragEvent event) {
|
||||
handleInventoryInteract(event);
|
||||
}
|
||||
|
||||
private void handleInventoryInteract(@NotNull final InventoryInteractEvent event) {
|
||||
HumanEntity entity = event.getWhoClicked();
|
||||
|
||||
// Un-cancel spectator interactions.
|
||||
if (entity.getGameMode() == GameMode.SPECTATOR && Permissions.SPECTATE_CLICK.hasPermission(entity)) {
|
||||
event.setCancelled(false);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory inventory = event.getView().getTopInventory();
|
||||
if (inventory instanceof ViewOnly) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+81
-66
@@ -1,12 +1,13 @@
|
||||
package com.lishid.openinv;
|
||||
package com.lishid.openinv.listener;
|
||||
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.internal.ISpecialEnderChest;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
||||
import com.lishid.openinv.util.AccessEqualMode;
|
||||
import com.lishid.openinv.util.InventoryAccess;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import org.bukkit.GameMode;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -14,50 +15,34 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryAction;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.inventory.InventoryInteractEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
record LegacyInventoryListener(OpenInv plugin) implements Listener {
|
||||
/**
|
||||
* A listener used to enable functionality and prevent issues on versions < 1.21.
|
||||
*/
|
||||
public class LegacyInventoryListener implements Listener {
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onInventoryClose(@NotNull final InventoryCloseEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
private final @NotNull Plugin plugin;
|
||||
private final @NotNull Config config;
|
||||
|
||||
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(specialInventory);
|
||||
} else {
|
||||
specialInventory = InventoryAccess.getPlayerInventory(event.getInventory());
|
||||
if (specialInventory != null) {
|
||||
this.plugin.handleCloseInventory(specialInventory);
|
||||
}
|
||||
}
|
||||
public LegacyInventoryListener(@NotNull Plugin plugin, @NotNull Config config) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
private void onInventoryClick(@NotNull final InventoryClickEvent event) {
|
||||
if (handleInventoryInteract(event)) {
|
||||
return;
|
||||
@@ -86,13 +71,18 @@ record LegacyInventoryListener(OpenInv plugin) implements Listener {
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
private void onInventoryDrag(@NotNull final InventoryDragEvent event) {
|
||||
if (handleInventoryInteract(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryView view = event.getView();
|
||||
|
||||
if (view.getCursor() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int topSize = view.getTopInventory().getSize();
|
||||
|
||||
// Get bottom inventory active slots as player inventory slots.
|
||||
@@ -123,15 +113,29 @@ record LegacyInventoryListener(OpenInv plugin) implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack cursor = event.getCursor();
|
||||
if (cursor != null) {
|
||||
cursor.setAmount(cursor.getAmount() + overlapLosses);
|
||||
} else {
|
||||
cursor = event.getOldCursor().clone();
|
||||
cursor.setAmount(overlapLosses);
|
||||
}
|
||||
final ItemStack lost = view.getCursor().clone();
|
||||
lost.setAmount(overlapLosses);
|
||||
|
||||
event.setCursor(cursor);
|
||||
// Re-add the lost items in the same tick after the event has completed.
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
InventoryView currentOpen = event.getWhoClicked().getOpenInventory();
|
||||
|
||||
if (!currentOpen.equals(view)) {
|
||||
event.getWhoClicked().getWorld().dropItem(event.getWhoClicked().getLocation(), lost).setPickupDelay(0);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack cursor = currentOpen.getCursor();
|
||||
|
||||
if (cursor == null) {
|
||||
currentOpen.setCursor(lost);
|
||||
} else if (lost.isSimilar(cursor)) {
|
||||
cursor.setAmount(cursor.getAmount() + lost.getAmount());
|
||||
currentOpen.setCursor(cursor);
|
||||
} else {
|
||||
event.getWhoClicked().getWorld().dropItem(event.getWhoClicked().getLocation(), lost).setPickupDelay(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int getCountDiff(@Nullable ItemStack original, @NotNull ItemStack result) {
|
||||
@@ -149,44 +153,55 @@ record LegacyInventoryListener(OpenInv plugin) implements Listener {
|
||||
* @return true unless the top inventory is the holder's own inventory
|
||||
*/
|
||||
private boolean handleInventoryInteract(@NotNull final InventoryInteractEvent event) {
|
||||
HumanEntity entity = event.getWhoClicked();
|
||||
|
||||
// Un-cancel spectator interactions.
|
||||
if (Permissions.SPECTATE_CLICK.hasPermission(entity) && entity.getGameMode() == GameMode.SPECTATOR) {
|
||||
event.setCancelled(false);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return true;
|
||||
}
|
||||
HumanEntity viewer = event.getWhoClicked();
|
||||
|
||||
Inventory inventory = event.getView().getTopInventory();
|
||||
ISpecialInventory backing = InventoryAccess.getInventory(inventory);
|
||||
Permissions editSelf;
|
||||
Permissions editOther;
|
||||
if (backing instanceof ISpecialEnderChest) {
|
||||
editSelf = Permissions.ENDERCHEST_EDIT_SELF;
|
||||
editOther = Permissions.ENDERCHEST_EDIT_OTHER;
|
||||
} else if (backing instanceof ISpecialPlayerInventory) {
|
||||
editSelf = Permissions.INVENTORY_EDIT_SELF;
|
||||
editOther = Permissions.INVENTORY_EDIT_OTHER;
|
||||
|
||||
if (backing == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Permissions edit;
|
||||
HumanEntity target = backing.getPlayer();
|
||||
boolean ownContainer = viewer.equals(target);
|
||||
if (backing instanceof ISpecialPlayerInventory) {
|
||||
edit = ownContainer ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER;
|
||||
} else if (backing instanceof ISpecialEnderChest) {
|
||||
edit = ownContainer ? Permissions.ENDERCHEST_EDIT_SELF : Permissions.ENDERCHEST_OPEN_OTHER;
|
||||
} else {
|
||||
// Unknown implementation.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Objects.equals(entity, backing.getPlayer())) {
|
||||
if (!editSelf.hasPermission(entity)) {
|
||||
event.setCancelled(true);
|
||||
return true;
|
||||
}
|
||||
return !(backing instanceof ISpecialPlayerInventory);
|
||||
} else {
|
||||
if (!editOther.hasPermission(entity)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
if (!edit.hasPermission(viewer)) {
|
||||
event.setCancelled(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If access ties aren't view-only mode, don't bother with permission checks.
|
||||
if (config.getAccessEqualMode() != AccessEqualMode.VIEW) {
|
||||
return !ownContainer || !(backing instanceof ISpecialPlayerInventory);
|
||||
}
|
||||
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
// If the target doesn't have this access level...
|
||||
if (!target.hasPermission(permission)) {
|
||||
// If the viewer does have the access level, all good.
|
||||
if (viewer.hasPermission(permission)) {
|
||||
break;
|
||||
}
|
||||
// Otherwise check next access level.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Either the viewer lacks access (which shouldn't be possible) or this is a tie. View-only.
|
||||
event.setCancelled(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return !ownContainer || !(backing instanceof ISpecialPlayerInventory);
|
||||
}
|
||||
|
||||
private static int convertToPlayerSlot(InventoryView view, int rawSlot) {
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.lishid.openinv.listener;
|
||||
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ToggleListener implements Listener {
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
UUID playerId = event.getPlayer().getUniqueId();
|
||||
PlayerToggles.any().set(playerId, false);
|
||||
PlayerToggles.silent().set(playerId, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.lishid.openinv.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum AccessEqualMode {
|
||||
|
||||
DENY, ALLOW, VIEW;
|
||||
|
||||
public static @NotNull AccessEqualMode of(@Nullable String value) {
|
||||
if (value == null) {
|
||||
return VIEW;
|
||||
}
|
||||
return switch (value.toLowerCase(Locale.ENGLISH)) {
|
||||
case "deny", "false" -> DENY;
|
||||
case "allow", "true" -> ALLOW;
|
||||
default -> VIEW;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,162 +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.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
public record ConfigUpdater(OpenInv plugin) {
|
||||
|
||||
public void checkForUpdates() {
|
||||
final int version = plugin.getConfig().getInt("config-version", 1);
|
||||
ConfigurationSection defaults = plugin.getConfig().getDefaults();
|
||||
if (defaults == null || version >= defaults.getInt("config-version")) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Configuration update found! Performing update...");
|
||||
|
||||
// Backup the old config file
|
||||
try {
|
||||
plugin.getConfig().save(new File(plugin.getDataFolder(), "config_old.yml"));
|
||||
plugin.getLogger().info("Backed up config.yml to config_old.yml before updating.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Could not back up config.yml before updating!");
|
||||
}
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
if (version < 2) {
|
||||
updateConfig1To2();
|
||||
}
|
||||
if (version < 3) {
|
||||
updateConfig2To3();
|
||||
}
|
||||
if (version < 4) {
|
||||
updateConfig3To4();
|
||||
}
|
||||
if (version < 5) {
|
||||
updateConfig4To5();
|
||||
}
|
||||
if (version < 6) {
|
||||
updateConfig5To6();
|
||||
}
|
||||
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
plugin.saveConfig();
|
||||
plugin.getLogger().info("Configuration update complete!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
plugin.getConfig().set("config-version", 5);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateConfig3To4() {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
plugin.getConfig().set("notify", null);
|
||||
plugin.getConfig().set("settings.locale", "en_US");
|
||||
plugin.getConfig().set("config-version", 4);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateConfig2To3() {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
plugin.getConfig().set("config-version", 3);
|
||||
plugin.getConfig().set("items.open-inv", null);
|
||||
plugin.getConfig().set("ItemOpenInv", null);
|
||||
plugin.getConfig().set("toggles.items.open-inv", null);
|
||||
plugin.getConfig().set("settings.disable-saving",
|
||||
plugin.getConfig().getBoolean("DisableSaving", false));
|
||||
plugin.getConfig().set("DisableSaving", null);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateConfig1To2() {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
// Get the old config settings
|
||||
boolean notifySilentChest = plugin.getConfig().getBoolean("NotifySilentChest", true);
|
||||
boolean notifyAnyChest = plugin.getConfig().getBoolean("NotifyAnyChest", true);
|
||||
plugin.getConfig().set("ItemOpenInvItemID", null);
|
||||
plugin.getConfig().set("NotifySilentChest", null);
|
||||
plugin.getConfig().set("NotifyAnyChest", null);
|
||||
plugin.getConfig().set("config-version", 2);
|
||||
plugin.getConfig().set("notify.any-chest", notifyAnyChest);
|
||||
plugin.getConfig().set("notify.silent-chest", notifySilentChest);
|
||||
});
|
||||
|
||||
updateToggles("AnyChest", "toggles.any-chest");
|
||||
updateToggles("SilentChest", "toggles.silent-chest");
|
||||
}
|
||||
|
||||
private void updateToggles(final String sectionName, final String newSectionName) {
|
||||
ConfigurationSection section = plugin.getConfig().getConfigurationSection(sectionName);
|
||||
// Ensure section exists
|
||||
if (section == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> keys = section.getKeys(false);
|
||||
|
||||
// Ensure section has content
|
||||
if (keys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, Boolean> toggles = new HashMap<>();
|
||||
|
||||
for (String playerName : keys) {
|
||||
OfflinePlayer player = plugin.matchPlayer(playerName);
|
||||
if (player != null) {
|
||||
toggles.put(player.getUniqueId().toString(), section.getBoolean(playerName + ".toggle", false));
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
// Wipe old ConfigurationSection
|
||||
plugin.getConfig().set(sectionName, null);
|
||||
|
||||
// Prepare new ConfigurationSection
|
||||
ConfigurationSection newSection = plugin.getConfig().getConfigurationSection(newSectionName);
|
||||
if (newSection == null) {
|
||||
newSection = plugin.getConfig().createSection(newSectionName);
|
||||
}
|
||||
// Set new values
|
||||
for (Map.Entry<String, Boolean> entry : toggles.entrySet()) {
|
||||
newSection.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,11 +21,13 @@ import com.github.jikoo.planarwrappers.util.version.Version;
|
||||
import com.lishid.openinv.internal.Accessor;
|
||||
import com.lishid.openinv.internal.IAnySilentContainer;
|
||||
import com.lishid.openinv.internal.ISpecialEnderChest;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
||||
import com.lishid.openinv.internal.PlayerManager;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -38,7 +40,8 @@ public class InternalAccessor {
|
||||
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
|
||||
try {
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21))) {
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 1))
|
||||
|| BukkitVersions.MINECRAFT.equals(Version.of(1, 21))) {
|
||||
internal = new com.lishid.openinv.internal.v1_21_R1.InternalAccessor(logger, lang);
|
||||
} else if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 4))) {
|
||||
internal = new com.lishid.openinv.internal.v1_20_R3.InternalAccessor(logger, lang);
|
||||
@@ -121,32 +124,6 @@ public class InternalAccessor {
|
||||
return "https://github.com/Jikoo/OpenInv/releases";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the IAnySilentContainer implementation for the current server version.
|
||||
*
|
||||
* @return the IAnySilentContainer
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
public @NotNull IAnySilentContainer getAnySilentContainer() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getAnySilentContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the IPlayerDataManager implementation for the current server version.
|
||||
*
|
||||
* @return the IPlayerDataManager
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
public @NotNull PlayerManager getPlayerDataManager() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload internal features.
|
||||
*/
|
||||
@@ -174,13 +151,46 @@ public class InternalAccessor {
|
||||
return internal != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of the IAnySilentContainer implementation for the current server version.
|
||||
*
|
||||
* @return the IAnySilentContainer
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
public @NotNull IAnySilentContainer getAnySilentContainer() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getAnySilentContainer();
|
||||
}
|
||||
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager().openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of the IPlayerDataManager implementation for the current server version.
|
||||
*
|
||||
* @return the IPlayerDataManager
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
@NotNull PlayerManager getPlayerDataManager() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the ISpecialEnderChest implementation for the given Player.
|
||||
*
|
||||
* @param player the Player
|
||||
* @return the ISpecialEnderChest created
|
||||
*/
|
||||
public ISpecialEnderChest newSpecialEnderChest(final Player player) {
|
||||
ISpecialEnderChest createEnderChest(final Player player) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
@@ -193,7 +203,7 @@ public class InternalAccessor {
|
||||
* @param player the Player
|
||||
* @return the ISpecialPlayerInventory created
|
||||
*/
|
||||
public ISpecialPlayerInventory newSpecialPlayerInventory(final Player player) {
|
||||
ISpecialPlayerInventory createInventory(final Player player) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
package com.lishid.openinv.util;
|
||||
|
||||
import com.github.jikoo.planarwrappers.util.version.BukkitVersions;
|
||||
import com.github.jikoo.planarwrappers.util.version.Version;
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.event.OpenEvents;
|
||||
import com.lishid.openinv.internal.ISpecialEnderChest;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A manager for special inventories. Delegates creation and tracks copies in use.
|
||||
*/
|
||||
public class InventoryManager implements Listener {
|
||||
|
||||
private final Map<UUID, ISpecialPlayerInventory> inventories = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, ISpecialEnderChest> enderChests = new ConcurrentHashMap<>();
|
||||
private final Set<UUID> expectedCloses = new HashSet<>();
|
||||
private final @NotNull Plugin plugin;
|
||||
private final @NotNull Config config;
|
||||
private final @NotNull InternalAccessor accessor;
|
||||
|
||||
public InventoryManager(@NotNull Plugin plugin, @NotNull Config config, @NotNull InternalAccessor accessor) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.accessor = accessor;
|
||||
}
|
||||
|
||||
public void evictAll() {
|
||||
Stream.concat(inventories.values().stream(), enderChests.values().stream())
|
||||
.map(inventory -> {
|
||||
// Rather than iterate twice, evict all viewers during remapping.
|
||||
for (HumanEntity viewer : List.copyOf(inventory.getBukkitInventory().getViewers())) {
|
||||
expectedCloses.add(viewer.getUniqueId());
|
||||
viewer.closeInventory();
|
||||
}
|
||||
// If saving is prevented, return a null value for the player to save.
|
||||
if (config.isSaveDisabled() || OpenEvents.saveCancelled(inventory)) {
|
||||
return null;
|
||||
}
|
||||
if (inventory.getPlayer() instanceof Player player) {
|
||||
return player;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.forEach(player -> {
|
||||
if (!player.isOnline()) {
|
||||
accessor.getPlayerDataManager().inject(player).saveData();
|
||||
}
|
||||
});
|
||||
inventories.clear();
|
||||
enderChests.clear();
|
||||
expectedCloses.clear();
|
||||
}
|
||||
|
||||
public @NotNull ISpecialPlayerInventory getInventory(@NotNull Player player) {
|
||||
return inventories.computeIfAbsent(player.getUniqueId(), uuid -> accessor.createInventory(player));
|
||||
}
|
||||
|
||||
public @NotNull ISpecialEnderChest getEnderChest(@NotNull Player player) {
|
||||
return enderChests.computeIfAbsent(player.getUniqueId(), uuid -> accessor.createEnderChest(player));
|
||||
}
|
||||
|
||||
public @Nullable Player getLoadedPlayer(@NotNull UUID uuid) {
|
||||
ISpecialInventory inUse = inventories.get(uuid);
|
||||
if (inUse != null) {
|
||||
return (Player) inUse.getPlayer();
|
||||
}
|
||||
inUse = enderChests.get(uuid);
|
||||
if (inUse != null) {
|
||||
return (Player) inUse.getPlayer();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void unload(@NotNull UUID uuid) {
|
||||
inventories.computeIfPresent(uuid, this::remove);
|
||||
enderChests.computeIfPresent(uuid, this::remove);
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
consumeLoaded(event.getPlayer().getUniqueId(), inventory -> {
|
||||
inventory.setPlayerOnline(event.getPlayer());
|
||||
checkViewerAccess(inventory, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
consumeLoaded(event.getPlayer().getUniqueId(), inventory -> {
|
||||
inventory.setPlayerOffline();
|
||||
checkViewerAccess(inventory, false);
|
||||
});
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onWorldChanged(@NotNull PlayerChangedWorldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
consumeLoaded(player.getUniqueId(), inventory -> checkViewerAccess(inventory, player.isOnline()));
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void onInventoryClose(@NotNull InventoryCloseEvent event) {
|
||||
ISpecialInventory inventory = InventoryAccess.getInventory(event.getInventory());
|
||||
|
||||
// If this is not an ISpecialInventory or the inventory was closed elsewhere internally, don't handle.
|
||||
if (inventory == null || expectedCloses.remove(event.getPlayer().getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the active ISpecialInventory of this type.
|
||||
Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
|
||||
UUID key = inventory.getPlayer().getUniqueId();
|
||||
ISpecialInventory loaded = map.get(key);
|
||||
|
||||
// If there is no loaded inventory, it has already been removed and saved.
|
||||
if (loaded == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This should only be possible if a plugin is going to extreme lengths to mess with our inventories.
|
||||
if (loaded != inventory) {
|
||||
// Immediately remove affected inventory, then dump all viewers. We don't want to risk duplication bugs.
|
||||
map.remove(key);
|
||||
remove(key, loaded);
|
||||
remove(key, inventory);
|
||||
// The loaded one is "correct" as far as we're concerned, so save that.
|
||||
save(loaded);
|
||||
}
|
||||
|
||||
// Schedule task to check in use status later this tick. Closing user is still in viewer list.
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
if (loaded.isInUse()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-fetch from map to reduce odds of a duplicate save.
|
||||
ISpecialInventory current = map.remove(key);
|
||||
|
||||
if (current != null) {
|
||||
save(current);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
private void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
||||
ISpecialInventory inventory = InventoryAccess.getInventory(event.getInventory());
|
||||
if (inventory == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
|
||||
UUID key = inventory.getPlayer().getUniqueId();
|
||||
ISpecialInventory loaded = map.get(key);
|
||||
|
||||
if (!inventory.equals(loaded)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().log(
|
||||
Level.WARNING,
|
||||
"Prevented a plugin from opening an untracked ISpecialInventory!",
|
||||
new Throwable("Untracked ISpecialInventory"));
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends ISpecialInventory> void checkViewerAccess(@NotNull T inventory, boolean online) {
|
||||
|
||||
Player owner = (Player) inventory.getPlayer();
|
||||
Permissions connectedState = online ? Permissions.ACCESS_ONLINE : Permissions.ACCESS_OFFLINE;
|
||||
boolean alwaysDenied = !online && config.isOfflineDisabled();
|
||||
|
||||
// Copy viewers so we don't modify the list we're iterating over when closing inventories.
|
||||
List<HumanEntity> viewers = new ArrayList<>(inventory.getBukkitInventory().getViewers());
|
||||
// Legacy: Owner is always a viewer of own inventory.
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21))) {
|
||||
if (inventory instanceof ISpecialPlayerInventory) {
|
||||
Inventory active = owner.getOpenInventory().getTopInventory();
|
||||
if (!active.equals(inventory.getBukkitInventory())) {
|
||||
viewers.remove(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (HumanEntity viewer : viewers) {
|
||||
if (alwaysDenied
|
||||
|| !connectedState.hasPermission(viewer)
|
||||
|| (!Objects.equals(owner.getWorld(), viewer.getWorld()) && !Permissions.ACCESS_CROSSWORLD.hasPermission(viewer))) {
|
||||
expectedCloses.add(viewer.getUniqueId());
|
||||
viewer.closeInventory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void consumeLoaded(@NotNull UUID key, @NotNull Consumer<@NotNull ISpecialInventory> consumer) {
|
||||
boolean saved = consumeLoaded(inventories, key, false, consumer);
|
||||
consumeLoaded(enderChests, key, saved, consumer);
|
||||
}
|
||||
|
||||
private <T extends ISpecialInventory> boolean consumeLoaded(
|
||||
@NotNull Map<UUID, T> map,
|
||||
@NotNull UUID key,
|
||||
boolean saved,
|
||||
@NotNull Consumer<@NotNull ISpecialInventory> consumer) {
|
||||
T inventory = map.get(key);
|
||||
|
||||
if (inventory == null) {
|
||||
return saved;
|
||||
}
|
||||
|
||||
consumer.accept(inventory);
|
||||
if (!inventory.isInUse()) {
|
||||
map.remove(key);
|
||||
|
||||
if (!saved) {
|
||||
save(inventory);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
private void save(@NotNull ISpecialInventory inventory) {
|
||||
if (config.isSaveDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) inventory.getPlayer();
|
||||
|
||||
if (!player.isOnline() && !OpenEvents.saveCancelled(inventory)) {
|
||||
accessor.getPlayerDataManager().inject(player).saveData();
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("_, _ -> null")
|
||||
private <T extends ISpecialInventory> @Nullable T remove(@NotNull UUID key, @NotNull T inventory) {
|
||||
for (HumanEntity viewer : List.copyOf(inventory.getBukkitInventory().getViewers())) {
|
||||
expectedCloses.add(viewer.getUniqueId());
|
||||
viewer.closeInventory();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.lishid.openinv.util;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A utility for looking up and loading players.
|
||||
*/
|
||||
public class PlayerLoader implements Listener {
|
||||
|
||||
private final @NotNull Plugin plugin;
|
||||
private final @NotNull Config config;
|
||||
private final @NotNull InventoryManager inventoryManager;
|
||||
private final @NotNull InternalAccessor internalAccessor;
|
||||
private final @NotNull Logger logger;
|
||||
private final @NotNull Cache<String, PlayerProfile> lookupCache;
|
||||
|
||||
public PlayerLoader(
|
||||
@NotNull Plugin plugin,
|
||||
@NotNull Config config,
|
||||
@NotNull InventoryManager inventoryManager,
|
||||
@NotNull InternalAccessor internalAccessor,
|
||||
@NotNull Logger logger) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.inventoryManager = inventoryManager;
|
||||
this.internalAccessor = internalAccessor;
|
||||
this.logger = logger;
|
||||
this.lookupCache = CacheBuilder.newBuilder().maximumSize(20).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for
|
||||
* the server is not loaded, this will return {@code null}.
|
||||
*
|
||||
* @param offline the {@code OfflinePlayer} to load a {@code Player} for
|
||||
* @return the loaded {@code Player}
|
||||
* @throws IllegalStateException if the server version is unsupported
|
||||
*/
|
||||
public @Nullable Player load(@NotNull OfflinePlayer offline) {
|
||||
UUID key = offline.getUniqueId();
|
||||
|
||||
Player player = offline.getPlayer();
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
player = inventoryManager.getLoadedPlayer(key);
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
if (config.isOfflineDisabled() || !internalAccessor.isSupported()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
return internalAccessor.getPlayerDataManager().loadPlayer(offline);
|
||||
}
|
||||
|
||||
Future<Player> future = Bukkit.getScheduler().callSyncMethod(plugin,
|
||||
() -> internalAccessor.getPlayerDataManager().loadPlayer(offline));
|
||||
|
||||
try {
|
||||
player = future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.log(Level.WARNING, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
public @Nullable OfflinePlayer match(@NotNull String name) {
|
||||
// Warn if called on the main thread - if we resort to searching offline players, this may take several seconds.
|
||||
if (Bukkit.getServer().isPrimaryThread()) {
|
||||
logger.warning("Call to PlayerSearchCache#matchPlayer made on the main thread!");
|
||||
logger.warning("This can cause the server to hang, potentially severely.");
|
||||
logger.log(Level.WARNING, "Current stack trace", new Throwable("Current stack trace"));
|
||||
}
|
||||
|
||||
OfflinePlayer player;
|
||||
|
||||
try {
|
||||
UUID uuid = UUID.fromString(name);
|
||||
player = Bukkit.getOfflinePlayer(uuid);
|
||||
// Ensure player is an existing player.
|
||||
if (player.hasPlayedBefore() || player.isOnline()) {
|
||||
return player;
|
||||
}
|
||||
// Return null otherwise.
|
||||
return null;
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// Not a UUID
|
||||
}
|
||||
|
||||
// Exact online match first.
|
||||
player = Bukkit.getServer().getPlayerExact(name);
|
||||
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
// Cached offline match.
|
||||
PlayerProfile cachedResult = lookupCache.getIfPresent(name);
|
||||
if (cachedResult != null && cachedResult.getUniqueId() != null) {
|
||||
player = Bukkit.getOfflinePlayer(cachedResult.getUniqueId());
|
||||
// Ensure player is an existing player.
|
||||
if (player.hasPlayedBefore() || player.isOnline()) {
|
||||
return player;
|
||||
}
|
||||
// Return null otherwise.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exact offline match second - ensure offline access works when matchable users are online.
|
||||
player = Bukkit.getServer().getOfflinePlayer(name);
|
||||
|
||||
if (player.hasPlayedBefore()) {
|
||||
lookupCache.put(name, player.getPlayerProfile());
|
||||
return player;
|
||||
}
|
||||
|
||||
// Inexact online match.
|
||||
player = Bukkit.getServer().getPlayer(name);
|
||||
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
// Finally, inexact offline match.
|
||||
float bestMatch = 0;
|
||||
for (OfflinePlayer offline : Bukkit.getServer().getOfflinePlayers()) {
|
||||
if (offline.getName() == null) {
|
||||
// Loaded by UUID only, name has never been looked up.
|
||||
continue;
|
||||
}
|
||||
|
||||
float currentMatch = StringMetric.compareJaroWinkler(name, offline.getName());
|
||||
|
||||
if (currentMatch == 1.0F) {
|
||||
return offline;
|
||||
}
|
||||
|
||||
if (currentMatch > bestMatch) {
|
||||
bestMatch = currentMatch;
|
||||
player = offline;
|
||||
}
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
// If a match was found, store it.
|
||||
lookupCache.put(name, player.getPlayerProfile());
|
||||
return player;
|
||||
}
|
||||
|
||||
// No players have ever joined the server.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler
|
||||
private void updateMatches(@NotNull PlayerJoinEvent event) {
|
||||
// If player is not new, any cached values are valid.
|
||||
if (event.getPlayer().hasPlayedBefore()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// New player may have a name that already points to someone else in lookup cache.
|
||||
String name = event.getPlayer().getName();
|
||||
lookupCache.invalidate(name);
|
||||
|
||||
// If the cache is empty, nothing to do. Don't hit scheduler.
|
||||
if (lookupCache.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> {
|
||||
Iterator<Map.Entry<String, PlayerProfile>> iterator = lookupCache.asMap().entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, PlayerProfile> entry = iterator.next();
|
||||
String oldMatch = entry.getValue().getName();
|
||||
|
||||
// Shouldn't be possible - all profiles should be complete.
|
||||
if (oldMatch == null) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
String lookup = entry.getKey();
|
||||
float oldMatchScore = StringMetric.compareJaroWinkler(lookup, oldMatch);
|
||||
float newMatchScore = StringMetric.compareJaroWinkler(lookup, name);
|
||||
|
||||
// If new match exceeds old match, delete old match.
|
||||
if (newMatchScore > oldMatchScore) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}, 7L);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.lishid.openinv.util.config;
|
||||
|
||||
import com.lishid.openinv.util.AccessEqualMode;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Config {
|
||||
|
||||
private @NotNull Configuration root;
|
||||
private @Nullable AccessEqualMode accessEqualMode;
|
||||
|
||||
public Config() {
|
||||
root = new MemoryConfiguration();
|
||||
}
|
||||
|
||||
public void reload(@NotNull Configuration configuration) {
|
||||
root = configuration;
|
||||
accessEqualMode = null;
|
||||
}
|
||||
|
||||
public boolean isSaveDisabled() {
|
||||
return root.getBoolean("settings.disable-saving", false);
|
||||
}
|
||||
|
||||
public boolean isOfflineDisabled() {
|
||||
return root.getBoolean("settings.disable-offline-access", false);
|
||||
}
|
||||
|
||||
public boolean doesNoArgsOpenSelf() {
|
||||
return root.getBoolean("settings.command.open.no-args-opens-self", false);
|
||||
}
|
||||
|
||||
public @NotNull AccessEqualMode getAccessEqualMode() {
|
||||
if (accessEqualMode == null) {
|
||||
accessEqualMode = AccessEqualMode.of(root.getString("settings.equal-access"));
|
||||
}
|
||||
|
||||
return accessEqualMode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public record ConfigUpdater(@NotNull Plugin plugin) {
|
||||
|
||||
public void checkForUpdates() {
|
||||
final int version = plugin.getConfig().getInt("config-version", 1);
|
||||
ConfigurationSection defaults = plugin.getConfig().getDefaults();
|
||||
if (defaults == null || version >= defaults.getInt("config-version")) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Configuration update found! Performing update...");
|
||||
|
||||
// Backup the old config file
|
||||
try {
|
||||
plugin.getConfig().save(new File(plugin.getDataFolder(), "config_old.yml"));
|
||||
plugin.getLogger().info("Backed up config.yml to config_old.yml before updating.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Could not back up config.yml before updating!");
|
||||
}
|
||||
|
||||
if (version < 2) {
|
||||
updateConfig1To2();
|
||||
}
|
||||
if (version < 3) {
|
||||
updateConfig2To3();
|
||||
}
|
||||
if (version < 4) {
|
||||
updateConfig3To4();
|
||||
}
|
||||
if (version < 5) {
|
||||
updateConfig4To5();
|
||||
}
|
||||
if (version < 6) {
|
||||
updateConfig5To6();
|
||||
}
|
||||
if (version < 7) {
|
||||
updateConfig6To7();
|
||||
}
|
||||
if (version < 8) {
|
||||
updateConfig7To8();
|
||||
}
|
||||
|
||||
plugin.saveConfig();
|
||||
plugin.getLogger().info("Configuration update complete!");
|
||||
}
|
||||
|
||||
private void updateConfig7To8() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.equal-access", "view");
|
||||
config.set("config-version", 8);
|
||||
}
|
||||
|
||||
private void updateConfig6To7() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("toggles", null);
|
||||
String consoleLocale = config.getString("settings.locale", "en");
|
||||
if (consoleLocale.isBlank() || consoleLocale.equalsIgnoreCase("en_us")) {
|
||||
consoleLocale = "en";
|
||||
}
|
||||
config.set("settings.console-locale", consoleLocale);
|
||||
config.set("settings.locale", null);
|
||||
config.set("config-version", 7);
|
||||
}
|
||||
|
||||
private void updateConfig5To6() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.command.open.no-args-opens-self", false);
|
||||
config.set("settings.command.searchcontainer.max-radius", 10);
|
||||
config.set("config-version", 6);
|
||||
}
|
||||
|
||||
private void updateConfig4To5() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.disable-offline-access", false);
|
||||
config.set("config-version", 5);
|
||||
}
|
||||
|
||||
private void updateConfig3To4() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("notify", null);
|
||||
config.set("config-version", 4);
|
||||
}
|
||||
|
||||
private void updateConfig2To3() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("items", null);
|
||||
config.set("ItemOpenInv", null);
|
||||
config.set("toggles", null);
|
||||
config.set("settings.disable-saving", config.getBoolean("DisableSaving", false));
|
||||
config.set("DisableSaving", null);
|
||||
config.set("config-version", 3);
|
||||
}
|
||||
|
||||
private void updateConfig1To2() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("ItemOpenInvItemID", null);
|
||||
config.set("NotifySilentChest", null);
|
||||
config.set("NotifyAnyChest", null);
|
||||
config.set("AnyChest", null);
|
||||
config.set("SilentChest", null);
|
||||
config.set("config-version", 2);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.lishid.openinv.util;
|
||||
package com.lishid.openinv.util.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
config-version: 6
|
||||
config-version: 8
|
||||
settings:
|
||||
equal-access: view
|
||||
command:
|
||||
open:
|
||||
no-args-opens-self: false
|
||||
@@ -7,4 +8,4 @@ settings:
|
||||
max-radius: 10
|
||||
disable-offline-access: false
|
||||
disable-saving: false
|
||||
locale: 'en_us'
|
||||
console-locale: 'en'
|
||||
@@ -24,7 +24,7 @@
|
||||
<modules>
|
||||
<module>common</module>
|
||||
</modules>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<id>all</id>
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>addon/togglepersist</module>
|
||||
<module>common</module>
|
||||
<module>internal/v1_21_R1</module>
|
||||
<module>internal/v1_20_R4</module>
|
||||
@@ -116,7 +117,7 @@
|
||||
-->
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<version>3.7.1</version>
|
||||
<version>3.8.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
@@ -134,7 +135,7 @@
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<version>3.4.1</version>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
@@ -151,7 +152,7 @@
|
||||
<path>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_core</artifactId>
|
||||
<version>2.28.0</version>
|
||||
<version>2.31.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "minecraft:item/white_stained_glass_pane"
|
||||
"layer0": "minecraft:block/white_stained_glass"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user