Skip to content

Commit

Permalink
Merge Add support of multiple save modes (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
utarwyn authored Mar 25, 2024
2 parents 6c659c3 + af488b7 commit 671111d
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 67 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 All @@ -49,7 +51,6 @@ public class Configuration {
private final boolean blockNametag;
private final boolean updateChecker;
private final boolean globalSound;
private final boolean saveOnChestClose;

/**
* Create a configuration object from plugin configuration.
Expand All @@ -59,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 @@ -109,7 +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);
this.saveOnChestClose = loadValue("others.saveOnChestClose", 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 @@ -140,6 +152,10 @@ public boolean isNumberingEnderchests() {
return this.numberingEnderchests;
}

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

public List<Material> getForbiddenMaterials() {
return this.forbiddenMaterials;
}
Expand Down Expand Up @@ -212,10 +228,6 @@ public boolean isGlobalSound() {
return this.globalSound;
}

public boolean isSaveOnChestClose() {
return this.saveOnChestClose;
}

private <T> T loadValue(String key, Predicate<String> checker, Function<String, T> getter) throws ConfigLoadingException {
Throwable cause = null;
if (checker.test(key)) {
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 @@ -64,6 +64,7 @@ protected synchronized void unload() {

// Save and unload all data
this.loadingContexts.clear();
this.contextMap.values().forEach(PlayerContext::update);
this.contextMap.values().forEach(PlayerContext::save);
this.contextMap.clear();
}
Expand Down Expand Up @@ -97,6 +98,15 @@ public Optional<VanillaEnderChest> getVanillaEnderchestUsedBy(Player player) {
.findFirst();
}

/**
* Get the all context of loaded players.
*
* @return context map of all players
*/
public Map<UUID, PlayerContext> getContextMap() {
return contextMap;
}

/**
* Check if the context of a specific player is unused at a given time.
* All chests of this context must not have viewer in their container.
Expand Down Expand Up @@ -140,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 @@ -188,11 +188,17 @@ public boolean openEnderchestInventory(Player viewer, int num) {
return accessible;
}

/**
* Update container of all chests.
*/
public void update() {
this.chests.forEach(EnderChest::updateContainer);
}

/**
* Save all datas stored in the context.
*/
public void save() {
this.chests.forEach(EnderChest::updateContainer);
this.data.saveContext(this.chests);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,31 +73,31 @@ public void onInventoryDrag(InventoryDragEvent event) {
*/
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
if (!(event.getPlayer() instanceof Player)) return;
Player player = (Player) event.getPlayer();
if (!(event.getPlayer() instanceof Player) || !this.isEnderChestInventory(event.getInventory())) {
return;
}

// Play the closing sound when we use the default enderchest!
if (this.isEnderChestInventory(event.getInventory())) {
Optional<VanillaEnderChest> vanilla = this.manager.getVanillaEnderchestUsedBy(player);
Player player = (Player) event.getPlayer();
Optional<VanillaEnderChest> vanilla = this.manager.getVanillaEnderchestUsedBy(player);

// When closing the default enderchest ...
if (vanilla.isPresent()) {
Player ownerObj = vanilla.get().getOwnerAsPlayer();
// 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(), true);
ownerObj.saveData();
}
// ... 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);
}
// 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,9 +2,11 @@

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.block.Block;
Expand All @@ -15,7 +17,10 @@
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;
import java.util.Map;
import java.util.UUID;

/**
Expand Down Expand Up @@ -95,11 +100,40 @@ public void onPlayerInteract(PlayerInteractEvent event) {
*/
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
UUID owner = event.getPlayer().getUniqueId();
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) {
if (Files.getConfiguration().getSaveMode() != SaveMode.WORLD_SAVE) {
return;
}

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();

Player player = entry.getValue().getOwnerAsObject();
// 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(entry.getKey());

// Clear all the player data from memory
boolean unused = this.manager.isContextUnused(owner);
this.manager.savePlayerContext(owner, unused);
this.manager.savePlayerContext(entry.getKey());
if (unused) {
it.remove();
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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;
Expand Down Expand Up @@ -121,9 +122,13 @@ public void onClose(Player player) {
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 (experimental)
if (offlineOwner || Files.getConfiguration().isSaveOnChestClose()) {
Managers.get(EnderChestManager.class).savePlayerContext(this.chest.getOwner(), offlineOwner);
// 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
Expand Down
8 changes: 4 additions & 4 deletions plugin/src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ enderchests:
# Use the vanilla enderchest as the first one. Otherwise, it will be managed by EnderContainers.
useVanillaEnderchest: true

# The mode in which the plugin will save enderchests
# Can be: logout, on-close or world-save
saveMode: 'logout'

# A list of item materials which are forbidden to store in chests
# Use Minecraft item IDs displayed ingame (shortcut: F3+H)
forbiddenMaterials: [ ]
Expand Down Expand Up @@ -102,7 +106,3 @@ others:

# Use a global sound when a player opens/closes an enderchest
globalSound: true

# Save enderchests on close. Beware! This could be very resource intensive.
# Enable this ONLY if you have issues with the normal saving system.
saveOnChestClose: false
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import fr.utarwyn.endercontainers.TestHelper;
import fr.utarwyn.endercontainers.TestInitializationException;
import fr.utarwyn.endercontainers.configuration.enderchests.SaveMode;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -30,14 +31,14 @@ public void loadFromTestFile() throws ConfigLoadingException, TestInitialization
assertThat(config.getDisabledWorlds()).containsExactly("disabled");
assertThat(config.isOnlyShowAccessibleEnderchests()).isFalse();
assertThat(config.isUseVanillaEnderchest()).isTrue();
assertThat(config.getSaveMode()).isEqualTo(SaveMode.LOGOUT);
assertThat(config.getEnderchestItem()).isNotNull();
assertThat(config.getEnderchestItemVariants()).hasSize(5);
assertThat(config.isNumberingEnderchests()).isTrue();
assertThat(config.getMysqlSslKeystoreFile()).isNull();
assertThat(config.getMysqlSslKeystorePassword()).isNull();
assertThat(config.getMysqlSslTrustKeystoreFile()).isNull();
assertThat(config.getMysqlSslTrustKeystorePassword()).isNull();
assertThat(config.isSaveOnChestClose()).isFalse();
}

@Test
Expand Down
Loading

0 comments on commit 671111d

Please sign in to comment.