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