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