aboutsummaryrefslogtreecommitdiff
path: root/src/script/lua_api/l_noise.cpp
blob: f43ba837a3d6b2bc17cd5d3ce1bd22f46d2721be (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
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU 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 "lua_api/l_noise.h"
#include "lua_api/l_internal.h"
#include "common/c_converter.h"
#include "common/c_content.h"
#include "log.h"
#include "porting.h"
#include "util/numeric.h"

///////////////////////////////////////
/*
  LuaPerlinNoise
*/

LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) :
	np(*params)
{
}


int LuaPerlinNoise::l_get_2d(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;
	LuaPerlinNoise *o = checkobject(L, 1);
	v2f p = readParam<v2f>(L, 2);
	lua_Number val = NoisePerlin2D(&o->np, p.X, p.Y, 0);
	lua_pushnumber(L, val);
	return 1;
}


int LuaPerlinNoise::l_get_3d(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;
	LuaPerlinNoise *o = checkobject(L, 1);
	v3f p = check_v3f(L, 2);
	lua_Number val = NoisePerlin3D(&o->np, p.X, p.Y, p.Z, 0);
	lua_pushnumber(L, val);
	return 1;
}


int LuaPerlinNoise::create_object(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	NoiseParams params;

	if (lua_istable(L, 1)) {
		read_noiseparams(L, 1, &params);
	} else {
		params.seed    = luaL_checkint(L, 1);
		params.octaves = luaL_checkint(L, 2);
		params.persist = readParam<float>(L, 3);
		params.spread  = v3f(1, 1, 1) * readParam<float>(L, 4);
	}

	LuaPerlinNoise *o = new LuaPerlinNoise(&params);

	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
	luaL_getmetatable(L, className);
	lua_setmetatable(L, -2);
	return 1;
}


int LuaPerlinNoise::gc_object(lua_State *L)
{
	LuaPerlinNoise *o = *(LuaPerlinNoise **)(lua_touserdata(L, 1));
	delete o;
	return 0;
}


LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg)
{
	NO_MAP_LOCK_REQUIRED;
	luaL_checktype(L, narg, LUA_TUSERDATA);
	void *ud = luaL_checkudata(L, narg, className);
	if (!ud)
		luaL_typerror(L, narg, className);
	return *(LuaPerlinNoise **)ud;
}


void LuaPerlinNoise::Register(lua_State *L)
{
	lua_newtable(L);
	int methodtable = lua_gettop(L);
	luaL_newmetatable(L, className);
	int metatable = lua_gettop(L);

	lua_pushliteral(L, "__metatable");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__index");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__gc");
	lua_pushcfunction(L, gc_object);
	lua_settable(L, metatable);

	lua_pop(L, 1);

	luaL_register(L, nullptr, methods);
	lua_pop(L, 1);

	lua_register(L, className, create_object);
}


const char LuaPerlinNoise::className[] = "PerlinNoise";
luaL_Reg LuaPerlinNoise::methods[] = {
	luamethod_aliased(LuaPerlinNoise, get_2d, get2d),
	luamethod_aliased(LuaPerlinNoise, get_3d, get3d),
	{0,0}
};

///////////////////////////////////////
/*
  LuaPerlinNoiseMap
*/

LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size)
{
	m_is3d = size.Z > 1;
	np = *params;
	try {
		noise = new Noise(&np, seed, size.X, size.Y, size.Z);
	} catch (InvalidNoiseParamsException &e) {
		throw LuaError(e.what());
	}
}


LuaPerlinNoiseMap::~LuaPerlinNoiseMap()
{
	delete noise;
}


int LuaPerlinNoiseMap::l_get_2d_map(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;
	size_t i = 0;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v2f p = readParam<v2f>(L, 2);

	Noise *n = o->noise;
	n->perlinMap2D(p.X, p.Y);

	lua_createtable(L, n->sy, 0);
	for (u32 y = 0; y != n->sy; y++) {
		lua_createtable(L, n->sx, 0);
		for (u32 x = 0; x != n->sx; x++) {
			lua_pushnumber(L, n->result[i++]);
			lua_rawseti(L, -2, x + 1);
		}
		lua_rawseti(L, -2, y + 1);
	}
	return 1;
}


int LuaPerlinNoiseMap::l_get_2d_map_flat(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v2f p = readParam<v2f>(L, 2);
	bool use_buffer = lua_istable(L, 3);

	Noise *n = o->noise;
	n->perlinMap2D(p.X, p.Y);

	size_t maplen = n->sx * n->sy;

	if (use_buffer)
		lua_pushvalue(L, 3);
	else
		lua_createtable(L, maplen, 0);

	for (size_t i = 0; i != maplen; i++) {
		lua_pushnumber(L, n->result[i]);
		lua_rawseti(L, -2, i + 1);
	}
	return 1;
}


int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;
	size_t i = 0;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v3f p = check_v3f(L, 2);

	if (!o->m_is3d)
		return 0;

	Noise *n = o->noise;
	n->perlinMap3D(p.X, p.Y, p.Z);

	lua_createtable(L, n->sz, 0);
	for (u32 z = 0; z != n->sz; z++) {
		lua_createtable(L, n->sy, 0);
		for (u32 y = 0; y != n->sy; y++) {
			lua_createtable(L, n->sx, 0);
			for (u32 x = 0; x != n->sx; x++) {
				lua_pushnumber(L, n->result[i++]);
				lua_rawseti(L, -2, x + 1);
			}
			lua_rawseti(L, -2, y + 1);
		}
		lua_rawseti(L, -2, z + 1);
	}
	return 1;
}


int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v3f p                = check_v3f(L, 2);
	bool use_buffer      = lua_istable(L, 3);

	if (!o->m_is3d)
		return 0;

	Noise *n = o->noise;
	n->perlinMap3D(p.X, p.Y, p.Z);

	size_t maplen = n->sx * n->sy * n->sz;

	if (use_buffer)
		lua_pushvalue(L, 3);
	else
		lua_createtable(L, maplen, 0);

	for (size_t i = 0; i != maplen; i++) {
		lua_pushnumber(L, n->result[i]);
		lua_rawseti(L, -2, i + 1);
	}
	return 1;
}


int LuaPerlinNoiseMap::l_calc_2d_map(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v2f p = readParam<v2f>(L, 2);

	Noise *n = o->noise;
	n->perlinMap2D(p.X, p.Y);

	return 0;
}

int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v3f p                = check_v3f(L, 2);

	if (!o->m_is3d)
		return 0;

	Noise *n = o->noise;
	n->perlinMap3D(p.X, p.Y, p.Z);

	return 0;
}


int LuaPerlinNoiseMap::l_get_map_slice(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPerlinNoiseMap *o = checkobject(L, 1);
	v3s16 slice_offset   = read_v3s16(L, 2);
	v3s16 slice_size     = read_v3s16(L, 3);
	bool use_buffer      = lua_istable(L, 4);

	Noise *n = o->noise;

	if (use_buffer)
		lua_pushvalue(L, 4);
	else
		lua_newtable(L);

	write_array_slice_float(L, lua_gettop(L), n->result,
		v3u16(n->sx, n->sy, n->sz),
		v3u16(slice_offset.X, slice_offset.Y, slice_offset.Z),
		v3u16(slice_size.X, slice_size.Y, slice_size.Z));

	return 1;
}


int LuaPerlinNoiseMap::create_object(lua_State *L)
{
	NoiseParams np;
	if (!read_noiseparams(L, 1, &np))
		return 0;
	v3s16 size = read_v3s16(L, 2);

	LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&np, 0, size);
	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
	luaL_getmetatable(L, className);
	lua_setmetatable(L, -2);
	return 1;
}


int LuaPerlinNoiseMap::gc_object(lua_State *L)
{
	LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)(lua_touserdata(L, 1));
	delete o;
	return 0;
}


LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg)
{
	luaL_checktype(L, narg, LUA_TUSERDATA);

	void *ud = luaL_checkudata(L, narg, className);
	if (!ud)
		luaL_typerror(L, narg, className);

	return *(LuaPerlinNoiseMap **)ud;
}


void LuaPerlinNoiseMap::Register(lua_State *L)
{
	lua_newtable(L);
	int methodtable = lua_gettop(L);
	luaL_newmetatable(L, className);
	int metatable = lua_gettop(L);

	lua_pushliteral(L, "__metatable");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__index");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__gc");
	lua_pushcfunction(L, gc_object);
	lua_settable(L, metatable);

	lua_pop(L, 1);

	luaL_register(L, nullptr, methods);
	lua_pop(L, 1);

	lua_register(L, className, create_object);
}


const char LuaPerlinNoiseMap::className[] = "PerlinNoiseMap";
luaL_Reg LuaPerlinNoiseMap::methods[] = {
	luamethod_aliased(LuaPerlinNoiseMap, get_2d_map,      get2dMap),
	luamethod_aliased(LuaPerlinNoiseMap, get_2d_map_flat, get2dMap_flat),
	luamethod_aliased(LuaPerlinNoiseMap, calc_2d_map,     calc2dMap),
	luamethod_aliased(LuaPerlinNoiseMap, get_3d_map,      get3dMap),
	luamethod_aliased(LuaPerlinNoiseMap, get_3d_map_flat, get3dMap_flat),
	luamethod_aliased(LuaPerlinNoiseMap, calc_3d_map,     calc3dMap),
	luamethod_aliased(LuaPerlinNoiseMap, get_map_slice,   getMapSlice),
	{0,0}
};

///////////////////////////////////////
/*
	LuaPseudoRandom
*/

int LuaPseudoRandom::l_next(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPseudoRandom *o = checkobject(L, 1);
	int min = 0;
	int max = 32767;
	lua_settop(L, 3);
	if (lua_isnumber(L, 2))
		min = luaL_checkinteger(L, 2);
	if (lua_isnumber(L, 3))
		max = luaL_checkinteger(L, 3);
	if (max < min) {
		errorstream<<"PseudoRandom.next(): max="<<max<<" min="<<min<<std::endl;
		throw LuaError("PseudoRandom.next(): max < min");
	}
	if(max - min != 32767 && max - min > 32767/5)
		throw LuaError("PseudoRandom.next() max-min is not 32767"
				" and is > 32768/5. This is disallowed due to"
				" the bad random distribution the"
				" implementation would otherwise make.");
	PseudoRandom &pseudo = o->m_pseudo;
	int val = pseudo.next();
	val = (val % (max-min+1)) + min;
	lua_pushinteger(L, val);
	return 1;
}


int LuaPseudoRandom::create_object(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	u64 seed = luaL_checknumber(L, 1);
	LuaPseudoRandom *o = new LuaPseudoRandom(seed);
	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
	luaL_getmetatable(L, className);
	lua_setmetatable(L, -2);
	return 1;
}


int LuaPseudoRandom::gc_object(lua_State *L)
{
	LuaPseudoRandom *o = *(LuaPseudoRandom **)(lua_touserdata(L, 1));
	delete o;
	return 0;
}


LuaPseudoRandom *LuaPseudoRandom::checkobject(lua_State *L, int narg)
{
	luaL_checktype(L, narg, LUA_TUSERDATA);
	void *ud = luaL_checkudata(L, narg, className);
	if (!ud)
		luaL_typerror(L, narg, className);
	return *(LuaPseudoRandom **)ud;
}


void LuaPseudoRandom::Register(lua_State *L)
{
	lua_newtable(L);
	int methodtable = lua_gettop(L);
	luaL_newmetatable(L, className);
	int metatable = lua_gettop(L);

	lua_pushliteral(L, "__metatable");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__index");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__gc");
	lua_pushcfunction(L, gc_object);
	lua_settable(L, metatable);

	lua_pop(L, 1);

	luaL_register(L, nullptr, methods);
	lua_pop(L, 1);

	lua_register(L, className, create_object);
}


const char LuaPseudoRandom::className[] = "PseudoRandom";
const luaL_Reg LuaPseudoRandom::methods[] = {
	luamethod(LuaPseudoRandom, next),
	{0,0}
};

///////////////////////////////////////
/*
	LuaPcgRandom
*/

int LuaPcgRandom::l_next(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPcgRandom *o = checkobject(L, 1);
	u32 min = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : o->m_rnd.RANDOM_MIN;
	u32 max = lua_isnumber(L, 3) ? lua_tointeger(L, 3) : o->m_rnd.RANDOM_MAX;

	lua_pushinteger(L, o->m_rnd.range(min, max));
	return 1;
}


int LuaPcgRandom::l_rand_normal_dist(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaPcgRandom *o = checkobject(L, 1);
	u32 min = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : o->m_rnd.RANDOM_MIN;
	u32 max = lua_isnumber(L, 3) ? lua_tointeger(L, 3) : o->m_rnd.RANDOM_MAX;
	int num_trials = lua_isnumber(L, 4) ? lua_tointeger(L, 4) : 6;

	lua_pushinteger(L, o->m_rnd.randNormalDist(min, max, num_trials));
	return 1;
}


int LuaPcgRandom::create_object(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	u64 seed = luaL_checknumber(L, 1);
	LuaPcgRandom *o = lua_isnumber(L, 2) ?
		new LuaPcgRandom(seed, lua_tointeger(L, 2)) :
		new LuaPcgRandom(seed);
	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
	luaL_getmetatable(L, className);
	lua_setmetatable(L, -2);
	return 1;
}


int LuaPcgRandom::gc_object(lua_State *L)
{
	LuaPcgRandom *o = *(LuaPcgRandom **)(lua_touserdata(L, 1));
	delete o;
	return 0;
}


LuaPcgRandom *LuaPcgRandom::checkobject(lua_State *L, int narg)
{
	luaL_checktype(L, narg, LUA_TUSERDATA);
	void *ud = luaL_checkudata(L, narg, className);
	if (!ud)
		luaL_typerror(L, narg, className);
	return *(LuaPcgRandom **)ud;
}


void LuaPcgRandom::Register(lua_State *L)
{
	lua_newtable(L);
	int methodtable = lua_gettop(L);
	luaL_newmetatable(L, className);
	int metatable = lua_gettop(L);

	lua_pushliteral(L, "__metatable");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__index");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__gc");
	lua_pushcfunction(L, gc_object);
	lua_settable(L, metatable);

	lua_pop(L, 1);

	luaL_register(L, nullptr, methods);
	lua_pop(L, 1);

	lua_register(L, className, create_object);
}


const char LuaPcgRandom::className[] = "PcgRandom";
const luaL_Reg LuaPcgRandom::methods[] = {
	luamethod(LuaPcgRandom, next),
	luamethod(LuaPcgRandom, rand_normal_dist),
	{0,0}
};

///////////////////////////////////////
/*
	LuaSecureRandom
*/

bool LuaSecureRandom::fillRandBuf()
{
	return porting::secure_rand_fill_buf(m_rand_buf, RAND_BUF_SIZE);
}

int LuaSecureRandom::l_next_bytes(lua_State *L)
{
	NO_MAP_LOCK_REQUIRED;

	LuaSecureRandom *o = checkobject(L, 1);
	u32 count = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : 1;

	// Limit count
	count = MYMIN(RAND_BUF_SIZE, count);

	// Find out whether we can pass directly from our array, or have to do some gluing
	size_t count_remaining = RAND_BUF_SIZE - o->m_rand_idx;
	if (count_remaining >= count) {
		lua_pushlstring(L, o->m_rand_buf + o->m_rand_idx, count);
		o->m_rand_idx += count;
	} else {
		char output_buf[RAND_BUF_SIZE];

		// Copy over with what we have left from our current buffer
		memcpy(output_buf, o->m_rand_buf + o->m_rand_idx, count_remaining);

		// Refill buffer and copy over the remainder of what was requested
		o->fillRandBuf();
		memcpy(output_buf + count_remaining, o->m_rand_buf, count - count_remaining);

		// Update index
		o->m_rand_idx = count - count_remaining;

		lua_pushlstring(L, output_buf, count);
	}

	return 1;
}


int LuaSecureRandom::create_object(lua_State *L)
{
	LuaSecureRandom *o = new LuaSecureRandom();

	// Fail and return nil if we can't securely fill the buffer
	if (!o->fillRandBuf()) {
		delete o;
		return 0;
	}

	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
	luaL_getmetatable(L, className);
	lua_setmetatable(L, -2);
	return 1;
}


int LuaSecureRandom::gc_object(lua_State *L)
{
	LuaSecureRandom *o = *(LuaSecureRandom **)(lua_touserdata(L, 1));
	delete o;
	return 0;
}


LuaSecureRandom *LuaSecureRandom::checkobject(lua_State *L, int narg)
{
	luaL_checktype(L, narg, LUA_TUSERDATA);
	void *ud = luaL_checkudata(L, narg, className);
	if (!ud)
		luaL_typerror(L, narg, className);
	return *(LuaSecureRandom **)ud;
}


void LuaSecureRandom::Register(lua_State *L)
{
	lua_newtable(L);
	int methodtable = lua_gettop(L);
	luaL_newmetatable(L, className);
	int metatable = lua_gettop(L);

	lua_pushliteral(L, "__metatable");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__index");
	lua_pushvalue(L, methodtable);
	lua_settable(L, metatable);

	lua_pushliteral(L, "__gc");
	lua_pushcfunction(L, gc_object);
	lua_settable(L, metatable);

	lua_pop(L, 1);

	luaL_register(L, nullptr, methods);
	lua_pop(L, 1);

	lua_register(L, className, create_object);
}

const char LuaSecureRandom::className[] = "SecureRandom";
const luaL_Reg LuaSecureRandom::methods[] = {
	luamethod(LuaSecureRandom, next_bytes),
	{0,0}
};
d TestCAO::removeFromScene(bool permanent) { if (!m_node) return; m_node->remove(); m_node = NULL; } void TestCAO::updateLight(u32 day_night_ratio) { } void TestCAO::updateNodePos() { if (!m_node) return; m_node->setPosition(m_position); //m_node->setRotation(v3f(0, 45, 0)); } void TestCAO::step(float dtime, ClientEnvironment *env) { if(m_node) { v3f rot = m_node->getRotation(); //infostream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl; rot.Y += dtime * 180; m_node->setRotation(rot); } } void TestCAO::processMessage(const std::string &data) { infostream<<"TestCAO: Got data: "<<data<<std::endl; std::istringstream is(data, std::ios::binary); u16 cmd; is>>cmd; if(cmd == 0) { v3f newpos; is>>newpos.X; is>>newpos.Y; is>>newpos.Z; m_position = newpos; updateNodePos(); } } /* GenericCAO */ #include "clientobject.h" GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): ClientActiveObject(0, client, env) { if (client == NULL) { ClientActiveObject::registerType(getType(), create); } else { m_client = client; } } bool GenericCAO::getCollisionBox(aabb3f *toset) const { if (m_prop.physical) { //update collision box toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; toset->MinEdge += m_position; toset->MaxEdge += m_position; return true; } return false; } bool GenericCAO::collideWithObjects() const { return m_prop.collideWithObjects; } void GenericCAO::initialize(const std::string &data) { infostream<<"GenericCAO: Got init data"<<std::endl; processInitData(data); m_enable_shaders = g_settings->getBool("enable_shaders"); } void GenericCAO::processInitData(const std::string &data) { std::istringstream is(data, std::ios::binary); const u8 version = readU8(is); if (version < 1) { errorstream << "GenericCAO: Unsupported init data version" << std::endl; return; } // PROTOCOL_VERSION >= 37 m_name = deSerializeString16(is); m_is_player = readU8(is); m_id = readU16(is); m_position = readV3F32(is); m_rotation = readV3F32(is); m_hp = readU16(is); if (m_is_player) { // Check if it's the current player LocalPlayer *player = m_env->getLocalPlayer(); if (player && strcmp(player->getName(), m_name.c_str()) == 0) { m_is_local_player = true; m_is_visible = false; player->setCAO(this); } } const u8 num_messages = readU8(is); for (int i = 0; i < num_messages; i++) { std::string message = deSerializeString32(is); processMessage(message); } m_rotation = wrapDegrees_0_360_v3f(m_rotation); pos_translator.init(m_position); rot_translator.init(m_rotation); updateNodePos(); } GenericCAO::~GenericCAO() { removeFromScene(true); } bool GenericCAO::getSelectionBox(aabb3f *toset) const { if (!m_prop.is_visible || !m_is_visible || m_is_local_player || !m_prop.pointable) { return false; } *toset = m_selection_box; return true; } const v3f GenericCAO::getPosition() const { if (!getParent()) return pos_translator.val_current; // Calculate real position in world based on MatrixNode if (m_matrixnode) { v3s16 camera_offset = m_env->getCameraOffset(); return m_matrixnode->getAbsolutePosition() + intToFloat(camera_offset, BS); } return m_position; } bool GenericCAO::isImmortal() const { return itemgroup_get(getGroups(), "immortal"); } scene::ISceneNode *GenericCAO::getSceneNode() const { if (m_meshnode) { return m_meshnode; } if (m_animated_meshnode) { return m_animated_meshnode; } if (m_wield_meshnode) { return m_wield_meshnode; } if (m_spritenode) { return m_spritenode; } return NULL; } scene::IAnimatedMeshSceneNode *GenericCAO::getAnimatedMeshSceneNode() const { return m_animated_meshnode; } void GenericCAO::setChildrenVisible(bool toset) { for (u16 cao_id : m_attachment_child_ids) { GenericCAO *obj = m_env->getGenericCAO(cao_id); if (obj) { // Check if the entity is forced to appear in first person. obj->setVisible(obj->m_force_visible ? true : toset); } } } void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation, bool force_visible) { int old_parent = m_attachment_parent_id; m_attachment_parent_id = parent_id; m_attachment_bone = bone; m_attachment_position = position; m_attachment_rotation = rotation; m_force_visible = force_visible; ClientActiveObject *parent = m_env->getActiveObject(parent_id); if (parent_id != old_parent) { if (auto *o = m_env->getActiveObject(old_parent)) o->removeAttachmentChild(m_id); if (parent) parent->addAttachmentChild(m_id); } updateAttachments(); // Forcibly show attachments if required by set_attach if (m_force_visible) { m_is_visible = true; } else if (!m_is_local_player) { // Objects attached to the local player should be hidden in first person m_is_visible = !m_attached_to_local || m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST; m_force_visible = false; } else { // Local players need to have this set, // otherwise first person attachments fail. m_is_visible = true; } } void GenericCAO::getAttachment(int *parent_id, std::string *bone, v3f *position, v3f *rotation, bool *force_visible) const { *parent_id = m_attachment_parent_id; *bone = m_attachment_bone; *position = m_attachment_position; *rotation = m_attachment_rotation; *force_visible = m_force_visible; } void GenericCAO::clearChildAttachments() { // Cannot use for-loop here: setAttachment() modifies 'm_attachment_child_ids'! while (!m_attachment_child_ids.empty()) { int child_id = *m_attachment_child_ids.begin(); if (ClientActiveObject *child = m_env->getActiveObject(child_id)) child->setAttachment(0, "", v3f(), v3f(), false); removeAttachmentChild(child_id); } } void GenericCAO::clearParentAttachment() { if (m_attachment_parent_id) setAttachment(0, "", m_attachment_position, m_attachment_rotation, false); else setAttachment(0, "", v3f(), v3f(), false); } void GenericCAO::addAttachmentChild(int child_id) { m_attachment_child_ids.insert(child_id); } void GenericCAO::removeAttachmentChild(int child_id) { m_attachment_child_ids.erase(child_id); } ClientActiveObject* GenericCAO::getParent() const { return m_attachment_parent_id ? m_env->getActiveObject(m_attachment_parent_id) : nullptr; } void GenericCAO::removeFromScene(bool permanent) { // Should be true when removing the object permanently // and false when refreshing (eg: updating visuals) if (m_env && permanent) { // The client does not know whether this object does re-appear to // a later time, thus do not clear child attachments. clearParentAttachment(); } if (auto shadow = RenderingEngine::get_shadow_renderer()) shadow->removeNodeFromShadowList(getSceneNode()); if (m_meshnode) { m_meshnode->remove(); m_meshnode->drop(); m_meshnode = nullptr; } else if (m_animated_meshnode) { m_animated_meshnode->remove(); m_animated_meshnode->drop(); m_animated_meshnode = nullptr; } else if (m_wield_meshnode) { m_wield_meshnode->remove(); m_wield_meshnode->drop(); m_wield_meshnode = nullptr; } else if (m_spritenode) { m_spritenode->remove(); m_spritenode->drop(); m_spritenode = nullptr; } if (m_matrixnode) { m_matrixnode->remove(); m_matrixnode->drop(); m_matrixnode = nullptr; } if (m_nametag) { m_client->getCamera()->removeNametag(m_nametag); m_nametag = nullptr; } if (m_marker && m_client->getMinimap()) m_client->getMinimap()->removeMarker(&m_marker); } void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) { m_smgr = smgr; if (getSceneNode() != NULL) { return; } m_visuals_expired = false; if (!m_prop.is_visible) return; infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; if (m_enable_shaders) { IShaderSource *shader_source = m_client->getShaderSource(); MaterialType material_type; if (m_prop.shaded && m_prop.glow == 0) material_type = (m_prop.use_texture_alpha) ? TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC; else material_type = (m_prop.use_texture_alpha) ? TILE_MATERIAL_PLAIN_ALPHA : TILE_MATERIAL_PLAIN; u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL); m_material_type = shader_source->getShaderInfo(shader_id).material; } else { m_material_type = (m_prop.use_texture_alpha) ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; } auto grabMatrixNode = [this] { m_matrixnode = m_smgr->addDummyTransformationSceneNode(); m_matrixnode->grab(); }; auto setSceneNodeMaterial = [this] (scene::ISceneNode *node) { node->setMaterialFlag(video::EMF_LIGHTING, false); node->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); node->setMaterialFlag(video::EMF_FOG_ENABLE, true); node->setMaterialType(m_material_type); if (m_enable_shaders) { node->setMaterialFlag(video::EMF_GOURAUD_SHADING, false); node->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); } }; if (m_prop.visual == "sprite") { grabMatrixNode(); m_spritenode = m_smgr->addBillboardSceneNode( m_matrixnode, v2f(1, 1), v3f(0,0,0), -1); m_spritenode->grab(); m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh("no_texture.png")); setSceneNodeMaterial(m_spritenode); m_spritenode->setSize(v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); { const float txs = 1.0 / 1; const float tys = 1.0 / 1; setBillboardTextureMatrix(m_spritenode, txs, tys, 0, 0); } } else if (m_prop.visual == "upright_sprite") { grabMatrixNode(); scene::SMesh *mesh = new scene::SMesh(); double dx = BS * m_prop.visual_size.X / 2; double dy = BS * m_prop.visual_size.Y / 2; video::SColor c(0xFFFFFFFF); { // Front scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { video::S3DVertex(-dx, -dy, 0, 0,0,1, c, 1,1), video::S3DVertex( dx, -dy, 0, 0,0,1, c, 0,1), video::S3DVertex( dx, dy, 0, 0,0,1, c, 0,0), video::S3DVertex(-dx, dy, 0, 0,0,1, c, 1,0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) for (video::S3DVertex &vertex : vertices) vertex.Pos.Y += dy; } u16 indices[] = {0,1,2,2,3,0}; buf->append(vertices, 4, indices, 6); // Set material buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); buf->getMaterial().MaterialType = m_material_type; if (m_enable_shaders) { buf->getMaterial().EmissiveColor = c; buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); } // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); } { // Back scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { video::S3DVertex( dx,-dy, 0, 0,0,-1, c, 1,1), video::S3DVertex(-dx,-dy, 0, 0,0,-1, c, 0,1), video::S3DVertex(-dx, dy, 0, 0,0,-1, c, 0,0), video::S3DVertex( dx, dy, 0, 0,0,-1, c, 1,0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) for (video::S3DVertex &vertex : vertices) vertex.Pos.Y += dy; } u16 indices[] = {0,1,2,2,3,0}; buf->append(vertices, 4, indices, 6); // Set material buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); buf->getMaterial().MaterialType = m_material_type; if (m_enable_shaders) { buf->getMaterial().EmissiveColor = c; buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); } // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); } m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); } else if (m_prop.visual == "cube") { grabMatrixNode(); scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); m_meshnode->setScale(m_prop.visual_size); m_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); setSceneNodeMaterial(m_meshnode); } else if (m_prop.visual == "mesh") { grabMatrixNode(); scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); if (mesh) { if (!checkMeshNormals(mesh)) { infostream << "GenericCAO: recalculating normals for mesh " << m_prop.mesh << std::endl; m_smgr->getMeshManipulator()-> recalculateNormals(mesh, true, false); } m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode); m_animated_meshnode->grab(); mesh->drop(); // The scene node took hold of it m_animated_meshnode->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); // set vertex colors to ensure alpha is set setMeshColor(m_animated_meshnode->getMesh(), video::SColor(0xFFFFFFFF)); setAnimatedMeshColor(m_animated_meshnode, video::SColor(0xFFFFFFFF)); setSceneNodeMaterial(m_animated_meshnode); m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); } else errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl; } else if (m_prop.visual == "wielditem" || m_prop.visual == "item") { grabMatrixNode(); ItemStack item; if (m_prop.wield_item.empty()) { // Old format, only textures are specified. infostream << "textures: " << m_prop.textures.size() << std::endl; if (!m_prop.textures.empty()) { infostream << "textures[0]: " << m_prop.textures[0] << std::endl; IItemDefManager *idef = m_client->idef(); item = ItemStack(m_prop.textures[0], 1, 0, idef); } } else { infostream << "serialized form: " << m_prop.wield_item << std::endl; item.deSerialize(m_prop.wield_item, m_client->idef()); } m_wield_meshnode = new WieldMeshSceneNode(m_smgr, -1); m_wield_meshnode->setItem(item, m_client, (m_prop.visual == "wielditem")); m_wield_meshnode->setScale(m_prop.visual_size / 2.0f); m_wield_meshnode->setColor(video::SColor(0xFFFFFFFF)); } else { infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual <<"\" not supported"<<std::endl; } /* don't update while punch texture modifier is active */ if (m_reset_textures_timer < 0) updateTextures(m_current_texture_modifier); if (scene::ISceneNode *node = getSceneNode()) { if (m_matrixnode) node->setParent(m_matrixnode); if (auto shadow = RenderingEngine::get_shadow_renderer()) shadow->addNodeToShadowList(node); } updateNametag(); updateMarker(); updateNodePos(); updateAnimation(); updateBonePosition(); updateAttachments(); setNodeLight(m_last_light); updateMeshCulling(); if (m_animated_meshnode) { u32 mat_count = m_animated_meshnode->getMaterialCount(); if (mat_count == 0 || m_prop.textures.empty()) { // nothing } else if (mat_count > m_prop.textures.size()) { std::ostringstream oss; oss << "GenericCAO::addToScene(): Model " << m_prop.mesh << " loaded with " << mat_count << " mesh buffers but only " << m_prop.textures.size() << " texture(s) specifed, this is deprecated."; logOnce(oss, warningstream); video::ITexture *last = m_animated_meshnode->getMaterial(0).TextureLayer[0].Texture; for (u32 i = 1; i < mat_count; i++) { auto &layer = m_animated_meshnode->getMaterial(i).TextureLayer[0]; if (!layer.Texture) layer.Texture = last; last = layer.Texture; } } } } void GenericCAO::updateLight(u32 day_night_ratio) { if (m_prop.glow < 0) return; u16 light_at_pos = 0; u8 light_at_pos_intensity = 0; bool pos_ok = false; v3s16 pos[3]; u16 npos = getLightPosition(pos); for (u16 i = 0; i < npos; i++) { bool this_ok; MapNode n = m_env->getMap().getNode(pos[i], &this_ok); if (this_ok) { u16 this_light = getInteriorLight(n, 0, m_client->ndef()); u8 this_light_intensity = MYMAX(this_light & 0xFF, this_light >> 8); if (this_light_intensity > light_at_pos_intensity) { light_at_pos = this_light; light_at_pos_intensity = this_light_intensity; } pos_ok = true; } } if (!pos_ok) light_at_pos = LIGHT_SUN; video::SColor light = encode_light(light_at_pos, m_prop.glow); if (!m_enable_shaders) final_color_blend(&light, light_at_pos, day_night_ratio); if (light != m_last_light) { m_last_light = light; setNodeLight(light); } } void GenericCAO::setNodeLight(const video::SColor &light_color) { if (m_prop.visual == "wielditem" || m_prop.visual == "item") { if (m_wield_meshnode) m_wield_meshnode->setNodeLightColor(light_color); return; } if (m_enable_shaders) { if (m_prop.visual == "upright_sprite") { if (!m_meshnode) return; for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) m_meshnode->getMaterial(i).EmissiveColor = light_color; } else { scene::ISceneNode *node = getSceneNode(); if (!node) return; for (u32 i = 0; i < node->getMaterialCount(); ++i) { video::SMaterial &material = node->getMaterial(i); material.EmissiveColor = light_color; } } } else { if (m_meshnode) { setMeshColor(m_meshnode->getMesh(), light_color); } else if (m_animated_meshnode) { setAnimatedMeshColor(m_animated_meshnode, light_color); } else if (m_spritenode) { m_spritenode->setColor(light_color); } } } u16 GenericCAO::getLightPosition(v3s16 *pos) { const auto &box = m_prop.collisionbox; pos[0] = floatToInt(m_position + box.MinEdge * BS, BS); pos[1] = floatToInt(m_position + box.MaxEdge * BS, BS); // Skip center pos if it falls into the same node as Min or MaxEdge if ((box.MaxEdge - box.MinEdge).getLengthSQ() < 3.0f) return 2; pos[2] = floatToInt(m_position + box.getCenter() * BS, BS); return 3; } void GenericCAO::updateMarker() { if (!m_client->getMinimap()) return; if (!m_prop.show_on_minimap) { if (m_marker) m_client->getMinimap()->removeMarker(&m_marker); return; } if (m_marker) return; scene::ISceneNode *node = getSceneNode(); if (!node) return; m_marker = m_client->getMinimap()->addMarker(node); } void GenericCAO::updateNametag() { if (m_is_local_player) // No nametag for local player return; if (m_prop.nametag.empty() || m_prop.nametag_color.getAlpha() == 0) { // Delete nametag if (m_nametag) { m_client->getCamera()->removeNametag(m_nametag); m_nametag = nullptr; } return; } scene::ISceneNode *node = getSceneNode(); if (!node) return; v3f pos; pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f; if (!m_nametag) { // Add nametag m_nametag = m_client->getCamera()->addNametag(node, m_prop.nametag, m_prop.nametag_color, m_prop.nametag_bgcolor, pos); } else { // Update nametag m_nametag->text = m_prop.nametag; m_nametag->textcolor = m_prop.nametag_color; m_nametag->bgcolor = m_prop.nametag_bgcolor; m_nametag->pos = pos; } } void GenericCAO::updateNodePos() { if (getParent() != NULL) return; scene::ISceneNode *node = getSceneNode(); if (node) { v3s16 camera_offset = m_env->getCameraOffset(); v3f pos = pos_translator.val_current - intToFloat(camera_offset, BS); getPosRotMatrix().setTranslation(pos); if (node != m_spritenode) { // rotate if not a sprite v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current; setPitchYawRoll(getPosRotMatrix(), rot); } } } void GenericCAO::step(float dtime, ClientEnvironment *env) { // Handle model animations and update positions instantly to prevent lags if (m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); m_position = player->getPosition(); pos_translator.val_current = m_position; m_rotation.Y = wrapDegrees_0_360(player->getYaw()); rot_translator.val_current = m_rotation; if (m_is_visible) { int old_anim = player->last_animation; float old_anim_speed = player->last_animation_speed; m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); const PlayerControl &controls = player->getPlayerControl(); f32 new_speed = player->local_animation_speed; bool walking = false; if (controls.movement_speed > 0.001f) { new_speed *= controls.movement_speed; walking = true; } v2s32 new_anim = v2s32(0,0); bool allow_update = false; // increase speed if using fast or flying fast if((g_settings->getBool("fast_move") && m_client->checkLocalPrivilege("fast")) && (controls.aux1 || (!player->touching_ground && g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")))) new_speed *= 1.5; // slowdown speed if sneaking if (controls.sneak && walking) new_speed /= 2; if (walking && (controls.dig || controls.place)) { new_anim = player->local_animations[3]; player->last_animation = WD_ANIM; } else if (walking) { new_anim = player->local_animations[1]; player->last_animation = WALK_ANIM; } else if (controls.dig || controls.place) { new_anim = player->local_animations[2]; player->last_animation = DIG_ANIM; } // Apply animations if input detected and not attached // or set idle animation if ((new_anim.X + new_anim.Y) > 0 && !getParent()) { allow_update = true; m_animation_range = new_anim; m_animation_speed = new_speed; player->last_animation_speed = m_animation_speed; } else { player->last_animation = NO_ANIM; if (old_anim != NO_ANIM) { m_animation_range = player->local_animations[0]; updateAnimation(); } } // Update local player animations if ((player->last_animation != old_anim || m_animation_speed != old_anim_speed) && player->last_animation != NO_ANIM && allow_update) updateAnimation(); } } if (m_visuals_expired && m_smgr) { m_visuals_expired = false; // Attachments, part 1: All attached objects must be unparented first, // or Irrlicht causes a segmentation fault for (u16 cao_id : m_attachment_child_ids) { ClientActiveObject *obj = m_env->getActiveObject(cao_id); if (obj) { scene::ISceneNode *child_node = obj->getSceneNode(); // The node's parent is always an IDummyTraformationSceneNode, // so we need to reparent that one instead. if (child_node) child_node->getParent()->setParent(m_smgr->getRootSceneNode()); } } removeFromScene(false); addToScene(m_client->tsrc(), m_smgr); // Attachments, part 2: Now that the parent has been refreshed, put its attachments back for (u16 cao_id : m_attachment_child_ids) { ClientActiveObject *obj = m_env->getActiveObject(cao_id); if (obj) obj->updateAttachments(); } } // Make sure m_is_visible is always applied scene::ISceneNode *node = getSceneNode(); if (node) node->setVisible(m_is_visible); if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht { // Set these for later m_position = getPosition(); m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); pos_translator.val_current = m_position; pos_translator.val_target = m_position; } else { rot_translator.translate(dtime); v3f lastpos = pos_translator.val_current; if(m_prop.physical) { aabb3f box = m_prop.collisionbox; box.MinEdge *= BS; box.MaxEdge *= BS; collisionMoveResult moveresult; f32 pos_max_d = BS*0.125; // Distance per iteration v3f p_pos = m_position; v3f p_velocity = m_velocity; moveresult = collisionMoveSimple(env,env->getGameDef(), pos_max_d, box, m_prop.stepheight, dtime, &p_pos, &p_velocity, m_acceleration, this, m_prop.collideWithObjects); // Apply results m_position = p_pos; m_velocity = p_velocity; bool is_end_position = moveresult.collides; pos_translator.update(m_position, is_end_position, dtime); } else { m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; m_velocity += dtime * m_acceleration; pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); } pos_translator.translate(dtime); updateNodePos(); float moved = lastpos.getDistanceFrom(pos_translator.val_current); m_step_distance_counter += moved; if (m_step_distance_counter > 1.5f * BS) { m_step_distance_counter = 0.0f; if (!m_is_local_player && m_prop.makes_footstep_sound) { const NodeDefManager *ndef = m_client->ndef(); v3s16 p = floatToInt(getPosition() + v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS); MapNode n = m_env->getMap().getNode(p); SimpleSoundSpec spec = ndef->get(n).sound_footstep; // Reduce footstep gain, as non-local-player footsteps are // somehow louder. spec.gain *= 0.6f; m_client->sound()->playSoundAt(spec, getPosition()); } } } m_anim_timer += dtime; if(m_anim_timer >= m_anim_framelength) { m_anim_timer -= m_anim_framelength; m_anim_frame++; if(m_anim_frame >= m_anim_num_frames) m_anim_frame = 0; } updateTexturePos(); if(m_reset_textures_timer >= 0) { m_reset_textures_timer -= dtime; if(m_reset_textures_timer <= 0) { m_reset_textures_timer = -1; updateTextures(m_previous_texture_modifier); } } if (node && fabs(m_prop.automatic_rotate) > 0.001f) { // This is the child node's rotation. It is only used for automatic_rotate. v3f local_rot = node->getRotation(); local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG * m_prop.automatic_rotate); node->setRotation(local_rot); } if (!getParent() && m_prop.automatic_face_movement_dir && (fabs(m_velocity.Z) > 0.001f || fabs(m_velocity.X) > 0.001f)) { float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI + m_prop.automatic_face_movement_dir_offset; float max_rotation_per_sec = m_prop.automatic_face_movement_max_rotation_per_sec; if (max_rotation_per_sec > 0) { wrappedApproachShortest(m_rotation.Y, target_yaw, dtime * max_rotation_per_sec, 360.f); } else { // Negative values of max_rotation_per_sec mean disabled. m_rotation.Y = target_yaw; } rot_translator.val_current = m_rotation; updateNodePos(); } if (m_animated_meshnode) { // Everything must be updated; the whole transform // chain as well as the animated mesh node. // Otherwise, bone attachments would be relative to // a position that's one frame old. if (m_matrixnode) updatePositionRecursive(m_matrixnode); m_animated_meshnode->updateAbsolutePosition(); m_animated_meshnode->animateJoints(); updateBonePosition(); } } void GenericCAO::updateTexturePos() { if(m_spritenode) { scene::ICameraSceneNode* camera = m_spritenode->getSceneManager()->getActiveCamera(); if(!camera) return; v3f cam_to_entity = m_spritenode->getAbsolutePosition() - camera->getAbsolutePosition(); cam_to_entity.normalize(); int row = m_tx_basepos.Y; int col = m_tx_basepos.X; // Yawpitch goes rightwards if (m_tx_select_horiz_by_yawpitch) { if (cam_to_entity.Y > 0.75) col += 5; else if (cam_to_entity.Y < -0.75) col += 4; else { float mob_dir = atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.; float dir = mob_dir - m_rotation.Y; dir = wrapDegrees_180(dir); if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f) col += 2; else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f) col += 3; else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f) col += 0; else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f) col += 1; else col += 4; } } // Animation goes downwards row += m_anim_frame; float txs = m_tx_size.X; float tys = m_tx_size.Y; setBillboardTextureMatrix(m_spritenode, txs, tys, col, row); } else if (m_meshnode) { if (m_prop.visual == "upright_sprite") { int row = m_tx_basepos.Y; int col = m_tx_basepos.X; // Animation goes downwards row += m_anim_frame; const auto &tx = m_tx_size; v2f t[4] = { // cf. vertices in GenericCAO::addToScene() tx * v2f(col+1, row+1), tx * v2f(col, row+1), tx * v2f(col, row), tx * v2f(col+1, row), }; auto mesh = m_meshnode->getMesh(); setMeshBufferTextureCoords(mesh->getMeshBuffer(0), t, 4); setMeshBufferTextureCoords(mesh->getMeshBuffer(1), t, 4); } } } // Do not pass by reference, see header. void GenericCAO::updateTextures(std::string mod) { ITextureSource *tsrc = m_client->tsrc(); bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); m_previous_texture_modifier = m_current_texture_modifier; m_current_texture_modifier = mod; if (m_spritenode) { if (m_prop.visual == "sprite") { std::string texturestring = "no_texture.png"; if (!m_prop.textures.empty()) texturestring = m_prop.textures[0]; texturestring += mod; m_spritenode->getMaterial(0).MaterialType = m_material_type; m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f; m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh(texturestring)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if (!m_prop.colors.empty()) { m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0]; m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0]; m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0]; } m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } } else if (m_animated_meshnode) { if (m_prop.visual == "mesh") { for (u32 i = 0; i < m_prop.textures.size() && i < m_animated_meshnode->getMaterialCount(); ++i) { std::string texturestring = m_prop.textures[i]; if (texturestring.empty()) continue; // Empty texture string means don't modify that material texturestring += mod; video::ITexture* texture = tsrc->getTextureForMesh(texturestring); if (!texture) { errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl; continue; } // Set material flags and texture video::SMaterial& material = m_animated_meshnode->getMaterial(i); material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.TextureLayer[0].Texture = texture; material.setFlag(video::EMF_LIGHTING, true); material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); // don't filter low-res textures, makes them look blurry // player models have a res of 64 const core::dimension2d<u32> &size = texture->getOriginalSize(); const u32 res = std::min(size.Height, size.Width); use_trilinear_filter &= res > 64; use_bilinear_filter &= res > 64; m_animated_meshnode->getMaterial(i) .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); m_animated_meshnode->getMaterial(i) .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); m_animated_meshnode->getMaterial(i) .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } for (u32 i = 0; i < m_prop.colors.size() && i < m_animated_meshnode->getMaterialCount(); ++i) { // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; } } } else if (m_meshnode) { if(m_prop.visual == "cube") { for (u32 i = 0; i < 6; ++i) { std::string texturestring = "no_texture.png"; if(m_prop.textures.size() > i) texturestring = m_prop.textures[i]; texturestring += mod; // Set material flags and texture video::SMaterial& material = m_meshnode->getMaterial(i); material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setTexture(0, tsrc->getTextureForMesh(texturestring)); material.getTextureMatrix(0).makeIdentity(); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if(m_prop.colors.size() > i) { m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; } m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } } else if (m_prop.visual == "upright_sprite") { scene::IMesh *mesh = m_meshnode->getMesh(); { std::string tname = "no_texture.png"; if (!m_prop.textures.empty()) tname = m_prop.textures[0]; tname += mod; auto& material = m_meshnode->getMaterial(0); material.setTexture(0, tsrc->getTextureForMesh(tname)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if(!m_prop.colors.empty()) { material.AmbientColor = m_prop.colors[0]; material.DiffuseColor = m_prop.colors[0]; material.SpecularColor = m_prop.colors[0]; } material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } { std::string tname = "no_texture.png"; if (m_prop.textures.size() >= 2) tname = m_prop.textures[1]; else if (!m_prop.textures.empty()) tname = m_prop.textures[0]; tname += mod; auto& material = m_meshnode->getMaterial(1); material.setTexture(0, tsrc->getTextureForMesh(tname)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if (m_prop.colors.size() >= 2) { material.AmbientColor = m_prop.colors[1]; material.DiffuseColor = m_prop.colors[1]; material.SpecularColor = m_prop.colors[1]; } else if (!m_prop.colors.empty()) { material.AmbientColor = m_prop.colors[0]; material.DiffuseColor = m_prop.colors[0]; material.SpecularColor = m_prop.colors[0]; } material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } // Set mesh color (only if lighting is disabled) if (!m_prop.colors.empty() && m_prop.glow < 0) setMeshColor(mesh, m_prop.colors[0]); } } // Prevent showing the player after changing texture if (m_is_local_player) updateMeshCulling(); } void GenericCAO::updateAnimation() { if (!m_animated_meshnode) return; if (m_animated_meshnode->getStartFrame() != m_animation_range.X || m_animated_meshnode->getEndFrame() != m_animation_range.Y) m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed) m_animated_meshnode->setAnimationSpeed(m_animation_speed); m_animated_meshnode->setTransitionTime(m_animation_blend); if (m_animated_meshnode->getLoopMode() != m_animation_loop) m_animated_meshnode->setLoopMode(m_animation_loop); } void GenericCAO::updateAnimationSpeed() { if (!m_animated_meshnode) return; m_animated_meshnode->setAnimationSpeed(m_animation_speed); } void GenericCAO::updateBonePosition() { if (m_bone_position.empty() || !m_animated_meshnode) return; m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render for (auto &it : m_bone_position) { std::string bone_name = it.first; scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); if (bone) { bone->setPosition(it.second.X); bone->setRotation(it.second.Y); } } // search through bones to find mistakenly rotated bones due to bug in Irrlicht for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) { scene::IBoneSceneNode *bone = m_animated_meshnode->getJointNode(i); if (!bone) continue; //If bone is manually positioned there is no need to perform the bug check bool skip = false; for (auto &it : m_bone_position) { if (it.first == bone->getName()) { skip = true; break; } } if (skip) continue; // Workaround for Irrlicht bug // We check each bone to see if it has been rotated ~180deg from its expected position due to a bug in Irricht // when using EJUOR_CONTROL joint control. If the bug is detected we update the bone to the proper position // and update the bones transformation. v3f bone_rot = bone->getRelativeTransformation().getRotationDegrees(); float offset = fabsf(bone_rot.X - bone->getRotation().X); if (offset > 179.9f && offset < 180.1f) { bone->setRotation(bone_rot); bone->updateAbsolutePosition(); } } // The following is needed for set_bone_pos to propagate to // attached objects correctly. // Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL. for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) { auto bone = m_animated_meshnode->getJointNode(i); // Look for the root bone. if (bone && bone->getParent() == m_animated_meshnode) { // Update entire skeleton. bone->updateAbsolutePositionOfAllChildren(); break; } } } void GenericCAO::updateAttachments() { ClientActiveObject *parent = getParent(); m_attached_to_local = parent && parent->isLocalPlayer(); /* Following cases exist: m_attachment_parent_id == 0 && !parent This object is not attached m_attachment_parent_id != 0 && parent This object is attached m_attachment_parent_id != 0 && !parent This object will be attached as soon the parent is known m_attachment_parent_id == 0 && parent Impossible case */ if (!parent) { // Detach or don't attach if (m_matrixnode) { v3s16 camera_offset = m_env->getCameraOffset(); v3f old_pos = getPosition(); m_matrixnode->setParent(m_smgr->getRootSceneNode()); getPosRotMatrix().setTranslation(old_pos - intToFloat(camera_offset, BS)); m_matrixnode->updateAbsolutePosition(); } } else // Attach { parent->updateAttachments(); scene::ISceneNode *parent_node = parent->getSceneNode(); scene::IAnimatedMeshSceneNode *parent_animated_mesh_node = parent->getAnimatedMeshSceneNode(); if (parent_animated_mesh_node && !m_attachment_bone.empty()) { parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str()); } if (m_matrixnode && parent_node) { m_matrixnode->setParent(parent_node); parent_node->updateAbsolutePosition(); getPosRotMatrix().setTranslation(m_attachment_position); //setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation); // use Irrlicht eulers instead getPosRotMatrix().setRotationDegrees(m_attachment_rotation); m_matrixnode->updateAbsolutePosition(); } } } bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const { const ObjectProperties &old = m_prop; /* Visuals do not need to be expired for: * - nametag props: handled by updateNametag() * - textures: handled by updateTextures() * - sprite props: handled by updateTexturePos() * - glow: handled by updateLight() * - any other properties that do not change appearance */ bool uses_legacy_texture = new_.wield_item.empty() && (new_.visual == "wielditem" || new_.visual == "item"); // Ordered to compare primitive types before std::vectors return old.backface_culling != new_.backface_culling || old.is_visible != new_.is_visible || old.mesh != new_.mesh || old.shaded != new_.shaded || old.use_texture_alpha != new_.use_texture_alpha || old.visual != new_.visual || old.visual_size != new_.visual_size || old.wield_item != new_.wield_item || old.colors != new_.colors || (uses_legacy_texture && old.textures != new_.textures); } void GenericCAO::processMessage(const std::string &data) { //infostream<<"GenericCAO: Got message"<<std::endl; std::istringstream is(data, std::ios::binary); // command u8 cmd = readU8(is); if (cmd == AO_CMD_SET_PROPERTIES) { ObjectProperties newprops; newprops.show_on_minimap = m_is_player; // default newprops.deSerialize(is); // Check what exactly changed bool expire_visuals = visualExpiryRequired(newprops); bool textures_changed = m_prop.textures != newprops.textures; // Apply changes m_prop = std::move(newprops); m_selection_box = m_prop.selectionbox; m_selection_box.MinEdge *= BS; m_selection_box.MaxEdge *= BS; m_tx_size.X = 1.0f / m_prop.spritediv.X; m_tx_size.Y = 1.0f / m_prop.spritediv.Y; if(!m_initial_tx_basepos_set){ m_initial_tx_basepos_set = true; m_tx_basepos = m_prop.initial_sprite_basepos; } if (m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); player->makes_footstep_sound = m_prop.makes_footstep_sound; aabb3f collision_box = m_prop.collisionbox; collision_box.MinEdge *= BS; collision_box.MaxEdge *= BS; player->setCollisionbox(collision_box); player->setEyeHeight(m_prop.eye_height); player->setZoomFOV(m_prop.zoom_fov); } if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) m_prop.nametag = m_name; if (m_is_local_player) m_prop.show_on_minimap = false; if (expire_visuals) { expireVisuals(); } else { infostream << "GenericCAO: properties updated but expiring visuals" << " not necessary" << std::endl; if (textures_changed) { // don't update while punch texture modifier is active if (m_reset_textures_timer < 0) updateTextures(m_current_texture_modifier); } updateNametag(); updateMarker(); } } else if (cmd == AO_CMD_UPDATE_POSITION) { // Not sent by the server if this object is an attachment. // We might however get here if the server notices the object being detached before the client. m_position = readV3F32(is); m_velocity = readV3F32(is); m_acceleration = readV3F32(is); m_rotation = readV3F32(is); m_rotation = wrapDegrees_0_360_v3f(m_rotation); bool do_interpolate = readU8(is); bool is_end_position = readU8(is); float update_interval = readF32(is); // Place us a bit higher if we're physical, to not sink into // the ground due to sucky collision detection... if(m_prop.physical) m_position += v3f(0,0.002,0); if(getParent() != NULL) // Just in case return; if(do_interpolate) { if(!m_prop.physical) pos_translator.update(m_position, is_end_position, update_interval); } else { pos_translator.init(m_position); } rot_translator.update(m_rotation, false, update_interval); updateNodePos(); } else if (cmd == AO_CMD_SET_TEXTURE_MOD) { std::string mod = deSerializeString16(is); // immediately reset a engine issued texture modifier if a mod sends a different one if (m_reset_textures_timer > 0) { m_reset_textures_timer = -1; updateTextures(m_previous_texture_modifier); } updateTextures(mod); } else if (cmd == AO_CMD_SET_SPRITE) { v2s16 p = readV2S16(is); int num_frames = readU16(is); float framelength = readF32(is); bool select_horiz_by_yawpitch = readU8(is); m_tx_basepos = p; m_anim_num_frames = num_frames; m_anim_frame = 0; m_anim_framelength = framelength; m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; updateTexturePos(); } else if (cmd == AO_CMD_SET_PHYSICS_OVERRIDE) { float override_speed = readF32(is); float override_jump = readF32(is); float override_gravity = readF32(is); // these are sent inverted so we get true when the server sends nothing bool sneak = !readU8(is); bool sneak_glitch = !readU8(is); bool new_move = !readU8(is); if(m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); player->physics_override_speed = override_speed; player->physics_override_jump = override_jump; player->physics_override_gravity = override_gravity; player->physics_override_sneak = sneak; player->physics_override_sneak_glitch = sneak_glitch; player->physics_override_new_move = new_move; } } else if (cmd == AO_CMD_SET_ANIMATION) { // TODO: change frames send as v2s32 value v2f range = readV2F32(is); if (!m_is_local_player) { m_animation_range = v2s32((s32)range.X, (s32)range.Y); m_animation_speed = readF32(is); m_animation_blend = readF32(is); // these are sent inverted so we get true when the server sends nothing m_animation_loop = !readU8(is); updateAnimation(); } else { LocalPlayer *player = m_env->getLocalPlayer(); if(player->last_animation == NO_ANIM) { m_animation_range = v2s32((s32)range.X, (s32)range.Y); m_animation_speed = readF32(is); m_animation_blend = readF32(is); // these are sent inverted so we get true when the server sends nothing m_animation_loop = !readU8(is); } // update animation only if local animations present // and received animation is unknown (except idle animation) bool is_known = false; for (int i = 1;i<4;i++) { if(m_animation_range.Y == player->local_animations[i].Y) is_known = true; } if(!is_known || (player->local_animations[1].Y + player->local_animations[2].Y < 1)) { updateAnimation(); } // FIXME: ^ This code is trash. It's also broken. } } else if (cmd == AO_CMD_SET_ANIMATION_SPEED) { m_animation_speed = readF32(is); updateAnimationSpeed(); } else if (cmd == AO_CMD_SET_BONE_POSITION) { std::string bone = deSerializeString16(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); m_bone_position[bone] = core::vector2d<v3f>(position, rotation); // updateBonePosition(); now called every step } else if (cmd == AO_CMD_ATTACH_TO) { u16 parent_id = readS16(is); std::string bone = deSerializeString16(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); bool force_visible = readU8(is); // Returns false for EOF setAttachment(parent_id, bone, position, rotation, force_visible); } else if (cmd == AO_CMD_PUNCHED) { u16 result_hp = readU16(is); // Use this instead of the send damage to not interfere with prediction s32 damage = (s32)m_hp - (s32)result_hp; m_hp = result_hp; if (m_is_local_player) m_env->getLocalPlayer()->hp = m_hp; if (damage > 0) { if (m_hp == 0) { // TODO: Execute defined fast response // As there is no definition, make a smoke puff ClientSimpleObject *simple = createSmokePuff( m_smgr, m_env, m_position, v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); m_env->addSimpleObject(simple); } else if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { m_reset_textures_timer = 0.05; if(damage >= 2) m_reset_textures_timer += 0.05 * damage; // Cap damage overlay to 1 second m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f); updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); } } if (m_hp == 0) { // Same as 'Server::DiePlayer' clearParentAttachment(); // Same as 'ObjectRef::l_remove' if (!m_is_player) clearChildAttachments(); } } else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) { m_armor_groups.clear(); int armor_groups_size = readU16(is); for(int i=0; i<armor_groups_size; i++) { std::string name = deSerializeString16(is); int rating = readS16(is); m_armor_groups[name] = rating; } } else if (cmd == AO_CMD_SPAWN_INFANT) { u16 child_id = readU16(is); u8 type = readU8(is); // maybe this will be useful later (void)type; addAttachmentChild(child_id); } else if (cmd == AO_CMD_OBSOLETE1) { // Don't do anything and also don't log a warning } else { warningstream << FUNCTION_NAME << ": unknown command or outdated client \"" << +cmd << "\"" << std::endl; } } /* \pre punchitem != NULL */ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, float time_from_last_punch) { assert(punchitem); // pre-condition const ToolCapabilities *toolcap = &punchitem->getToolCapabilities(m_client->idef()); PunchDamageResult result = getPunchDamage( m_armor_groups, toolcap, punchitem, time_from_last_punch, punchitem->wear); if(result.did_punch && result.damage != 0) { if(result.damage < m_hp) { m_hp -= result.damage; } else { m_hp = 0; // TODO: Execute defined fast response // As there is no definition, make a smoke puff ClientSimpleObject *simple = createSmokePuff( m_smgr, m_env, m_position, v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); m_env->addSimpleObject(simple); } if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { m_reset_textures_timer = 0.05; if (result.damage >= 2) m_reset_textures_timer += 0.05 * result.damage; // Cap damage overlay to 1 second m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f); updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); } } return false; } std::string GenericCAO::debugInfoText() { std::ostringstream os(std::ios::binary); os<<"GenericCAO hp="<<m_hp<<"\n"; os<<"armor={"; for(ItemGroupList::const_iterator i = m_armor_groups.begin(); i != m_armor_groups.end(); ++i) { os<<i->first<<"="<<i->second<<", "; } os<<"}"; return os.str(); } void GenericCAO::updateMeshCulling() { if (!m_is_local_player) return; const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST; scene::ISceneNode *node = getSceneNode(); if (!node) return; if (m_prop.visual == "upright_sprite") { // upright sprite has no backface culling node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, hidden); return; } if (hidden) { // Hide the mesh by culling both front and // back faces. Serious hackyness but it works for our // purposes. This also preserves the skeletal armature. node->setMaterialFlag(video::EMF_BACK_FACE_CULLING, true); node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, true); } else { // Restore mesh visibility. node->setMaterialFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, false); } } // Prototype GenericCAO proto_GenericCAO(NULL, NULL);