aboutsummaryrefslogtreecommitdiff
path: root/src/shader.cpp
blob: 66f32c9a1846bc6e6439f63829841bbc72306698 (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
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Kahrl <kahrl@gmx.net>

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

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

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

#include <fstream>
#include <iterator>
#include "shader.h"
#include "irrlichttypes_extrabloated.h"
#include "debug.h"
#include "filesys.h"
#include "util/container.h"
#include "util/thread.h"
#include "settings.h"
#include <ICameraSceneNode.h>
#include <IGPUProgrammingServices.h>
#include <IMaterialRenderer.h>
#include <IMaterialRendererServices.h>
#include <IShaderConstantSetCallBack.h>
#include "EShaderTypes.h"
#include "log.h"
#include "gamedef.h"
#include "client/tile.h"

/*
	A cache from shader name to shader path
*/
MutexedMap<std::string, std::string> g_shadername_to_path_cache;

/*
	Gets the path to a shader by first checking if the file
	  name_of_shader/filename
	exists in shader_path and if not, using the data path.

	If not found, returns "".

	Utilizes a thread-safe cache.
*/
std::string getShaderPath(const std::string &name_of_shader,
		const std::string &filename)
{
	std::string combined = name_of_shader + DIR_DELIM + filename;
	std::string fullpath = "";
	/*
		Check from cache
	*/
	bool incache = g_shadername_to_path_cache.get(combined, &fullpath);
	if(incache)
		return fullpath;

	/*
		Check from shader_path
	*/
	std::string shader_path = g_settings->get("shader_path");
	if(shader_path != "")
	{
		std::string testpath = shader_path + DIR_DELIM + combined;
		if(fs::PathExists(testpath))
			fullpath = testpath;
	}

	/*
		Check from default data directory
	*/
	if(fullpath == "")
	{
		std::string rel_path = std::string("client") + DIR_DELIM
				+ "shaders" + DIR_DELIM
				+ name_of_shader + DIR_DELIM
				+ filename;
		std::string testpath = porting::path_share + DIR_DELIM + rel_path;
		if(fs::PathExists(testpath))
			fullpath = testpath;
	}

	// Add to cache (also an empty result is cached)
	g_shadername_to_path_cache.set(combined, fullpath);

	// Finally return it
	return fullpath;
}

/*
	SourceShaderCache: A cache used for storing source shaders.
*/

class SourceShaderCache
{
public:
	void insert(const std::string &name_of_shader, const std::string &filename,
		const std::string &program, bool prefer_local)
	{
		std::string combined = name_of_shader + DIR_DELIM + filename;
		// Try to use local shader instead if asked to
		if(prefer_local){
			std::string path = getShaderPath(name_of_shader, filename);
			if(path != ""){
				std::string p = readFile(path);
				if(p != ""){
					m_programs[combined] = p;
					return;
				}
			}
		}
		m_programs[combined] = program;
	}

	std::string get(const std::string &name_of_shader,
		const std::string &filename)
	{
		std::string combined = name_of_shader + DIR_DELIM + filename;
		StringMap::iterator n = m_programs.find(combined);
		if (n != m_programs.end())
			return n->second;
		return "";
	}

	// Primarily fetches from cache, secondarily tries to read from filesystem
	std::string getOrLoad(const std::string &name_of_shader,
		const std::string &filename)
	{
		std::string combined = name_of_shader + DIR_DELIM + filename;
		StringMap::iterator n = m_programs.find(combined);
		if (n != m_programs.end())
			return n->second;
		std::string path = getShaderPath(name_of_shader, filename);
		if (path == "") {
			infostream << "SourceShaderCache::getOrLoad(): No path found for \""
				<< combined << "\"" << std::endl;
			return "";
		}
		infostream << "SourceShaderCache::getOrLoad(): Loading path \""
			<< path << "\"" << std::endl;
		std::string p = readFile(path);
		if (p != "") {
			m_programs[combined] = p;
			return p;
		}
		return "";
	}
private:
	StringMap m_programs;

	std::string readFile(const std::string &path)
	{
		std::ifstream is(path.c_str(), std::ios::binary);
		if(!is.is_open())
			return "";
		std::ostringstream tmp_os;
		tmp_os << is.rdbuf();
		return tmp_os.str();
	}
};


/*
	ShaderCallback: Sets constants that can be used in shaders
*/

class ShaderCallback : public video::IShaderConstantSetCallBack
{
	std::vector<IShaderConstantSetter*> m_setters;

public:
	ShaderCallback(const std::vector<IShaderConstantSetterFactory*> &factories)
	{
		for (u32 i = 0; i < factories.size(); ++i)
			m_setters.push_back(factories[i]->create());
	}

	~ShaderCallback()
	{
		for (u32 i = 0; i < m_setters.size(); ++i)
			delete m_setters[i];
	}

	virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData)
	{
		video::IVideoDriver *driver = services->getVideoDriver();
		sanity_check(driver != NULL);

		bool is_highlevel = userData;

		for (u32 i = 0; i < m_setters.size(); ++i)
			m_setters[i]->onSetConstants(services, is_highlevel);
	}
};


/*
	MainShaderConstantSetter: Set basic constants required for almost everything
*/

class MainShaderConstantSetter : public IShaderConstantSetter
{
	CachedVertexShaderSetting<float, 16> m_world_view_proj;
	CachedVertexShaderSetting<float, 16> m_world;

public:
	MainShaderConstantSetter() :
		m_world_view_proj("mWorldViewProj"),
		m_world("mWorld")
	{}
	~MainShaderConstantSetter() {}

	virtual void onSetConstants(video::IMaterialRendererServices *services,
			bool is_highlevel)
	{
		video::IVideoDriver *driver = services->getVideoDriver();
		sanity_check(driver);

		// Set clip matrix
		core::matrix4 worldViewProj;
		worldViewProj = driver->getTransform(video::ETS_PROJECTION);
		worldViewProj *= driver->getTransform(video::ETS_VIEW);
		worldViewProj *= driver->getTransform(video::ETS_WORLD);
		if (is_highlevel)
			m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
		else
			services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4);

		// Set world matrix
		core::matrix4 world = driver->getTransform(video::ETS_WORLD);
		if (is_highlevel)
			m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
		else
			services->setVertexShaderConstant(world.pointer(), 4, 4);

	}
};


class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
{
public:
	virtual IShaderConstantSetter* create()
		{ return new MainShaderConstantSetter(); }
};


/*
	ShaderSource
*/

class ShaderSource : public IWritableShaderSource
{
public:
	ShaderSource(IrrlichtDevice *device);
	~ShaderSource();

	/*
		- If shader material specified by name is found from cache,
		  return the cached id.
		- Otherwise generate the shader material, add to cache and return id.

		The id 0 points to a null shader. Its material is EMT_SOLID.
	*/
	u32 getShaderIdDirect(const std::string &name,
		const u8 material_type, const u8 drawtype);

	/*
		If shader specified by the name pointed by the id doesn't
		exist, create it, then return id.

		Can be called from any thread. If called from some other thread
		and not found in cache, the call is queued to the main thread
		for processing.
	*/

	u32 getShader(const std::string &name,
		const u8 material_type, const u8 drawtype);

	ShaderInfo getShaderInfo(u32 id);

	// Processes queued shader requests from other threads.
	// Shall be called from the main thread.
	void processQueue();

	// Insert a shader program into the cache without touching the
	// filesystem. Shall be called from the main thread.
	void insertSourceShader(const std::string &name_of_shader,
		const std::string &filename, const std::string &program);

	// Rebuild shaders from the current set of source shaders
	// Shall be called from the main thread.
	void rebuildShaders();

	void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter)
	{
		m_setter_factories.push_back(setter);
	}

private:

	// The id of the thread that is allowed to use irrlicht directly
	threadid_t m_main_thread;
	// The irrlicht device
	IrrlichtDevice *m_device;

	// Cache of source shaders
	// This should be only accessed from the main thread
	SourceShaderCache m_sourcecache;

	// A shader id is index in this array.
	// The first position contains a dummy shader.
	std::vector<ShaderInfo> m_shaderinfo_cache;
	// The former container is behind this mutex
	Mutex m_shaderinfo_cache_mutex;

	// Queued shader fetches (to be processed by the main thread)
	RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;

	// Global constant setter factories
	std::vector<IShaderConstantSetterFactory *> m_setter_factories;

	// Shader callbacks
	std::vector<ShaderCallback *> m_callbacks;
};

IWritableShaderSource* createShaderSource(IrrlichtDevice *device)
{
	return new ShaderSource(device);
}

/*
	Generate shader given the shader name.
*/
ShaderInfo generate_shader(const std::string &name,
		u8 material_type, u8 drawtype,
		IrrlichtDevice *device, std::vector<ShaderCallback *> &callbacks,
		const std::vector<IShaderConstantSetterFactory*> &setter_factories,
		SourceShaderCache *sourcecache);

/*
	Load shader programs
*/
void load_shaders(std::string name, SourceShaderCache *sourcecache,
		video::E_DRIVER_TYPE drivertype, bool enable_shaders,
		std::string &vertex_program, std::string &pixel_program,
		std::string &geometry_program, bool &is_highlevel);

ShaderSource::ShaderSource(IrrlichtDevice *device):
		m_device(device)
{
	assert(m_device); // Pre-condition

	m_main_thread = thr_get_current_thread_id();

	// Add a dummy ShaderInfo as the first index, named ""
	m_shaderinfo_cache.push_back(ShaderInfo());

	// Add main global constant setter
	addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
}

ShaderSource::~ShaderSource()
{
	for (std::vector<ShaderCallback *>::iterator iter = m_callbacks.begin();
			iter != m_callbacks.end(); ++iter) {
		delete *iter;
	}
	for (std::vector<IShaderConstantSetterFactory *>::iterator iter = m_setter_factories.begin();
			iter != m_setter_factories.end(); ++iter) {
		delete *iter;
	}
}

u32 ShaderSource::getShader(const std::string &name,
		const u8 material_type, const u8 drawtype)
{
	/*
		Get shader
	*/

	if (thr_is_current_thread(m_main_thread)) {
		return getShaderIdDirect(name, material_type, drawtype);
	} else {
		/*errorstream<<"getShader(): Queued: name=\""<<name<<"\""<<std::endl;*/

		// We're gonna ask the result to be put into here

		static ResultQueue<std::string, u32, u8, u8> result_queue;

		// Throw a request in
		m_get_shader_queue.add(name, 0, 0, &result_queue);

		/* infostream<<"Waiting for shader from main thread, name=\""
				<<name<<"\""<<std::endl;*/

		while(true) {
			GetResult<std::string, u32, u8, u8>
				result = result_queue.pop_frontNoEx();

			if (result.key == name) {
				return result.item;
			}
			else {
				errorstream << "Got shader with invalid name: " << result.key << std::endl;
			}
		}

	}

	infostream<<"getShader(): Failed"<<std::endl;

	return 0;
}

/*
	This method generates all the shaders
*/
u32 ShaderSource::getShaderIdDirect(const std::string &name,
		const u8 material_type, const u8 drawtype)
{
	//infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;

	// Empty name means shader 0
	if(name == ""){
		infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
		return 0;
	}

	// Check if already have such instance
	for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
		ShaderInfo *info = &m_shaderinfo_cache[i];
		if(info->name == name && info->material_type == material_type &&
			info->drawtype == drawtype)
			return i;
	}

	/*
		Calling only allowed from main thread
	*/
	if (!thr_is_current_thread(m_main_thread)) {
		errorstream<<"ShaderSource::getShaderIdDirect() "
				"called not from main thread"<<std::endl;
		return 0;
	}

	ShaderInfo info = generate_shader(name, material_type, drawtype,
			m_device, m_callbacks, m_setter_factories, &m_sourcecache);

	/*
		Add shader to caches (add dummy shaders too)
	*/

	MutexAutoLock lock(m_shaderinfo_cache_mutex);

	u32 id = m_shaderinfo_cache.size();
	m_shaderinfo_cache.push_back(info);

	infostream<<"getShaderIdDirect(): "
			<<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;

	return id;
}


ShaderInfo ShaderSource::getShaderInfo(u32 id)
{
	MutexAutoLock lock(m_shaderinfo_cache_mutex);

	if(id >= m_shaderinfo_cache.size())
		return ShaderInfo();

	return m_shaderinfo_cache[id];
}

void ShaderSource::processQueue()
{


}

void ShaderSource::insertSourceShader(const std::string &name_of_shader,
		const std::string &filename, const std::string &program)
{
	/*infostream<<"ShaderSource::insertSourceShader(): "
			"name_of_shader=\""<<name_of_shader<<"\", "
			"filename=\""<<filename<<"\""<<std::endl;*/

	sanity_check(thr_is_current_thread(m_main_thread));

	m_sourcecache.insert(name_of_shader, filename, program, true);
}

void ShaderSource::rebuildShaders()
{
	MutexAutoLock lock(m_shaderinfo_cache_mutex);

	/*// Oh well... just clear everything, they'll load sometime.
	m_shaderinfo_cache.clear();
	m_name_to_id.clear();*/

	/*
		FIXME: Old shader materials can't be deleted in Irrlicht,
		or can they?
		(This would be nice to do in the destructor too)
	*/

	// Recreate shaders
	for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
		ShaderInfo *info = &m_shaderinfo_cache[i];
		if(info->name != ""){
			*info = generate_shader(info->name, info->material_type,
					info->drawtype, m_device, m_callbacks,
					m_setter_factories, &m_sourcecache);
		}
	}
}


ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype,
		IrrlichtDevice *device, std::vector<ShaderCallback *> &callbacks,
		const std::vector<IShaderConstantSetterFactory*> &setter_factories,
		SourceShaderCache *sourcecache)
{
	ShaderInfo shaderinfo;
	shaderinfo.name = name;
	shaderinfo.material_type = material_type;
	shaderinfo.drawtype = drawtype;
	shaderinfo.material = video::EMT_SOLID;
	switch(material_type){
		case TILE_MATERIAL_BASIC:
			shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			break;
		case TILE_MATERIAL_ALPHA:
			shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
			break;
		case TILE_MATERIAL_LIQUID_TRANSPARENT:
			shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
			break;
		case TILE_MATERIAL_LIQUID_OPAQUE:
			shaderinfo.base_material = video::EMT_SOLID;
			break;
		case TILE_MATERIAL_WAVING_LEAVES:
			shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			break;
		case TILE_MATERIAL_WAVING_PLANTS:
			shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
		break;
	}

	bool enable_shaders = g_settings->getBool("enable_shaders");
	if (!enable_shaders)
		return shaderinfo;

	video::IVideoDriver* driver = device->getVideoDriver();
	sanity_check(driver);

	video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
	if(!gpu){
		errorstream<<"generate_shader(): "
				"failed to generate \""<<name<<"\", "
				"GPU programming not supported."
				<<std::endl;
		return shaderinfo;
	}

	// Choose shader language depending on driver type and settings
	// Then load shaders
	std::string vertex_program;
	std::string pixel_program;
	std::string geometry_program;
	bool is_highlevel;
	load_shaders(name, sourcecache, driver->getDriverType(),
			enable_shaders, vertex_program, pixel_program,
			geometry_program, is_highlevel);
	// Check hardware/driver support
	if(vertex_program != "" &&
			!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
			!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){
		infostream<<"generate_shader(): vertex shaders disabled "
				"because of missing driver/hardware support."
				<<std::endl;
		vertex_program = "";
	}
	if(pixel_program != "" &&
			!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
			!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){
		infostream<<"generate_shader(): pixel shaders disabled "
				"because of missing driver/hardware support."
				<<std::endl;
		pixel_program = "";
	}
	if(geometry_program != "" &&
			!driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
		infostream<<"generate_shader(): geometry shaders disabled "
				"because of missing driver/hardware support."
				<<std::endl;
		geometry_program = "";
	}

	// If no shaders are used, don't make a separate material type
	if(vertex_program == "" && pixel_program == "" && geometry_program == "")
		return shaderinfo;

	// Create shaders header
	std::string shaders_header = "#version 120\n";

	static const char* drawTypes[] = {
		"NDT_NORMAL",
		"NDT_AIRLIKE",
		"NDT_LIQUID",
		"NDT_FLOWINGLIQUID",
		"NDT_GLASSLIKE",
		"NDT_ALLFACES",
		"NDT_ALLFACES_OPTIONAL",
		"NDT_TORCHLIKE",
		"NDT_SIGNLIKE",
		"NDT_PLANTLIKE",
		"NDT_FENCELIKE",
		"NDT_RAILLIKE",
		"NDT_NODEBOX",
		"NDT_GLASSLIKE_FRAMED",
		"NDT_FIRELIKE",
		"NDT_GLASSLIKE_FRAMED_OPTIONAL"
	};

	for (int i = 0; i < 14; i++){
		shaders_header += "#define ";
		shaders_header += drawTypes[i];
		shaders_header += " ";
		shaders_header += itos(i);
		shaders_header += "\n";
	}

	static const char* materialTypes[] = {
		"TILE_MATERIAL_BASIC",
		"TILE_MATERIAL_ALPHA",
		"TILE_MATERIAL_LIQUID_TRANSPARENT",
		"TILE_MATERIAL_LIQUID_OPAQUE",
		"TILE_MATERIAL_WAVING_LEAVES",
		"TILE_MATERIAL_WAVING_PLANTS"
	};

	for (int i = 0; i < 6; i++){
		shaders_header += "#define ";
		shaders_header += materialTypes[i];
		shaders_header += " ";
		shaders_header += itos(i);
		shaders_header += "\n";
	}

	shaders_header += "#define MATERIAL_TYPE ";
	shaders_header += itos(material_type);
	shaders_header += "\n";
	shaders_header += "#define DRAW_TYPE ";
	shaders_header += itos(drawtype);
	shaders_header += "\n";

	if (g_settings->getBool("generate_normalmaps")) {
		shaders_header += "#define GENERATE_NORMALMAPS 1\n";
	} else {
		shaders_header += "#define GENERATE_NORMALMAPS 0\n";
	}
	shaders_header += "#define NORMALMAPS_STRENGTH ";
	shaders_header += ftos(g_settings->getFloat("normalmaps_strength"));
	shaders_header += "\n";
	float sample_step;
	int smooth = (int)g_settings->getFloat("normalmaps_smooth");
	switch (smooth){
	case 0:
		sample_step = 0.0078125; // 1.0 / 128.0
		break;
	case 1:
		sample_step = 0.00390625; // 1.0 / 256.0
		break;
	case 2:
		sample_step = 0.001953125; // 1.0 / 512.0
		break;
	default:
		sample_step = 0.0078125;
		break;
	}
	shaders_header += "#define SAMPLE_STEP ";
	shaders_header += ftos(sample_step);
	shaders_header += "\n";

	if (g_settings->getBool("enable_bumpmapping"))
		shaders_header += "#define ENABLE_BUMPMAPPING\n";

	if (g_settings->getBool("enable_parallax_occlusion")){
		int mode = g_settings->getFloat("parallax_occlusion_mode");
		float scale = g_settings->getFloat("parallax_occlusion_scale");
		float bias = g_settings->getFloat("parallax_occlusion_bias");
		int iterations = g_settings->getFloat("parallax_occlusion_iterations");
		shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n";
		shaders_header += "#define PARALLAX_OCCLUSION_MODE ";
		shaders_header += itos(mode);
		shaders_header += "\n";
		shaders_header += "#define PARALLAX_OCCLUSION_SCALE ";
		shaders_header += ftos(scale);
		shaders_header += "\n";
		shaders_header += "#define PARALLAX_OCCLUSION_BIAS ";
		shaders_header += ftos(bias);
		shaders_header += "\n";
		shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS ";
		shaders_header += itos(iterations);
		shaders_header += "\n";
	}

	shaders_header += "#define USE_NORMALMAPS ";
	if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion"))
		shaders_header += "1\n";
	else
		shaders_header += "0\n";

	if (g_settings->getBool("enable_waving_water")){
		shaders_header += "#define ENABLE_WAVING_WATER 1\n";
		shaders_header += "#define WATER_WAVE_HEIGHT ";
		shaders_header += ftos(g_settings->getFloat("water_wave_height"));
		shaders_header += "\n";
		shaders_header += "#define WATER_WAVE_LENGTH ";
		shaders_header += ftos(g_settings->getFloat("water_wave_length"));
		shaders_header += "\n";
		shaders_header += "#define WATER_WAVE_SPEED ";
		shaders_header += ftos(g_settings->getFloat("water_wave_speed"));
		shaders_header += "\n";
	} else{
		shaders_header += "#define ENABLE_WAVING_WATER 0\n";
	}

	shaders_header += "#define ENABLE_WAVING_LEAVES ";
	if (g_settings->getBool("enable_waving_leaves"))
		shaders_header += "1\n";
	else
		shaders_header += "0\n";

	shaders_header += "#define ENABLE_WAVING_PLANTS ";
	if (g_settings->getBool("enable_waving_plants"))
		shaders_header += "1\n";
	else
		shaders_header += "0\n";

	if (g_settings->getBool("tone_mapping"))
		shaders_header += "#define ENABLE_TONE_MAPPING\n";

	shaders_header += "#define FOG_START ";
	shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f));
	shaders_header += "\n";

	// Call addHighLevelShaderMaterial() or addShaderMaterial()
	const c8* vertex_program_ptr = 0;
	const c8* pixel_program_ptr = 0;
	const c8* geometry_program_ptr = 0;
	if (!vertex_program.empty()) {
		vertex_program = shaders_header + vertex_program;
		vertex_program_ptr = vertex_program.c_str();
	}
	if (!pixel_program.empty()) {
		pixel_program = shaders_header + pixel_program;
		pixel_program_ptr = pixel_program.c_str();
	}
	if (!geometry_program.empty()) {
		geometry_program = shaders_header + geometry_program;
		geometry_program_ptr = geometry_program.c_str();
	}
	ShaderCallback *cb = new ShaderCallback(setter_factories);
	s32 shadermat = -1;
	if(is_highlevel){
		infostream<<"Compiling high level shaders for "<<name<<std::endl;
		shadermat = gpu->addHighLevelShaderMaterial(
			vertex_program_ptr,   // Vertex shader program
			"vertexMain",         // Vertex shader entry point
			video::EVST_VS_1_1,   // Vertex shader version
			pixel_program_ptr,    // Pixel shader program
			"pixelMain",          // Pixel shader entry point
			video::EPST_PS_1_2,   // Pixel shader version
			geometry_program_ptr, // Geometry shader program
			"geometryMain",       // Geometry shader entry point
			video::EGST_GS_4_0,   // Geometry shader version
			scene::EPT_TRIANGLES,      // Geometry shader input
			scene::EPT_TRIANGLE_STRIP, // Geometry shader output
			0,                         // Support maximum number of vertices
			cb, // Set-constant callback
			shaderinfo.base_material,  // Base material
			1                          // Userdata passed to callback
			);
		if(shadermat == -1){
			errorstream<<"generate_shader(): "
					"failed to generate \""<<name<<"\", "
					"addHighLevelShaderMaterial failed."
					<<std::endl;
			dumpShaderProgram(warningstream, "Vertex", vertex_program);
			dumpShaderProgram(warningstream, "Pixel", pixel_program);
			dumpShaderProgram(warningstream, "Geometry", geometry_program);
			delete cb;
			return shaderinfo;
		}
	}
	else{
		infostream<<"Compiling assembly shaders for "<<name<<std::endl;
		shadermat = gpu->addShaderMaterial(
			vertex_program_ptr,   // Vertex shader program
			pixel_program_ptr,    // Pixel shader program
			cb, // Set-constant callback
			shaderinfo.base_material,  // Base material
			0                     // Userdata passed to callback
			);

		if(shadermat == -1){
			errorstream<<"generate_shader(): "
					"failed to generate \""<<name<<"\", "
					"addShaderMaterial failed."
					<<std::endl;
			dumpShaderProgram(warningstream, "Vertex", vertex_program);
			dumpShaderProgram(warningstream,"Pixel", pixel_program);
			delete cb;
			return shaderinfo;
		}
	}
	callbacks.push_back(cb);

	// HACK, TODO: investigate this better
	// Grab the material renderer once more so minetest doesn't crash on exit
	driver->getMaterialRenderer(shadermat)->grab();

	// Apply the newly created material type
	shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
	return shaderinfo;
}

void load_shaders(std::string name, SourceShaderCache *sourcecache,
		video::E_DRIVER_TYPE drivertype, bool enable_shaders,
		std::string &vertex_program, std::string &pixel_program,
		std::string &geometry_program, bool &is_highlevel)
{
	vertex_program = "";
	pixel_program = "";
	geometry_program = "";
	is_highlevel = false;

	if(enable_shaders){
		// Look for high level shaders
		if(drivertype == video::EDT_DIRECT3D9){
			// Direct3D 9: HLSL
			// (All shaders in one file)
			vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl");
			pixel_program = vertex_program;
			geometry_program = vertex_program;
		}
		else if(drivertype == video::EDT_OPENGL){
			// OpenGL: GLSL
			vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl");
			pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl");
			geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl");
		}
		if(vertex_program != "" || pixel_program != "" || geometry_program != ""){
			is_highlevel = true;
			return;
		}
	}

}

void dumpShaderProgram(std::ostream &output_stream,
		const std::string &program_type, const std::string &program)
{
	output_stream << program_type << " shader program:" << std::endl <<
		"----------------------------------" << std::endl;
	size_t pos = 0;
	size_t prev = 0;
	s16 line = 1;
	while ((pos = program.find("\n", prev)) != std::string::npos) {
		output_stream << line++ << ": "<< program.substr(prev, pos - prev) <<
			std::endl;
		prev = pos + 1;
	}
	output_stream << line << ": " << program.substr(prev) << std::endl <<
		"End of " << program_type << " shader program." << std::endl <<
		" " << std::endl;
}
rval = 10.0f; float count_before = std::floor(m_inventory_from_server_age / interval); m_inventory_from_server_age += dtime; float count_after = std::floor(m_inventory_from_server_age / interval); if (count_after != count_before) { // Do this every <interval> seconds after TOCLIENT_INVENTORY // Reset the locally changed inventory to the authoritative inventory player->inventory = *m_inventory_from_server; m_update_wielded_item = true; } } /* Update positions of sounds attached to objects */ { for (auto &m_sounds_to_object : m_sounds_to_objects) { int client_id = m_sounds_to_object.first; u16 object_id = m_sounds_to_object.second; ClientActiveObject *cao = m_env.getActiveObject(object_id); if (!cao) continue; m_sound->updateSoundPosition(client_id, cao->getPosition()); } } /* Handle removed remotely initiated sounds */ m_removed_sounds_check_timer += dtime; if(m_removed_sounds_check_timer >= 2.32) { m_removed_sounds_check_timer = 0; // Find removed sounds and clear references to them std::vector<s32> removed_server_ids; for (std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.begin(); i != m_sounds_server_to_client.end();) { s32 server_id = i->first; int client_id = i->second; ++i; if(!m_sound->soundExists(client_id)) { m_sounds_server_to_client.erase(server_id); m_sounds_client_to_server.erase(client_id); m_sounds_to_objects.erase(client_id); removed_server_ids.push_back(server_id); } } // Sync to server if(!removed_server_ids.empty()) { sendRemovedSounds(removed_server_ids); } } // Write changes to the mod storage m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); m_mod_storage_database->endSave(); m_mod_storage_database->beginSave(); } // Write server map if (m_localdb && m_localdb_save_interval.step(dtime, m_cache_save_interval)) { m_localdb->endSave(); m_localdb->beginSave(); } } bool Client::loadMedia(const std::string &data, const std::string &filename, bool from_media_push) { std::string name; const char *image_ext[] = { ".png", ".jpg", ".bmp", ".tga", ".pcx", ".ppm", ".psd", ".wal", ".rgb", NULL }; name = removeStringEnd(filename, image_ext); if (!name.empty()) { TRACESTREAM(<< "Client: Attempting to load image " << "file \"" << filename << "\"" << std::endl); io::IFileSystem *irrfs = m_rendering_engine->get_filesystem(); video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver(); io::IReadFile *rfile = irrfs->createMemoryReadFile( data.c_str(), data.size(), "_tempreadfile"); FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); // Read image video::IImage *img = vdrv->createImageFromFile(rfile); if (!img) { errorstream<<"Client: Cannot create image from data of " <<"file \""<<filename<<"\""<<std::endl; rfile->drop(); return false; } m_tsrc->insertSourceImage(filename, img); img->drop(); rfile->drop(); return true; } const char *sound_ext[] = { ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg", ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", ".ogg", NULL }; name = removeStringEnd(filename, sound_ext); if (!name.empty()) { TRACESTREAM(<< "Client: Attempting to load sound " << "file \"" << filename << "\"" << std::endl); return m_sound->loadSoundData(name, data); } const char *model_ext[] = { ".x", ".b3d", ".md2", ".obj", NULL }; name = removeStringEnd(filename, model_ext); if (!name.empty()) { verbosestream<<"Client: Storing model into memory: " <<"\""<<filename<<"\""<<std::endl; if(m_mesh_data.count(filename)) errorstream<<"Multiple models with name \""<<filename.c_str() <<"\" found; replacing previous model"<<std::endl; m_mesh_data[filename] = data; return true; } const char *translate_ext[] = { ".tr", NULL }; name = removeStringEnd(filename, translate_ext); if (!name.empty()) { if (from_media_push) return false; TRACESTREAM(<< "Client: Loading translation: " << "\"" << filename << "\"" << std::endl); g_client_translations->loadTranslation(data); return true; } errorstream << "Client: Don't know how to load file \"" << filename << "\"" << std::endl; return false; } // Virtual methods from con::PeerHandler void Client::peerAdded(con::Peer *peer) { infostream << "Client::peerAdded(): peer->id=" << peer->id << std::endl; } void Client::deletingPeer(con::Peer *peer, bool timeout) { infostream << "Client::deletingPeer(): " "Server Peer is getting deleted " << "(timeout=" << timeout << ")" << std::endl; if (timeout) { m_access_denied = true; m_access_denied_reason = gettext("Connection timed out."); } } /* u16 command u16 number of files requested for each file { u16 length of name string name } */ void Client::request_media(const std::vector<std::string> &file_requests) { std::ostringstream os(std::ios_base::binary); writeU16(os, TOSERVER_REQUEST_MEDIA); size_t file_requests_size = file_requests.size(); FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); // Packet dynamicly resized NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); pkt << (u16) (file_requests_size & 0xFFFF); for (const std::string &file_request : file_requests) { pkt << file_request; } Send(&pkt); infostream << "Client: Sending media request list to server (" << file_requests.size() << " files, packet size " << pkt.getSize() << ")" << std::endl; } void Client::initLocalMapSaving(const Address &address, const std::string &hostname, bool is_local_server) { if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { return; } std::string world_path; #define set_world_path(hostname) \ world_path = porting::path_user \ + DIR_DELIM + "worlds" \ + DIR_DELIM + "server_" \ + hostname + "_" + std::to_string(address.getPort()); set_world_path(hostname); if (!fs::IsDir(world_path)) { std::string hostname_escaped = hostname; str_replace(hostname_escaped, ':', '_'); set_world_path(hostname_escaped); } #undef set_world_path fs::CreateAllDirs(world_path); m_localdb = new MapDatabaseSQLite3(world_path); m_localdb->beginSave(); actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; } void Client::ReceiveAll() { NetworkPacket pkt; u64 start_ms = porting::getTimeMs(); const u64 budget = 100; for(;;) { // Limit time even if there would be huge amounts of data to // process if (porting::getTimeMs() > start_ms + budget) { infostream << "Client::ReceiveAll(): " "Packet processing budget exceeded." << std::endl; break; } pkt.clear(); try { if (!m_con->TryReceive(&pkt)) break; ProcessData(&pkt); } catch (const con::InvalidIncomingDataException &e) { infostream << "Client::ReceiveAll(): " "InvalidIncomingDataException: what()=" << e.what() << std::endl; } } } inline void Client::handleCommand(NetworkPacket* pkt) { const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; (this->*opHandle.handler)(pkt); } /* sender_peer_id given to this shall be quaranteed to be a valid peer */ void Client::ProcessData(NetworkPacket *pkt) { ToClientCommand command = (ToClientCommand) pkt->getCommand(); u32 sender_peer_id = pkt->getPeerId(); //infostream<<"Client: received command="<<command<<std::endl; m_packetcounter.add((u16)command); g_profiler->graphAdd("client_received_packets", 1); /* If this check is removed, be sure to change the queue system to know the ids */ if(sender_peer_id != PEER_ID_SERVER) { infostream << "Client::ProcessData(): Discarding data not " "coming from server: peer_id=" << sender_peer_id << " command=" << pkt->getCommand() << std::endl; return; } // Command must be handled into ToClientCommandHandler if (command >= TOCLIENT_NUM_MSG_TYPES) { infostream << "Client: Ignoring unknown command " << command << std::endl; return; } /* * Those packets are handled before m_server_ser_ver is set, it's normal * But we must use the new ToClientConnectionState in the future, * as a byte mask */ if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) { handleCommand(pkt); return; } if(m_server_ser_ver == SER_FMT_VER_INVALID) { infostream << "Client: Server serialization" " format invalid or not initialized." " Skipping incoming command=" << command << std::endl; return; } /* Handle runtime commands */ handleCommand(pkt); } void Client::Send(NetworkPacket* pkt) { m_con->Send(PEER_ID_SERVER, serverCommandFactoryTable[pkt->getCommand()].channel, pkt, serverCommandFactoryTable[pkt->getCommand()].reliable); } // Will fill up 12 + 12 + 4 + 4 + 4 bytes void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt) { v3f pf = myplayer->getPosition() * 100; v3f sf = myplayer->getSpeed() * 100; s32 pitch = myplayer->getPitch() * 100; s32 yaw = myplayer->getYaw() * 100; u32 keyPressed = myplayer->control.getKeysPressed(); // scaled by 80, so that pi can fit into a u8 u8 fov = clientMap->getCameraFov() * 80; u8 wanted_range = MYMIN(255, std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE)); v3s32 position(pf.X, pf.Y, pf.Z); v3s32 speed(sf.X, sf.Y, sf.Z); /* Format: [0] v3s32 position*100 [12] v3s32 speed*100 [12+12] s32 pitch*100 [12+12+4] s32 yaw*100 [12+12+4+4] u32 keyPressed [12+12+4+4+4] u8 fov*80 [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) */ *pkt << position << speed << pitch << yaw << keyPressed; *pkt << fov << wanted_range; } void Client::interact(InteractAction action, const PointedThing& pointed) { if(m_state != LC_Ready) { errorstream << "Client::interact() " "Canceled (not connected)" << std::endl; return; } LocalPlayer *myplayer = m_env.getLocalPlayer(); if (myplayer == NULL) return; /* [0] u16 command [2] u8 action [3] u16 item [5] u32 length of the next item (plen) [9] serialized PointedThing [9 + plen] player position information */ NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0); pkt << (u8)action; pkt << myplayer->getWieldIndex(); std::ostringstream tmp_os(std::ios::binary); pointed.serialize(tmp_os); pkt.putLongString(tmp_os.str()); writePlayerPos(myplayer, &m_env.getClientMap(), &pkt); Send(&pkt); } void Client::deleteAuthData() { if (!m_auth_data) return; switch (m_chosen_auth_mech) { case AUTH_MECHANISM_FIRST_SRP: break; case AUTH_MECHANISM_SRP: case AUTH_MECHANISM_LEGACY_PASSWORD: srp_user_delete((SRPUser *) m_auth_data); m_auth_data = NULL; break; case AUTH_MECHANISM_NONE: break; } m_chosen_auth_mech = AUTH_MECHANISM_NONE; } AuthMechanism Client::choseAuthMech(const u32 mechs) { if (mechs & AUTH_MECHANISM_SRP) return AUTH_MECHANISM_SRP; if (mechs & AUTH_MECHANISM_FIRST_SRP) return AUTH_MECHANISM_FIRST_SRP; if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD) return AUTH_MECHANISM_LEGACY_PASSWORD; return AUTH_MECHANISM_NONE; } void Client::sendInit(const std::string &playerName) { NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); // we don't support network compression yet u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE; pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes; pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; pkt << playerName; Send(&pkt); } void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism) { m_chosen_auth_mech = chosen_auth_mechanism; m_is_registration_confirmation_state = true; } void Client::confirmRegistration() { m_is_registration_confirmation_state = false; startAuth(m_chosen_auth_mech); } void Client::startAuth(AuthMechanism chosen_auth_mechanism) { m_chosen_auth_mech = chosen_auth_mechanism; switch (chosen_auth_mechanism) { case AUTH_MECHANISM_FIRST_SRP: { // send srp verifier to server std::string verifier; std::string salt; generate_srp_verifier_and_salt(getPlayerName(), m_password, &verifier, &salt); NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0); resp_pkt << salt << verifier << (u8)((m_password.empty()) ? 1 : 0); Send(&resp_pkt); break; } case AUTH_MECHANISM_SRP: case AUTH_MECHANISM_LEGACY_PASSWORD: { u8 based_on = 1; if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) { m_password = translate_password(getPlayerName(), m_password); based_on = 0; } std::string playername_u = lowercase(getPlayerName()); m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048, getPlayerName().c_str(), playername_u.c_str(), (const unsigned char *) m_password.c_str(), m_password.length(), NULL, NULL); char *bytes_A = 0; size_t len_A = 0; SRP_Result res = srp_user_start_authentication( (struct SRPUser *) m_auth_data, NULL, NULL, 0, (unsigned char **) &bytes_A, &len_A); FATAL_ERROR_IF(res != SRP_OK, "Creating local SRP user failed."); NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0); resp_pkt << std::string(bytes_A, len_A) << based_on; Send(&resp_pkt); break; } case AUTH_MECHANISM_NONE: break; // not handled in this method } } void Client::sendDeletedBlocks(std::vector<v3s16> &blocks) { NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size()); pkt << (u8) blocks.size(); for (const v3s16 &block : blocks) { pkt << block; } Send(&pkt); } void Client::sendGotBlocks(const std::vector<v3s16> &blocks) { NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6 * blocks.size()); pkt << (u8) blocks.size(); for (const v3s16 &block : blocks) pkt << block; Send(&pkt); } void Client::sendRemovedSounds(std::vector<s32> &soundList) { size_t server_ids = soundList.size(); assert(server_ids <= 0xFFFF); NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); pkt << (u16) (server_ids & 0xFFFF); for (s32 sound_id : soundList) pkt << sound_id; Send(&pkt); } void Client::sendNodemetaFields(v3s16 p, const std::string &formname, const StringMap &fields) { size_t fields_size = fields.size(); FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields"); NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); pkt << p << formname << (u16) (fields_size & 0xFFFF); StringMap::const_iterator it; for (it = fields.begin(); it != fields.end(); ++it) { const std::string &name = it->first; const std::string &value = it->second; pkt << name; pkt.putLongString(value); } Send(&pkt); } void Client::sendInventoryFields(const std::string &formname, const StringMap &fields) { size_t fields_size = fields.size(); FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); pkt << formname << (u16) (fields_size & 0xFFFF); StringMap::const_iterator it; for (it = fields.begin(); it != fields.end(); ++it) { const std::string &name = it->first; const std::string &value = it->second; pkt << name; pkt.putLongString(value); } Send(&pkt); } void Client::sendInventoryAction(InventoryAction *a) { std::ostringstream os(std::ios_base::binary); a->serialize(os); // Make data buffer std::string s = os.str(); NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); pkt.putRawString(s.c_str(),s.size()); Send(&pkt); } bool Client::canSendChatMessage() const { u32 now = time(NULL); float time_passed = now - m_last_chat_message_sent; float virt_chat_message_allowance = m_chat_message_allowance + time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); if (virt_chat_message_allowance < 1.0f) return false; return true; } void Client::sendChatMessage(const std::wstring &message) { const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size"); if (canSendChatMessage()) { u32 now = time(NULL); float time_passed = now - m_last_chat_message_sent; m_last_chat_message_sent = now; m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S) m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S; m_chat_message_allowance -= 1.0f; NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); pkt << message; Send(&pkt); } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) { m_out_chat_queue.push(message); } else { infostream << "Could not queue chat message because maximum out chat queue size (" << max_queue_size << ") is reached." << std::endl; } } void Client::clearOutChatQueue() { m_out_chat_queue = std::queue<std::wstring>(); } void Client::sendChangePassword(const std::string &oldpassword, const std::string &newpassword) { LocalPlayer *player = m_env.getLocalPlayer(); if (player == NULL) return; // get into sudo mode and then send new password to server m_password = oldpassword; m_new_password = newpassword; startAuth(choseAuthMech(m_sudo_auth_methods)); } void Client::sendDamage(u16 damage) { NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u16)); pkt << damage; Send(&pkt); } void Client::sendRespawn() { NetworkPacket pkt(TOSERVER_RESPAWN, 0); Send(&pkt); } void Client::sendReady() { NetworkPacket pkt(TOSERVER_CLIENT_READY, 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash) + 2); pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH << (u8) 0 << (u16) strlen(g_version_hash); pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); pkt << (u16)FORMSPEC_API_VERSION; Send(&pkt); } void Client::sendPlayerPos() { LocalPlayer *player = m_env.getLocalPlayer(); if (!player) return; // Save bandwidth by only updating position when // player is not dead and something changed if (m_activeobjects_received && player->isDead()) return; ClientMap &map = m_env.getClientMap(); u8 camera_fov = map.getCameraFov(); u8 wanted_range = map.getControl().wanted_range; u32 keyPressed = player->control.getKeysPressed(); if ( player->last_position == player->getPosition() && player->last_speed == player->getSpeed() && player->last_pitch == player->getPitch() && player->last_yaw == player->getYaw() && player->last_keyPressed == keyPressed && player->last_camera_fov == camera_fov && player->last_wanted_range == wanted_range) return; player->last_position = player->getPosition(); player->last_speed = player->getSpeed(); player->last_pitch = player->getPitch(); player->last_yaw = player->getYaw(); player->last_keyPressed = keyPressed; player->last_camera_fov = camera_fov; player->last_wanted_range = wanted_range; NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); writePlayerPos(player, &map, &pkt); Send(&pkt); } void Client::sendHaveMedia(const std::vector<u32> &tokens) { NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4); sanity_check(tokens.size() < 256); pkt << static_cast<u8>(tokens.size()); for (u32 token : tokens) pkt << token; Send(&pkt); } void Client::removeNode(v3s16 p) { std::map<v3s16, MapBlock*> modified_blocks; try { m_env.getMap().removeNodeAndUpdate(p, modified_blocks); } catch(InvalidPositionException &e) { } for (const auto &modified_block : modified_blocks) { addUpdateMeshTaskWithEdge(modified_block.first, false, true); } } /** * Helper function for Client Side Modding * CSM restrictions are applied there, this should not be used for core engine * @param p * @param is_valid_position * @return */ MapNode Client::CSMGetNode(v3s16 p, bool *is_valid_position) { if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); if ((u32) ppos.getDistanceFrom(p) > m_csm_restriction_noderange) { *is_valid_position = false; return {}; } } return m_env.getMap().getNode(p, is_valid_position); } int Client::CSMClampRadius(v3s16 pos, int radius) { if (!checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) return radius; // This is approximate and will cause some allowed nodes to be excluded v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); u32 distance = ppos.getDistanceFrom(pos); if (distance >= m_csm_restriction_noderange) return 0; return std::min<int>(radius, m_csm_restriction_noderange - distance); } v3s16 Client::CSMClampPos(v3s16 pos) { if (!checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) return pos; v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); const int range = m_csm_restriction_noderange; return v3s16( core::clamp<int>(pos.X, (int)ppos.X - range, (int)ppos.X + range), core::clamp<int>(pos.Y, (int)ppos.Y - range, (int)ppos.Y + range), core::clamp<int>(pos.Z, (int)ppos.Z - range, (int)ppos.Z + range) ); } void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) { //TimeTaker timer1("Client::addNode()"); std::map<v3s16, MapBlock*> modified_blocks; try { //TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata); } catch(InvalidPositionException &e) { } for (const auto &modified_block : modified_blocks) { addUpdateMeshTaskWithEdge(modified_block.first, false, true); } } void Client::setPlayerControl(PlayerControl &control) { LocalPlayer *player = m_env.getLocalPlayer(); assert(player); player->control = control; } void Client::setPlayerItem(u16 item) { m_env.getLocalPlayer()->setWieldIndex(item); m_update_wielded_item = true; NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); pkt << item; Send(&pkt); } // Returns true once after the inventory of the local player // has been updated from the server. bool Client::updateWieldedItem() { if (!m_update_wielded_item) return false; m_update_wielded_item = false; LocalPlayer *player = m_env.getLocalPlayer(); assert(player); if (auto *list = player->inventory.getList("main")) list->setModified(false); if (auto *list = player->inventory.getList("hand")) list->setModified(false); return true; } scene::ISceneManager* Client::getSceneManager() { return m_rendering_engine->get_scene_manager(); } Inventory* Client::getInventory(const InventoryLocation &loc) { switch(loc.type){ case InventoryLocation::UNDEFINED: {} break; case InventoryLocation::CURRENT_PLAYER: { LocalPlayer *player = m_env.getLocalPlayer(); assert(player); return &player->inventory; } break; case InventoryLocation::PLAYER: { // Check if we are working with local player inventory LocalPlayer *player = m_env.getLocalPlayer(); if (!player || strcmp(player->getName(), loc.name.c_str()) != 0) return NULL; return &player->inventory; } break; case InventoryLocation::NODEMETA: { NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p); if(!meta) return NULL; return meta->getInventory(); } break; case InventoryLocation::DETACHED: { if (m_detached_inventories.count(loc.name) == 0) return NULL; return m_detached_inventories[loc.name]; } break; default: FATAL_ERROR("Invalid inventory location type."); break; } return NULL; } void Client::inventoryAction(InventoryAction *a) { /* Send it to the server */ sendInventoryAction(a); /* Predict some local inventory changes */ a->clientApply(this, this); // Remove it delete a; } float Client::getAnimationTime() { return m_animation_time; } int Client::getCrackLevel() { return m_crack_level; } v3s16 Client::getCrackPos() { return m_crack_pos; } void Client::setCrack(int level, v3s16 pos) { int old_crack_level = m_crack_level; v3s16 old_crack_pos = m_crack_pos; m_crack_level = level; m_crack_pos = pos; if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos)) { // remove old crack addUpdateMeshTaskForNode(old_crack_pos, false, true); } if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos)) { // add new crack addUpdateMeshTaskForNode(pos, false, true); } } u16 Client::getHP() { LocalPlayer *player = m_env.getLocalPlayer(); assert(player); return player->hp; } bool Client::getChatMessage(std::wstring &res) { if (m_chat_queue.empty()) return false; ChatMessage *chatMessage = m_chat_queue.front(); m_chat_queue.pop(); res = L""; switch (chatMessage->type) { case CHATMESSAGE_TYPE_RAW: case CHATMESSAGE_TYPE_ANNOUNCE: case CHATMESSAGE_TYPE_SYSTEM: res = chatMessage->message; break; case CHATMESSAGE_TYPE_NORMAL: { if (!chatMessage->sender.empty()) res = L"<" + chatMessage->sender + L"> " + chatMessage->message; else res = chatMessage->message; break; } default: break; } delete chatMessage; return true; } void Client::typeChatMessage(const std::wstring &message) { // Discard empty line if (message.empty()) return; // If message was consumed by script API, don't send it to server if (m_mods_loaded && m_script->on_sending_message(wide_to_utf8(message))) return; // Send to others sendChatMessage(message); } void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) { // Check if the block exists to begin with. In the case when a non-existing // neighbor is automatically added, it may not. In that case we don't want // to tell the mesh update thread about it. MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p); if (b == NULL) return; m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); } void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) { m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true); } void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) { { v3s16 p = nodepos; infostream<<"Client::addUpdateMeshTaskForNode(): " <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")" <<std::endl; } v3s16 blockpos = getNodeBlockPos(nodepos); v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE; m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false); // Leading edge if (nodepos.X == blockpos_relative.X) addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent); if (nodepos.Y == blockpos_relative.Y) addUpdateMeshTask(blockpos + v3s16(0, -1, 0), false, urgent); if (nodepos.Z == blockpos_relative.Z) addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent); } ClientEvent *Client::getClientEvent() { FATAL_ERROR_IF(m_client_event_queue.empty(), "Cannot getClientEvent, queue is empty."); ClientEvent *event = m_client_event_queue.front(); m_client_event_queue.pop(); return event; } const Address Client::getServerAddress() { return m_con->GetPeerAddress(PEER_ID_SERVER); } float Client::mediaReceiveProgress() { if (m_media_downloader) return m_media_downloader->getProgress(); return 1.0; // downloader only exists when not yet done } struct TextureUpdateArgs { gui::IGUIEnvironment *guienv; u64 last_time_ms; u16 last_percent; const wchar_t* text_base; ITextureSource *tsrc; }; void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress) { TextureUpdateArgs* targs = (TextureUpdateArgs*) args; u16 cur_percent = ceil(progress / (double) max_progress * 100.); // update the loading menu -- if neccessary bool do_draw = false; u64 time_ms = targs->last_time_ms; if (cur_percent != targs->last_percent) { targs->last_percent = cur_percent; time_ms = porting::getTimeMs(); // only draw when the user will notice something: do_draw = (time_ms - targs->last_time_ms > 100); } if (do_draw) { targs->last_time_ms = time_ms; std::wostringstream strm; strm << targs->text_base << L" " << targs->last_percent << L"%..."; m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); } } void Client::afterContentReceived() { infostream<<"Client::afterContentReceived() started"<<std::endl; assert(m_itemdef_received); // pre-condition assert(m_nodedef_received); // pre-condition assert(mediaReceived()); // pre-condition const wchar_t* text = wgettext("Loading textures..."); // Clear cached pre-scaled 2D GUI images, as this cache // might have images with the same name but different // content from previous sessions. guiScalingCacheClear(); // Rebuild inherited images and recreate textures infostream<<"- Rebuilding images and textures"<<std::endl; m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 70); m_tsrc->rebuildImagesAndTextures(); delete[] text; // Rebuild shaders infostream<<"- Rebuilding shaders"<<std::endl; text = wgettext("Rebuilding shaders..."); m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 71); m_shsrc->rebuildShaders(); delete[] text; // Update node aliases infostream<<"- Updating node aliases"<<std::endl; text = wgettext("Initializing nodes..."); m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 72); m_nodedef->updateAliases(m_itemdef); for (const auto &path : getTextureDirs()) { TextureOverrideSource override_source(path + DIR_DELIM + "override.txt"); m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides()); m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides()); } m_nodedef->setNodeRegistrationStatus(true); m_nodedef->runNodeResolveCallbacks(); delete[] text; // Update node textures and assign shaders to each tile infostream<<"- Updating node textures"<<std::endl; TextureUpdateArgs tu_args; tu_args.guienv = guienv; tu_args.last_time_ms = porting::getTimeMs(); tu_args.last_percent = 0; tu_args.text_base = wgettext("Initializing nodes"); tu_args.tsrc = m_tsrc; m_nodedef->updateTextures(this, &tu_args); delete[] tu_args.text_base; // Start mesh update thread after setting up content definitions infostream<<"- Starting mesh update thread"<<std::endl; m_mesh_update_thread.start(); m_state = LC_Ready; sendReady(); if (m_mods_loaded) m_script->on_client_ready(m_env.getLocalPlayer()); text = wgettext("Done!"); m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, 0, 100); infostream<<"Client::afterContentReceived() done"<<std::endl; delete[] text; } float Client::getRTT() { return m_con->getPeerStat(PEER_ID_SERVER,con::AVG_RTT); } float Client::getCurRate() { return (m_con->getLocalStat(con::CUR_INC_RATE) + m_con->getLocalStat(con::CUR_DL_RATE)); } void Client::makeScreenshot() { irr::video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); irr::video::IImage* const raw_image = driver->createScreenShot(); if (!raw_image) return; time_t t = time(NULL); struct tm *tm = localtime(&t); char timetstamp_c[64]; strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); std::string screenshot_dir; if (fs::IsPathAbsolute(g_settings->get("screenshot_path"))) screenshot_dir = g_settings->get("screenshot_path"); else screenshot_dir = porting::path_user + DIR_DELIM + g_settings->get("screenshot_path"); std::string filename_base = screenshot_dir + DIR_DELIM + std::string("screenshot_") + std::string(timetstamp_c); std::string filename_ext = "." + g_settings->get("screenshot_format"); std::string filename; // Create the directory if it doesn't already exist. // Otherwise, saving the screenshot would fail. fs::CreateDir(screenshot_dir); u32 quality = (u32)g_settings->getS32("screenshot_quality"); quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; // Try to find a unique filename unsigned serial = 0; while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; std::ifstream tmp(filename.c_str()); if (!tmp.good()) break; // File did not apparently exist, we'll go with it serial++; } if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { infostream << "Could not find suitable filename for screenshot" << std::endl; } else { irr::video::IImage* const image = driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); if (image) { raw_image->copyTo(image); std::ostringstream sstr; if (driver->writeImageToFile(image, filename.c_str(), quality)) { sstr << "Saved screenshot to '" << filename << "'"; } else { sstr << "Failed to save screenshot '" << filename << "'"; } pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, utf8_to_wide(sstr.str()))); infostream << sstr.str() << std::endl; image->drop(); } } raw_image->drop(); } bool Client::shouldShowMinimap() const { return !m_minimap_disabled_by_server; } void Client::pushToEventQueue(ClientEvent *event) { m_client_event_queue.push(event); } void Client::showMinimap(const bool show) { m_game_ui->showMinimap(show); } // IGameDef interface // Under envlock IItemDefManager* Client::getItemDefManager() { return m_itemdef; } const NodeDefManager* Client::getNodeDefManager() { return m_nodedef; } ICraftDefManager* Client::getCraftDefManager() { return NULL; //return m_craftdef; } ITextureSource* Client::getTextureSource() { return m_tsrc; } IWritableShaderSource* Client::getShaderSource() { return m_shsrc; } u16 Client::allocateUnknownNodeId(const std::string &name) { errorstream << "Client::allocateUnknownNodeId(): " << "Client cannot allocate node IDs" << std::endl; FATAL_ERROR("Client allocated unknown node"); return CONTENT_IGNORE; } ISoundManager* Client::getSoundManager() { return m_sound; } MtEventManager* Client::getEventManager() { return m_event; } ParticleManager* Client::getParticleManager() { return &m_particle_manager; } scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) { StringMap::const_iterator it = m_mesh_data.find(filename); if (it == m_mesh_data.end()) { errorstream << "Client::getMesh(): Mesh not found: \"" << filename << "\"" << std::endl; return NULL; } const std::string &data = it->second; // Create the mesh, remove it from cache and return it // This allows unique vertex colors and other properties for each instance io::IReadFile *rfile = m_rendering_engine->get_filesystem()->createMemoryReadFile( data.c_str(), data.size(), filename.c_str()); FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); scene::IAnimatedMesh *mesh = m_rendering_engine->get_scene_manager()->getMesh(rfile); rfile->drop(); if (!mesh) return nullptr; mesh->grab(); if (!cache) m_rendering_engine->removeMesh(mesh); return mesh; } const std::string* Client::getModFile(std::string filename) { // strip dir delimiter from beginning of path auto pos = filename.find_first_of(':'); if (pos == std::string::npos) return nullptr; pos++; auto pos2 = filename.find_first_not_of('/', pos); if (pos2 > pos) filename.erase(pos, pos2 - pos); StringMap::const_iterator it = m_mod_vfs.find(filename); if (it == m_mod_vfs.end()) return nullptr; return &it->second; } bool Client::registerModStorage(ModMetadata *storage) { if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { errorstream << "Unable to register same mod storage twice. Storage name: " << storage->getModName() << std::endl; return false; } m_mod_storages[storage->getModName()] = storage; return true; } void Client::unregisterModStorage(const std::string &name) { std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.find(name); if (it != m_mod_storages.end()) m_mod_storages.erase(name); } /* * Mod channels */ bool Client::joinModChannel(const std::string &channel) { if (m_modchannel_mgr->channelRegistered(channel)) return false; NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size()); pkt << channel; Send(&pkt); m_modchannel_mgr->joinChannel(channel, 0); return true; } bool Client::leaveModChannel(const std::string &channel) { if (!m_modchannel_mgr->channelRegistered(channel)) return false; NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size()); pkt << channel; Send(&pkt); m_modchannel_mgr->leaveChannel(channel, 0); return true; } bool Client::sendModChannelMessage(const std::string &channel, const std::string &message) { if (!m_modchannel_mgr->canWriteOnChannel(channel)) return false; if (message.size() > STRING_MAX_LEN) { warningstream << "ModChannel message too long, dropping before sending " << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " << channel << ")" << std::endl; return false; } // @TODO: do some client rate limiting NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); pkt << channel << message; Send(&pkt); return true; } ModChannel* Client::getModChannel(const std::string &channel) { return m_modchannel_mgr->getModChannel(channel); }