lishid / OpenInv

Open anyone's inventory as a chest, real-time!
GNU General Public License v3.0
119 stars 94 forks source link

Enhancement: Option to disable offline inventory viewing #171

Closed Combustible closed 2 years ago

Combustible commented 3 years ago

We've got a relatively complicated data storage system on my server which redirects the player data storage to a database. So there are no files to store locally - this allows us to share player data across multiple worlds (the plugin we developed for this is https://github.com/TeamMonumenta/monumenta-redis-sync)

It took us a long time to track down an obscure inventory corruption bug, and eventually tracked it back to the way OpenInv switches a player to offline mode. What would happen is that a moderator would open a player's inventory on server 1, then they would transfer to server 2, change their inventory, and go back to server 1. From server 1's perspective, the player logs out, then logs back in - so openinv helpfully overwrites their inventory with the state from before (and thus losing any changes the player made on server 2).

We've been working around this by building our own OpenInv version with the following patch. It'd be nice if we had some config file option so we wouldn't need to build our own version for this. Totally understand if it's not worth your time - this is obviously a special case that will probably never effect many people. Just thought I'd ask.

+++ b/plugin/src/main/java/com/lishid/openinv/OpenInv.java
@@ -447,12 +447,23 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
         // Replace stored player with our own version
         this.playerCache.put(key, this.accessor.getPlayerDataManager().inject(player));

+        /* Remove the cached entry if it exists */
+        this.playerCache.invalidate(key);
+
         if (this.inventories.containsKey(key)) {
-            this.inventories.get(key).setPlayerOffline();
+            Iterator<HumanEntity> iterator = this.inventories.remove(key).getBukkitInventory().getViewers().iterator();
+            while (iterator.hasNext()) {
+                HumanEntity human = iterator.next();
+                human.closeInventory();
+            }
         }

         if (this.enderChests.containsKey(key)) {
-            this.enderChests.get(key).setPlayerOffline();
+            Iterator<HumanEntity> iterator = this.enderChests.remove(key).getBukkitInventory().getViewers().iterator();
+            while (iterator.hasNext()) {
+                HumanEntity human = iterator.next();
+                human.closeInventory();
+            }
         }
     }
Jikoo commented 3 years ago

I'm definitely interested in helping work around this case, but isn't it possible to work around already without patching OpenInv? I.E.:

@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
  closeAll(event.getPlayer().getInventory().getViewers());
  closeAll(event.getPlayer().getEnderChest().getViewers());
  OpenInv.getPlugin(OpenInv.class).unload(event.getPlayer());
}

private void closeAll(Iterable<HumanEntity> viewers) {
  Iterator<HumanEntity> iterator = viewers.iterator();
  while (iterator.hasNext()) {
    iterator.next().closeInventory();
  }
}

Deny moderators OpenInv.openoffline and voila, no offline access, right?

Combustible commented 3 years ago

Hadn't thought of that - yeah, that does seem like it would work. I wish the permission wasn't needed though - because if anyone did want to use my redis sync plugin and openinv, that'd be something they'd have to do manually and if they forget they'd randomly lose data randomly for a long time before figuring out why. This was our problem - it wasn't at all clear that this was the interaction causing this, since it's quite rare to have someone's inventory opened when they log out. If I could solve it purely by "integrating" my plugin with the OpenInv API (the code snippet) then that would be perfect.

Could be as simple as an API method that says "disableOfflineViewing()"... but not sure if you think that's too ugly. Would only be a couple lines of code & no config file change though...

Actually, on second thought... I might not need to deny the permission. There's no player data for openinv to open - so I assume if the player file is missing, it just fails and doesn't open anything. Cool. I'll give this a try.

Jikoo commented 3 years ago

Yeah, OpenInv shouldn't open a nonexistent player. Alternately, it should be possible to force users to not have permission to view offline via a PermissionAttachment if you're after maximum portability in a hurry - on login, just slap a new attachment with the node set to false on everyone.

I'm definitely still interested in implementing a cleaner solution on the OpenInv end, but I've been focused elsewhere and haven't really been working on this project much.

Combustible commented 3 years ago

Hmm... No, this doesn't quite work, even though it seems like it should. Apparently getPlayer().getInventory().getViewers() is empty when I have an OpenInv viewer attached to it.

It looks like OpenInv's .getBukkitInventory().getViewers() is returning more things somehow. I don't really understand why this is happening, but it definitely is.

Jikoo commented 3 years ago

Hm. I know Bukkit and NMS actually keep separate lists, probably has to do with it. I think we used to manage the Bukkit list but it ended up causing duplicate entries. Will definitely look into that Eventually(TM).

Jikoo commented 2 years ago

https://github.com/Jikoo/OpenInv/releases/tag/4.2.0

Combustible commented 2 years ago

Just discovered this as I'm trying to rebase my ancient disabling patch onto OpenInv so I can update my server to 1.18.2. Hooray, thank you very much for implementing this! My one-patch fork can finally die :)