diff options
-rw-r--r-- | doc/lua_api.txt | 2 | ||||
-rw-r--r-- | src/content_cao.cpp | 123 | ||||
-rw-r--r-- | src/content_sao.cpp | 102 | ||||
-rw-r--r-- | src/content_sao.h | 14 | ||||
-rw-r--r-- | src/guiFormSpecMenu.cpp | 2 | ||||
-rw-r--r-- | src/localplayer.cpp | 7 | ||||
-rw-r--r-- | src/localplayer.h | 4 | ||||
-rw-r--r-- | src/scriptapi.cpp | 17 | ||||
-rw-r--r-- | src/server.cpp | 4 | ||||
-rw-r--r-- | src/serverobject.h | 2 |
10 files changed, 171 insertions, 106 deletions
diff --git a/doc/lua_api.txt b/doc/lua_api.txt index cb84b5459..45ee7648b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1104,6 +1104,8 @@ methods: - set_wielded_item(item): replaces the wielded item, returns true if successful - set_armor_groups({group1=rating, group2=rating, ...}) - set_animations({x=1,y=1}, frame_speed=15, frame_blend=0) +- set_attachment(parent, "", {x=0,y=0,z=0}, {x=0,y=0,z=0}) +- set_detachment() - set_bone_posrot("", {x=0,y=0,z=0}, {x=0,y=0,z=0}) - set_properties(object property table) LuaEntitySAO-only: (no-op for other objects) diff --git a/src/content_cao.cpp b/src/content_cao.cpp index c2cce3d58..821862c9b 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -768,50 +768,26 @@ public: void removeFromScene(bool permanent) { - // bool permanent should be true when removing the object permanently and false when it's only refreshed (and comes back in a few frames) - - // If this object is being permanently removed, delete it from the attachments list - if(permanent) + if(permanent) // Should be true when removing the object permanently and false when refreshing (eg: updating visuals) { + // Detach this object's children for(std::vector<core::vector2d<int> >::iterator ii = attachment_list.begin(); ii != attachment_list.end(); ii++) { - if(ii->X == this->getId()) // This is the ID of our object + if(ii->Y == this->getId()) // Is a child of our object { - attachment_list.erase(ii); - break; + ii->Y = 0; + ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child + if(obj) + obj->updateParent(); } } - } - - // If this object is being removed, either permanently or just to refresh it, then all - // objects attached to it must be unparented else Irrlicht causes a segmentation fault. - for(std::vector<core::vector2d<int> >::iterator ii = attachment_list.begin(); ii != attachment_list.end(); ii++) - { - if(ii->Y == this->getId()) // This is a child of our parent + // Delete this object from the attachments list + for(std::vector<core::vector2d<int> >::iterator ii = attachment_list.begin(); ii != attachment_list.end(); ii++) { - ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child - if(obj) + if(ii->X == this->getId()) // Is our object { - if(permanent) - { - // The parent is being permanently removed, so the child stays detached - ii->Y = 0; - obj->updateParent(); - } - else - { - // The parent is being refreshed, detach our child enough to avoid bad memory reads - // This only stays into effect for a few frames, as addToScene will parent its children back - scene::IMeshSceneNode *m_child_meshnode = obj->getMeshSceneNode(); - scene::IAnimatedMeshSceneNode *m_child_animated_meshnode = obj->getAnimatedMeshSceneNode(); - scene::IBillboardSceneNode *m_child_spritenode = obj->getSpriteSceneNode(); - if(m_child_meshnode) - m_child_meshnode->setParent(m_smgr->getRootSceneNode()); - if(m_child_animated_meshnode) - m_child_animated_meshnode->setParent(m_smgr->getRootSceneNode()); - if(m_child_spritenode) - m_child_spritenode->setParent(m_smgr->getRootSceneNode()); - } + attachment_list.erase(ii); + break; } } } @@ -836,18 +812,6 @@ public: m_smgr = smgr; m_irr = irr; - // If this object has attachments and is being re-added after having been refreshed, parent its children back. - // The parent ID for this child hasn't been changed in attachment_list, so just update its attachments. - for(std::vector<core::vector2d<int> >::iterator ii = attachment_list.begin(); ii != attachment_list.end(); ii++) - { - if(ii->Y == this->getId()) // This is a child of our parent - { - ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child - if(obj) - obj->updateParent(); - } - } - if(m_meshnode != NULL || m_animated_meshnode != NULL || m_spritenode != NULL) return; @@ -1074,14 +1038,45 @@ public: if(m_visuals_expired && m_smgr && m_irr){ m_visuals_expired = false; + + // Attachments, part 1: All attached objects must be unparented first, or Irrlicht causes a segmentation fault + for(std::vector<core::vector2d<int> >::iterator ii = attachment_list.begin(); ii != attachment_list.end(); ii++) + { + if(ii->Y == this->getId()) // This is a child of our parent + { + ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child + if(obj) + { + scene::IMeshSceneNode *m_child_meshnode = obj->getMeshSceneNode(); + scene::IAnimatedMeshSceneNode *m_child_animated_meshnode = obj->getAnimatedMeshSceneNode(); + scene::IBillboardSceneNode *m_child_spritenode = obj->getSpriteSceneNode(); + if(m_child_meshnode) + m_child_meshnode->setParent(m_smgr->getRootSceneNode()); + if(m_child_animated_meshnode) + m_child_animated_meshnode->setParent(m_smgr->getRootSceneNode()); + if(m_child_spritenode) + m_child_spritenode->setParent(m_smgr->getRootSceneNode()); + } + } + } + removeFromScene(false); addToScene(m_smgr, m_gamedef->tsrc(), m_irr); updateAnimations(); updateBonePosRot(); updateAttachments(); - return; - } + // Attachments, part 2: Now that the parent has been refreshed, put its attachments back + for(std::vector<core::vector2d<int> >::iterator ii = attachment_list.begin(); ii != attachment_list.end(); ii++) + { + if(ii->Y == this->getId()) // This is a child of our parent + { + ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child + if(obj) + obj->updateParent(); + } + } + } if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht { // Set these for later @@ -1093,6 +1088,12 @@ public: m_position = m_spritenode->getAbsolutePosition(); m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); + + if(m_is_local_player) // Update local player attachment position + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->overridePosition = getParent()->getPosition(); + } } else { @@ -1422,6 +1423,11 @@ public: m_spritenode->setRotation(old_rotation); m_spritenode->updateAbsolutePosition(); } + if(m_is_local_player) + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = false; + } } else // Attach { @@ -1528,6 +1534,12 @@ public: } } } + if(m_is_local_player) + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = true; + player->overridePosition = m_attachment_position; + } } } @@ -1557,7 +1569,8 @@ public: } else if(cmd == GENERIC_CMD_UPDATE_POSITION) { - // Not sent by the server if the object is an attachment + // Not sent by the server if this object is an attachment. + // We might however get here if the server notices the object being detached before the client. m_position = readV3F1000(is); m_velocity = readV3F1000(is); m_acceleration = readV3F1000(is); @@ -1567,14 +1580,14 @@ public: bool is_end_position = readU8(is); float update_interval = readF1000(is); - if(getParent() != NULL) // Just in case - return; - // Place us a bit higher if we're physical, to not sink into // the ground due to sucky collision detection... if(m_prop.physical) m_position += v3f(0,0.002,0); - + + if(getParent() != NULL) // Just in case + return; + if(do_interpolate){ if(!m_prop.physical) pos_translator.update(m_position, is_end_position, update_interval); diff --git a/src/content_sao.cpp b/src/content_sao.cpp index c906383af..963e4b43a 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -218,7 +218,6 @@ public: if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS) { - // TODO: We shouldn't be sending this when the object is attached, but we can't check m_parent here setBasePosition(pos_f); m_last_sent_position = pos_f; @@ -387,7 +386,6 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) // Create entity from name lua_State *L = m_env->getLua(); m_registered = scriptapi_luaentity_add(L, m_id, m_init_name.c_str()); - m_parent = NULL; if(m_registered){ // Get properties @@ -434,6 +432,17 @@ ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos, return sao; } +bool LuaEntitySAO::isAttached() +{ + if(!m_attachment_parent_id) + return false; + // Check if the parent still exists + ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); + if(obj) + return true; + return false; +} + void LuaEntitySAO::step(float dtime, bool send_recommended) { if(!m_properties_sent) @@ -445,13 +454,23 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_messages_out.push_back(aom); } + // If attached, check that our parent is still there. If it isn't, detach. + if(m_attachment_parent_id && !isAttached()) + { + m_attachment_parent_id = 0; + m_attachment_bone = ""; + m_attachment_position = v3f(0,0,0); + m_attachment_rotation = v3f(0,0,0); + sendPosition(false, true); + } + m_last_sent_position_timer += dtime; - + // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally // If the object gets detached this comes into effect automatically from the last known origin - if(m_parent != NULL) + if(isAttached()) { - v3f pos = m_parent->getBasePosition(); + v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); m_base_position = pos; m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); @@ -491,7 +510,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) if(send_recommended == false) return; - if(m_parent != NULL) + if(!isAttached()) { // TODO: force send when acceleration changes enough? float minchange = 0.2*BS; @@ -608,7 +627,7 @@ int LuaEntitySAO::punch(v3f dir, } // It's best that attachments cannot be punched - if(m_parent != NULL) + if(isAttached()) return 0; ItemStack *punchitem = NULL; @@ -660,7 +679,7 @@ void LuaEntitySAO::rightClick(ServerActiveObject *clicker) void LuaEntitySAO::setPos(v3f pos) { - if(m_parent != NULL) + if(isAttached()) return; m_base_position = pos; sendPosition(false, true); @@ -668,7 +687,7 @@ void LuaEntitySAO::setPos(v3f pos) void LuaEntitySAO::moveTo(v3f pos, bool continuous) { - if(m_parent != NULL) + if(isAttached()) return; m_base_position = pos; if(!continuous) @@ -722,7 +741,7 @@ void LuaEntitySAO::setBonePosRot(std::string bone, v3f position, v3f rotation) m_animations_bone_sent = false; } -void LuaEntitySAO::setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation) +void LuaEntitySAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) { // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. Attachments @@ -732,11 +751,7 @@ void LuaEntitySAO::setAttachment(ServerActiveObject *parent, std::string bone, v // This breaks some things so we also give the server the most accurate representation // even if players only see the client changes. - // Server attachment: - m_parent = parent; - - // Client attachment: - m_attachment_parent_id = parent->getId(); + m_attachment_parent_id = parent_id; m_attachment_bone = bone; m_attachment_position = position; m_attachment_rotation = rotation; @@ -818,7 +833,7 @@ std::string LuaEntitySAO::getPropertyPacket() void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) { // If the object is attached client-side, don't waste bandwidth sending its position to clients - if(m_parent != NULL) + if(isAttached()) return; m_last_sent_move_precision = m_base_position.getDistanceFrom( @@ -872,7 +887,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_animations_bone_sent(false), m_attachment_sent(false), // public - m_teleported(false), + m_moved(false), m_inventory_not_sent(false), m_hp_not_sent(false), m_wielded_item_not_sent(false) @@ -919,7 +934,6 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); ServerActiveObject::setBasePosition(m_player->getPosition()); - m_parent = NULL; m_player->setPlayerSAO(this); m_player->peer_id = m_peer_id; m_last_good_position = m_player->getPosition(); @@ -979,6 +993,17 @@ std::string PlayerSAO::getStaticData() return ""; } +bool PlayerSAO::isAttached() +{ + if(!m_attachment_parent_id) + return false; + // Check if the parent still exists + ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); + if(obj) + return true; + return false; +} + void PlayerSAO::step(float dtime, bool send_recommended) { if(!m_properties_sent) @@ -990,14 +1015,25 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push_back(aom); } + // If attached, check that our parent is still there. If it isn't, detach. + if(m_attachment_parent_id && !isAttached()) + { + m_attachment_parent_id = 0; + m_attachment_bone = ""; + m_attachment_position = v3f(0,0,0); + m_attachment_rotation = v3f(0,0,0); + m_player->setPosition(m_last_good_position); + m_moved = true; + } + m_time_from_last_punch += dtime; m_nocheat_dig_time += dtime; // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally // If the object gets detached this comes into effect automatically from the last known origin - if(m_parent != NULL) + if(isAttached()) { - v3f pos = m_parent->getBasePosition(); + v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); m_last_good_position = pos; m_last_good_position_age = 0; m_player->setPosition(pos); @@ -1053,7 +1089,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) <<" moved too fast; resetting position" <<std::endl; m_player->setPosition(m_last_good_position); - m_teleported = true; + m_moved = true; } m_last_good_position_age = 0; } @@ -1064,13 +1100,13 @@ void PlayerSAO::step(float dtime, bool send_recommended) return; // If the object is attached client-side, don't waste bandwidth sending its position to clients - if(m_position_not_sent && m_parent == NULL) + if(m_position_not_sent && !isAttached()) { m_position_not_sent = false; float update_interval = m_env->getSendRecommendedInterval(); v3f pos; - if(m_parent != NULL) // Just in case we ever do send attachment position too - pos = m_parent->getBasePosition(); + if(isAttached()) // Just in case we ever do send attachment position too + pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); else pos = m_player->getPosition() + v3f(0,BS*1,0); std::string str = gob_cmd_update_position( @@ -1138,26 +1174,26 @@ void PlayerSAO::setBasePosition(const v3f &position) void PlayerSAO::setPos(v3f pos) { - if(m_parent != NULL) + if(isAttached()) return; m_player->setPosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; m_last_good_position_age = 0; // Force position change on client - m_teleported = true; + m_moved = true; } void PlayerSAO::moveTo(v3f pos, bool continuous) { - if(m_parent != NULL) + if(isAttached()) return; m_player->setPosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; m_last_good_position_age = 0; // Force position change on client - m_teleported = true; + m_moved = true; } int PlayerSAO::punch(v3f dir, @@ -1166,7 +1202,7 @@ int PlayerSAO::punch(v3f dir, float time_from_last_punch) { // It's best that attachments cannot be punched - if(m_parent != NULL) + if(isAttached()) return 0; if(!toolcap) @@ -1266,7 +1302,7 @@ void PlayerSAO::setBonePosRot(std::string bone, v3f position, v3f rotation) m_animations_bone_sent = false; } -void PlayerSAO::setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation) +void PlayerSAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) { // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. Attachments @@ -1276,11 +1312,7 @@ void PlayerSAO::setAttachment(ServerActiveObject *parent, std::string bone, v3f // This breaks some things so we also give the server the most accurate representation // even if players only see the client changes. - // Server attachment: - m_parent = parent; - - // Client attachment: - m_attachment_parent_id = parent->getId(); + m_attachment_parent_id = parent_id; m_attachment_bone = bone; m_attachment_position = position; m_attachment_rotation = rotation; diff --git a/src/content_sao.h b/src/content_sao.h index 9e79ec0e0..f6e0bac5b 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -46,6 +46,7 @@ public: virtual void addedToEnvironment(u32 dtime_s); static ServerActiveObject* create(ServerEnvironment *env, v3f pos, const std::string &data); + bool isAttached(); void step(float dtime, bool send_recommended); std::string getClientInitializationData(); std::string getStaticData(); @@ -63,7 +64,7 @@ public: void setArmorGroups(const ItemGroupList &armor_groups); void setAnimations(v2f frames, float frame_speed, float frame_blend); void setBonePosRot(std::string bone, v3f position, v3f rotation); - void setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation); + void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); /* LuaEntitySAO-specific */ @@ -107,8 +108,7 @@ private: std::map<std::string, core::vector2d<v3f> > m_animation_bone; bool m_animations_bone_sent; - - ServerActiveObject *m_parent; + int m_attachment_parent_id; std::string m_attachment_bone; v3f m_attachment_position; @@ -142,6 +142,7 @@ public: bool unlimitedTransferDistance() const; std::string getClientInitializationData(); std::string getStaticData(); + bool isAttached(); void step(float dtime, bool send_recommended); void setBasePosition(const v3f &position); void setPos(v3f pos); @@ -162,7 +163,7 @@ public: void setArmorGroups(const ItemGroupList &armor_groups); void setAnimations(v2f frames, float frame_speed, float frame_blend); void setBonePosRot(std::string bone, v3f position, v3f rotation); - void setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation); + void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); @@ -266,8 +267,7 @@ private: std::map<std::string, core::vector2d<v3f> > m_animation_bone; // stores position and rotation for each bone name bool m_animations_bone_sent; - - ServerActiveObject *m_parent; + int m_attachment_parent_id; std::string m_attachment_bone; v3f m_attachment_position; @@ -276,7 +276,7 @@ private: public: // Some flags used by Server - bool m_teleported; + bool m_moved; bool m_inventory_not_sent; bool m_hp_not_sent; bool m_wielded_item_not_sent; diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 4db020c11..7f638830f 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -714,7 +714,6 @@ void GUIFormSpecMenu::drawMenu() Draw backgrounds */ for(u32 i=0; i<m_backgrounds.size(); i++) - { const ImageDrawSpec &spec = m_backgrounds[i]; video::ITexture *texture = m_gamedef->tsrc()->getTextureRaw(spec.name); @@ -728,7 +727,6 @@ void GUIFormSpecMenu::drawMenu() core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), NULL/*&AbsoluteClippingRect*/, colors, true); - } /* Draw images diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 4b5e53fea..ecfa4467c 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -53,6 +53,13 @@ LocalPlayer::~LocalPlayer() void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, core::list<CollisionInfo> *collision_info) { + // Copy parent position if local player is attached + if(isAttached) + { + setPosition(overridePosition); + return; + } + INodeDefManager *nodemgr = m_gamedef->ndef(); v3f position = getPosition(); diff --git a/src/localplayer.h b/src/localplayer.h index fb57e6538..b613fdb0f 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -79,6 +79,10 @@ public: { return true; } + + bool isAttached; + + v3f overridePosition; void move(f32 dtime, Map &map, f32 pos_max_d, core::list<CollisionInfo> *collision_info); diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 3c505c881..9fd55b2e3 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -2723,7 +2723,6 @@ private: ServerActiveObject *co = getobject(ref); if(co == NULL) return 0; // Do it - v2f frames = v2f(1, 1); if(!lua_isnil(L, 2)) frames = read_v2f(L, 2); @@ -2744,7 +2743,6 @@ private: ServerActiveObject *co = getobject(ref); if(co == NULL) return 0; // Do it - std::string bone = ""; if(!lua_isnil(L, 2)) bone = lua_tostring(L, 2); @@ -2767,6 +2765,7 @@ private: ServerActiveObject *parent = getobject(parent_ref); if(co == NULL) return 0; if(parent == NULL) return 0; + // Do it std::string bone = ""; if(!lua_isnil(L, 3)) bone = lua_tostring(L, 3); @@ -2776,9 +2775,18 @@ private: v3f rotation = v3f(0, 0, 0); if(!lua_isnil(L, 5)) rotation = read_v3f(L, 5); - // Do it + co->setAttachment(parent->getId(), bone, position, rotation); + return 0; + } - co->setAttachment(parent, bone, position, rotation); + // set_detachment(self) + static int l_set_detachment(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if(co == NULL) return 0; + // Do it + co->setAttachment(0, "", v3f(0,0,0), v3f(0,0,0)); return 0; } @@ -3099,6 +3107,7 @@ const luaL_reg ObjectRef::methods[] = { method(ObjectRef, set_animations), method(ObjectRef, set_bone_posrot), method(ObjectRef, set_attachment), + method(ObjectRef, set_detachment), method(ObjectRef, set_properties), // LuaEntitySAO-only method(ObjectRef, setvelocity), diff --git a/src/server.cpp b/src/server.cpp index 930938ecb..1a401bb62 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1371,9 +1371,9 @@ void Server::AsyncRunStep() /* Send player inventories and HPs if necessary */ - if(playersao->m_teleported){ + if(playersao->m_moved){ SendMovePlayer(client->peer_id); - playersao->m_teleported = false; + playersao->m_moved = false; } if(playersao->m_inventory_not_sent){ UpdateCrafting(client->peer_id); diff --git a/src/serverobject.h b/src/serverobject.h index 3dcb99552..a0886ed1e 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -156,7 +156,7 @@ public: {} virtual void setBonePosRot(std::string bone, v3f position, v3f rotation) {} - virtual void setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation) + virtual void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) {} virtual ObjectProperties* accessObjectProperties() { return NULL; } |