Skip to content

Commit

Permalink
Support multiple saving modes
Browse files Browse the repository at this point in the history
  • Loading branch information
utarwyn committed Mar 24, 2024
1 parent e5cd481 commit af488b7
Show file tree
Hide file tree
Showing 17 changed files with 269 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.utarwyn.endercontainers.configuration;

import fr.utarwyn.endercontainers.configuration.enderchests.SaveMode;
import fr.utarwyn.endercontainers.configuration.ui.EnderChestItem;
import fr.utarwyn.endercontainers.configuration.ui.EnderChestItemVariant;
import org.bukkit.Material;
Expand All @@ -26,6 +27,7 @@ public class Configuration {
private final int maxEnderchests;
private final int defaultEnderchests;
private final boolean useVanillaEnderchest;
private final SaveMode saveMode;
private final List<Material> forbiddenMaterials;

private final EnderChestItem enderchestItem;
Expand Down Expand Up @@ -58,6 +60,7 @@ public class Configuration {
*/
Configuration(FileConfiguration config) throws ConfigLoadingException {
boolean legacyOnlyShowAccessible;
boolean legacySaveOnChestClose;

this.locale = loadValue("locale", config::isString, config::getString);
this.disabledWorlds = loadValue("disabledWorlds", config::isList, config::getStringList);
Expand Down Expand Up @@ -108,6 +111,17 @@ public class Configuration {
this.blockNametag = loadValue("others.blockNametag", config::isBoolean, config::getBoolean);
this.updateChecker = loadValue("others.updateChecker", config::isBoolean, config::getBoolean);
this.globalSound = loadValue("others.globalSound", config::isBoolean, config::getBoolean);

legacySaveOnChestClose = loadValue("others.saveOnChestClose", v -> true, config::getBoolean);
if (legacySaveOnChestClose) {
this.saveMode = SaveMode.ON_CLOSE;
} else {
this.saveMode = loadValue(
"enderchests.saveMode",
key -> config.isString(key) && SaveMode.fromName(config.getString(key)) != null,
key -> SaveMode.fromName(config.getString(key))
);
}
}

public String getLocale() {
Expand Down Expand Up @@ -138,6 +152,10 @@ public boolean isNumberingEnderchests() {
return this.numberingEnderchests;
}

public SaveMode getSaveMode() {
return this.saveMode;
}

public List<Material> getForbiddenMaterials() {
return this.forbiddenMaterials;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package fr.utarwyn.endercontainers.configuration.enderchests;

/**
* The mode in which the plugin will save enderchests.
*
* @author Utarwyn
* @since 2.3.0
*/
public enum SaveMode {
/**
* Save enderchests of a player when he logs out.
* It's the default mode.
*/
LOGOUT("logout"),
/**
* Save enderchests of a player when he closes the inventory.
*/
ON_CLOSE("on-close"),
/**
* Save all enderchests during a world save.
*/
WORLD_SAVE("world-save"),
;

private final String name;

SaveMode(String name) {
this.name = name;
}

public String getName() {
return name;
}

public static SaveMode fromName(String name) {
for (SaveMode mode : SaveMode.values()) {
if (mode.getName().equalsIgnoreCase(name)) {
return mode;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import fr.utarwyn.endercontainers.inventory.InventoryManager;
import org.bukkit.entity.Player;

import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

Expand Down Expand Up @@ -61,7 +64,8 @@ protected synchronized void unload() {

// Save and unload all data
this.loadingContexts.clear();
this.contextMap.values().forEach(pc -> pc.save(Collections.emptySet()));
this.contextMap.values().forEach(PlayerContext::update);
this.contextMap.values().forEach(PlayerContext::save);
this.contextMap.clear();
}

Expand Down Expand Up @@ -146,19 +150,27 @@ public void registerPlayerContext(PlayerContext context) {

/**
* Save all data of a player.
* Also purge its context from memory if needed.
*
* @param owner owner of the player context to save
* @param delete should the context must be deleted from the memory
* @param owner owner of the player context to save
*/
public void savePlayerContext(UUID owner, boolean delete) {
public void savePlayerContext(UUID owner) {
if (this.contextMap.containsKey(owner)) {
SaveTask saveTask = new SaveTask(this.contextMap.get(owner));
PlayerContext playerContext = this.contextMap.get(owner);
SaveTask saveTask = new SaveTask(playerContext);

playerContext.update();
this.plugin.executeTaskOnOtherThread(saveTask);
}
}

if (delete) {
this.contextMap.remove(owner);
}
/**
* Delete a player context from memory if it is unused.
*
* @param owner owner of the player context
*/
public void deletePlayerContextIfUnused(UUID owner) {
if (this.isContextUnused(owner)) {
this.contextMap.remove(owner);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.bukkit.entity.Player;

import java.util.*;
import java.util.stream.Collectors;

/**
* A context in which all enderchests of a player are loaded.
Expand Down Expand Up @@ -190,24 +189,16 @@ public boolean openEnderchestInventory(Player viewer, int num) {
}

/**
* Update used inventories in the context.
* This method is called synchronously before the async SaveTask.
*
* @return set of used enderchests
* Update container of all chests.
*/
public Set<EnderChest> preSave() {
Set<EnderChest> usedChests = this.chests.stream().filter(EnderChest::isContainerUsed).collect(Collectors.toSet());
usedChests.forEach(EnderChest::updateContainer);
return usedChests;
public void update() {
this.chests.forEach(EnderChest::updateContainer);
}

/**
* Save all datas stored in the context.
*
* @param usedChests set of used enderchests (previously returned by preSave)
*/
public void save(Set<EnderChest> usedChests) {
this.chests.stream().filter(chest -> !usedChests.contains(chest)).forEach(EnderChest::updateContainer);
public void save() {
this.data.saveContext(this.chests);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package fr.utarwyn.endercontainers.enderchest.context;

import fr.utarwyn.endercontainers.enderchest.EnderChest;

import java.util.Set;

/**
* Represents the task which saves in a persistant storage all data
* of the context of a specific player.
Expand All @@ -18,27 +14,21 @@ public class SaveTask implements Runnable {
*/
private final PlayerContext context;

/**
* Set of used enderchests
*/
private final Set<EnderChest> usedChests;

/**
* Construct a new saving task.
*
* @param context the player context to save
*/
public SaveTask(PlayerContext context) {
this.context = context;
this.usedChests = context.preSave();
}

/**
* {@inheritDoc}
*/
@Override
public void run() {
this.context.save(this.usedChests);
this.context.save();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import fr.utarwyn.endercontainers.compatibility.CompatibilityHelper;
import fr.utarwyn.endercontainers.configuration.Files;
import fr.utarwyn.endercontainers.enderchest.EnderChestManager;
import fr.utarwyn.endercontainers.enderchest.VanillaEnderChest;
import fr.utarwyn.endercontainers.inventory.EnderChestInventory;
import fr.utarwyn.endercontainers.inventory.InventoryManager;
import org.bukkit.Sound;
Expand All @@ -18,6 +19,8 @@
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;

import java.util.Optional;

/**
* Intercepts events about chest inventories.
*
Expand Down Expand Up @@ -70,19 +73,32 @@ public void onInventoryDrag(InventoryDragEvent event) {
*/
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
if (!(event.getPlayer() instanceof Player)) return;
if (!(event.getPlayer() instanceof Player) || !this.isEnderChestInventory(event.getInventory())) {
return;
}

Player player = (Player) event.getPlayer();
Optional<VanillaEnderChest> vanilla = this.manager.getVanillaEnderchestUsedBy(player);

// Play the closing sound when we use the default enderchest!
if (this.isEnderChestInventory(event.getInventory())) {
// Play the closing sound
Sound sound = CompatibilityHelper.searchSound("CHEST_CLOSE", "BLOCK_CHEST_CLOSE");
if (Files.getConfiguration().isGlobalSound()) {
player.getWorld().playSound(player.getLocation(), sound, 1f, 1f);
} else {
player.playSound(player.getLocation(), sound, 1f, 1f);
// When closing the default enderchest ...
if (vanilla.isPresent()) {
Player ownerObj = vanilla.get().getOwnerAsPlayer();

// ... save and delete the context from memory if the player is offline.
if (!ownerObj.equals(player) && !ownerObj.isOnline()) {
this.manager.savePlayerContext(vanilla.get().getOwner());
this.manager.deletePlayerContextIfUnused(ownerObj.getUniqueId());
ownerObj.saveData();
}
}

// Play the closing sound
Sound sound = CompatibilityHelper.searchSound("CHEST_CLOSE", "BLOCK_CHEST_CLOSE");
if (Files.getConfiguration().isGlobalSound()) {
player.getWorld().playSound(player.getLocation(), sound, 1f, 1f);
} else {
player.playSound(player.getLocation(), sound, 1f, 1f);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

import fr.utarwyn.endercontainers.Managers;
import fr.utarwyn.endercontainers.configuration.Files;
import fr.utarwyn.endercontainers.configuration.enderchests.SaveMode;
import fr.utarwyn.endercontainers.dependency.DependenciesManager;
import fr.utarwyn.endercontainers.dependency.exceptions.BlockChestOpeningException;
import fr.utarwyn.endercontainers.enderchest.EnderChestManager;
import fr.utarwyn.endercontainers.enderchest.context.PlayerContext;
import fr.utarwyn.endercontainers.util.PluginMsg;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.WorldSaveEvent;

import java.util.Iterator;
Expand Down Expand Up @@ -92,30 +93,42 @@ public void onPlayerInteract(PlayerInteractEvent event) {
}
}

/**
* Method called when a player quits the server
*
* @param event The quit event
*/
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
if (Files.getConfiguration().getSaveMode() == SaveMode.LOGOUT) {
UUID owner = event.getPlayer().getUniqueId();
this.manager.savePlayerContext(owner);
this.manager.deletePlayerContextIfUnused(owner);
}
}

/**
* Method called when a world is saved
*
* @param event The save event
*/
@EventHandler
public void onWorldSave(WorldSaveEvent event) {
World world = event.getWorld();
if (Files.getConfiguration().getSaveMode() != SaveMode.WORLD_SAVE) {
return;
}

// Iterate over all the player contexts to save them
Map<UUID, PlayerContext> contextMap = this.manager.getContextMap();
for (Iterator<Map.Entry<UUID, PlayerContext>> it = contextMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<UUID, PlayerContext> entry = it.next();

// Only the player in the world of the event will be saved
Player player = entry.getValue().getOwnerAsObject();
if (player != null && world.equals(player.getWorld())) {
UUID owner = entry.getKey();

// Avoids saving players data several times in a row if multiple worlds are on the server
if (player == null || player.getWorld().equals(event.getWorld())) {
// Clear all the player data from memory
boolean unused = this.manager.isContextUnused(owner);
boolean unused = this.manager.isContextUnused(entry.getKey());

// Because iterator is used, we need to clear the player data manually with it.remove()
this.manager.savePlayerContext(owner, false);
this.manager.savePlayerContext(entry.getKey());
if (unused) {
it.remove();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package fr.utarwyn.endercontainers.inventory;

import com.google.common.base.Preconditions;
import fr.utarwyn.endercontainers.Managers;
import fr.utarwyn.endercontainers.compatibility.CompatibilityHelper;
import fr.utarwyn.endercontainers.configuration.Files;
import fr.utarwyn.endercontainers.configuration.LocaleKey;
import fr.utarwyn.endercontainers.configuration.enderchests.SaveMode;
import fr.utarwyn.endercontainers.enderchest.EnderChest;
import fr.utarwyn.endercontainers.enderchest.EnderChestManager;
import fr.utarwyn.endercontainers.util.uuid.UUIDFetcher;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
Expand Down Expand Up @@ -114,6 +118,19 @@ protected String getTitle() {
*/
@Override
public void onClose(Player player) {
// Save and delete the player context if the owner of the chest is offline
Player owner = Bukkit.getPlayer(this.chest.getOwner());
boolean offlineOwner = owner == null || !owner.isOnline();

// Save chest inventory if owner is offline or forced by the configuration
if (offlineOwner || Files.getConfiguration().getSaveMode() == SaveMode.ON_CLOSE) {
EnderChestManager enderChestManager = Managers.get(EnderChestManager.class);
enderChestManager.savePlayerContext(this.chest.getOwner());
if (offlineOwner) {
enderChestManager.deletePlayerContextIfUnused(this.chest.getOwner());
}
}

// Play the closing sound
Sound sound = CompatibilityHelper.searchSound("CHEST_CLOSE", "BLOCK_CHEST_CLOSE");
if (Files.getConfiguration().isGlobalSound()) {
Expand Down
Loading

0 comments on commit af488b7

Please sign in to comment.