Fix inventory config

This commit is contained in:
NotAlexNoyle committed 2026-08-19 10:02:20 -04:00
1 parent c129e29c32
commit 4e8c8255db
4 files changed
+897 -6

No files matched your search

+1 -1
View File
@@ -30,7 +30,7 @@ sourceSets {
main {
resources {
srcDir("src/main/java")
include("plugin.yml", "config.yml", "defaultproperties.yml", "inventories.yml")
include("plugin.yml", "config.yml", "defaultproperties.yml", "inventories.yml", "worlds.yml", "Localization.yml", "PermissionDefaults.yml")
}
}
}
@@ -173,6 +173,10 @@ public class WorldConfigStore {
}
// Worlds configuration
// Extract bundled default worlds.yml on first run
if (!new java.io.File(MyWorlds.plugin.getDataFolder(), "worlds.yml").exists()) {
MyWorlds.plugin.saveResource("worlds.yml", false);
}
worldConfigs.clear();
FileConfiguration config = new FileConfiguration(MyWorlds.plugin, "worlds.yml");
config.load();
@@ -1,12 +1,19 @@
package com.bergerkiller.bukkit.mw;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import com.bergerkiller.bukkit.common.config.ConfigurationNode;
@@ -17,6 +24,8 @@ import com.bergerkiller.bukkit.common.wrappers.PlayerRespawnPoint;
public class WorldInventory {
private static final Set<WorldInventory> inventories = new HashSet<WorldInventory>();
private static boolean inventoriesLoaded = false;
private static boolean enforcingCanonical = false;
private static List<CanonicalGroup> canonicalGroups = null;
private static int counter = 0;
private final Set<String> worlds = new HashSet<String>();
private String worldname;
@@ -31,8 +40,6 @@ public class WorldInventory {
}
public static void load() {
inventoriesLoaded = true;
// Check whether there are any configured entries that would result in saving
boolean hadExistingInventoriesThatRequiredSaving = false;
for (WorldInventory inv : inventories) {
@@ -68,8 +75,19 @@ public class WorldInventory {
}
}
// Only mark as loaded after parsing: WorldConfig.get() above creates missing
// configurations, whose constructor path calls save(). With the flag raised
// mid-parse those saves would rewrite inventories.yml from a half-read state,
// silently truncating every group not yet parsed.
inventoriesLoaded = true;
// The groups bundled in the plugin jar are canonical and must survive even
// when the server's inventories.yml lost them; save() also re-asserts them
// before every write.
boolean changed = applyCanonicalGroups();
// Re-save after loading in case merging of previous default inventories caused changes
if (hadExistingInventoriesThatRequiredSaving) {
if (hadExistingInventoriesThatRequiredSaving || changed) {
save();
}
}
@@ -80,22 +98,166 @@ public class WorldInventory {
return;
}
// A save triggered from within the canonical reconcile below; the outer
// call writes the final state once
if (enforcingCanonical) {
return;
}
// Re-assert the canonical groups before every write: save() is the only
// path that rewrites inventories.yml, so enforcing here guarantees no
// rewrite, by whatever plugin or command, can persist a broken main bundle
applyCanonicalGroups();
FileConfiguration config = new FileConfiguration(MyWorlds.plugin, "inventories.yml");
Set<String> savedNames = new HashSet<String>();
for (WorldInventory inventory : inventories) {
if (inventory.isRequiredSaving()) {
// Count only worlds whose registered configuration still references this
// group. Stale groups linger in the static set after /world config load
// re-creates all WorldConfigs and would otherwise be written as duplicates.
List<String> effectiveWorlds = new ArrayList<String>();
for (String world : inventory.worlds) {
WorldConfig wc = WorldConfig.getIfExists(world);
if (wc != null && wc.inventory == inventory) {
effectiveWorlds.add(world);
}
}
if (effectiveWorlds.size() > 1) {
String name = inventory.name;
for (int i = 0; i < Integer.MAX_VALUE && !savedNames.add(name.toLowerCase()); i++) {
name = inventory.name + i;
}
ConfigurationNode node = config.getNode(name);
node.set("folder", inventory.worldname);
node.set("worlds", new ArrayList<String>(inventory.worlds));
node.set("worlds", effectiveWorlds);
}
}
config.save();
}
/**
* Applies the inventory groups bundled inside the plugin jar (the shipped
* inventories.yml resource). These groups are canonical: their listed worlds are
* always merged into one group, worlds that joined that group but are not listed
* are moved back into their own group, and the shared storage world is pinned to
* the configured folder. Groups in the server's inventories.yml that do not
* involve canonical worlds are never touched. Worlds that are not registered or
* whose folder no longer exists are ignored, so this is safe on servers with a
* different level-name. All mutations here avoid save(); callers persist.
*
* @return True if any group was changed
*/
private static boolean applyCanonicalGroups() {
if (enforcingCanonical) {
return false;
}
enforcingCanonical = true;
try {
boolean changed = false;
for (CanonicalGroup canonical : getCanonicalGroups()) {
if (!WorldConfigStore.exists(canonical.folder) || !WorldManager.worldExists(canonical.folder)) {
continue;
}
// Members must be known to the server and exist on disk. Loadedness is
// deliberately not required: MyWorlds tracks groups for unloaded worlds
// too, and requiring it would eject a temporarily unloaded dimension.
List<String> members = new ArrayList<String>();
Set<String> membersLower = new HashSet<String>();
for (String worldName : canonical.worlds) {
if (WorldConfigStore.exists(worldName) && WorldManager.worldExists(worldName)) {
members.add(worldName);
membersLower.add(worldName.toLowerCase());
}
}
if (members.size() < 2 || !membersLower.contains(canonical.folder.toLowerCase())) {
continue;
}
WorldConfig folderConfig = WorldConfig.get(canonical.folder);
WorldInventory group = folderConfig.inventory;
boolean groupChanged = false;
// Merge in missing members
for (String member : members) {
if (!group.contains(member)) {
group.addWithoutSaving(member);
groupChanged = true;
}
}
// Move foreign members back into their own group
for (String extra : new ArrayList<String>(group.worlds)) {
if (!membersLower.contains(extra)) {
group.removeWithoutSaving(extra, true);
groupChanged = true;
}
}
// Pin the shared storage world to the canonical folder
if (!folderConfig.worldname.equalsIgnoreCase(group.worldname)) {
group.worldname = folderConfig.worldname;
groupChanged = true;
}
// Give auto-named groups the canonical name for a recognizable file
if (groupChanged && group.name.matches("inv\\d+")) {
group.name = canonical.name;
}
if (groupChanged) {
changed = true;
MyWorlds.plugin.log(Level.WARNING, "Re-asserted canonical inventory group '" +
group.name + "' (folder: " + group.worldname + "): " + group.worlds);
}
}
return changed;
} finally {
enforcingCanonical = false;
}
}
private static List<CanonicalGroup> getCanonicalGroups() {
if (canonicalGroups != null) {
return canonicalGroups;
}
List<CanonicalGroup> result = new ArrayList<CanonicalGroup>();
try (InputStream stream = MyWorlds.plugin.getResource("inventories.yml")) {
if (stream != null) {
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(
new InputStreamReader(stream, StandardCharsets.UTF_8));
for (String groupName : defaults.getKeys(false)) {
ConfigurationSection groupConfig = defaults.getConfigurationSection(groupName);
if (groupConfig == null) {
continue;
}
String folder = groupConfig.getString("folder");
List<String> worlds = groupConfig.getStringList("worlds");
if (folder != null && !worlds.isEmpty()) {
result.add(new CanonicalGroup(groupName, folder, worlds));
}
}
}
} catch (IOException ex) {
MyWorlds.plugin.getLogger().log(Level.WARNING, "Failed to read bundled inventories.yml", ex);
}
canonicalGroups = result;
return result;
}
private static final class CanonicalGroup {
public final String name;
public final String folder;
public final List<String> worlds;
public CanonicalGroup(String name, String folder, List<String> worlds) {
this.name = name;
this.folder = folder;
this.worlds = worlds;
}
}
public static void detach(Collection<String> worldnames) {
// Collect all the loaded Bukkit worlds impacted by this
Set<World> loadedWorlds = new HashSet<>();
+725
View File
@@ -0,0 +1,725 @@
Loss:
loaded: true
keepSpawnLoaded: true
environment: normal
chunkGenerator: Splegg-OG:void
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures:
- PHANTOM
- CREEPER
- ILLUSIONER
- CAT
- ZOMBIFIED_PIGLIN
- CAMEL
- SHULKER
- ELDER_GUARDIAN
- WITHER_SKELETON
- FROG
- GOAT
- VINDICATOR
- SLIME
- ZOMBIE_HORSE
- PARROT
- DONKEY
- TRADER_LLAMA
- STRAY
- OCELOT
- GUARDIAN
- WARDEN
- RABBIT
- GLOW_SQUID
- HOGLIN
- LLAMA
- CAVE_SPIDER
- HORSE
- MULE
- SKELETON
- VEX
- PIGLIN
- ZOMBIE_VILLAGER
- ENDERMAN
- STRIDER
- SNOWMAN
- CHICKEN
- RAVAGER
- SKELETON_HORSE
- GIANT
- SNIFFER
- ZOMBIE
- PUFFERFISH
- COD
- PILLAGER
- SILVERFISH
- TADPOLE
- TROPICAL_FISH
- FOX
- SALMON
- POLAR_BEAR
- COW
- WOLF
- AXOLOTL
- GHAST
- WITHER
- SQUID
- ENDERMITE
- DROWNED
- SHEEP
- BAT
- TURTLE
- MUSHROOM_COW
- BEE
- PIG
- EVOKER
- ZOGLIN
- SPIDER
- IRON_GOLEM
- HUSK
- MAGMA_CUBE
- PIGLIN_BRUTE
- DOLPHIN
- PANDA
- WITCH
- BLAZE
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
world:
loaded: true
keepSpawnLoaded: true
environment: normal
chunkGenerator: ''
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: NETHER_LINK
name: world_nether
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: END_PLATFORM
name: world_the_end
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures: []
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
spawn:
world: world
x: 0.5
y: 64.0
z: -202.5
yaw: 90.0
pitch: -0.5
world_the_end:
loaded: true
keepSpawnLoaded: true
environment: the_end
chunkGenerator: ''
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: RESPAWN
name: world
display: ''
playersOnly: true
teleportMounts: true
teleportToLastPosition: false
showCredits: true
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures: []
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
respawn:
type: WORLD_SPAWN
world: world
HB1-hub:
loaded: true
keepSpawnLoaded: true
environment: normal
chunkGenerator: TheHerobrine-OG:void
gamemode: SURVIVAL
lockedtime: 18000
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures:
- PHANTOM
- CREEPER
- ILLUSIONER
- CAT
- ZOMBIFIED_PIGLIN
- CAMEL
- SHULKER
- ELDER_GUARDIAN
- WITHER_SKELETON
- FROG
- GOAT
- VINDICATOR
- SLIME
- ZOMBIE_HORSE
- PARROT
- DONKEY
- TRADER_LLAMA
- OCELOT
- STRAY
- GUARDIAN
- WARDEN
- RABBIT
- GLOW_SQUID
- HOGLIN
- LLAMA
- CAVE_SPIDER
- HORSE
- MULE
- SKELETON
- VEX
- ZOMBIE_VILLAGER
- PIGLIN
- ENDERMAN
- STRIDER
- SNOWMAN
- CHICKEN
- RAVAGER
- SKELETON_HORSE
- GIANT
- SNIFFER
- ZOMBIE
- PUFFERFISH
- COD
- PILLAGER
- SILVERFISH
- TADPOLE
- TROPICAL_FISH
- FOX
- SALMON
- POLAR_BEAR
- COW
- WOLF
- AXOLOTL
- SQUID
- GHAST
- WITHER
- ENDERMITE
- SHEEP
- DROWNED
- BAT
- TURTLE
- MUSHROOM_COW
- BEE
- PIG
- EVOKER
- ZOGLIN
- SPIDER
- IRON_GOLEM
- HUSK
- MAGMA_CUBE
- DOLPHIN
- PIGLIN_BRUTE
- PANDA
- BLAZE
- WITCH
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: PEACEFUL
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
world_nether:
loaded: true
keepSpawnLoaded: true
environment: nether
chunkGenerator: ''
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: NETHER_LINK
name: world
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures: []
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
respawn:
type: WORLD_SPAWN
world: world
BB1-map:
loaded: true
keepSpawnLoaded: true
environment: normal
chunkGenerator: ''
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures:
- PHANTOM
- CREEPER
- ILLUSIONER
- CAT
- ZOMBIFIED_PIGLIN
- CAMEL
- SHULKER
- ELDER_GUARDIAN
- WITHER_SKELETON
- FROG
- GOAT
- VINDICATOR
- SLIME
- ZOMBIE_HORSE
- PARROT
- DONKEY
- TRADER_LLAMA
- STRAY
- OCELOT
- GUARDIAN
- WARDEN
- RABBIT
- GLOW_SQUID
- HOGLIN
- LLAMA
- CAVE_SPIDER
- HORSE
- MULE
- SKELETON
- VEX
- PIGLIN
- ZOMBIE_VILLAGER
- ENDERMAN
- STRIDER
- SNOWMAN
- CHICKEN
- RAVAGER
- SKELETON_HORSE
- GIANT
- SNIFFER
- ZOMBIE
- PUFFERFISH
- COD
- PILLAGER
- SILVERFISH
- TADPOLE
- TROPICAL_FISH
- FOX
- SALMON
- POLAR_BEAR
- COW
- WOLF
- AXOLOTL
- GHAST
- WITHER
- SQUID
- ENDERMITE
- DROWNED
- SHEEP
- BAT
- TURTLE
- MUSHROOM_COW
- BEE
- PIG
- EVOKER
- ZOGLIN
- SPIDER
- IRON_GOLEM
- HUSK
- MAGMA_CUBE
- PIGLIN_BRUTE
- DOLPHIN
- PANDA
- WITCH
- BLAZE
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
SP1-hub:
loaded: true
keepSpawnLoaded: true
environment: normal
chunkGenerator: Splegg-OG:void
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures:
- PHANTOM
- CREEPER
- ILLUSIONER
- CAT
- ZOMBIFIED_PIGLIN
- CAMEL
- SHULKER
- ELDER_GUARDIAN
- WITHER_SKELETON
- FROG
- GOAT
- VINDICATOR
- SLIME
- ZOMBIE_HORSE
- PARROT
- DONKEY
- TRADER_LLAMA
- STRAY
- OCELOT
- GUARDIAN
- WARDEN
- RABBIT
- GLOW_SQUID
- HOGLIN
- LLAMA
- CAVE_SPIDER
- HORSE
- MULE
- SKELETON
- VEX
- PIGLIN
- ZOMBIE_VILLAGER
- ENDERMAN
- STRIDER
- SNOWMAN
- CHICKEN
- RAVAGER
- SKELETON_HORSE
- GIANT
- SNIFFER
- ZOMBIE
- PUFFERFISH
- COD
- PILLAGER
- SILVERFISH
- TADPOLE
- TROPICAL_FISH
- FOX
- SALMON
- POLAR_BEAR
- COW
- WOLF
- AXOLOTL
- GHAST
- WITHER
- SQUID
- ENDERMITE
- DROWNED
- SHEEP
- BAT
- TURTLE
- MUSHROOM_COW
- BEE
- PIG
- EVOKER
- ZOGLIN
- SPIDER
- IRON_GOLEM
- HUSK
- MAGMA_CUBE
- PIGLIN_BRUTE
- DOLPHIN
- PANDA
- WITCH
- BLAZE
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1
BB1-hub:
loaded: true
keepSpawnLoaded: true
environment: normal
chunkGenerator: ''
gamemode: SURVIVAL
rememberlastplayerpos: false
pvp: true
defaultNetherPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
defaultEndPortal:
mode: DEFAULT
name: ''
display: ''
playersOnly: false
teleportMounts: true
teleportToLastPosition: false
showCredits: false
nonPlayersCreatePortals: true
activationEnabled: true
autoDetect: true
operators: []
deniedCreatures:
- PHANTOM
- CREEPER
- ILLUSIONER
- CAT
- ZOMBIFIED_PIGLIN
- CAMEL
- SHULKER
- ELDER_GUARDIAN
- WITHER_SKELETON
- FROG
- GOAT
- VINDICATOR
- SLIME
- ZOMBIE_HORSE
- PARROT
- DONKEY
- TRADER_LLAMA
- STRAY
- OCELOT
- GUARDIAN
- WARDEN
- RABBIT
- GLOW_SQUID
- HOGLIN
- LLAMA
- CAVE_SPIDER
- HORSE
- MULE
- SKELETON
- VEX
- PIGLIN
- ZOMBIE_VILLAGER
- ENDERMAN
- STRIDER
- SNOWMAN
- CHICKEN
- RAVAGER
- SKELETON_HORSE
- GIANT
- SNIFFER
- ZOMBIE
- PUFFERFISH
- COD
- PILLAGER
- SILVERFISH
- TADPOLE
- TROPICAL_FISH
- FOX
- SALMON
- POLAR_BEAR
- COW
- WOLF
- AXOLOTL
- GHAST
- WITHER
- SQUID
- ENDERMITE
- DROWNED
- SHEEP
- BAT
- TURTLE
- MUSHROOM_COW
- BEE
- PIG
- EVOKER
- ZOGLIN
- SPIDER
- IRON_GOLEM
- HUSK
- MAGMA_CUBE
- PIGLIN_BRUTE
- DOLPHIN
- PANDA
- WITCH
- BLAZE
spawnDefaultsApplied: true
hunger: true
formIce: false
formSnow: false
difficulty: HARD
reloadWhenEmpty: false
bedRespawnEnabled: false
advancementsEnabled: true
advancementsSilent: false
playerLimit: -1