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}