001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.world; 019 020import com.google.common.base.Preconditions; 021import net.tridentsdk.Trident; 022import net.tridentsdk.docs.InternalUseOnly; 023import net.tridentsdk.server.world.gen.DefaultWorldGen; 024import net.tridentsdk.server.world.gen.brush.OakTreeBrush; 025import net.tridentsdk.server.world.gen.brush.TallGrassBrush; 026import net.tridentsdk.util.TridentLogger; 027import net.tridentsdk.world.Chunk; 028import net.tridentsdk.world.ChunkLocation; 029import net.tridentsdk.world.World; 030import net.tridentsdk.world.WorldLoader; 031import net.tridentsdk.world.gen.ChunkGenerator; 032import net.tridentsdk.world.gen.FeatureGenerator; 033import net.tridentsdk.world.settings.WorldCreateOptions; 034 035import java.io.File; 036import java.io.IOException; 037import java.lang.reflect.Constructor; 038import java.lang.reflect.InvocationTargetException; 039import java.nio.charset.Charset; 040import java.nio.file.Files; 041import java.nio.file.Path; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.List; 045import java.util.Map; 046import java.util.concurrent.ConcurrentHashMap; 047import java.util.concurrent.CopyOnWriteArrayList; 048import java.util.concurrent.ThreadLocalRandom; 049 050/** 051 * The world loading class, creates, saves, handles worlds 052 * 053 * @author The TridentSDK Team 054 */ 055public class TridentWorldLoader implements WorldLoader { 056 private static final ChunkGenerator DEFAULT_GEN = new DefaultWorldGen(ThreadLocalRandom.current().nextLong()); 057 public static final Map<String, TridentWorld> WORLDS = new ConcurrentHashMap<>(); 058 059 private final WorldCreateOptions opt; 060 private final List<FeatureGenerator> brushes = new CopyOnWriteArrayList<>(); 061 private volatile ChunkGenerator generator; 062 volatile TridentWorld world; 063 064 public TridentWorldLoader(WorldCreateOptions opt) { 065 this.opt = opt; 066 } 067 068 public TridentWorldLoader() { 069 this.opt = new WorldCreateOptions(); 070 this.opt.generator(DefaultWorldGen.class); 071 } 072 073 public Collection<TridentWorld> worlds() { 074 return WORLDS.values(); 075 } 076 077 // Prevents this reference from escaping during construction 078 // besides, user created WorldLoaders should not re-create 079 // the world 080 @InternalUseOnly 081 public static void loadAll() { 082 TridentLogger.get().log("Loading worlds..."); 083 for (File file : Trident.fileContainer().toFile().listFiles()) { 084 if (!(file.isDirectory()) || file.getName().contains(" ")) 085 continue; 086 087 boolean isWorld = false; 088 089 for (File f : file.listFiles()) { 090 if (f.getName().equals("level.dat")) { 091 isWorld = true; 092 continue; 093 } 094 095 if (f.getName().equals("gensig")) { 096 String className = null; 097 098 try { 099 byte[] sig = Files.readAllBytes( 100 Trident.fileContainer().resolve(file.getName()).resolve("gensig")); 101 className = new String(sig); 102 if (!className.equals(DEFAULT_GEN.getClass().getName())) { 103 // Create a new loader with that class, don't load it with this one 104 new TridentWorldLoader(Class.forName(className).asSubclass(WorldCreateOptions.class).newInstance()) 105 .load(file.getName()); 106 isWorld = false; 107 } 108 } catch (IOException e) { 109 TridentLogger.get().error(e); 110 isWorld = true; 111 } catch (ClassNotFoundException e) { 112 TridentLogger.get().error("Could not find loader " + className + ", resorting to default"); 113 TridentLogger.get().error(e); 114 115 // Nevermind, load with this one anyways 116 isWorld = true; 117 } catch (InstantiationException e) { 118 e.printStackTrace(); 119 } catch (IllegalAccessException e) { 120 e.printStackTrace(); 121 } 122 } 123 } 124 125 if (!(isWorld)) 126 continue; 127 128 Path gensig = Trident.fileContainer().resolve(file.getName()).resolve("gensig"); 129 if (!Files.exists(gensig)) { 130 try { 131 Files.createFile(gensig); 132 Files.write(gensig, DEFAULT_GEN.getClass().getName().getBytes(Charset.defaultCharset())); 133 } catch (IOException e) { 134 TridentLogger.get().error("Could not write gensig file"); 135 TridentLogger.get().error(e); 136 } 137 } 138 new TridentWorldLoader().load(file.getName()); 139 } 140 if (WORLDS.size() == 0) { 141 TridentLogger.get().error("No worlds found, there is no world loaded!"); 142 } 143 TridentLogger.get().log("Finished loading worlds!"); 144 } 145 146 @Override 147 public World load(String world) { 148 Preconditions.checkArgument(this.world == null, "This WorldLoader has already loadd a world"); 149 TridentWorld w = new TridentWorld(world, this); 150 WORLDS.put(world, w); 151 152 return w; 153 } 154 155 @Override 156 public void save() { 157 checkNotNull(); 158 world.save(); 159 // TODO save player and entity data 160 // consider saving the STATE instead 161 } 162 163 @Override 164 public World createWorld(String name) { 165 Preconditions.checkArgument(world == null, "This WorldLoader has already loaded a world"); 166 167 if (WorldLoader.worldExists(name)) { 168 TridentLogger.get().error(new IllegalArgumentException("Cannot create a duplicate world name")); 169 return null; 170 } 171 172 // TODO load world settings 173 TridentWorld world = TridentWorld.createWorld(name, this); 174 WORLDS.put(name, world); 175 176 return world; 177 } 178 179 @Override 180 public boolean chunkExists(int x, int z) { 181 checkNotNull(); 182 return new File(world.name() + "/region/", WorldUtils.regionFile(x, z)).exists(); 183 } 184 185 @Override 186 public boolean chunkExists(ChunkLocation location) { 187 return this.chunkExists(location.x(), location.z()); 188 } 189 190 @Override 191 public Chunk loadChunk(int x, int z) { 192 return this.loadChunk(ChunkLocation.create(x, z)); 193 } 194 195 @Override 196 public TridentChunk loadChunk(ChunkLocation location) { 197 checkNotNull(); 198 return RegionFile.fromPath(world.name(), location).loadChunkData(world, location); 199 } 200 201 @Override 202 public void saveChunk(Chunk chunk) { 203 RegionFile.fromPath(chunk.world().name(), chunk.location()) 204 .saveChunkData((TridentChunk) chunk); 205 } 206 207 @Override 208 public WorldCreateOptions options() { 209 return opt; 210 } 211 212 public void setGenerator(long seed) { 213 ChunkGenerator gen; 214 Class<? extends ChunkGenerator> generatorClass = opt.generator(); 215 try { 216 Constructor<? extends ChunkGenerator> g = generatorClass.getDeclaredConstructor(long.class); 217 gen = g.newInstance(seed); 218 Collections.addAll(brushes, new TallGrassBrush(seed), new OakTreeBrush(seed)); 219 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 220 TridentLogger.get().error("Error occurred while instantiating generator " + generatorClass.getName()); 221 TridentLogger.get().error("Switching to the default"); 222 TridentLogger.get().error(e); 223 gen = DEFAULT_GEN; 224 } catch (NoSuchMethodException e) { 225 TridentLogger.get().error("Provided generator does not have a default constructor"); 226 TridentLogger.get().error("Switching to the default"); 227 TridentLogger.get().error(e); 228 gen = DEFAULT_GEN; 229 } 230 231 this.generator = gen; 232 } 233 234 @Override 235 public ChunkGenerator generator() { 236 return generator; 237 } 238 239 @Override 240 public List<FeatureGenerator> brushes() { 241 return brushes; 242 } 243 244 private void checkNotNull() { 245 Preconditions.checkArgument(world != null, "The current world must not be null"); 246 } 247}