Detach the player from entities on death. (#5077)
[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_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::detachFromParent()
207 {
208         ServerActiveObject *parent = NULL;
209         if (m_attachment_parent_id)
210                 parent = m_env->getActiveObject(m_attachment_parent_id);
211         setAttachment(NULL, "", v3f(0, 0, 0), v3f(0, 0, 0));
212         if (parent != NULL)
213                 parent->removeAttachmentChild(m_id);
214 }
215
216 void UnitSAO::addAttachmentChild(int child_id)
217 {
218         m_attachment_child_ids.insert(child_id);
219 }
220
221 void UnitSAO::removeAttachmentChild(int child_id)
222 {
223         m_attachment_child_ids.erase(child_id);
224 }
225
226 const UNORDERED_SET<int> &UnitSAO::getAttachmentChildIds()
227 {
228         return m_attachment_child_ids;
229 }
230
231 ObjectProperties* UnitSAO::accessObjectProperties()
232 {
233         return &m_prop;
234 }
235
236 void UnitSAO::notifyObjectPropertiesModified()
237 {
238         m_properties_sent = false;
239 }
240
241 /*
242         LuaEntitySAO
243 */
244
245 // Prototype (registers item for deserialization)
246 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
247
248 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
249                 const std::string &name, const std::string &state):
250         UnitSAO(env, pos),
251         m_init_name(name),
252         m_init_state(state),
253         m_registered(false),
254         m_velocity(0,0,0),
255         m_acceleration(0,0,0),
256         m_last_sent_yaw(0),
257         m_last_sent_position(0,0,0),
258         m_last_sent_velocity(0,0,0),
259         m_last_sent_position_timer(0),
260         m_last_sent_move_precision(0)
261 {
262         // Only register type if no environment supplied
263         if(env == NULL){
264                 ServerActiveObject::registerType(getType(), create);
265                 return;
266         }
267 }
268
269 LuaEntitySAO::~LuaEntitySAO()
270 {
271         if(m_registered){
272                 m_env->getScriptIface()->luaentity_Remove(m_id);
273         }
274
275         for (UNORDERED_SET<u32>::iterator it = m_attached_particle_spawners.begin();
276                 it != m_attached_particle_spawners.end(); ++it) {
277                 m_env->deleteParticleSpawner(*it, false);
278         }
279 }
280
281 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
282 {
283         ServerActiveObject::addedToEnvironment(dtime_s);
284
285         // Create entity from name
286         m_registered = m_env->getScriptIface()->
287                 luaentity_Add(m_id, m_init_name.c_str());
288
289         if(m_registered){
290                 // Get properties
291                 m_env->getScriptIface()->
292                         luaentity_GetProperties(m_id, &m_prop);
293                 // Initialize HP from properties
294                 m_hp = m_prop.hp_max;
295                 // Activate entity, supplying serialized state
296                 m_env->getScriptIface()->
297                         luaentity_Activate(m_id, m_init_state.c_str(), dtime_s);
298         } else {
299                 m_prop.infotext = m_init_name;
300         }
301 }
302
303 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
304                 const std::string &data)
305 {
306         std::string name;
307         std::string state;
308         s16 hp = 1;
309         v3f velocity;
310         float yaw = 0;
311         if(data != ""){
312                 std::istringstream is(data, std::ios::binary);
313                 // read version
314                 u8 version = readU8(is);
315                 // check if version is supported
316                 if(version == 0){
317                         name = deSerializeString(is);
318                         state = deSerializeLongString(is);
319                 }
320                 else if(version == 1){
321                         name = deSerializeString(is);
322                         state = deSerializeLongString(is);
323                         hp = readS16(is);
324                         velocity = readV3F1000(is);
325                         yaw = readF1000(is);
326                 }
327         }
328         // create object
329         infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
330                         <<state<<"\")"<<std::endl;
331         LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
332         sao->m_hp = hp;
333         sao->m_velocity = velocity;
334         sao->m_yaw = yaw;
335         return sao;
336 }
337
338 void LuaEntitySAO::step(float dtime, bool send_recommended)
339 {
340         if(!m_properties_sent)
341         {
342                 m_properties_sent = true;
343                 std::string str = getPropertyPacket();
344                 // create message and add to list
345                 ActiveObjectMessage aom(getId(), true, str);
346                 m_messages_out.push(aom);
347         }
348
349         // If attached, check that our parent is still there. If it isn't, detach.
350         if(m_attachment_parent_id && !isAttached())
351         {
352                 m_attachment_parent_id = 0;
353                 m_attachment_bone = "";
354                 m_attachment_position = v3f(0,0,0);
355                 m_attachment_rotation = v3f(0,0,0);
356                 sendPosition(false, true);
357         }
358
359         m_last_sent_position_timer += dtime;
360
361         // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
362         // If the object gets detached this comes into effect automatically from the last known origin
363         if(isAttached())
364         {
365                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
366                 m_base_position = pos;
367                 m_velocity = v3f(0,0,0);
368                 m_acceleration = v3f(0,0,0);
369         }
370         else
371         {
372                 if(m_prop.physical){
373                         aabb3f box = m_prop.collisionbox;
374                         box.MinEdge *= BS;
375                         box.MaxEdge *= BS;
376                         collisionMoveResult moveresult;
377                         f32 pos_max_d = BS*0.25; // Distance per iteration
378                         v3f p_pos = m_base_position;
379                         v3f p_velocity = m_velocity;
380                         v3f p_acceleration = m_acceleration;
381                         moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
382                                         pos_max_d, box, m_prop.stepheight, dtime,
383                                         &p_pos, &p_velocity, p_acceleration,
384                                         this, m_prop.collideWithObjects);
385
386                         // Apply results
387                         m_base_position = p_pos;
388                         m_velocity = p_velocity;
389                         m_acceleration = p_acceleration;
390                 } else {
391                         m_base_position += dtime * m_velocity + 0.5 * dtime
392                                         * dtime * m_acceleration;
393                         m_velocity += dtime * m_acceleration;
394                 }
395
396                 if((m_prop.automatic_face_movement_dir) &&
397                                 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001))
398                 {
399                         float optimal_yaw = atan2(m_velocity.Z,m_velocity.X) * 180 / M_PI
400                                         + m_prop.automatic_face_movement_dir_offset;
401                         float max_rotation_delta =
402                                         dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
403
404                         if ((m_prop.automatic_face_movement_max_rotation_per_sec > 0) &&
405                                 (fabs(m_yaw - optimal_yaw) > max_rotation_delta)) {
406
407                                 m_yaw = optimal_yaw < m_yaw ? m_yaw - max_rotation_delta : m_yaw + max_rotation_delta;
408                         } else {
409                                 m_yaw = optimal_yaw;
410                         }
411                 }
412         }
413
414         if(m_registered){
415                 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
416         }
417
418         if(send_recommended == false)
419                 return;
420
421         if(!isAttached())
422         {
423                 // TODO: force send when acceleration changes enough?
424                 float minchange = 0.2*BS;
425                 if(m_last_sent_position_timer > 1.0){
426                         minchange = 0.01*BS;
427                 } else if(m_last_sent_position_timer > 0.2){
428                         minchange = 0.05*BS;
429                 }
430                 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
431                 move_d += m_last_sent_move_precision;
432                 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
433                 if(move_d > minchange || vel_d > minchange ||
434                                 fabs(m_yaw - m_last_sent_yaw) > 1.0){
435                         sendPosition(true, false);
436                 }
437         }
438
439         if(m_armor_groups_sent == false){
440                 m_armor_groups_sent = true;
441                 std::string str = gob_cmd_update_armor_groups(
442                                 m_armor_groups);
443                 // create message and add to list
444                 ActiveObjectMessage aom(getId(), true, str);
445                 m_messages_out.push(aom);
446         }
447
448         if(m_animation_sent == false){
449                 m_animation_sent = true;
450                 std::string str = gob_cmd_update_animation(
451                         m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
452                 // create message and add to list
453                 ActiveObjectMessage aom(getId(), true, str);
454                 m_messages_out.push(aom);
455         }
456
457         if(m_bone_position_sent == false){
458                 m_bone_position_sent = true;
459                 for (UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator
460                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
461                         std::string str = gob_cmd_update_bone_position((*ii).first,
462                                         (*ii).second.X, (*ii).second.Y);
463                         // create message and add to list
464                         ActiveObjectMessage aom(getId(), true, str);
465                         m_messages_out.push(aom);
466                 }
467         }
468
469         if(m_attachment_sent == false){
470                 m_attachment_sent = true;
471                 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
472                 // create message and add to list
473                 ActiveObjectMessage aom(getId(), true, str);
474                 m_messages_out.push(aom);
475         }
476 }
477
478 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
479 {
480         std::ostringstream os(std::ios::binary);
481
482         if(protocol_version >= 14)
483         {
484                 writeU8(os, 1); // version
485                 os<<serializeString(""); // name
486                 writeU8(os, 0); // is_player
487                 writeS16(os, getId()); //id
488                 writeV3F1000(os, m_base_position);
489                 writeF1000(os, m_yaw);
490                 writeS16(os, m_hp);
491
492                 std::ostringstream msg_os(std::ios::binary);
493                 msg_os << serializeLongString(getPropertyPacket()); // message 1
494                 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
495                 msg_os << serializeLongString(gob_cmd_update_animation(
496                         m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
497                 for (UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator
498                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
499                         msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
500                                         (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
501                 }
502                 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
503                         m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
504                 int message_count = 4 + m_bone_position.size();
505                 for (UNORDERED_SET<int>::const_iterator ii = m_attachment_child_ids.begin();
506                                 (ii != m_attachment_child_ids.end()); ++ii) {
507                         if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
508                                 message_count++;
509                                 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
510                                         obj->getClientInitializationData(protocol_version)));
511                         }
512                 }
513
514                 writeU8(os, message_count);
515                 os.write(msg_os.str().c_str(), msg_os.str().size());
516         }
517         else
518         {
519                 writeU8(os, 0); // version
520                 os<<serializeString(""); // name
521                 writeU8(os, 0); // is_player
522                 writeV3F1000(os, m_base_position);
523                 writeF1000(os, m_yaw);
524                 writeS16(os, m_hp);
525                 writeU8(os, 2); // number of messages stuffed in here
526                 os<<serializeLongString(getPropertyPacket()); // message 1
527                 os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
528         }
529
530         // return result
531         return os.str();
532 }
533
534 void LuaEntitySAO::getStaticData(std::string *result) const
535 {
536         verbosestream<<FUNCTION_NAME<<std::endl;
537         std::ostringstream os(std::ios::binary);
538         // version
539         writeU8(os, 1);
540         // name
541         os<<serializeString(m_init_name);
542         // state
543         if(m_registered){
544                 std::string state = m_env->getScriptIface()->
545                         luaentity_GetStaticdata(m_id);
546                 os<<serializeLongString(state);
547         } else {
548                 os<<serializeLongString(m_init_state);
549         }
550         // hp
551         writeS16(os, m_hp);
552         // velocity
553         writeV3F1000(os, m_velocity);
554         // yaw
555         writeF1000(os, m_yaw);
556         *result = os.str();
557 }
558
559 int LuaEntitySAO::punch(v3f dir,
560                 const ToolCapabilities *toolcap,
561                 ServerActiveObject *puncher,
562                 float time_from_last_punch)
563 {
564         if (!m_registered){
565                 // Delete unknown LuaEntities when punched
566                 m_removed = true;
567                 return 0;
568         }
569
570         // It's best that attachments cannot be punched
571         if (isAttached())
572                 return 0;
573
574         ItemStack *punchitem = NULL;
575         ItemStack punchitem_static;
576         if (puncher) {
577                 punchitem_static = puncher->getWieldedItem();
578                 punchitem = &punchitem_static;
579         }
580
581         PunchDamageResult result = getPunchDamage(
582                         m_armor_groups,
583                         toolcap,
584                         punchitem,
585                         time_from_last_punch);
586
587         if (result.did_punch) {
588                 setHP(getHP() - result.damage);
589
590                 if (result.damage > 0) {
591                         std::string punchername = puncher ? puncher->getDescription() : "nil";
592
593                         actionstream << getDescription() << " punched by "
594                                         << punchername << ", damage " << result.damage
595                                         << " hp, health now " << getHP() << " hp" << std::endl;
596                 }
597
598                 std::string str = gob_cmd_punched(result.damage, getHP());
599                 // create message and add to list
600                 ActiveObjectMessage aom(getId(), true, str);
601                 m_messages_out.push(aom);
602         }
603
604         if (getHP() == 0)
605                 m_removed = true;
606
607         m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
608                         time_from_last_punch, toolcap, dir);
609
610         return result.wear;
611 }
612
613 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
614 {
615         if (!m_registered)
616                 return;
617         // It's best that attachments cannot be clicked
618         if (isAttached())
619                 return;
620         m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
621 }
622
623 void LuaEntitySAO::setPos(const v3f &pos)
624 {
625         if(isAttached())
626                 return;
627         m_base_position = pos;
628         sendPosition(false, true);
629 }
630
631 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
632 {
633         if(isAttached())
634                 return;
635         m_base_position = pos;
636         if(!continuous)
637                 sendPosition(true, true);
638 }
639
640 float LuaEntitySAO::getMinimumSavedMovement()
641 {
642         return 0.1 * BS;
643 }
644
645 std::string LuaEntitySAO::getDescription()
646 {
647         std::ostringstream os(std::ios::binary);
648         os<<"LuaEntitySAO at (";
649         os<<(m_base_position.X/BS)<<",";
650         os<<(m_base_position.Y/BS)<<",";
651         os<<(m_base_position.Z/BS);
652         os<<")";
653         return os.str();
654 }
655
656 void LuaEntitySAO::setHP(s16 hp)
657 {
658         if(hp < 0) hp = 0;
659         m_hp = hp;
660 }
661
662 s16 LuaEntitySAO::getHP() const
663 {
664         return m_hp;
665 }
666
667 void LuaEntitySAO::setVelocity(v3f velocity)
668 {
669         m_velocity = velocity;
670 }
671
672 v3f LuaEntitySAO::getVelocity()
673 {
674         return m_velocity;
675 }
676
677 void LuaEntitySAO::setAcceleration(v3f acceleration)
678 {
679         m_acceleration = acceleration;
680 }
681
682 v3f LuaEntitySAO::getAcceleration()
683 {
684         return m_acceleration;
685 }
686
687 void LuaEntitySAO::setTextureMod(const std::string &mod)
688 {
689         std::string str = gob_cmd_set_texture_mod(mod);
690         // create message and add to list
691         ActiveObjectMessage aom(getId(), true, str);
692         m_messages_out.push(aom);
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 }