Move HTTP request logging to infostream (#8526)
[oweals/minetest.git] / src / content_sao.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_sao.h"
21 #include "util/serialize.h"
22 #include "collision.h"
23 #include "environment.h"
24 #include "tool.h" // For ToolCapabilities
25 #include "gamedef.h"
26 #include "nodedef.h"
27 #include "remoteplayer.h"
28 #include "server.h"
29 #include "scripting_server.h"
30 #include "genericobject.h"
31 #include "settings.h"
32 #include <algorithm>
33 #include <cmath>
34
35 std::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types;
36
37 /*
38         TestSAO
39 */
40
41 class TestSAO : public ServerActiveObject
42 {
43 public:
44         TestSAO(ServerEnvironment *env, v3f pos):
45                 ServerActiveObject(env, pos),
46                 m_timer1(0),
47                 m_age(0)
48         {
49                 ServerActiveObject::registerType(getType(), create);
50         }
51         ActiveObjectType getType() const
52         { return ACTIVEOBJECT_TYPE_TEST; }
53
54         static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
55                         const std::string &data)
56         {
57                 return new TestSAO(env, pos);
58         }
59
60         void step(float dtime, bool send_recommended)
61         {
62                 m_age += dtime;
63                 if(m_age > 10)
64                 {
65                         m_pending_removal = true;
66                         return;
67                 }
68
69                 m_base_position.Y += dtime * BS * 2;
70                 if(m_base_position.Y > 8*BS)
71                         m_base_position.Y = 2*BS;
72
73                 if (!send_recommended)
74                         return;
75
76                 m_timer1 -= dtime;
77                 if(m_timer1 < 0.0)
78                 {
79                         m_timer1 += 0.125;
80
81                         std::string data;
82
83                         data += itos(0); // 0 = position
84                         data += " ";
85                         data += itos(m_base_position.X);
86                         data += " ";
87                         data += itos(m_base_position.Y);
88                         data += " ";
89                         data += itos(m_base_position.Z);
90
91                         ActiveObjectMessage aom(getId(), false, data);
92                         m_messages_out.push(aom);
93                 }
94         }
95
96         bool getCollisionBox(aabb3f *toset) const { return false; }
97
98         virtual bool getSelectionBox(aabb3f *toset) const { return false; }
99
100         bool collideWithObjects() const { return false; }
101
102 private:
103         float m_timer1;
104         float m_age;
105 };
106
107 // Prototype (registers item for deserialization)
108 TestSAO proto_TestSAO(NULL, v3f(0,0,0));
109
110 /*
111         UnitSAO
112  */
113
114 UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos):
115         ServerActiveObject(env, pos)
116 {
117         // Initialize something to armor groups
118         m_armor_groups["fleshy"] = 100;
119 }
120
121 ServerActiveObject *UnitSAO::getParent() const
122 {
123         if (!m_attachment_parent_id)
124                 return nullptr;
125         // Check if the parent still exists
126         ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
127
128         return obj;
129 }
130
131 void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
132 {
133         m_armor_groups = armor_groups;
134         m_armor_groups_sent = false;
135 }
136
137 const ItemGroupList &UnitSAO::getArmorGroups()
138 {
139         return m_armor_groups;
140 }
141
142 void UnitSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
143 {
144         // store these so they can be updated to clients
145         m_animation_range = frame_range;
146         m_animation_speed = frame_speed;
147         m_animation_blend = frame_blend;
148         m_animation_loop = frame_loop;
149         m_animation_sent = false;
150 }
151
152 void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop)
153 {
154         *frame_range = m_animation_range;
155         *frame_speed = m_animation_speed;
156         *frame_blend = m_animation_blend;
157         *frame_loop = m_animation_loop;
158 }
159
160 void UnitSAO::setAnimationSpeed(float frame_speed)
161 {
162         m_animation_speed = frame_speed;
163         m_animation_speed_sent = false;
164 }
165
166 void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
167 {
168         // store these so they can be updated to clients
169         m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
170         m_bone_position_sent = false;
171 }
172
173 void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
174 {
175         *position = m_bone_position[bone].X;
176         *rotation = m_bone_position[bone].Y;
177 }
178
179 void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
180 {
181         // Attachments need to be handled on both the server and client.
182         // If we just attach on the server, we can only copy the position of the parent. Attachments
183         // are still sent to clients at an interval so players might see them lagging, plus we can't
184         // read and attach to skeletal bones.
185         // If we just attach on the client, the server still sees the child at its original location.
186         // This breaks some things so we also give the server the most accurate representation
187         // even if players only see the client changes.
188
189         int old_parent = m_attachment_parent_id;
190         m_attachment_parent_id = parent_id;
191         m_attachment_bone = bone;
192         m_attachment_position = position;
193         m_attachment_rotation = rotation;
194         m_attachment_sent = false;
195
196         if (parent_id != old_parent) {
197                 onDetach(old_parent);
198                 onAttach(parent_id);
199         }
200 }
201
202 void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
203         v3f *rotation)
204 {
205         *parent_id = m_attachment_parent_id;
206         *bone = m_attachment_bone;
207         *position = m_attachment_position;
208         *rotation = m_attachment_rotation;
209 }
210
211 void UnitSAO::clearChildAttachments()
212 {
213         for (int child_id : m_attachment_child_ids) {
214                 // Child can be NULL if it was deleted earlier
215                 if (ServerActiveObject *child = m_env->getActiveObject(child_id))
216                         child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
217         }
218         m_attachment_child_ids.clear();
219 }
220
221 void UnitSAO::clearParentAttachment()
222 {
223         ServerActiveObject *parent = nullptr;
224         if (m_attachment_parent_id) {
225                 parent = m_env->getActiveObject(m_attachment_parent_id);
226                 setAttachment(0, "", m_attachment_position, m_attachment_rotation);
227         } else {
228                 setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
229         }
230         // Do it
231         if (parent)
232                 parent->removeAttachmentChild(m_id);
233 }
234
235 void UnitSAO::addAttachmentChild(int child_id)
236 {
237         m_attachment_child_ids.insert(child_id);
238 }
239
240 void UnitSAO::removeAttachmentChild(int child_id)
241 {
242         m_attachment_child_ids.erase(child_id);
243 }
244
245 const std::unordered_set<int> &UnitSAO::getAttachmentChildIds()
246 {
247         return m_attachment_child_ids;
248 }
249
250 void UnitSAO::onAttach(int parent_id)
251 {
252         if (!parent_id)
253                 return;
254
255         ServerActiveObject *parent = m_env->getActiveObject(parent_id);
256
257         if (!parent || parent->isGone())
258                 return; // Do not try to notify soon gone parent
259
260         if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) {
261                 // Call parent's on_attach field
262                 m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this);
263         }
264 }
265
266 void UnitSAO::onDetach(int parent_id)
267 {
268         if (!parent_id)
269                 return;
270
271         ServerActiveObject *parent = m_env->getActiveObject(parent_id);
272         if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
273                 m_env->getScriptIface()->luaentity_on_detach(m_id, parent);
274
275         if (!parent || parent->isGone())
276                 return; // Do not try to notify soon gone parent
277
278         if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
279                 m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this);
280 }
281
282 ObjectProperties* UnitSAO::accessObjectProperties()
283 {
284         return &m_prop;
285 }
286
287 void UnitSAO::notifyObjectPropertiesModified()
288 {
289         m_properties_sent = false;
290 }
291
292 /*
293         LuaEntitySAO
294 */
295
296 // Prototype (registers item for deserialization)
297 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
298
299 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
300                 const std::string &name, const std::string &state):
301         UnitSAO(env, pos),
302         m_init_name(name),
303         m_init_state(state)
304 {
305         // Only register type if no environment supplied
306         if(env == NULL){
307                 ServerActiveObject::registerType(getType(), create);
308                 return;
309         }
310 }
311
312 LuaEntitySAO::~LuaEntitySAO()
313 {
314         if(m_registered){
315                 m_env->getScriptIface()->luaentity_Remove(m_id);
316         }
317
318         for (u32 attached_particle_spawner : m_attached_particle_spawners) {
319                 m_env->deleteParticleSpawner(attached_particle_spawner, false);
320         }
321 }
322
323 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
324 {
325         ServerActiveObject::addedToEnvironment(dtime_s);
326
327         // Create entity from name
328         m_registered = m_env->getScriptIface()->
329                 luaentity_Add(m_id, m_init_name.c_str());
330
331         if(m_registered){
332                 // Get properties
333                 m_env->getScriptIface()->
334                         luaentity_GetProperties(m_id, &m_prop);
335                 // Initialize HP from properties
336                 m_hp = m_prop.hp_max;
337                 // Activate entity, supplying serialized state
338                 m_env->getScriptIface()->
339                         luaentity_Activate(m_id, m_init_state, dtime_s);
340         } else {
341                 m_prop.infotext = m_init_name;
342         }
343 }
344
345 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
346                 const std::string &data)
347 {
348         std::string name;
349         std::string state;
350         u16 hp = 1;
351         v3f velocity;
352         v3f rotation;
353
354         while (!data.empty()) { // breakable, run for one iteration
355                 std::istringstream is(data, std::ios::binary);
356                 // 'version' does not allow to incrementally extend the parameter list thus
357                 // we need another variable to build on top of 'version=1'. Ugly hack but worksâ„¢
358                 u8 version2 = 0;
359                 u8 version = readU8(is);
360
361                 name = deSerializeString(is);
362                 state = deSerializeLongString(is);
363
364                 if (version < 1)
365                         break;
366
367                 hp = readU16(is);
368                 velocity = readV3F1000(is);
369                 // yaw must be yaw to be backwards-compatible
370                 rotation.Y = readF1000(is);
371
372                 if (is.good()) // EOF for old formats
373                         version2 = readU8(is);
374
375                 if (version2 < 1) // PROTOCOL_VERSION < 37
376                         break;
377
378                 // version2 >= 1
379                 rotation.X = readF1000(is);
380                 rotation.Z = readF1000(is);
381
382                 // if (version2 < 2)
383                 //     break;
384                 // <read new values>
385                 break;
386         }
387         // create object
388         infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\""
389                          << state << "\")" << std::endl;
390         LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
391         sao->m_hp = hp;
392         sao->m_velocity = velocity;
393         sao->m_rotation = rotation;
394         return sao;
395 }
396
397 void LuaEntitySAO::step(float dtime, bool send_recommended)
398 {
399         if(!m_properties_sent)
400         {
401                 m_properties_sent = true;
402                 std::string str = getPropertyPacket();
403                 // create message and add to list
404                 ActiveObjectMessage aom(getId(), true, str);
405                 m_messages_out.push(aom);
406         }
407
408         // If attached, check that our parent is still there. If it isn't, detach.
409         if(m_attachment_parent_id && !isAttached())
410         {
411                 m_attachment_parent_id = 0;
412                 m_attachment_bone = "";
413                 m_attachment_position = v3f(0,0,0);
414                 m_attachment_rotation = v3f(0,0,0);
415                 sendPosition(false, true);
416         }
417
418         m_last_sent_position_timer += dtime;
419
420         // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
421         // If the object gets detached this comes into effect automatically from the last known origin
422         if(isAttached())
423         {
424                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
425                 m_base_position = pos;
426                 m_velocity = v3f(0,0,0);
427                 m_acceleration = v3f(0,0,0);
428         }
429         else
430         {
431                 if(m_prop.physical){
432                         aabb3f box = m_prop.collisionbox;
433                         box.MinEdge *= BS;
434                         box.MaxEdge *= BS;
435                         collisionMoveResult moveresult;
436                         f32 pos_max_d = BS*0.25; // Distance per iteration
437                         v3f p_pos = m_base_position;
438                         v3f p_velocity = m_velocity;
439                         v3f p_acceleration = m_acceleration;
440                         moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
441                                         pos_max_d, box, m_prop.stepheight, dtime,
442                                         &p_pos, &p_velocity, p_acceleration,
443                                         this, m_prop.collideWithObjects);
444
445                         // Apply results
446                         m_base_position = p_pos;
447                         m_velocity = p_velocity;
448                         m_acceleration = p_acceleration;
449                 } else {
450                         m_base_position += dtime * m_velocity + 0.5 * dtime
451                                         * dtime * m_acceleration;
452                         m_velocity += dtime * m_acceleration;
453                 }
454
455                 if (m_prop.automatic_face_movement_dir &&
456                                 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
457                         float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
458                                 + m_prop.automatic_face_movement_dir_offset;
459                         float max_rotation_per_sec =
460                                         m_prop.automatic_face_movement_max_rotation_per_sec;
461
462                         if (max_rotation_per_sec > 0) {
463                                 m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
464                                 wrappedApproachShortest(m_rotation.Y, target_yaw,
465                                         dtime * max_rotation_per_sec, 360.f);
466                         } else {
467                                 // Negative values of max_rotation_per_sec mean disabled.
468                                 m_rotation.Y = target_yaw;
469                         }
470                 }
471         }
472
473         if(m_registered){
474                 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
475         }
476
477         if (!send_recommended)
478                 return;
479
480         if(!isAttached())
481         {
482                 // TODO: force send when acceleration changes enough?
483                 float minchange = 0.2*BS;
484                 if(m_last_sent_position_timer > 1.0){
485                         minchange = 0.01*BS;
486                 } else if(m_last_sent_position_timer > 0.2){
487                         minchange = 0.05*BS;
488                 }
489                 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
490                 move_d += m_last_sent_move_precision;
491                 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
492                 if (move_d > minchange || vel_d > minchange ||
493                                 std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
494                                 std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
495                                 std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
496
497                         sendPosition(true, false);
498                 }
499         }
500
501         if (!m_armor_groups_sent) {
502                 m_armor_groups_sent = true;
503                 std::string str = gob_cmd_update_armor_groups(
504                                 m_armor_groups);
505                 // create message and add to list
506                 ActiveObjectMessage aom(getId(), true, str);
507                 m_messages_out.push(aom);
508         }
509
510         if (!m_animation_sent) {
511                 m_animation_sent = true;
512                 std::string str = gob_cmd_update_animation(
513                         m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
514                 // create message and add to list
515                 ActiveObjectMessage aom(getId(), true, str);
516                 m_messages_out.push(aom);
517         }
518
519         if (!m_animation_speed_sent) {
520                 m_animation_speed_sent = true;
521                 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
522                 // create message and add to list
523                 ActiveObjectMessage aom(getId(), true, str);
524                 m_messages_out.push(aom);
525         }
526
527         if (!m_bone_position_sent) {
528                 m_bone_position_sent = true;
529                 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
530                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
531                         std::string str = gob_cmd_update_bone_position((*ii).first,
532                                         (*ii).second.X, (*ii).second.Y);
533                         // create message and add to list
534                         ActiveObjectMessage aom(getId(), true, str);
535                         m_messages_out.push(aom);
536                 }
537         }
538
539         if (!m_attachment_sent) {
540                 m_attachment_sent = true;
541                 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
542                 // create message and add to list
543                 ActiveObjectMessage aom(getId(), true, str);
544                 m_messages_out.push(aom);
545         }
546 }
547
548 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
549 {
550         std::ostringstream os(std::ios::binary);
551
552         // PROTOCOL_VERSION >= 37
553         writeU8(os, 1); // version
554         os << serializeString(""); // name
555         writeU8(os, 0); // is_player
556         writeU16(os, getId()); //id
557         writeV3F32(os, m_base_position);
558         writeV3F32(os, m_rotation);
559         writeU16(os, m_hp);
560
561         std::ostringstream msg_os(std::ios::binary);
562         msg_os << serializeLongString(getPropertyPacket()); // message 1
563         msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
564         msg_os << serializeLongString(gob_cmd_update_animation(
565                 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
566         for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
567                         ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
568                 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
569                                 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
570         }
571         msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
572                 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
573         int message_count = 4 + m_bone_position.size();
574         for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
575                         (ii != m_attachment_child_ids.end()); ++ii) {
576                 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
577                         message_count++;
578                         msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
579                                 obj->getClientInitializationData(protocol_version)));
580                 }
581         }
582
583         msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
584         message_count++;
585
586         writeU8(os, message_count);
587         os.write(msg_os.str().c_str(), msg_os.str().size());
588
589         // return result
590         return os.str();
591 }
592
593 void LuaEntitySAO::getStaticData(std::string *result) const
594 {
595         verbosestream<<FUNCTION_NAME<<std::endl;
596         std::ostringstream os(std::ios::binary);
597         // version must be 1 to keep backwards-compatibility. See version2
598         writeU8(os, 1);
599         // name
600         os<<serializeString(m_init_name);
601         // state
602         if(m_registered){
603                 std::string state = m_env->getScriptIface()->
604                         luaentity_GetStaticdata(m_id);
605                 os<<serializeLongString(state);
606         } else {
607                 os<<serializeLongString(m_init_state);
608         }
609         writeU16(os, m_hp);
610         writeV3F1000(os, m_velocity);
611         // yaw
612         writeF1000(os, m_rotation.Y);
613
614         // version2. Increase this variable for new values
615         writeU8(os, 1); // PROTOCOL_VERSION >= 37
616
617         writeF1000(os, m_rotation.X);
618         writeF1000(os, m_rotation.Z);
619
620         // <write new values>
621
622         *result = os.str();
623 }
624
625 int LuaEntitySAO::punch(v3f dir,
626                 const ToolCapabilities *toolcap,
627                 ServerActiveObject *puncher,
628                 float time_from_last_punch)
629 {
630         if (!m_registered) {
631                 // Delete unknown LuaEntities when punched
632                 m_pending_removal = true;
633                 return 0;
634         }
635
636         ItemStack *punchitem = NULL;
637         ItemStack punchitem_static;
638         if (puncher) {
639                 punchitem_static = puncher->getWieldedItem();
640                 punchitem = &punchitem_static;
641         }
642
643         PunchDamageResult result = getPunchDamage(
644                         m_armor_groups,
645                         toolcap,
646                         punchitem,
647                         time_from_last_punch);
648
649         bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
650                         time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
651
652         if (!damage_handled) {
653                 if (result.did_punch) {
654                         setHP((s32)getHP() - result.damage,
655                                 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
656
657                         if (result.damage > 0) {
658                                 std::string punchername = puncher ? puncher->getDescription() : "nil";
659
660                                 actionstream << getDescription() << " punched by "
661                                                 << punchername << ", damage " << result.damage
662                                                 << " hp, health now " << getHP() << " hp" << std::endl;
663                         }
664
665                         std::string str = gob_cmd_punched(getHP());
666                         // create message and add to list
667                         ActiveObjectMessage aom(getId(), true, str);
668                         m_messages_out.push(aom);
669                 }
670         }
671
672         if (getHP() == 0 && !isGone()) {
673                 m_pending_removal = true;
674                 clearParentAttachment();
675                 clearChildAttachments();
676                 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
677         }
678
679         return result.wear;
680 }
681
682 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
683 {
684         if (!m_registered)
685                 return;
686
687         m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
688 }
689
690 void LuaEntitySAO::setPos(const v3f &pos)
691 {
692         if(isAttached())
693                 return;
694         m_base_position = pos;
695         sendPosition(false, true);
696 }
697
698 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
699 {
700         if(isAttached())
701                 return;
702         m_base_position = pos;
703         if(!continuous)
704                 sendPosition(true, true);
705 }
706
707 float LuaEntitySAO::getMinimumSavedMovement()
708 {
709         return 0.1 * BS;
710 }
711
712 std::string LuaEntitySAO::getDescription()
713 {
714         std::ostringstream os(std::ios::binary);
715         os<<"LuaEntitySAO at (";
716         os<<(m_base_position.X/BS)<<",";
717         os<<(m_base_position.Y/BS)<<",";
718         os<<(m_base_position.Z/BS);
719         os<<")";
720         return os.str();
721 }
722
723 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
724 {
725         m_hp = rangelim(hp, 0, U16_MAX);
726 }
727
728 u16 LuaEntitySAO::getHP() const
729 {
730         return m_hp;
731 }
732
733 void LuaEntitySAO::setVelocity(v3f velocity)
734 {
735         m_velocity = velocity;
736 }
737
738 v3f LuaEntitySAO::getVelocity()
739 {
740         return m_velocity;
741 }
742
743 void LuaEntitySAO::setAcceleration(v3f acceleration)
744 {
745         m_acceleration = acceleration;
746 }
747
748 v3f LuaEntitySAO::getAcceleration()
749 {
750         return m_acceleration;
751 }
752
753 void LuaEntitySAO::setTextureMod(const std::string &mod)
754 {
755         std::string str = gob_cmd_set_texture_mod(mod);
756         m_current_texture_modifier = mod;
757         // create message and add to list
758         ActiveObjectMessage aom(getId(), true, str);
759         m_messages_out.push(aom);
760 }
761
762 std::string LuaEntitySAO::getTextureMod() const
763 {
764         return m_current_texture_modifier;
765 }
766
767 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
768                 bool select_horiz_by_yawpitch)
769 {
770         std::string str = gob_cmd_set_sprite(
771                 p,
772                 num_frames,
773                 framelength,
774                 select_horiz_by_yawpitch
775         );
776         // create message and add to list
777         ActiveObjectMessage aom(getId(), true, str);
778         m_messages_out.push(aom);
779 }
780
781 std::string LuaEntitySAO::getName()
782 {
783         return m_init_name;
784 }
785
786 std::string LuaEntitySAO::getPropertyPacket()
787 {
788         return gob_cmd_set_properties(m_prop);
789 }
790
791 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
792 {
793         // If the object is attached client-side, don't waste bandwidth sending its position to clients
794         if(isAttached())
795                 return;
796
797         m_last_sent_move_precision = m_base_position.getDistanceFrom(
798                         m_last_sent_position);
799         m_last_sent_position_timer = 0;
800         m_last_sent_position = m_base_position;
801         m_last_sent_velocity = m_velocity;
802         //m_last_sent_acceleration = m_acceleration;
803         m_last_sent_rotation = m_rotation;
804
805         float update_interval = m_env->getSendRecommendedInterval();
806
807         std::string str = gob_cmd_update_position(
808                 m_base_position,
809                 m_velocity,
810                 m_acceleration,
811                 m_rotation,
812                 do_interpolate,
813                 is_movement_end,
814                 update_interval
815         );
816         // create message and add to list
817         ActiveObjectMessage aom(getId(), false, str);
818         m_messages_out.push(aom);
819 }
820
821 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
822 {
823         if (m_prop.physical)
824         {
825                 //update collision box
826                 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
827                 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
828
829                 toset->MinEdge += m_base_position;
830                 toset->MaxEdge += m_base_position;
831
832                 return true;
833         }
834
835         return false;
836 }
837
838 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
839 {
840         if (!m_prop.is_visible || !m_prop.pointable) {
841                 return false;
842         }
843
844         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
845         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
846
847         return true;
848 }
849
850 bool LuaEntitySAO::collideWithObjects() const
851 {
852         return m_prop.collideWithObjects;
853 }
854
855 /*
856         PlayerSAO
857 */
858
859 // No prototype, PlayerSAO does not need to be deserialized
860
861 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
862                 bool is_singleplayer):
863         UnitSAO(env_, v3f(0,0,0)),
864         m_player(player_),
865         m_peer_id(peer_id_),
866         m_is_singleplayer(is_singleplayer)
867 {
868         assert(m_peer_id != 0); // pre-condition
869
870         m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
871         m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
872         m_prop.physical = false;
873         m_prop.weight = 75;
874         m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
875         m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
876         m_prop.pointable = true;
877         // Start of default appearance, this should be overwritten by Lua
878         m_prop.visual = "upright_sprite";
879         m_prop.visual_size = v3f(1, 2, 1);
880         m_prop.textures.clear();
881         m_prop.textures.emplace_back("player.png");
882         m_prop.textures.emplace_back("player_back.png");
883         m_prop.colors.clear();
884         m_prop.colors.emplace_back(255, 255, 255, 255);
885         m_prop.spritediv = v2s16(1,1);
886         m_prop.eye_height = 1.625f;
887         // End of default appearance
888         m_prop.is_visible = true;
889         m_prop.backface_culling = false;
890         m_prop.makes_footstep_sound = true;
891         m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
892         m_hp = m_prop.hp_max;
893         m_breath = m_prop.breath_max;
894         // Disable zoom in survival mode using a value of 0
895         m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
896 }
897
898 PlayerSAO::~PlayerSAO()
899 {
900         if(m_inventory != &m_player->inventory)
901                 delete m_inventory;
902 }
903
904 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
905 {
906         assert(player);
907         m_player = player;
908         m_privs = privs;
909         m_inventory = &m_player->inventory;
910 }
911
912 v3f PlayerSAO::getEyeOffset() const
913 {
914         return v3f(0, BS * m_prop.eye_height, 0);
915 }
916
917 std::string PlayerSAO::getDescription()
918 {
919         return std::string("player ") + m_player->getName();
920 }
921
922 // Called after id has been set and has been inserted in environment
923 void PlayerSAO::addedToEnvironment(u32 dtime_s)
924 {
925         ServerActiveObject::addedToEnvironment(dtime_s);
926         ServerActiveObject::setBasePosition(m_base_position);
927         m_player->setPlayerSAO(this);
928         m_player->setPeerId(m_peer_id);
929         m_last_good_position = m_base_position;
930 }
931
932 // Called before removing from environment
933 void PlayerSAO::removingFromEnvironment()
934 {
935         ServerActiveObject::removingFromEnvironment();
936         if (m_player->getPlayerSAO() == this) {
937                 unlinkPlayerSessionAndSave();
938                 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
939                         m_env->deleteParticleSpawner(attached_particle_spawner, false);
940                 }
941         }
942 }
943
944 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
945 {
946         std::ostringstream os(std::ios::binary);
947
948         // Protocol >= 15
949         writeU8(os, 1); // version
950         os << serializeString(m_player->getName()); // name
951         writeU8(os, 1); // is_player
952         writeS16(os, getId()); // id
953         writeV3F32(os, m_base_position);
954         writeV3F32(os, m_rotation);
955         writeU16(os, getHP());
956
957         std::ostringstream msg_os(std::ios::binary);
958         msg_os << serializeLongString(getPropertyPacket()); // message 1
959         msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
960         msg_os << serializeLongString(gob_cmd_update_animation(
961                 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
962         for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
963                         ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
964                 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
965                         (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
966         }
967         msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
968                 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
969         msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
970                         m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
971                         m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
972         // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
973         msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
974         int message_count = 6 + m_bone_position.size();
975         for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
976                         ii != m_attachment_child_ids.end(); ++ii) {
977                 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
978                         message_count++;
979                         msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
980                                 obj->getClientInitializationData(protocol_version)));
981                 }
982         }
983
984         writeU8(os, message_count);
985         os.write(msg_os.str().c_str(), msg_os.str().size());
986
987         // return result
988         return os.str();
989 }
990
991 void PlayerSAO::getStaticData(std::string * result) const
992 {
993         FATAL_ERROR("Deprecated function");
994 }
995
996 void PlayerSAO::step(float dtime, bool send_recommended)
997 {
998         if (m_drowning_interval.step(dtime, 2.0f)) {
999                 // Get nose/mouth position, approximate with eye position
1000                 v3s16 p = floatToInt(getEyePosition(), BS);
1001                 MapNode n = m_env->getMap().getNodeNoEx(p);
1002                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1003                 // If node generates drown
1004                 if (c.drowning > 0 && m_hp > 0) {
1005                         if (m_breath > 0)
1006                                 setBreath(m_breath - 1);
1007
1008                         // No more breath, damage player
1009                         if (m_breath == 0) {
1010                                 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1011                                 setHP(m_hp - c.drowning, reason);
1012                                 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1013                         }
1014                 }
1015         }
1016
1017         if (m_breathing_interval.step(dtime, 0.5f)) {
1018                 // Get nose/mouth position, approximate with eye position
1019                 v3s16 p = floatToInt(getEyePosition(), BS);
1020                 MapNode n = m_env->getMap().getNodeNoEx(p);
1021                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1022                 // If player is alive & no drowning & not in ignore, breathe
1023                 if (m_breath < m_prop.breath_max &&
1024                                 c.drowning == 0 && n.getContent() != CONTENT_IGNORE && m_hp > 0)
1025                         setBreath(m_breath + 1);
1026         }
1027
1028         if (m_node_hurt_interval.step(dtime, 1.0f)) {
1029                 u32 damage_per_second = 0;
1030                 std::string nodename;
1031                 // Lowest and highest damage points are 0.1 within collisionbox
1032                 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1033
1034                 // Sequence of damage points, starting 0.1 above feet and progressing
1035                 // upwards in 1 node intervals, stopping below top damage point.
1036                 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1037                         v3s16 p = floatToInt(m_base_position +
1038                                 v3f(0.0f, dam_height * BS, 0.0f), BS);
1039                         MapNode n = m_env->getMap().getNodeNoEx(p);
1040                         const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1041                         if (c.damage_per_second > damage_per_second) {
1042                                 damage_per_second = c.damage_per_second;
1043                                 nodename = c.name;
1044                         }
1045                 }
1046
1047                 // Top damage point
1048                 v3s16 ptop = floatToInt(m_base_position +
1049                         v3f(0.0f, dam_top * BS, 0.0f), BS);
1050                 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1051                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
1052                 if (c.damage_per_second > damage_per_second) {
1053                         damage_per_second = c.damage_per_second;
1054                         nodename = c.name;
1055                 }
1056
1057                 if (damage_per_second != 0 && m_hp > 0) {
1058                         s32 newhp = (s32)m_hp - (s32)damage_per_second;
1059                         PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
1060                         setHP(newhp, reason);
1061                         m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1062                 }
1063         }
1064
1065         if (!m_properties_sent) {
1066                 m_properties_sent = true;
1067                 std::string str = getPropertyPacket();
1068                 // create message and add to list
1069                 ActiveObjectMessage aom(getId(), true, str);
1070                 m_messages_out.push(aom);
1071         }
1072
1073         // If attached, check that our parent is still there. If it isn't, detach.
1074         if (m_attachment_parent_id && !isAttached()) {
1075                 m_attachment_parent_id = 0;
1076                 m_attachment_bone = "";
1077                 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1078                 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1079                 setBasePosition(m_last_good_position);
1080                 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1081         }
1082
1083         //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1084
1085         // Set lag pool maximums based on estimated lag
1086         const float LAG_POOL_MIN = 5.0f;
1087         float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1088         if(lag_pool_max < LAG_POOL_MIN)
1089                 lag_pool_max = LAG_POOL_MIN;
1090         m_dig_pool.setMax(lag_pool_max);
1091         m_move_pool.setMax(lag_pool_max);
1092
1093         // Increment cheat prevention timers
1094         m_dig_pool.add(dtime);
1095         m_move_pool.add(dtime);
1096         m_time_from_last_teleport += dtime;
1097         m_time_from_last_punch += dtime;
1098         m_nocheat_dig_time += dtime;
1099
1100         // Each frame, parent position is copied if the object is attached,
1101         // otherwise it's calculated normally.
1102         // If the object gets detached this comes into effect automatically from
1103         // the last known origin.
1104         if (isAttached()) {
1105                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1106                 m_last_good_position = pos;
1107                 setBasePosition(pos);
1108         }
1109
1110         if (!send_recommended)
1111                 return;
1112
1113         // If the object is attached client-side, don't waste bandwidth sending its
1114         // position or rotation to clients.
1115         if (m_position_not_sent && !isAttached()) {
1116                 m_position_not_sent = false;
1117                 float update_interval = m_env->getSendRecommendedInterval();
1118                 v3f pos;
1119                 if (isAttached()) // Just in case we ever do send attachment position too
1120                         pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1121                 else
1122                         pos = m_base_position;
1123
1124                 std::string str = gob_cmd_update_position(
1125                         pos,
1126                         v3f(0.0f, 0.0f, 0.0f),
1127                         v3f(0.0f, 0.0f, 0.0f),
1128                         m_rotation,
1129                         true,
1130                         false,
1131                         update_interval
1132                 );
1133                 // create message and add to list
1134                 ActiveObjectMessage aom(getId(), false, str);
1135                 m_messages_out.push(aom);
1136         }
1137
1138         if (!m_armor_groups_sent) {
1139                 m_armor_groups_sent = true;
1140                 std::string str = gob_cmd_update_armor_groups(
1141                                 m_armor_groups);
1142                 // create message and add to list
1143                 ActiveObjectMessage aom(getId(), true, str);
1144                 m_messages_out.push(aom);
1145         }
1146
1147         if (!m_physics_override_sent) {
1148                 m_physics_override_sent = true;
1149                 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1150                                 m_physics_override_jump, m_physics_override_gravity,
1151                                 m_physics_override_sneak, m_physics_override_sneak_glitch,
1152                                 m_physics_override_new_move);
1153                 // create message and add to list
1154                 ActiveObjectMessage aom(getId(), true, str);
1155                 m_messages_out.push(aom);
1156         }
1157
1158         if (!m_animation_sent) {
1159                 m_animation_sent = true;
1160                 std::string str = gob_cmd_update_animation(
1161                         m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1162                 // create message and add to list
1163                 ActiveObjectMessage aom(getId(), true, str);
1164                 m_messages_out.push(aom);
1165         }
1166
1167         if (!m_bone_position_sent) {
1168                 m_bone_position_sent = true;
1169                 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1170                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1171                         std::string str = gob_cmd_update_bone_position((*ii).first,
1172                                         (*ii).second.X, (*ii).second.Y);
1173                         // create message and add to list
1174                         ActiveObjectMessage aom(getId(), true, str);
1175                         m_messages_out.push(aom);
1176                 }
1177         }
1178
1179         if (!m_attachment_sent) {
1180                 m_attachment_sent = true;
1181                 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1182                                 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1183                 // create message and add to list
1184                 ActiveObjectMessage aom(getId(), true, str);
1185                 m_messages_out.push(aom);
1186         }
1187 }
1188
1189 void PlayerSAO::setBasePosition(const v3f &position)
1190 {
1191         if (m_player && position != m_base_position)
1192                 m_player->setDirty(true);
1193
1194         // This needs to be ran for attachments too
1195         ServerActiveObject::setBasePosition(position);
1196
1197         // Updating is not wanted/required for player migration
1198         if (m_env) {
1199                 m_position_not_sent = true;
1200         }
1201 }
1202
1203 void PlayerSAO::setPos(const v3f &pos)
1204 {
1205         if(isAttached())
1206                 return;
1207
1208         // Send mapblock of target location
1209         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
1210         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
1211
1212         setBasePosition(pos);
1213         // Movement caused by this command is always valid
1214         m_last_good_position = pos;
1215         m_move_pool.empty();
1216         m_time_from_last_teleport = 0.0;
1217         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1218 }
1219
1220 void PlayerSAO::moveTo(v3f pos, bool continuous)
1221 {
1222         if(isAttached())
1223                 return;
1224
1225         setBasePosition(pos);
1226         // Movement caused by this command is always valid
1227         m_last_good_position = pos;
1228         m_move_pool.empty();
1229         m_time_from_last_teleport = 0.0;
1230         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1231 }
1232
1233 void PlayerSAO::setPlayerYaw(const float yaw)
1234 {
1235         v3f rotation(0, yaw, 0);
1236         if (m_player && yaw != m_rotation.Y)
1237                 m_player->setDirty(true);
1238
1239         // Set player model yaw, not look view
1240         UnitSAO::setRotation(rotation);
1241 }
1242
1243 void PlayerSAO::setFov(const float fov)
1244 {
1245         if (m_player && fov != m_fov)
1246                 m_player->setDirty(true);
1247
1248         m_fov = fov;
1249 }
1250
1251 void PlayerSAO::setWantedRange(const s16 range)
1252 {
1253         if (m_player && range != m_wanted_range)
1254                 m_player->setDirty(true);
1255
1256         m_wanted_range = range;
1257 }
1258
1259 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1260 {
1261         setPlayerYaw(yaw);
1262         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1263 }
1264
1265 void PlayerSAO::setLookPitch(const float pitch)
1266 {
1267         if (m_player && pitch != m_pitch)
1268                 m_player->setDirty(true);
1269
1270         m_pitch = pitch;
1271 }
1272
1273 void PlayerSAO::setLookPitchAndSend(const float pitch)
1274 {
1275         setLookPitch(pitch);
1276         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1277 }
1278
1279 int PlayerSAO::punch(v3f dir,
1280         const ToolCapabilities *toolcap,
1281         ServerActiveObject *puncher,
1282         float time_from_last_punch)
1283 {
1284         if (!toolcap)
1285                 return 0;
1286
1287         // No effect if PvP disabled
1288         if (!g_settings->getBool("enable_pvp")) {
1289                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1290                         std::string str = gob_cmd_punched(getHP());
1291                         // create message and add to list
1292                         ActiveObjectMessage aom(getId(), true, str);
1293                         m_messages_out.push(aom);
1294                         return 0;
1295                 }
1296         }
1297
1298         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1299                         time_from_last_punch);
1300
1301         std::string punchername = "nil";
1302
1303         if (puncher != 0)
1304                 punchername = puncher->getDescription();
1305
1306         PlayerSAO *playersao = m_player->getPlayerSAO();
1307
1308         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1309                                 puncher, time_from_last_punch, toolcap, dir,
1310                                 hitparams.hp);
1311
1312         if (!damage_handled) {
1313                 setHP((s32)getHP() - (s32)hitparams.hp,
1314                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1315         } else { // override client prediction
1316                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1317                         std::string str = gob_cmd_punched(getHP());
1318                         // create message and add to list
1319                         ActiveObjectMessage aom(getId(), true, str);
1320                         m_messages_out.push(aom);
1321                 }
1322         }
1323
1324
1325         actionstream << "Player " << m_player->getName() << " punched by "
1326                         << punchername;
1327         if (!damage_handled) {
1328                 actionstream << ", damage " << hitparams.hp << " HP";
1329         } else {
1330                 actionstream << ", damage handled by lua";
1331         }
1332         actionstream << std::endl;
1333
1334         return hitparams.wear;
1335 }
1336
1337 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
1338 {
1339         s32 oldhp = m_hp;
1340
1341         hp = rangelim(hp, 0, m_prop.hp_max);
1342
1343         if (oldhp != hp) {
1344                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1345                 if (hp_change == 0)
1346                         return;
1347
1348                 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
1349         }
1350
1351         if (hp < oldhp && !g_settings->getBool("enable_damage"))
1352                 return;
1353
1354         m_hp = hp;
1355
1356         // Update properties on death
1357         if ((hp == 0) != (oldhp == 0))
1358                 m_properties_sent = false;
1359 }
1360
1361 void PlayerSAO::setBreath(const u16 breath, bool send)
1362 {
1363         if (m_player && breath != m_breath)
1364                 m_player->setDirty(true);
1365
1366         m_breath = rangelim(breath, 0, m_prop.breath_max);
1367
1368         if (send)
1369                 m_env->getGameDef()->SendPlayerBreath(this);
1370 }
1371
1372 Inventory* PlayerSAO::getInventory()
1373 {
1374         return m_inventory;
1375 }
1376 const Inventory* PlayerSAO::getInventory() const
1377 {
1378         return m_inventory;
1379 }
1380
1381 InventoryLocation PlayerSAO::getInventoryLocation() const
1382 {
1383         InventoryLocation loc;
1384         loc.setPlayer(m_player->getName());
1385         return loc;
1386 }
1387
1388 std::string PlayerSAO::getWieldList() const
1389 {
1390         return "main";
1391 }
1392
1393 ItemStack PlayerSAO::getWieldedItem() const
1394 {
1395         const Inventory *inv = getInventory();
1396         ItemStack ret;
1397         const InventoryList *mlist = inv->getList(getWieldList());
1398         if (mlist && getWieldIndex() < (s32)mlist->getSize())
1399                 ret = mlist->getItem(getWieldIndex());
1400         return ret;
1401 }
1402
1403 ItemStack PlayerSAO::getWieldedItemOrHand() const
1404 {
1405         const Inventory *inv = getInventory();
1406         ItemStack ret;
1407         const InventoryList *mlist = inv->getList(getWieldList());
1408         if (mlist && getWieldIndex() < (s32)mlist->getSize())
1409                 ret = mlist->getItem(getWieldIndex());
1410         if (ret.name.empty()) {
1411                 const InventoryList *hlist = inv->getList("hand");
1412                 if (hlist)
1413                         ret = hlist->getItem(0);
1414         }
1415         return ret;
1416 }
1417
1418 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1419 {
1420         Inventory *inv = getInventory();
1421         if (inv) {
1422                 InventoryList *mlist = inv->getList(getWieldList());
1423                 if (mlist) {
1424                         mlist->changeItem(getWieldIndex(), item);
1425                         return true;
1426                 }
1427         }
1428         return false;
1429 }
1430
1431 int PlayerSAO::getWieldIndex() const
1432 {
1433         return m_wield_index;
1434 }
1435
1436 void PlayerSAO::setWieldIndex(int i)
1437 {
1438         if(i != m_wield_index) {
1439                 m_wield_index = i;
1440         }
1441 }
1442
1443 void PlayerSAO::disconnected()
1444 {
1445         m_peer_id = 0;
1446         m_pending_removal = true;
1447 }
1448
1449 void PlayerSAO::unlinkPlayerSessionAndSave()
1450 {
1451         assert(m_player->getPlayerSAO() == this);
1452         m_player->setPeerId(PEER_ID_INEXISTENT);
1453         m_env->savePlayer(m_player);
1454         m_player->setPlayerSAO(NULL);
1455         m_env->removePlayer(m_player);
1456 }
1457
1458 std::string PlayerSAO::getPropertyPacket()
1459 {
1460         m_prop.is_visible = (true);
1461         return gob_cmd_set_properties(m_prop);
1462 }
1463
1464 bool PlayerSAO::checkMovementCheat()
1465 {
1466         if (isAttached() || m_is_singleplayer ||
1467                         g_settings->getBool("disable_anticheat")) {
1468                 m_last_good_position = m_base_position;
1469                 return false;
1470         }
1471
1472         bool cheated = false;
1473         /*
1474                 Check player movements
1475
1476                 NOTE: Actually the server should handle player physics like the
1477                 client does and compare player's position to what is calculated
1478                 on our side. This is required when eg. players fly due to an
1479                 explosion. Altough a node-based alternative might be possible
1480                 too, and much more lightweight.
1481         */
1482
1483         float player_max_walk = 0; // horizontal movement
1484         float player_max_jump = 0; // vertical upwards movement
1485
1486         if (m_privs.count("fast") != 0)
1487                 player_max_walk = m_player->movement_speed_fast; // Fast speed
1488         else
1489                 player_max_walk = m_player->movement_speed_walk; // Normal speed
1490         player_max_walk *= m_physics_override_speed;
1491         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1492         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1493         //        until this can be verified correctly, tolerate higher jumping speeds
1494         player_max_jump *= 2.0;
1495
1496         // Don't divide by zero!
1497         if (player_max_walk < 0.0001f)
1498                 player_max_walk = 0.0001f;
1499         if (player_max_jump < 0.0001f)
1500                 player_max_jump = 0.0001f;
1501
1502         v3f diff = (m_base_position - m_last_good_position);
1503         float d_vert = diff.Y;
1504         diff.Y = 0;
1505         float d_horiz = diff.getLength();
1506         float required_time = d_horiz / player_max_walk;
1507
1508         // FIXME: Checking downwards movement is not easily possible currently,
1509         //        the server could calculate speed differences to examine the gravity
1510         if (d_vert > 0) {
1511                 // In certain cases (water, ladders) walking speed is applied vertically
1512                 float s = MYMAX(player_max_jump, player_max_walk);
1513                 required_time = MYMAX(required_time, d_vert / s);
1514         }
1515
1516         if (m_move_pool.grab(required_time)) {
1517                 m_last_good_position = m_base_position;
1518         } else {
1519                 const float LAG_POOL_MIN = 5.0;
1520                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1521                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1522                 if (m_time_from_last_teleport > lag_pool_max) {
1523                         actionstream << "Player " << m_player->getName()
1524                                         << " moved too fast; resetting position"
1525                                         << std::endl;
1526                         cheated = true;
1527                 }
1528                 setBasePosition(m_last_good_position);
1529         }
1530         return cheated;
1531 }
1532
1533 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1534 {
1535         //update collision box
1536         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1537         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1538
1539         toset->MinEdge += m_base_position;
1540         toset->MaxEdge += m_base_position;
1541         return true;
1542 }
1543
1544 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1545 {
1546         if (!m_prop.is_visible || !m_prop.pointable) {
1547                 return false;
1548         }
1549
1550         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1551         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1552
1553         return true;
1554 }
1555
1556 float PlayerSAO::getZoomFOV() const
1557 {
1558         return m_prop.zoom_fov;
1559 }