aboutsummaryrefslogtreecommitdiff
path: root/src/mapgen/mapgen.cpp
blob: 7de367a27e4ce1079c813138414e6ff757c47a5f (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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
/*
Minetest
Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
Copyright (C) 2015-2018 paramat

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 "mapgen.h"
#include "voxel.h"
#include "noise.h"
#include "gamedef.h"
#include "mg_biome.h"
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
#include "content_sao.h"
#include "nodedef.h"
#include "emerge.h"
#include "voxelalgorithms.h"
#include "porting.h"
#include "profiler.h"
#include "settings.h"
#include "treegen.h"
#include "serialization.h"
#include "util/serialize.h"
#include "util/numeric.h"
#include "filesys.h"
#include "log.h"
#include "mapgen_carpathian.h"
#include "mapgen_flat.h"
#include "mapgen_fractal.h"
#include "mapgen_v5.h"
#include "mapgen_v6.h"
#include "mapgen_v7.h"
#include "mapgen_valleys.h"
#include "mapgen_singlenode.h"
#include "cavegen.h"
#include "dungeongen.h"

FlagDesc flagdesc_mapgen[] = {
	{"caves",       MG_CAVES},
	{"dungeons",    MG_DUNGEONS},
	{"light",       MG_LIGHT},
	{"decorations", MG_DECORATIONS},
	{"biomes",      MG_BIOMES},
	{NULL,          0}
};

FlagDesc flagdesc_gennotify[] = {
	{"dungeon",          1 << GENNOTIFY_DUNGEON},
	{"temple",           1 << GENNOTIFY_TEMPLE},
	{"cave_begin",       1 << GENNOTIFY_CAVE_BEGIN},
	{"cave_end",         1 << GENNOTIFY_CAVE_END},
	{"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN},
	{"large_cave_end",   1 << GENNOTIFY_LARGECAVE_END},
	{"decoration",       1 << GENNOTIFY_DECORATION},
	{NULL,               0}
};

struct MapgenDesc {
	const char *name;
	bool is_user_visible;
};

////
//// Built-in mapgens
////

static MapgenDesc g_reg_mapgens[] = {
	{"v5",         true},
	{"v6",         true},
	{"v7",         true},
	{"flat",       true},
	{"fractal",    true},
	{"valleys",    true},
	{"singlenode", true},
	{"carpathian", true},
};

STATIC_ASSERT(
	ARRLEN(g_reg_mapgens) == MAPGEN_INVALID,
	registered_mapgens_is_wrong_size);

////
//// Mapgen
////

Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) :
	gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids)
{
	id           = mapgenid;
	water_level  = params->water_level;
	mapgen_limit = params->mapgen_limit;
	flags        = params->flags;
	csize        = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE);

	/*
		We are losing half our entropy by doing this, but it is necessary to
		preserve reverse compatibility.  If the top half of our current 64 bit
		seeds ever starts getting used, existing worlds will break due to a
		different hash outcome and no way to differentiate between versions.

		A solution could be to add a new bit to designate that the top half of
		the seed value should be used, essentially a 1-bit version code, but
		this would require increasing the total size of a seed to 9 bytes (yuck)

		It's probably okay if this never gets fixed.  4.2 billion possibilities
		ought to be enough for anyone.
	*/
	seed = (s32)params->seed;

	ndef      = emerge->ndef;
}


MapgenType Mapgen::getMapgenType(const std::string &mgname)
{
	for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) {
		if (mgname == g_reg_mapgens[i].name)
			return (MapgenType)i;
	}

	return MAPGEN_INVALID;
}


const char *Mapgen::getMapgenName(MapgenType mgtype)
{
	size_t index = (size_t)mgtype;
	if (index == MAPGEN_INVALID || index >= ARRLEN(g_reg_mapgens))
		return "invalid";

	return g_reg_mapgens[index].name;
}


Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid,
	MapgenParams *params, EmergeManager *emerge)
{
	switch (mgtype) {
	case MAPGEN_CARPATHIAN:
		return new MapgenCarpathian(mgid, (MapgenCarpathianParams *)params, emerge);
	case MAPGEN_FLAT:
		return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge);
	case MAPGEN_FRACTAL:
		return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge);
	case MAPGEN_SINGLENODE:
		return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge);
	case MAPGEN_V5:
		return new MapgenV5(mgid, (MapgenV5Params *)params, emerge);
	case MAPGEN_V6:
		return new MapgenV6(mgid, (MapgenV6Params *)params, emerge);
	case MAPGEN_V7:
		return new MapgenV7(mgid, (MapgenV7Params *)params, emerge);
	case MAPGEN_VALLEYS:
		return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge);
	default:
		return NULL;
	}
}


MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype)
{
	switch (mgtype) {
	case MAPGEN_CARPATHIAN:
		return new MapgenCarpathianParams;
	case MAPGEN_FLAT:
		return new MapgenFlatParams;
	case MAPGEN_FRACTAL:
		return new MapgenFractalParams;
	case MAPGEN_SINGLENODE:
		return new MapgenSinglenodeParams;
	case MAPGEN_V5:
		return new MapgenV5Params;
	case MAPGEN_V6:
		return new MapgenV6Params;
	case MAPGEN_V7:
		return new MapgenV7Params;
	case MAPGEN_VALLEYS:
		return new MapgenValleysParams;
	default:
		return NULL;
	}
}


void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden)
{
	for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) {
		if (include_hidden || g_reg_mapgens[i].is_user_visible)
			mgnames->push_back(g_reg_mapgens[i].name);
	}
}


u32 Mapgen::getBlockSeed(v3s16 p, s32 seed)
{
	return (u32)seed   +
		p.Z * 38134234 +
		p.Y * 42123    +
		p.X * 23;
}


u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed)
{
	u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed;
	n = (n >> 13) ^ n;
	return (n * (n * n * 60493 + 19990303) + 1376312589);
}


// Returns Y one under area minimum if not found
s16 Mapgen::findGroundLevelFull(v2s16 p2d)
{
	const v3s16 &em = vm->m_area.getExtent();
	s16 y_nodes_max = vm->m_area.MaxEdge.Y;
	s16 y_nodes_min = vm->m_area.MinEdge.Y;
	u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
	s16 y;

	for (y = y_nodes_max; y >= y_nodes_min; y--) {
		MapNode &n = vm->m_data[i];
		if (ndef->get(n).walkable)
			break;

		VoxelArea::add_y(em, i, -1);
	}
	return (y >= y_nodes_min) ? y : y_nodes_min - 1;
}


// Returns -MAX_MAP_GENERATION_LIMIT if not found
s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax)
{
	const v3s16 &em = vm->m_area.getExtent();
	u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
	s16 y;

	for (y = ymax; y >= ymin; y--) {
		MapNode &n = vm->m_data[i];
		if (ndef->get(n).walkable)
			break;

		VoxelArea::add_y(em, i, -1);
	}
	return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
}


// Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first
s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax)
{
	const v3s16 &em = vm->m_area.getExtent();
	u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
	s16 y;

	for (y = ymax; y >= ymin; y--) {
		MapNode &n = vm->m_data[i];
		if (ndef->get(n).walkable)
			return -MAX_MAP_GENERATION_LIMIT;

		if (ndef->get(n).isLiquid())
			break;

		VoxelArea::add_y(em, i, -1);
	}
	return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
}


void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax)
{
	if (!heightmap)
		return;

	//TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO);
	int index = 0;
	for (s16 z = nmin.Z; z <= nmax.Z; z++) {
		for (s16 x = nmin.X; x <= nmax.X; x++, index++) {
			s16 y = findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);

			heightmap[index] = y;
		}
	}
}


void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
	std::vector<s16> &floors, std::vector<s16> &ceilings)
{
	const v3s16 &em = vm->m_area.getExtent();

	bool is_walkable = false;
	u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y);
	MapNode mn_max = vm->m_data[vi];
	bool walkable_above = ndef->get(mn_max).walkable;
	VoxelArea::add_y(em, vi, -1);

	for (s16 y = ymax - 1; y >= ymin; y--) {
		MapNode mn = vm->m_data[vi];
		is_walkable = ndef->get(mn).walkable;

		if (is_walkable && !walkable_above) {
			floors.push_back(y);
		} else if (!is_walkable && walkable_above) {
			ceilings.push_back(y + 1);
		}

		VoxelArea::add_y(em, vi, -1);
		walkable_above = is_walkable;
	}
}


inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em)
{
	u32 vi_neg_x = vi;
	VoxelArea::add_x(em, vi_neg_x, -1);
	if (vm->m_data[vi_neg_x].getContent() != CONTENT_IGNORE) {
		const ContentFeatures &c_nx = ndef->get(vm->m_data[vi_neg_x]);
		if (c_nx.floodable && !c_nx.isLiquid())
			return true;
	}
	u32 vi_pos_x = vi;
	VoxelArea::add_x(em, vi_pos_x, +1);
	if (vm->m_data[vi_pos_x].getContent() != CONTENT_IGNORE) {
		const ContentFeatures &c_px = ndef->get(vm->m_data[vi_pos_x]);
		if (c_px.floodable && !c_px.isLiquid())
			return true;
	}
	u32 vi_neg_z = vi;
	VoxelArea::add_z(em, vi_neg_z, -1);
	if (vm->m_data[vi_neg_z].getContent() != CONTENT_IGNORE) {
		const ContentFeatures &c_nz = ndef->get(vm->m_data[vi_neg_z]);
		if (c_nz.floodable && !c_nz.isLiquid())
			return true;
	}
	u32 vi_pos_z = vi;
	VoxelArea::add_z(em, vi_pos_z, +1);
	if (vm->m_data[vi_pos_z].getContent() != CONTENT_IGNORE) {
		const ContentFeatures &c_pz = ndef->get(vm->m_data[vi_pos_z]);
		if (c_pz.floodable && !c_pz.isLiquid())
			return true;
	}
	return false;
}

void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax)
{
	bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed;
	const v3s16 &em  = vm->m_area.getExtent();

	for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++)
	for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) {
		wasignored = true;
		wasliquid = false;
		waschecked = false;
		waspushed = false;

		u32 vi = vm->m_area.index(x, nmax.Y, z);
		for (s16 y = nmax.Y; y >= nmin.Y; y--) {
			isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE;
			isliquid = ndef->get(vm->m_data[vi]).isLiquid();

			if (isignored || wasignored || isliquid == wasliquid) {
				// Neither topmost node of liquid column nor topmost node below column
				waschecked = false;
				waspushed = false;
			} else if (isliquid) {
				// This is the topmost node in the column
				bool ispushed = false;
				if (isLiquidHorizontallyFlowable(vi, em)) {
					trans_liquid->push_back(v3s16(x, y, z));
					ispushed = true;
				}
				// Remember waschecked and waspushed to avoid repeated
				// checks/pushes in case the column consists of only this node
				waschecked = true;
				waspushed = ispushed;
			} else {
				// This is the topmost node below a liquid column
				u32 vi_above = vi;
				VoxelArea::add_y(em, vi_above, 1);
				if (!waspushed && (ndef->get(vm->m_data[vi]).floodable ||
						(!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) {
					// Push back the lowest node in the column which is one
					// node above this one
					trans_liquid->push_back(v3s16(x, y + 1, z));
				}
			}

			wasliquid = isliquid;
			wasignored = isignored;
			VoxelArea::add_y(em, vi, -1);
		}
	}
}


void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax)
{
	ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
	VoxelArea a(nmin, nmax);

	for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
		for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
			u32 i = vm->m_area.index(a.MinEdge.X, y, z);
			for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++)
				vm->m_data[i].param1 = light;
		}
	}
}


void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
{
	if (light <= 1 || !a.contains(p))
		return;

	u32 vi = vm->m_area.index(p);
	MapNode &n = vm->m_data[vi];

	// Decay light in each of the banks separately
	u8 light_day = light & 0x0F;
	if (light_day > 0)
		light_day -= 0x01;

	u8 light_night = light & 0xF0;
	if (light_night > 0)
		light_night -= 0x10;

	// Bail out only if we have no more light from either bank to propogate, or
	// we hit a solid block that light cannot pass through.
	if ((light_day  <= (n.param1 & 0x0F) &&
		light_night <= (n.param1 & 0xF0)) ||
		!ndef->get(n).light_propagates)
		return;

	// Since this recursive function only terminates when there is no light from
	// either bank left, we need to take the max of both banks into account for
	// the case where spreading has stopped for one light bank but not the other.
	light = MYMAX(light_day, n.param1 & 0x0F) |
			MYMAX(light_night, n.param1 & 0xF0);

	n.param1 = light;

	lightSpread(a, p + v3s16(0, 0, 1), light);
	lightSpread(a, p + v3s16(0, 1, 0), light);
	lightSpread(a, p + v3s16(1, 0, 0), light);
	lightSpread(a, p - v3s16(0, 0, 1), light);
	lightSpread(a, p - v3s16(0, 1, 0), light);
	lightSpread(a, p - v3s16(1, 0, 0), light);
}


void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
	bool propagate_shadow)
{
	ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
	//TimeTaker t("updateLighting");

	propagateSunlight(nmin, nmax, propagate_shadow);
	spreadLight(full_nmin, full_nmax);

	//printf("updateLighting: %dms\n", t.stop());
}


void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow)
{
	//TimeTaker t("propagateSunlight");
	VoxelArea a(nmin, nmax);
	bool block_is_underground = (water_level >= nmax.Y);
	const v3s16 &em = vm->m_area.getExtent();

	// NOTE: Direct access to the low 4 bits of param1 is okay here because,
	// by definition, sunlight will never be in the night lightbank.

	for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
		for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++) {
			// see if we can get a light value from the overtop
			u32 i = vm->m_area.index(x, a.MaxEdge.Y + 1, z);
			if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
				if (block_is_underground)
					continue;
			} else if ((vm->m_data[i].param1 & 0x0F) != LIGHT_SUN &&
					propagate_shadow) {
				continue;
			}
			VoxelArea::add_y(em, i, -1);

			for (int y = a.MaxEdge.Y; y >= a.MinEdge.Y; y--) {
				MapNode &n = vm->m_data[i];
				if (!ndef->get(n).sunlight_propagates)
					break;
				n.param1 = LIGHT_SUN;
				VoxelArea::add_y(em, i, -1);
			}
		}
	}
	//printf("propagateSunlight: %dms\n", t.stop());
}


void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
{
	//TimeTaker t("spreadLight");
	VoxelArea a(nmin, nmax);

	for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
		for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
			u32 i = vm->m_area.index(a.MinEdge.X, y, z);
			for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++) {
				MapNode &n = vm->m_data[i];
				if (n.getContent() == CONTENT_IGNORE)
					continue;

				const ContentFeatures &cf = ndef->get(n);
				if (!cf.light_propagates)
					continue;

				// TODO(hmmmmm): Abstract away direct param1 accesses with a
				// wrapper, but something lighter than MapNode::get/setLight

				u8 light_produced = cf.light_source;
				if (light_produced)
					n.param1 = light_produced | (light_produced << 4);

				u8 light = n.param1;
				if (light) {
					lightSpread(a, v3s16(x,     y,     z + 1), light);
					lightSpread(a, v3s16(x,     y + 1, z    ), light);
					lightSpread(a, v3s16(x + 1, y,     z    ), light);
					lightSpread(a, v3s16(x,     y,     z - 1), light);
					lightSpread(a, v3s16(x,     y - 1, z    ), light);
					lightSpread(a, v3s16(x - 1, y,     z    ), light);
				}
			}
		}
	}

	//printf("spreadLight: %dms\n", t.stop());
}


////
//// MapgenBasic
////

MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge)
	: Mapgen(mapgenid, params, emerge)
{
	this->m_emerge = emerge;
	this->m_bmgr   = emerge->biomemgr;

	//// Here, 'stride' refers to the number of elements needed to skip to index
	//// an adjacent element for that coordinate in noise/height/biome maps
	//// (*not* vmanip content map!)

	// Note there is no X stride explicitly defined.  Items adjacent in the X
	// coordinate are assumed to be adjacent in memory as well (i.e. stride of 1).

	// Number of elements to skip to get to the next Y coordinate
	this->ystride = csize.X;

	// Number of elements to skip to get to the next Z coordinate
	this->zstride = csize.X * csize.Y;

	// Z-stride value for maps oversized for 1-down overgeneration
	this->zstride_1d = csize.X * (csize.Y + 1);

	// Z-stride value for maps oversized for 1-up 1-down overgeneration
	this->zstride_1u1d = csize.X * (csize.Y + 2);

	//// Allocate heightmap
	this->heightmap = new s16[csize.X * csize.Z];

	//// Initialize biome generator
	// TODO(hmmmm): should we have a way to disable biomemanager biomes?
	biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize);
	biomemap = biomegen->biomemap;

	//// Look up some commonly used content
	c_stone              = ndef->getId("mapgen_stone");
	c_desert_stone       = ndef->getId("mapgen_desert_stone");
	c_sandstone          = ndef->getId("mapgen_sandstone");
	c_water_source       = ndef->getId("mapgen_water_source");
	c_river_water_source = ndef->getId("mapgen_river_water_source");
	c_lava_source        = ndef->getId("mapgen_lava_source");

	// Fall back to more basic content if not defined
	// river_water_source cannot fallback to water_source because river water
	// needs to be non-renewable and have a short flow range.
	if (c_desert_stone == CONTENT_IGNORE)
		c_desert_stone = c_stone;
	if (c_sandstone == CONTENT_IGNORE)
		c_sandstone = c_stone;

	//// Content used for dungeon generation
	c_cobble                = ndef->getId("mapgen_cobble");
	c_mossycobble           = ndef->getId("mapgen_mossycobble");
	c_stair_cobble          = ndef->getId("mapgen_stair_cobble");
	c_stair_desert_stone    = ndef->getId("mapgen_stair_desert_stone");
	c_sandstonebrick        = ndef->getId("mapgen_sandstonebrick");
	c_stair_sandstone_block = ndef->getId("mapgen_stair_sandstone_block");

	// Fall back to more basic content if not defined
	if (c_mossycobble == CONTENT_IGNORE)
		c_mossycobble = c_cobble;
	if (c_stair_cobble == CONTENT_IGNORE)
		c_stair_cobble = c_cobble;
	if (c_stair_desert_stone == CONTENT_IGNORE)
		c_stair_desert_stone = c_desert_stone;
	if (c_sandstonebrick == CONTENT_IGNORE)
		c_sandstonebrick = c_sandstone;
	if (c_stair_sandstone_block == CONTENT_IGNORE)
		c_stair_sandstone_block = c_sandstonebrick;
}


MapgenBasic::~MapgenBasic()
{
	delete biomegen;
	delete []heightmap;
}


void MapgenBasic::generateBiomes()
{
	// can't generate biomes without a biome generator!
	assert(biomegen);
	assert(biomemap);

	const v3s16 &em = vm->m_area.getExtent();
	u32 index = 0;

	noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);

	for (s16 z = node_min.Z; z <= node_max.Z; z++)
	for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
		Biome *biome = NULL;
		biome_t water_biome_index = 0;
		u16 depth_top = 0;
		u16 base_filler = 0;
		u16 depth_water_top = 0;
		u16 depth_riverbed = 0;
		s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT;
		u32 vi = vm->m_area.index(x, node_max.Y, z);

		// Check node at base of mapchunk above, either a node of a previously
		// generated mapchunk or if not, a node of overgenerated base terrain.
		content_t c_above = vm->m_data[vi + em.X].getContent();
		bool air_above = c_above == CONTENT_AIR;
		bool river_water_above = c_above == c_river_water_source;
		bool water_above = c_above == c_water_source || river_water_above;

		biomemap[index] = BIOME_NONE;

		// If there is air or water above enable top/filler placement, otherwise force
		// nplaced to stone level by setting a number exceeding any possible filler depth.
		u16 nplaced = (air_above || water_above) ? 0 : U16_MAX;

		for (s16 y = node_max.Y; y >= node_min.Y; y--) {
			content_t c = vm->m_data[vi].getContent();
			// Biome is (re)calculated:
			// 1. At the surface of stone below air or water.
			// 2. At the surface of water below air.
			// 3. When stone or water is detected but biome has not yet been calculated.
			// 4. When stone or water is detected just below a biome's lower limit.
			bool is_stone_surface = (c == c_stone) &&
				(air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4

			bool is_water_surface =
				(c == c_water_source || c == c_river_water_source) &&
				(air_above || !biome || y < biome_y_min); // 2, 3, 4

			if (is_stone_surface || is_water_surface) {
				// (Re)calculate biome
				biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z));

				// Add biome to biomemap at first stone surface detected
				if (biomemap[index] == BIOME_NONE && is_stone_surface)
					biomemap[index] = biome->index;

				// Store biome of first water surface detected, as a fallback
				// entry for the biomemap.
				if (water_biome_index == 0 && is_water_surface)
					water_biome_index = biome->index;

				depth_top = biome->depth_top;
				base_filler = MYMAX(depth_top +
					biome->depth_filler +
					noise_filler_depth->result[index], 0.0f);
				depth_water_top = biome->depth_water_top;
				depth_riverbed = biome->depth_riverbed;
				biome_y_min = biome->min_pos.Y;
			}

			if (c == c_stone) {
				content_t c_below = vm->m_data[vi - em.X].getContent();

				// If the node below isn't solid, make this node stone, so that
				// any top/filler nodes above are structurally supported.
				// This is done by aborting the cycle of top/filler placement
				// immediately by forcing nplaced to stone level.
				if (c_below == CONTENT_AIR
						|| c_below == c_water_source
						|| c_below == c_river_water_source)
					nplaced = U16_MAX;

				if (river_water_above) {
					if (nplaced < depth_riverbed) {
						vm->m_data[vi] = MapNode(biome->c_riverbed);
						nplaced++;
					} else {
						nplaced = U16_MAX;  // Disable top/filler placement
						river_water_above = false;
					}
				} else if (nplaced < depth_top) {
					vm->m_data[vi] = MapNode(biome->c_top);
					nplaced++;
				} else if (nplaced < base_filler) {
					vm->m_data[vi] = MapNode(biome->c_filler);
					nplaced++;
				} else {
					vm->m_data[vi] = MapNode(biome->c_stone);
					nplaced = U16_MAX;  // Disable top/filler placement
				}

				air_above = false;
				water_above = false;
			} else if (c == c_water_source) {
				vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top))
						? biome->c_water_top : biome->c_water);
				nplaced = 0;  // Enable top/filler placement for next surface
				air_above = false;
				water_above = true;
			} else if (c == c_river_water_source) {
				vm->m_data[vi] = MapNode(biome->c_river_water);
				nplaced = 0;  // Enable riverbed placement for next surface
				air_above = false;
				water_above = true;
				river_water_above = true;
			} else if (c == CONTENT_AIR) {
				nplaced = 0;  // Enable top/filler placement for next surface
				air_above = true;
				water_above = false;
			} else {  // Possible various nodes overgenerated from neighbouring mapchunks
				nplaced = U16_MAX;  // Disable top/filler placement
				air_above = false;
				water_above = false;
			}

			VoxelArea::add_y(em, vi, -1);
		}
		// If no stone surface detected in mapchunk column and a water surface
		// biome fallback exists, add it to the biomemap. This avoids water
		// surface decorations failing in deep water.
 		if (biomemap[index] == BIOME_NONE && water_biome_index != 0)
			biomemap[index] = water_biome_index;
	}
}


void MapgenBasic::dustTopNodes()
{
	if (node_max.Y < water_level)
		return;

	const v3s16 &em = vm->m_area.getExtent();
	u32 index = 0;

	for (s16 z = node_min.Z; z <= node_max.Z; z++)
	for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
		Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]);

		if (biome->c_dust == CONTENT_IGNORE)
			continue;

		// Check if mapchunk above has generated, if so, drop dust from 16 nodes
		// above current mapchunk top, above decorations that will extend above
		// the current mapchunk. If the mapchunk above has not generated, it
		// will provide this required dust when it does.
		u32 vi = vm->m_area.index(x, full_node_max.Y, z);
		content_t c_full_max = vm->m_data[vi].getContent();
		s16 y_start;

		if (c_full_max == CONTENT_AIR) {
			y_start = full_node_max.Y - 1;
		} else if (c_full_max == CONTENT_IGNORE) {
			vi = vm->m_area.index(x, node_max.Y + 1, z);
			content_t c_max = vm->m_data[vi].getContent();

			if (c_max == CONTENT_AIR)
				y_start = node_max.Y;
			else
				continue;
		} else {
			continue;
		}

		vi = vm->m_area.index(x, y_start, z);
		for (s16 y = y_start; y >= node_min.Y - 1; y--) {
			if (vm->m_data[vi].getContent() != CONTENT_AIR)
				break;

			VoxelArea::add_y(em, vi, -1);
		}

		content_t c = vm->m_data[vi].getContent();
		NodeDrawType dtype = ndef->get(c).drawtype;
		// Only place on cubic, walkable, non-dust nodes.
		// Dust check needed due to avoid double layer of dust caused by
		// dropping dust from 16 nodes above mapchunk top.
		if ((dtype == NDT_NORMAL ||
				dtype == NDT_ALLFACES ||
				dtype == NDT_ALLFACES_OPTIONAL ||
				dtype == NDT_GLASSLIKE ||
				dtype == NDT_GLASSLIKE_FRAMED ||
				dtype == NDT_GLASSLIKE_FRAMED_OPTIONAL) &&
				ndef->get(c).walkable && c != biome->c_dust) {
			VoxelArea::add_y(em, vi, 1);
			vm->m_data[vi] = MapNode(biome->c_dust);
		}
	}
}


void MapgenBasic::generateCavesNoiseIntersection(s16 max_stone_y)
{
	if (node_min.Y > max_stone_y)
		return;

	CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize,
		&np_cave1, &np_cave2, seed, cave_width);

	caves_noise.generateCaves(vm, node_min, node_max, biomemap);
}


void MapgenBasic::generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_depth)
{
	if (node_min.Y > max_stone_y || node_max.Y > large_cave_depth)
		return;

	PseudoRandom ps(blockseed + 21343);
	u32 bruises_count = ps.range(0, 2);

	for (u32 i = 0; i < bruises_count; i++) {
		CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
			c_water_source, c_lava_source, lava_depth, biomegen);

		cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y,
			heightmap);
	}
}


bool MapgenBasic::generateCavernsNoise(s16 max_stone_y)
{
	if (node_min.Y > max_stone_y || node_min.Y > cavern_limit)
		return false;

	CavernsNoise caverns_noise(ndef, csize, &np_cavern,
		seed, cavern_limit, cavern_taper, cavern_threshold);

	return caverns_noise.generateCaverns(vm, node_min, node_max);
}


void MapgenBasic::generateDungeons(s16 max_stone_y)
{
	if (max_stone_y < node_min.Y)
		return;

	// Get biome at mapchunk midpoint
	v3s16 chunk_mid = node_min + (node_max - node_min) / v3s16(2, 2, 2);
	Biome *biome = (Biome *)biomegen->getBiomeAtPoint(chunk_mid);

	DungeonParams dp;

	dp.seed             = seed;
	dp.only_in_ground   = true;
	dp.corridor_len_min = 1;
	dp.corridor_len_max = 13;
	dp.rooms_min        = 2;
	dp.rooms_max        = 16;

	dp.np_density       = nparams_dungeon_density;
	dp.np_alt_wall      = nparams_dungeon_alt_wall;

	// Biome-defined dungeon nodes
	if (biome->c_dungeon != CONTENT_IGNORE) {
		dp.c_wall              = biome->c_dungeon;
		// If 'node_dungeon_alt' is not defined by biome, it and dp.c_alt_wall
		// become CONTENT_IGNORE which skips the alt wall node placement loop in
		// dungeongen.cpp.
		dp.c_alt_wall          = biome->c_dungeon_alt;
		// Stairs fall back to 'c_dungeon' if not defined by biome
		dp.c_stair = (biome->c_dungeon_stair != CONTENT_IGNORE) ?
			biome->c_dungeon_stair : biome->c_dungeon;

		dp.diagonal_dirs       = false;
		dp.holesize            = v3s16(2, 2, 2);
		dp.room_size_min       = v3s16(6, 4, 6);
		dp.room_size_max       = v3s16(10, 6, 10);
		dp.room_size_large_min = v3s16(10, 8, 10);
		dp.room_size_large_max = v3s16(18, 16, 18);
		dp.notifytype          = GENNOTIFY_DUNGEON;

	// Otherwise classic behaviour
	} else if (biome->c_stone == c_stone) {
		dp.c_wall              = c_cobble;
		dp.c_alt_wall          = c_mossycobble;
		dp.c_stair             = c_stair_cobble;

		dp.diagonal_dirs       = false;
		dp.holesize            = v3s16(1, 2, 1);
		dp.room_size_min       = v3s16(4, 4, 4);
		dp.room_size_max       = v3s16(8, 6, 8);
		dp.room_size_large_min = v3s16(8, 8, 8);
		dp.room_size_large_max = v3s16(16, 16, 16);
		dp.notifytype          = GENNOTIFY_DUNGEON;

	} else if (biome->c_stone == c_desert_stone) {
		dp.c_wall              = c_desert_stone;
		dp.c_alt_wall          = CONTENT_IGNORE;
		dp.c_stair             = c_stair_desert_stone;

		dp.diagonal_dirs       = true;
		dp.holesize            = v3s16(2, 3, 2);
		dp.room_size_min       = v3s16(6, 9, 6);
		dp.room_size_max       = v3s16(10, 11, 10);
		dp.room_size_large_min = v3s16(10, 13, 10);
		dp.room_size_large_max = v3s16(18, 21, 18);
		dp.notifytype          = GENNOTIFY_TEMPLE;

	} else if (biome->c_stone == c_sandstone) {
		dp.c_wall              = c_sandstonebrick;
		dp.c_alt_wall          = CONTENT_IGNORE;
		dp.c_stair             = c_stair_sandstone_block;

		dp.diagonal_dirs       = false;
		dp.holesize            = v3s16(2, 2, 2);
		dp.room_size_min       = v3s16(6, 4, 6);
		dp.room_size_max       = v3s16(10, 6, 10);
		dp.room_size_large_min = v3s16(10, 8, 10);
		dp.room_size_large_max = v3s16(18, 16, 18);
		dp.notifytype          = GENNOTIFY_DUNGEON;

	// Fallback to using biome 'node_stone'
	} else {
		dp.c_wall              = biome->c_stone;
		dp.c_alt_wall          = CONTENT_IGNORE;
		dp.c_stair             = biome->c_stone;

		dp.diagonal_dirs       = false;
		dp.holesize            = v3s16(2, 2, 2);
		dp.room_size_min       = v3s16(6, 4, 6);
		dp.room_size_max       = v3s16(10, 6, 10);
		dp.room_size_large_min = v3s16(10, 8, 10);
		dp.room_size_large_max = v3s16(18, 16, 18);
		dp.notifytype          = GENNOTIFY_DUNGEON;
	}

	DungeonGen dgen(ndef, &gennotify, &dp);
	dgen.generate(vm, blockseed, full_node_min, full_node_max);
}


////
//// GenerateNotifier
////

GenerateNotifier::GenerateNotifier(u32 notify_on,
	std::set<u32> *notify_on_deco_ids)
{
	m_notify_on = notify_on;
	m_notify_on_deco_ids = notify_on_deco_ids;
}


void GenerateNotifier::setNotifyOn(u32 notify_on)
{
	m_notify_on = notify_on;
}


void GenerateNotifier::setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids)
{
	m_notify_on_deco_ids = notify_on_deco_ids;
}


bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id)
{
	if (!(m_notify_on & (1 << type)))
		return false;

	if (type == GENNOTIFY_DECORATION &&
		m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->end())
		return false;

	GenNotifyEvent gne;
	gne.type = type;
	gne.pos  = pos;
	gne.id   = id;
	m_notify_events.push_back(gne);

	return true;
}


void GenerateNotifier::getEvents(
	std::map<std::string, std::vector<v3s16> > &event_map)
{
	std::list<GenNotifyEvent>::iterator it;

	for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) {
		GenNotifyEvent &gn = *it;
		std::string name = (gn.type == GENNOTIFY_DECORATION) ?
			"decoration#"+ itos(gn.id) :
			flagdesc_gennotify[gn.type].name;

		event_map[name].push_back(gn.pos);
	}
}


void GenerateNotifier::clearEvents()
{
	m_notify_events.clear();
}


////
//// MapgenParams
////


MapgenParams::~MapgenParams()
{
	delete bparams;
}


void MapgenParams::readParams(const Settings *settings)
{
	std::string seed_str;
	const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed";

	if (settings->getNoEx(seed_name, seed_str)) {
		if (!seed_str.empty())
			seed = read_seed(seed_str.c_str());
		else
			myrand_bytes(&seed, sizeof(seed));
	}

	std::string mg_name;
	if (settings->getNoEx("mg_name", mg_name)) {
		mgtype = Mapgen::getMapgenType(mg_name);
		if (mgtype == MAPGEN_INVALID)
			mgtype = MAPGEN_DEFAULT;
	}

	settings->getS16NoEx("water_level", water_level);
	settings->getS16NoEx("mapgen_limit", mapgen_limit);
	settings->getS16NoEx("chunksize", chunksize);
	settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen);

	delete bparams;
	bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL);
	if (bparams) {
		bparams->readParams(settings);
		bparams->seed = seed;
	}
}


void MapgenParams::writeParams(Settings *settings) const
{
	settings->set("mg_name", Mapgen::getMapgenName(mgtype));
	settings->setU64("seed", seed);
	settings->setS16("water_level", water_level);
	settings->setS16("mapgen_limit", mapgen_limit);
	settings->setS16("chunksize", chunksize);
	settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX);

	if (bparams)
		bparams->writeParams(settings);
}


// Calculate exact edges of the outermost mapchunks that are within the
// set 'mapgen_limit'.
void MapgenParams::calcMapgenEdges()
{
	// Central chunk offset, in blocks
	s16 ccoff_b = -chunksize / 2;
	// Chunksize, in nodes
	s32 csize_n = chunksize * MAP_BLOCKSIZE;
	// Minp/maxp of central chunk, in nodes
	s16 ccmin = ccoff_b * MAP_BLOCKSIZE;
	s16 ccmax = ccmin + csize_n - 1;
	// Fullminp/fullmaxp of central chunk, in nodes
	s16 ccfmin = ccmin - MAP_BLOCKSIZE;
	s16 ccfmax = ccmax + MAP_BLOCKSIZE;
	// Effective mapgen limit, in blocks
	// Uses same calculation as ServerMap::blockpos_over_mapgen_limit(v3s16 p)
	s16 mapgen_limit_b = rangelim(mapgen_limit,
		0, MAX_MAP_GENERATION_LIMIT) / MAP_BLOCKSIZE;
	// Effective mapgen limits, in nodes
	s16 mapgen_limit_min = -mapgen_limit_b * MAP_BLOCKSIZE;
	s16 mapgen_limit_max = (mapgen_limit_b + 1) * MAP_BLOCKSIZE - 1;
	// Number of complete chunks from central chunk fullminp/fullmaxp
	// to effective mapgen limits.
	s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0);
	s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0);
	// Mapgen edges, in nodes
	mapgen_edge_min = ccmin - numcmin * csize_n;
	mapgen_edge_max = ccmax + numcmax * csize_n;

	m_mapgen_edges_calculated = true;
}


s32 MapgenParams::getSpawnRangeMax()
{
	if (!m_mapgen_edges_calculated)
		calcMapgenEdges();

	return MYMIN(-mapgen_edge_min, mapgen_edge_max);
}
(playerspeed.getLength() > 1.0*BS) playerspeeddir = playerspeed / playerspeed.getLength(); // Predict to next block v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS; v3s16 center_nodepos = floatToInt(playerpos_predicted, BS); 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()); /*dstream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<"," <<camera_dir.Z<<")"<<std::endl;*/ /* Get the starting value of the block finder radius. */ 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;*/ // This has to be incremented only when the nothing to send pause // is not active m_nearest_unsent_reset_timer += dtime; // Reset periodically to avoid possible bugs or other mishaps if(m_nearest_unsent_reset_timer > 10.0) { m_nearest_unsent_reset_timer = 0; m_nearest_unsent_d = 0; /*dstream<<"Resetting m_nearest_unsent_d for " <<server->getPlayerName(peer_id)<<std::endl;*/ } //s16 last_nearest_unsent_d = m_nearest_unsent_d; s16 d_start = m_nearest_unsent_d; //dstream<<"d_start="<<d_start<<std::endl; u16 max_simul_sends_setting = g_settings.getU16 ("max_simultaneous_block_sends_per_client"); u16 max_simul_sends_usually = max_simul_sends_setting; /* Check the time from last addNode/removeNode. Decrease send rate if player is building stuff. */ m_time_from_building += dtime; if(m_time_from_building < g_settings.getFloat( "full_block_send_enable_min_time_from_building")) { max_simul_sends_usually = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS; } /* Number of blocks sending + number of blocks selected for sending */ u32 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; s16 d_max = g_settings.getS16("max_block_send_distance"); s16 d_max_gen = g_settings.getS16("max_block_generate_distance"); // Don't loop very much at a time if(d_max > d_start+1) d_max = d_start+1; /*if(d_max_gen > d_start+2) d_max_gen = d_start+2;*/ //dstream<<"Starting from "<<d_start<<std::endl; bool sending_something = false; bool no_blocks_found_for_sending = true; bool queue_is_full = false; s16 d; for(d = d_start; d <= d_max; d++) { //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl; /* 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. */ // Start with the usual maximum u16 max_simul_dynamic = max_simul_sends_usually; // If block is very close, allow full maximum if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D) max_simul_dynamic = max_simul_sends_setting; // Don't select too many blocks for sending if(num_blocks_selected >= max_simul_dynamic) { queue_is_full = true; goto queue_full_break; } // Don't send blocks that are currently being transferred 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; // If this is true, inexistent block will be made from scratch bool generate = d <= d_max_gen; { /*// Limit the generating area vertically to 2/3 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3) generate = false;*/ // Limit the send area vertically to 2/3 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3) continue; } #if 1 /* If block is far away, don't generate it unless it is near ground level. */ if(d >= 4) { #if 1 // Block center y in nodes f32 y = (f32)(p.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE/2); // Don't generate if it's very high or very low if(y < -64 || y > 64) generate = false; #endif #if 0 v2s16 p2d_nodes_center( MAP_BLOCKSIZE*p.X, MAP_BLOCKSIZE*p.Z); // Get ground height in nodes s16 gh = server->m_env.getServerMap().findGroundLevel( p2d_nodes_center); // If differs a lot, don't generate if(fabs(gh - y) > MAP_BLOCKSIZE*2) generate = false; // Actually, don't even send it //continue; #endif } #endif //dstream<<"d="<<d<<std::endl; /* Don't generate or send if not in sight */ if(isBlockInSight(p, camera_pos, camera_dir, 10000*BS) == false) { continue; } /* Don't send already sent blocks */ { if(m_blocks_sent.find(p) != NULL) { continue; } } /* Check if map has this block */ MapBlock *block = server->m_env.getMap().getBlockNoCreateNoEx(p); bool surely_not_found_on_disk = false; bool block_is_invalid = false; if(block != NULL) { // Block is dummy if data doesn't exist. // It means it has been not found from disk and not generated if(block->isDummy()) { surely_not_found_on_disk = true; } // Block is valid if lighting is up-to-date and data exists if(block->isValid() == false) { block_is_invalid = true; } /*if(block->isFullyGenerated() == false) { block_is_invalid = true; }*/ #if 0 v2s16 p2d(p.X, p.Z); ServerMap *map = (ServerMap*)(&server->m_env.getMap()); v2s16 chunkpos = map->sector_to_chunk(p2d); if(map->chunkNonVolatile(chunkpos) == false) block_is_invalid = true; #endif if(block->isGenerated() == false) block_is_invalid = true; #if 1 /* If block is not close, don't send it unless it is near ground level. Block is near ground level if night-time mesh differs from day-time mesh. */ if(d > 3) { if(block->dayNightDiffed() == false) continue; } #endif } /* 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 block has been found being not sent and possibly to exist */ if(no_blocks_found_for_sending) { if(generate == true) new_nearest_unsent_d = d; } no_blocks_found_for_sending = false; /* Add inexistent block to emerge queue. */ if(block == NULL || surely_not_found_on_disk || block_is_invalid) { //TODO: Get value from somewhere // Allow only one block in emerge queue //if(server->m_emerge_queue.peerItemCount(peer_id) < 1) // Allow two blocks in queue per client if(server->m_emerge_queue.peerItemCount(peer_id) < 2) { //dstream<<"Adding block to emerge queue"<<std::endl; // 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 send queue */ PrioritySortedBlockTransfer q((float)d, p, peer_id); dest.push_back(q); num_blocks_selected += 1; sending_something = true; } } queue_full_break: //dstream<<"Stopped at "<<d<<std::endl; if(no_blocks_found_for_sending) { if(queue_is_full == false) new_nearest_unsent_d = d; } if(new_nearest_unsent_d != -1) m_nearest_unsent_d = new_nearest_unsent_d; if(sending_something == false) { m_nothing_to_send_counter++; if((s16)m_nothing_to_send_counter >= g_settings.getS16("max_block_send_distance")) { // Pause time in seconds m_nothing_to_send_pause_timer = 1.0; /*dstream<<"nothing to send to " <<server->getPlayerName(peer_id) <<" (d="<<d<<")"<<std::endl;*/ } } else { m_nothing_to_send_counter = 0; } /*timer_result = timer.stop(true); if(timer_result != 0) dstream<<"GetNextBlocks duration: "<<timer_result<<" (!=0)"<<std::endl;*/ } 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, BS); 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 */ { 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->m_env.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); // DEPRECATED // count=0 writeU16(bos, 0); 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) { 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) { 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) { 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) { 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; avg_rtt = 0; } void PlayerInfo::PrintLine(std::ostream *s) { (*s)<<id<<": "; (*s)<<"\""<<name<<"\" (" <<(position.X/10)<<","<<(position.Y/10) <<","<<(position.Z/10)<<") "; 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 ): m_env(new ServerMap(mapsavedir), this), m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this), m_authmanager(mapsavedir+"/auth.txt"), m_thread(this), m_emergethread(this), m_time_counter(0), m_time_of_day_send_timer(0), m_uptime(0), m_mapsavedir(mapsavedir), m_shutdown_requested(false), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(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; // Register us to receive map edit events m_env.getMap().addEventReceiver(this); // If file exists, load environment metadata if(fs::PathExists(m_mapsavedir+"/env_meta.txt")) { dstream<<"Server: Loading environment metadata"<<std::endl; m_env.loadMeta(m_mapsavedir); } // Load players dstream<<"Server: Loading players"<<std::endl; m_env.deSerializePlayers(m_mapsavedir); } Server::~Server() { dstream<<"Server::~Server()"<<std::endl; /* Send shutdown message */ { JMutexAutoLock conlock(m_con_mutex); std::wstring line = L"*** Server shutting down"; /* Send the message to 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; try{ SendChatMessage(client->peer_id, line); } catch(con::PeerNotFoundException &e) {} } } /* Save players */ dstream<<"Server: Saving players"<<std::endl; m_env.serializePlayers(m_mapsavedir); /* Save environment metadata */ dstream<<"Server: Saving environment metadata"<<std::endl; m_env.saveMeta(m_mapsavedir); /* Stop threads */ stop(); /* Delete clients */ { 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; } { ScopeProfiler sp(&g_profiler, "Server: selecting and sending " "blocks to clients"); // 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 and overall game time */ { JMutexAutoLock envlock(m_env_mutex); 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_env.setTimeOfDay((m_env.getTimeOfDay() + 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_env.getTimeOfDay()); // Send as reliable m_con.Send(client->peer_id, 0, data, true); } } } { // Process connection's timeouts JMutexAutoLock lock2(m_con_mutex); ScopeProfiler sp(&g_profiler, "Server: connection timeout processing"); m_con.RunTimeouts(dtime); } { // This has to be called so that the client list gets synced // with the peer list of the connection ScopeProfiler sp(&g_profiler, "Server: peer change handling"); handlePeerChanges(); } { // Step environment // This also runs Map's timers JMutexAutoLock lock(m_env_mutex); ScopeProfiler sp(&g_profiler, "Server: environment step"); 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); ScopeProfiler sp(&g_profiler, "Server: liquid transform"); 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); } } } // 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(); Player *player = m_env.getPlayer(client->peer_id); if(player==NULL) continue; std::cout<<player->getName()<<"\t"; client->PrintInfo(std::cout); } } } //if(g_settings.getBool("enable_experimental")) { /* Check added and deleted active objects */ { //dstream<<"Server: Checking added and deleted active objects"<<std::endl; JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); ScopeProfiler sp(&g_profiler, "Server: checking added and deleted objects"); // Radius inside which objects are active s16 radius = 32; 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); if(player==NULL) { // This can happen if the client timeouts somehow /*dstream<<"WARNING: "<<__FUNCTION_NAME<<": Client " <<client->peer_id <<" has no associated player"<<std::endl;*/ continue; } v3s16 pos = floatToInt(player->getPosition(), BS); core::map<u16, bool> removed_objects; core::map<u16, bool> added_objects; m_env.getRemovedActiveObjects(pos, radius, client->m_known_objects, removed_objects); m_env.getAddedActiveObjects(pos, radius, client->m_known_objects, added_objects); // Ignore if nothing happened if(removed_objects.size() == 0 && added_objects.size() == 0) { //dstream<<"INFO: active objects: none changed"<<std::endl; continue; } std::string data_buffer; char buf[4]; // Handle removed objects writeU16((u8*)buf, removed_objects.size()); data_buffer.append(buf, 2); for(core::map<u16, bool>::Iterator i = removed_objects.getIterator(); i.atEnd()==false; i++) { // Get object u16 id = i.getNode()->getKey(); ServerActiveObject* obj = m_env.getActiveObject(id); // Add to data buffer for sending writeU16((u8*)buf, i.getNode()->getKey()); data_buffer.append(buf, 2); // Remove from known objects client->m_known_objects.remove(i.getNode()->getKey()); if(obj && obj->m_known_by_count > 0) obj->m_known_by_count--; } // Handle added objects writeU16((u8*)buf, added_objects.size()); data_buffer.append(buf, 2); for(core::map<u16, bool>::Iterator i = added_objects.getIterator(); i.atEnd()==false; i++) { // Get object u16 id = i.getNode()->getKey(); ServerActiveObject* obj = m_env.getActiveObject(id); // Get object type u8 type = ACTIVEOBJECT_TYPE_INVALID; if(obj == NULL) dstream<<"WARNING: "<<__FUNCTION_NAME <<": NULL object"<<std::endl; else type = obj->getType(); // Add to data buffer for sending writeU16((u8*)buf, id); data_buffer.append(buf, 2); writeU8((u8*)buf, type); data_buffer.append(buf, 1); if(obj) data_buffer.append(serializeLongString( obj->getClientInitializationData())); else data_buffer.append(serializeLongString("")); // Add to known objects client->m_known_objects.insert(i.getNode()->getKey(), false); if(obj) obj->m_known_by_count++; } // Send packet SharedBuffer<u8> reply(2 + data_buffer.size()); writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD); memcpy((char*)&reply[2], data_buffer.c_str(), data_buffer.size()); // Send as reliable m_con.Send(client->peer_id, 0, reply, true); dstream<<"INFO: Server: Sent object remove/add: " <<removed_objects.size()<<" removed, " <<added_objects.size()<<" added, " <<"packet size is "<<reply.getSize()<<std::endl; } #if 0 /* Collect a list of all the objects known by the clients and report it back to the environment. */ core::map<u16, bool> all_known_objects; for(core::map<u16, RemoteClient*>::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); // Go through all known objects of client for(core::map<u16, bool>::Iterator i = client->m_known_objects.getIterator(); i.atEnd()==false; i++) { u16 id = i.getNode()->getKey(); all_known_objects[id] = true; } } m_env.setKnownActiveObjects(whatever); #endif } /* Send object messages */ { JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); ScopeProfiler sp(&g_profiler, "Server: sending object messages"); // Key = object id // Value = data sent by object core::map<u16, core::list<ActiveObjectMessage>* > buffered_messages; // Get active object messages from environment for(;;) { ActiveObjectMessage aom = m_env.getActiveObjectMessage(); if(aom.id == 0) break; core::list<ActiveObjectMessage>* message_list = NULL; core::map<u16, core::list<ActiveObjectMessage>* >::Node *n; n = buffered_messages.find(aom.id); if(n == NULL) { message_list = new core::list<ActiveObjectMessage>; buffered_messages.insert(aom.id, message_list); } else { message_list = n->getValue(); } message_list->push_back(aom); } // Route data to every client for(core::map<u16, RemoteClient*>::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); std::string reliable_data; std::string unreliable_data; // Go through all objects in message buffer for(core::map<u16, core::list<ActiveObjectMessage>* >::Iterator j = buffered_messages.getIterator(); j.atEnd()==false; j++) { // If object is not known by client, skip it u16 id = j.getNode()->getKey(); if(client->m_known_objects.find(id) == NULL) continue; // Get message list of object core::list<ActiveObjectMessage>* list = j.getNode()->getValue(); // Go through every message for(core::list<ActiveObjectMessage>::Iterator k = list->begin(); k != list->end(); k++) { // Compose the full new data with header ActiveObjectMessage aom = *k; std::string new_data; // Add object id char buf[2]; writeU16((u8*)&buf[0], aom.id); new_data.append(buf, 2); // Add data new_data += serializeString(aom.datastring); // Add data to buffer if(aom.reliable) reliable_data += new_data; else unreliable_data += new_data; } } /* reliable_data and unreliable_data are now ready. Send them. */ if(reliable_data.size() > 0) { SharedBuffer<u8> reply(2 + reliable_data.size()); writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES); memcpy((char*)&reply[2], reliable_data.c_str(), reliable_data.size()); // Send as reliable m_con.Send(client->peer_id, 0, reply, true); } if(unreliable_data.size() > 0) { SharedBuffer<u8> reply(2 + unreliable_data.size()); writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES); memcpy((char*)&reply[2], unreliable_data.c_str(), unreliable_data.size()); // Send as unreliable m_con.Send(client->peer_id, 0, reply, false); } /*if(reliable_data.size() > 0 || unreliable_data.size() > 0) { dstream<<"INFO: Server: Size of object message data: " <<"reliable: "<<reliable_data.size() <<", unreliable: "<<unreliable_data.size() <<std::endl; }*/ } // Clear buffered_messages for(core::map<u16, core::list<ActiveObjectMessage>* >::Iterator i = buffered_messages.getIterator(); i.atEnd()==false; i++) { delete i.getNode()->getValue(); } } } // enable_experimental /* Send queued-for-sending map edit events. */ { // Don't send too many at a time u32 count = 0; while(m_unsent_map_edit_queue.size() != 0) { MapEditEvent* event = m_unsent_map_edit_queue.pop_front(); // Players far away from the change are stored here. // Instead of sending the changes, MapBlocks are set not sent // for them. core::list<u16> far_players; if(event->type == MEET_ADDNODE) { dstream<<"Server: MEET_ADDNODE"<<std::endl; sendAddNode(event->p, event->n, event->already_known_by_peer, &far_players, 30); } else if(event->type == MEET_REMOVENODE) { dstream<<"Server: MEET_REMOVENODE"<<std::endl; sendRemoveNode(event->p, event->already_known_by_peer, &far_players, 30); } else if(event->type == MEET_BLOCK_NODE_METADATA_CHANGED) { dstream<<"Server: MEET_BLOCK_NODE_METADATA_CHANGED"<<std::endl; setBlockNotSent(event->p); } else if(event->type == MEET_OTHER) { dstream<<"WARNING: Server: MEET_OTHER not implemented" <<std::endl; } else { dstream<<"WARNING: Server: Unknown MapEditEvent " <<((u32)event->type)<<std::endl; } /* Set blocks not sent to far players */ core::map<v3s16, MapBlock*> modified_blocks2; for(core::map<v3s16, bool>::Iterator i = event->modified_blocks.getIterator(); i.atEnd()==false; i++) { v3s16 p = i.getNode()->getKey(); modified_blocks2.insert(p, m_env.getMap().getBlockNoCreateNoEx(p)); } for(core::list<u16>::Iterator i = far_players.begin(); i != far_players.end(); i++) { u16 peer_id = *i; RemoteClient *client = getClient(peer_id); if(client==NULL) continue; client->SetBlocksNotSent(modified_blocks2); } delete event; // Don't send too many at a time count++; if(count >= 1 && m_unsent_map_edit_queue.size() < 100) break; } } /* Send object positions TODO: Get rid of MapBlockObjects */ { float &counter = m_objectdata_timer; counter += dtime; if(counter >= g_settings.getFloat("objectdata_interval")) { JMutexAutoLock lock1(m_env_mutex); JMutexAutoLock lock2(m_con_mutex); ScopeProfiler sp(&g_profiler, "Server: sending mbo positions"); SendObjectData(counter); counter = 0.0; } } /* Step node metadata TODO: Move to ServerEnvironment and utilize active block stuff */ /*{ //TimeTaker timer("Step node metadata"); JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); ScopeProfiler sp(&g_profiler, "Server: stepping node metadata"); core::map<v3s16, MapBlock*> changed_blocks; m_env.getMap().nodeMetadataStep(dtime, changed_blocks); // Use setBlockNotSent for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); for(core::map<u16, RemoteClient*>::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); client->SetBlockNotSent(block->getPos()); } } }*/ /* Trigger emergethread (it 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, players and auth stuff { float &counter = m_savemap_timer; counter += dtime; if(counter >= g_settings.getFloat("server_map_save_interval")) { counter = 0.0; ScopeProfiler sp(&g_profiler, "Server: saving stuff"); // Auth stuff if(m_authmanager.isModified()) m_authmanager.save(); // Map JMutexAutoLock lock(m_env_mutex); if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == true) { // Save only changed parts m_env.getMap().save(true); // Delete unused sectors u32 deleted_count = m_env.getMap().unloadUnusedData( g_settings.getFloat("server_unload_unused_sectors_timeout")); if(deleted_count > 0) { dout_server<<"Server: Unloaded "<<deleted_count <<" sectors from memory"<<std::endl; } // Save players m_env.serializePlayers(m_mapsavedir); // Save environment metadata m_env.saveMeta(m_mapsavedir); } } } } 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 = 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 // [23] u8[28] password <--- can be sent without this, from old versions if(datasize < 2+1+PLAYERNAME_SIZE) 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 char playername[PLAYERNAME_SIZE]; for(u32 i=0; i<PLAYERNAME_SIZE-1; i++) { playername[i] = data[3+i]; } playername[PLAYERNAME_SIZE-1] = 0; if(playername[0]=='\0') { derr_server<<DTIME<<"Server: Player has empty name"<<std::endl; SendAccessDenied(m_con, peer_id, L"Empty name"); return; } if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false) { derr_server<<DTIME<<"Server: Player has invalid name"<<std::endl; SendAccessDenied(m_con, peer_id, L"Name contains unallowed characters"); return; } // Get password char password[PASSWORD_SIZE]; if(datasize == 2+1+PLAYERNAME_SIZE) { // old version - assume blank password password[0] = 0; } else { for(u32 i=0; i<PASSWORD_SIZE-1; i++) { password[i] = data[23+i]; } password[PASSWORD_SIZE-1] = 0; } std::string checkpwd; if(m_authmanager.exists(playername)) { checkpwd = m_authmanager.getPassword(playername); } else { checkpwd = g_settings.get("default_password"); } if(password != checkpwd && checkpwd != "") { derr_server<<DTIME<<"Server: peer_id="<<peer_id <<": supplied invalid password for " <<playername<<std::endl; SendAccessDenied(m_con, peer_id, L"Invalid password"); return; } // Add player to auth manager if(m_authmanager.exists(playername) == false) { derr_server<<DTIME<<"Server: adding player "<<playername <<" to auth manager"<<std::endl; m_authmanager.add(playername); m_authmanager.setPassword(playername, checkpwd); m_authmanager.setPrivs(playername, stringToPrivs(g_settings.get("default_privs"))); m_authmanager.save(); } // Get player Player *player = emergePlayer(playername, password, peer_id); /*{ // DEBUG: Test serialization std::ostringstream test_os; player->serialize(test_os); dstream<<"Player serialization test: \""<<test_os.str() <<"\""<<std::endl; std::istringstream test_is(test_os.str()); player->deSerialize(test_is); }*/ // 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]); }*/ /* Answer with a TOCLIENT_INIT */ { SharedBuffer<u8> reply(2+1+6+8); writeU16(&reply[0], TOCLIENT_INIT); writeU8(&reply[2], deployed); writeV3S16(&reply[2+1], floatToInt(player->getPosition()+v3f(0,BS/2,0), BS)); writeU64(&reply[2+1+6], m_env.getServerMap().getSeed()); // Send as reliable m_con.Send(peer_id, 0, reply, true); } /* Send complete position information */ SendMovePlayer(player); 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 UpdateCrafting(peer->id); SendInventory(peer->id); // Send HP { Player *player = m_env.getPlayer(peer_id); SendPlayerHP(player); } // Send time of day { SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY( m_env.getTimeOfDay()); m_con.Send(peer->id, 0, data, true); } // Send information about server to player in chat SendChatMessage(peer_id, getStatusString()); // 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; if((getPlayerPrivs(player) & PRIV_BUILD) == 0) 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); UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } // Remove from block block->removeObject(id); } } else if(command == TOSERVER_CLICK_ACTIVEOBJECT) { if(datasize < 7) return; if((getPlayerPrivs(player) & PRIV_BUILD) == 0) return; /* length: 7 [0] u16 command [2] u8 button (0=left, 1=right) [3] u16 id [5] u16 item */ u8 button = readU8(&data[2]); u16 id = readS16(&data[3]); u16 item_i = readU16(&data[11]); ServerActiveObject *obj = m_env.getActiveObject(id); if(obj == NULL) { derr_server<<"Server: CLICK_ACTIVEOBJECT: object not found" <<std::endl; return; } //TODO: Check that object is reasonably close // Left click, pick object up (usually) 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; } // Skip if object has been removed if(obj->m_removed) return; /* Create the inventory item */ InventoryItem *item = obj->createPickedUpItem(); if(item) { // Add to inventory and send inventory ilist->addItem(item); UpdateCrafting(player->peer_id); SendInventory(player->peer_id); // Remove object from environment obj->m_removed = true; } else { /* Item cannot be picked up. Punch it instead. */ ToolItem *titem = NULL; std::string toolname = ""; InventoryList *mlist = player->inventory.getList("main"); if(mlist != NULL) { InventoryItem *item = mlist->getItem(item_i); if(item && (std::string)item->getName() == "ToolItem") { titem = (ToolItem*)item; toolname = titem->getToolName(); } } u16 wear = obj->punch(toolname); if(titem) { bool weared_out = titem->addWear(wear); if(weared_out) mlist->deleteItem(item_i); SendInventory(player->peer_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) 3: digging completed */ 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 = CONTENT_IGNORE; u8 mineral = MINERAL_NONE; bool cannot_remove_node = false; try { MapNode n = m_env.getMap().getNode(p_under); // Get mineral mineral = n.getMineral(); // Get material at position material = n.d; // If not yet cancelled if(cannot_remove_node == false) { // If it's not diggable, do nothing if(content_diggable(material) == false) { derr_server<<"Server: Not finishing digging: " <<"Node not diggable" <<std::endl; cannot_remove_node = true; } } // If not yet cancelled if(cannot_remove_node == false) { // Get node metadata NodeMetadata *meta = m_env.getMap().getNodeMetadata(p_under); if(meta && meta->nodeRemovalDisabled() == true) { derr_server<<"Server: Not finishing digging: " <<"Node metadata disables removal" <<std::endl; cannot_remove_node = true; } } } catch(InvalidPositionException &e) { derr_server<<"Server: Not finishing digging: Node not found." <<" Adding block to emerge queue." <<std::endl; m_emerge_queue.addBlock(peer_id, getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK); cannot_remove_node = true; } // Make sure the player is allowed to do it if((getPlayerPrivs(player) & PRIV_BUILD) == 0) { dstream<<"Player "<<player->getName()<<" cannot remove node" <<" because privileges are "<<getPlayerPrivs(player) <<std::endl; cannot_remove_node = true; } /* If node can't be removed, set block to be re-sent to client and quit. */ if(cannot_remove_node) { derr_server<<"Server: Not finishing digging."<<std::endl; // Client probably has wrong data. // Set block not sent, so that client will get // a valid one. dstream<<"Client "<<peer_id<<" tried to dig " <<"node; but node cannot be removed." <<" setting MapBlock not sent."<<std::endl; RemoteClient *client = getClient(peer_id); v3s16 blockpos = getNodeBlockPos(p_under); client->SetBlockNotSent(blockpos); return; } /* Send the removal to all other clients. - If other player is close, send REMOVENODE - Otherwise set blocks not sent */ core::list<u16> far_players; sendRemoveNode(p_under, peer_id, &far_players, 100); /* 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 dug item to inventory */ InventoryItem *item = NULL; if(mineral != MINERAL_NONE) item = getDiggedMineralItem(mineral); // If not mineral if(item == NULL) { std::string &dug_s = content_features(material).dug_item; if(dug_s != "") { std::istringstream is(dug_s, std::ios::binary); item = InventoryItem::deSerialize(is); } } if(item != NULL) { // Add a item to inventory player->inventory.addItem("main", item); // Send inventory UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } } /* Remove the node (this takes some time so it is done after the quick stuff) */ { MapEditEventIgnorer ign(&m_ignore_map_edit_events); m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks); } /* Set blocks not sent to far players */ for(core::list<u16>::Iterator i = far_players.begin(); i != far_players.end(); i++) { u16 peer_id = *i; RemoteClient *client = getClient(peer_id); if(client==NULL) continue; client->SetBlocksNotSent(modified_blocks); } } /* 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); bool no_enough_privs = ((getPlayerPrivs(player) & PRIV_BUILD)==0); if(no_enough_privs) dstream<<"Player "<<player->getName()<<" cannot add node" <<" because privileges are "<<getPlayerPrivs(player) <<std::endl; if(content_buildable_to(n2.d) == false || no_enough_privs) { // Client probably has wrong data. // Set block not sent, so that client will get // a valid one. dstream<<"Client "<<peer_id<<" tried to place" <<" node in invalid position; setting" <<" MapBlock not sent."<<std::endl; RemoteClient *client = getClient(peer_id); v3s16 blockpos = getNodeBlockPos(p_over); client->SetBlockNotSent(blockpos); return; } } catch(InvalidPositionException &e) { derr_server<<"Server: Ignoring ADDNODE: Node not found" <<" Adding block to emerge queue." <<std::endl; m_emerge_queue.addBlock(peer_id, getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK); return; } // Reset build time counter getClient(peer->id)->m_time_from_building = 0.0; // Create node data MaterialItem *mitem = (MaterialItem*)item; MapNode n; n.d = mitem->getMaterial(); if(content_features(n.d).wall_mounted) n.dir = packDir(p_under - p_over); /* Send to all players */ core::list<u16> far_players; sendAddNode(p_over, n, 0, &far_players, 100); /* 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 UpdateCrafting(peer_id); SendInventory(peer_id); } /* Add node. This takes some time so it is done after the quick stuff */ core::map<v3s16, MapBlock*> modified_blocks; { MapEditEventIgnorer ign(&m_ignore_map_edit_events); m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks); } /* Set blocks not sent to far players */ for(core::list<u16>::Iterator i = far_players.begin(); i != far_players.end(); i++) { u16 peer_id = *i; RemoteClient *client = getClient(peer_id); if(client==NULL) continue; client->SetBlocksNotSent(modified_blocks); } /* Calculate special events */ /*if(n.d == CONTENT_MESE) { u32 count = 0; for(s16 z=-1; z<=1; z++) for(s16 y=-1; y<=1; y++) for(s16 x=-1; x<=1; x++) { } }*/ } /* Place other item (not a block) */ else { v3s16 blockpos = getNodeBlockPos(p_over); /* Check that the block is loaded so that the item can properly be added to the static list too */ MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(blockpos); if(block==NULL) { derr_server<<"Error while placing object: " "block not found"<<std::endl; return; } dout_server<<"Placing a miscellaneous item on map" <<std::endl; // Calculate a position for it v3f pos = intToFloat(p_over, BS); //pos.Y -= BS*0.45; pos.Y -= BS*0.25; // let it drop a bit // Randomize a bit pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; /* Create the object */ ServerActiveObject *obj = item->createSAO(&m_env, 0, pos); if(obj == NULL) { derr_server<<"WARNING: item resulted in NULL object, " <<"not placing onto map" <<std::endl; } else { // Add the object to the environment m_env.addActiveObject(obj); dout_server<<"Placed object"<<std::endl; if(g_settings.getBool("creative_mode") == false) { // Delete the right amount of items from the slot u16 dropcount = item->getDropCount(); // Delete item if all gone if(item->getCount() <= dropcount) { if(item->getCount() < dropcount) dstream<<"WARNING: Server: dropped more items" <<" than the slot contains"<<std::endl; InventoryList *ilist = player->inventory.getList("main"); if(ilist) // Remove from inventory and send inventory ilist->deleteItem(item_i); } // Else decrement it else item->remove(dropcount); // Send inventory UpdateCrafting(peer_id); 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) { if((getPlayerPrivs(player) & PRIV_BUILD) == 0) return; /* 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_SIGNNODETEXT) { if((getPlayerPrivs(player) & PRIV_BUILD) == 0) return; /* u16 command v3s16 p 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 p = readV3S16(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]; } NodeMetadata *meta = m_env.getMap().getNodeMetadata(p); if(!meta) return; if(meta->typeId() != CONTENT_SIGN_WALL) return; SignNodeMetadata *signmeta = (SignNodeMetadata*)meta; signmeta->setText(text); v3s16 blockpos = getNodeBlockPos(p); MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(blockpos); if(block) { block->setChangedFlag(); } for(core::map<u16, RemoteClient*>::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); client->SetBlockNotSent(blockpos); } } 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) { // Create context InventoryContext c; c.current_player = player; /* 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; if(ma->to_inv == "current_player" && ma->from_inv == "current_player") { InventoryList *rlist = player->inventory.getList("craftresult"); assert(rlist); InventoryList *clist = player->inventory.getList("craft"); assert(clist); InventoryList *mlist = player->inventory.getList("main"); assert(mlist); /* Craftresult is no longer preview if something is moved into it */ if(ma->to_list == "craftresult" && ma->from_list != "craftresult") { // If it currently is a preview, remove // its contents if(player->craftresult_is_preview) { rlist->deleteItem(0); } player->craftresult_is_preview = false; } /* Crafting takes place if this condition is true. */ if(player->craftresult_is_preview && ma->from_list == "craftresult") { player->craftresult_is_preview = false; clist->decrementMaterials(1); } /* If the craftresult is placed on itself, move it to main inventory instead of doing the action */ if(ma->to_list == "craftresult" && ma->from_list == "craftresult") { disable_action = true; InventoryItem *item1 = rlist->changeItem(0, NULL); mlist->addItem(item1); } } } if(disable_action == false) { // Feed action to player inventory a->apply(&c, this); // Eat the action delete a; } else { // Send inventory UpdateCrafting(player->peer_id); 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()); // Line to send to players std::wstring line; // Whether to send to the player that sent the line bool send_to_sender = false; // Whether to send to other players bool send_to_others = false; // Local player gets all privileges regardless of // what's set on their account. u64 privs = getPlayerPrivs(player); // Parse commands std::wstring commandprefix = L"/#"; if(message.substr(0, commandprefix.size()) == commandprefix) { line += L"Server: "; message = message.substr(commandprefix.size()); ServerCommandContext *ctx = new ServerCommandContext( str_split(message, L' '), this, &m_env, player, privs); line += processServerCommand(ctx); send_to_sender = ctx->flags & 1; send_to_others = ctx->flags & 2; delete ctx; } else { if(privs & PRIV_SHOUT) { line += L"<"; line += name; line += L"> "; line += message; send_to_others = true; } else { line += L"Server: You are not allowed to shout"; send_to_sender = true; } } if(line != L"") { dstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl; /* Send the message to 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; // Filter recipient bool sender_selected = (peer_id == client->peer_id); if(sender_selected == true && send_to_sender == false) continue; if(sender_selected == false && send_to_others == false) continue; SendChatMessage(client->peer_id, line); } } } else if(command == TOSERVER_DAMAGE) { if(g_settings.getBool("enable_damage")) { std::string datastring((char*)&data[2], datasize-2); std::istringstream is(datastring, std::ios_base::binary); u8 damage = readU8(is); if(player->hp > damage) { player->hp -= damage; } else { player->hp = 0; dstream<<"TODO: Server: TOSERVER_HP_DECREMENT: Player dies" <<std::endl; v3f pos = findSpawnPos(m_env.getServerMap()); player->setPosition(pos); player->hp = 20; SendMovePlayer(player); SendPlayerHP(player); //TODO: Throw items around } } SendPlayerHP(player); } else if(command == TOSERVER_PASSWORD) { /* [0] u16 TOSERVER_PASSWORD [2] u8[28] old password [30] u8[28] new password */ if(datasize != 2+PASSWORD_SIZE*2) return; /*char password[PASSWORD_SIZE]; for(u32 i=0; i<PASSWORD_SIZE-1; i++) password[i] = data[2+i]; password[PASSWORD_SIZE-1] = 0;*/ std::string oldpwd; for(u32 i=0; i<PASSWORD_SIZE-1; i++) { char c = data[2+i]; if(c == 0) break; oldpwd += c; } std::string newpwd; for(u32 i=0; i<PASSWORD_SIZE-1; i++) { char c = data[2+PASSWORD_SIZE+i]; if(c == 0) break; newpwd += c; } std::string playername = player->getName(); if(m_authmanager.exists(playername) == false) { dstream<<"Server: playername not found in authmanager"<<std::endl; // Wrong old password supplied!! SendChatMessage(peer_id, L"playername not found in authmanager"); return; } std::string checkpwd = m_authmanager.getPassword(playername); if(oldpwd != checkpwd) { dstream<<"Server: invalid old password"<<std::endl; // Wrong old password supplied!! SendChatMessage(peer_id, L"Invalid old password supplied. Password NOT changed."); return; } m_authmanager.setPassword(playername, newpwd); dstream<<"Server: password change successful for "<<playername <<std::endl; SendChatMessage(peer_id, L"Password change successful"); } 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::onMapEditEvent(MapEditEvent *event) { dstream<<"Server::onMapEditEvent()"<<std::endl; if(m_ignore_map_edit_events) return; MapEditEvent *e = event->clone(); m_unsent_map_edit_queue.push_back(e); } Inventory* Server::getInventory(InventoryContext *c, std::string id) { if(id == "current_player") { assert(c->current_player); return &(c->current_player->inventory); } Strfnd fn(id); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); NodeMetadata *meta = m_env.getMap().getNodeMetadata(p); if(meta) return meta->getInventory(); dstream<<"nodemeta at ("<<p.X<<","<<p.Y<<","<<p.Z<<"): " <<"no metadata found"<<std::endl; return NULL; } dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl; return NULL; } void Server::inventoryModified(InventoryContext *c, std::string id) { if(id == "current_player") { assert(c->current_player); // Send inventory UpdateCrafting(c->current_player->peer_id); SendInventory(c->current_player->peer_id); return; } Strfnd fn(id); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); v3s16 blockpos = getNodeBlockPos(p); NodeMetadata *meta = m_env.getMap().getNodeMetadata(p); if(meta) meta->inventoryModified(); for(core::map<u16, RemoteClient*>::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); client->SetBlockNotSent(blockpos); } return; } dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl; } 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); } /* Static send methods */ void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_HP); writeU8(os, hp); // Make data buffer std::string s = os.str(); SharedBuffer<u8> data((u8*)s.c_str(), s.size()); // Send as reliable con.Send(peer_id, 0, data, true); } void Server::SendAccessDenied(con::Connection &con, u16 peer_id, const std::wstring &reason) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_ACCESS_DENIED); os<<serializeWideString(reason); // Make data buffer std::string s = os.str(); SharedBuffer<u8> data((u8*)s.c_str(), s.size()); // Send as reliable con.Send(peer_id, 0, data, true); } /* Non-static send methods */ 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); memset((char*)&data[start+2], 0, PLAYERNAME_SIZE); 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); } void Server::SendInventory(u16 peer_id) { DSTACK(__FUNCTION_NAME); Player* player = m_env.getPlayer(peer_id); assert(player); /* 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::SendPlayerHP(Player *player) { SendHP(m_con, player->peer_id, player->hp); } void Server::SendMovePlayer(Player *player) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_MOVE_PLAYER); writeV3F1000(os, player->getPosition()); writeF1000(os, player->getPitch()); writeF1000(os, player->getYaw()); { v3f pos = player->getPosition(); f32 pitch = player->getPitch(); f32 yaw = player->getYaw(); dstream<<"Server sending TOCLIENT_MOVE_PLAYER" <<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")" <<" pitch="<<pitch <<" yaw="<<yaw <<std::endl; } // Make data buffer std::string s = os.str(); SharedBuffer<u8> data((u8*)s.c_str(), s.size()); // Send as reliable m_con.Send(player->peer_id, 0, data, true); } void Server::sendRemoveNode(v3s16 p, u16 ignore_id, core::list<u16> *far_players, float far_d_nodes) { float maxd = far_d_nodes*BS; v3f p_f = intToFloat(p, BS); // Create packet u32 replysize = 8; SharedBuffer<u8> reply(replysize); writeU16(&reply[0], TOCLIENT_REMOVENODE); writeS16(&reply[2], p.X); writeS16(&reply[4], p.Y); writeS16(&reply[6], p.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(client->peer_id == ignore_id) continue; if(far_players) { // Get player Player *player = m_env.getPlayer(client->peer_id); if(player) { // If player is far away, only set modified blocks not sent v3f player_pos = player->getPosition(); if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(client->peer_id); continue; } } } // Send as reliable m_con.Send(client->peer_id, 0, reply, true); } } void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id, core::list<u16> *far_players, float far_d_nodes) { float maxd = far_d_nodes*BS; v3f p_f = intToFloat(p, BS); 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(client->peer_id == ignore_id) continue; if(far_players) { // Get player Player *player = m_env.getPlayer(client->peer_id); if(player) { // If player is far away, only set modified blocks not sent v3f player_pos = player->getPosition(); if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(client->peer_id); continue; } } } // Create packet u32 replysize = 8 + MapNode::serializedLength(client->serialization_version); SharedBuffer<u8> reply(replysize); writeU16(&reply[0], TOCLIENT_ADDNODE); writeS16(&reply[2], p.X); writeS16(&reply[4], p.Y); writeS16(&reply[6], p.Z); n.serialize(&reply[8], client->serialization_version); // Send as reliable m_con.Send(client->peer_id, 0, reply, true); } } void Server::setBlockNotSent(v3s16 p) { for(core::map<u16, RemoteClient*>::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); client->SetBlockNotSent(p); } } void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver) { DSTACK(__FUNCTION_NAME); v3s16 p = block->getPos(); #if 0 // Analyze it a bit bool completely_air = true; for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++) for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++) for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++) { if(block->getNodeNoEx(v3s16(x0,y0,z0)).d != CONTENT_AIR) { completely_air = false; x0 = y0 = z0 = MAP_BLOCKSIZE; // Break out } } // Print result dstream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<"): "; if(completely_air) dstream<<"[completely air] "; dstream<<std::endl; #endif /* 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); 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<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")" <<": \tpacket size: "<<replysize<<std::endl;*/ /* Send packet */ m_con.Send(peer_id, 1, reply, true); } void Server::SendBlocks(float dtime) { DSTACK(__FUNCTION_NAME); JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); //TimeTaker timer("Server::SendBlocks"); core::array<PrioritySortedBlockTransfer> queue; s32 total_sending = 0; { ScopeProfiler sp(&g_profiler, "Server: selecting blocks for sending"); 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(); 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++; } } /* Something random */ void Server::UpdateCrafting(u16 peer_id) { DSTACK(__FUNCTION_NAME); Player* player = m_env.getPlayer(peer_id); assert(player); /* Calculate crafting stuff */ if(g_settings.getBool("creative_mode") == false) { InventoryList *clist = player->inventory.getList("craft"); InventoryList *rlist = player->inventory.getList("craftresult"); if(rlist->getUsedSlots() == 0) player->craftresult_is_preview = true; if(rlist && player->craftresult_is_preview) { rlist->clearItems(); } if(clist && rlist && player->craftresult_is_preview) { InventoryItem *items[9]; for(u16 i=0; i<9; i++) { items[i] = clist->getItem(i); } // Get result of crafting grid InventoryItem *result = craft_get_result(items); if(result) rlist->addItem(result); } } // if creative_mode == false } 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(); } std::wstring Server::getStatusString() { std::wostringstream os(std::ios_base::binary); os<<L"# Server: "; // Version os<<L"version="<<narrow_to_wide(VERSION_STRING); // 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"}"; if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == false) os<<" WARNING: Map saving is disabled."<<std::endl; return os.str(); } v3f findSpawnPos(ServerMap &map) { //return v3f(50,50,50)*BS; v2s16 nodepos; s16 groundheight = 0; #if 0 nodepos = v2s16(0,0); groundheight = 20; #endif #if 1 // Try to find a good place a few times for(s32 i=0; i<1000; 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 (NOTE: Don't get because it's slow) //m_env.getMap().emergeSector(sectorpos); // Get ground height at point (fallbacks to heightmap function) groundheight = map.findGroundLevel(nodepos); // Don't go underwater if(groundheight < WATER_LEVEL) { //dstream<<"-> Underwater"<<std::endl; continue; } // Don't go to high places if(groundheight > WATER_LEVEL + 4) { //dstream<<"-> Underwater"<<std::endl; continue; } // 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; return intToFloat(v3s16( nodepos.X, groundheight + 3, nodepos.Y ), BS); } 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; // Reset inventory to creative if in creative mode if(g_settings.getBool("creative_mode")) { craft_set_creative_inventory(player); } 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); m_authmanager.add(name); m_authmanager.setPassword(name, password); m_authmanager.setPrivs(name, stringToPrivs(g_settings.get("default_privs"))); /* Set player position */ dstream<<"Server: Finding spawn place for player \"" <<player->getName()<<"\""<<std::endl; v3f pos = findSpawnPos(m_env.getServerMap()); player->setPosition(pos); /* Add player to environment */ m_env.addPlayer(player); /* Add stuff to inventory */ if(g_settings.getBool("creative_mode")) { craft_set_creative_inventory(player); } else if(g_settings.getBool("give_initial_stuff")) { craft_give_initial_stuff(player); } return player; } // create new player } void Server::handlePeerChange(PeerChange &c) { JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); if(c.type == PEER_ADDED) { /* Add */ // Error check core::map<u16, RemoteClient*>::Node *n; n = m_clients.find(c.peer_id); // The client shouldn't already exist assert(n == NULL);