diff options
Diffstat (limited to 'src')
46 files changed, 2075 insertions, 478 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8cdaa510d..e1639b46f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -202,6 +202,8 @@ set(common_SRCS sha1.cpp base64.cpp ban.cpp + clientserver.cpp + staticobject.cpp util/serialize.cpp util/directiontables.cpp util/numeric.cpp diff --git a/src/camera.cpp b/src/camera.cpp index f87f660e9..43f26cd8f 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -189,7 +189,7 @@ void Camera::step(f32 dtime) if (m_digging_button != -1) { - f32 offset = dtime * 3.5; + f32 offset = dtime * 4.5; float m_digging_anim_was = m_digging_anim; m_digging_anim += offset; if (m_digging_anim >= 1) @@ -336,13 +336,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, v2u32 screensize, if (m_digging_button != -1) { f32 digfrac = m_digging_anim; - wield_position.X -= 30 * sin(pow(digfrac, 0.8f) * M_PI); - wield_position.Y += 15 * sin(digfrac * 2 * M_PI); - wield_position.Z += 5 * digfrac; - + wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); + wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); + wield_position.Z += 25 * 0.5; + // Euler angles are PURE EVIL, so why not use quaternions? core::quaternion quat_begin(wield_rotation * core::DEGTORAD); - core::quaternion quat_end(v3f(90, -10, -130) * core::DEGTORAD); + core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD); core::quaternion quat_slerp; quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI)); quat_slerp.toEuler(wield_rotation); diff --git a/src/client.cpp b/src/client.cpp index e47bce142..4117a9130 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -41,6 +41,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "sound.h" #include "util/string.h" #include "hex.h" +#include "IMeshCache.h" +#include "util/serialize.h" static std::string getMediaCacheDir() { @@ -265,6 +267,7 @@ Client::Client( m_time_of_day_set(false), m_last_time_of_day_f(-1), m_time_of_day_update_timer(0), + m_recommended_send_interval(0.1), m_removed_sounds_check_timer(0) { m_packetcounter_timer = 0.0; @@ -498,8 +501,9 @@ void Client::step(float dtime) // [2] u8 SER_FMT_VER_HIGHEST // [3] u8[20] player_name // [23] u8[28] password (new in some version) - // [51] u16 client network protocol version (new in some version) - SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2); + // [51] u16 minimum supported network protocol version (added sometime) + // [53] u16 maximum supported network protocol version (added later than the previous one) + SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2+2); writeU16(&data[0], TOSERVER_INIT); writeU8(&data[2], SER_FMT_VER_HIGHEST); @@ -512,8 +516,8 @@ void Client::step(float dtime) memset((char*)&data[23], 0, PASSWORD_SIZE); snprintf((char*)&data[23], PASSWORD_SIZE, "%s", m_password.c_str()); - // This should be incremented in each version - writeU16(&data[51], PROTOCOL_VERSION); + writeU16(&data[51], CLIENT_PROTOCOL_VERSION_MIN); + writeU16(&data[53], CLIENT_PROTOCOL_VERSION_MAX); // Send as unreliable Send(0, data, false); @@ -655,7 +659,7 @@ void Client::step(float dtime) { float &counter = m_playerpos_send_timer; counter += dtime; - if(counter >= 0.2) + if(counter >= m_recommended_send_interval) { counter = 0.0; sendPlayerPos(); @@ -821,7 +825,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) if(name != "") { verbosestream<<"Client: Attempting to load image " - <<"file \""<<filename<<"\""<<std::endl; + <<"file \""<<filename<<"\""<<std::endl; io::IFileSystem *irrfs = m_device->getFileSystem(); video::IVideoDriver *vdrv = m_device->getVideoDriver(); @@ -855,11 +859,33 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) if(name != "") { verbosestream<<"Client: Attempting to load sound " - <<"file \""<<filename<<"\""<<std::endl; + <<"file \""<<filename<<"\""<<std::endl; m_sound->loadSoundData(name, data); return true; } + const char *model_ext[] = { + ".x", ".b3d", ".md2", ".obj", + NULL + }; + name = removeStringEnd(filename, model_ext); + if(name != "") + { + verbosestream<<"Client: Storing model into Irrlicht: " + <<"\""<<filename<<"\""<<std::endl; + + io::IFileSystem *irrfs = m_device->getFileSystem(); + io::IReadFile *rfile = irrfs->createMemoryReadFile( + *data_rw, data_rw.getSize(), filename.c_str()); + assert(rfile); + + scene::ISceneManager *smgr = m_device->getSceneManager(); + scene::IAnimatedMesh *mesh = smgr->getMesh(rfile); + smgr->getMeshCache()->addMesh(filename.c_str(), mesh); + + return true; + } + errorstream<<"Client: Don't know how to load file \"" <<filename<<"\""<<std::endl; return false; @@ -997,6 +1023,14 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) m_map_seed = readU64(&data[2+1+6]); infostream<<"Client: received map seed: "<<m_map_seed<<std::endl; } + + if(datasize >= 2+1+6+8+4) + { + // Get map seed + m_recommended_send_interval = readF1000(&data[2+1+6+8]); + infostream<<"Client: received recommended send interval " + <<m_recommended_send_interval<<std::endl; + } // Reply to server u32 replysize = 2; @@ -1961,7 +1995,7 @@ void Client::sendPlayerPos() v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100); s32 pitch = myplayer->getPitch() * 100; s32 yaw = myplayer->getYaw() * 100; - + u32 keyPressed=myplayer->keyPressed; /* Format: [0] u16 command @@ -1969,15 +2003,15 @@ void Client::sendPlayerPos() [2+12] v3s32 speed*100 [2+12+12] s32 pitch*100 [2+12+12+4] s32 yaw*100 + [2+12+12+4+4] u32 keyPressed */ - - SharedBuffer<u8> data(2+12+12+4+4); + SharedBuffer<u8> data(2+12+12+4+4+4); writeU16(&data[0], TOSERVER_PLAYERPOS); writeV3S32(&data[2], position); writeV3S32(&data[2+12], speed); writeS32(&data[2+12+12], pitch); - writeS32(&data[2+12+12+4], yaw); - + writeS32(&data[2+12+12+4], yaw); + writeU32(&data[2+12+12+4+4], keyPressed); // Send as unreliable Send(0, data, false); } diff --git a/src/client.h b/src/client.h index 154c8bb00..b4b7af7c3 100644 --- a/src/client.h +++ b/src/client.h @@ -382,6 +382,9 @@ private: float m_last_time_of_day_f; float m_time_of_day_update_timer; + // An interval for generally sending object positions and stuff + float m_recommended_send_interval; + // Sounds float m_removed_sounds_check_timer; // Mapping from server sound ids to our sound ids diff --git a/src/clientobject.cpp b/src/clientobject.cpp index 869bd7483..e1dbaf627 100644 --- a/src/clientobject.cpp +++ b/src/clientobject.cpp @@ -36,7 +36,7 @@ ClientActiveObject::ClientActiveObject(u16 id, IGameDef *gamedef, ClientActiveObject::~ClientActiveObject() { - removeFromScene(); + removeFromScene(true); } ClientActiveObject* ClientActiveObject::create(u8 type, IGameDef *gamedef, diff --git a/src/clientobject.h b/src/clientobject.h index 8b0b57147..852d2c76b 100644 --- a/src/clientobject.h +++ b/src/clientobject.h @@ -49,13 +49,19 @@ public: virtual void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, IrrlichtDevice *irr){} - virtual void removeFromScene(){} + virtual void removeFromScene(bool permanent){} // 0 <= light_at_pos <= LIGHT_SUN virtual void updateLight(u8 light_at_pos){} virtual v3s16 getLightPosition(){return v3s16(0,0,0);} virtual core::aabbox3d<f32>* getSelectionBox(){return NULL;} virtual core::aabbox3d<f32>* getCollisionBox(){return NULL;} virtual v3f getPosition(){return v3f(0,0,0);} + virtual scene::IMeshSceneNode *getMeshSceneNode(){return NULL;} + virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(){return NULL;} + virtual scene::IBillboardSceneNode *getSpriteSceneNode(){return NULL;} + virtual bool isPlayer(){return false;} + virtual bool isLocalPlayer(){return false;} + virtual void setAttachments(){} virtual bool doShowSelectionBox(){return true;} // Step object in time diff --git a/src/clientserver.cpp b/src/clientserver.cpp new file mode 100644 index 000000000..bd0a8ede0 --- /dev/null +++ b/src/clientserver.cpp @@ -0,0 +1,31 @@ +/* +Minetest-c55 +Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "clientserver.h" +#include "util/serialize.h" + +SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed) +{ + SharedBuffer<u8> data(2+2+4); + writeU16(&data[0], TOCLIENT_TIME_OF_DAY); + writeU16(&data[2], time); + writeF1000(&data[4], time_speed); + return data; +} + diff --git a/src/clientserver.h b/src/clientserver.h index 82e485d95..6f9396c02 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -1,6 +1,6 @@ /* Minetest-c55 -Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -20,7 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef CLIENTSERVER_HEADER #define CLIENTSERVER_HEADER -#include "util/serialize.h" +#include "util/pointer.h" + +SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed); /* changes by PROTOCOL_VERSION: @@ -67,10 +69,27 @@ with this program; if not, write to the Free Software Foundation, Inc., TOCLIENT_DETACHED_INVENTORY PROTOCOL_VERSION 13: InventoryList field "Width" (deserialization fails with old versions) + PROTOCOL_VERSION 14: + Added transfer of player pressed keys to the server + Added new messages for mesh and bone animation, as well as attachments + GENERIC_CMD_SET_ANIMATION + GENERIC_CMD_SET_BONE_POSITION + GENERIC_CMD_SET_ATTACHMENT + PROTOCOL_VERSION 15: + Serialization format changes */ -#define PROTOCOL_VERSION 13 +#define LATEST_PROTOCOL_VERSION 15 + +// Server's supported network protocol range +#define SERVER_PROTOCOL_VERSION_MIN 13 +#define SERVER_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION + +// Client's supported network protocol range +#define CLIENT_PROTOCOL_VERSION_MIN 13 +#define CLIENT_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION +// Constant that differentiates the protocol from random data and other protocols #define PROTOCOL_ID 0x4f457403 #define PASSWORD_SIZE 28 // Maximum password length. Allows for @@ -89,6 +108,7 @@ enum ToClientCommand [2] u8 deployed version [3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd [12] u64 map seed (new as of 2011-02-27) + [20] f1000 recommended send interval (in seconds) (new as of 14) NOTE: The position in here is deprecated; position is explicitly sent afterwards @@ -344,7 +364,8 @@ enum ToServerCommand [2] u8 SER_FMT_VER_HIGHEST [3] u8[20] player_name [23] u8[28] password (new in some version) - [51] u16 client network protocol version (new in some version) + [51] u16 minimum supported network protocol version (added sometime) + [53] u16 maximum supported network protocol version (added later than the previous one) */ TOSERVER_INIT2 = 0x11, @@ -366,6 +387,7 @@ enum ToServerCommand [2+12] v3s32 speed*100 [2+12+12] s32 pitch*100 [2+12+12+4] s32 yaw*100 + [2+12+12+4+4] u32 keyPressed */ TOSERVER_GOTBLOCKS = 0x24, @@ -551,14 +573,5 @@ enum ToServerCommand }; -inline SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed) -{ - SharedBuffer<u8> data(2+2+4); - writeU16(&data[0], TOCLIENT_TIME_OF_DAY); - writeU16(&data[2], time); - writeF1000(&data[4], time_speed); - return data; -} - #endif diff --git a/src/connection.cpp b/src/connection.cpp index 4f5d095e5..ed5a752be 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "util/numeric.h" #include "util/string.h" +#include "settings.h" namespace con { @@ -466,7 +467,10 @@ Peer::Peer(u16 a_id, Address a_address): m_sendtime_accu(0), m_max_packets_per_second(10), m_num_sent(0), - m_max_num_sent(0) + m_max_num_sent(0), + congestion_control_aim_rtt(0.2), + congestion_control_max_rate(400), + congestion_control_min_rate(10) { } Peer::~Peer() @@ -477,15 +481,15 @@ void Peer::reportRTT(float rtt) { if(rtt >= 0.0){ if(rtt < 0.01){ - if(m_max_packets_per_second < 400) + if(m_max_packets_per_second < congestion_control_max_rate) m_max_packets_per_second += 10; - } else if(rtt < 0.2){ - if(m_max_packets_per_second < 100) + } else if(rtt < congestion_control_aim_rtt){ + if(m_max_packets_per_second < congestion_control_max_rate) m_max_packets_per_second += 2; } else { m_max_packets_per_second *= 0.8; - if(m_max_packets_per_second < 10) - m_max_packets_per_second = 10; + if(m_max_packets_per_second < congestion_control_min_rate) + m_max_packets_per_second = congestion_control_min_rate; } } @@ -891,12 +895,24 @@ void Connection::receive() void Connection::runTimeouts(float dtime) { + float congestion_control_aim_rtt + = g_settings->getFloat("congestion_control_aim_rtt"); + float congestion_control_max_rate + = g_settings->getFloat("congestion_control_max_rate"); + float congestion_control_min_rate + = g_settings->getFloat("congestion_control_min_rate"); + core::list<u16> timeouted_peers; core::map<u16, Peer*>::Iterator j; j = m_peers.getIterator(); for(; j.atEnd() == false; j++) { Peer *peer = j.getNode()->getValue(); + + // Update congestion control values + peer->congestion_control_aim_rtt = congestion_control_aim_rtt; + peer->congestion_control_max_rate = congestion_control_max_rate; + peer->congestion_control_min_rate = congestion_control_min_rate; /* Check peer timeout diff --git a/src/connection.h b/src/connection.h index f88e813a3..f99cd1bf9 100644 --- a/src/connection.h +++ b/src/connection.h @@ -394,7 +394,11 @@ public: float m_max_packets_per_second; int m_num_sent; int m_max_num_sent; - + + // Updated from configuration by Connection + float congestion_control_aim_rtt; + float congestion_control_max_rate; + float congestion_control_min_rate; private: }; diff --git a/src/content_cao.cpp b/src/content_cao.cpp index cb14cf395..8229ded62 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -40,7 +40,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "util/mathconstants.h" #include "map.h" +#include "main.h" // g_settings #include <IMeshManipulator.h> +#include <IAnimatedMeshSceneNode.h> +#include <IBoneSceneNode.h> class Settings; struct ToolCapabilities; @@ -552,7 +555,8 @@ private: // Only set at initialization std::string m_name; bool m_is_player; - bool m_is_local_player; // determined locally + bool m_is_local_player; + int m_id; // Property-ish things ObjectProperties m_prop; // @@ -560,6 +564,7 @@ private: IrrlichtDevice *m_irr; core::aabbox3d<f32> m_selection_box; scene::IMeshSceneNode *m_meshnode; + scene::IAnimatedMeshSceneNode *m_animated_meshnode; scene::IBillboardSceneNode *m_spritenode; scene::ITextSceneNode* m_textnode; v3f m_position; @@ -573,6 +578,14 @@ private: v2s16 m_tx_basepos; bool m_initial_tx_basepos_set; bool m_tx_select_horiz_by_yawpitch; + v2f m_animation_range; + int m_animation_speed; + int m_animation_blend; + std::map<std::string, core::vector2d<v3f> > m_bone_position; // stores position and rotation for each bone name + std::string m_attachment_bone; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attached_to_local; int m_anim_frame; int m_anim_num_frames; float m_anim_framelength; @@ -582,6 +595,7 @@ private: bool m_visuals_expired; float m_step_distance_counter; u8 m_last_light; + bool m_is_visible; public: GenericCAO(IGameDef *gamedef, ClientEnvironment *env): @@ -589,11 +603,13 @@ public: // m_is_player(false), m_is_local_player(false), + m_id(0), // m_smgr(NULL), m_irr(NULL), m_selection_box(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.), m_meshnode(NULL), + m_animated_meshnode(NULL), m_spritenode(NULL), m_textnode(NULL), m_position(v3f(0,10*BS,0)), @@ -605,6 +621,14 @@ public: m_tx_basepos(0,0), m_initial_tx_basepos_set(false), m_tx_select_horiz_by_yawpitch(false), + m_animation_range(v2f(0,0)), + m_animation_speed(15), + m_animation_blend(0), + m_bone_position(std::map<std::string, core::vector2d<v3f> >()), + m_attachment_bone(""), + m_attachment_position(v3f(0,0,0)), + m_attachment_rotation(v3f(0,0,0)), + m_attached_to_local(false), m_anim_frame(0), m_anim_num_frames(1), m_anim_framelength(0.2), @@ -612,7 +636,8 @@ public: m_reset_textures_timer(-1), m_visuals_expired(false), m_step_distance_counter(0), - m_last_light(255) + m_last_light(255), + m_is_visible(false) { if(gamedef == NULL) ClientActiveObject::registerType(getType(), create); @@ -622,21 +647,36 @@ public: { infostream<<"GenericCAO: Got init data"<<std::endl; std::istringstream is(data, std::ios::binary); + int num_messages = 0; // version u8 version = readU8(is); // check version - if(version != 0){ + if(version == 1) // In PROTOCOL_VERSION 14 + { + m_name = deSerializeString(is); + m_is_player = readU8(is); + m_id = readS16(is); + m_position = readV3F1000(is); + m_yaw = readF1000(is); + m_hp = readS16(is); + num_messages = readU8(is); + } + else if(version == 0) // In PROTOCOL_VERSION 13 + { + m_name = deSerializeString(is); + m_is_player = readU8(is); + m_position = readV3F1000(is); + m_yaw = readF1000(is); + m_hp = readS16(is); + num_messages = readU8(is); + } + else + { errorstream<<"GenericCAO: Unsupported init data version" <<std::endl; return; } - m_name = deSerializeString(is); - m_is_player = readU8(is); - m_position = readV3F1000(is); - m_yaw = readF1000(is); - m_hp = readS16(is); - - int num_messages = readU8(is); + for(int i=0; i<num_messages; i++){ std::string message = deSerializeLongString(is); processMessage(message); @@ -668,21 +708,113 @@ public: } core::aabbox3d<f32>* getSelectionBox() { - if(!m_prop.is_visible || m_is_local_player) + if(!m_prop.is_visible || !m_is_visible || m_is_local_player || getParent() != NULL) return NULL; return &m_selection_box; } v3f getPosition() { + if(getParent() != NULL){ + if(m_meshnode) + return m_meshnode->getAbsolutePosition(); + if(m_animated_meshnode) + return m_animated_meshnode->getAbsolutePosition(); + if(m_spritenode) + return m_spritenode->getAbsolutePosition(); + return m_position; + } return pos_translator.vect_show; } - void removeFromScene() + scene::IMeshSceneNode *getMeshSceneNode() + { + if(m_meshnode) + return m_meshnode; + return NULL; + } + + scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() + { + if(m_animated_meshnode) + return m_animated_meshnode; + return NULL; + } + + scene::IBillboardSceneNode *getSpriteSceneNode() + { + if(m_spritenode) + return m_spritenode; + return NULL; + } + + bool isPlayer() + { + return m_is_player; + } + + bool isLocalPlayer() + { + return m_is_local_player; + } + + void setAttachments() + { + updateAttachments(); + } + + ClientActiveObject *getParent() { + ClientActiveObject *obj = NULL; + for(std::vector<core::vector2d<int> >::const_iterator cii = m_env->attachment_list.begin(); cii != m_env->attachment_list.end(); cii++) + { + if(cii->X == getId()){ // This ID is our child + if(cii->Y > 0){ // A parent ID exists for our child + if(cii->X != cii->Y){ // The parent and child ID are not the same + obj = m_env->getActiveObject(cii->Y); + } + } + break; + } + } + if(obj) + return obj; + return NULL; + } + + void removeFromScene(bool 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 = m_env->attachment_list.begin(); ii != m_env->attachment_list.end(); ii++) + { + if(ii->Y == getId()) // Is a child of our object + { + ii->Y = 0; + ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child + if(obj) + obj->setAttachments(); + } + } + // Delete this object from the attachments list + for(std::vector<core::vector2d<int> >::iterator ii = m_env->attachment_list.begin(); ii != m_env->attachment_list.end(); ii++) + { + if(ii->X == getId()) // Is our object + { + m_env->attachment_list.erase(ii); + break; + } + } + } + if(m_meshnode){ m_meshnode->remove(); m_meshnode = NULL; } + if(m_animated_meshnode){ + m_animated_meshnode->remove(); + m_animated_meshnode = NULL; + } if(m_spritenode){ m_spritenode->remove(); m_spritenode = NULL; @@ -695,7 +827,7 @@ public: m_smgr = smgr; m_irr = irr; - if(m_meshnode != NULL || m_spritenode != NULL) + if(m_meshnode != NULL || m_animated_meshnode != NULL || m_spritenode != NULL) return; m_visuals_expired = false; @@ -791,7 +923,34 @@ public: m_prop.visual_size.X)); u8 li = m_last_light; setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li)); - } else if(m_prop.visual == "wielditem"){ + + m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); + m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_meshnode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + } + else if(m_prop.visual == "mesh"){ + infostream<<"GenericCAO::addToScene(): mesh"<<std::endl; + scene::IAnimatedMesh *mesh = smgr->getMesh(m_prop.mesh.c_str()); + if(mesh) + { + m_animated_meshnode = smgr->addAnimatedMeshSceneNode(mesh, NULL); + m_animated_meshnode->animateJoints(); // Needed for some animations + m_animated_meshnode->setScale(v3f(m_prop.visual_size.X, + m_prop.visual_size.Y, + m_prop.visual_size.X)); + u8 li = m_last_light; + setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li)); + + m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); + m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_animated_meshnode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + } + else + errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl; + } + else if(m_prop.visual == "wielditem"){ infostream<<"GenericCAO::addToScene(): node"<<std::endl; infostream<<"textures: "<<m_prop.textures.size()<<std::endl; if(m_prop.textures.size() >= 1){ @@ -823,6 +982,8 @@ public: scene::ISceneNode *node = NULL; if(m_spritenode) node = m_spritenode; + else if(m_animated_meshnode) + node = m_animated_meshnode; else if(m_meshnode) node = m_meshnode; if(node && m_is_player && !m_is_local_player){ @@ -833,8 +994,11 @@ public: wname.c_str(), video::SColor(255,255,255,255), node); m_textnode->setPosition(v3f(0, BS*1.1, 0)); } - + updateNodePos(); + updateAnimation(); + updateBonePosition(); + updateAttachments(); } void expireVisuals() @@ -844,19 +1008,16 @@ public: void updateLight(u8 light_at_pos) { - bool is_visible = (m_hp != 0); u8 li = decode_light(light_at_pos); if(li != m_last_light){ m_last_light = li; video::SColor color(255,li,li,li); - if(m_meshnode){ + if(m_meshnode) setMeshColor(m_meshnode->getMesh(), color); - m_meshnode->setVisible(is_visible); - } - if(m_spritenode){ + if(m_animated_meshnode) + setMeshColor(m_animated_meshnode->getMesh(), color); + if(m_spritenode) m_spritenode->setColor(color); - m_spritenode->setVisible(is_visible); - } } } @@ -867,12 +1028,21 @@ public: void updateNodePos() { + if(getParent() != NULL) + return; + if(m_meshnode){ m_meshnode->setPosition(pos_translator.vect_show); v3f rot = m_meshnode->getRotation(); rot.Y = -m_yaw; m_meshnode->setRotation(rot); } + if(m_animated_meshnode){ + m_animated_meshnode->setPosition(pos_translator.vect_show); + v3f rot = m_animated_meshnode->getRotation(); + rot.Y = -m_yaw; + m_animated_meshnode->setRotation(rot); + } if(m_spritenode){ m_spritenode->setPosition(pos_translator.vect_show); } @@ -880,56 +1050,116 @@ public: void step(float dtime, ClientEnvironment *env) { - v3f lastpos = pos_translator.vect_show; - if(m_visuals_expired && m_smgr && m_irr){ m_visuals_expired = false; - removeFromScene(); + + // Attachments, part 1: All attached objects must be unparented first, or Irrlicht causes a segmentation fault + for(std::vector<core::vector2d<int> >::iterator ii = m_env->attachment_list.begin(); ii != m_env->attachment_list.end(); ii++) + { + if(ii->Y == 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); + + // Attachments, part 2: Now that the parent has been refreshed, put its attachments back + for(std::vector<core::vector2d<int> >::iterator ii = m_env->attachment_list.begin(); ii != m_env->attachment_list.end(); ii++) + { + if(ii->Y == getId()) // This is a child of our parent + { + ClientActiveObject *obj = m_env->getActiveObject(ii->X); // Get the object of the child + if(obj) + obj->setAttachments(); + } + } } - if(m_prop.physical){ - core::aabbox3d<f32> box = m_prop.collisionbox; - box.MinEdge *= BS; - box.MaxEdge *= BS; - collisionMoveResult moveresult; - f32 pos_max_d = BS*0.125; // Distance per iteration - f32 stepheight = 0; - v3f p_pos = m_position; - v3f p_velocity = m_velocity; - v3f p_acceleration = m_acceleration; - IGameDef *gamedef = env->getGameDef(); - moveresult = collisionMoveSimple(&env->getMap(), gamedef, - pos_max_d, box, stepheight, dtime, - p_pos, p_velocity, p_acceleration); - // Apply results - m_position = p_pos; - m_velocity = p_velocity; - m_acceleration = p_acceleration; - - bool is_end_position = moveresult.collides; - pos_translator.update(m_position, is_end_position, dtime); - pos_translator.translate(dtime); - updateNodePos(); - } else { - m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; - m_velocity += dtime * m_acceleration; - pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); - pos_translator.translate(dtime); - updateNodePos(); + // Make sure m_is_visible is always applied + if(m_meshnode) + m_meshnode->setVisible(m_is_visible); + if(m_animated_meshnode) + m_animated_meshnode->setVisible(m_is_visible); + if(m_spritenode) + m_spritenode->setVisible(m_is_visible); + if(m_textnode) + m_textnode->setVisible(m_is_visible); + + if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht + { + // Set these for later + m_position = getPosition(); + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + pos_translator.vect_show = m_position; + + if(m_is_local_player) // Update local player attachment position + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->overridePosition = getParent()->getPosition(); + } } + else + { + v3f lastpos = pos_translator.vect_show; + + if(m_prop.physical){ + core::aabbox3d<f32> box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.125; // Distance per iteration + f32 stepheight = 0; + v3f p_pos = m_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + IGameDef *gamedef = env->getGameDef(); + moveresult = collisionMoveSimple(&env->getMap(), gamedef, + pos_max_d, box, stepheight, dtime, + p_pos, p_velocity, p_acceleration); + // Apply results + m_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + + bool is_end_position = moveresult.collides; + pos_translator.update(m_position, is_end_position, dtime); + pos_translator.translate(dtime); + updateNodePos(); + } else { + m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); + pos_translator.translate(dtime); + updateNodePos(); + } - float moved = lastpos.getDistanceFrom(pos_translator.vect_show); - m_step_distance_counter += moved; - if(m_step_distance_counter > 1.5*BS){ - m_step_distance_counter = 0; - if(!m_is_local_player && m_prop.makes_footstep_sound){ - INodeDefManager *ndef = m_gamedef->ndef(); - v3s16 p = floatToInt(getPosition() + v3f(0, - (m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); - MapNode n = m_env->getMap().getNodeNoEx(p); - SimpleSoundSpec spec = ndef->get(n).sound_footstep; - m_gamedef->sound()->playSoundAt(spec, false, getPosition()); + float moved = lastpos.getDistanceFrom(pos_translator.vect_show); + m_step_distance_counter += moved; + if(m_step_distance_counter > 1.5*BS){ + m_step_distance_counter = 0; + if(!m_is_local_player && m_prop.makes_footstep_sound){ + INodeDefManager *ndef = m_gamedef->ndef(); + v3s16 p = floatToInt(getPosition() + v3f(0, + (m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + SimpleSoundSpec spec = ndef->get(n).sound_footstep; + m_gamedef->sound()->playSoundAt(spec, false, getPosition()); + } } } @@ -950,7 +1180,7 @@ public: updateTextures(""); } } - if(fabs(m_prop.automatic_rotate) > 0.001){ + if(getParent() == NULL && fabs(m_prop.automatic_rotate) > 0.001){ m_yaw += dtime * m_prop.automatic_rotate * 180 / M_PI; updateNodePos(); } @@ -1008,6 +1238,10 @@ public: { ITextureSource *tsrc = m_gamedef->tsrc(); + bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); + bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); + bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); + if(m_spritenode) { if(m_prop.visual == "sprite") @@ -1018,6 +1252,58 @@ public: texturestring += mod; m_spritenode->setMaterialTexture(0, tsrc->getTextureRaw(texturestring)); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() >= 1) + { + m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0]; + } + + m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + } + if(m_animated_meshnode) + { + if(m_prop.visual == "mesh") + { + for (u32 i = 0; i < m_prop.textures.size() && i < m_animated_meshnode->getMaterialCount(); ++i) + { + std::string texturestring = m_prop.textures[i]; + if(texturestring == "") + continue; // Empty texture string means don't modify that material + texturestring += mod; + video::ITexture* texture = tsrc->getTextureRaw(texturestring); + if(!texture) + { + errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl; + continue; + } + + // Set material flags and texture + m_animated_meshnode->setMaterialTexture(i, texture); + video::SMaterial& material = m_animated_meshnode->getMaterial(i); + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + + m_animated_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_animated_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_animated_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + for (u32 i = 0; i < m_prop.colors.size() && i < m_animated_meshnode->getMaterialCount(); ++i) + { + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + } } } if(m_meshnode) @@ -1044,6 +1330,20 @@ public: material.setTexture(0, atlas); material.getTextureMatrix(0).setTextureTranslate(pos.X, pos.Y); material.getTextureMatrix(0).setTextureScale(size.X, size.Y); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() > i) + { + m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; + m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; + m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + } + + m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } } else if(m_prop.visual == "upright_sprite") @@ -1057,6 +1357,20 @@ public: scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); buf->getMaterial().setTexture(0, tsrc->getTextureRaw(tname)); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() >= 1) + { + buf->getMaterial().AmbientColor = m_prop.colors[0]; + buf->getMaterial().DiffuseColor = m_prop.colors[0]; + buf->getMaterial().SpecularColor = m_prop.colors[0]; + } + + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } { std::string tname = "unknown_object.png"; @@ -1068,11 +1382,213 @@ public: scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); buf->getMaterial().setTexture(0, tsrc->getTextureRaw(tname)); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() >= 2) + { + buf->getMaterial().AmbientColor = m_prop.colors[1]; + buf->getMaterial().DiffuseColor = m_prop.colors[1]; + buf->getMaterial().SpecularColor = m_prop.colors[1]; + } + else if(m_prop.colors.size() >= 1) + { + buf->getMaterial().AmbientColor = m_prop.colors[0]; + buf->getMaterial().DiffuseColor = m_prop.colors[0]; + buf->getMaterial().SpecularColor = m_prop.colors[0]; + } + + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } } } } + void updateAnimation() + { + if(m_animated_meshnode == NULL) + return; + + m_animated_meshnode->setFrameLoop((int)m_animation_range.X, (int)m_animation_range.Y); + m_animated_meshnode->setAnimationSpeed(m_animation_speed); + m_animated_meshnode->setTransitionTime(m_animation_blend); + } + + void updateBonePosition() + { + if(!m_bone_position.size() || m_animated_meshnode == NULL) + return; + + m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render + for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string bone_name = (*ii).first; + v3f bone_pos = (*ii).second.X; + v3f bone_rot = (*ii).second.Y; + irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); + if(bone) + { + bone->setPosition(bone_pos); + bone->setRotation(bone_rot); + } + } + } + + void updateAttachments() + { + m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer(); + m_is_visible = !m_attached_to_local; // Objects attached to the local player should always be hidden + + if(getParent() == NULL || m_attached_to_local) // Detach or don't attach + { + if(m_meshnode) + { + v3f old_position = m_meshnode->getAbsolutePosition(); + v3f old_rotation = m_meshnode->getRotation(); + m_meshnode->setParent(m_smgr->getRootSceneNode()); + m_meshnode->setPosition(old_position); + m_meshnode->setRotation(old_rotation); + m_meshnode->updateAbsolutePosition(); + } + if(m_animated_meshnode) + { + v3f old_position = m_animated_meshnode->getAbsolutePosition(); + v3f old_rotation = m_animated_meshnode->getRotation(); + m_animated_meshnode->setParent(m_smgr->getRootSceneNode()); + m_animated_meshnode->setPosition(old_position); + m_animated_meshnode->setRotation(old_rotation); + m_animated_meshnode->updateAbsolutePosition(); + } + if(m_spritenode) + { + v3f old_position = m_spritenode->getAbsolutePosition(); + v3f old_rotation = m_spritenode->getRotation(); + m_spritenode->setParent(m_smgr->getRootSceneNode()); + m_spritenode->setPosition(old_position); + m_spritenode->setRotation(old_rotation); + m_spritenode->updateAbsolutePosition(); + } + if(m_is_local_player) + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = false; + } + } + else // Attach + { + scene::IMeshSceneNode *parent_mesh = NULL; + if(getParent()->getMeshSceneNode()) + parent_mesh = getParent()->getMeshSceneNode(); + scene::IAnimatedMeshSceneNode *parent_animated_mesh = NULL; + if(getParent()->getAnimatedMeshSceneNode()) + parent_animated_mesh = getParent()->getAnimatedMeshSceneNode(); + scene::IBillboardSceneNode *parent_sprite = NULL; + if(getParent()->getSpriteSceneNode()) + parent_sprite = getParent()->getSpriteSceneNode(); + + scene::IBoneSceneNode *parent_bone = NULL; + if(parent_animated_mesh && m_attachment_bone != "") + parent_bone = parent_animated_mesh->getJointNode(m_attachment_bone.c_str()); + + // The spaghetti code below makes sure attaching works if either the parent or child is a spritenode, meshnode, or animatedmeshnode + // TODO: Perhaps use polymorphism here to save code duplication + if(m_meshnode){ + if(parent_bone){ + m_meshnode->setParent(parent_bone); + m_meshnode->setPosition(m_attachment_position); + m_meshnode->setRotation(m_attachment_rotation); + m_meshnode->updateAbsolutePosition(); + } + else + { + if(parent_mesh){ + m_meshnode->setParent(parent_mesh); + m_meshnode->setPosition(m_attachment_position); + m_meshnode->setRotation(m_attachment_rotation); + m_meshnode->updateAbsolutePosition(); + } + else if(parent_animated_mesh){ + m_meshnode->setParent(parent_animated_mesh); + m_meshnode->setPosition(m_attachment_position); + m_meshnode->setRotation(m_attachment_rotation); + m_meshnode->updateAbsolutePosition(); + } + else if(parent_sprite){ + m_meshnode->setParent(parent_sprite); + m_meshnode->setPosition(m_attachment_position); + m_meshnode->setRotation(m_attachment_rotation); + m_meshnode->updateAbsolutePosition(); + } + } + } + if(m_animated_meshnode){ + if(parent_bone){ + m_animated_meshnode->setParent(parent_bone); + m_animated_meshnode->setPosition(m_attachment_position); + m_animated_meshnode->setRotation(m_attachment_rotation); + m_animated_meshnode->updateAbsolutePosition(); + } + else + { + if(parent_mesh){ + m_animated_meshnode->setParent(parent_mesh); + m_animated_meshnode->setPosition(m_attachment_position); + m_animated_meshnode->setRotation(m_attachment_rotation); + m_animated_meshnode->updateAbsolutePosition(); + } + else if(parent_animated_mesh){ + m_animated_meshnode->setParent(parent_animated_mesh); + m_animated_meshnode->setPosition(m_attachment_position); + m_animated_meshnode->setRotation(m_attachment_rotation); + m_animated_meshnode->updateAbsolutePosition(); + } + else if(parent_sprite){ + m_animated_meshnode->setParent(parent_sprite); + m_animated_meshnode->setPosition(m_attachment_position); + m_animated_meshnode->setRotation(m_attachment_rotation); + m_animated_meshnode->updateAbsolutePosition(); + } + } + } + if(m_spritenode){ + if(parent_bone){ + m_spritenode->setParent(parent_bone); + m_spritenode->setPosition(m_attachment_position); + m_spritenode->setRotation(m_attachment_rotation); + m_spritenode->updateAbsolutePosition(); + } + else + { + if(parent_mesh){ + m_spritenode->setParent(parent_mesh); + m_spritenode->setPosition(m_attachment_position); + m_spritenode->setRotation(m_attachment_rotation); + m_spritenode->updateAbsolutePosition(); + } + else if(parent_animated_mesh){ + m_spritenode->setParent(parent_animated_mesh); + m_spritenode->setPosition(m_attachment_position); + m_spritenode->setRotation(m_attachment_rotation); + m_spritenode->updateAbsolutePosition(); + } + else if(parent_sprite){ + m_spritenode->setParent(parent_sprite); + m_spritenode->setPosition(m_attachment_position); + m_spritenode->setRotation(m_attachment_rotation); + m_spritenode->updateAbsolutePosition(); + } + } + } + if(m_is_local_player) + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = true; + } + } + } + void processMessage(const std::string &data) { //infostream<<"GenericCAO: Got message"<<std::endl; @@ -1099,6 +1615,8 @@ public: } else if(cmd == GENERIC_CMD_UPDATE_POSITION) { + // 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); @@ -1112,7 +1630,10 @@ public: // 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); @@ -1140,6 +1661,41 @@ public: updateTexturePos(); } + else if(cmd == GENERIC_CMD_SET_ANIMATION) + { + m_animation_range = readV2F1000(is); + m_animation_speed = readF1000(is); + m_animation_blend = readF1000(is); + + updateAnimation(); + } + else if(cmd == GENERIC_CMD_SET_BONE_POSITION) + { + std::string bone = deSerializeString(is); + v3f position = readV3F1000(is); + v3f rotation = readV3F1000(is); + m_bone_position[bone] = core::vector2d<v3f>(position, rotation); + + updateBonePosition(); + } + else if(cmd == GENERIC_CMD_SET_ATTACHMENT) + { + // If an entry already exists for this object, delete it first to avoid duplicates + for(std::vector<core::vector2d<int> >::iterator ii = m_env->attachment_list.begin(); ii != m_env->attachment_list.end(); ii++) + { + if(ii->X == getId()) // This is the ID of our object + { + m_env->attachment_list.erase(ii); + break; + } + } + m_env->attachment_list.push_back(core::vector2d<int>(getId(), readS16(is))); + m_attachment_bone = deSerializeString(is); + m_attachment_position = readV3F1000(is); + m_attachment_rotation = readV3F1000(is); + + updateAttachments(); + } else if(cmd == GENERIC_CMD_PUNCHED) { /*s16 damage =*/ readS16(is); diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index ff8ef5276..68895c396 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tile.h" #include "gamedef.h" #include "util/numeric.h" -#include "util/serialize.h" #include "util/directiontables.h" // Create a cuboid. diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 7526e0353..8916b4926 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -245,7 +245,7 @@ public: } } - std::string getClientInitializationData() + std::string getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); // version @@ -355,7 +355,10 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, m_last_sent_velocity(0,0,0), m_last_sent_position_timer(0), m_last_sent_move_precision(0), - m_armor_groups_sent(false) + m_armor_groups_sent(false), + m_animation_sent(false), + m_bone_position_sent(false), + m_attachment_sent(false) { // Only register type if no environment supplied if(env == NULL){ @@ -429,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) @@ -440,30 +454,52 @@ 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; - - if(m_prop.physical){ - core::aabbox3d<f32> box = m_prop.collisionbox; - box.MinEdge *= BS; - box.MaxEdge *= BS; - collisionMoveResult moveresult; - f32 pos_max_d = BS*0.25; // Distance per iteration - f32 stepheight = 0; // Maximum climbable step height - v3f p_pos = m_base_position; - v3f p_velocity = m_velocity; - v3f p_acceleration = m_acceleration; - IGameDef *gamedef = m_env->getGameDef(); - moveresult = collisionMoveSimple(&m_env->getMap(), gamedef, - pos_max_d, box, stepheight, dtime, - p_pos, p_velocity, p_acceleration); - // Apply results - m_base_position = p_pos; - m_velocity = p_velocity; - m_acceleration = p_acceleration; - } else { - m_base_position += dtime * m_velocity + 0.5 * dtime - * dtime * m_acceleration; - m_velocity += dtime * m_acceleration; + + // 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(isAttached()) + { + 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); + } + else + { + if(m_prop.physical){ + core::aabbox3d<f32> box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.25; // Distance per iteration + f32 stepheight = 0; // Maximum climbable step height + v3f p_pos = m_base_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + IGameDef *gamedef = m_env->getGameDef(); + moveresult = collisionMoveSimple(&m_env->getMap(), gamedef, + pos_max_d, box, stepheight, dtime, + p_pos, p_velocity, p_acceleration); + // Apply results + m_base_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + } else { + m_base_position += dtime * m_velocity + 0.5 * dtime + * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + } } if(m_registered){ @@ -473,20 +509,23 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) if(send_recommended == false) return; - - // TODO: force send when acceleration changes enough? - float minchange = 0.2*BS; - if(m_last_sent_position_timer > 1.0){ - minchange = 0.01*BS; - } else if(m_last_sent_position_timer > 0.2){ - minchange = 0.05*BS; - } - float move_d = m_base_position.getDistanceFrom(m_last_sent_position); - move_d += m_last_sent_move_precision; - float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity); - if(move_d > minchange || vel_d > minchange || - fabs(m_yaw - m_last_sent_yaw) > 1.0){ - sendPosition(true, false); + + if(!isAttached()) + { + // TODO: force send when acceleration changes enough? + float minchange = 0.2*BS; + if(m_last_sent_position_timer > 1.0){ + minchange = 0.01*BS; + } else if(m_last_sent_position_timer > 0.2){ + minchange = 0.05*BS; + } + float move_d = m_base_position.getDistanceFrom(m_last_sent_position); + move_d += m_last_sent_move_precision; + float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity); + if(move_d > minchange || vel_d > minchange || + fabs(m_yaw - m_last_sent_yaw) > 1.0){ + sendPosition(true, false); + } } if(m_armor_groups_sent == false){ @@ -497,20 +536,70 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) ActiveObjectMessage aom(getId(), true, str); m_messages_out.push_back(aom); } + + if(m_animation_sent == false){ + m_animation_sent = true; + std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push_back(aom); + } + + if(m_bone_position_sent == false){ + m_bone_position_sent = true; + for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push_back(aom); + } + } + + if(m_attachment_sent == false){ + m_attachment_sent = true; + std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push_back(aom); + } } -std::string LuaEntitySAO::getClientInitializationData() +std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); - writeU8(os, 0); // version - os<<serializeString(""); // name - writeU8(os, 0); // is_player - writeV3F1000(os, m_base_position); - writeF1000(os, m_yaw); - writeS16(os, m_hp); - writeU8(os, 2); // number of messages stuffed in here - os<<serializeLongString(getPropertyPacket()); // message 1 - os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + + if(protocol_version >= 14) + { + writeU8(os, 1); // version + os<<serializeString(""); // name + writeU8(os, 0); // is_player + writeS16(os, getId()); //id + writeV3F1000(os, m_base_position); + writeF1000(os, m_yaw); + writeS16(os, m_hp); + + writeU8(os, 4 + m_bone_position.size()); // number of messages stuffed in here + os<<serializeLongString(getPropertyPacket()); // message 1 + os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + os<<serializeLongString(gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend)); // 3 + for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + os<<serializeLongString(gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y)); // m_bone_position.size + } + os<<serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 + } + else + { + writeU8(os, 0); // version + os<<serializeString(""); // name + writeU8(os, 0); // is_player + writeV3F1000(os, m_base_position); + writeF1000(os, m_yaw); + writeS16(os, m_hp); + writeU8(os, 2); // number of messages stuffed in here + os<<serializeLongString(getPropertyPacket()); // message 1 + os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + } + // return result return os.str(); } @@ -550,6 +639,10 @@ int LuaEntitySAO::punch(v3f dir, m_removed = true; return 0; } + + // It's best that attachments cannot be punched + if(isAttached()) + return 0; ItemStack *punchitem = NULL; ItemStack punchitem_static; @@ -594,18 +687,25 @@ void LuaEntitySAO::rightClick(ServerActiveObject *clicker) { if(!m_registered) return; + // It's best that attachments cannot be clicked + if(isAttached()) + return; lua_State *L = m_env->getLua(); scriptapi_luaentity_rightclick(L, m_id, clicker); } void LuaEntitySAO::setPos(v3f pos) { + if(isAttached()) + return; m_base_position = pos; sendPosition(false, true); } void LuaEntitySAO::moveTo(v3f pos, bool continuous) { + if(isAttached()) + return; m_base_position = pos; if(!continuous) sendPosition(true, true); @@ -644,6 +744,37 @@ void LuaEntitySAO::setArmorGroups(const ItemGroupList &armor_groups) m_armor_groups_sent = false; } +void LuaEntitySAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) +{ + m_animation_range = frame_range; + m_animation_speed = frame_speed; + m_animation_blend = frame_blend; + m_animation_sent = false; +} + +void LuaEntitySAO::setBonePosition(std::string bone, v3f position, v3f rotation) +{ + m_bone_position[bone] = core::vector2d<v3f>(position, rotation); + m_bone_position_sent = false; +} + +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 + // are still sent to clients at an interval so players might see them lagging, plus we can't + // read and attach to skeletal bones. + // If we just attach on the client, the server still sees the child at its original location. + // This breaks some things so we also give the server the most accurate representation + // even if players only see the client changes. + + m_attachment_parent_id = parent_id; + m_attachment_bone = bone; + m_attachment_position = position; + m_attachment_rotation = rotation; + m_attachment_sent = false; +} + ObjectProperties* LuaEntitySAO::accessObjectProperties() { return &m_prop; @@ -718,6 +849,10 @@ 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(isAttached()) + return; + m_last_sent_move_precision = m_base_position.getDistanceFrom( m_last_sent_position); m_last_sent_position_timer = 0; @@ -765,8 +900,11 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_properties_sent(true), m_privs(privs), m_is_singleplayer(is_singleplayer), + m_animation_sent(false), + m_bone_position_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) @@ -782,13 +920,17 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_prop.physical = false; m_prop.weight = 75; m_prop.collisionbox = core::aabbox3d<f32>(-1/3.,-1.0,-1/3., 1/3.,1.0,1/3.); + // start of default appearance, this should be overwritten by LUA m_prop.visual = "upright_sprite"; m_prop.visual_size = v2f(1, 2); m_prop.textures.clear(); m_prop.textures.push_back("player.png"); m_prop.textures.push_back("player_back.png"); + m_prop.colors.clear(); + m_prop.colors.push_back(video::SColor(255, 255, 255, 255)); m_prop.spritediv = v2s16(1,1); - m_prop.is_visible = (getHP() != 0); + // end of default appearance + m_prop.is_visible = true; m_prop.makes_footstep_sound = true; } @@ -836,18 +978,43 @@ bool PlayerSAO::unlimitedTransferDistance() const return g_settings->getBool("unlimited_player_transfer_distance"); } -std::string PlayerSAO::getClientInitializationData() +std::string PlayerSAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); - writeU8(os, 0); // version - os<<serializeString(m_player->getName()); // name - writeU8(os, 1); // is_player - writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); - writeF1000(os, m_player->getYaw()); - writeS16(os, getHP()); - writeU8(os, 2); // number of messages stuffed in here - os<<serializeLongString(getPropertyPacket()); // message 1 - os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + + if(protocol_version >= 15) + { + writeU8(os, 1); // version + os<<serializeString(m_player->getName()); // name + writeU8(os, 1); // is_player + writeS16(os, getId()); //id + writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); + writeF1000(os, m_player->getYaw()); + writeS16(os, getHP()); + + writeU8(os, 4 + m_bone_position.size()); // number of messages stuffed in here + os<<serializeLongString(getPropertyPacket()); // message 1 + os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + os<<serializeLongString(gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend)); // 3 + for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + os<<serializeLongString(gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y)); // m_bone_position.size + } + os<<serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 + } + else + { + writeU8(os, 0); // version + os<<serializeString(m_player->getName()); // name + writeU8(os, 1); // is_player + writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); + writeF1000(os, m_player->getYaw()); + writeS16(os, getHP()); + writeU8(os, 2); // number of messages stuffed in here + os<<serializeLongString(getPropertyPacket()); // message 1 + os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + } + + // return result return os.str(); } @@ -857,6 +1024,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) @@ -868,73 +1046,102 @@ 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; - - if(m_is_singleplayer || g_settings->getBool("disable_anticheat")) + + // 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(isAttached()) { - m_last_good_position = m_player->getPosition(); + 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); } else { - /* - Check player movements - - NOTE: Actually the server should handle player physics like the - client does and compare player's position to what is calculated - on our side. This is required when eg. players fly due to an - explosion. Altough a node-based alternative might be possible - too, and much more lightweight. - */ - - float player_max_speed = 0; - float player_max_speed_up = 0; - if(m_privs.count("fast") != 0){ - // Fast speed - player_max_speed = BS * 20; - player_max_speed_up = BS * 20; - } else { - // Normal speed - player_max_speed = BS * 4.0; - player_max_speed_up = BS * 4.0; + if(m_is_singleplayer || g_settings->getBool("disable_anticheat")) + { + m_last_good_position = m_player->getPosition(); + m_last_good_position_age = 0; } - // Tolerance - player_max_speed *= 2.5; - player_max_speed_up *= 2.5; - - m_last_good_position_age += dtime; - if(m_last_good_position_age >= 1.0){ - float age = m_last_good_position_age; - v3f diff = (m_player->getPosition() - m_last_good_position); - float d_vert = diff.Y; - diff.Y = 0; - float d_horiz = diff.getLength(); - /*infostream<<m_player->getName()<<"'s horizontal speed is " - <<(d_horiz/age)<<std::endl;*/ - if(d_horiz <= age * player_max_speed && - (d_vert < 0 || d_vert < age * player_max_speed_up)){ - m_last_good_position = m_player->getPosition(); + else + { + /* + Check player movements + + NOTE: Actually the server should handle player physics like the + client does and compare player's position to what is calculated + on our side. This is required when eg. players fly due to an + explosion. Altough a node-based alternative might be possible + too, and much more lightweight. + */ + + float player_max_speed = 0; + float player_max_speed_up = 0; + if(m_privs.count("fast") != 0){ + // Fast speed + player_max_speed = BS * 20; + player_max_speed_up = BS * 20; } else { - actionstream<<"Player "<<m_player->getName() - <<" moved too fast; resetting position" - <<std::endl; - m_player->setPosition(m_last_good_position); - m_teleported = true; + // Normal speed + player_max_speed = BS * 4.0; + player_max_speed_up = BS * 4.0; + } + // Tolerance + player_max_speed *= 2.5; + player_max_speed_up *= 2.5; + + m_last_good_position_age += dtime; + if(m_last_good_position_age >= 1.0){ + float age = m_last_good_position_age; + v3f diff = (m_player->getPosition() - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + /*infostream<<m_player->getName()<<"'s horizontal speed is " + <<(d_horiz/age)<<std::endl;*/ + if(d_horiz <= age * player_max_speed && + (d_vert < 0 || d_vert < age * player_max_speed_up)){ + m_last_good_position = m_player->getPosition(); + } else { + actionstream<<"Player "<<m_player->getName() + <<" moved too fast; resetting position" + <<std::endl; + m_player->setPosition(m_last_good_position); + m_moved = true; + } + m_last_good_position_age = 0; } - m_last_good_position_age = 0; } } if(send_recommended == false) return; - if(m_position_not_sent) + // If the object is attached client-side, don't waste bandwidth sending its position to clients + if(m_position_not_sent && !isAttached()) { m_position_not_sent = false; float update_interval = m_env->getSendRecommendedInterval(); + v3f pos; + 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( - m_player->getPosition() + v3f(0,BS*1,0), + pos, v3f(0,0,0), v3f(0,0,0), m_player->getYaw(), @@ -961,32 +1168,63 @@ void PlayerSAO::step(float dtime, bool send_recommended) ActiveObjectMessage aom(getId(), true, str); m_messages_out.push_back(aom); } + + if(m_animation_sent == false){ + m_animation_sent = true; + std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push_back(aom); + } + + if(m_bone_position_sent == false){ + m_bone_position_sent = true; + for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push_back(aom); + } + } + + if(m_attachment_sent == false){ + m_attachment_sent = true; + std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push_back(aom); + } } void PlayerSAO::setBasePosition(const v3f &position) { + // This needs to be ran for attachments too ServerActiveObject::setBasePosition(position); m_position_not_sent = true; } void PlayerSAO::setPos(v3f pos) { + 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(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, @@ -994,6 +1232,10 @@ int PlayerSAO::punch(v3f dir, ServerActiveObject *puncher, float time_from_last_punch) { + // It's best that attachments cannot be punched + if(isAttached()) + return 0; + if(!toolcap) return 0; @@ -1075,6 +1317,39 @@ void PlayerSAO::setArmorGroups(const ItemGroupList &armor_groups) m_armor_groups_sent = false; } +void PlayerSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) +{ + // store these so they can be updated to clients + m_animation_range = frame_range; + m_animation_speed = frame_speed; + m_animation_blend = frame_blend; + m_animation_sent = false; +} + +void PlayerSAO::setBonePosition(std::string bone, v3f position, v3f rotation) +{ + // store these so they can be updated to clients + m_bone_position[bone] = core::vector2d<v3f>(position, rotation); + m_bone_position_sent = false; +} + +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 + // are still sent to clients at an interval so players might see them lagging, plus we can't + // read and attach to skeletal bones. + // If we just attach on the client, the server still sees the child at its original location. + // This breaks some things so we also give the server the most accurate representation + // even if players only see the client changes. + + m_attachment_parent_id = parent_id; + m_attachment_bone = bone; + m_attachment_position = position; + m_attachment_rotation = rotation; + m_attachment_sent = false; +} + ObjectProperties* PlayerSAO::accessObjectProperties() { return &m_prop; @@ -1138,7 +1413,7 @@ void PlayerSAO::disconnected() std::string PlayerSAO::getPropertyPacket() { - m_prop.is_visible = (getHP() != 0); + m_prop.is_visible = (true); return gob_cmd_set_properties(m_prop); } diff --git a/src/content_sao.h b/src/content_sao.h index 05c77e2cb..065c6a039 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -46,8 +46,9 @@ 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 getClientInitializationData(u16 protocol_version); std::string getStaticData(); int punch(v3f dir, const ToolCapabilities *toolcap=NULL, @@ -61,6 +62,9 @@ public: void setHP(s16 hp); s16 getHP() const; void setArmorGroups(const ItemGroupList &armor_groups); + void setAnimation(v2f frame_range, float frame_speed, float frame_blend); + void setBonePosition(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 */ @@ -96,6 +100,20 @@ private: float m_last_sent_position_timer; float m_last_sent_move_precision; bool m_armor_groups_sent; + + v2f m_animation_range; + float m_animation_speed; + float m_animation_blend; + bool m_animation_sent; + + std::map<std::string, core::vector2d<v3f> > m_bone_position; + bool m_bone_position_sent; + + int m_attachment_parent_id; + std::string m_attachment_bone; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attachment_sent; }; /* @@ -122,8 +140,9 @@ public: void removingFromEnvironment(); bool isStaticAllowed() const; bool unlimitedTransferDistance() const; - std::string getClientInitializationData(); + std::string getClientInitializationData(u16 protocol_version); std::string getStaticData(); + bool isAttached(); void step(float dtime, bool send_recommended); void setBasePosition(const v3f &position); void setPos(v3f pos); @@ -142,6 +161,9 @@ public: void setHP(s16 hp); void setArmorGroups(const ItemGroupList &armor_groups); + void setAnimation(v2f frame_range, float frame_speed, float frame_blend); + void setBonePosition(std::string bone, v3f position, v3f rotation); + void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); @@ -229,15 +251,32 @@ private: bool m_position_not_sent; ItemGroupList m_armor_groups; bool m_armor_groups_sent; + + + bool m_properties_sent; struct ObjectProperties m_prop; // Cached privileges for enforcement std::set<std::string> m_privs; bool m_is_singleplayer; + v2f m_animation_range; + float m_animation_speed; + float m_animation_blend; + bool m_animation_sent; + + std::map<std::string, core::vector2d<v3f> > m_bone_position; // Stores position and rotation for each bone name + bool m_bone_position_sent; + + int m_attachment_parent_id; + std::string m_attachment_bone; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attachment_sent; + 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/defaultsettings.cpp b/src/defaultsettings.cpp index f1858c2e7..333d98cd8 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -109,12 +109,17 @@ void set_default_settings(Settings *settings) settings->setDefault("sound_volume", "0.8"); settings->setDefault("desynchronize_mapblock_texture_animation", "true"); + settings->setDefault("mip_map", "false"); + settings->setDefault("anisotropic_filter", "false"); + settings->setDefault("bilinear_filter", "false"); + settings->setDefault("trilinear_filter", "false"); + // Server stuff // "map-dir" doesn't exist by default. settings->setDefault("default_game", "minetest"); settings->setDefault("motd", ""); settings->setDefault("max_users", "100"); - settings->setDefault("strict_protocol_version_checking", "true"); + settings->setDefault("strict_protocol_version_checking", "false"); settings->setDefault("creative_mode", "false"); settings->setDefault("enable_damage", "true"); settings->setDefault("only_peaceful_mobs", "false"); @@ -143,12 +148,10 @@ void set_default_settings(Settings *settings) settings->setDefault("server_unload_unused_data_timeout", "29"); settings->setDefault("server_map_save_interval", "5.3"); settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); - settings->setDefault("dedicated_server_step", "0.05"); + settings->setDefault("dedicated_server_step", "0.1"); settings->setDefault("ignore_world_load_errors", "false"); - settings->setDefault("mip_map", "false"); - settings->setDefault("anisotropic_filter", "false"); - settings->setDefault("bilinear_filter", "false"); - settings->setDefault("trilinear_filter", "false"); - + settings->setDefault("congestion_control_aim_rtt", "0.2"); + settings->setDefault("congestion_control_max_rate", "400"); + settings->setDefault("congestion_control_min_rate", "10"); } diff --git a/src/environment.cpp b/src/environment.cpp index 4abba6359..e70cb39b7 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif #include "daynightratio.h" #include "map.h" +#include "util/serialize.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -328,7 +329,8 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, lua_State *L, m_send_recommended_timer(0), m_active_block_interval_overload_skip(0), m_game_time(0), - m_game_time_fraction_counter(0) + m_game_time_fraction_counter(0), + m_recommended_send_interval(0.1) { } @@ -939,6 +941,11 @@ void ServerEnvironment::step(float dtime) /* Step time of day */ stepTimeOfDay(dtime); + // Update this one + // NOTE: This is kind of funny on a singleplayer game, but doesn't + // really matter that much. + m_recommended_send_interval = g_settings->getFloat("dedicated_server_step"); + /* Increment game time */ @@ -2296,8 +2303,9 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, { errorstream<<"ClientEnvironment::addActiveObject():" <<" id="<<id<<" type="<<type - <<": SerializationError in initialize()," - <<" init_data="<<serializeJsonString(init_data) + <<": SerializationError in initialize(): " + <<e.what() + <<": init_data="<<serializeJsonString(init_data) <<std::endl; } @@ -2315,7 +2323,7 @@ void ClientEnvironment::removeActiveObject(u16 id) <<"id="<<id<<" not found"<<std::endl; return; } - obj->removeFromScene(); + obj->removeFromScene(true); delete obj; m_active_objects.remove(id); } diff --git a/src/environment.h b/src/environment.h index 042229038..0cc53f9a6 100644 --- a/src/environment.h +++ b/src/environment.h @@ -205,9 +205,7 @@ public: { return m_gamedef; } float getSendRecommendedInterval() - { - return 0.10; - } + { return m_recommended_send_interval; } /* Save players @@ -367,6 +365,8 @@ private: // A helper variable for incrementing the latter float m_game_time_fraction_counter; core::list<ABMWithState> m_abms; + // An interval for generally sending object positions and stuff + float m_recommended_send_interval; }; #ifndef SERVER @@ -463,6 +463,8 @@ public: // Get event from queue. CEE_NONE is returned if queue is empty. ClientEnvEvent getClientEvent(); + + std::vector<core::vector2d<int> > attachment_list; // X is child ID, Y is parent ID private: ClientMap *m_map; diff --git a/src/game.cpp b/src/game.cpp index b6accfe37..086293894 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1513,7 +1513,7 @@ void the_game( <<"Launching inventory"<<std::endl; GUIFormSpecMenu *menu = - new GUIFormSpecMenu(guienv, guiroot, -1, + new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr, &client, gamedef); @@ -1881,6 +1881,8 @@ void the_game( bool a_jump, bool a_superspeed, bool a_sneak, + bool a_LMB, + bool a_RMB, float a_pitch, float a_yaw*/ PlayerControl control( @@ -1891,10 +1893,24 @@ void the_game( input->isKeyDown(getKeySetting("keymap_jump")), input->isKeyDown(getKeySetting("keymap_special1")), input->isKeyDown(getKeySetting("keymap_sneak")), + input->getLeftState(), + input->getRightState(), camera_pitch, camera_yaw ); client.setPlayerControl(control); + u32 keyPressed= + 1*(int)input->isKeyDown(getKeySetting("keymap_forward"))+ + 2*(int)input->isKeyDown(getKeySetting("keymap_backward"))+ + 4*(int)input->isKeyDown(getKeySetting("keymap_left"))+ + 8*(int)input->isKeyDown(getKeySetting("keymap_right"))+ + 16*(int)input->isKeyDown(getKeySetting("keymap_jump"))+ + 32*(int)input->isKeyDown(getKeySetting("keymap_special1"))+ + 64*(int)input->isKeyDown(getKeySetting("keymap_sneak"))+ + 128*(int)input->getLeftState()+ + 256*(int)input->getRightState(); + LocalPlayer* player = client.getEnv().getLocalPlayer(); + player->keyPressed=keyPressed; } /* @@ -2280,7 +2296,7 @@ void the_game( /* Create menu */ GUIFormSpecMenu *menu = - new GUIFormSpecMenu(guienv, guiroot, -1, + new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr, &client, gamedef); menu->setFormSpec(meta->getString("formspec"), diff --git a/src/genericobject.cpp b/src/genericobject.cpp index 4ab031b5d..398b07feb 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -117,4 +117,40 @@ std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups) return os.str(); } +std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_ANIMATION); + // parameters + writeV2F1000(os, frames); + writeF1000(os, frame_speed); + writeF1000(os, frame_blend); + return os.str(); +} + +std::string gob_cmd_update_bone_position(std::string bone, v3f position, v3f rotation) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_BONE_POSITION); + // parameters + os<<serializeString(bone); + writeV3F1000(os, position); + writeV3F1000(os, rotation); + return os.str(); +} + +std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f position, v3f rotation) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_ATTACHMENT); + // parameters + writeS16(os, parent_id); + os<<serializeString(bone); + writeV3F1000(os, position); + writeV3F1000(os, rotation); + return os.str(); +} diff --git a/src/genericobject.h b/src/genericobject.h index 81563c19b..b69c24b48 100644 --- a/src/genericobject.h +++ b/src/genericobject.h @@ -30,6 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #define GENERIC_CMD_SET_SPRITE 3 #define GENERIC_CMD_PUNCHED 4 #define GENERIC_CMD_UPDATE_ARMOR_GROUPS 5 +#define GENERIC_CMD_SET_ANIMATION 6 +#define GENERIC_CMD_SET_BONE_POSITION 7 +#define GENERIC_CMD_SET_ATTACHMENT 8 #include "object_properties.h" std::string gob_cmd_set_properties(const ObjectProperties &prop); @@ -59,5 +62,11 @@ std::string gob_cmd_punched(s16 damage, s16 result_hp); #include "itemgroup.h" std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups); +std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend); + +std::string gob_cmd_update_bone_position(std::string bone, v3f position, v3f rotation); + +std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f position, v3f rotation); + #endif diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index affbd1a34..618141d24 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -125,13 +125,14 @@ void drawItemStack(video::IVideoDriver *driver, GUIFormSpecMenu */ -GUIFormSpecMenu::GUIFormSpecMenu(gui::IGUIEnvironment* env, +GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, IGameDef *gamedef ): - GUIModalMenu(env, parent, id, menumgr), + GUIModalMenu(dev->getGUIEnvironment(), parent, id, menumgr), + m_device(dev), m_invmgr(invmgr), m_gamedef(gamedef), m_form_src(NULL), @@ -199,6 +200,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_inventorylists.clear(); m_images.clear(); + m_backgrounds.clear(); m_fields.clear(); Strfnd f(m_formspec_string); @@ -278,9 +280,26 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) <<", geom=("<<geom.X<<","<<geom.Y<<")" <<std::endl; if(bp_set != 2) - errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl; + errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl; m_images.push_back(ImageDrawSpec(name, pos, geom)); } + else if(type == "background") + { + v2s32 pos = basepos; + pos.X += stof(f.next(",")) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2; + pos.Y += stof(f.next(";")) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2; + v2s32 geom; + geom.X = stof(f.next(",")) * (float)spacing.X; + geom.Y = stof(f.next(";")) * (float)spacing.Y; + std::string name = f.next("]"); + infostream<<"image name="<<name + <<", pos=("<<pos.X<<","<<pos.Y<<")" + <<", geom=("<<geom.X<<","<<geom.Y<<")" + <<std::endl; + if(bp_set != 2) + errorstream<<"WARNING: invalid use of background without a size[] element"<<std::endl; + m_backgrounds.push_back(ImageDrawSpec(name, pos, geom)); + } else if(type == "field") { std::string fname = f.next(";"); @@ -458,6 +477,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) video::ITexture *texture = m_gamedef->tsrc()->getTextureRaw(fimage); gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + e->setUseAlphaChannel(true); e->setImage(texture); e->setPressedImage(texture); e->setScaleImage(true); @@ -679,6 +699,8 @@ void GUIFormSpecMenu::drawMenu() } } + m_pointer = m_device->getCursorControl()->getPosition(); + updateSelectedItem(); gui::IGUISkin* skin = Environment->getSkin(); @@ -692,6 +714,26 @@ void GUIFormSpecMenu::drawMenu() m_tooltip_element->setVisible(false); /* + 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); + // Image size on screen + core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y); + // Image rectangle on screen + core::rect<s32> rect = imgrect + spec.pos; + const video::SColor color(255,255,255,255); + const video::SColor colors[] = {color,color,color,color}; + driver->draw2DImage(texture, rect, + core::rect<s32>(core::position2d<s32>(0,0), + core::dimension2di(texture->getOriginalSize())), + NULL/*&AbsoluteClippingRect*/, colors, true); + } + + /* Draw images */ for(u32 i=0; i<m_images.size(); i++) @@ -715,8 +757,11 @@ void GUIFormSpecMenu::drawMenu() Draw items Phase 0: Item slot rectangles Phase 1: Item images; prepare tooltip + If backgrounds used, do not draw Item slot rectangles */ - for(int phase=0; phase<=1; phase++) + int start_phase=0; + if (m_backgrounds.size() > 0) start_phase=1; + for(int phase=start_phase; phase<=1; phase++) for(u32 i=0; i<m_inventorylists.size(); i++) { drawList(m_inventorylists[i], phase); @@ -896,23 +941,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } if(event.EventType==EET_MOUSE_INPUT_EVENT - && event.MouseInput.Event == EMIE_MOUSE_MOVED) - { - // Mouse moved - m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y); - } - if(event.EventType==EET_MOUSE_INPUT_EVENT && event.MouseInput.Event != EMIE_MOUSE_MOVED) { // Mouse event other than movement - v2s32 p(event.MouseInput.X, event.MouseInput.Y); - m_pointer = p; - // Get selected item and hovered/clicked item (s) updateSelectedItem(); - ItemSpec s = getItemAtPos(p); + ItemSpec s = getItemAtPos(m_pointer); Inventory *inv_selected = NULL; Inventory *inv_s = NULL; diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 5c01bdcd2..e6a2efe42 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -144,7 +144,7 @@ class GUIFormSpecMenu : public GUIModalMenu }; public: - GUIFormSpecMenu(gui::IGUIEnvironment* env, + GUIFormSpecMenu(irr::IrrlichtDevice* dev, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, @@ -197,6 +197,7 @@ protected: v2s32 spacing; v2s32 imgsize; + irr::IrrlichtDevice* m_device; InventoryManager *m_invmgr; IGameDef *m_gamedef; @@ -206,6 +207,7 @@ protected: TextDest *m_text_dst; core::array<ListDrawSpec> m_inventorylists; + core::array<ImageDrawSpec> m_backgrounds; core::array<ImageDrawSpec> m_images; core::array<FieldSpec> m_fields; diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 4b5e53fea..17c4cdeb9 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., LocalPlayer::LocalPlayer(IGameDef *gamedef): Player(gamedef), + isAttached(false), + overridePosition(v3f(0,0,0)), m_sneak_node(32767,32767,32767), m_sneak_node_exists(false), m_old_node_below(32767,32767,32767), @@ -59,6 +61,13 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, v3f old_speed = m_speed; + // Copy parent position if local player is attached + if(isAttached) + { + setPosition(overridePosition); + return; + } + // Skip collision detection if a special movement mode is used bool fly_allowed = m_gamedef->checkLocalPrivilege("fly"); bool free_move = fly_allowed && g_settings->getBool("free_move"); @@ -314,7 +323,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, v3s16 camera_np = floatToInt(getEyePosition(), BS); MapNode n = map.getNodeNoEx(camera_np); if(n.getContent() != CONTENT_IGNORE){ - if(nodemgr->get(n).walkable){ + if(nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2){ camera_barely_in_ceiling = true; } } @@ -352,7 +361,14 @@ void LocalPlayer::applyControl(float dtime) setPitch(control.pitch); setYaw(control.yaw); - + + // Nullify speed and don't run positioning code if the player is attached + if(isAttached) + { + setSpeed(v3f(0,0,0)); + return; + } + v3f move_direction = v3f(0,0,1); move_direction.rotateXZBy(getYaw()); diff --git a/src/localplayer.h b/src/localplayer.h index fb57e6538..9d1829db8 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -22,53 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" -struct PlayerControl -{ - PlayerControl() - { - up = false; - down = false; - left = false; - right = false; - jump = false; - aux1 = false; - sneak = false; - pitch = 0; - yaw = 0; - } - PlayerControl( - bool a_up, - bool a_down, - bool a_left, - bool a_right, - bool a_jump, - bool a_aux1, - bool a_sneak, - float a_pitch, - float a_yaw - ) - { - up = a_up; - down = a_down; - left = a_left; - right = a_right; - jump = a_jump; - aux1 = a_aux1; - sneak = a_sneak; - pitch = a_pitch; - yaw = a_yaw; - } - bool up; - bool down; - bool left; - bool right; - bool jump; - bool aux1; - bool sneak; - float pitch; - float yaw; -}; - class LocalPlayer : public Player { public: @@ -79,6 +32,10 @@ public: { return true; } + + bool isAttached; + + v3f overridePosition; void move(f32 dtime, Map &map, f32 pos_max_d, core::list<CollisionInfo> *collision_info); @@ -87,9 +44,6 @@ public: void applyControl(float dtime); v3s16 getStandingNodePos(); - - PlayerControl control; - private: // This is used for determining the sneaking range v3s16 m_sneak_node; diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 2ae6e9bd7..e9c8fadff 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock_mesh.h" #endif #include "util/string.h" +#include "util/serialize.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index 9ae9b21c0..fdeb31f4d 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -77,9 +77,9 @@ void MeshMakeData::fill(MapBlock *block) // Get map Map *map = block->getParent(); - for(u16 i=0; i<6; i++) + for(u16 i=0; i<26; i++) { - const v3s16 &dir = g_6dirs[i]; + const v3s16 &dir = g_26dirs[i]; v3s16 bp = m_blockpos + dir; MapBlock *b = map->getBlockNoCreateNoEx(bp); if(b) diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 77b133020..782f00b62 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -132,7 +132,8 @@ void make_tree(ManualMapVoxelManipulator &vmanip, v3s16 p0, for(s16 ii=0; ii<trunk_h; ii++) { if(vmanip.m_area.contains(p1)) - vmanip.m_data[vmanip.m_area.index(p1)] = treenode; + if(ii == 0 || vmanip.getNodeNoExNoEmerge(p1).getContent() == CONTENT_AIR) + vmanip.m_data[vmanip.m_area.index(p1)] = treenode; p1.Y++; } diff --git a/src/mesh.cpp b/src/mesh.cpp index b9ec82e18..0f075f72b 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -433,9 +433,6 @@ video::ITexture *generateTextureFromMesh(scene::IMesh *mesh, } // Create render target texture - video::ITexture *oldtexture = driver->findTexture(texture_name.c_str()); - if(oldtexture) - driver->removeTexture(oldtexture); video::ITexture *rtt = driver->addRenderTargetTexture( dim, texture_name.c_str(), video::ECF_A8R8G8B8); if(rtt == NULL) diff --git a/src/mods.cpp b/src/mods.cpp index c2bb907c2..08e8e276f 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -39,6 +39,10 @@ static void collectMods(const std::string &modspath, if(!dirlist[j].dir) continue; std::string modname = dirlist[j].name; + // Ignore all directories beginning with a ".", especially + // VCS directories like ".git" or ".svn" + if(modname[0] == '.') + continue; std::string modpath = modspath + DIR_DELIM + modname; TRACESTREAM(<<indentation<<"collectMods: "<<modname<<" at \""<<modpath<<"\""<<std::endl); // Handle modpacks (defined by containing modpack.txt) diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 180219ba8..c48e2ff97 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -215,9 +215,14 @@ void ContentFeatures::reset() sound_dug = SimpleSoundSpec(); } -void ContentFeatures::serialize(std::ostream &os) +void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) { - writeU8(os, 5); // version + if(protocol_version < 14){ + serializeOld(os, protocol_version); + return; + } + + writeU8(os, 6); // version os<<serializeString(name); writeU16(os, groups.size()); for(ItemGroupList::const_iterator @@ -254,6 +259,7 @@ void ContentFeatures::serialize(std::ostream &os) os<<serializeString(liquid_alternative_flowing); os<<serializeString(liquid_alternative_source); writeU8(os, liquid_viscosity); + writeU8(os, liquid_renewable); writeU8(os, light_source); writeU32(os, damage_per_second); node_box.serialize(os); @@ -265,14 +271,16 @@ void ContentFeatures::serialize(std::ostream &os) serializeSimpleSoundSpec(sound_dug, os); // Stuff below should be moved to correct place in a version that otherwise changes // the protocol version - writeU8(os, liquid_renewable); } void ContentFeatures::deSerialize(std::istream &is) { int version = readU8(is); - if(version != 5) - throw SerializationError("unsupported ContentFeatures version"); + if(version != 6){ + deSerializeOld(is, version); + return; + } + name = deSerializeString(is); groups.clear(); u32 groups_size = readU16(is); @@ -311,6 +319,7 @@ void ContentFeatures::deSerialize(std::istream &is) liquid_alternative_flowing = deSerializeString(is); liquid_alternative_source = deSerializeString(is); liquid_viscosity = readU8(is); + liquid_renewable = readU8(is); light_source = readU8(is); damage_per_second = readU32(is); node_box.deSerialize(is); @@ -325,7 +334,6 @@ void ContentFeatures::deSerialize(std::istream &is) try{ // Stuff below should be moved to correct place in a version that // otherwise changes the protocol version - liquid_renewable = readU8(is); }catch(SerializationError &e) {}; } @@ -693,7 +701,7 @@ public: } #endif } - void serialize(std::ostream &os) + void serialize(std::ostream &os, u16 protocol_version) { writeU8(os, 1); // version u16 count = 0; @@ -709,7 +717,7 @@ public: // Wrap it in a string to allow different lengths without // strict version incompatibilities std::ostringstream wrapper_os(std::ios::binary); - f->serialize(wrapper_os); + f->serialize(wrapper_os, protocol_version); os2<<serializeString(wrapper_os.str()); count++; } @@ -766,3 +774,122 @@ IWritableNodeDefManager* createNodeDefManager() return new CNodeDefManager(); } +/* + Serialization of old ContentFeatures formats +*/ + +void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) +{ + if(protocol_version == 13) + { + writeU8(os, 5); // version + os<<serializeString(name); + writeU16(os, groups.size()); + for(ItemGroupList::const_iterator + i = groups.begin(); i != groups.end(); i++){ + os<<serializeString(i->first); + writeS16(os, i->second); + } + writeU8(os, drawtype); + writeF1000(os, visual_scale); + writeU8(os, 6); + for(u32 i=0; i<6; i++) + tiledef[i].serialize(os); + writeU8(os, CF_SPECIAL_COUNT); + for(u32 i=0; i<CF_SPECIAL_COUNT; i++){ + tiledef_special[i].serialize(os); + } + writeU8(os, alpha); + writeU8(os, post_effect_color.getAlpha()); + writeU8(os, post_effect_color.getRed()); + writeU8(os, post_effect_color.getGreen()); + writeU8(os, post_effect_color.getBlue()); + writeU8(os, param_type); + writeU8(os, param_type_2); + writeU8(os, is_ground_content); + writeU8(os, light_propagates); + writeU8(os, sunlight_propagates); + writeU8(os, walkable); + writeU8(os, pointable); + writeU8(os, diggable); + writeU8(os, climbable); + writeU8(os, buildable_to); + os<<serializeString(""); // legacy: used to be metadata_name + writeU8(os, liquid_type); + os<<serializeString(liquid_alternative_flowing); + os<<serializeString(liquid_alternative_source); + writeU8(os, liquid_viscosity); + writeU8(os, light_source); + writeU32(os, damage_per_second); + node_box.serialize(os); + selection_box.serialize(os); + writeU8(os, legacy_facedir_simple); + writeU8(os, legacy_wallmounted); + serializeSimpleSoundSpec(sound_footstep, os); + serializeSimpleSoundSpec(sound_dig, os); + serializeSimpleSoundSpec(sound_dug, os); + } + else + { + throw SerializationError("ContentFeatures::serialize(): Unsupported version requested"); + } +} + +void ContentFeatures::deSerializeOld(std::istream &is, int version) +{ + if(version == 5) // In PROTOCOL_VERSION 13 + { + name = deSerializeString(is); + groups.clear(); + u32 groups_size = readU16(is); + for(u32 i=0; i<groups_size; i++){ + std::string name = deSerializeString(is); + int value = readS16(is); + groups[name] = value; + } + drawtype = (enum NodeDrawType)readU8(is); + visual_scale = readF1000(is); + if(readU8(is) != 6) + throw SerializationError("unsupported tile count"); + for(u32 i=0; i<6; i++) + tiledef[i].deSerialize(is); + if(readU8(is) != CF_SPECIAL_COUNT) + throw SerializationError("unsupported CF_SPECIAL_COUNT"); + for(u32 i=0; i<CF_SPECIAL_COUNT; i++) + tiledef_special[i].deSerialize(is); + alpha = readU8(is); + post_effect_color.setAlpha(readU8(is)); + post_effect_color.setRed(readU8(is)); + post_effect_color.setGreen(readU8(is)); + post_effect_color.setBlue(readU8(is)); + param_type = (enum ContentParamType)readU8(is); + param_type_2 = (enum ContentParamType2)readU8(is); + is_ground_content = readU8(is); + light_propagates = readU8(is); + sunlight_propagates = readU8(is); + walkable = readU8(is); + pointable = readU8(is); + diggable = readU8(is); + climbable = readU8(is); + buildable_to = readU8(is); + deSerializeString(is); // legacy: used to be metadata_name + liquid_type = (enum LiquidType)readU8(is); + liquid_alternative_flowing = deSerializeString(is); + liquid_alternative_source = deSerializeString(is); + liquid_viscosity = readU8(is); + light_source = readU8(is); + damage_per_second = readU32(is); + node_box.deSerialize(is); + selection_box.deSerialize(is); + legacy_facedir_simple = readU8(is); + legacy_wallmounted = readU8(is); + deSerializeSimpleSoundSpec(sound_footstep, is); + deSerializeSimpleSoundSpec(sound_dig, is); + deSerializeSimpleSoundSpec(sound_dug, is); + } + else + { + throw SerializationError("unsupported ContentFeatures version"); + } +} + diff --git a/src/nodedef.h b/src/nodedef.h index 4ff6c6b48..8588caeab 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -234,8 +234,10 @@ struct ContentFeatures ContentFeatures(); ~ContentFeatures(); void reset(); - void serialize(std::ostream &os); + void serialize(std::ostream &os, u16 protocol_version); void deSerialize(std::istream &is); + void serializeOld(std::ostream &os, u16 protocol_version); + void deSerializeOld(std::istream &is, int version); /* Some handy methods @@ -264,7 +266,7 @@ public: const=0; virtual const ContentFeatures& get(const std::string &name) const=0; - virtual void serialize(std::ostream &os)=0; + virtual void serialize(std::ostream &os, u16 protocol_version)=0; }; class IWritableNodeDefManager : public INodeDefManager @@ -305,7 +307,7 @@ public: */ virtual void updateTextures(ITextureSource *tsrc)=0; - virtual void serialize(std::ostream &os)=0; + virtual void serialize(std::ostream &os, u16 protocol_version)=0; virtual void deSerialize(std::istream &is)=0; }; diff --git a/src/object_properties.cpp b/src/object_properties.cpp index e67b78b52..ec988a37d 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -18,8 +18,10 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "object_properties.h" +#include "irrlichttypes_bloated.h" #include "util/serialize.h" #include <sstream> +#include <map> #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" #define PP2(x) "("<<(x).X<<","<<(x).Y<<")" @@ -30,6 +32,7 @@ ObjectProperties::ObjectProperties(): weight(5), collisionbox(-0.5,-0.5,-0.5, 0.5,0.5,0.5), visual("sprite"), + mesh(""), visual_size(1,1), spritediv(1,1), initial_sprite_basepos(0,0), @@ -38,6 +41,7 @@ ObjectProperties::ObjectProperties(): automatic_rotate(0) { textures.push_back("unknown_object.png"); + colors.push_back(video::SColor(255,255,255,255)); } std::string ObjectProperties::dump() @@ -48,12 +52,18 @@ std::string ObjectProperties::dump() os<<", weight="<<weight; os<<", collisionbox="<<PP(collisionbox.MinEdge)<<","<<PP(collisionbox.MaxEdge); os<<", visual="<<visual; + os<<", mesh="<<mesh; os<<", visual_size="<<PP2(visual_size); os<<", textures=["; for(u32 i=0; i<textures.size(); i++){ os<<"\""<<textures[i]<<"\" "; } os<<"]"; + os<<", colors=["; + for(u32 i=0; i<colors.size(); i++){ + os<<"\""<<colors[i].getAlpha()<<","<<colors[i].getRed()<<","<<colors[i].getGreen()<<","<<colors[i].getBlue()<<"\" "; + } + os<<"]"; os<<", spritediv="<<PP2(spritediv); os<<", initial_sprite_basepos="<<PP2(initial_sprite_basepos); os<<", is_visible="<<is_visible; @@ -81,32 +91,49 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, is_visible); writeU8(os, makes_footstep_sound); writeF1000(os, automatic_rotate); + // Added in protocol version 14 + os<<serializeString(mesh); + writeU16(os, colors.size()); + for(u32 i=0; i<colors.size(); i++){ + writeARGB8(os, colors[i]); + } + // Add stuff only at the bottom. + // Never remove anything, because we don't want new versions of this } void ObjectProperties::deSerialize(std::istream &is) { int version = readU8(is); - if(version != 1) throw SerializationError( - "unsupported ObjectProperties version"); - hp_max = readS16(is); - physical = readU8(is); - weight = readF1000(is); - collisionbox.MinEdge = readV3F1000(is); - collisionbox.MaxEdge = readV3F1000(is); - visual = deSerializeString(is); - visual_size = readV2F1000(is); - textures.clear(); - u32 texture_count = readU16(is); - for(u32 i=0; i<texture_count; i++){ - textures.push_back(deSerializeString(is)); + if(version == 1) + { + try{ + hp_max = readS16(is); + physical = readU8(is); + weight = readF1000(is); + collisionbox.MinEdge = readV3F1000(is); + collisionbox.MaxEdge = readV3F1000(is); + visual = deSerializeString(is); + visual_size = readV2F1000(is); + textures.clear(); + u32 texture_count = readU16(is); + for(u32 i=0; i<texture_count; i++){ + textures.push_back(deSerializeString(is)); + } + spritediv = readV2S16(is); + initial_sprite_basepos = readV2S16(is); + is_visible = readU8(is); + makes_footstep_sound = readU8(is); + automatic_rotate = readF1000(is); + mesh = deSerializeString(is); + u32 color_count = readU16(is); + for(u32 i=0; i<color_count; i++){ + colors.push_back(readARGB8(is)); + } + }catch(SerializationError &e){} + } + else + { + throw SerializationError("unsupported ObjectProperties version"); } - spritediv = readV2S16(is); - initial_sprite_basepos = readV2S16(is); - is_visible = readU8(is); - makes_footstep_sound = readU8(is); - try{ - automatic_rotate = readF1000(is); - }catch(SerializationError &e){} } - diff --git a/src/object_properties.h b/src/object_properties.h index 3f44771e9..d7d44625e 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include "irrlichttypes_bloated.h" #include <iostream> +#include <map> struct ObjectProperties { @@ -32,14 +33,17 @@ struct ObjectProperties float weight; core::aabbox3d<f32> collisionbox; std::string visual; + std::string mesh; v2f visual_size; core::array<std::string> textures; + core::array<video::SColor> colors; v2s16 spritediv; v2s16 initial_sprite_basepos; bool is_visible; bool makes_footstep_sound; float automatic_rotate; + ObjectProperties(); std::string dump(); void serialize(std::ostream &os) const; diff --git a/src/player.h b/src/player.h index 47f34c178..6c7c1e4ea 100644 --- a/src/player.h +++ b/src/player.h @@ -28,6 +28,61 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" +struct PlayerControl +{ + PlayerControl() + { + up = false; + down = false; + left = false; + right = false; + jump = false; + aux1 = false; + sneak = false; + LMB = false; + RMB = false; + pitch = 0; + yaw = 0; + } + PlayerControl( + bool a_up, + bool a_down, + bool a_left, + bool a_right, + bool a_jump, + bool a_aux1, + bool a_sneak, + bool a_LMB, + bool a_RMB, + float a_pitch, + float a_yaw + ) + { + up = a_up; + down = a_down; + left = a_left; + right = a_right; + jump = a_jump; + aux1 = a_aux1; + sneak = a_sneak; + LMB = a_LMB; + RMB = a_RMB; + pitch = a_pitch; + yaw = a_yaw; + } + bool up; + bool down; + bool left; + bool right; + bool jump; + bool aux1; + bool sneak; + bool LMB; + bool RMB; + float pitch; + float yaw; +}; + class Map; class IGameDef; struct CollisionInfo; @@ -155,9 +210,17 @@ public: u16 hp; u16 peer_id; - + std::string inventory_formspec; - + + PlayerControl control; + PlayerControl getPlayerControl() + { + return control; + } + + u32 keyPressed; + protected: IGameDef *m_gamedef; @@ -182,7 +245,7 @@ public: void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } void setPosition(const v3f &position); - + private: PlayerSAO *m_sao; }; diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 09900ce1f..e5815c462 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -936,6 +936,8 @@ static void read_object_properties(lua_State *L, int index, lua_pop(L, 1); getstringfield(L, -1, "visual", prop->visual); + + getstringfield(L, -1, "mesh", prop->mesh); lua_getfield(L, -1, "visual_size"); if(lua_istable(L, -1)) @@ -958,6 +960,23 @@ static void read_object_properties(lua_State *L, int index, } } lua_pop(L, 1); + + lua_getfield(L, -1, "colors"); + if(lua_istable(L, -1)){ + prop->colors.clear(); + int table = lua_gettop(L); + lua_pushnil(L); + while(lua_next(L, table) != 0){ + // key at index -2 and value at index -1 + if(lua_isstring(L, -1)) + prop->colors.push_back(readARGB8(L, -1)); + else + prop->colors.push_back(video::SColor(255, 255, 255, 255)); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + } + lua_pop(L, 1); lua_getfield(L, -1, "spritediv"); if(lua_istable(L, -1)) @@ -2697,6 +2716,80 @@ private: return 0; } + // set_animation(self, frame_range, frame_speed, frame_blend) + static int l_set_animation(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + 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); + float frame_speed = 15; + if(!lua_isnil(L, 3)) + frame_speed = lua_tonumber(L, 3); + float frame_blend = 0; + if(!lua_isnil(L, 4)) + frame_blend = lua_tonumber(L, 4); + co->setAnimation(frames, frame_speed, frame_blend); + return 0; + } + + // set_bone_position(self, std::string bone, v3f position, v3f rotation) + static int l_set_bone_position(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if(co == NULL) return 0; + // Do it + std::string bone = ""; + if(!lua_isnil(L, 2)) + bone = lua_tostring(L, 2); + v3f position = v3f(0, 0, 0); + if(!lua_isnil(L, 3)) + position = read_v3f(L, 3); + v3f rotation = v3f(0, 0, 0); + if(!lua_isnil(L, 4)) + rotation = read_v3f(L, 4); + co->setBonePosition(bone, position, rotation); + return 0; + } + + // set_attach(self, parent, bone, position, rotation) + static int l_set_attach(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + ObjectRef *parent_ref = checkobject(L, 2); + ServerActiveObject *co = getobject(ref); + 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); + v3f position = v3f(0, 0, 0); + if(!lua_isnil(L, 4)) + position = read_v3f(L, 4); + v3f rotation = v3f(0, 0, 0); + if(!lua_isnil(L, 5)) + rotation = read_v3f(L, 5); + co->setAttachment(parent->getId(), bone, position, rotation); + return 0; + } + + // set_detach(self) + static int l_set_detach(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; + } + // set_properties(self, properties) static int l_set_properties(lua_State *L) { @@ -2932,7 +3025,54 @@ private: lua_pushlstring(L, formspec.c_str(), formspec.size()); return 1; } - + + // get_player_control(self) + static int l_get_player_control(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if(player == NULL){ + lua_pushlstring(L, "", 0); + return 1; + } + // Do it + PlayerControl control = player->getPlayerControl(); + lua_newtable(L); + lua_pushboolean(L, control.up); + lua_setfield(L, -2, "up"); + lua_pushboolean(L, control.down); + lua_setfield(L, -2, "down"); + lua_pushboolean(L, control.left); + lua_setfield(L, -2, "left"); + lua_pushboolean(L, control.right); + lua_setfield(L, -2, "right"); + lua_pushboolean(L, control.jump); + lua_setfield(L, -2, "jump"); + lua_pushboolean(L, control.aux1); + lua_setfield(L, -2, "aux1"); + lua_pushboolean(L, control.sneak); + lua_setfield(L, -2, "sneak"); + lua_pushboolean(L, control.LMB); + lua_setfield(L, -2, "LMB"); + lua_pushboolean(L, control.RMB); + lua_setfield(L, -2, "RMB"); + return 1; + } + + // get_player_control_bits(self) + static int l_get_player_control_bits(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if(player == NULL){ + lua_pushlstring(L, "", 0); + return 1; + } + // Do it + lua_pushnumber(L, player->keyPressed); + return 1; + } + public: ObjectRef(ServerActiveObject *object): m_object(object) @@ -3011,6 +3151,10 @@ const luaL_reg ObjectRef::methods[] = { method(ObjectRef, get_wielded_item), method(ObjectRef, set_wielded_item), method(ObjectRef, set_armor_groups), + method(ObjectRef, set_animation), + method(ObjectRef, set_bone_position), + method(ObjectRef, set_attach), + method(ObjectRef, set_detach), method(ObjectRef, set_properties), // LuaEntitySAO-only method(ObjectRef, setvelocity), @@ -3031,6 +3175,8 @@ const luaL_reg ObjectRef::methods[] = { method(ObjectRef, get_look_yaw), method(ObjectRef, set_inventory_formspec), method(ObjectRef, get_inventory_formspec), + method(ObjectRef, get_player_control), + method(ObjectRef, get_player_control_bits), {0,0} }; @@ -5430,6 +5576,19 @@ bool scriptapi_on_chat_message(lua_State *L, const std::string &name, return ate; } +void scriptapi_on_shutdown(lua_State *L) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + // Get registered shutdown hooks + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "registered_on_shutdown"); + // Call callbacks + scriptapi_run_callbacks(L, 0, RUN_CALLBACKS_MODE_FIRST); +} + void scriptapi_on_newplayer(lua_State *L, ServerActiveObject *player) { realitycheck(L); @@ -6588,6 +6747,8 @@ void scriptapi_luaentity_get_properties(lua_State *L, u16 id, lua_pop(L, 1); getstringfield(L, -1, "visual", prop->visual); + + getstringfield(L, -1, "mesh", prop->mesh); // Deprecated: read object properties directly read_object_properties(L, -1, prop); diff --git a/src/scriptapi.h b/src/scriptapi.h index 144cb3bc6..d71b8fe41 100644 --- a/src/scriptapi.h +++ b/src/scriptapi.h @@ -55,6 +55,9 @@ void scriptapi_environment_step(lua_State *L, float dtime); void scriptapi_environment_on_generated(lua_State *L, v3s16 minp, v3s16 maxp, u32 blockseed); +/* server */ +void scriptapi_on_shutdown(lua_State *L); + /* misc */ void scriptapi_on_newplayer(lua_State *L, ServerActiveObject *player); void scriptapi_on_dieplayer(lua_State *L, ServerActiveObject *player); diff --git a/src/server.cpp b/src/server.cpp index 2da9cbe24..a793c6e2a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -54,6 +54,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/pointedthing.h" #include "util/mathconstants.h" #include "rollback.h" +#include "util/serialize.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -1111,7 +1112,17 @@ Server::~Server() {} } } - + + { + JMutexAutoLock envlock(m_env_mutex); + JMutexAutoLock conlock(m_con_mutex); + + /* + Execute script shutdown hooks + */ + scriptapi_on_shutdown(m_lua); + } + { JMutexAutoLock envlock(m_env_mutex); @@ -1143,14 +1154,6 @@ Server::~Server() i = m_clients.getIterator(); i.atEnd() == false; i++) { - /*// Delete player - // NOTE: These are removed by env destructor - { - u16 peer_id = i.getNode()->getKey(); - JMutexAutoLock envlock(m_env_mutex); - m_env->removePlayer(peer_id); - }*/ - // Delete client delete i.getNode()->getValue(); } @@ -1371,9 +1374,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); @@ -1567,7 +1570,7 @@ void Server::AsyncRunStep() if(obj) data_buffer.append(serializeLongString( - obj->getClientInitializationData())); + obj->getClientInitializationData(client->net_proto_version))); else data_buffer.append(serializeLongString("")); @@ -2037,40 +2040,74 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) Read and check network protocol version */ - u16 net_proto_version = 0; + u16 min_net_proto_version = 0; if(datasize >= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2) + min_net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE]); + + // Use same version as minimum and maximum if maximum version field + // doesn't exist (backwards compatibility) + u16 max_net_proto_version = min_net_proto_version; + if(datasize >= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2+2) + max_net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2]); + + // Start with client's maximum version + u16 net_proto_version = max_net_proto_version; + + // Figure out a working version if it is possible at all + if(max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN || + min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) { - net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE]); + // If maximum is larger than our maximum, go with our maximum + if(max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX) + net_proto_version = SERVER_PROTOCOL_VERSION_MAX; + // Else go with client's maximum + else + net_proto_version = max_net_proto_version; } + verbosestream<<"Server: "<<peer_id<<" Protocol version: min: " + <<min_net_proto_version<<", max: "<<max_net_proto_version + <<", chosen: "<<net_proto_version<<std::endl; + getClient(peer_id)->net_proto_version = net_proto_version; - if(net_proto_version == 0) + if(net_proto_version < SERVER_PROTOCOL_VERSION_MIN || + net_proto_version > SERVER_PROTOCOL_VERSION_MAX) { - actionstream<<"Server: An old tried to connect from "<<addr_s + actionstream<<"Server: A mismatched client tried to connect from "<<addr_s <<std::endl; SendAccessDenied(m_con, peer_id, std::wstring( L"Your client's version is not supported.\n" L"Server version is ") - + narrow_to_wide(VERSION_STRING) + L"." + + narrow_to_wide(VERSION_STRING) + L",\n" + + L"server's PROTOCOL_VERSION is " + + narrow_to_wide(itos(SERVER_PROTOCOL_VERSION_MIN)) + + L"..." + + narrow_to_wide(itos(SERVER_PROTOCOL_VERSION_MAX)) + + L", client's PROTOCOL_VERSION is " + + narrow_to_wide(itos(min_net_proto_version)) + + L"..." + + narrow_to_wide(itos(max_net_proto_version)) ); return; } if(g_settings->getBool("strict_protocol_version_checking")) { - if(net_proto_version != PROTOCOL_VERSION) + if(net_proto_version != LATEST_PROTOCOL_VERSION) { - actionstream<<"Server: A mismatched client tried to connect" - <<" from "<<addr_s<<std::endl; + actionstream<<"Server: A mismatched (strict) client tried to " + <<"connect from "<<addr_s<<std::endl; SendAccessDenied(m_con, peer_id, std::wstring( L"Your client's version is not supported.\n" L"Server version is ") + narrow_to_wide(VERSION_STRING) + L",\n" - + L"server's PROTOCOL_VERSION is " - + narrow_to_wide(itos(PROTOCOL_VERSION)) + + L"server's PROTOCOL_VERSION (strict) is " + + narrow_to_wide(itos(LATEST_PROTOCOL_VERSION)) + L", client's PROTOCOL_VERSION is " - + narrow_to_wide(itos(net_proto_version)) + + narrow_to_wide(itos(min_net_proto_version)) + + L"..." + + narrow_to_wide(itos(max_net_proto_version)) ); return; } @@ -2212,11 +2249,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) Answer with a TOCLIENT_INIT */ { - SharedBuffer<u8> reply(2+1+6+8); + SharedBuffer<u8> reply(2+1+6+8+4); writeU16(&reply[0], TOCLIENT_INIT); writeU8(&reply[2], deployed); writeV3S16(&reply[2+1], floatToInt(playersao->getPlayer()->getPosition()+v3f(0,BS/2,0), BS)); writeU64(&reply[2+1+6], m_env->getServerMap().getSeed()); + writeF1000(&reply[2+1+6+8], g_settings->getFloat("dedicated_server_step")); // Send as reliable m_con.Send(peer_id, 0, reply, true); @@ -2242,8 +2280,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) return; } - getClient(peer_id)->serialization_version - = getClient(peer_id)->pending_serialization_version; + RemoteClient *client = getClient(peer_id); + client->serialization_version = + getClient(peer_id)->pending_serialization_version; /* Send some initialization data @@ -2256,7 +2295,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) SendItemDef(m_con, peer_id, m_itemdef); // Send node definitions - SendNodeDef(m_con, peer_id, m_nodedef); + SendNodeDef(m_con, peer_id, m_nodedef, client->net_proto_version); // Send media announcement sendMediaAnnouncement(peer_id); @@ -2310,9 +2349,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) } // Warnings about protocol version can be issued here - if(getClient(peer_id)->net_proto_version < PROTOCOL_VERSION) + if(getClient(peer_id)->net_proto_version < LATEST_PROTOCOL_VERSION) { - SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT IS OLD AND MAY WORK PROPERLY WITH THIS SERVER!"); + SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT'S " + L"VERSION MAY NOT BE FULLY COMPATIBLE WITH THIS SERVER!"); } /* @@ -2377,6 +2417,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) v3s32 ss = readV3S32(&data[start+2+12]); f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0; f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0; + u32 keyPressed = 0; + if(datasize >= 2+12+12+4+4+4) + keyPressed = (u32)readU32(&data[2+12+12+4+4]); v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.); v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.); pitch = wrapDegrees(pitch); @@ -2386,6 +2429,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) player->setSpeed(speed); player->setPitch(pitch); player->setYaw(yaw); + player->keyPressed=keyPressed; + player->control.up = (bool)(keyPressed&1); + player->control.down = (bool)(keyPressed&2); + player->control.left = (bool)(keyPressed&4); + player->control.right = (bool)(keyPressed&8); + player->control.jump = (bool)(keyPressed&16); + player->control.aux1 = (bool)(keyPressed&32); + player->control.sneak = (bool)(keyPressed&64); + player->control.LMB = (bool)(keyPressed&128); + player->control.RMB = (bool)(keyPressed&256); /*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to " <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")" @@ -3166,6 +3219,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) } } // action == 4 + /* Catch invalid actions @@ -3496,7 +3550,7 @@ void Server::SendItemDef(con::Connection &con, u16 peer_id, } void Server::SendNodeDef(con::Connection &con, u16 peer_id, - INodeDefManager *nodedef) + INodeDefManager *nodedef, u16 protocol_version) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); @@ -3508,7 +3562,7 @@ void Server::SendNodeDef(con::Connection &con, u16 peer_id, */ writeU16(os, TOCLIENT_NODEDEF); std::ostringstream tmp_os(std::ios::binary); - nodedef->serialize(tmp_os); + nodedef->serialize(tmp_os, protocol_version); std::ostringstream tmp_os2(std::ios::binary); compressZlib(tmp_os.str(), tmp_os2); os<<serializeLongString(tmp_os2.str()); @@ -4029,6 +4083,7 @@ void Server::fillMediaCache() paths.push_back(mod.path + DIR_DELIM + "textures"); paths.push_back(mod.path + DIR_DELIM + "sounds"); paths.push_back(mod.path + DIR_DELIM + "media"); + paths.push_back(mod.path + DIR_DELIM + "models"); } std::string path_all = "textures"; paths.push_back(path_all + DIR_DELIM + "all"); @@ -4054,6 +4109,7 @@ void Server::fillMediaCache() ".png", ".jpg", ".bmp", ".tga", ".pcx", ".ppm", ".psd", ".wal", ".rgb", ".ogg", + ".x", ".b3d", ".md2", ".obj", NULL }; if(removeStringEnd(filename, supported_ext) == ""){ diff --git a/src/server.h b/src/server.h index 223c1b0ff..f770fa3d4 100644 --- a/src/server.h +++ b/src/server.h @@ -602,7 +602,7 @@ private: static void SendItemDef(con::Connection &con, u16 peer_id, IItemDefManager *itemdef); static void SendNodeDef(con::Connection &con, u16 peer_id, - INodeDefManager *nodedef); + INodeDefManager *nodedef, u16 protocol_version); /* Non-static send methods. diff --git a/src/serverobject.h b/src/serverobject.h index ece53fd98..14752878f 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -118,7 +118,7 @@ public: The return value of this is passed to the client-side object when it is created */ - virtual std::string getClientInitializationData(){return "";} + virtual std::string getClientInitializationData(u16 protocol_version){return "";} /* The return value of this is passed to the server-side object @@ -152,6 +152,12 @@ public: virtual void setArmorGroups(const ItemGroupList &armor_groups) {} + virtual void setAnimation(v2f frames, float frame_speed, float frame_blend) + {} + virtual void setBonePosition(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; } virtual void notifyObjectPropertiesModified() diff --git a/src/staticobject.cpp b/src/staticobject.cpp new file mode 100644 index 000000000..2183f2ffe --- /dev/null +++ b/src/staticobject.cpp @@ -0,0 +1,92 @@ +/* +Minetest-c55 +Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "staticobject.h" +#include "util/serialize.h" + +void StaticObject::serialize(std::ostream &os) +{ + char buf[12]; + // type + buf[0] = type; + os.write(buf, 1); + // pos + writeV3S32((u8*)buf, v3s32(pos.X*1000,pos.Y*1000,pos.Z*1000)); + os.write(buf, 12); + // data + os<<serializeString(data); +} +void StaticObject::deSerialize(std::istream &is, u8 version) +{ + char buf[12]; + // type + is.read(buf, 1); + type = buf[0]; + // pos + is.read(buf, 12); + v3s32 intp = readV3S32((u8*)buf); + pos.X = (f32)intp.X/1000; + pos.Y = (f32)intp.Y/1000; + pos.Z = (f32)intp.Z/1000; + // data + data = deSerializeString(is); +} + +void StaticObjectList::serialize(std::ostream &os) +{ + char buf[12]; + // version + buf[0] = 0; + os.write(buf, 1); + // count + u16 count = m_stored.size() + m_active.size(); + writeU16((u8*)buf, count); + os.write(buf, 2); + for(core::list<StaticObject>::Iterator + i = m_stored.begin(); + i != m_stored.end(); i++) + { + StaticObject &s_obj = *i; + s_obj.serialize(os); + } + for(core::map<u16, StaticObject>::Iterator + i = m_active.getIterator(); + i.atEnd()==false; i++) + { + StaticObject s_obj = i.getNode()->getValue(); + s_obj.serialize(os); + } +} +void StaticObjectList::deSerialize(std::istream &is) +{ + char buf[12]; + // version + is.read(buf, 1); + u8 version = buf[0]; + // count + is.read(buf, 2); + u16 count = readU16((u8*)buf); + for(u16 i=0; i<count; i++) + { + StaticObject s_obj; + s_obj.deSerialize(is, version); + m_stored.push_back(s_obj); + } +} + diff --git a/src/staticobject.h b/src/staticobject.h index 87ba111ec..6fccbdd4f 100644 --- a/src/staticobject.h +++ b/src/staticobject.h @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include <string> #include <sstream> -#include "util/serialize.h" +#include "debug.h" struct StaticObject { @@ -43,33 +43,8 @@ struct StaticObject { } - void serialize(std::ostream &os) - { - char buf[12]; - // type - buf[0] = type; - os.write(buf, 1); - // pos - writeV3S32((u8*)buf, v3s32(pos.X*1000,pos.Y*1000,pos.Z*1000)); - os.write(buf, 12); - // data - os<<serializeString(data); - } - void deSerialize(std::istream &is, u8 version) - { - char buf[12]; - // type - is.read(buf, 1); - type = buf[0]; - // pos - is.read(buf, 12); - v3s32 intp = readV3S32((u8*)buf); - pos.X = (f32)intp.X/1000; - pos.Y = (f32)intp.Y/1000; - pos.Z = (f32)intp.Z/1000; - // data - data = deSerializeString(is); - } + void serialize(std::ostream &os); + void deSerialize(std::istream &is, u8 version); }; class StaticObjectList @@ -110,47 +85,8 @@ public: m_active.remove(id); } - void serialize(std::ostream &os) - { - char buf[12]; - // version - buf[0] = 0; - os.write(buf, 1); - // count - u16 count = m_stored.size() + m_active.size(); - writeU16((u8*)buf, count); - os.write(buf, 2); - for(core::list<StaticObject>::Iterator - i = m_stored.begin(); - i != m_stored.end(); i++) - { - StaticObject &s_obj = *i; - s_obj.serialize(os); - } - for(core::map<u16, StaticObject>::Iterator - i = m_active.getIterator(); - i.atEnd()==false; i++) - { - StaticObject s_obj = i.getNode()->getValue(); - s_obj.serialize(os); - } - } - void deSerialize(std::istream &is) - { - char buf[12]; - // version - is.read(buf, 1); - u8 version = buf[0]; - // count - is.read(buf, 2); - u16 count = readU16((u8*)buf); - for(u16 i=0; i<count; i++) - { - StaticObject s_obj; - s_obj.deSerialize(is, version); - m_stored.push_back(s_obj); - } - } + void serialize(std::ostream &os); + void deSerialize(std::istream &is); /* NOTE: When an object is transformed to active, it is removed diff --git a/src/test.cpp b/src/test.cpp index f81f2910c..bc0692a78 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "util/serialize.h" #include "noise.h" // PseudoRandom used for random data for compression +#include "clientserver.h" // LATEST_PROTOCOL_VERSION /* Asserts that the exception occurs @@ -314,6 +315,26 @@ struct TestSerialization: public TestBase } }; +struct TestNodedefSerialization: public TestBase +{ + void Run() + { + ContentFeatures f; + f.name = "default:stone"; + for(int i = 0; i < 6; i++) + f.tiledef[i].name = "default_stone.png"; + f.is_ground_content = true; + std::ostringstream os(std::ios::binary); + f.serialize(os, LATEST_PROTOCOL_VERSION); + verbosestream<<"Test ContentFeatures size: "<<os.str().size()<<std::endl; + std::istringstream is(os.str(), std::ios::binary); + ContentFeatures f2; + f2.deSerialize(is); + UASSERT(f.walkable == f2.walkable); + UASSERT(f.node_box.type == f2.node_box.type); + } +}; + struct TestCompress: public TestBase { void Run() @@ -1736,6 +1757,7 @@ void run_tests() TEST(TestSettings); TEST(TestCompress); TEST(TestSerialization); + TEST(TestNodedefSerialization); TESTPARAMS(TestMapNode, ndef); TESTPARAMS(TestVoxelManipulator, ndef); TESTPARAMS(TestVoxelAlgorithms, ndef); diff --git a/src/tile.cpp b/src/tile.cpp index e676c56c4..7cad1b836 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -372,6 +372,18 @@ public: // Update new texture pointer and texture coordinates to an // AtlasPointer based on it's texture id void updateAP(AtlasPointer &ap); + + bool isKnownSourceImage(const std::string &name) + { + bool is_known = false; + bool cache_found = m_source_image_existence.get(name, &is_known); + if(cache_found) + return is_known; + // Not found in cache; find out if a local file exists + is_known = (getTexturePath(name) != ""); + m_source_image_existence.set(name, is_known); + return is_known; + } // Processes queued texture requests from other threads. // Shall be called from the main thread. @@ -400,6 +412,9 @@ private: // This should be only accessed from the main thread SourceImageCache m_sourcecache; + // Thread-safe cache of what source images are known (true = known) + MutexedMap<std::string, bool> m_source_image_existence; + // A texture id is index in this array. // The first position contains a NULL texture. core::array<SourceAtlasPointer> m_atlaspointer_cache; @@ -519,15 +534,6 @@ core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d< void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); /* - Adds a new texture to the video driver and returns a pointer to it. - This pointer should not be dropped. Any texture that was registered - with that name before is removed (this may invalidate some ITexture - pointers). -*/ -video::ITexture* register_texture(video::IVideoDriver *driver, - std::string name, video::IImage *img); - -/* Generate image based on a string like "stone.png" or "[crack0". if baseimg is NULL, it is created. Otherwise stuff is made on it. */ @@ -695,9 +701,11 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) " create texture \""<<name<<"\""<<std::endl; } - // Create texture from resulting image if(baseimg != NULL) - t = register_texture(driver, name, baseimg); + { + // Create texture from resulting image + t = driver->addTexture(name.c_str(), baseimg); + } /* Add texture to caches (add NULL textures too) @@ -788,6 +796,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im assert(get_current_thread_id() == m_main_thread); m_sourcecache.insert(name, img, true, m_device->getVideoDriver()); + m_source_image_existence.set(name, true); } void TextureSource::rebuildImagesAndTextures() @@ -816,7 +825,7 @@ void TextureSource::rebuildImagesAndTextures() // Create texture from resulting image video::ITexture *t = NULL; if(img) - t = register_texture(driver, sap->name, img); + t = driver->addTexture(sap->name.c_str(), img); // Replace texture sap->a.atlas = t; @@ -1051,7 +1060,7 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) /* Make texture */ - video::ITexture *t = register_texture(driver, "__main_atlas__", atlas_img); + video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img); assert(t); /* @@ -1142,15 +1151,6 @@ video::IImage* generate_image_from_scratch(std::string name, return baseimg; } -video::ITexture* register_texture(video::IVideoDriver *driver, - std::string name, video::IImage *img) -{ - video::ITexture *old_texture = driver->findTexture(name.c_str()); - if(old_texture) - driver->removeTexture(old_texture); - return driver->addTexture(name.c_str(), img); -} - bool generate_image(std::string part_of_name, video::IImage *& baseimg, IrrlichtDevice *device, SourceImageCache *sourcecache) { @@ -1557,12 +1557,12 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, assert(img_top && img_left && img_right); // Create textures from images - video::ITexture *texture_top = register_texture(driver, - imagename_top + "__temp1__", img_top); - video::ITexture *texture_left = register_texture(driver, - imagename_left + "__temp2__", img_left); - video::ITexture *texture_right = register_texture(driver, - imagename_right + "__temp3__", img_right); + video::ITexture *texture_top = driver->addTexture( + (imagename_top + "__temp__").c_str(), img_top); + video::ITexture *texture_left = driver->addTexture( + (imagename_left + "__temp__").c_str(), img_left); + video::ITexture *texture_right = driver->addTexture( + (imagename_right + "__temp__").c_str(), img_right); assert(texture_top && texture_left && texture_right); // Drop images diff --git a/src/tile.h b/src/tile.h index ae986e797..12c40c833 100644 --- a/src/tile.h +++ b/src/tile.h @@ -131,6 +131,7 @@ public: virtual IrrlichtDevice* getDevice() {return NULL;} virtual void updateAP(AtlasPointer &ap){}; + virtual bool isKnownSourceImage(const std::string &name)=0; }; class IWritableTextureSource : public ITextureSource @@ -149,6 +150,7 @@ public: virtual IrrlichtDevice* getDevice() {return NULL;} virtual void updateAP(AtlasPointer &ap){}; + virtual bool isKnownSourceImage(const std::string &name)=0; virtual void processQueue()=0; virtual void insertSourceImage(const std::string &name, video::IImage *img)=0; diff --git a/src/util/pointer.h b/src/util/pointer.h index 766cc2328..775f0a336 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -222,7 +222,7 @@ public: /* Copies whole buffer */ - SharedBuffer(T *t, unsigned int size) + SharedBuffer(const T *t, unsigned int size) { m_size = size; if(m_size != 0) diff --git a/src/util/serialize.h b/src/util/serialize.h index 50a002c10..d552dec94 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define UTIL_SERIALIZE_HEADER #include "../irrlichttypes.h" +#include "../irrlichttypes_bloated.h" #include "../irr_v2d.h" #include "../irr_v3d.h" #include <iostream> @@ -197,6 +198,24 @@ inline v3s16 readV3S16(u8 *data) return p; } +inline void writeARGB8(u8 *data, video::SColor p) +{ + writeU8(&data[0], p.getAlpha()); + writeU8(&data[1], p.getRed()); + writeU8(&data[2], p.getGreen()); + writeU8(&data[3], p.getBlue()); +} + +inline video::SColor readARGB8(u8 *data) +{ + video::SColor p; + p.setAlpha(readU8(&data[0])); + p.setRed(readU8(&data[1])); + p.setGreen(readU8(&data[2])); + p.setBlue(readU8(&data[3])); + return p; +} + /* The above stuff directly interfaced to iostream */ @@ -344,6 +363,20 @@ inline v3s16 readV3S16(std::istream &is) return readV3S16((u8*)buf); } +inline void writeARGB8(std::ostream &os, video::SColor p) +{ + char buf[4] = {0}; + writeARGB8((u8*)buf, p); + os.write(buf, 4); +} + +inline video::SColor readARGB8(std::istream &is) +{ + char buf[4] = {0}; + is.read(buf, 4); + return readARGB8((u8*)buf); +} + /* More serialization stuff */ |