]> git.lizzy.rs Git - minetest.git/blob - src/content_sao.cpp
Optimize usage of TOSERVER_GOTBLOCKS packet
[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() const
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) const
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() const
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, this, &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                         // TODO after a protocol bump: only send the object initialization data
579                         // to older clients (superfluous since this message exists)
580                         msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
581                                 obj->getClientInitializationData(protocol_version)));
582                 }
583         }
584
585         msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
586         message_count++;
587
588         writeU8(os, message_count);
589         os.write(msg_os.str().c_str(), msg_os.str().size());
590
591         // return result
592         return os.str();
593 }
594
595 void LuaEntitySAO::getStaticData(std::string *result) const
596 {
597         verbosestream<<FUNCTION_NAME<<std::endl;
598         std::ostringstream os(std::ios::binary);
599         // version must be 1 to keep backwards-compatibility. See version2
600         writeU8(os, 1);
601         // name
602         os<<serializeString(m_init_name);
603         // state
604         if(m_registered){
605                 std::string state = m_env->getScriptIface()->
606                         luaentity_GetStaticdata(m_id);
607                 os<<serializeLongString(state);
608         } else {
609                 os<<serializeLongString(m_init_state);
610         }
611         writeU16(os, m_hp);
612         writeV3F1000(os, m_velocity);
613         // yaw
614         writeF1000(os, m_rotation.Y);
615
616         // version2. Increase this variable for new values
617         writeU8(os, 1); // PROTOCOL_VERSION >= 37
618
619         writeF1000(os, m_rotation.X);
620         writeF1000(os, m_rotation.Z);
621
622         // <write new values>
623
624         *result = os.str();
625 }
626
627 int LuaEntitySAO::punch(v3f dir,
628                 const ToolCapabilities *toolcap,
629                 ServerActiveObject *puncher,
630                 float time_from_last_punch)
631 {
632         if (!m_registered) {
633                 // Delete unknown LuaEntities when punched
634                 m_pending_removal = true;
635                 return 0;
636         }
637
638         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
639
640         s32 old_hp = getHP();
641         const ItemStack &punchitem = puncher->getWieldedItem();
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                         std::string str = gob_cmd_punched(getHP());
658                         // create message and add to list
659                         ActiveObjectMessage aom(getId(), true, str);
660                         m_messages_out.push(aom);
661                 }
662         }
663
664         if (getHP() == 0 && !isGone()) {
665                 m_pending_removal = true;
666                 clearParentAttachment();
667                 clearChildAttachments();
668                 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
669         }
670
671         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
672                         ", hp=" << puncher->getHP() << ") punched " <<
673                         getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
674                         "), damage=" << (old_hp - (s32)getHP()) <<
675                         (damage_handled ? " (handled by Lua)" : "") << std::endl;
676
677         return result.wear;
678 }
679
680 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
681 {
682         if (!m_registered)
683                 return;
684
685         m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
686 }
687
688 void LuaEntitySAO::setPos(const v3f &pos)
689 {
690         if(isAttached())
691                 return;
692         m_base_position = pos;
693         sendPosition(false, true);
694 }
695
696 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
697 {
698         if(isAttached())
699                 return;
700         m_base_position = pos;
701         if(!continuous)
702                 sendPosition(true, true);
703 }
704
705 float LuaEntitySAO::getMinimumSavedMovement()
706 {
707         return 0.1 * BS;
708 }
709
710 std::string LuaEntitySAO::getDescription()
711 {
712         std::ostringstream os(std::ios::binary);
713         os<<"LuaEntitySAO at (";
714         os<<(m_base_position.X/BS)<<",";
715         os<<(m_base_position.Y/BS)<<",";
716         os<<(m_base_position.Z/BS);
717         os<<")";
718         return os.str();
719 }
720
721 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
722 {
723         m_hp = rangelim(hp, 0, U16_MAX);
724 }
725
726 u16 LuaEntitySAO::getHP() const
727 {
728         return m_hp;
729 }
730
731 void LuaEntitySAO::setVelocity(v3f velocity)
732 {
733         m_velocity = velocity;
734 }
735
736 v3f LuaEntitySAO::getVelocity()
737 {
738         return m_velocity;
739 }
740
741 void LuaEntitySAO::setAcceleration(v3f acceleration)
742 {
743         m_acceleration = acceleration;
744 }
745
746 v3f LuaEntitySAO::getAcceleration()
747 {
748         return m_acceleration;
749 }
750
751 void LuaEntitySAO::setTextureMod(const std::string &mod)
752 {
753         std::string str = gob_cmd_set_texture_mod(mod);
754         m_current_texture_modifier = mod;
755         // create message and add to list
756         ActiveObjectMessage aom(getId(), true, str);
757         m_messages_out.push(aom);
758 }
759
760 std::string LuaEntitySAO::getTextureMod() const
761 {
762         return m_current_texture_modifier;
763 }
764
765 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
766                 bool select_horiz_by_yawpitch)
767 {
768         std::string str = gob_cmd_set_sprite(
769                 p,
770                 num_frames,
771                 framelength,
772                 select_horiz_by_yawpitch
773         );
774         // create message and add to list
775         ActiveObjectMessage aom(getId(), true, str);
776         m_messages_out.push(aom);
777 }
778
779 std::string LuaEntitySAO::getName()
780 {
781         return m_init_name;
782 }
783
784 std::string LuaEntitySAO::getPropertyPacket()
785 {
786         return gob_cmd_set_properties(m_prop);
787 }
788
789 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
790 {
791         // If the object is attached client-side, don't waste bandwidth sending its position to clients
792         if(isAttached())
793                 return;
794
795         m_last_sent_move_precision = m_base_position.getDistanceFrom(
796                         m_last_sent_position);
797         m_last_sent_position_timer = 0;
798         m_last_sent_position = m_base_position;
799         m_last_sent_velocity = m_velocity;
800         //m_last_sent_acceleration = m_acceleration;
801         m_last_sent_rotation = m_rotation;
802
803         float update_interval = m_env->getSendRecommendedInterval();
804
805         std::string str = gob_cmd_update_position(
806                 m_base_position,
807                 m_velocity,
808                 m_acceleration,
809                 m_rotation,
810                 do_interpolate,
811                 is_movement_end,
812                 update_interval
813         );
814         // create message and add to list
815         ActiveObjectMessage aom(getId(), false, str);
816         m_messages_out.push(aom);
817 }
818
819 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
820 {
821         if (m_prop.physical)
822         {
823                 //update collision box
824                 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
825                 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
826
827                 toset->MinEdge += m_base_position;
828                 toset->MaxEdge += m_base_position;
829
830                 return true;
831         }
832
833         return false;
834 }
835
836 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
837 {
838         if (!m_prop.is_visible || !m_prop.pointable) {
839                 return false;
840         }
841
842         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
843         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
844
845         return true;
846 }
847
848 bool LuaEntitySAO::collideWithObjects() const
849 {
850         return m_prop.collideWithObjects;
851 }
852
853 /*
854         PlayerSAO
855 */
856
857 // No prototype, PlayerSAO does not need to be deserialized
858
859 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
860                 bool is_singleplayer):
861         UnitSAO(env_, v3f(0,0,0)),
862         m_player(player_),
863         m_peer_id(peer_id_),
864         m_is_singleplayer(is_singleplayer)
865 {
866         assert(m_peer_id != 0); // pre-condition
867
868         m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
869         m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
870         m_prop.physical = false;
871         m_prop.weight = 75;
872         m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
873         m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
874         m_prop.pointable = true;
875         // Start of default appearance, this should be overwritten by Lua
876         m_prop.visual = "upright_sprite";
877         m_prop.visual_size = v3f(1, 2, 1);
878         m_prop.textures.clear();
879         m_prop.textures.emplace_back("player.png");
880         m_prop.textures.emplace_back("player_back.png");
881         m_prop.colors.clear();
882         m_prop.colors.emplace_back(255, 255, 255, 255);
883         m_prop.spritediv = v2s16(1,1);
884         m_prop.eye_height = 1.625f;
885         // End of default appearance
886         m_prop.is_visible = true;
887         m_prop.backface_culling = false;
888         m_prop.makes_footstep_sound = true;
889         m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
890         m_hp = m_prop.hp_max;
891         m_breath = m_prop.breath_max;
892         // Disable zoom in survival mode using a value of 0
893         m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
894
895         if (!g_settings->getBool("enable_damage"))
896                 m_armor_groups["immortal"] = 1;
897 }
898
899 PlayerSAO::~PlayerSAO()
900 {
901         if(m_inventory != &m_player->inventory)
902                 delete m_inventory;
903 }
904
905 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
906 {
907         assert(player);
908         m_player = player;
909         m_privs = privs;
910         m_inventory = &m_player->inventory;
911 }
912
913 v3f PlayerSAO::getEyeOffset() const
914 {
915         return v3f(0, BS * m_prop.eye_height, 0);
916 }
917
918 std::string PlayerSAO::getDescription()
919 {
920         return std::string("player ") + m_player->getName();
921 }
922
923 // Called after id has been set and has been inserted in environment
924 void PlayerSAO::addedToEnvironment(u32 dtime_s)
925 {
926         ServerActiveObject::addedToEnvironment(dtime_s);
927         ServerActiveObject::setBasePosition(m_base_position);
928         m_player->setPlayerSAO(this);
929         m_player->setPeerId(m_peer_id);
930         m_last_good_position = m_base_position;
931 }
932
933 // Called before removing from environment
934 void PlayerSAO::removingFromEnvironment()
935 {
936         ServerActiveObject::removingFromEnvironment();
937         if (m_player->getPlayerSAO() == this) {
938                 unlinkPlayerSessionAndSave();
939                 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
940                         m_env->deleteParticleSpawner(attached_particle_spawner, false);
941                 }
942         }
943 }
944
945 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
946 {
947         std::ostringstream os(std::ios::binary);
948
949         // Protocol >= 15
950         writeU8(os, 1); // version
951         os << serializeString(m_player->getName()); // name
952         writeU8(os, 1); // is_player
953         writeS16(os, getId()); // id
954         writeV3F32(os, m_base_position);
955         writeV3F32(os, m_rotation);
956         writeU16(os, getHP());
957
958         std::ostringstream msg_os(std::ios::binary);
959         msg_os << serializeLongString(getPropertyPacket()); // message 1
960         msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
961         msg_os << serializeLongString(gob_cmd_update_animation(
962                 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
963         for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
964                         ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
965                 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
966                         (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
967         }
968         msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
969                 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
970         msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
971                         m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
972                         m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
973         // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
974         msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
975         int message_count = 6 + m_bone_position.size();
976         for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
977                         ii != m_attachment_child_ids.end(); ++ii) {
978                 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
979                         message_count++;
980                         msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
981                                 obj->getClientInitializationData(protocol_version)));
982                 }
983         }
984
985         writeU8(os, message_count);
986         os.write(msg_os.str().c_str(), msg_os.str().size());
987
988         // return result
989         return os.str();
990 }
991
992 void PlayerSAO::getStaticData(std::string * result) const
993 {
994         FATAL_ERROR("Deprecated function");
995 }
996
997 void PlayerSAO::step(float dtime, bool send_recommended)
998 {
999         if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
1000                 // Get nose/mouth position, approximate with eye position
1001                 v3s16 p = floatToInt(getEyePosition(), BS);
1002                 MapNode n = m_env->getMap().getNodeNoEx(p);
1003                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1004                 // If node generates drown
1005                 if (c.drowning > 0 && m_hp > 0) {
1006                         if (m_breath > 0)
1007                                 setBreath(m_breath - 1);
1008
1009                         // No more breath, damage player
1010                         if (m_breath == 0) {
1011                                 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1012                                 setHP(m_hp - c.drowning, reason);
1013                                 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1014                         }
1015                 }
1016         }
1017
1018         if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
1019                 // Get nose/mouth position, approximate with eye position
1020                 v3s16 p = floatToInt(getEyePosition(), BS);
1021                 MapNode n = m_env->getMap().getNodeNoEx(p);
1022                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1023                 // If player is alive & not drowning & not in ignore & not immortal, breathe
1024                 if (m_breath < m_prop.breath_max && c.drowning == 0 &&
1025                                 n.getContent() != CONTENT_IGNORE && m_hp > 0)
1026                         setBreath(m_breath + 1);
1027         }
1028
1029         if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
1030                 u32 damage_per_second = 0;
1031                 std::string nodename;
1032                 // Lowest and highest damage points are 0.1 within collisionbox
1033                 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1034
1035                 // Sequence of damage points, starting 0.1 above feet and progressing
1036                 // upwards in 1 node intervals, stopping below top damage point.
1037                 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1038                         v3s16 p = floatToInt(m_base_position +
1039                                 v3f(0.0f, dam_height * BS, 0.0f), BS);
1040                         MapNode n = m_env->getMap().getNodeNoEx(p);
1041                         const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1042                         if (c.damage_per_second > damage_per_second) {
1043                                 damage_per_second = c.damage_per_second;
1044                                 nodename = c.name;
1045                         }
1046                 }
1047
1048                 // Top damage point
1049                 v3s16 ptop = floatToInt(m_base_position +
1050                         v3f(0.0f, dam_top * BS, 0.0f), BS);
1051                 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1052                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
1053                 if (c.damage_per_second > damage_per_second) {
1054                         damage_per_second = c.damage_per_second;
1055                         nodename = c.name;
1056                 }
1057
1058                 if (damage_per_second != 0 && m_hp > 0) {
1059                         s32 newhp = (s32)m_hp - (s32)damage_per_second;
1060                         PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
1061                         setHP(newhp, reason);
1062                         m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1063                 }
1064         }
1065
1066         if (!m_properties_sent) {
1067                 m_properties_sent = true;
1068                 std::string str = getPropertyPacket();
1069                 // create message and add to list
1070                 ActiveObjectMessage aom(getId(), true, str);
1071                 m_messages_out.push(aom);
1072                 m_env->getScriptIface()->player_event(this, "properties_changed");
1073         }
1074
1075         // If attached, check that our parent is still there. If it isn't, detach.
1076         if (m_attachment_parent_id && !isAttached()) {
1077                 m_attachment_parent_id = 0;
1078                 m_attachment_bone = "";
1079                 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1080                 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1081                 setBasePosition(m_last_good_position);
1082                 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1083         }
1084
1085         //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1086
1087         // Set lag pool maximums based on estimated lag
1088         const float LAG_POOL_MIN = 5.0f;
1089         float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1090         if(lag_pool_max < LAG_POOL_MIN)
1091                 lag_pool_max = LAG_POOL_MIN;
1092         m_dig_pool.setMax(lag_pool_max);
1093         m_move_pool.setMax(lag_pool_max);
1094
1095         // Increment cheat prevention timers
1096         m_dig_pool.add(dtime);
1097         m_move_pool.add(dtime);
1098         m_time_from_last_teleport += dtime;
1099         m_time_from_last_punch += dtime;
1100         m_nocheat_dig_time += dtime;
1101
1102         // Each frame, parent position is copied if the object is attached,
1103         // otherwise it's calculated normally.
1104         // If the object gets detached this comes into effect automatically from
1105         // the last known origin.
1106         if (isAttached()) {
1107                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1108                 m_last_good_position = pos;
1109                 setBasePosition(pos);
1110         }
1111
1112         if (!send_recommended)
1113                 return;
1114
1115         // If the object is attached client-side, don't waste bandwidth sending its
1116         // position or rotation to clients.
1117         if (m_position_not_sent && !isAttached()) {
1118                 m_position_not_sent = false;
1119                 float update_interval = m_env->getSendRecommendedInterval();
1120                 v3f pos;
1121                 if (isAttached()) // Just in case we ever do send attachment position too
1122                         pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1123                 else
1124                         pos = m_base_position;
1125
1126                 std::string str = gob_cmd_update_position(
1127                         pos,
1128                         v3f(0.0f, 0.0f, 0.0f),
1129                         v3f(0.0f, 0.0f, 0.0f),
1130                         m_rotation,
1131                         true,
1132                         false,
1133                         update_interval
1134                 );
1135                 // create message and add to list
1136                 ActiveObjectMessage aom(getId(), false, str);
1137                 m_messages_out.push(aom);
1138         }
1139
1140         if (!m_armor_groups_sent) {
1141                 m_armor_groups_sent = true;
1142                 std::string str = gob_cmd_update_armor_groups(
1143                                 m_armor_groups);
1144                 // create message and add to list
1145                 ActiveObjectMessage aom(getId(), true, str);
1146                 m_messages_out.push(aom);
1147         }
1148
1149         if (!m_physics_override_sent) {
1150                 m_physics_override_sent = true;
1151                 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1152                                 m_physics_override_jump, m_physics_override_gravity,
1153                                 m_physics_override_sneak, m_physics_override_sneak_glitch,
1154                                 m_physics_override_new_move);
1155                 // create message and add to list
1156                 ActiveObjectMessage aom(getId(), true, str);
1157                 m_messages_out.push(aom);
1158         }
1159
1160         if (!m_animation_sent) {
1161                 m_animation_sent = true;
1162                 std::string str = gob_cmd_update_animation(
1163                         m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1164                 // create message and add to list
1165                 ActiveObjectMessage aom(getId(), true, str);
1166                 m_messages_out.push(aom);
1167         }
1168
1169         if (!m_bone_position_sent) {
1170                 m_bone_position_sent = true;
1171                 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1172                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1173                         std::string str = gob_cmd_update_bone_position((*ii).first,
1174                                         (*ii).second.X, (*ii).second.Y);
1175                         // create message and add to list
1176                         ActiveObjectMessage aom(getId(), true, str);
1177                         m_messages_out.push(aom);
1178                 }
1179         }
1180
1181         if (!m_attachment_sent) {
1182                 m_attachment_sent = true;
1183                 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1184                                 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1185                 // create message and add to list
1186                 ActiveObjectMessage aom(getId(), true, str);
1187                 m_messages_out.push(aom);
1188         }
1189 }
1190
1191 void PlayerSAO::setBasePosition(const v3f &position)
1192 {
1193         if (m_player && position != m_base_position)
1194                 m_player->setDirty(true);
1195
1196         // This needs to be ran for attachments too
1197         ServerActiveObject::setBasePosition(position);
1198
1199         // Updating is not wanted/required for player migration
1200         if (m_env) {
1201                 m_position_not_sent = true;
1202         }
1203 }
1204
1205 void PlayerSAO::setPos(const v3f &pos)
1206 {
1207         if(isAttached())
1208                 return;
1209
1210         // Send mapblock of target location
1211         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
1212         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
1213
1214         setBasePosition(pos);
1215         // Movement caused by this command is always valid
1216         m_last_good_position = pos;
1217         m_move_pool.empty();
1218         m_time_from_last_teleport = 0.0;
1219         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1220 }
1221
1222 void PlayerSAO::moveTo(v3f pos, bool continuous)
1223 {
1224         if(isAttached())
1225                 return;
1226
1227         setBasePosition(pos);
1228         // Movement caused by this command is always valid
1229         m_last_good_position = pos;
1230         m_move_pool.empty();
1231         m_time_from_last_teleport = 0.0;
1232         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1233 }
1234
1235 void PlayerSAO::setPlayerYaw(const float yaw)
1236 {
1237         v3f rotation(0, yaw, 0);
1238         if (m_player && yaw != m_rotation.Y)
1239                 m_player->setDirty(true);
1240
1241         // Set player model yaw, not look view
1242         UnitSAO::setRotation(rotation);
1243 }
1244
1245 void PlayerSAO::setFov(const float fov)
1246 {
1247         if (m_player && fov != m_fov)
1248                 m_player->setDirty(true);
1249
1250         m_fov = fov;
1251 }
1252
1253 void PlayerSAO::setWantedRange(const s16 range)
1254 {
1255         if (m_player && range != m_wanted_range)
1256                 m_player->setDirty(true);
1257
1258         m_wanted_range = range;
1259 }
1260
1261 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1262 {
1263         setPlayerYaw(yaw);
1264         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1265 }
1266
1267 void PlayerSAO::setLookPitch(const float pitch)
1268 {
1269         if (m_player && pitch != m_pitch)
1270                 m_player->setDirty(true);
1271
1272         m_pitch = pitch;
1273 }
1274
1275 void PlayerSAO::setLookPitchAndSend(const float pitch)
1276 {
1277         setLookPitch(pitch);
1278         m_env->getGameDef()->SendMovePlayer(m_peer_id);
1279 }
1280
1281 int PlayerSAO::punch(v3f dir,
1282         const ToolCapabilities *toolcap,
1283         ServerActiveObject *puncher,
1284         float time_from_last_punch)
1285 {
1286         if (!toolcap)
1287                 return 0;
1288
1289         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
1290
1291         // No effect if PvP disabled or if immortal
1292         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
1293                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1294                         std::string str = gob_cmd_punched(getHP());
1295                         // create message and add to list
1296                         ActiveObjectMessage aom(getId(), true, str);
1297                         m_messages_out.push(aom);
1298                         return 0;
1299                 }
1300         }
1301
1302         s32 old_hp = getHP();
1303         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1304                         time_from_last_punch);
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         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
1325                 ", hp=" << puncher->getHP() << ") punched " <<
1326                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
1327                 "), damage=" << (old_hp - (s32)getHP()) <<
1328                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
1329
1330         return hitparams.wear;
1331 }
1332
1333 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
1334 {
1335         s32 oldhp = m_hp;
1336
1337         hp = rangelim(hp, 0, m_prop.hp_max);
1338
1339         if (oldhp != hp) {
1340                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1341                 if (hp_change == 0)
1342                         return;
1343
1344                 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
1345         }
1346
1347         if (hp < oldhp && isImmortal())
1348                 return;
1349
1350         m_hp = hp;
1351
1352         // Update properties on death
1353         if ((hp == 0) != (oldhp == 0))
1354                 m_properties_sent = false;
1355 }
1356
1357 void PlayerSAO::setBreath(const u16 breath, bool send)
1358 {
1359         if (m_player && breath != m_breath)
1360                 m_player->setDirty(true);
1361
1362         m_breath = rangelim(breath, 0, m_prop.breath_max);
1363
1364         if (send)
1365                 m_env->getGameDef()->SendPlayerBreath(this);
1366 }
1367
1368 Inventory* PlayerSAO::getInventory()
1369 {
1370         return m_inventory;
1371 }
1372 const Inventory* PlayerSAO::getInventory() const
1373 {
1374         return m_inventory;
1375 }
1376
1377 InventoryLocation PlayerSAO::getInventoryLocation() const
1378 {
1379         InventoryLocation loc;
1380         loc.setPlayer(m_player->getName());
1381         return loc;
1382 }
1383
1384 std::string PlayerSAO::getWieldList() const
1385 {
1386         return "main";
1387 }
1388
1389 ItemStack PlayerSAO::getWieldedItem() const
1390 {
1391         const Inventory *inv = getInventory();
1392         ItemStack ret;
1393         const InventoryList *mlist = inv->getList(getWieldList());
1394         if (mlist && getWieldIndex() < (s32)mlist->getSize())
1395                 ret = mlist->getItem(getWieldIndex());
1396         return ret;
1397 }
1398
1399 ItemStack PlayerSAO::getWieldedItemOrHand() const
1400 {
1401         const Inventory *inv = getInventory();
1402         ItemStack ret;
1403         const InventoryList *mlist = inv->getList(getWieldList());
1404         if (mlist && getWieldIndex() < (s32)mlist->getSize())
1405                 ret = mlist->getItem(getWieldIndex());
1406         if (ret.name.empty()) {
1407                 const InventoryList *hlist = inv->getList("hand");
1408                 if (hlist)
1409                         ret = hlist->getItem(0);
1410         }
1411         return ret;
1412 }
1413
1414 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1415 {
1416         Inventory *inv = getInventory();
1417         if (inv) {
1418                 InventoryList *mlist = inv->getList(getWieldList());
1419                 if (mlist) {
1420                         mlist->changeItem(getWieldIndex(), item);
1421                         return true;
1422                 }
1423         }
1424         return false;
1425 }
1426
1427 int PlayerSAO::getWieldIndex() const
1428 {
1429         return m_wield_index;
1430 }
1431
1432 void PlayerSAO::setWieldIndex(int i)
1433 {
1434         if(i != m_wield_index) {
1435                 m_wield_index = i;
1436         }
1437 }
1438
1439 void PlayerSAO::disconnected()
1440 {
1441         m_peer_id = 0;
1442         m_pending_removal = true;
1443 }
1444
1445 void PlayerSAO::unlinkPlayerSessionAndSave()
1446 {
1447         assert(m_player->getPlayerSAO() == this);
1448         m_player->setPeerId(PEER_ID_INEXISTENT);
1449         m_env->savePlayer(m_player);
1450         m_player->setPlayerSAO(NULL);
1451         m_env->removePlayer(m_player);
1452 }
1453
1454 std::string PlayerSAO::getPropertyPacket()
1455 {
1456         m_prop.is_visible = (true);
1457         return gob_cmd_set_properties(m_prop);
1458 }
1459
1460 bool PlayerSAO::checkMovementCheat()
1461 {
1462         if (isAttached() || m_is_singleplayer ||
1463                         g_settings->getBool("disable_anticheat")) {
1464                 m_last_good_position = m_base_position;
1465                 return false;
1466         }
1467
1468         bool cheated = false;
1469         /*
1470                 Check player movements
1471
1472                 NOTE: Actually the server should handle player physics like the
1473                 client does and compare player's position to what is calculated
1474                 on our side. This is required when eg. players fly due to an
1475                 explosion. Altough a node-based alternative might be possible
1476                 too, and much more lightweight.
1477         */
1478
1479         float player_max_walk = 0; // horizontal movement
1480         float player_max_jump = 0; // vertical upwards movement
1481
1482         if (m_privs.count("fast") != 0)
1483                 player_max_walk = m_player->movement_speed_fast; // Fast speed
1484         else
1485                 player_max_walk = m_player->movement_speed_walk; // Normal speed
1486         player_max_walk *= m_physics_override_speed;
1487         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1488         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1489         //        until this can be verified correctly, tolerate higher jumping speeds
1490         player_max_jump *= 2.0;
1491
1492         // Don't divide by zero!
1493         if (player_max_walk < 0.0001f)
1494                 player_max_walk = 0.0001f;
1495         if (player_max_jump < 0.0001f)
1496                 player_max_jump = 0.0001f;
1497
1498         v3f diff = (m_base_position - m_last_good_position);
1499         float d_vert = diff.Y;
1500         diff.Y = 0;
1501         float d_horiz = diff.getLength();
1502         float required_time = d_horiz / player_max_walk;
1503
1504         // FIXME: Checking downwards movement is not easily possible currently,
1505         //        the server could calculate speed differences to examine the gravity
1506         if (d_vert > 0) {
1507                 // In certain cases (water, ladders) walking speed is applied vertically
1508                 float s = MYMAX(player_max_jump, player_max_walk);
1509                 required_time = MYMAX(required_time, d_vert / s);
1510         }
1511
1512         if (m_move_pool.grab(required_time)) {
1513                 m_last_good_position = m_base_position;
1514         } else {
1515                 const float LAG_POOL_MIN = 5.0;
1516                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1517                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1518                 if (m_time_from_last_teleport > lag_pool_max) {
1519                         actionstream << "Player " << m_player->getName()
1520                                         << " moved too fast; resetting position"
1521                                         << std::endl;
1522                         cheated = true;
1523                 }
1524                 setBasePosition(m_last_good_position);
1525         }
1526         return cheated;
1527 }
1528
1529 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1530 {
1531         //update collision box
1532         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1533         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1534
1535         toset->MinEdge += m_base_position;
1536         toset->MaxEdge += m_base_position;
1537         return true;
1538 }
1539
1540 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1541 {
1542         if (!m_prop.is_visible || !m_prop.pointable) {
1543                 return false;
1544         }
1545
1546         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1547         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1548
1549         return true;
1550 }
1551
1552 float PlayerSAO::getZoomFOV() const
1553 {
1554         return m_prop.zoom_fov;
1555 }