/*
* This file is part of NeptuneVanilla, licensed under the MIT License (MIT).
*
* Copyright (c) 2015-2017, Jamie Mansfield <https://github.com/jamierocks>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.neptunepowered.vanilla.world;
import static net.canarymod.Canary.log;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.canarymod.Canary;
import net.canarymod.api.world.DimensionType;
import net.canarymod.api.world.UnknownWorldException;
import net.canarymod.api.world.World;
import net.canarymod.api.world.WorldManager;
import net.canarymod.api.world.WorldType;
import net.canarymod.config.Configuration;
import net.canarymod.config.WorldConfiguration;
import net.canarymod.hook.system.LoadWorldHook;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.WorldServer;
import net.minecraft.world.WorldServerMulti;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.chunk.storage.AnvilSaveHandler;
import net.minecraft.world.storage.WorldInfo;
import org.neptunepowered.vanilla.interfaces.minecraft.server.IMixinMinecraftServer;
import org.neptunepowered.vanilla.interfaces.minecraft.world.IMixinWorld;
import org.neptunepowered.vanilla.interfaces.minecraft.world.storage.IMixinWorldInfo;
import org.neptunepowered.vanilla.util.converter.GameModeConverter;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class NeptuneWorldManager implements WorldManager {
public static final File WORLDS_DIR = new File(Canary.getWorkingDirectory(), "worlds");
public static final File WORLDS_BACKUP_DIR = new File(Canary.getWorkingDirectory(), "worldsbackup");
public static final File PLAYERS_DIR = new File(NeptuneWorldManager.WORLDS_DIR, "players");
public static final File STATS_DIR = new File(NeptuneWorldManager.WORLDS_DIR, "stats");
private final Map<String, World> loadedWorlds = Maps.newHashMap();
private final List<String> existingWorlds = Lists.newArrayList();
public NeptuneWorldManager() {
if (!WORLDS_DIR.exists()) {
WORLDS_DIR.mkdirs();
return;
}
final File[] worlds = WORLDS_DIR.listFiles(File::isDirectory);
if (worlds == null) {
return;
}
for (File world : worlds) {
final File[] dimensions = world.listFiles(pathname -> pathname.isDirectory() && pathname.getName().contains("_"));
if (dimensions == null) {
continue;
}
for (File dimension : dimensions) {
this.existingWorlds.add(dimension.getName());
}
}
}
public void addWorld(WorldServer worldServer) {
log.debug(String.format("Adding new world to world manager, filed as %s_%s",
((World) worldServer).getName(), ((World) worldServer).getType().getName()));
this.loadedWorlds.put(((World) worldServer).getName() + "_" + ((World) worldServer).getType().getName(), (World) worldServer);
}
@Override
public World getWorld(String name, boolean autoload) {
if (name == null || name.isEmpty()) {
// assume the world is the default world
name = Configuration.getServerConfig().getDefaultWorldName() + "_" + DimensionType.fromId(0).getName();
}
final String world = name.substring(0, Math.max(0, name.lastIndexOf("_")));
final DimensionType type = DimensionType.fromName(name.substring(Math.max(0, name.lastIndexOf("_") + 1)));
if (type != null) {
return this.getWorld(world, type, autoload);
}
if (this.worldIsLoaded(name)) {
return this.loadedWorlds.get(name);
} else if (this.worldIsLoaded(name, DimensionType.NORMAL)) {
return this.loadedWorlds.get(name + "_NORMAL");
} else {
if (autoload) {
if (this.worldExists(name)) {
return loadWorld(name, DimensionType.NORMAL);
} else if (this.worldExists(name + "_NORMAL")) {
return loadWorld(name, DimensionType.NORMAL);
} else {
throw new UnknownWorldException("World " + name + " is unknown. Autoload was enabled for this call.");
}
} else {
throw new UnknownWorldException("World " + name + " is not loaded. Autoload was disabled for this call.");
}
}
}
@Override
public World getWorld(String world, DimensionType type, boolean autoload) {
if (world == null || world.isEmpty()) {
// assume that the world is the default world
world = Configuration.getServerConfig().getDefaultWorldName();
}
final String worldId = world + "_" + type.getName();
if (this.worldIsLoaded(worldId)) {
return this.loadedWorlds.get(worldId);
} else {
if (this.worldExists(worldId) && autoload) {
log.debug("World exists but is not loaded. Loading ...");
return this.loadWorld(world, type);
} else {
if (autoload) {
log.debug("World does not exist, we can autoload, will load!");
this.createWorld(world, type);
return this.loadedWorlds.get(worldId);
} else {
throw new UnknownWorldException("Tried to get a none existent world: " + world + " (" + type.toString() +
") either use autoload or have it pre-created!");
}
}
}
}
@Override
public boolean createWorld(String name, DimensionType type) {
return this.createWorld(name, System.currentTimeMillis(), type, WorldType.DEFAULT);
}
@Override
public boolean createWorld(String name, long seed, DimensionType type) {
return this.createWorld(name, seed, type, WorldType.DEFAULT);
}
@Override
public boolean createWorld(String name, long seed, DimensionType dimensionType, WorldType worldType) {
WorldConfiguration worldConfiguration = WorldConfiguration.create(name, dimensionType);
if (worldConfiguration == null) {
log.debug("World configuration already exists for " + name + "_" + dimensionType.getName());
worldConfiguration = Configuration.getWorldConfig(name + "_" + dimensionType.getName());
} else {
log.debug("Updating world configuration for " + name + "_" + dimensionType.getName());
worldConfiguration.getFile().setLong("world-seed", seed);
worldConfiguration.getFile().setString("world-type", worldType.toString());
}
return this.createWorld(worldConfiguration);
}
@Override
public boolean createWorld(WorldConfiguration worldConfiguration) {
if (worldConfiguration == null) {
return false;
}
final MinecraftServer minecraftServer = MinecraftServer.getServer();
final String worldFqName = worldConfiguration.getFile().getFileName().replace(".cfg", "");
final String worldName = worldFqName.replaceAll("_.+", "");
final DimensionType dimensionType = DimensionType.fromName(worldFqName.replaceAll(".+_", ""));
final long seed = worldConfiguration.getWorldSeed().matches("\\d+") ?
Long.valueOf(worldConfiguration.getWorldSeed()) : worldConfiguration.getWorldSeed().hashCode();
final AnvilSaveHandler saveHandler = new AnvilSaveHandler(WORLDS_DIR, worldName, true);
final WorldSettings worldSettings = new WorldSettings(
seed,
GameModeConverter.of(worldConfiguration.getGameMode()),
worldConfiguration.generatesStructures(),
false,
net.minecraft.world.WorldType.parseWorldType(worldConfiguration.getWorldType().toString()));
final WorldInfo worldInfo = new WorldInfo(worldSettings, worldName);
((IMixinWorldInfo) worldInfo).setDimensionType(dimensionType);
final WorldServer worldServer;
if (dimensionType == DimensionType.NORMAL) {
worldServer = new WorldServer(minecraftServer, saveHandler, worldInfo, dimensionType.getId(),
minecraftServer.theProfiler);
} else {
worldServer = new WorldServerMulti(minecraftServer, saveHandler, dimensionType.getId(),
(WorldServer) this.getWorld(worldName, DimensionType.NORMAL, true), minecraftServer.theProfiler);
((IMixinWorld) worldServer).setWorldInfo(worldInfo);
}
worldServer.initialize(worldSettings);
worldServer.addWorldAccess(new net.minecraft.world.WorldManager(minecraftServer, worldServer));
worldServer.getWorldInfo().setGameType(GameModeConverter.of(worldConfiguration.getGameMode()));
minecraftServer.getConfigurationManager().setPlayerManager(new WorldServer[] { worldServer });
worldServer.getWorldInfo().setDifficulty(EnumDifficulty.getDifficultyEnum(worldConfiguration.getDifficulty().getId()));
((IMixinMinecraftServer) minecraftServer).prepareSpawnArea(worldServer);
this.existingWorlds.add(worldName + "_" + dimensionType.getName());
this.addWorld(worldServer);
new LoadWorldHook((World) worldServer).call();
return true;
}
@Override
public boolean destroyWorld(String name) {
final File worldDir = new File(WORLDS_DIR, name.replaceAll("_.+", "") + "/" + name);
final File backupDir = new File(WORLDS_BACKUP_DIR, name + "(" + System.currentTimeMillis() + ")");
final boolean success = backupDir.mkdirs() && worldDir.renameTo(new File(backupDir, worldDir.getName()));
if (success) {
this.existingWorlds.remove(name);
}
return success;
}
@Override
public World loadWorld(String s, DimensionType dimensionType) {
return null;
}
@Override
public void unloadWorld(String s, DimensionType dimensionType, boolean b) {
}
@Override
public Collection<World> getAllWorlds() {
return Lists.newArrayList(this.loadedWorlds.values());
}
@Override
public boolean worldIsLoaded(String s) {
return this.loadedWorlds.containsKey(s);
}
@Override
public boolean worldIsLoaded(String name, DimensionType type) {
return this.worldIsLoaded(name + "_" + type.getName());
}
@Override
public boolean worldExists(String name) {
return this.existingWorlds.contains(name);
}
@Override
public List<String> getExistingWorlds() {
return Lists.newArrayList(this.existingWorlds);
}
@Override
public String[] getExistingWorldsArray() {
return this.existingWorlds.toArray(new String[this.existingWorlds.size()]);
}
@Override
public String[] getLoadedWorldsNames() {
return this.loadedWorlds.keySet().toArray(new String[this.loadedWorlds.keySet().size()]);
}
@Override
public String[] getLoadedWorldsNamesOfDimension(DimensionType dimensionType) {
final List<String> loadedWorlds = this.loadedWorlds.values().stream()
.filter(w -> w.getType() == dimensionType)
.map(World::getFqName)
.collect(Collectors.toList());
return loadedWorlds.toArray(new String[loadedWorlds.size()]);
}
}