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