aboutsummaryrefslogtreecommitdiff
path: root/src/unittest/test_noise.cpp
blob: 421f3b66e5c29442b1841c7ef049336f225dbb00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "test.h"

#include <cmath>
#include "exceptions.h"
#include "noise.h"

class TestNoise : public TestBase {
public:
	TestNoise() { TestManager::registerTestModule(this); }
	const char *getName() { return "TestNoise"; }

	void runTests(IGameDef *gamedef);

	void testNoise2dPoint();
	void testNoise2dBulk();
	void testNoise3dPoint();
	void testNoise3dBulk();
	void testNoiseInvalidParams();

	static const float expected_2d_results[10 * 10];
	static const float expected_3d_results[10 * 10 * 10];
};

static TestNoise g_test_instance;

void TestNoise::runTests(IGameDef *gamedef)
{
	TEST(testNoise2dPoint);
	TEST(testNoise2dBulk);
	TEST(testNoise3dPoint);
	TEST(testNoise3dBulk);
	TEST(testNoiseInvalidParams);
}

////////////////////////////////////////////////////////////////////////////////

void TestNoise::testNoise2dPoint()
{
	NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9,  5, 0.6, 2.0);

	u32 i = 0;
	for (u32 y = 0; y != 10; y++)
	for (u32 x = 0; x != 10; x++, i++) {
		float actual   = NoisePerlin2D(&np_normal, x, y, 1337);
		float expected = expected_2d_results[i];
		UASSERT(std::fabs(actual - expected) <= 0.00001);
	}
}

void TestNoise::testNoise2dBulk()
{
	NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9,  5, 0.6, 2.0);
	Noise noise_normal_2d(&np_normal, 1337, 10, 10);
	float *noisevals = noise_normal_2d.perlinMap2D(0, 0, NULL);

	for (u32 i = 0; i != 10 * 10; i++) {
		float actual   = noisevals[i];
		float expected = expected_2d_results[i];
		UASSERT(std::fabs(actual - expected) <= 0.00001);
	}
}

void TestNoise::testNoise3dPoint()
{
	NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9,  5, 0.6, 2.0);

	u32 i = 0;
	for (u32 z = 0; z != 10; z++)
	for (u32 y = 0; y != 10; y++)
	for (u32 x = 0; x != 10; x++, i++) {
		float actual   = NoisePerlin3D(&np_normal, x, y, z, 1337);
		float expected = expected_3d_results[i];
		UASSERT(std::fabs(actual - expected) <= 0.00001);
	}
}

void TestNoise::testNoise3dBulk()
{
	NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0);
	Noise noise_normal_3d(&np_normal, 1337, 10, 10, 10);
	float *noisevals = noise_normal_3d.perlinMap3D(0, 0, 0, NULL);

	for (u32 i = 0; i != 10 * 10 * 10; i++) {
		float actual   = noisevals[i];
		float expected = expected_3d_results[i];
		UASSERT(std::fabs(actual - expected) <= 0.00001);
	}
}

void TestNoise::testNoiseInvalidParams()
{
	bool exception_thrown = false;

	try {
		NoiseParams np_highmem(4, 70, v3f(1, 1, 1), 5, 60, 0.7, 10.0);
		Noise noise_highmem_3d(&np_highmem, 1337, 200, 200, 200);
		noise_highmem_3d.perlinMap3D(0, 0, 0, NULL);
	} catch (InvalidNoiseParamsException &) {
		exception_thrown = true;
	}

	UASSERT(exception_thrown);
}

const float TestNoise::expected_2d_results[10 * 10] = {
	19.11726, 18.49626, 16.48476, 15.02135, 14.75713, 16.26008, 17.54822,
	18.06860, 18.57016, 18.48407, 18.49649, 17.89160, 15.94162, 14.54901,
	14.31298, 15.72643, 16.94669, 17.55494, 18.58796, 18.87925, 16.08101,
	15.53764, 13.83844, 12.77139, 12.73648, 13.95632, 14.97904, 15.81829,
	18.37694, 19.73759, 13.19182, 12.71924, 11.34560, 10.78025, 11.18980,
	12.52303, 13.45012, 14.30001, 17.43298, 19.15244, 10.93217, 10.48625,
	 9.30923,  9.18632, 10.16251, 12.11264, 13.19697, 13.80801, 16.39567,
	17.66203, 10.40222,  9.86070,  8.47223,  8.45471, 10.04780, 13.54730,
	15.33709, 15.48503, 16.46177, 16.52508, 10.80333, 10.19045,  8.59420,
	 8.47646, 10.22676, 14.43173, 16.48353, 16.24859, 16.20863, 15.52847,
	11.01179, 10.45209,  8.98678,  8.83986, 10.43004, 14.46054, 16.29387,
	15.73521, 15.01744, 13.85542, 10.55201, 10.33375,  9.85102, 10.07821,
	11.58235, 15.62046, 17.35505, 16.13181, 12.66011,  9.51853, 11.50994,
	11.54074, 11.77989, 12.29790, 13.76139, 17.81982, 19.49008, 17.79470,
	12.34344,  7.78363,
};

const float TestNoise::expected_3d_results[10 * 10 * 10] = {
	19.11726, 18.01059, 16.90392, 15.79725, 16.37154, 17.18597, 18.00040,
	18.33467, 18.50889, 18.68311, 17.85386, 16.90585, 15.95785, 15.00985,
	15.61132, 16.43415, 17.25697, 17.95415, 18.60942, 19.26471, 16.59046,
	15.80112, 15.01178, 14.22244, 14.85110, 15.68232, 16.51355, 17.57361,
	18.70996, 19.84631, 15.32705, 14.69638, 14.06571, 13.43504, 14.09087,
	14.93050, 15.77012, 17.19309, 18.81050, 20.42790, 15.06729, 14.45855,
	13.84981, 13.24107, 14.39364, 15.79782, 17.20201, 18.42640, 19.59085,
	20.75530, 14.95090, 14.34456, 13.73821, 13.13187, 14.84825, 16.89645,
	18.94465, 19.89025, 20.46832, 21.04639, 14.83452, 14.23057, 13.62662,
	13.02267, 15.30287, 17.99508, 20.68730, 21.35411, 21.34580, 21.33748,
	15.39817, 15.03590, 14.67364, 14.31137, 16.78242, 19.65824, 22.53405,
	22.54626, 21.60395, 20.66164, 16.18850, 16.14768, 16.10686, 16.06603,
	18.60362, 21.50956, 24.41549, 23.64784, 21.65566, 19.66349, 16.97884,
	17.25946, 17.54008, 17.82069, 20.42482, 23.36088, 26.29694, 24.74942,
	21.70738, 18.66534, 18.78506, 17.51834, 16.25162, 14.98489, 15.14217,
	15.50287, 15.86357, 16.40597, 17.00895, 17.61193, 18.20160, 16.98795,
	15.77430, 14.56065, 14.85059, 15.35533, 15.86007, 16.63399, 17.49763,
	18.36128, 17.61814, 16.45757, 15.29699, 14.13641, 14.55902, 15.20779,
	15.85657, 16.86200, 17.98632, 19.11064, 17.03468, 15.92718, 14.81968,
	13.71218, 14.26744, 15.06026, 15.85306, 17.09001, 18.47501, 19.86000,
	16.67870, 15.86256, 15.04641, 14.23026, 15.31397, 16.66909, 18.02420,
	18.89042, 19.59369, 20.29695, 16.35522, 15.86447, 15.37372, 14.88297,
	16.55165, 18.52883, 20.50600, 20.91547, 20.80237, 20.68927, 16.03174,
	15.86639, 15.70103, 15.53568, 17.78933, 20.38857, 22.98780, 22.94051,
	22.01105, 21.08159, 16.42434, 16.61407, 16.80381, 16.99355, 19.16133,
	21.61169, 24.06204, 23.65252, 22.28970, 20.92689, 17.05562, 17.61035,
	18.16508, 18.71981, 20.57809, 22.62260, 24.66711, 23.92686, 22.25835,
	20.58984, 17.68691, 18.60663, 19.52635, 20.44607, 21.99486, 23.63352,
	25.27217, 24.20119, 22.22699, 20.25279, 18.45285, 17.02608, 15.59931,
	14.17254, 13.91279, 13.81976, 13.72674, 14.47727, 15.50900, 16.54073,
	18.54934, 17.07005, 15.59076, 14.11146, 14.08987, 14.27651, 14.46316,
	15.31383, 16.38584, 17.45785, 18.64582, 17.11401, 15.58220, 14.05039,
	14.26694, 14.73326, 15.19958, 16.15038, 17.26268, 18.37498, 18.74231,
	17.15798, 15.57364, 13.98932, 14.44402, 15.19001, 15.93600, 16.98694,
	18.13952, 19.29210, 18.29012, 17.26656, 16.24301, 15.21946, 16.23430,
	17.54035, 18.84639, 19.35445, 19.59653, 19.83860, 17.75954, 17.38438,
	17.00923, 16.63407, 18.25505, 20.16120, 22.06734, 21.94068, 21.13642,
	20.33215, 17.22896, 17.50220, 17.77544, 18.04868, 20.27580, 22.78205,
	25.28829, 24.52691, 22.67631, 20.82571, 17.45050, 18.19224, 18.93398,
	19.67573, 21.54024, 23.56514, 25.59004, 24.75878, 22.97546, 21.19213,
	17.92274, 19.07302, 20.22330, 21.37358, 22.55256, 23.73565, 24.91873,
	24.20587, 22.86103, 21.51619, 18.39499, 19.95381, 21.51263, 23.07145,
	23.56490, 23.90615, 24.24741, 23.65296, 22.74660, 21.84024, 18.12065,
	16.53382, 14.94700, 13.36018, 12.68341, 12.13666, 11.58990, 12.54858,
	14.00906, 15.46955, 18.89708, 17.15214, 15.40721, 13.66227, 13.32914,
	13.19769, 13.06625, 13.99367, 15.27405, 16.55443, 19.67351, 17.77046,
	15.86741, 13.96436, 13.97486, 14.25873, 14.54260, 15.43877, 16.53904,
	17.63931, 20.44994, 18.38877, 16.32761, 14.26645, 14.62059, 15.31977,
	16.01895, 16.88387, 17.80403, 18.72419, 19.90153, 18.67057, 17.43962,
	16.20866, 17.15464, 18.41161, 19.66858, 19.81848, 19.59936, 19.38024,
	19.16386, 18.90429, 18.64473, 18.38517, 19.95845, 21.79357, 23.62868,
	22.96589, 21.47046, 19.97503, 18.42618, 19.13802, 19.84985, 20.56168,
	22.76226, 25.17553, 27.58879, 26.11330, 23.34156, 20.56982, 18.47667,
	19.77041, 21.06416, 22.35790, 23.91914, 25.51859, 27.11804, 25.86504,
	23.66121, 21.45738, 18.78986, 20.53570, 22.28153, 24.02736, 24.52704,
	24.84869, 25.17035, 24.48488, 23.46371, 22.44254, 19.10306, 21.30098,
	23.49890, 25.69682, 25.13494, 24.17879, 23.22265, 23.10473, 23.26621,
	23.42769, 17.93453, 16.72707, 15.51962, 14.31216, 12.96039, 11.58800,
	10.21561, 11.29675, 13.19573, 15.09471, 18.05853, 16.85308, 15.64762,
	14.44216, 13.72634, 13.08047, 12.43459, 13.48912, 15.11045, 16.73179,
	18.18253, 16.97908, 15.77562, 14.57217, 14.49229, 14.57293, 14.65357,
	15.68150, 17.02518, 18.36887, 18.30654, 17.10508, 15.90363, 14.70217,
	15.25825, 16.06540, 16.87255, 17.87387, 18.93991, 20.00595, 17.54117,
	17.32369, 17.10622, 16.88875, 18.07494, 19.46166, 20.84837, 21.12988,
	21.04298, 20.95609, 16.64874, 17.55554, 18.46234, 19.36913, 21.18461,
	23.12989, 25.07517, 24.53784, 23.17297, 21.80810, 15.75632, 17.78738,
	19.81845, 21.84951, 24.29427, 26.79812, 29.30198, 27.94580, 25.30295,
	22.66010, 15.98046, 18.43027, 20.88008, 23.32989, 25.21976, 27.02964,
	28.83951, 27.75863, 25.71416, 23.66970, 16.57679, 19.21017, 21.84355,
	24.47693, 25.41719, 26.11557, 26.81396, 26.37308, 25.55245, 24.73182,
	17.17313, 19.99008, 22.80702, 25.62397, 25.61462, 25.20151, 24.78840,
	24.98753, 25.39074, 25.79395, 17.76927, 17.01824, 16.26722, 15.51620,
	13.45256, 11.20141,  8.95025, 10.14162, 12.48049, 14.81936, 17.05051,
	16.49955, 15.94860, 15.39764, 14.28896, 13.10061, 11.91225, 13.10109,
	15.08232, 17.06355, 16.33175, 15.98086, 15.62998, 15.27909, 15.12537,
	14.99981, 14.87425, 16.06056, 17.68415, 19.30775, 15.61299, 15.46217,
	15.31136, 15.16054, 15.96177, 16.89901, 17.83625, 19.02003, 20.28599,
	21.55194, 14.61341, 15.58383, 16.55426, 17.52469, 18.99524, 20.53725,
	22.07925, 22.56233, 22.69243, 22.82254, 13.57371, 15.79697, 18.02024,
	20.24351, 22.34258, 24.42392, 26.50526, 26.18790, 25.07097, 23.95404,
	12.53401, 16.01011, 19.48622, 22.96232, 25.68993, 28.31060, 30.93126,
	29.81347, 27.44951, 25.08555, 12.98106, 16.67323, 20.36540, 24.05756,
	26.36633, 28.47748, 30.58862, 29.76471, 27.96244, 26.16016, 13.92370,
	17.48634, 21.04897, 24.61161, 26.15244, 27.40443, 28.65643, 28.49117,
	27.85349, 27.21581, 14.86633, 18.29944, 21.73255, 25.16566, 25.93854,
	26.33138, 26.72423, 27.21763, 27.74455, 28.27147, 17.60401, 17.30942,
	17.01482, 16.72023, 13.94473, 10.81481,  7.68490,  8.98648, 11.76524,
	14.54400, 16.04249, 16.14603, 16.24958, 16.35312, 14.85158, 13.12075,
	11.38991, 12.71305, 15.05418, 17.39531, 14.48097, 14.98265, 15.48433,
	15.98602, 15.75844, 15.42668, 15.09493, 16.43962, 18.34312, 20.24663,
	12.91945, 13.81927, 14.71909, 15.61891, 16.66530, 17.73262, 18.79995,
	20.16619, 21.63206, 23.09794, 11.68565, 13.84398, 16.00230, 18.16062,
	19.91554, 21.61284, 23.31013, 23.99478, 24.34188, 24.68898, 10.49868,
	14.03841, 17.57814, 21.11788, 23.50056, 25.71795, 27.93534, 27.83796,
	26.96897, 26.09999,  9.31170, 14.23284, 19.15399, 24.07513, 27.08558,
	29.82307, 32.56055, 31.68113, 29.59606, 27.51099,  9.98166, 14.91619,
	19.85071, 24.78524, 27.51291, 29.92532, 32.33773, 31.77077, 30.21070,
	28.65063, 11.27060, 15.76250, 20.25440, 24.74629, 26.88768, 28.69329,
	30.49889, 30.60925, 30.15453, 29.69981, 12.55955, 16.60881, 20.65808,
	24.70735, 26.26245, 27.46126, 28.66005, 29.44773, 30.09835, 30.74898,
	15.20134, 15.53016, 15.85898, 16.18780, 13.53087, 10.44740,  7.36393,
	 8.95806, 12.11139, 15.26472, 13.87432, 14.52378, 15.17325, 15.82272,
	14.49093, 12.87611, 11.26130, 12.73342, 15.23453, 17.73563, 12.54730,
	13.51741, 14.48752, 15.45763, 15.45100, 15.30483, 15.15867, 16.50878,
	18.35766, 20.20654, 11.22027, 12.51103, 13.80179, 15.09254, 16.41106,
	17.73355, 19.05603, 20.28415, 21.48080, 22.67745, 10.27070, 12.53633,
	14.80195, 17.06758, 19.04654, 20.98454, 22.92254, 23.63840, 23.94687,
	24.25534,  9.37505, 12.70901, 16.04297, 19.37693, 21.92136, 24.35300,
	26.78465, 26.93249, 26.31907, 25.70565,  8.47939, 12.88168, 17.28398,
	21.68627, 24.79618, 27.72146, 30.64674, 30.22658, 28.69127, 27.15597,
	 9.77979, 13.97583, 18.17186, 22.36790, 25.18828, 27.81215, 30.43601,
	30.34293, 29.34420, 28.34548, 11.81220, 15.37712, 18.94204, 22.50695,
	24.75282, 26.81024, 28.86766, 29.40003, 29.42404, 29.44806, 13.84461,
	16.77841, 19.71221, 22.64601, 24.31735, 25.80833, 27.29932, 28.45713,
	29.50388, 30.55064, 12.05287, 13.06077, 14.06866, 15.07656, 12.81500,
	10.08638,  7.35776,  9.30520, 12.81134, 16.31747, 11.31943, 12.47863,
	13.63782, 14.79702, 13.82253, 12.54323, 11.26392, 12.88993, 15.48436,
	18.07880, 10.58600, 11.89649, 13.20698, 14.51747, 14.83005, 15.00007,
	15.17009, 16.47465, 18.15739, 19.84013,  9.85256, 11.31435, 12.77614,
	14.23793, 15.83757, 17.45691, 19.07625, 20.05937, 20.83042, 21.60147,
	 9.36002, 11.37275, 13.38548, 15.39822, 17.58109, 19.78828, 21.99546,
	22.68573, 22.87036, 23.05500,  8.90189, 11.52266, 14.14343, 16.76420,
	19.42976, 22.10172, 24.77368, 25.17519, 24.81987, 24.46455,  8.44375,
	11.67256, 14.90137, 18.13018, 21.27843, 24.41516, 27.55190, 27.66464,
	26.76937, 25.87411, 10.51042, 13.30769, 16.10496, 18.90222, 21.70659,
	24.51197, 27.31734, 27.77045, 27.43945, 27.10846, 13.41869, 15.43789,
	17.45709, 19.47628, 21.66124, 23.86989, 26.07853, 27.08170, 27.68305,
	28.28440, 16.32697, 17.56809, 18.80922, 20.05033, 21.61590, 23.22781,
	24.83972, 26.39296, 27.92665, 29.46033,  8.90439, 10.59137, 12.27835,
	13.96532, 12.09914,  9.72536,  7.35159,  9.65235, 13.51128, 17.37022,
	 8.76455, 10.43347, 12.10239, 13.77132, 13.15412, 12.21033, 11.26655,
	13.04643, 15.73420, 18.42198,  8.62470, 10.27557, 11.92644, 13.57731,
	14.20910, 14.69531, 15.18151, 16.44051, 17.95712, 19.47373,  8.48485,
	10.11767, 11.75049, 13.38331, 15.26408, 17.18027, 19.09647, 19.83460,
	20.18004, 20.52548,  8.44933, 10.20917, 11.96901, 13.72885, 16.11565,
	18.59202, 21.06838, 21.73307, 21.79386, 21.85465,  8.42872, 10.33631,
	12.24389, 14.15147, 16.93816, 19.85044, 22.76272, 23.41788, 23.32067,
	23.22346,  8.40812, 10.46344, 12.51877, 14.57409, 17.76068, 21.10886,
	24.45705, 25.10269, 24.84748, 24.59226, 11.24106, 12.63955, 14.03805,
	15.43654, 18.22489, 21.21178, 24.19868, 25.19796, 25.53469, 25.87143,
	15.02519, 15.49866, 15.97213, 16.44560, 18.56967, 20.92953, 23.28940,
	24.76337, 25.94205, 27.12073, 18.80933, 18.35777, 17.90622, 17.45466,
	18.91445, 20.64729, 22.38013, 24.32880, 26.34941, 28.37003,
};
id='n2822' href='#n2822'>2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483
/*
Minetest-c55
Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/*
(c) 2010 Perttu Ahola <celeron55@gmail.com>
*/

#include "server.h"
#include "utility.h"
#include <iostream>
#include "clientserver.h"
#include "map.h"
#include "jmutexautolock.h"
#include "main.h"
#include "constants.h"
#include "voxel.h"
#include "materials.h"

#define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)

void * ServerThread::Thread()
{
	ThreadStarted();

	DSTACK(__FUNCTION_NAME);

	BEGIN_DEBUG_EXCEPTION_HANDLER

	while(getRun())
	{
		try{
			m_server->AsyncRunStep();
		
			//dout_server<<"Running m_server->Receive()"<<std::endl;
			m_server->Receive();
		}
		catch(con::NoIncomingDataException &e)
		{
		}
		catch(con::PeerNotFoundException &e)
		{
			dout_server<<"Server: PeerNotFoundException"<<std::endl;
		}
	}
	
	END_DEBUG_EXCEPTION_HANDLER

	return NULL;
}

void * EmergeThread::Thread()
{
	ThreadStarted();

	DSTACK(__FUNCTION_NAME);

	bool debug=false;
	
	BEGIN_DEBUG_EXCEPTION_HANDLER

	/*
		Get block info from queue, emerge them and send them
		to clients.

		After queue is empty, exit.
	*/
	while(getRun())
	{
		QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
		if(qptr == NULL)
			break;
		
		SharedPtr<QueuedBlockEmerge> q(qptr);

		v3s16 &p = q->pos;
		
		//derr_server<<"EmergeThread::Thread(): running"<<std::endl;

		//TimeTaker timer("block emerge");
		
		/*
			Try to emerge it from somewhere.

			If it is only wanted as optional, only loading from disk
			will be allowed.
		*/
		
		/*
			Check if any peer wants it as non-optional. In that case it
			will be generated.

			Also decrement the emerge queue count in clients.
		*/

		bool optional = true;

		{
			core::map<u16, u8>::Iterator i;
			for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
			{
				//u16 peer_id = i.getNode()->getKey();

				// Check flags
				u8 flags = i.getNode()->getValue();
				if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
					optional = false;
				
			}
		}

		/*dstream<<"EmergeThread: p="
				<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
				<<"optional="<<optional<<std::endl;*/
		
		ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
			
		core::map<v3s16, MapBlock*> changed_blocks;
		core::map<v3s16, MapBlock*> lighting_invalidated_blocks;

		MapBlock *block = NULL;
		bool got_block = true;
		core::map<v3s16, MapBlock*> modified_blocks;
		
		{//envlock

		//TimeTaker envlockwaittimer("block emerge envlock wait time");
		
		// 0-50ms
		JMutexAutoLock envlock(m_server->m_env_mutex);

		//envlockwaittimer.stop();

		//TimeTaker timer("block emerge (while env locked)");
			
		try{
			bool only_from_disk = false;
			
			if(optional)
				only_from_disk = true;
			
			// First check if the block already exists
			if(only_from_disk)
			{
				block = map.getBlockNoCreate(p);
			}

			if(block == NULL)
			{
				block = map.emergeBlock(
						p,
						only_from_disk,
						changed_blocks,
						lighting_invalidated_blocks);

#if 0
				/*
					EXPERIMENTAL: Create a few other blocks too
				*/
				
				map.emergeBlock(
						p + v3s16(0,1,0),
						only_from_disk,
						changed_blocks,
						lighting_invalidated_blocks);

				map.emergeBlock(
						p + v3s16(0,-1,0),
						only_from_disk,
						changed_blocks,
						lighting_invalidated_blocks);
#endif
			}

			// If it is a dummy, block was not found on disk
			if(block->isDummy())
			{
				//dstream<<"EmergeThread: Got a dummy block"<<std::endl;
				got_block = false;
			}
		}
		catch(InvalidPositionException &e)
		{
			// Block not found.
			// This happens when position is over limit.
			got_block = false;
		}
		
		if(got_block)
		{
			if(debug && changed_blocks.size() > 0)
			{
				dout_server<<DTIME<<"Got changed_blocks: ";
				for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
						i.atEnd() == false; i++)
				{
					MapBlock *block = i.getNode()->getValue();
					v3s16 p = block->getPos();
					dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
				}
				dout_server<<std::endl;
			}

#if 0
			/*
				Update water pressure
			*/

			m_server->UpdateBlockWaterPressure(block, modified_blocks);

			for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
					i.atEnd() == false; i++)
			{
				MapBlock *block = i.getNode()->getValue();
				m_server->UpdateBlockWaterPressure(block, modified_blocks);
				//v3s16 p = i.getNode()->getKey();
				//m_server->UpdateBlockWaterPressure(p, modified_blocks);
			}
#endif

			/*
				Collect a list of blocks that have been modified in
				addition to the fetched one.
			*/

			// Add all the "changed blocks" to modified_blocks
			for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
					i.atEnd() == false; i++)
			{
				MapBlock *block = i.getNode()->getValue();
				modified_blocks.insert(block->getPos(), block);
			}
			
			/*dstream<<"lighting "<<lighting_invalidated_blocks.size()
					<<" blocks"<<std::endl;*/
			
			//TimeTaker timer("** updateLighting", g_device);
			
			// Update lighting without locking the environment mutex,
			// add modified blocks to changed blocks
			map.updateLighting(lighting_invalidated_blocks, modified_blocks);
		}
		// If we got no block, there should be no invalidated blocks
		else
		{
			assert(lighting_invalidated_blocks.size() == 0);
		}

		}//envlock

		/*
			Set sent status of modified blocks on clients
		*/
	
		// NOTE: Server's clients are also behind the connection mutex
		JMutexAutoLock lock(m_server->m_con_mutex);

		/*
			Add the originally fetched block to the modified list
		*/
		if(got_block)
		{
			modified_blocks.insert(p, block);
		}
		
		/*
			Set the modified blocks unsent for all the clients
		*/
		
		for(core::map<u16, RemoteClient*>::Iterator
				i = m_server->m_clients.getIterator();
				i.atEnd() == false; i++)
		{
			RemoteClient *client = i.getNode()->getValue();
			
			if(modified_blocks.size() > 0)
			{
				// Remove block from sent history
				client->SetBlocksNotSent(modified_blocks);
			}
		}
		
	}

	END_DEBUG_EXCEPTION_HANDLER

	return NULL;
}

void RemoteClient::GetNextBlocks(Server *server, float dtime,
		core::array<PrioritySortedBlockTransfer> &dest)
{
	DSTACK(__FUNCTION_NAME);
	
	// Increment timers
	{
		JMutexAutoLock lock(m_blocks_sent_mutex);
		m_nearest_unsent_reset_timer += dtime;
	}

	// Won't send anything if already sending
	{
		JMutexAutoLock lock(m_blocks_sending_mutex);
		
		if(m_blocks_sending.size() >= g_settings.getU16
				("max_simultaneous_block_sends_per_client"))
		{
			//dstream<<"Not sending any blocks, Queue full."<<std::endl;
			return;
		}
	}

	bool haxmode = g_settings.getBool("haxmode");
	
	Player *player = server->m_env.getPlayer(peer_id);

	assert(player != NULL);

	v3f playerpos = player->getPosition();
	v3f playerspeed = player->getSpeed();

	v3s16 center_nodepos = floatToInt(playerpos);

	v3s16 center = getNodeBlockPos(center_nodepos);
	
	// Camera position and direction
	v3f camera_pos =
			playerpos + v3f(0, BS+BS/2, 0);
	v3f camera_dir = v3f(0,0,1);
	camera_dir.rotateYZBy(player->getPitch());
	camera_dir.rotateXZBy(player->getYaw());

	/*
		Get the starting value of the block finder radius.
	*/
	s16 last_nearest_unsent_d;
	s16 d_start;
	{
		JMutexAutoLock lock(m_blocks_sent_mutex);
		
		if(m_last_center != center)
		{
			m_nearest_unsent_d = 0;
			m_last_center = center;
		}

		/*dstream<<"m_nearest_unsent_reset_timer="
				<<m_nearest_unsent_reset_timer<<std::endl;*/
		if(m_nearest_unsent_reset_timer > 5.0)
		{
			m_nearest_unsent_reset_timer = 0;
			m_nearest_unsent_d = 0;
			//dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
		}

		last_nearest_unsent_d = m_nearest_unsent_d;
		
		d_start = m_nearest_unsent_d;
	}

	u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
			("max_simultaneous_block_sends_per_client");
	u16 maximum_simultaneous_block_sends = 
			maximum_simultaneous_block_sends_setting;

	/*
		Check the time from last addNode/removeNode.
		
		Decrease send rate if player is building stuff.
	*/
	{
		SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
		m_time_from_building.m_value += dtime;
		/*if(m_time_from_building.m_value
				< FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)*/
		if(m_time_from_building.m_value < g_settings.getFloat(
					"full_block_send_enable_min_time_from_building"))
		{
			maximum_simultaneous_block_sends
				= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
		}
	}
	
	u32 num_blocks_selected;
	{
		JMutexAutoLock lock(m_blocks_sending_mutex);
		num_blocks_selected = m_blocks_sending.size();
	}
	
	/*
		next time d will be continued from the d from which the nearest
		unsent block was found this time.

		This is because not necessarily any of the blocks found this
		time are actually sent.
	*/
	s32 new_nearest_unsent_d = -1;

	// Serialization version used
	//u8 ser_version = serialization_version;

	//bool has_incomplete_blocks = false;
	
	s16 d_max = g_settings.getS16("max_block_send_distance");
	s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
	
	//dstream<<"Starting from "<<d_start<<std::endl;

	for(s16 d = d_start; d <= d_max; d++)
	{
		//dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
		
		//if(has_incomplete_blocks == false)
		{
			JMutexAutoLock lock(m_blocks_sent_mutex);
			/*
				If m_nearest_unsent_d was changed by the EmergeThread
				(it can change it to 0 through SetBlockNotSent),
				update our d to it.
				Else update m_nearest_unsent_d
			*/
			if(m_nearest_unsent_d != last_nearest_unsent_d)
			{
				d = m_nearest_unsent_d;
				last_nearest_unsent_d = m_nearest_unsent_d;
			}
		}

		/*
			Get the border/face dot coordinates of a "d-radiused"
			box
		*/
		core::list<v3s16> list;
		getFacePositions(list, d);
		
		core::list<v3s16>::Iterator li;
		for(li=list.begin(); li!=list.end(); li++)
		{
			v3s16 p = *li + center;
			
			/*
				Send throttling
				- Don't allow too many simultaneous transfers
				- EXCEPT when the blocks are very close

				Also, don't send blocks that are already flying.
			*/
			
			u16 maximum_simultaneous_block_sends_now =
					maximum_simultaneous_block_sends;
			
			if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
			{
				maximum_simultaneous_block_sends_now =
						maximum_simultaneous_block_sends_setting;
			}

			{
				JMutexAutoLock lock(m_blocks_sending_mutex);
				
				// Limit is dynamically lowered when building
				if(num_blocks_selected
						>= maximum_simultaneous_block_sends_now)
				{
					/*dstream<<"Not sending more blocks. Queue full. "
							<<m_blocks_sending.size()
							<<std::endl;*/
					goto queue_full;
				}

				if(m_blocks_sending.find(p) != NULL)
					continue;
			}
			
			/*
				Do not go over-limit
			*/
			if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
				continue;

			bool generate = d <= d_max_gen;
			
			if(haxmode)
			{
				// Don't generate above player
				if(p.Y > center.Y)
					generate = false;
			}
			else
			{
				// Limit the generating area vertically to 2/3
				if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
					generate = false;
			}

			/*
				Don't draw if not in sight
			*/

			if(isBlockInSight(p, camera_pos, camera_dir, 10000*BS) == false)
			{
				continue;
			}
			
			/*
				Don't send already sent blocks
			*/
			{
				JMutexAutoLock lock(m_blocks_sent_mutex);
				
				if(m_blocks_sent.find(p) != NULL)
					continue;
			}

			if(haxmode)
			{
				/*
					Ignore block if it is not at ground surface
					but don't ignore water surface blocks
				*/
				v2s16 p2d(p.X*MAP_BLOCKSIZE + MAP_BLOCKSIZE/2,
						p.Z*MAP_BLOCKSIZE + MAP_BLOCKSIZE/2);
				f32 y = server->m_env.getMap().getGroundHeight(p2d);
				// The sector might not exist yet, thus no heightmap
				if(y > GROUNDHEIGHT_VALID_MINVALUE)
				{
					f32 by = p.Y*MAP_BLOCKSIZE + MAP_BLOCKSIZE/2;
					if(fabs(by - y) > MAP_BLOCKSIZE + MAP_BLOCKSIZE/3
							&& fabs(by - WATER_LEVEL) >= MAP_BLOCKSIZE)
						continue;
				}
			}

			/*
				Check if map has this block
			*/
			MapBlock *block = NULL;
			try
			{
				block = server->m_env.getMap().getBlockNoCreate(p);
			}
			catch(InvalidPositionException &e)
			{
			}
			
			bool surely_not_found_on_disk = false;
			if(block != NULL)
			{
				/*if(block->isIncomplete())
				{
					has_incomplete_blocks = true;
					continue;
				}*/

				if(block->isDummy())
				{
					surely_not_found_on_disk = true;
				}
			}

			/*
				If block has been marked to not exist on disk (dummy)
				and generating new ones is not wanted, skip block.
			*/
			if(generate == false && surely_not_found_on_disk == true)
			{
				// get next one.
				continue;
			}

			/*
				Record the lowest d from which a a block has been
				found being not sent and possibly to exist
			*/
			if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
			{
				new_nearest_unsent_d = d;
			}
					
			/*
				Add inexistent block to emerge queue.
			*/
			if(block == NULL || surely_not_found_on_disk)
			{
				/*SharedPtr<JMutexAutoLock> lock
						(m_num_blocks_in_emerge_queue.getLock());*/
				
				//TODO: Get value from somewhere
				// Allow only one block in emerge queue
				if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
				{
					// Add it to the emerge queue and trigger the thread
					
					u8 flags = 0;
					if(generate == false)
						flags |= BLOCK_EMERGE_FLAG_FROMDISK;
					
					server->m_emerge_queue.addBlock(peer_id, p, flags);
					server->m_emergethread.trigger();
				}
				
				// get next one.
				continue;
			}

			/*
				Add block to queue
			*/

			PrioritySortedBlockTransfer q((float)d, p, peer_id);

			dest.push_back(q);

			num_blocks_selected += 1;
		}
	}
queue_full:

	if(new_nearest_unsent_d != -1)
	{
		JMutexAutoLock lock(m_blocks_sent_mutex);
		m_nearest_unsent_d = new_nearest_unsent_d;
	}
}

void RemoteClient::SendObjectData(
		Server *server,
		float dtime,
		core::map<v3s16, bool> &stepped_blocks
	)
{
	DSTACK(__FUNCTION_NAME);

	// Can't send anything without knowing version
	if(serialization_version == SER_FMT_VER_INVALID)
	{
		dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
				<<std::endl;
		return;
	}

	/*
		Send a TOCLIENT_OBJECTDATA packet.
		Sent as unreliable.

		u16 command
		u16 number of player positions
		for each player:
			v3s32 position*100
			v3s32 speed*100
			s32 pitch*100
			s32 yaw*100
		u16 count of blocks
		for each block:
			block objects
	*/

	std::ostringstream os(std::ios_base::binary);
	u8 buf[12];
	
	// Write command
	writeU16(buf, TOCLIENT_OBJECTDATA);
	os.write((char*)buf, 2);
	
	/*
		Get and write player data
	*/
	
	// Get connected players
	core::list<Player*> players = server->m_env.getPlayers(true);

	// Write player count
	u16 playercount = players.size();
	writeU16(buf, playercount);
	os.write((char*)buf, 2);

	core::list<Player*>::Iterator i;
	for(i = players.begin();
			i != players.end(); i++)
	{
		Player *player = *i;

		v3f pf = player->getPosition();
		v3f sf = player->getSpeed();

		v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
		v3s32 speed_i   (sf.X*100, sf.Y*100, sf.Z*100);
		s32   pitch_i   (player->getPitch() * 100);
		s32   yaw_i     (player->getYaw() * 100);
		
		writeU16(buf, player->peer_id);
		os.write((char*)buf, 2);
		writeV3S32(buf, position_i);
		os.write((char*)buf, 12);
		writeV3S32(buf, speed_i);
		os.write((char*)buf, 12);
		writeS32(buf, pitch_i);
		os.write((char*)buf, 4);
		writeS32(buf, yaw_i);
		os.write((char*)buf, 4);
	}
	
	/*
		Get and write object data
	*/

	/*
		Get nearby blocks.
		
		For making players to be able to build to their nearby
		environment (building is not possible on blocks that are not
		in memory):
		- Set blocks changed
		- Add blocks to emerge queue if they are not found

		SUGGESTION: These could be ignored from the backside of the player
	*/

	Player *player = server->m_env.getPlayer(peer_id);

	assert(player);

	v3f playerpos = player->getPosition();
	v3f playerspeed = player->getSpeed();

	v3s16 center_nodepos = floatToInt(playerpos);
	v3s16 center = getNodeBlockPos(center_nodepos);

	s16 d_max = g_settings.getS16("active_object_range");
	
	// Number of blocks whose objects were written to bos
	u16 blockcount = 0;

	std::ostringstream bos(std::ios_base::binary);

	for(s16 d = 0; d <= d_max; d++)
	{
		core::list<v3s16> list;
		getFacePositions(list, d);
		
		core::list<v3s16>::Iterator li;
		for(li=list.begin(); li!=list.end(); li++)
		{
			v3s16 p = *li + center;

			/*
				Ignore blocks that haven't been sent to the client
			*/
			{
				JMutexAutoLock sentlock(m_blocks_sent_mutex);
				if(m_blocks_sent.find(p) == NULL)
					continue;
			}
			
			// Try stepping block and add it to a send queue
			try
			{

			// Get block
			MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);

			/*
				Step block if not in stepped_blocks and add to stepped_blocks.
			*/
			if(stepped_blocks.find(p) == NULL)
			{
				block->stepObjects(dtime, true, server->getDayNightRatio());
				stepped_blocks.insert(p, true);
				block->setChangedFlag();
			}

			// Skip block if there are no objects
			if(block->getObjectCount() == 0)
				continue;
			
			/*
				Write objects
			*/

			// Write blockpos
			writeV3S16(buf, p);
			bos.write((char*)buf, 6);

			// Write objects
			block->serializeObjects(bos, serialization_version);

			blockcount++;

			/*
				Stop collecting objects if data is already too big
			*/
			// Sum of player and object data sizes
			s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
			// break out if data too big
			if(sum > MAX_OBJECTDATA_SIZE)
			{
				goto skip_subsequent;
			}
			
			} //try
			catch(InvalidPositionException &e)
			{
				// Not in memory
				// Add it to the emerge queue and trigger the thread.
				// Fetch the block only if it is on disk.
				
				// Grab and increment counter
				/*SharedPtr<JMutexAutoLock> lock
						(m_num_blocks_in_emerge_queue.getLock());
				m_num_blocks_in_emerge_queue.m_value++;*/
				
				// Add to queue as an anonymous fetch from disk
				u8 flags = BLOCK_EMERGE_FLAG_FROMDISK;
				server->m_emerge_queue.addBlock(0, p, flags);
				server->m_emergethread.trigger();
			}
		}
	}

skip_subsequent:

	// Write block count
	writeU16(buf, blockcount);
	os.write((char*)buf, 2);

	// Write block objects
	os<<bos.str();

	/*
		Send data
	*/
	
	//dstream<<"Server: Sending object data to "<<peer_id<<std::endl;

	// Make data buffer
	std::string s = os.str();
	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
	// Send as unreliable
	server->m_con.Send(peer_id, 0, data, false);
}

void RemoteClient::GotBlock(v3s16 p)
{
	JMutexAutoLock lock(m_blocks_sending_mutex);
	JMutexAutoLock lock2(m_blocks_sent_mutex);
	if(m_blocks_sending.find(p) != NULL)
		m_blocks_sending.remove(p);
	else
	{
		/*dstream<<"RemoteClient::GotBlock(): Didn't find in"
				" m_blocks_sending"<<std::endl;*/
		m_excess_gotblocks++;
	}
	m_blocks_sent.insert(p, true);
}

void RemoteClient::SentBlock(v3s16 p)
{
	JMutexAutoLock lock(m_blocks_sending_mutex);
	/*if(m_blocks_sending.size() > 15)
	{
		dstream<<"RemoteClient::SentBlock(): "
				<<"m_blocks_sending.size()="
				<<m_blocks_sending.size()<<std::endl;
	}*/
	if(m_blocks_sending.find(p) == NULL)
		m_blocks_sending.insert(p, 0.0);
	else
		dstream<<"RemoteClient::SentBlock(): Sent block"
				" already in m_blocks_sending"<<std::endl;
}

void RemoteClient::SetBlockNotSent(v3s16 p)
{
	JMutexAutoLock sendinglock(m_blocks_sending_mutex);
	JMutexAutoLock sentlock(m_blocks_sent_mutex);

	m_nearest_unsent_d = 0;
	
	if(m_blocks_sending.find(p) != NULL)
		m_blocks_sending.remove(p);
	if(m_blocks_sent.find(p) != NULL)
		m_blocks_sent.remove(p);
}

void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
{
	JMutexAutoLock sendinglock(m_blocks_sending_mutex);
	JMutexAutoLock sentlock(m_blocks_sent_mutex);

	m_nearest_unsent_d = 0;
	
	for(core::map<v3s16, MapBlock*>::Iterator
			i = blocks.getIterator();
			i.atEnd()==false; i++)
	{
		v3s16 p = i.getNode()->getKey();

		if(m_blocks_sending.find(p) != NULL)
			m_blocks_sending.remove(p);
		if(m_blocks_sent.find(p) != NULL)
			m_blocks_sent.remove(p);
	}
}

/*
	PlayerInfo
*/

PlayerInfo::PlayerInfo()
{
	name[0] = 0;
}

void PlayerInfo::PrintLine(std::ostream *s)
{
	(*s)<<id<<": ";
	(*s)<<"\""<<name<<"\" ("
			<<position.X<<","<<position.Y
			<<","<<position.Z<<") ";
	address.print(s);
	(*s)<<" avg_rtt="<<avg_rtt;
	(*s)<<std::endl;
}

u32 PIChecksum(core::list<PlayerInfo> &l)
{
	core::list<PlayerInfo>::Iterator i;
	u32 checksum = 1;
	u32 a = 10;
	for(i=l.begin(); i!=l.end(); i++)
	{
		checksum += a * (i->id+1);
		checksum ^= 0x435aafcd;
		a *= 10;
	}
	return checksum;
}

/*
	Server
*/

Server::Server(
		std::string mapsavedir,
		HMParams hm_params,
		MapParams map_params
	):
	m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
	m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
	m_thread(this),
	m_emergethread(this),
	m_time_of_day(9000),
	m_time_counter(0),
	m_time_of_day_send_timer(0),
	m_uptime(0)
{
	//m_flowwater_timer = 0.0;
	m_liquid_transform_timer = 0.0;
	m_print_info_timer = 0.0;
	m_objectdata_timer = 0.0;
	m_emergethread_trigger_timer = 0.0;
	m_savemap_timer = 0.0;
	
	m_env_mutex.Init();
	m_con_mutex.Init();
	m_step_dtime_mutex.Init();
	m_step_dtime = 0.0;
}

Server::~Server()
{
	// Stop threads
	stop();

	JMutexAutoLock clientslock(m_con_mutex);

	for(core::map<u16, RemoteClient*>::Iterator
		i = m_clients.getIterator();
		i.atEnd() == false; i++)
	{
		/*// Delete player
		// NOTE: These are removed by env destructor
		{
			u16 peer_id = i.getNode()->getKey();
			JMutexAutoLock envlock(m_env_mutex);
			m_env.removePlayer(peer_id);
		}*/
		
		// Delete client
		delete i.getNode()->getValue();
	}
}

void Server::start(unsigned short port)
{
	DSTACK(__FUNCTION_NAME);
	// Stop thread if already running
	m_thread.stop();
	
	// Initialize connection
	m_con.setTimeoutMs(30);
	m_con.Serve(port);

	// Start thread
	m_thread.setRun(true);
	m_thread.Start();
	
	dout_server<<"Server started on port "<<port<<std::endl;
}

void Server::stop()
{
	DSTACK(__FUNCTION_NAME);
	// Stop threads (set run=false first so both start stopping)
	m_thread.setRun(false);
	m_emergethread.setRun(false);
	m_thread.stop();
	m_emergethread.stop();
	
	dout_server<<"Server threads stopped"<<std::endl;
}

void Server::step(float dtime)
{
	DSTACK(__FUNCTION_NAME);
	// Limit a bit
	if(dtime > 2.0)
		dtime = 2.0;
	{
		JMutexAutoLock lock(m_step_dtime_mutex);
		m_step_dtime += dtime;
	}
}

void Server::AsyncRunStep()
{
	DSTACK(__FUNCTION_NAME);
	
	float dtime;
	{
		JMutexAutoLock lock1(m_step_dtime_mutex);
		dtime = m_step_dtime;
	}
	
	// Send blocks to clients
	SendBlocks(dtime);
	
	if(dtime < 0.001)
		return;
	
	//dstream<<"Server steps "<<dtime<<std::endl;
	//dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
	
	{
		JMutexAutoLock lock1(m_step_dtime_mutex);
		m_step_dtime -= dtime;
	}

	/*
		Update uptime
	*/
	{
		m_uptime.set(m_uptime.get() + dtime);
	}
	
	/*
		Update m_time_of_day
	*/
	{
		m_time_counter += dtime;
		f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
		u32 units = (u32)(m_time_counter*speed);
		m_time_counter -= (f32)units / speed;
		m_time_of_day.set((m_time_of_day.get() + units) % 24000);
		
		//dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;

		/*
			Send to clients at constant intervals
		*/

		m_time_of_day_send_timer -= dtime;
		if(m_time_of_day_send_timer < 0.0)
		{
			m_time_of_day_send_timer = g_settings.getFloat("time_send_interval");

			//JMutexAutoLock envlock(m_env_mutex);
			JMutexAutoLock conlock(m_con_mutex);

			for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++)
			{
				RemoteClient *client = i.getNode()->getValue();
				//Player *player = m_env.getPlayer(client->peer_id);
				
				SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
						m_time_of_day.get());
				// Send as reliable
				m_con.Send(client->peer_id, 0, data, true);
			}
		}
	}

	{
		// Process connection's timeouts
		JMutexAutoLock lock2(m_con_mutex);
		m_con.RunTimeouts(dtime);
	}
	
	{
		// This has to be called so that the client list gets synced
		// with the peer list of the connection
		handlePeerChanges();
	}

	{
		// Step environment
		// This also runs Map's timers
		JMutexAutoLock lock(m_env_mutex);
		m_env.step(dtime);
	}
	
	/*
		Do background stuff
	*/
	
	/*
		Transform liquids
	*/
	m_liquid_transform_timer += dtime;
	if(m_liquid_transform_timer >= 1.00)
	{
		m_liquid_transform_timer -= 1.00;
		
		JMutexAutoLock lock(m_env_mutex);
		
		core::map<v3s16, MapBlock*> modified_blocks;
		m_env.getMap().transformLiquids(modified_blocks);
#if 0		
		/*
			Update lighting
		*/
		core::map<v3s16, MapBlock*> lighting_modified_blocks;
		ServerMap &map = ((ServerMap&)m_env.getMap());
		map.updateLighting(modified_blocks, lighting_modified_blocks);
		
		// Add blocks modified by lighting to modified_blocks
		for(core::map<v3s16, MapBlock*>::Iterator
				i = lighting_modified_blocks.getIterator();
				i.atEnd() == false; i++)
		{
			MapBlock *block = i.getNode()->getValue();
			modified_blocks.insert(block->getPos(), block);
		}
#endif
		/*
			Set the modified blocks unsent for all the clients
		*/
		
		JMutexAutoLock lock2(m_con_mutex);

		for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++)
		{
			RemoteClient *client = i.getNode()->getValue();
			
			if(modified_blocks.size() > 0)
			{
				// Remove block from sent history
				client->SetBlocksNotSent(modified_blocks);
			}
		}
	}

#if 0
	/*
		Update water
	*/
	if(g_settings.getBool("water_moves") == true)
	{
		float interval;
		
		if(g_settings.getBool("endless_water") == false)
			interval = 1.0;
		else
			interval = 0.25;

		float &counter = m_flowwater_timer;
		counter += dtime;
		if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
		{
		
		counter = 0.0;

		core::map<v3s16, MapBlock*> modified_blocks;

		{

			JMutexAutoLock envlock(m_env_mutex);
			
			MapVoxelManipulator v(&m_env.getMap());
			v.m_disable_water_climb =
					g_settings.getBool("disable_water_climb");
			
			if(g_settings.getBool("endless_water") == false)
				v.flowWater(m_flow_active_nodes, 0, false, 250);
			else
				v.flowWater(m_flow_active_nodes, 0, false, 50);

			v.blitBack(modified_blocks);

			ServerMap &map = ((ServerMap&)m_env.getMap());
			
			// Update lighting
			core::map<v3s16, MapBlock*> lighting_modified_blocks;
			map.updateLighting(modified_blocks, lighting_modified_blocks);
			
			// Add blocks modified by lighting to modified_blocks
			for(core::map<v3s16, MapBlock*>::Iterator
					i = lighting_modified_blocks.getIterator();
					i.atEnd() == false; i++)
			{
				MapBlock *block = i.getNode()->getValue();
				modified_blocks.insert(block->getPos(), block);
			}
		} // envlock

		/*
			Set the modified blocks unsent for all the clients
		*/
		
		JMutexAutoLock lock2(m_con_mutex);

		for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++)
		{
			RemoteClient *client = i.getNode()->getValue();
			
			if(modified_blocks.size() > 0)
			{
				// Remove block from sent history
				client->SetBlocksNotSent(modified_blocks);
			}
		}

		} // interval counter
	}
#endif
	
	// Periodically print some info
	{
		float &counter = m_print_info_timer;
		counter += dtime;
		if(counter >= 30.0)
		{
			counter = 0.0;

			JMutexAutoLock lock2(m_con_mutex);

			for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++)
			{
				//u16 peer_id = i.getNode()->getKey();
				RemoteClient *client = i.getNode()->getValue();
				client->PrintInfo(std::cout);
			}
		}
	}

	/*
		Update digging

		NOTE: Some of this could be moved to RemoteClient
	*/
#if 0
	{
		JMutexAutoLock envlock(m_env_mutex);
		JMutexAutoLock conlock(m_con_mutex);

		for(core::map<u16, RemoteClient*>::Iterator
			i = m_clients.getIterator();
			i.atEnd() == false; i++)
		{
			RemoteClient *client = i.getNode()->getValue();
			Player *player = m_env.getPlayer(client->peer_id);

			JMutexAutoLock digmutex(client->m_dig_mutex);

			if(client->m_dig_tool_item == -1)
				continue;

			client->m_dig_time_remaining -= dtime;

			if(client->m_dig_time_remaining > 0)
			{
				client->m_time_from_building.set(0.0);
				continue;
			}

			v3s16 p_under = client->m_dig_position;
			
			// Mandatory parameter; actually used for nothing
			core::map<v3s16, MapBlock*> modified_blocks;

			u8 material;

			try
			{
				// Get material at position
				material = m_env.getMap().getNode(p_under).d;
				// If it's not diggable, do nothing
				if(content_diggable(material) == false)
				{
					derr_server<<"Server: Not finishing digging: Node not diggable"
							<<std::endl;
					client->m_dig_tool_item = -1;
					break;
				}
			}
			catch(InvalidPositionException &e)
			{
				derr_server<<"Server: Not finishing digging: Node not found"
						<<std::endl;
				client->m_dig_tool_item = -1;
				break;
			}
			
			// Create packet
			u32 replysize = 8;
			SharedBuffer<u8> reply(replysize);
			writeU16(&reply[0], TOCLIENT_REMOVENODE);
			writeS16(&reply[2], p_under.X);
			writeS16(&reply[4], p_under.Y);
			writeS16(&reply[6], p_under.Z);
			// Send as reliable
			m_con.SendToAll(0, reply, true);
			
			if(g_settings.getBool("creative_mode") == false)
			{
				// Add to inventory and send inventory
				InventoryItem *item = new MaterialItem(material, 1);
				player->inventory.addItem("main", item);
				SendInventory(player->peer_id);
			}

			/*
				Remove the node
				(this takes some time so it is done after the quick stuff)
			*/
			m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
			
			/*
				Update water
			*/
			
			// Update water pressure around modification
			// This also adds it to m_flow_active_nodes if appropriate

			MapVoxelManipulator v(&m_env.getMap());
			v.m_disable_water_climb =
					g_settings.getBool("disable_water_climb");
			
			VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));

			try
			{
				v.updateAreaWaterPressure(area, m_flow_active_nodes);
			}
			catch(ProcessingLimitException &e)
			{
				dstream<<"Processing limit reached (1)"<<std::endl;
			}
			
			v.blitBack(modified_blocks);
		}
	}
#endif

	// Send object positions
	{
		float &counter = m_objectdata_timer;
		counter += dtime;
		if(counter >= g_settings.getFloat("objectdata_interval"))
		{
			JMutexAutoLock lock1(m_env_mutex);
			JMutexAutoLock lock2(m_con_mutex);
			SendObjectData(counter);

			counter = 0.0;
		}
	}
	
	// Trigger emergethread (it gets somehow gets to a
	// non-triggered but bysy state sometimes)
	{
		float &counter = m_emergethread_trigger_timer;
		counter += dtime;
		if(counter >= 2.0)
		{
			counter = 0.0;
			
			m_emergethread.trigger();
		}
	}

	// Save map
	{
		float &counter = m_savemap_timer;
		counter += dtime;
		if(counter >= g_settings.getFloat("server_map_save_interval"))
		{
			counter = 0.0;

			JMutexAutoLock lock(m_env_mutex);

			// Save only changed parts
			m_env.getMap().save(true);

			// Delete unused sectors
			u32 deleted_count = m_env.getMap().deleteUnusedSectors(
					g_settings.getFloat("server_unload_unused_sectors_timeout"));
			if(deleted_count > 0)
			{
				dout_server<<"Server: Unloaded "<<deleted_count
						<<" sectors from memory"<<std::endl;
			}
		}
	}
}

void Server::Receive()
{
	DSTACK(__FUNCTION_NAME);
	u32 data_maxsize = 10000;
	Buffer<u8> data(data_maxsize);
	u16 peer_id;
	u32 datasize;
	try{
		{
			JMutexAutoLock conlock(m_con_mutex);
			datasize = m_con.Receive(peer_id, *data, data_maxsize);
		}

		// This has to be called so that the client list gets synced
		// with the peer list of the connection
		handlePeerChanges();

		ProcessData(*data, datasize, peer_id);
	}
	catch(con::InvalidIncomingDataException &e)
	{
		derr_server<<"Server::Receive(): "
				"InvalidIncomingDataException: what()="
				<<e.what()<<std::endl;
	}
	catch(con::PeerNotFoundException &e)
	{
		//NOTE: This is not needed anymore
		
		// The peer has been disconnected.
		// Find the associated player and remove it.

		/*JMutexAutoLock envlock(m_env_mutex);

		dout_server<<"ServerThread: peer_id="<<peer_id
				<<" has apparently closed connection. "
				<<"Removing player."<<std::endl;

		m_env.removePlayer(peer_id);*/
	}
}

void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);
	// Environment is locked first.
	JMutexAutoLock envlock(m_env_mutex);
	JMutexAutoLock conlock(m_con_mutex);
	
	con::Peer *peer;
	try{
		peer = m_con.GetPeer(peer_id);
	}
	catch(con::PeerNotFoundException &e)
	{
		derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
				<<peer_id<<" not found"<<std::endl;
		return;
	}
	
	//u8 peer_ser_ver = peer->serialization_version;
	u8 peer_ser_ver = getClient(peer->id)->serialization_version;

	try
	{

	if(datasize < 2)
		return;

	ToServerCommand command = (ToServerCommand)readU16(&data[0]);
	
	if(command == TOSERVER_INIT)
	{
		// [0] u16 TOSERVER_INIT
		// [2] u8 SER_FMT_VER_HIGHEST
		// [3] u8[20] player_name

		if(datasize < 3)
			return;

		derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
				<<peer->id<<std::endl;

		// First byte after command is maximum supported
		// serialization version
		u8 client_max = data[2];
		u8 our_max = SER_FMT_VER_HIGHEST;
		// Use the highest version supported by both
		u8 deployed = core::min_(client_max, our_max);
		// If it's lower than the lowest supported, give up.
		if(deployed < SER_FMT_VER_LOWEST)
			deployed = SER_FMT_VER_INVALID;

		//peer->serialization_version = deployed;
		getClient(peer->id)->pending_serialization_version = deployed;

		if(deployed == SER_FMT_VER_INVALID)
		{
			derr_server<<DTIME<<"Server: Cannot negotiate "
					"serialization version with peer "
					<<peer_id<<std::endl;
			return;
		}

		/*
			Set up player
		*/
		
		// Get player name
		const u32 playername_size = 20;
		char playername[playername_size];
		for(u32 i=0; i<playername_size-1; i++)
		{
			playername[i] = data[3+i];
		}
		playername[playername_size-1] = 0;
		
		// Get player
		Player *player = emergePlayer(playername, "", peer_id);
		//Player *player = m_env.getPlayer(peer_id);

		// If failed, cancel
		if(player == NULL)
		{
			derr_server<<DTIME<<"Server: peer_id="<<peer_id
					<<": failed to emerge player"<<std::endl;
			return;
		}

		/*
		// If a client is already connected to the player, cancel
		if(player->peer_id != 0)
		{
			derr_server<<DTIME<<"Server: peer_id="<<peer_id
					<<" tried to connect to "
					"an already connected player (peer_id="
					<<player->peer_id<<")"<<std::endl;
			return;
		}
		// Set client of player
		player->peer_id = peer_id;
		*/

		// Check if player doesn't exist
		if(player == NULL)
			throw con::InvalidIncomingDataException
				("Server::ProcessData(): INIT: Player doesn't exist");

		/*// update name if it was supplied
		if(datasize >= 20+3)
		{
			data[20+3-1] = 0;
			player->updateName((const char*)&data[3]);
		}*/

		// Now answer with a TOCLIENT_INIT
		
		SharedBuffer<u8> reply(2+1+6);
		writeU16(&reply[0], TOCLIENT_INIT);
		writeU8(&reply[2], deployed);
		writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
		// Send as reliable
		m_con.Send(peer_id, 0, reply, true);

		return;
	}
	if(command == TOSERVER_INIT2)
	{
		derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
				<<peer->id<<std::endl;


		getClient(peer->id)->serialization_version
				= getClient(peer->id)->pending_serialization_version;

		/*
			Send some initialization data
		*/
		
		// Send player info to all players
		SendPlayerInfos();

		// Send inventory to player
		SendInventory(peer->id);
		
		// Send time of day
		{
			SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
					m_time_of_day.get());
			m_con.Send(peer->id, 0, data, true);
		}

		// Send information about server to player in chat
		{
			std::wostringstream os(std::ios_base::binary);
			os<<L"# Server: ";
			// Uptime
			os<<L"uptime="<<m_uptime.get();
			// Information about clients
			os<<L", clients={";
			for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++)
			{
				// Get client and check that it is valid
				RemoteClient *client = i.getNode()->getValue();
				assert(client->peer_id == i.getNode()->getKey());
				if(client->serialization_version == SER_FMT_VER_INVALID)
					continue;
				// Get player
				Player *player = m_env.getPlayer(client->peer_id);
				// Get name of player
				std::wstring name = L"unknown";
				if(player != NULL)
					name = narrow_to_wide(player->getName());
				// Add name to information string
				os<<name<<L",";
			}
			os<<L"}";
			// Send message
			SendChatMessage(peer_id, os.str());
		}
		
		// Send information about joining in chat
		{
			std::wstring name = L"unknown";
			Player *player = m_env.getPlayer(peer_id);
			if(player != NULL)
				name = narrow_to_wide(player->getName());
			
			std::wstring message;
			message += L"*** ";
			message += name;
			message += L" joined game";
			BroadcastChatMessage(message);
		}

		return;
	}

	if(peer_ser_ver == SER_FMT_VER_INVALID)
	{
		derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
				" serialization format invalid or not initialized."
				" Skipping incoming command="<<command<<std::endl;
		return;
	}
	
	Player *player = m_env.getPlayer(peer_id);

	if(player == NULL){
		derr_server<<"Server::ProcessData(): Cancelling: "
				"No player for peer_id="<<peer_id
				<<std::endl;
		return;
	}
	if(command == TOSERVER_PLAYERPOS)
	{
		if(datasize < 2+12+12+4+4)
			return;
	
		u32 start = 0;
		v3s32 ps = readV3S32(&data[start+2]);
		v3s32 ss = readV3S32(&data[start+2+12]);
		f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
		f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
		v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
		v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
		pitch = wrapDegrees(pitch);
		yaw = wrapDegrees(yaw);
		player->setPosition(position);
		player->setSpeed(speed);
		player->setPitch(pitch);
		player->setYaw(yaw);
		
		/*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
				<<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
				<<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
	}
	else if(command == TOSERVER_GOTBLOCKS)
	{
		if(datasize < 2+1)
			return;
		
		/*
			[0] u16 command
			[2] u8 count
			[3] v3s16 pos_0
			[3+6] v3s16 pos_1
			...
		*/

		u16 count = data[2];
		for(u16 i=0; i<count; i++)
		{
			if((s16)datasize < 2+1+(i+1)*6)
				throw con::InvalidIncomingDataException
					("GOTBLOCKS length is too short");
			v3s16 p = readV3S16(&data[2+1+i*6]);
			/*dstream<<"Server: GOTBLOCKS ("
					<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
			RemoteClient *client = getClient(peer_id);
			client->GotBlock(p);
		}
	}
	else if(command == TOSERVER_DELETEDBLOCKS)
	{
		if(datasize < 2+1)
			return;
		
		/*
			[0] u16 command
			[2] u8 count
			[3] v3s16 pos_0
			[3+6] v3s16 pos_1
			...
		*/

		u16 count = data[2];
		for(u16 i=0; i<count; i++)
		{
			if((s16)datasize < 2+1+(i+1)*6)
				throw con::InvalidIncomingDataException
					("DELETEDBLOCKS length is too short");
			v3s16 p = readV3S16(&data[2+1+i*6]);
			/*dstream<<"Server: DELETEDBLOCKS ("
					<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
			RemoteClient *client = getClient(peer_id);
			client->SetBlockNotSent(p);
		}
	}
	else if(command == TOSERVER_CLICK_OBJECT)
	{
		if(datasize < 13)
			return;

		/*
			[0] u16 command
			[2] u8 button (0=left, 1=right)
			[3] v3s16 block
			[9] s16 id
			[11] u16 item
		*/
		u8 button = readU8(&data[2]);
		v3s16 p;
		p.X = readS16(&data[3]);
		p.Y = readS16(&data[5]);
		p.Z = readS16(&data[7]);
		s16 id = readS16(&data[9]);
		//u16 item_i = readU16(&data[11]);

		MapBlock *block = NULL;
		try
		{
			block = m_env.getMap().getBlockNoCreate(p);
		}
		catch(InvalidPositionException &e)
		{
			derr_server<<"CLICK_OBJECT block not found"<<std::endl;
			return;
		}

		MapBlockObject *obj = block->getObject(id);

		if(obj == NULL)
		{
			derr_server<<"CLICK_OBJECT object not found"<<std::endl;
			return;
		}

		//TODO: Check that object is reasonably close
		
		// Left click
		if(button == 0)
		{
			InventoryList *ilist = player->inventory.getList("main");
			if(g_settings.getBool("creative_mode") == false && ilist != NULL)
			{
			
				// Skip if inventory has no free space
				if(ilist->getUsedSlots() == ilist->getSize())
				{
					dout_server<<"Player inventory has no free space"<<std::endl;
					return;
				}
				
				/*
					Create the inventory item
				*/
				InventoryItem *item = NULL;
				// If it is an item-object, take the item from it
				if(obj->getTypeId() == MAPBLOCKOBJECT_TYPE_ITEM)
				{
					item = ((ItemObject*)obj)->createInventoryItem();
				}
				// Else create an item of the object
				else
				{
					item = new MapBlockObjectItem
							(obj->getInventoryString());
				}
				
				// Add to inventory and send inventory
				ilist->addItem(item);
				SendInventory(player->peer_id);
			}

			// Remove from block
			block->removeObject(id);
		}
	}
	else if(command == TOSERVER_GROUND_ACTION)
	{
		if(datasize < 17)
			return;
		/*
			length: 17
			[0] u16 command
			[2] u8 action
			[3] v3s16 nodepos_undersurface
			[9] v3s16 nodepos_abovesurface
			[15] u16 item
			actions:
			0: start digging
			1: place block
			2: stop digging (all parameters ignored)
		*/
		u8 action = readU8(&data[2]);
		v3s16 p_under;
		p_under.X = readS16(&data[3]);
		p_under.Y = readS16(&data[5]);
		p_under.Z = readS16(&data[7]);
		v3s16 p_over;
		p_over.X = readS16(&data[9]);
		p_over.Y = readS16(&data[11]);
		p_over.Z = readS16(&data[13]);
		u16 item_i = readU16(&data[15]);

		//TODO: Check that target is reasonably close
		
		/*
			0: start digging
		*/
		if(action == 0)
		{
			/*
				NOTE: This can be used in the future to check if
				somebody is cheating, by checking the timing.
			*/
		} // action == 0

		/*
			2: stop digging
		*/
		else if(action == 2)
		{
#if 0
			RemoteClient *client = getClient(peer->id);
			JMutexAutoLock digmutex(client->m_dig_mutex);
			client->m_dig_tool_item = -1;
#endif
		}

		/*
			3: Digging completed
		*/
		else if(action == 3)
		{
			// Mandatory parameter; actually used for nothing
			core::map<v3s16, MapBlock*> modified_blocks;

			u8 material;

			try
			{
				// Get material at position
				material = m_env.getMap().getNode(p_under).d;
				// If it's not diggable, do nothing
				if(content_diggable(material) == false)
				{
					derr_server<<"Server: Not finishing digging: Node not diggable"
							<<std::endl;
					return;
				}
			}
			catch(InvalidPositionException &e)
			{
				derr_server<<"Server: Not finishing digging: Node not found"
						<<std::endl;
				return;
			}
			
			//TODO: Send to only other clients
			
			/*
				Send the removal to all other clients
			*/

			// Create packet
			u32 replysize = 8;
			SharedBuffer<u8> reply(replysize);
			writeU16(&reply[0], TOCLIENT_REMOVENODE);
			writeS16(&reply[2], p_under.X);
			writeS16(&reply[4], p_under.Y);
			writeS16(&reply[6], p_under.Z);

			for(core::map<u16, RemoteClient*>::Iterator
				i = m_clients.getIterator();
				i.atEnd() == false; i++)
			{
				// Get client and check that it is valid
				RemoteClient *client = i.getNode()->getValue();
				assert(client->peer_id == i.getNode()->getKey());
				if(client->serialization_version == SER_FMT_VER_INVALID)
					continue;

				// Don't send if it's the same one
				if(peer_id == client->peer_id)
					continue;

				// Send as reliable
				m_con.Send(client->peer_id, 0, reply, true);
			}
			
			/*
				Update and send inventory
			*/

			if(g_settings.getBool("creative_mode") == false)
			{
				/*
					Wear out tool
				*/
				InventoryList *mlist = player->inventory.getList("main");
				if(mlist != NULL)
				{
					InventoryItem *item = mlist->getItem(item_i);
					if(item && (std::string)item->getName() == "ToolItem")
					{
						ToolItem *titem = (ToolItem*)item;
						std::string toolname = titem->getToolName();

						// Get digging properties for material and tool
						DiggingProperties prop =
								getDiggingProperties(material, toolname);

						if(prop.diggable == false)
						{
							derr_server<<"Server: WARNING: Player digged"
									<<" with impossible material + tool"
									<<" combination"<<std::endl;
						}
						
						bool weared_out = titem->addWear(prop.wear);

						if(weared_out)
						{
							mlist->deleteItem(item_i);
						}
					}
				}

				/*
					Add digged item to inventory
				*/
				InventoryItem *item = new MaterialItem(material, 1);
				player->inventory.addItem("main", item);

				/*
					Send inventory
				*/
				SendInventory(player->peer_id);
			}

			/*
				Remove the node
				(this takes some time so it is done after the quick stuff)
			*/
			m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);

#if 0
			/*
				Update water
			*/
			
			// Update water pressure around modification
			// This also adds it to m_flow_active_nodes if appropriate

			MapVoxelManipulator v(&m_env.getMap());
			v.m_disable_water_climb =
					g_settings.getBool("disable_water_climb");
			
			VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));

			try
			{
				v.updateAreaWaterPressure(area, m_flow_active_nodes);
			}
			catch(ProcessingLimitException &e)
			{
				dstream<<"Processing limit reached (1)"<<std::endl;
			}
			
			v.blitBack(modified_blocks);
#endif
		}
		
		/*
			1: place block
		*/
		else if(action == 1)
		{

			InventoryList *ilist = player->inventory.getList("main");
			if(ilist == NULL)
				return;

			// Get item
			InventoryItem *item = ilist->getItem(item_i);
			
			// If there is no item, it is not possible to add it anywhere
			if(item == NULL)
				return;
			
			/*
				Handle material items
			*/
			if(std::string("MaterialItem") == item->getName())
			{
				try{
					// Don't add a node if this is not a free space
					MapNode n2 = m_env.getMap().getNode(p_over);
					if(content_buildable_to(n2.d) == false)
						return;
				}
				catch(InvalidPositionException &e)
				{
					derr_server<<"Server: Ignoring ADDNODE: Node not found"
							<<std::endl;
					return;
				}

				// Reset build time counter
				getClient(peer->id)->m_time_from_building.set(0.0);
				
				// Create node data
				MaterialItem *mitem = (MaterialItem*)item;
				MapNode n;
				n.d = mitem->getMaterial();
				if(content_directional(n.d))
					n.dir = packDir(p_under - p_over);

#if 1
				// Create packet
				u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
				SharedBuffer<u8> reply(replysize);
				writeU16(&reply[0], TOCLIENT_ADDNODE);
				writeS16(&reply[2], p_over.X);
				writeS16(&reply[4], p_over.Y);
				writeS16(&reply[6], p_over.Z);
				n.serialize(&reply[8], peer_ser_ver);
				// Send as reliable
				m_con.SendToAll(0, reply, true);
				
				/*
					Handle inventory
				*/
				InventoryList *ilist = player->inventory.getList("main");
				if(g_settings.getBool("creative_mode") == false && ilist)
				{
					// Remove from inventory and send inventory
					if(mitem->getCount() == 1)
						ilist->deleteItem(item_i);
					else
						mitem->remove(1);
					// Send inventory
					SendInventory(peer_id);
				}
				
				/*
					Add node.

					This takes some time so it is done after the quick stuff
				*/
				core::map<v3s16, MapBlock*> modified_blocks;
				m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
#endif
#if 0
				/*
					Handle inventory
				*/
				InventoryList *ilist = player->inventory.getList("main");
				if(g_settings.getBool("creative_mode") == false && ilist)
				{
					// Remove from inventory and send inventory
					if(mitem->getCount() == 1)
						ilist->deleteItem(item_i);
					else
						mitem->remove(1);
					// Send inventory
					SendInventory(peer_id);
				}

				/*
					Add node.

					This takes some time so it is done after the quick stuff
				*/
				core::map<v3s16, MapBlock*> modified_blocks;
				m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);

				/*
					Set the modified blocks unsent for all the clients
				*/
				
				//JMutexAutoLock lock2(m_con_mutex);

				for(core::map<u16, RemoteClient*>::Iterator
						i = m_clients.getIterator();
						i.atEnd() == false; i++)
				{
					RemoteClient *client = i.getNode()->getValue();
					
					if(modified_blocks.size() > 0)
					{
						// Remove block from sent history
						client->SetBlocksNotSent(modified_blocks);
					}
				}
#endif

#if 0
				/*
					Update water
				*/
				
				// Update water pressure around modification
				// This also adds it to m_flow_active_nodes if appropriate

				MapVoxelManipulator v(&m_env.getMap());
				v.m_disable_water_climb =
						g_settings.getBool("disable_water_climb");
				
				VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));

				try
				{
					v.updateAreaWaterPressure(area, m_flow_active_nodes);
				}
				catch(ProcessingLimitException &e)
				{
					dstream<<"Processing limit reached (1)"<<std::endl;
				}
				
				v.blitBack(modified_blocks);
#endif
			}
			/*
				Handle other items
			*/
			else
			{
				v3s16 blockpos = getNodeBlockPos(p_over);

				MapBlock *block = NULL;
				try
				{
					block = m_env.getMap().getBlockNoCreate(blockpos);
				}
				catch(InvalidPositionException &e)
				{
					derr_server<<"Error while placing object: "
							"block not found"<<std::endl;
					return;
				}

				v3s16 block_pos_i_on_map = block->getPosRelative();
				v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);

				v3f pos = intToFloat(p_over);
				pos -= block_pos_f_on_map;
				
				/*dout_server<<"pos="
						<<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
						<<std::endl;*/

				MapBlockObject *obj = NULL;

				/*
					Handle block object items
				*/
				if(std::string("MBOItem") == item->getName())
				{
					MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;

					/*dout_server<<"Trying to place a MapBlockObjectItem: "
							"inventorystring=\""
							<<oitem->getInventoryString()
							<<"\""<<std::endl;*/
							
					obj = oitem->createObject
							(pos, player->getYaw(), player->getPitch());
				}
				/*
					Handle other items
				*/
				else
				{
					dout_server<<"Placing a miscellaneous item on map"
							<<std::endl;
					/*
						Create an ItemObject that contains the item.
					*/
					ItemObject *iobj = new ItemObject(NULL, -1, pos);
					std::ostringstream os(std::ios_base::binary);
					item->serialize(os);
					dout_server<<"Item string is \""<<os.str()<<"\""<<std::endl;
					iobj->setItemString(os.str());
					obj = iobj;
				}

				if(obj == NULL)
				{
					derr_server<<"WARNING: item resulted in NULL object, "
							<<"not placing onto map"
							<<std::endl;
				}
				else
				{
					block->addObject(obj);

					dout_server<<"Placed object"<<std::endl;

					InventoryList *ilist = player->inventory.getList("main");
					if(g_settings.getBool("creative_mode") == false && ilist)
					{
						// Remove from inventory and send inventory
						ilist->deleteItem(item_i);
						// Send inventory
						SendInventory(peer_id);
					}
				}
			}

		} // action == 1

		/*
			Catch invalid actions
		*/
		else
		{
			derr_server<<"WARNING: Server: Invalid action "
					<<action<<std::endl;
		}
	}
#if 0
	else if(command == TOSERVER_RELEASE)
	{
		if(datasize < 3)
			return;
		/*
			length: 3
			[0] u16 command
			[2] u8 button
		*/
		dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
	}
#endif
	else if(command == TOSERVER_SIGNTEXT)
	{
		/*
			u16 command
			v3s16 blockpos
			s16 id
			u16 textlen
			textdata
		*/
		std::string datastring((char*)&data[2], datasize-2);
		std::istringstream is(datastring, std::ios_base::binary);
		u8 buf[6];
		// Read stuff
		is.read((char*)buf, 6);
		v3s16 blockpos = readV3S16(buf);
		is.read((char*)buf, 2);
		s16 id = readS16(buf);
		is.read((char*)buf, 2);
		u16 textlen = readU16(buf);
		std::string text;
		for(u16 i=0; i<textlen; i++)
		{
			is.read((char*)buf, 1);
			text += (char)buf[0];
		}

		MapBlock *block = NULL;
		try
		{
			block = m_env.getMap().getBlockNoCreate(blockpos);
		}
		catch(InvalidPositionException &e)
		{
			derr_server<<"Error while setting sign text: "
					"block not found"<<std::endl;
			return;
		}

		MapBlockObject *obj = block->getObject(id);
		if(obj == NULL)
		{
			derr_server<<"Error while setting sign text: "
					"object not found"<<std::endl;
			return;
		}
		
		if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
		{
			derr_server<<"Error while setting sign text: "
					"object is not a sign"<<std::endl;
			return;
		}

		((SignObject*)obj)->setText(text);

		obj->getBlock()->setChangedFlag();
	}
	else if(command == TOSERVER_INVENTORY_ACTION)
	{
		/*// Ignore inventory changes if in creative mode
		if(g_settings.getBool("creative_mode") == true)
		{
			dstream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode"
					<<std::endl;
			return;
		}*/
		// Strip command and create a stream
		std::string datastring((char*)&data[2], datasize-2);
		dstream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
		std::istringstream is(datastring, std::ios_base::binary);
		// Create an action
		InventoryAction *a = InventoryAction::deSerialize(is);
		if(a != NULL)
		{
			/*
				Handle craftresult specially if not in creative mode
			*/
			bool disable_action = false;
			if(a->getType() == IACTION_MOVE
					&& g_settings.getBool("creative_mode") == false)
			{
				IMoveAction *ma = (IMoveAction*)a;
				// Don't allow moving anything to craftresult
				if(ma->to_name == "craftresult")
				{
					// Do nothing
					disable_action = true;
				}
				// When something is removed from craftresult
				if(ma->from_name == "craftresult")
				{
					disable_action = true;
					// Remove stuff from craft
					InventoryList *clist = player->inventory.getList("craft");
					if(clist)
					{
						u16 count = ma->count;
						if(count == 0)
							count = 1;
						clist->decrementMaterials(count);
					}
					// Do action
					// Feed action to player inventory
					a->apply(&player->inventory);
					// Eat it
					delete a;
					// If something appeared in craftresult, throw it
					// in the main list
					InventoryList *rlist = player->inventory.getList("craftresult");
					InventoryList *mlist = player->inventory.getList("main");
					if(rlist && mlist && rlist->getUsedSlots() == 1)
					{
						InventoryItem *item1 = rlist->changeItem(0, NULL);
						mlist->addItem(item1);
					}
				}
			}
			if(disable_action == false)
			{
				// Feed action to player inventory
				a->apply(&player->inventory);
				// Eat it
				delete a;
			}
			// Send inventory
			SendInventory(player->peer_id);
		}
		else
		{
			dstream<<"TOSERVER_INVENTORY_ACTION: "
					<<"InventoryAction::deSerialize() returned NULL"
					<<std::endl;
		}
	}
	else if(command == TOSERVER_CHAT_MESSAGE)
	{
		/*
			u16 command
			u16 length
			wstring message
		*/
		u8 buf[6];
		std::string datastring((char*)&data[2], datasize-2);
		std::istringstream is(datastring, std::ios_base::binary);
		
		// Read stuff
		is.read((char*)buf, 2);
		u16 len = readU16(buf);
		
		std::wstring message;
		for(u16 i=0; i<len; i++)
		{
			is.read((char*)buf, 2);
			message += (wchar_t)readU16(buf);
		}

		// Get player name of this client
		std::wstring name = narrow_to_wide(player->getName());

		std::wstring line = std::wstring(L"<")+name+L"> "+message;
		
		dstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl;

		/*
			Send the message to all other clients
		*/
		for(core::map<u16, RemoteClient*>::Iterator
			i = m_clients.getIterator();
			i.atEnd() == false; i++)
		{
			// Get client and check that it is valid
			RemoteClient *client = i.getNode()->getValue();
			assert(client->peer_id == i.getNode()->getKey());
			if(client->serialization_version == SER_FMT_VER_INVALID)
				continue;

			// Don't send if it's the same one
			if(peer_id == client->peer_id)
				continue;

			SendChatMessage(client->peer_id, line);
		}
	}
	else
	{
		derr_server<<"WARNING: Server::ProcessData(): Ignoring "
				"unknown command "<<command<<std::endl;
	}
	
	} //try
	catch(SendFailedException &e)
	{
		derr_server<<"Server::ProcessData(): SendFailedException: "
				<<"what="<<e.what()
				<<std::endl;
	}
}

/*void Server::Send(u16 peer_id, u16 channelnum,
		SharedBuffer<u8> data, bool reliable)
{
	JMutexAutoLock lock(m_con_mutex);
	m_con.Send(peer_id, channelnum, data, reliable);
}*/

void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
{
	DSTACK(__FUNCTION_NAME);
	/*
		Create a packet with the block in the right format
	*/
	
	std::ostringstream os(std::ios_base::binary);
	block->serialize(os, ver);
	std::string s = os.str();
	SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());

	u32 replysize = 8 + blockdata.getSize();
	SharedBuffer<u8> reply(replysize);
	v3s16 p = block->getPos();
	writeU16(&reply[0], TOCLIENT_BLOCKDATA);
	writeS16(&reply[2], p.X);
	writeS16(&reply[4], p.Y);
	writeS16(&reply[6], p.Z);
	memcpy(&reply[8], *blockdata, blockdata.getSize());

	/*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
			<<":  \tpacket size: "<<replysize<<std::endl;*/
	
	/*
		Send packet
	*/
	m_con.Send(peer_id, 1, reply, true);
}

core::list<PlayerInfo> Server::getPlayerInfo()
{
	DSTACK(__FUNCTION_NAME);
	JMutexAutoLock envlock(m_env_mutex);
	JMutexAutoLock conlock(m_con_mutex);
	
	core::list<PlayerInfo> list;

	core::list<Player*> players = m_env.getPlayers();
	
	core::list<Player*>::Iterator i;
	for(i = players.begin();
			i != players.end(); i++)
	{
		PlayerInfo info;

		Player *player = *i;

		try{
			con::Peer *peer = m_con.GetPeer(player->peer_id);
			// Copy info from peer to info struct
			info.id = peer->id;
			info.address = peer->address;
			info.avg_rtt = peer->avg_rtt;
		}
		catch(con::PeerNotFoundException &e)
		{
			// Set dummy peer info
			info.id = 0;
			info.address = Address(0,0,0,0,0);
			info.avg_rtt = 0.0;
		}

		snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
		info.position = player->getPosition();

		list.push_back(info);
	}

	return list;
}

void Server::peerAdded(con::Peer *peer)
{
	DSTACK(__FUNCTION_NAME);
	dout_server<<"Server::peerAdded(): peer->id="
			<<peer->id<<std::endl;
	
	PeerChange c;
	c.type = PEER_ADDED;
	c.peer_id = peer->id;
	c.timeout = false;
	m_peer_change_queue.push_back(c);
}

void Server::deletingPeer(con::Peer *peer, bool timeout)
{
	DSTACK(__FUNCTION_NAME);
	dout_server<<"Server::deletingPeer(): peer->id="
			<<peer->id<<", timeout="<<timeout<<std::endl;
	
	PeerChange c;
	c.type = PEER_REMOVED;
	c.peer_id = peer->id;
	c.timeout = timeout;
	m_peer_change_queue.push_back(c);
}

void Server::SendObjectData(float dtime)
{
	DSTACK(__FUNCTION_NAME);

	core::map<v3s16, bool> stepped_blocks;
	
	for(core::map<u16, RemoteClient*>::Iterator
		i = m_clients.getIterator();
		i.atEnd() == false; i++)
	{
		u16 peer_id = i.getNode()->getKey();
		RemoteClient *client = i.getNode()->getValue();
		assert(client->peer_id == peer_id);
		
		if(client->serialization_version == SER_FMT_VER_INVALID)
			continue;
		
		client->SendObjectData(this, dtime, stepped_blocks);
	}
}

void Server::SendPlayerInfos()
{
	DSTACK(__FUNCTION_NAME);

	//JMutexAutoLock envlock(m_env_mutex);
	
	// Get connected players
	core::list<Player*> players = m_env.getPlayers(true);
	
	u32 player_count = players.getSize();
	u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;

	SharedBuffer<u8> data(datasize);
	writeU16(&data[0], TOCLIENT_PLAYERINFO);
	
	u32 start = 2;
	core::list<Player*>::Iterator i;
	for(i = players.begin();
			i != players.end(); i++)
	{
		Player *player = *i;

		/*dstream<<"Server sending player info for player with "
				"peer_id="<<player->peer_id<<std::endl;*/
		
		writeU16(&data[start], player->peer_id);
		snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
		start += 2+PLAYERNAME_SIZE;
	}

	//JMutexAutoLock conlock(m_con_mutex);

	// Send as reliable
	m_con.SendToAll(0, data, true);
}

enum ItemSpecType
{
	ITEM_NONE,
	ITEM_MATERIAL,
	ITEM_CRAFT,
	ITEM_TOOL,
	ITEM_MBO
};

struct ItemSpec
{
	ItemSpec():
		type(ITEM_NONE)
	{
	}
	ItemSpec(enum ItemSpecType a_type, std::string a_name):
		type(a_type),
		name(a_name),
		num(65535)
	{
	}
	ItemSpec(enum ItemSpecType a_type, u16 a_num):
		type(a_type),
		name(""),
		num(a_num)
	{
	}
	enum ItemSpecType type;
	// Only other one of these is used
	std::string name;
	u16 num;
};

/*
	items: a pointer to an array of 9 pointers to items
	specs: a pointer to an array of 9 ItemSpecs
*/
bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
{
	u16 items_min_x = 100;
	u16 items_max_x = 100;
	u16 items_min_y = 100;
	u16 items_max_y = 100;
	for(u16 y=0; y<3; y++)
	for(u16 x=0; x<3; x++)
	{
		if(items[y*3 + x] == NULL)
			continue;
		if(items_min_x == 100 || x < items_min_x)
			items_min_x = x;
		if(items_min_y == 100 || y < items_min_y)
			items_min_y = y;
		if(items_max_x == 100 || x > items_max_x)
			items_max_x = x;
		if(items_max_y == 100 || y > items_max_y)
			items_max_y = y;
	}
	// No items at all, just return false
	if(items_min_x == 100)
		return false;
	
	u16 items_w = items_max_x - items_min_x + 1;
	u16 items_h = items_max_y - items_min_y + 1;

	u16 specs_min_x = 100;
	u16 specs_max_x = 100;
	u16 specs_min_y = 100;
	u16 specs_max_y = 100;
	for(u16 y=0; y<3; y++)
	for(u16 x=0; x<3; x++)
	{
		if(specs[y*3 + x].type == ITEM_NONE)
			continue;
		if(specs_min_x == 100 || x < specs_min_x)
			specs_min_x = x;
		if(specs_min_y == 100 || y < specs_min_y)
			specs_min_y = y;
		if(specs_max_x == 100 || x > specs_max_x)
			specs_max_x = x;
		if(specs_max_y == 100 || y > specs_max_y)
			specs_max_y = y;
	}
	// No specs at all, just return false
	if(specs_min_x == 100)
		return false;

	u16 specs_w = specs_max_x - specs_min_x + 1;
	u16 specs_h = specs_max_y - specs_min_y + 1;

	// Different sizes
	if(items_w != specs_w || items_h != specs_h)
		return false;

	for(u16 y=0; y<specs_h; y++)
	for(u16 x=0; x<specs_w; x++)
	{
		u16 items_x = items_min_x + x;
		u16 items_y = items_min_y + y;
		u16 specs_x = specs_min_x + x;
		u16 specs_y = specs_min_y + y;
		InventoryItem *item = items[items_y * 3 + items_x];
		ItemSpec &spec = specs[specs_y * 3 + specs_x];
		
		if(spec.type == ITEM_NONE)
		{
			// Has to be no item
			if(item != NULL)
				return false;
			continue;
		}
		
		// There should be an item
		if(item == NULL)
			return false;

		std::string itemname = item->getName();

		if(spec.type == ITEM_MATERIAL)
		{
			if(itemname != "MaterialItem")
				return false;
			MaterialItem *mitem = (MaterialItem*)item;
			if(mitem->getMaterial() != spec.num)
				return false;
		}
		else if(spec.type == ITEM_CRAFT)
		{
			if(itemname != "CraftItem")
				return false;
			CraftItem *mitem = (CraftItem*)item;
			if(mitem->getSubName() != spec.name)
				return false;
		}
		else if(spec.type == ITEM_TOOL)
		{
			// Not supported yet
			assert(0);
		}
		else if(spec.type == ITEM_MBO)
		{
			// Not supported yet
			assert(0);
		}
		else
		{
			// Not supported yet
			assert(0);
		}
	}

	return true;
}

void Server::SendInventory(u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);
	
	Player* player = m_env.getPlayer(peer_id);

	/*
		Calculate crafting stuff
	*/
	if(g_settings.getBool("creative_mode") == false)
	{
		InventoryList *clist = player->inventory.getList("craft");
		InventoryList *rlist = player->inventory.getList("craftresult");
		if(rlist)
		{
			rlist->clearItems();
		}
		if(clist && rlist)
		{
			InventoryItem *items[9];
			for(u16 i=0; i<9; i++)
			{
				items[i] = clist->getItem(i);
			}
			
			bool found = false;

			// Wood
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
					found = true;
				}
			}

			// Stick
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new CraftItem("Stick", 4));
					found = true;
				}
			}

			// Sign
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new MapBlockObjectItem("Sign"));
					found = true;
				}
			}

			// Torch
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COALSTONE);
				specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
					found = true;
				}
			}

			// Wooden pick
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
				specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new ToolItem("WPick", 0));
					found = true;
				}
			}

			// Stone pick
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
				specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
				specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
				specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new ToolItem("STPick", 0));
					found = true;
				}
			}

			// Mese pick
			if(!found)
			{
				ItemSpec specs[9];
				specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
				specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
				specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
				specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
				specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
				if(checkItemCombination(items, specs))
				{
					rlist->addItem(new ToolItem("MesePick", 0));
					found = true;
				}
			}
		}
	} // if creative_mode == false

	/*
		Serialize it
	*/

	std::ostringstream os;
	//os.imbue(std::locale("C"));

	player->inventory.serialize(os);

	std::string s = os.str();
	
	SharedBuffer<u8> data(s.size()+2);
	writeU16(&data[0], TOCLIENT_INVENTORY);
	memcpy(&data[2], s.c_str(), s.size());
	
	// Send as reliable
	m_con.Send(peer_id, 0, data, true);
}

void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
{
	DSTACK(__FUNCTION_NAME);
	
	std::ostringstream os(std::ios_base::binary);
	u8 buf[12];
	
	// Write command
	writeU16(buf, TOCLIENT_CHAT_MESSAGE);
	os.write((char*)buf, 2);
	
	// Write length
	writeU16(buf, message.size());
	os.write((char*)buf, 2);
	
	// Write string
	for(u32 i=0; i<message.size(); i++)
	{
		u16 w = message[i];
		writeU16(buf, w);
		os.write((char*)buf, 2);
	}
	
	// Make data buffer
	std::string s = os.str();
	SharedBuffer<u8> data((u8*)s.c_str(), s.size());
	// Send as reliable
	m_con.Send(peer_id, 0, data, true);
}

void Server::BroadcastChatMessage(const std::wstring &message)
{
	for(core::map<u16, RemoteClient*>::Iterator
		i = m_clients.getIterator();
		i.atEnd() == false; i++)
	{
		// Get client and check that it is valid
		RemoteClient *client = i.getNode()->getValue();
		assert(client->peer_id == i.getNode()->getKey());
		if(client->serialization_version == SER_FMT_VER_INVALID)
			continue;

		SendChatMessage(client->peer_id, message);
	}
}

void Server::SendBlocks(float dtime)
{
	DSTACK(__FUNCTION_NAME);

	JMutexAutoLock envlock(m_env_mutex);

	core::array<PrioritySortedBlockTransfer> queue;

	s32 total_sending = 0;

	for(core::map<u16, RemoteClient*>::Iterator
		i = m_clients.getIterator();
		i.atEnd() == false; i++)
	{
		RemoteClient *client = i.getNode()->getValue();
		assert(client->peer_id == i.getNode()->getKey());

		total_sending += client->SendingCount();
		
		if(client->serialization_version == SER_FMT_VER_INVALID)
			continue;
		
		client->GetNextBlocks(this, dtime, queue);
	}

	// Sort.
	// Lowest priority number comes first.
	// Lowest is most important.
	queue.sort();

	JMutexAutoLock conlock(m_con_mutex);

	for(u32 i=0; i<queue.size(); i++)
	{
		//TODO: Calculate limit dynamically
		if(total_sending >= g_settings.getS32
				("max_simultaneous_block_sends_server_total"))
			break;
		
		PrioritySortedBlockTransfer q = queue[i];

		MapBlock *block = NULL;
		try
		{
			block = m_env.getMap().getBlockNoCreate(q.pos);
		}
		catch(InvalidPositionException &e)
		{
			continue;
		}

		RemoteClient *client = getClient(q.peer_id);

		SendBlockNoLock(q.peer_id, block, client->serialization_version);

		client->SentBlock(q.pos);

		total_sending++;
	}
}


RemoteClient* Server::getClient(u16 peer_id)
{
	DSTACK(__FUNCTION_NAME);
	//JMutexAutoLock lock(m_con_mutex);
	core::map<u16, RemoteClient*>::Node *n;
	n = m_clients.find(peer_id);
	// A client should exist for all peers
	assert(n != NULL);
	return n->getValue();
}

Player *Server::emergePlayer(const char *name, const char *password,
		u16 peer_id)
{
	/*
		Try to get an existing player
	*/
	Player *player = m_env.getPlayer(name);
	if(player != NULL)
	{
		// If player is already connected, cancel
		if(player->peer_id != 0)
		{
			dstream<<"emergePlayer(): Player already connected"<<std::endl;
			return NULL;
		}
		// Got one.
		player->peer_id = peer_id;
		return player;
	}

	/*
		If player with the wanted peer_id already exists, cancel.
	*/
	if(m_env.getPlayer(peer_id) != NULL)
	{
		dstream<<"emergePlayer(): Player with wrong name but same"
				" peer_id already exists"<<std::endl;
		return NULL;
	}
	
	/*
		Create a new player
	*/
	{
		player = new ServerRemotePlayer();
		//player->peer_id = c.peer_id;
		//player->peer_id = PEER_ID_INEXISTENT;
		player->peer_id = peer_id;
		player->updateName(name);

		/*
			Set player position
		*/
		
		dstream<<"Server: Finding spawn place for player \""
				<<player->getName()<<"\""<<std::endl;

#if 1
		v2s16 nodepos;
		f32 groundheight = 0;
		// Try to find a good place a few times
		for(s32 i=0; i<500; i++)
		{
			s32 range = 1 + i;
			// We're going to try to throw the player to this position
			nodepos = v2s16(-range + (myrand()%(range*2)),
					-range + (myrand()%(range*2)));
			v2s16 sectorpos = getNodeSectorPos(nodepos);
			// Get sector
			m_env.getMap().emergeSector(sectorpos);
			// Get ground height at point
			groundheight = m_env.getMap().getGroundHeight(nodepos, true);
			// The sector should have been generated -> groundheight exists
			assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
			// Don't go underwater
			if(groundheight < WATER_LEVEL)
			{
				//dstream<<"-> Underwater"<<std::endl;
				continue;
			}
#if 0 // Doesn't work, generating blocks is a bit too complicated for doing here
			// Get block at point
			v3s16 nodepos3d;
			nodepos3d = v3s16(nodepos.X, groundheight+1, nodepos.Y);
			v3s16 blockpos = getNodeBlockPos(nodepos3d);
			((ServerMap*)(&m_env.getMap()))->emergeBlock(blockpos);
			// Don't go inside ground
			try{
				/*v3s16 footpos(nodepos.X, groundheight+1, nodepos.Y);
				v3s16 headpos(nodepos.X, groundheight+2, nodepos.Y);*/
				v3s16 footpos = nodepos3d + v3s16(0,0,0);
				v3s16 headpos = nodepos3d + v3s16(0,1,0);
				if(m_env.getMap().getNode(footpos).d != CONTENT_AIR
					|| m_env.getMap().getNode(headpos).d != CONTENT_AIR)
				{
					dstream<<"-> Inside ground"<<std::endl;
					// In ground
					continue;
				}
			}catch(InvalidPositionException &e)
			{
				dstream<<"-> Invalid position"<<std::endl;
				// Ignore invalid position
				continue;
			}
#endif
			// Found a good place
			dstream<<"Searched through "<<i<<" places."<<std::endl;
			break;
		}
#endif
		
		// If no suitable place was not found, go above water at least.
		if(groundheight < WATER_LEVEL)
			groundheight = WATER_LEVEL;

		player->setPosition(intToFloat(v3s16(
				nodepos.X,
				groundheight + 1,
				nodepos.Y
		)));

		/*
			Add player to environment
		*/

		m_env.addPlayer(player);

		/*
			Add stuff to inventory
		*/
		
		if(g_settings.getBool("creative_mode"))
		{
			// Give some good picks
			{
				InventoryItem *item = new ToolItem("STPick", 0);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
			{
				InventoryItem *item = new ToolItem("MesePick", 0);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}

			/*
				Give materials
			*/
			assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
			
			// add torch first
			InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
			player->inventory.addItem("main", item);
			
			// Then others
			for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
			{
				// Skip some materials
				if(i == CONTENT_WATER || i == CONTENT_TORCH)
					continue;

				InventoryItem *item = new MaterialItem(i, 1);
				player->inventory.addItem("main", item);
			}
			// Sign
			{
				InventoryItem *item = new MapBlockObjectItem("Sign Example text");
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
		}
		else
		{
			/*{
				InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
			{
				InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
			{
				InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
			{
				InventoryItem *item = new CraftItem("Stick", 4);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
			{
				InventoryItem *item = new ToolItem("WPick", 32000);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}
			{
				InventoryItem *item = new ToolItem("STPick", 32000);
				void* r = player->inventory.addItem("main", item);
				assert(r == NULL);
			}*/
			/*// Give some lights
			{
				InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
				bool r = player->inventory.addItem("main", item);
				assert(r == true);
			}
			// and some signs
			for(u16 i=0; i<4; i++)