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