]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/content_cao.cpp
Add spider
[dragonfireclient.git] / src / client / content_cao.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "content_cao.h"
21 #include <IBillboardSceneNode.h>
22 #include <ICameraSceneNode.h>
23 #include <IMeshManipulator.h>
24 #include <IAnimatedMeshSceneNode.h>
25 #include "client/client.h"
26 #include "client/renderingengine.h"
27 #include "client/sound.h"
28 #include "client/tile.h"
29 #include "util/basic_macros.h"
30 #include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll
31 #include "util/serialize.h"
32 #include "camera.h" // CameraModes
33 #include "collision.h"
34 #include "content_cso.h"
35 #include "environment.h"
36 #include "itemdef.h"
37 #include "localplayer.h"
38 #include "map.h"
39 #include "mesh.h"
40 #include "nodedef.h"
41 #include "serialization.h" // For decompressZlib
42 #include "settings.h"
43 #include "sound.h"
44 #include "tool.h"
45 #include "wieldmesh.h"
46 #include <algorithm>
47 #include <cmath>
48 #include "client/shader.h"
49 #include "script/scripting_client.h"
50 #include "client/minimap.h"
51
52 class Settings;
53 struct ToolCapabilities;
54
55 std::unordered_map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types;
56
57 template<typename T>
58 void SmoothTranslator<T>::init(T current)
59 {
60         val_old = current;
61         val_current = current;
62         val_target = current;
63         anim_time = 0;
64         anim_time_counter = 0;
65         aim_is_end = true;
66 }
67
68 template<typename T>
69 void SmoothTranslator<T>::update(T new_target, bool is_end_position, float update_interval)
70 {
71         aim_is_end = is_end_position;
72         val_old = val_current;
73         val_target = new_target;
74         if (update_interval > 0) {
75                 anim_time = update_interval;
76         } else {
77                 if (anim_time < 0.001 || anim_time > 1.0)
78                         anim_time = anim_time_counter;
79                 else
80                         anim_time = anim_time * 0.9 + anim_time_counter * 0.1;
81         }
82         anim_time_counter = 0;
83 }
84
85 template<typename T>
86 void SmoothTranslator<T>::translate(f32 dtime)
87 {
88         anim_time_counter = anim_time_counter + dtime;
89         T val_diff = val_target - val_old;
90         f32 moveratio = 1.0;
91         if (anim_time > 0.001)
92                 moveratio = anim_time_counter / anim_time;
93         f32 move_end = aim_is_end ? 1.0 : 1.5;
94
95         // Move a bit less than should, to avoid oscillation
96         moveratio = std::min(moveratio * 0.8f, move_end);
97         val_current = val_old + val_diff * moveratio;
98 }
99
100 void SmoothTranslatorWrapped::translate(f32 dtime)
101 {
102         anim_time_counter = anim_time_counter + dtime;
103         f32 val_diff = std::abs(val_target - val_old);
104         if (val_diff > 180.f)
105                 val_diff = 360.f - val_diff;
106
107         f32 moveratio = 1.0;
108         if (anim_time > 0.001)
109                 moveratio = anim_time_counter / anim_time;
110         f32 move_end = aim_is_end ? 1.0 : 1.5;
111
112         // Move a bit less than should, to avoid oscillation
113         moveratio = std::min(moveratio * 0.8f, move_end);
114         wrappedApproachShortest(val_current, val_target,
115                 val_diff * moveratio, 360.f);
116 }
117
118 void SmoothTranslatorWrappedv3f::translate(f32 dtime)
119 {
120         anim_time_counter = anim_time_counter + dtime;
121
122         v3f val_diff_v3f;
123         val_diff_v3f.X = std::abs(val_target.X - val_old.X);
124         val_diff_v3f.Y = std::abs(val_target.Y - val_old.Y);
125         val_diff_v3f.Z = std::abs(val_target.Z - val_old.Z);
126
127         if (val_diff_v3f.X > 180.f)
128                 val_diff_v3f.X = 360.f - val_diff_v3f.X;
129
130         if (val_diff_v3f.Y > 180.f)
131                 val_diff_v3f.Y = 360.f - val_diff_v3f.Y;
132
133         if (val_diff_v3f.Z > 180.f)
134                 val_diff_v3f.Z = 360.f - val_diff_v3f.Z;
135
136         f32 moveratio = 1.0;
137         if (anim_time > 0.001)
138                 moveratio = anim_time_counter / anim_time;
139         f32 move_end = aim_is_end ? 1.0 : 1.5;
140
141         // Move a bit less than should, to avoid oscillation
142         moveratio = std::min(moveratio * 0.8f, move_end);
143         wrappedApproachShortest(val_current.X, val_target.X,
144                 val_diff_v3f.X * moveratio, 360.f);
145
146         wrappedApproachShortest(val_current.Y, val_target.Y,
147                 val_diff_v3f.Y * moveratio, 360.f);
148
149         wrappedApproachShortest(val_current.Z, val_target.Z,
150                 val_diff_v3f.Z * moveratio, 360.f);
151 }
152
153 /*
154         Other stuff
155 */
156
157 static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
158                 float txs, float tys, int col, int row)
159 {
160         video::SMaterial& material = bill->getMaterial(0);
161         core::matrix4& matrix = material.getTextureMatrix(0);
162         matrix.setTextureTranslate(txs*col, tys*row);
163         matrix.setTextureScale(txs, tys);
164 }
165
166 // Evaluate transform chain recursively; irrlicht does not do this for us
167 static void updatePositionRecursive(scene::ISceneNode *node)
168 {
169         scene::ISceneNode *parent = node->getParent();
170         if (parent)
171                 updatePositionRecursive(parent);
172         node->updateAbsolutePosition();
173 }
174
175 /*
176         TestCAO
177 */
178
179 class TestCAO : public ClientActiveObject
180 {
181 public:
182         TestCAO(Client *client, ClientEnvironment *env);
183         virtual ~TestCAO() = default;
184
185         ActiveObjectType getType() const
186         {
187                 return ACTIVEOBJECT_TYPE_TEST;
188         }
189
190         static ClientActiveObject* create(Client *client, ClientEnvironment *env);
191
192         void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr);
193         void removeFromScene(bool permanent);
194         void updateLight(u32 day_night_ratio);
195         void updateNodePos();
196
197         void step(float dtime, ClientEnvironment *env);
198
199         void processMessage(const std::string &data);
200
201         bool getCollisionBox(aabb3f *toset) const { return false; }
202 private:
203         scene::IMeshSceneNode *m_node;
204         v3f m_position;
205 };
206
207 // Prototype
208 TestCAO proto_TestCAO(NULL, NULL);
209
210 TestCAO::TestCAO(Client *client, ClientEnvironment *env):
211         ClientActiveObject(0, client, env),
212         m_node(NULL),
213         m_position(v3f(0,10*BS,0))
214 {
215         ClientActiveObject::registerType(getType(), create);
216 }
217
218 ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env)
219 {
220         return new TestCAO(client, env);
221 }
222
223 void TestCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
224 {
225         if(m_node != NULL)
226                 return;
227
228         //video::IVideoDriver* driver = smgr->getVideoDriver();
229
230         scene::SMesh *mesh = new scene::SMesh();
231         scene::IMeshBuffer *buf = new scene::SMeshBuffer();
232         video::SColor c(255,255,255,255);
233         video::S3DVertex vertices[4] =
234         {
235                 video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1),
236                 video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1),
237                 video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0),
238                 video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0),
239         };
240         u16 indices[] = {0,1,2,2,3,0};
241         buf->append(vertices, 4, indices, 6);
242         // Set material
243         buf->getMaterial().setFlag(video::EMF_LIGHTING, true);  // false
244         buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
245         buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png"));
246         buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
247         buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
248         buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
249         // Add to mesh
250         mesh->addMeshBuffer(buf);
251         buf->drop();
252         m_node = smgr->addMeshSceneNode(mesh, NULL);
253         mesh->drop();
254         updateNodePos();
255 }
256
257 void TestCAO::removeFromScene(bool permanent)
258 {
259         if (!m_node)
260                 return;
261
262         m_node->remove();
263         m_node = NULL;
264 }
265
266 void TestCAO::updateLight(u32 day_night_ratio)
267 {
268 }
269
270 void TestCAO::updateNodePos()
271 {
272         if (!m_node)
273                 return;
274
275         m_node->setPosition(m_position);
276         //m_node->setRotation(v3f(0, 45, 0));
277 }
278
279 void TestCAO::step(float dtime, ClientEnvironment *env)
280 {
281         if(m_node)
282         {
283                 v3f rot = m_node->getRotation();
284                 //infostream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
285                 rot.Y += dtime * 180;
286                 m_node->setRotation(rot);
287         }
288 }
289
290 void TestCAO::processMessage(const std::string &data)
291 {
292         infostream<<"TestCAO: Got data: "<<data<<std::endl;
293         std::istringstream is(data, std::ios::binary);
294         u16 cmd;
295         is>>cmd;
296         if(cmd == 0)
297         {
298                 v3f newpos;
299                 is>>newpos.X;
300                 is>>newpos.Y;
301                 is>>newpos.Z;
302                 m_position = newpos;
303                 updateNodePos();
304         }
305 }
306
307 /*
308         GenericCAO
309 */
310
311 #include "clientobject.h"
312
313 GenericCAO::GenericCAO(Client *client, ClientEnvironment *env):
314                 ClientActiveObject(0, client, env)
315 {
316         if (client == NULL) {
317                 ClientActiveObject::registerType(getType(), create);
318         } else {
319                 m_client = client;
320         }
321 }
322
323 bool GenericCAO::getCollisionBox(aabb3f *toset) const
324 {
325         if (m_prop.physical)
326         {
327                 //update collision box
328                 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
329                 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
330
331                 toset->MinEdge += m_position;
332                 toset->MaxEdge += m_position;
333
334                 return true;
335         }
336
337         return false;
338 }
339
340 bool GenericCAO::collideWithObjects() const
341 {
342         return m_prop.collideWithObjects;
343 }
344
345 void GenericCAO::initialize(const std::string &data)
346 {
347         infostream<<"GenericCAO: Got init data"<<std::endl;
348         processInitData(data);
349
350         m_enable_shaders = g_settings->getBool("enable_shaders");
351 }
352
353 void GenericCAO::processInitData(const std::string &data)
354 {
355         std::istringstream is(data, std::ios::binary);
356         const u8 version = readU8(is);
357
358         if (version < 1) {
359                 errorstream << "GenericCAO: Unsupported init data version"
360                                 << std::endl;
361                 return;
362         }
363
364         // PROTOCOL_VERSION >= 37
365         m_name = deSerializeString16(is);
366         m_is_player = readU8(is);
367         m_id = readU16(is);
368         m_position = readV3F32(is);
369         m_rotation = readV3F32(is);
370         m_hp = readU16(is);
371
372         if (m_is_player) {
373                 // Check if it's the current player
374                 LocalPlayer *player = m_env->getLocalPlayer();
375                 if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
376                         m_is_local_player = true;
377                         m_is_visible = false;
378                         player->setCAO(this);
379                 }
380         }
381
382         const u8 num_messages = readU8(is);
383
384         for (int i = 0; i < num_messages; i++) {
385                 std::string message = deSerializeString32(is);
386                 processMessage(message);
387         }
388
389         m_rotation = wrapDegrees_0_360_v3f(m_rotation);
390         pos_translator.init(m_position);
391         rot_translator.init(m_rotation);
392         updateNodePos();
393 }
394
395 GenericCAO::~GenericCAO()
396 {
397         removeFromScene(true);
398 }
399
400 bool GenericCAO::getSelectionBox(aabb3f *toset) const
401 {
402         if (!m_prop.is_visible || !m_is_visible || m_is_local_player
403                         || !m_prop.pointable) {
404                 return false;
405         }
406         *toset = m_selection_box;
407         return true;
408 }
409
410 const v3f GenericCAO::getPosition() const
411 {
412         if (!getParent())
413                 return pos_translator.val_current;
414
415         // Calculate real position in world based on MatrixNode
416         if (m_matrixnode) {
417                 v3s16 camera_offset = m_env->getCameraOffset();
418                 return m_matrixnode->getAbsolutePosition() +
419                                 intToFloat(camera_offset, BS);
420         }
421
422         return m_position;
423 }
424
425 const bool GenericCAO::isImmortal()
426 {
427         return itemgroup_get(getGroups(), "immortal");
428 }
429
430 scene::ISceneNode *GenericCAO::getSceneNode() const
431 {
432         if (m_meshnode) {
433                 return m_meshnode;
434         }
435
436         if (m_animated_meshnode) {
437                 return m_animated_meshnode;
438         }
439
440         if (m_wield_meshnode) {
441                 return m_wield_meshnode;
442         }
443
444         if (m_spritenode) {
445                 return m_spritenode;
446         }
447         return NULL;
448 }
449
450 scene::IAnimatedMeshSceneNode *GenericCAO::getAnimatedMeshSceneNode() const
451 {
452         return m_animated_meshnode;
453 }
454
455 void GenericCAO::setChildrenVisible(bool toset)
456 {
457         for (u16 cao_id : m_attachment_child_ids) {
458                 GenericCAO *obj = m_env->getGenericCAO(cao_id);
459                 if (obj) {
460                         // Check if the entity is forced to appear in first person.
461                         obj->setVisible(obj->m_force_visible ? true : toset);
462                 }
463         }
464 }
465
466 void GenericCAO::setAttachment(int parent_id, const std::string &bone,
467                 v3f position, v3f rotation, bool force_visible)
468 {
469         int old_parent = m_attachment_parent_id;
470         m_attachment_parent_id = parent_id;
471         m_attachment_bone = bone;
472         m_attachment_position = position;
473         m_attachment_rotation = rotation;
474         m_force_visible = force_visible;
475
476         ClientActiveObject *parent = m_env->getActiveObject(parent_id);
477
478         if (parent_id != old_parent) {
479                 if (old_parent)
480                         m_waiting_for_reattach = 10;
481                 if (auto *o = m_env->getActiveObject(old_parent))
482                         o->removeAttachmentChild(m_id);
483                 if (parent)
484                         parent->addAttachmentChild(m_id);
485         }
486
487         updateAttachments();
488
489         // Forcibly show attachments if required by set_attach
490         if (m_force_visible) {
491                 m_is_visible = true;
492         } else if (!m_is_local_player) {
493                 // Objects attached to the local player should be hidden in first person
494                 m_is_visible = !m_attached_to_local ||
495                         m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST || g_settings->getBool("freecam");
496                 m_force_visible = false;
497         } else {
498                 // Local players need to have this set,
499                 // otherwise first person attachments fail.
500                 m_is_visible = true;
501         }
502 }
503
504 void GenericCAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
505         v3f *rotation, bool *force_visible) const
506 {
507         *parent_id = m_attachment_parent_id;
508         *bone = m_attachment_bone;
509         *position = m_attachment_position;
510         *rotation = m_attachment_rotation;
511         *force_visible = m_force_visible;
512 }
513
514 void GenericCAO::clearChildAttachments()
515 {
516         // Cannot use for-loop here: setAttachment() modifies 'm_attachment_child_ids'!
517         while (!m_attachment_child_ids.empty()) {
518                 int child_id = *m_attachment_child_ids.begin();
519
520                 if (ClientActiveObject *child = m_env->getActiveObject(child_id))
521                         child->setAttachment(0, "", v3f(), v3f(), false);
522
523                 removeAttachmentChild(child_id);
524         }
525 }
526
527 void GenericCAO::clearParentAttachment()
528 {
529         if (m_attachment_parent_id)
530                 setAttachment(0, "", m_attachment_position, m_attachment_rotation, false);
531         else
532                 setAttachment(0, "", v3f(), v3f(), false);
533 }
534
535 void GenericCAO::addAttachmentChild(int child_id)
536 {
537         m_attachment_child_ids.insert(child_id);
538 }
539
540 void GenericCAO::removeAttachmentChild(int child_id)
541 {
542         m_attachment_child_ids.erase(child_id);
543 }
544
545 ClientActiveObject* GenericCAO::getParent() const
546 {
547         return m_attachment_parent_id ? m_env->getActiveObject(m_attachment_parent_id) :
548                         nullptr;
549 }
550
551 void GenericCAO::removeFromScene(bool permanent)
552 {
553         // Should be true when removing the object permanently
554         // and false when refreshing (eg: updating visuals)
555         if (m_env && permanent) {
556                 // The client does not know whether this object does re-appear to
557                 // a later time, thus do not clear child attachments.
558
559                 clearParentAttachment();
560         }
561
562         if (auto shadow = RenderingEngine::get_shadow_renderer())
563                 shadow->removeNodeFromShadowList(getSceneNode());
564
565         if (m_meshnode) {
566                 m_meshnode->remove();
567                 m_meshnode->drop();
568                 m_meshnode = nullptr;
569         } else if (m_animated_meshnode) {
570                 m_animated_meshnode->remove();
571                 m_animated_meshnode->drop();
572                 m_animated_meshnode = nullptr;
573         } else if (m_wield_meshnode) {
574                 m_wield_meshnode->remove();
575                 m_wield_meshnode->drop();
576                 m_wield_meshnode = nullptr;
577         } else if (m_spritenode) {
578                 m_spritenode->remove();
579                 m_spritenode->drop();
580                 m_spritenode = nullptr;
581         }
582
583         if (m_matrixnode) {
584                 m_matrixnode->remove();
585                 m_matrixnode->drop();
586                 m_matrixnode = nullptr;
587         }
588
589         if (m_nametag) {
590                 m_client->getCamera()->removeNametag(m_nametag);
591                 m_nametag = nullptr;
592         }
593
594         if (m_marker && m_client->getMinimap())
595                 m_client->getMinimap()->removeMarker(&m_marker);
596 }
597
598 void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
599 {
600         m_smgr = smgr;
601
602         if (getSceneNode() != NULL) {
603                 return;
604         }
605
606         m_visuals_expired = false;
607
608         if (!m_prop.is_visible)
609                 return;
610
611         infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl;
612
613         if (m_enable_shaders) {
614                 IShaderSource *shader_source = m_client->getShaderSource();
615                 MaterialType material_type;
616
617                 if (m_prop.shaded && m_prop.glow == 0)
618                         material_type = (m_prop.use_texture_alpha) ?
619                                 TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC;
620                 else
621                         material_type = (m_prop.use_texture_alpha) ?
622                                 TILE_MATERIAL_PLAIN_ALPHA : TILE_MATERIAL_PLAIN;
623
624                 u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL);
625                 m_material_type = shader_source->getShaderInfo(shader_id).material;
626         } else {
627                 m_material_type = (m_prop.use_texture_alpha) ?
628                         video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
629         }
630
631         auto grabMatrixNode = [this] {
632                 m_matrixnode = m_smgr->addDummyTransformationSceneNode();
633                 m_matrixnode->grab();
634         };
635
636         auto setSceneNodeMaterial = [this] (scene::ISceneNode *node) {
637                 node->setMaterialFlag(video::EMF_LIGHTING, false);
638                 node->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
639                 node->setMaterialFlag(video::EMF_FOG_ENABLE, true);
640                 node->setMaterialType(m_material_type);
641
642                 if (m_enable_shaders) {
643                         node->setMaterialFlag(video::EMF_GOURAUD_SHADING, false);
644                         node->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
645                 }
646         };
647
648         if (m_prop.visual == "sprite") {
649                 grabMatrixNode();
650                 m_spritenode = m_smgr->addBillboardSceneNode(
651                                 m_matrixnode, v2f(1, 1), v3f(0,0,0), -1);
652                 m_spritenode->grab();
653                 m_spritenode->setMaterialTexture(0,
654                                 tsrc->getTextureForMesh("unknown_node.png"));
655
656                 setSceneNodeMaterial(m_spritenode);
657
658                 m_spritenode->setSize(v2f(m_prop.visual_size.X,
659                                 m_prop.visual_size.Y) * BS);
660                 {
661                         const float txs = 1.0 / 1;
662                         const float tys = 1.0 / 1;
663                         setBillboardTextureMatrix(m_spritenode,
664                                         txs, tys, 0, 0);
665                 }
666         } else if (m_prop.visual == "upright_sprite") {
667                 grabMatrixNode();
668                 scene::SMesh *mesh = new scene::SMesh();
669                 double dx = BS * m_prop.visual_size.X / 2;
670                 double dy = BS * m_prop.visual_size.Y / 2;
671                 video::SColor c(0xFFFFFFFF);
672
673                 { // Front
674                         scene::IMeshBuffer *buf = new scene::SMeshBuffer();
675                         video::S3DVertex vertices[4] = {
676                                 video::S3DVertex(-dx, -dy, 0, 0,0,1, c, 1,1),
677                                 video::S3DVertex( dx, -dy, 0, 0,0,1, c, 0,1),
678                                 video::S3DVertex( dx,  dy, 0, 0,0,1, c, 0,0),
679                                 video::S3DVertex(-dx,  dy, 0, 0,0,1, c, 1,0),
680                         };
681                         if (m_is_player) {
682                                 // Move minimal Y position to 0 (feet position)
683                                 for (video::S3DVertex &vertex : vertices)
684                                         vertex.Pos.Y += dy;
685                         }
686                         u16 indices[] = {0,1,2,2,3,0};
687                         buf->append(vertices, 4, indices, 6);
688                         // Set material
689                         buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
690                         buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
691                         buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
692                         buf->getMaterial().MaterialType = m_material_type;
693
694                         if (m_enable_shaders) {
695                                 buf->getMaterial().EmissiveColor = c;
696                                 buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false);
697                                 buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true);
698                         }
699
700                         // Add to mesh
701                         mesh->addMeshBuffer(buf);
702                         buf->drop();
703                 }
704                 { // Back
705                         scene::IMeshBuffer *buf = new scene::SMeshBuffer();
706                         video::S3DVertex vertices[4] = {
707                                 video::S3DVertex( dx,-dy, 0, 0,0,-1, c, 1,1),
708                                 video::S3DVertex(-dx,-dy, 0, 0,0,-1, c, 0,1),
709                                 video::S3DVertex(-dx, dy, 0, 0,0,-1, c, 0,0),
710                                 video::S3DVertex( dx, dy, 0, 0,0,-1, c, 1,0),
711                         };
712                         if (m_is_player) {
713                                 // Move minimal Y position to 0 (feet position)
714                                 for (video::S3DVertex &vertex : vertices)
715                                         vertex.Pos.Y += dy;
716                         }
717                         u16 indices[] = {0,1,2,2,3,0};
718                         buf->append(vertices, 4, indices, 6);
719                         // Set material
720                         buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
721                         buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
722                         buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
723                         buf->getMaterial().MaterialType = m_material_type;
724
725                         if (m_enable_shaders) {
726                                 buf->getMaterial().EmissiveColor = c;
727                                 buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false);
728                                 buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true);
729                         }
730
731                         // Add to mesh
732                         mesh->addMeshBuffer(buf);
733                         buf->drop();
734                 }
735                 m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
736                 m_meshnode->grab();
737                 mesh->drop();
738                 // Set it to use the materials of the meshbuffers directly.
739                 // This is needed for changing the texture in the future
740                 m_meshnode->setReadOnlyMaterials(true);
741         } else if (m_prop.visual == "cube") {
742                 grabMatrixNode();
743                 scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
744                 m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
745                 m_meshnode->grab();
746                 mesh->drop();
747
748                 m_meshnode->setScale(m_prop.visual_size);
749                 m_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
750                         m_prop.backface_culling);
751
752                 setSceneNodeMaterial(m_meshnode);
753         } else if (m_prop.visual == "mesh") {
754                 grabMatrixNode();
755                 scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
756                 if (mesh) {
757                         m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
758                         m_animated_meshnode->grab();
759                         mesh->drop(); // The scene node took hold of it
760
761                         if (!checkMeshNormals(mesh)) {
762                                 infostream << "GenericCAO: recalculating normals for mesh "
763                                         << m_prop.mesh << std::endl;
764                                 m_smgr->getMeshManipulator()->
765                                                 recalculateNormals(mesh, true, false);
766                         }
767
768                         m_animated_meshnode->animateJoints(); // Needed for some animations
769                         m_animated_meshnode->setScale(m_prop.visual_size);
770
771                         // set vertex colors to ensure alpha is set
772                         setMeshColor(m_animated_meshnode->getMesh(), video::SColor(0xFFFFFFFF));
773
774                         setAnimatedMeshColor(m_animated_meshnode, video::SColor(0xFFFFFFFF));
775
776                         setSceneNodeMaterial(m_animated_meshnode);
777
778                         m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
779                                 m_prop.backface_culling);
780                 } else
781                         errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
782         } else if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
783                 grabMatrixNode();
784                 ItemStack item;
785                 if (m_prop.wield_item.empty()) {
786                         // Old format, only textures are specified.
787                         infostream << "textures: " << m_prop.textures.size() << std::endl;
788                         if (!m_prop.textures.empty()) {
789                                 infostream << "textures[0]: " << m_prop.textures[0]
790                                         << std::endl;
791                                 IItemDefManager *idef = m_client->idef();
792                                 item = ItemStack(m_prop.textures[0], 1, 0, idef);
793                         }
794                 } else {
795                         infostream << "serialized form: " << m_prop.wield_item << std::endl;
796                         item.deSerialize(m_prop.wield_item, m_client->idef());
797                 }
798                 m_wield_meshnode = new WieldMeshSceneNode(m_smgr, -1);
799                 m_wield_meshnode->setItem(item, m_client,
800                         (m_prop.visual == "wielditem"));
801
802                 m_wield_meshnode->setScale(m_prop.visual_size / 2.0f);
803                 m_wield_meshnode->setColor(video::SColor(0xFFFFFFFF));
804         } else {
805                 infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual
806                                 <<"\" not supported"<<std::endl;
807         }
808
809         /* don't update while punch texture modifier is active */
810         if (m_reset_textures_timer < 0)
811                 updateTextures(m_current_texture_modifier);
812
813         if (scene::ISceneNode *node = getSceneNode()) {
814                 if (m_matrixnode)
815                         node->setParent(m_matrixnode);
816
817                 if (auto shadow = RenderingEngine::get_shadow_renderer())
818                         shadow->addNodeToShadowList(node);
819         }
820
821         updateNametag();
822         updateMarker();
823         updateNodePos();
824         updateAnimation();
825         updateBonePosition();
826         updateAttachments();
827         setNodeLight(m_last_light);
828         updateMeshCulling();
829
830         if (m_client->modsLoaded() && m_client->getScript()->on_object_add(m_id)) {
831                 removeFromScene(false);
832                 return;
833         }
834
835
836         if (m_client->modsLoaded())
837                 m_client->getScript()->on_object_properties_change(m_id);
838 }
839
840 void GenericCAO::updateLight(u32 day_night_ratio)
841 {
842         if (m_glow < 0)
843                 return;
844
845         u8 light_at_pos = 0;
846         bool pos_ok = false;
847
848         v3s16 pos[3];
849         u16 npos = getLightPosition(pos);
850         for (u16 i = 0; i < npos; i++) {
851                 bool this_ok;
852                 MapNode n = m_env->getMap().getNode(pos[i], &this_ok);
853                 if (this_ok) {
854                         u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef());
855                         light_at_pos = MYMAX(light_at_pos, this_light);
856                         pos_ok = true;
857                 }
858         }
859         if (!pos_ok)
860                 light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0);
861
862         u8 light = decode_light(light_at_pos + m_glow);
863         if (g_settings->getBool("fullbright"))
864                 light = 255;
865         if (light != m_last_light) {
866                 m_last_light = light;
867                 setNodeLight(light);
868         }
869 }
870
871 void GenericCAO::setNodeLight(u8 light)
872 {
873         video::SColor color(255, light, light, light);
874
875         if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
876                 if (m_wield_meshnode)
877                         m_wield_meshnode->setNodeLightColor(color);
878                 return;
879         }
880
881         if (m_enable_shaders) {
882                 if (m_prop.visual == "upright_sprite") {
883                         if (!m_meshnode)
884                                 return;
885
886                         scene::IMesh *mesh = m_meshnode->getMesh();
887                         for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) {
888                                 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
889                                 buf->getMaterial().EmissiveColor = color;
890                         }
891                 } else {
892                         scene::ISceneNode *node = getSceneNode();
893                         if (!node)
894                                 return;
895
896                         for (u32 i = 0; i < node->getMaterialCount(); ++i) {
897                                 video::SMaterial &material = node->getMaterial(i);
898                                 material.EmissiveColor = color;
899                         }
900                 }
901         } else {
902                 if (m_meshnode) {
903                         setMeshColor(m_meshnode->getMesh(), color);
904                 } else if (m_animated_meshnode) {
905                         setAnimatedMeshColor(m_animated_meshnode, color);
906                 } else if (m_spritenode) {
907                         m_spritenode->setColor(color);
908                 }
909         }
910 }
911
912 u16 GenericCAO::getLightPosition(v3s16 *pos)
913 {
914         const auto &box = m_prop.collisionbox;
915         pos[0] = floatToInt(m_position + box.MinEdge * BS, BS);
916         pos[1] = floatToInt(m_position + box.MaxEdge * BS, BS);
917
918         // Skip center pos if it falls into the same node as Min or MaxEdge
919         if ((box.MaxEdge - box.MinEdge).getLengthSQ() < 3.0f)
920                 return 2;
921         pos[2] = floatToInt(m_position + box.getCenter() * BS, BS);
922         return 3;
923 }
924
925 void GenericCAO::updateMarker()
926 {
927         if (!m_client->getMinimap())
928                 return;
929
930         if (!m_prop.show_on_minimap) {
931                 if (m_marker)
932                         m_client->getMinimap()->removeMarker(&m_marker);
933                 return;
934         }
935
936         if (m_marker)
937                 return;
938
939         scene::ISceneNode *node = getSceneNode();
940         if (!node)
941                 return;
942         m_marker = m_client->getMinimap()->addMarker(node);
943 }
944
945 void GenericCAO::updateNametag()
946 {
947         //if (m_is_local_player && ! g_settings->getBool("freecam")) // No nametag for local player
948                 //return;
949
950         if (m_prop.nametag.empty() || m_prop.nametag_color.getAlpha() == 0) {
951                 // Delete nametag
952                 if (m_nametag) {
953                         m_client->getCamera()->removeNametag(m_nametag);
954                         m_nametag = nullptr;
955                 }
956                 return;
957         }
958
959         scene::ISceneNode *node = getSceneNode();
960         if (!node)
961                 return;
962
963         v3f pos;
964         pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
965         if (!m_nametag) {
966                 // Add nametag
967                 m_nametag = m_client->getCamera()->addNametag(node,
968                         m_prop.nametag, m_prop.nametag_color,
969                         m_prop.nametag_bgcolor, pos, nametag_images);
970         } else {
971                 // Update nametag
972                 m_nametag->text = m_prop.nametag;
973                 m_nametag->textcolor = m_prop.nametag_color;
974                 m_nametag->bgcolor = m_prop.nametag_bgcolor;
975                 m_nametag->pos = pos;
976                 m_nametag->setImages(nametag_images);
977         }
978 }
979
980 void GenericCAO::updateNodePos()
981 {
982         if (getParent() != NULL)
983                 return;
984
985         scene::ISceneNode *node = getSceneNode();
986
987         if (node) {
988                 v3s16 camera_offset = m_env->getCameraOffset();
989                 v3f pos = pos_translator.val_current -
990                                 intToFloat(camera_offset, BS);
991                 getPosRotMatrix().setTranslation(pos);
992                 if (node != m_spritenode) { // rotate if not a sprite
993                         v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current;
994                         setPitchYawRoll(getPosRotMatrix(), rot);
995                 }
996         }
997 }
998
999 void GenericCAO::step(float dtime, ClientEnvironment *env)
1000 {
1001         // Handle model animations and update positions instantly to prevent lags
1002         if (m_is_local_player) {
1003                 LocalPlayer *player = m_env->getLocalPlayer();
1004                 m_position = player->getLegitPosition();
1005                 pos_translator.val_current = m_position;
1006                 if (! g_settings->getBool("freecam")) {
1007                         m_rotation.Y = wrapDegrees_0_360(player->getYaw());
1008                         rot_translator.val_current = m_rotation;
1009                 }
1010
1011                 if (m_is_visible) {
1012                         int old_anim = player->last_animation;
1013                         float old_anim_speed = player->last_animation_speed;
1014                         m_velocity = v3f(0,0,0);
1015                         m_acceleration = v3f(0,0,0);
1016                         const PlayerControl &controls = player->getPlayerControl();
1017
1018                         bool walking = false;
1019                         if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam"))
1020                                 walking = true;
1021
1022                         f32 new_speed = player->local_animation_speed;
1023                         v2s32 new_anim = v2s32(0,0);
1024                         bool allow_update = false;
1025
1026                         // increase speed if using fast or flying fast
1027                         if(((g_settings->getBool("fast_move") &&
1028                                         m_client->checkLocalPrivilege("fast")) &&
1029                                         (controls.aux1 ||
1030                                         (!player->touching_ground &&
1031                                         g_settings->getBool("free_move") &&
1032                                         m_client->checkLocalPrivilege("fly")))) || g_settings->getBool("freecam"))
1033                                         new_speed *= 1.5;
1034                         // slowdown speed if sneaking
1035                         if (controls.sneak && walking && ! g_settings->getBool("no_slow"))
1036                                 new_speed /= 2;
1037                         new_speed *= controls.movement_speed;
1038
1039                         if (walking && (controls.dig || controls.place)) {
1040                                 new_anim = player->local_animations[3];
1041                                 player->last_animation = WD_ANIM;
1042                         } else if (walking) {
1043                                 new_anim = player->local_animations[1];
1044                                 player->last_animation = WALK_ANIM;
1045                         } else if (controls.dig || controls.place) {
1046                                 new_anim = player->local_animations[2];
1047                                 player->last_animation = DIG_ANIM;
1048                         }
1049
1050                         // Apply animations if input detected and not attached
1051                         // or set idle animation
1052                         if ((new_anim.X + new_anim.Y) > 0 && !getParent()) {
1053                                 allow_update = true;
1054                                 m_animation_range = new_anim;
1055                                 m_animation_speed = new_speed;
1056                                 player->last_animation_speed = m_animation_speed;
1057                         } else {
1058                                 player->last_animation = NO_ANIM;
1059
1060                                 if (old_anim != NO_ANIM) {
1061                                         m_animation_range = player->local_animations[0];
1062                                         updateAnimation();
1063                                 }
1064                         }
1065
1066                         // Update local player animations
1067                         if ((player->last_animation != old_anim ||
1068                                         m_animation_speed != old_anim_speed) &&
1069                                         player->last_animation != NO_ANIM && allow_update)
1070                                 updateAnimation();
1071
1072                 }
1073         }
1074
1075         if (m_visuals_expired && m_smgr) {
1076                 m_visuals_expired = false;
1077
1078                 // Attachments, part 1: All attached objects must be unparented first,
1079                 // or Irrlicht causes a segmentation fault
1080                 for (u16 cao_id : m_attachment_child_ids) {
1081                         ClientActiveObject *obj = m_env->getActiveObject(cao_id);
1082                         if (obj) {
1083                                 scene::ISceneNode *child_node = obj->getSceneNode();
1084                                 // The node's parent is always an IDummyTraformationSceneNode,
1085                                 // so we need to reparent that one instead.
1086                                 if (child_node)
1087                                         child_node->getParent()->setParent(m_smgr->getRootSceneNode());
1088                         }
1089                 }
1090
1091                 removeFromScene(false);
1092                 addToScene(m_client->tsrc(), m_smgr);
1093
1094                 // Attachments, part 2: Now that the parent has been refreshed, put its attachments back
1095                 for (u16 cao_id : m_attachment_child_ids) {
1096                         ClientActiveObject *obj = m_env->getActiveObject(cao_id);
1097                         if (obj)
1098                                 obj->updateAttachments();
1099                 }
1100         }
1101
1102         // Make sure m_is_visible is always applied
1103         scene::ISceneNode *node = getSceneNode();
1104         if (node)
1105                 node->setVisible(m_is_visible);
1106
1107         if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht
1108         {
1109                 // Set these for later
1110                 m_position = getPosition();
1111                 m_velocity = v3f(0,0,0);
1112                 m_acceleration = v3f(0,0,0);
1113                 pos_translator.val_current = m_position;
1114                 pos_translator.val_target = m_position;
1115         } else {
1116                 rot_translator.translate(dtime);
1117                 v3f lastpos = pos_translator.val_current;
1118
1119                 if(m_prop.physical)
1120                 {
1121                         aabb3f box = m_prop.collisionbox;
1122                         box.MinEdge *= BS;
1123                         box.MaxEdge *= BS;
1124                         collisionMoveResult moveresult;
1125                         f32 pos_max_d = BS*0.125; // Distance per iteration
1126                         v3f p_pos = m_position;
1127                         v3f p_velocity = m_velocity;
1128                         moveresult = collisionMoveSimple(env,env->getGameDef(),
1129                                         pos_max_d, box, m_prop.stepheight, dtime,
1130                                         &p_pos, &p_velocity, m_acceleration,
1131                                         this, m_prop.collideWithObjects);
1132                         // Apply results
1133                         m_position = p_pos;
1134                         m_velocity = p_velocity;
1135
1136                         bool is_end_position = moveresult.collides;
1137                         pos_translator.update(m_position, is_end_position, dtime);
1138                 } else {
1139                         m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration;
1140                         m_velocity += dtime * m_acceleration;
1141                         pos_translator.update(m_position, pos_translator.aim_is_end,
1142                                         pos_translator.anim_time);
1143                 }
1144                 pos_translator.translate(dtime);
1145                 updateNodePos();
1146
1147                 float moved = lastpos.getDistanceFrom(pos_translator.val_current);
1148                 m_step_distance_counter += moved;
1149                 if (m_step_distance_counter > 1.5f * BS) {
1150                         m_step_distance_counter = 0.0f;
1151                         if (!m_is_local_player && m_prop.makes_footstep_sound) {
1152                                 const NodeDefManager *ndef = m_client->ndef();
1153                                 v3s16 p = floatToInt(getPosition() +
1154                                         v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS);
1155                                 MapNode n = m_env->getMap().getNode(p);
1156                                 SimpleSoundSpec spec = ndef->get(n).sound_footstep;
1157                                 // Reduce footstep gain, as non-local-player footsteps are
1158                                 // somehow louder.
1159                                 spec.gain *= 0.6f;
1160                                 m_client->sound()->playSoundAt(spec, false, getPosition());
1161                         }
1162                 }
1163         }
1164
1165         m_anim_timer += dtime;
1166         if(m_anim_timer >= m_anim_framelength)
1167         {
1168                 m_anim_timer -= m_anim_framelength;
1169                 m_anim_frame++;
1170                 if(m_anim_frame >= m_anim_num_frames)
1171                         m_anim_frame = 0;
1172         }
1173
1174         updateTexturePos();
1175
1176         if(m_reset_textures_timer >= 0)
1177         {
1178                 m_reset_textures_timer -= dtime;
1179                 if(m_reset_textures_timer <= 0) {
1180                         m_reset_textures_timer = -1;
1181                         updateTextures(m_previous_texture_modifier);
1182                 }
1183         }
1184
1185         if (!getParent() && node && fabs(m_prop.automatic_rotate) > 0.001f) {
1186                 // This is the child node's rotation. It is only used for automatic_rotate.
1187                 v3f local_rot = node->getRotation();
1188                 local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG *
1189                                 m_prop.automatic_rotate);
1190                 node->setRotation(local_rot);
1191         }
1192
1193         if (!getParent() && m_prop.automatic_face_movement_dir &&
1194                         (fabs(m_velocity.Z) > 0.001f || fabs(m_velocity.X) > 0.001f)) {
1195                 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
1196                                 + m_prop.automatic_face_movement_dir_offset;
1197                 float max_rotation_per_sec =
1198                                 m_prop.automatic_face_movement_max_rotation_per_sec;
1199
1200                 if (max_rotation_per_sec > 0) {
1201                         wrappedApproachShortest(m_rotation.Y, target_yaw,
1202                                 dtime * max_rotation_per_sec, 360.f);
1203                 } else {
1204                         // Negative values of max_rotation_per_sec mean disabled.
1205                         m_rotation.Y = target_yaw;
1206                 }
1207
1208                 rot_translator.val_current = m_rotation;
1209                 updateNodePos();
1210         }
1211
1212         if (m_animated_meshnode) {
1213                 // Everything must be updated; the whole transform
1214                 // chain as well as the animated mesh node.
1215                 // Otherwise, bone attachments would be relative to
1216                 // a position that's one frame old.
1217                 if (m_matrixnode)
1218                         updatePositionRecursive(m_matrixnode);
1219                 m_animated_meshnode->updateAbsolutePosition();
1220                 m_animated_meshnode->animateJoints();
1221                 updateBonePosition();
1222         }
1223 }
1224
1225 void GenericCAO::updateTexturePos()
1226 {
1227         if(m_spritenode)
1228         {
1229                 scene::ICameraSceneNode* camera =
1230                                 m_spritenode->getSceneManager()->getActiveCamera();
1231                 if(!camera)
1232                         return;
1233                 v3f cam_to_entity = m_spritenode->getAbsolutePosition()
1234                                 - camera->getAbsolutePosition();
1235                 cam_to_entity.normalize();
1236
1237                 int row = m_tx_basepos.Y;
1238                 int col = m_tx_basepos.X;
1239
1240                 // Yawpitch goes rightwards
1241                 if (m_tx_select_horiz_by_yawpitch) {
1242                         if (cam_to_entity.Y > 0.75)
1243                                 col += 5;
1244                         else if (cam_to_entity.Y < -0.75)
1245                                 col += 4;
1246                         else {
1247                                 float mob_dir =
1248                                                 atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.;
1249                                 float dir = mob_dir - m_rotation.Y;
1250                                 dir = wrapDegrees_180(dir);
1251                                 if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f)
1252                                         col += 2;
1253                                 else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f)
1254                                         col += 3;
1255                                 else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f)
1256                                         col += 0;
1257                                 else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f)
1258                                         col += 1;
1259                                 else
1260                                         col += 4;
1261                         }
1262                 }
1263
1264                 // Animation goes downwards
1265                 row += m_anim_frame;
1266
1267                 float txs = m_tx_size.X;
1268                 float tys = m_tx_size.Y;
1269                 setBillboardTextureMatrix(m_spritenode, txs, tys, col, row);
1270         }
1271
1272         else if (m_meshnode) {
1273                 if (m_prop.visual == "upright_sprite") {
1274                         int row = m_tx_basepos.Y;
1275                         int col = m_tx_basepos.X;
1276
1277                         // Animation goes downwards
1278                         row += m_anim_frame;
1279
1280                         const auto &tx = m_tx_size;
1281                         v2f t[4] = { // cf. vertices in GenericCAO::addToScene()
1282                                 tx * v2f(col+1, row+1),
1283                                 tx * v2f(col, row+1),
1284                                 tx * v2f(col, row),
1285                                 tx * v2f(col+1, row),
1286                         };
1287                         auto mesh = m_meshnode->getMesh();
1288                         setMeshBufferTextureCoords(mesh->getMeshBuffer(0), t, 4);
1289                         setMeshBufferTextureCoords(mesh->getMeshBuffer(1), t, 4);
1290                 }
1291         }
1292 }
1293
1294 // Do not pass by reference, see header.
1295 void GenericCAO::updateTextures(std::string mod)
1296 {
1297         ITextureSource *tsrc = m_client->tsrc();
1298
1299         bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
1300         bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
1301         bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
1302
1303         m_previous_texture_modifier = m_current_texture_modifier;
1304         m_current_texture_modifier = mod;
1305         m_glow = m_prop.glow;
1306
1307         if (m_spritenode) {
1308                 if (m_prop.visual == "sprite") {
1309                         std::string texturestring = "unknown_node.png";
1310                         if (!m_prop.textures.empty())
1311                                 texturestring = m_prop.textures[0];
1312                         texturestring += mod;
1313                         m_spritenode->getMaterial(0).MaterialType = m_material_type;
1314                         m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f;
1315                         m_spritenode->setMaterialTexture(0,
1316                                         tsrc->getTextureForMesh(texturestring));
1317
1318                         // This allows setting per-material colors. However, until a real lighting
1319                         // system is added, the code below will have no effect. Once MineTest
1320                         // has directional lighting, it should work automatically.
1321                         if (!m_prop.colors.empty()) {
1322                                 m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0];
1323                                 m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0];
1324                                 m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0];
1325                         }
1326
1327                         m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
1328                         m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
1329                         m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
1330                 }
1331         }
1332
1333         else if (m_animated_meshnode) {
1334                 if (m_prop.visual == "mesh") {
1335                         for (u32 i = 0; i < m_prop.textures.size() &&
1336                                         i < m_animated_meshnode->getMaterialCount(); ++i) {
1337                                 std::string texturestring = m_prop.textures[i];
1338                                 if (texturestring.empty())
1339                                         continue; // Empty texture string means don't modify that material
1340                                 texturestring += mod;
1341                                 video::ITexture* texture = tsrc->getTextureForMesh(texturestring);
1342                                 if (!texture) {
1343                                         errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl;
1344                                         continue;
1345                                 }
1346
1347                                 // Set material flags and texture
1348                                 video::SMaterial& material = m_animated_meshnode->getMaterial(i);
1349                                 material.MaterialType = m_material_type;
1350                                 material.MaterialTypeParam = 0.5f;
1351                                 material.TextureLayer[0].Texture = texture;
1352                                 material.setFlag(video::EMF_LIGHTING, true);
1353                                 material.setFlag(video::EMF_BILINEAR_FILTER, false);
1354                                 material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling);
1355
1356                                 // don't filter low-res textures, makes them look blurry
1357                                 // player models have a res of 64
1358                                 const core::dimension2d<u32> &size = texture->getOriginalSize();
1359                                 const u32 res = std::min(size.Height, size.Width);
1360                                 use_trilinear_filter &= res > 64;
1361                                 use_bilinear_filter &= res > 64;
1362
1363                                 m_animated_meshnode->getMaterial(i)
1364                                                 .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
1365                                 m_animated_meshnode->getMaterial(i)
1366                                                 .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
1367                                 m_animated_meshnode->getMaterial(i)
1368                                                 .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
1369                         }
1370                         for (u32 i = 0; i < m_prop.colors.size() &&
1371                         i < m_animated_meshnode->getMaterialCount(); ++i)
1372                         {
1373                                 // This allows setting per-material colors. However, until a real lighting
1374                                 // system is added, the code below will have no effect. Once MineTest
1375                                 // has directional lighting, it should work automatically.
1376                                 m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i];
1377                                 m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i];
1378                                 m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i];
1379                         }
1380                 }
1381         }
1382
1383         else if (m_meshnode) {
1384                 if(m_prop.visual == "cube")
1385                 {
1386                         for (u32 i = 0; i < 6; ++i)
1387                         {
1388                                 std::string texturestring = "unknown_node.png";
1389                                 if(m_prop.textures.size() > i)
1390                                         texturestring = m_prop.textures[i];
1391                                 texturestring += mod;
1392
1393
1394                                 // Set material flags and texture
1395                                 video::SMaterial& material = m_meshnode->getMaterial(i);
1396                                 material.MaterialType = m_material_type;
1397                                 material.MaterialTypeParam = 0.5f;
1398                                 material.setFlag(video::EMF_LIGHTING, false);
1399                                 material.setFlag(video::EMF_BILINEAR_FILTER, false);
1400                                 material.setTexture(0,
1401                                                 tsrc->getTextureForMesh(texturestring));
1402                                 material.getTextureMatrix(0).makeIdentity();
1403
1404                                 // This allows setting per-material colors. However, until a real lighting
1405                                 // system is added, the code below will have no effect. Once MineTest
1406                                 // has directional lighting, it should work automatically.
1407                                 if(m_prop.colors.size() > i)
1408                                 {
1409                                         m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i];
1410                                         m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i];
1411                                         m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i];
1412                                 }
1413
1414                                 m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
1415                                 m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
1416                                 m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
1417                         }
1418                 } else if (m_prop.visual == "upright_sprite") {
1419                         scene::IMesh *mesh = m_meshnode->getMesh();
1420                         {
1421                                 std::string tname = "unknown_object.png";
1422                                 if (!m_prop.textures.empty())
1423                                         tname = m_prop.textures[0];
1424                                 tname += mod;
1425                                 scene::IMeshBuffer *buf = mesh->getMeshBuffer(0);
1426                                 buf->getMaterial().setTexture(0,
1427                                                 tsrc->getTextureForMesh(tname));
1428
1429                                 // This allows setting per-material colors. However, until a real lighting
1430                                 // system is added, the code below will have no effect. Once MineTest
1431                                 // has directional lighting, it should work automatically.
1432                                 if(!m_prop.colors.empty()) {
1433                                         buf->getMaterial().AmbientColor = m_prop.colors[0];
1434                                         buf->getMaterial().DiffuseColor = m_prop.colors[0];
1435                                         buf->getMaterial().SpecularColor = m_prop.colors[0];
1436                                 }
1437
1438                                 buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
1439                                 buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
1440                                 buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
1441                         }
1442                         {
1443                                 std::string tname = "unknown_object.png";
1444                                 if (m_prop.textures.size() >= 2)
1445                                         tname = m_prop.textures[1];
1446                                 else if (!m_prop.textures.empty())
1447                                         tname = m_prop.textures[0];
1448                                 tname += mod;
1449                                 scene::IMeshBuffer *buf = mesh->getMeshBuffer(1);
1450                                 buf->getMaterial().setTexture(0,
1451                                                 tsrc->getTextureForMesh(tname));
1452
1453                                 // This allows setting per-material colors. However, until a real lighting
1454                                 // system is added, the code below will have no effect. Once MineTest
1455                                 // has directional lighting, it should work automatically.
1456                                 if (m_prop.colors.size() >= 2) {
1457                                         buf->getMaterial().AmbientColor = m_prop.colors[1];
1458                                         buf->getMaterial().DiffuseColor = m_prop.colors[1];
1459                                         buf->getMaterial().SpecularColor = m_prop.colors[1];
1460                                 } else if (!m_prop.colors.empty()) {
1461                                         buf->getMaterial().AmbientColor = m_prop.colors[0];
1462                                         buf->getMaterial().DiffuseColor = m_prop.colors[0];
1463                                         buf->getMaterial().SpecularColor = m_prop.colors[0];
1464                                 }
1465
1466                                 buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
1467                                 buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
1468                                 buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
1469                         }
1470                         // Set mesh color (only if lighting is disabled)
1471                         if (!m_prop.colors.empty() && m_glow < 0)
1472                                 setMeshColor(mesh, m_prop.colors[0]);
1473                 }
1474         }
1475         // Prevent showing the player after changing texture
1476         if (m_is_local_player)
1477                 updateMeshCulling();
1478 }
1479
1480 void GenericCAO::updateAnimation()
1481 {
1482         if (!m_animated_meshnode)
1483                 return;
1484
1485         if (m_animated_meshnode->getStartFrame() != m_animation_range.X ||
1486                 m_animated_meshnode->getEndFrame() != m_animation_range.Y)
1487                         m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y);
1488         if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed)
1489                 m_animated_meshnode->setAnimationSpeed(m_animation_speed);
1490         m_animated_meshnode->setTransitionTime(m_animation_blend);
1491         if (m_animated_meshnode->getLoopMode() != m_animation_loop)
1492                 m_animated_meshnode->setLoopMode(m_animation_loop);
1493 }
1494
1495 void GenericCAO::updateAnimationSpeed()
1496 {
1497         if (!m_animated_meshnode)
1498                 return;
1499
1500         m_animated_meshnode->setAnimationSpeed(m_animation_speed);
1501 }
1502
1503 void GenericCAO::updateBonePosition()
1504 {
1505         if (m_bone_position.empty() || !m_animated_meshnode)
1506                 return;
1507
1508         m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
1509         for (auto &it : m_bone_position) {
1510                 std::string bone_name = it.first;
1511                 scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
1512                 if (bone) {
1513                         bone->setPosition(it.second.X);
1514                         bone->setRotation(it.second.Y);
1515                 }
1516         }
1517
1518         // search through bones to find mistakenly rotated bones due to bug in Irrlicht
1519         for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
1520                 scene::IBoneSceneNode *bone = m_animated_meshnode->getJointNode(i);
1521                 if (!bone)
1522                         continue;
1523
1524                 //If bone is manually positioned there is no need to perform the bug check
1525                 bool skip = false;
1526                 for (auto &it : m_bone_position) {
1527                         if (it.first == bone->getName()) {
1528                                 skip = true;
1529                                 break;
1530                         }
1531                 }
1532                 if (skip)
1533                         continue;
1534
1535                 // Workaround for Irrlicht bug
1536                 // We check each bone to see if it has been rotated ~180deg from its expected position due to a bug in Irricht
1537                 // when using EJUOR_CONTROL joint control. If the bug is detected we update the bone to the proper position
1538                 // and update the bones transformation.
1539                 v3f bone_rot = bone->getRelativeTransformation().getRotationDegrees();
1540                 float offset = fabsf(bone_rot.X - bone->getRotation().X);
1541                 if (offset > 179.9f && offset < 180.1f) {
1542                         bone->setRotation(bone_rot);
1543                         bone->updateAbsolutePosition();
1544                 }
1545         }
1546         // The following is needed for set_bone_pos to propagate to
1547         // attached objects correctly.
1548         // Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL.
1549         for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
1550                 auto bone = m_animated_meshnode->getJointNode(i);
1551                 // Look for the root bone.
1552                 if (bone && bone->getParent() == m_animated_meshnode) {
1553                         // Update entire skeleton.
1554                         bone->updateAbsolutePositionOfAllChildren();
1555                         break;
1556                 }
1557         }
1558 }
1559
1560 void GenericCAO::updateAttachments()
1561 {
1562         ClientActiveObject *parent = getParent();
1563
1564         m_attached_to_local = parent && parent->isLocalPlayer();
1565
1566         /*
1567         Following cases exist:
1568                 m_attachment_parent_id == 0 && !parent
1569                         This object is not attached
1570                 m_attachment_parent_id != 0 && parent
1571                         This object is attached
1572                 m_attachment_parent_id != 0 && !parent
1573                         This object will be attached as soon the parent is known
1574                 m_attachment_parent_id == 0 && parent
1575                         Impossible case
1576         */
1577
1578         if (!parent) { // Detach or don't attach
1579                 if (m_matrixnode) {
1580                         v3s16 camera_offset = m_env->getCameraOffset();
1581                         v3f old_pos = getPosition();
1582
1583                         m_matrixnode->setParent(m_smgr->getRootSceneNode());
1584                         getPosRotMatrix().setTranslation(old_pos - intToFloat(camera_offset, BS));
1585                         m_matrixnode->updateAbsolutePosition();
1586                 }
1587         }
1588         else // Attach
1589         {
1590                 parent->updateAttachments();
1591                 scene::ISceneNode *parent_node = parent->getSceneNode();
1592                 scene::IAnimatedMeshSceneNode *parent_animated_mesh_node =
1593                                 parent->getAnimatedMeshSceneNode();
1594                 if (parent_animated_mesh_node && !m_attachment_bone.empty()) {
1595                         parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str());
1596                 }
1597
1598                 if (m_matrixnode && parent_node) {
1599                         m_matrixnode->setParent(parent_node);
1600                         parent_node->updateAbsolutePosition();
1601                         getPosRotMatrix().setTranslation(m_attachment_position);
1602                         //setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation);
1603                         // use Irrlicht eulers instead
1604                         getPosRotMatrix().setRotationDegrees(m_attachment_rotation);
1605                         m_matrixnode->updateAbsolutePosition();
1606                 }
1607         }
1608 }
1609
1610 bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const
1611 {
1612         const ObjectProperties &old = m_prop;
1613         /* Visuals do not need to be expired for:
1614          * - nametag props: handled by updateNametag()
1615          * - textures:      handled by updateTextures()
1616          * - sprite props:  handled by updateTexturePos()
1617          * - glow:          handled by updateLight()
1618          * - any other properties that do not change appearance
1619          */
1620
1621         bool uses_legacy_texture = new_.wield_item.empty() &&
1622                 (new_.visual == "wielditem" || new_.visual == "item");
1623         // Ordered to compare primitive types before std::vectors
1624         return old.backface_culling != new_.backface_culling ||
1625                 old.is_visible != new_.is_visible ||
1626                 old.mesh != new_.mesh ||
1627                 old.shaded != new_.shaded ||
1628                 old.use_texture_alpha != new_.use_texture_alpha ||
1629                 old.visual != new_.visual ||
1630                 old.visual_size != new_.visual_size ||
1631                 old.wield_item != new_.wield_item ||
1632                 old.colors != new_.colors ||
1633                 (uses_legacy_texture && old.textures != new_.textures);
1634 }
1635
1636 void GenericCAO::setProperties(ObjectProperties newprops)
1637 {
1638         // Check what exactly changed
1639         bool expire_visuals = visualExpiryRequired(newprops);
1640         bool textures_changed = m_prop.textures != newprops.textures;
1641
1642         // Apply changes
1643         m_prop = std::move(newprops);
1644
1645         m_selection_box = m_prop.selectionbox;
1646         m_selection_box.MinEdge *= BS;
1647         m_selection_box.MaxEdge *= BS;
1648
1649         m_tx_size.X = 1.0f / m_prop.spritediv.X;
1650         m_tx_size.Y = 1.0f / m_prop.spritediv.Y;
1651
1652         if(!m_initial_tx_basepos_set){
1653                 m_initial_tx_basepos_set = true;
1654                 m_tx_basepos = m_prop.initial_sprite_basepos;
1655         }
1656         if (m_is_local_player) {
1657                 LocalPlayer *player = m_env->getLocalPlayer();
1658                 player->makes_footstep_sound = m_prop.makes_footstep_sound;
1659                 aabb3f collision_box = m_prop.collisionbox;
1660                 collision_box.MinEdge *= BS;
1661                 collision_box.MaxEdge *= BS;
1662                 player->setCollisionbox(collision_box);
1663                 player->setEyeHeight(m_prop.eye_height);
1664                 player->setZoomFOV(m_prop.zoom_fov);
1665         }
1666
1667         if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty())
1668                 m_prop.nametag = m_name;
1669         if (m_is_local_player)
1670                 m_prop.show_on_minimap = false;
1671
1672         if (expire_visuals) {
1673                 expireVisuals();
1674         } else {
1675                 infostream << "GenericCAO: properties updated but expiring visuals"
1676                         << " not necessary" << std::endl;
1677                 if (textures_changed) {
1678                         // don't update while punch texture modifier is active
1679                         if (m_reset_textures_timer < 0)
1680                                 updateTextures(m_current_texture_modifier);
1681                 }
1682                 updateNametag();
1683                 updateMarker();
1684         }
1685 }
1686
1687 void GenericCAO::processMessage(const std::string &data)
1688 {
1689         //infostream<<"GenericCAO: Got message"<<std::endl;
1690         std::istringstream is(data, std::ios::binary);
1691         // command
1692         u8 cmd = readU8(is);
1693         if (cmd == AO_CMD_SET_PROPERTIES) {
1694                 ObjectProperties newprops;
1695                 newprops.show_on_minimap = m_is_player; // default
1696
1697                 newprops.deSerialize(is);
1698                 setProperties(newprops);
1699
1700                 // notify CSM
1701                 if (m_client->modsLoaded())
1702                         m_client->getScript()->on_object_properties_change(m_id);
1703
1704         } else if (cmd == AO_CMD_UPDATE_POSITION) {
1705                 // Not sent by the server if this object is an attachment.
1706                 // We might however get here if the server notices the object being detached before the client.
1707                 m_position = readV3F32(is);
1708                 m_velocity = readV3F32(is);
1709                 m_acceleration = readV3F32(is);
1710                 m_rotation = readV3F32(is);
1711
1712                 m_rotation = wrapDegrees_0_360_v3f(m_rotation);
1713                 bool do_interpolate = readU8(is);
1714                 bool is_end_position = readU8(is);
1715                 float update_interval = readF32(is);
1716
1717                 // Place us a bit higher if we're physical, to not sink into
1718                 // the ground due to sucky collision detection...
1719                 if(m_prop.physical)
1720                         m_position += v3f(0,0.002,0);
1721
1722                 if(getParent() != NULL) // Just in case
1723                         return;
1724
1725                 if(do_interpolate)
1726                 {
1727                         if(!m_prop.physical)
1728                                 pos_translator.update(m_position, is_end_position, update_interval);
1729                 } else {
1730                         pos_translator.init(m_position);
1731                 }
1732                 rot_translator.update(m_rotation, false, update_interval);
1733                 updateNodePos();
1734         } else if (cmd == AO_CMD_SET_TEXTURE_MOD) {
1735                 std::string mod = deSerializeString16(is);
1736
1737                 // immediately reset a engine issued texture modifier if a mod sends a different one
1738                 if (m_reset_textures_timer > 0) {
1739                         m_reset_textures_timer = -1;
1740                         updateTextures(m_previous_texture_modifier);
1741                 }
1742                 updateTextures(mod);
1743         } else if (cmd == AO_CMD_SET_SPRITE) {
1744                 v2s16 p = readV2S16(is);
1745                 int num_frames = readU16(is);
1746                 float framelength = readF32(is);
1747                 bool select_horiz_by_yawpitch = readU8(is);
1748
1749                 m_tx_basepos = p;
1750                 m_anim_num_frames = num_frames;
1751                 m_anim_frame = 0;
1752                 m_anim_framelength = framelength;
1753                 m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch;
1754
1755                 updateTexturePos();
1756         } else if (cmd == AO_CMD_SET_PHYSICS_OVERRIDE) {
1757                 float override_speed = readF32(is);
1758                 float override_jump = readF32(is);
1759                 float override_gravity = readF32(is);
1760                 // these are sent inverted so we get true when the server sends nothing
1761                 bool sneak = !readU8(is);
1762                 bool sneak_glitch = !readU8(is);
1763                 bool new_move = !readU8(is);
1764
1765
1766                 if(m_is_local_player)
1767                 {
1768                         Client *client = m_env->getGameDef();
1769
1770                         if (client->modsLoaded() && client->getScript()->on_recieve_physics_override(override_speed, override_jump, override_gravity, sneak, sneak_glitch, new_move))
1771                                 return;
1772
1773                         LocalPlayer *player = m_env->getLocalPlayer();
1774                         player->physics_override_speed = override_speed;
1775                         player->physics_override_jump = override_jump;
1776                         player->physics_override_gravity = override_gravity;
1777                         player->physics_override_sneak = sneak;
1778                         player->physics_override_sneak_glitch = sneak_glitch;
1779                         player->physics_override_new_move = new_move;
1780                 }
1781         } else if (cmd == AO_CMD_SET_ANIMATION) {
1782                 // TODO: change frames send as v2s32 value
1783                 v2f range = readV2F32(is);
1784                 if (!m_is_local_player) {
1785                         m_animation_range = v2s32((s32)range.X, (s32)range.Y);
1786                         m_animation_speed = readF32(is);
1787                         m_animation_blend = readF32(is);
1788                         // these are sent inverted so we get true when the server sends nothing
1789                         m_animation_loop = !readU8(is);
1790                         updateAnimation();
1791                 } else {
1792                         LocalPlayer *player = m_env->getLocalPlayer();
1793                         if(player->last_animation == NO_ANIM)
1794                         {
1795                                 m_animation_range = v2s32((s32)range.X, (s32)range.Y);
1796                                 m_animation_speed = readF32(is);
1797                                 m_animation_blend = readF32(is);
1798                                 // these are sent inverted so we get true when the server sends nothing
1799                                 m_animation_loop = !readU8(is);
1800                         }
1801                         // update animation only if local animations present
1802                         // and received animation is unknown (except idle animation)
1803                         bool is_known = false;
1804                         for (int i = 1;i<4;i++)
1805                         {
1806                                 if(m_animation_range.Y == player->local_animations[i].Y)
1807                                         is_known = true;
1808                         }
1809                         if(!is_known ||
1810                                         (player->local_animations[1].Y + player->local_animations[2].Y < 1))
1811                         {
1812                                         updateAnimation();
1813                         }
1814                 }
1815         } else if (cmd == AO_CMD_SET_ANIMATION_SPEED) {
1816                 m_animation_speed = readF32(is);
1817                 updateAnimationSpeed();
1818         } else if (cmd == AO_CMD_SET_BONE_POSITION) {
1819                 std::string bone = deSerializeString16(is);
1820                 v3f position = readV3F32(is);
1821                 v3f rotation = readV3F32(is);
1822                 m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
1823
1824                 // updateBonePosition(); now called every step
1825         } else if (cmd == AO_CMD_ATTACH_TO) {
1826                 u16 parent_id = readS16(is);
1827                 std::string bone = deSerializeString16(is);
1828                 v3f position = readV3F32(is);
1829                 v3f rotation = readV3F32(is);
1830                 bool force_visible = readU8(is); // Returns false for EOF
1831
1832                 setAttachment(parent_id, bone, position, rotation, force_visible);
1833         } else if (cmd == AO_CMD_PUNCHED) {
1834                 u16 result_hp = readU16(is);
1835
1836                 // Use this instead of the send damage to not interfere with prediction
1837                 s32 damage = (s32)m_hp - (s32)result_hp;
1838
1839                 m_hp = result_hp;
1840
1841                 if (m_is_local_player)
1842                         m_env->getLocalPlayer()->hp = m_hp;
1843
1844                 if (damage > 0)
1845                 {
1846                         if (m_hp == 0)
1847                         {
1848                                 // TODO: Execute defined fast response
1849                                 // As there is no definition, make a smoke puff
1850                                 ClientSimpleObject *simple = createSmokePuff(
1851                                                 m_smgr, m_env, m_position,
1852                                                 v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS);
1853                                 m_env->addSimpleObject(simple);
1854                         } else if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) {
1855                                 m_reset_textures_timer = 0.05;
1856                                 if(damage >= 2)
1857                                         m_reset_textures_timer += 0.05 * damage;
1858                                 updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
1859                         }
1860                 }
1861
1862                 if (m_hp == 0) {
1863                         // Same as 'Server::DiePlayer'
1864                         clearParentAttachment();
1865                         // Same as 'ObjectRef::l_remove'
1866                         if (!m_is_player)
1867                                 clearChildAttachments();
1868                 } else {
1869                         if (m_client->modsLoaded())
1870                                 m_client->getScript()->on_object_hp_change(m_id);
1871                 }
1872         } else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) {
1873                 m_armor_groups.clear();
1874                 int armor_groups_size = readU16(is);
1875                 for(int i=0; i<armor_groups_size; i++)
1876                 {
1877                         std::string name = deSerializeString16(is);
1878                         int rating = readS16(is);
1879                         m_armor_groups[name] = rating;
1880                 }
1881         } else if (cmd == AO_CMD_SPAWN_INFANT) {
1882                 u16 child_id = readU16(is);
1883                 u8 type = readU8(is); // maybe this will be useful later
1884                 (void)type;
1885
1886                 addAttachmentChild(child_id);
1887         } else if (cmd == AO_CMD_OBSOLETE1) {
1888                 // Don't do anything and also don't log a warning
1889         } else {
1890                 warningstream << FUNCTION_NAME
1891                         << ": unknown command or outdated client \""
1892                         << +cmd << "\"" << std::endl;
1893         }
1894 }
1895
1896 /* \pre punchitem != NULL
1897  */
1898 bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
1899                 float time_from_last_punch)
1900 {
1901         assert(punchitem);      // pre-condition
1902         const ToolCapabilities *toolcap =
1903                         &punchitem->getToolCapabilities(m_client->idef());
1904         PunchDamageResult result = getPunchDamage(
1905                         m_armor_groups,
1906                         toolcap,
1907                         punchitem,
1908                         time_from_last_punch);
1909
1910         if(result.did_punch && result.damage != 0)
1911         {
1912                 if(result.damage < m_hp)
1913                 {
1914                         m_hp -= result.damage;
1915                 } else {
1916                         m_hp = 0;
1917                         // TODO: Execute defined fast response
1918                         // As there is no definition, make a smoke puff
1919                         ClientSimpleObject *simple = createSmokePuff(
1920                                         m_smgr, m_env, m_position,
1921                                         v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS);
1922                         m_env->addSimpleObject(simple);
1923                 }
1924                 if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) {
1925                         m_reset_textures_timer = 0.05;
1926                         if (result.damage >= 2)
1927                                 m_reset_textures_timer += 0.05 * result.damage;
1928                         updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
1929                 }
1930         }
1931
1932         return false;
1933 }
1934
1935 std::string GenericCAO::debugInfoText()
1936 {
1937         std::ostringstream os(std::ios::binary);
1938         os<<"GenericCAO hp="<<m_hp<<"\n";
1939         os<<"armor={";
1940         for(ItemGroupList::const_iterator i = m_armor_groups.begin();
1941                         i != m_armor_groups.end(); ++i)
1942         {
1943                 os<<i->first<<"="<<i->second<<", ";
1944         }
1945         os<<"}";
1946         return os.str();
1947 }
1948
1949 void GenericCAO::updateMeshCulling()
1950 {
1951         if (!m_is_local_player)
1952                 return;
1953
1954         const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam");
1955
1956         if (m_meshnode && m_prop.visual == "upright_sprite") {
1957                 u32 buffers = m_meshnode->getMesh()->getMeshBufferCount();
1958                 for (u32 i = 0; i < buffers; i++) {
1959                         video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial();
1960                         // upright sprite has no backface culling
1961                         mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden);
1962                 }
1963                 return;
1964         }
1965
1966         scene::ISceneNode *node = getSceneNode();
1967         if (!node)
1968                 return;
1969
1970         if (hidden) {
1971                 // Hide the mesh by culling both front and
1972                 // back faces. Serious hackyness but it works for our
1973                 // purposes. This also preserves the skeletal armature.
1974                 node->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
1975                         true);
1976                 node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING,
1977                         true);
1978         } else {
1979                 // Restore mesh visibility.
1980                 node->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
1981                         m_prop.backface_culling);
1982                 node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING,
1983                         false);
1984         }
1985 }
1986
1987 // Prototype
1988 GenericCAO proto_GenericCAO(NULL, NULL);