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