1 /*
2  * Copyright (c) 2017-2020 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: Copyright (c) 2017-2020 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/sel-level/sel/level/level.d, sel/level/level.d)
28  */
29 module sel.level.level;
30 
31 import std.conv : to, ConvException;
32 import std.path : buildNormalizedPath, dirSeparator;
33 
34 import sel.level.data;
35 import sel.level.exception : LevelInfoException, ChunkException;
36 import sel.level.util;
37 
38 import sel.math : Vector2;
39 
40 import sel.nbt.tags : Named, Compound;
41 
42 abstract class Level {
43 
44 	public immutable string path;
45 
46 	private bool levelInfoLoaded = false;
47 	private LevelInfo _levelInfo;
48 
49 	public Chunk[Vector2!int] chunks;
50 
51 	public this(string path) {
52 		this.path = buildNormalizedPath(path) ~ dirSeparator;
53 	}
54 
55 	/**
56 	 * Throws: LevelInfoExeption if level.dat has an invalid format.
57 	 */
58 	public final @property ref LevelInfo levelInfo() {
59 		if(!this.levelInfoLoaded) this.reloadLevelInfo();
60 		return this._levelInfo;
61 	}
62 
63 	public final @property ref LevelInfo levelInfo(LevelInfo levelInfo) {
64 		this.levelInfoLoaded = true;
65 		return this._levelInfo = _levelInfo;
66 	}
67 
68 	public final void reloadLevelInfo() {
69 		this.levelInfoLoaded = true;
70 		this._levelInfo = this.readLevelInfo();
71 	}
72 
73 	protected abstract LevelInfo readLevelInfo();
74 
75 	protected abstract void writeLevelInfo(LevelInfo);
76 
77 	/**
78 	 * Reads the chunk at the given coordinates.
79 	 * Throws: ChunkException
80 	 */
81 	public final Chunk readChunk(Dimension dimension, Vector2!int position) {
82 		return this.readChunkImpl(dimension, position);
83 	}
84 
85 	/// ditto
86 	public final Chunk readChunk(Dimension dimension, int x, int z) {
87 		return this.readChunk(dimension, Vector2!int(x, z));
88 	}
89 
90 	/// ditto
91 	public final Chunk readChunk(Vector2!int position) {
92 		return this.readChunk(Dimension.overworld, position);
93 	}
94 
95 	/// ditto
96 	public final Chunk readChunk(int x, int z) {
97 		return this.readChunk(Vector2!int(x, z));
98 	}
99 
100 	protected abstract Chunk readChunkImpl(Dimension, Vector2!int);
101 
102 	/**
103 	 * Reads all chunks in the level.
104 	 * Throws: ChunkException
105 	 */
106 	public final ReadChunksResult readChunks(Dimension dimension=Dimension.overworld) {
107 		return this.readChunksImpl(dimension);
108 	}
109 
110 	protected abstract ReadChunksResult readChunksImpl(Dimension);
111 
112 }
113 
114 struct ReadChunksResult {
115 
116 	Chunk[Vector2!int] chunks;
117 	ChunkException[] exceptions;
118 
119 }
120 
121 LevelInfo readLevelInfoCompound(Info...)(Compound compound) if(Info.length % 3 == 0) {
122 	LevelInfo ret;
123 	foreach(i, T; Info) {
124 		static if(i % 3 == 0) {
125 			auto tag = Info[i+2] in compound;
126 			if(tag && cast(T)*tag) {
127 				try {
128 					mixin("ret." ~ Info[i+1]) = to!(typeof(mixin("ret." ~ Info[i+1])))((cast(T)*tag).value);
129 				} catch(ConvException) {
130 					throw new LevelInfoException(LevelInfoException.WRONG_VALUE, "Tag " ~ Info[i+2] ~ " cannot be converted to " ~ typeof(mixin("ret." ~ Info[i+1])).stringof);
131 				}
132 			}
133 		}
134 	}
135 	return ret;
136 }
137 
138 Compound writeLevelInfoCompound(Info...)(LevelInfo levelInfo) if(Info.length % 3 == 0) {
139 	Compound ret = new Compound();
140 	foreach(i, T; Info) {
141 		static if(i % 3 == 0) ret[] = new Named!T(Info[i+2], mixin("levelInfo." ~ Info[i+1]));
142 	}
143 	return ret;
144 }