From 7b37f2c1d88df053b335339706330c659be58d5e Mon Sep 17 00:00:00 2001 From: luk3yx Date: Tue, 18 Feb 2020 11:54:18 +0100 Subject: [PATCH] Re-add protocol v25-33 support for servers. --- builtin/game/item_entity.lua | 8 +- builtin/game/models/mc_compat_character.b3d | Bin 0 -> 173799 bytes src/activeobject.h | 7 +- src/clientiface.cpp | 32 +++- src/clientiface.h | 2 + src/itemdef.cpp | 25 ++- src/mapblock.cpp | 4 +- src/mapblock.h | 3 +- src/network/networkpacket.h | 1 + src/network/networkprotocol.h | 2 +- src/network/serveropcodes.cpp | 2 +- src/network/serverpackethandler.cpp | 32 +++- src/nodedef.cpp | 200 +++++++++++++++++--- src/nodedef.h | 1 + src/nodemetadata.cpp | 13 +- src/nodemetadata.h | 5 +- src/object_properties.cpp | 73 +++++-- src/object_properties.h | 2 +- src/server.cpp | 147 +++++++++++--- src/server.h | 2 +- src/server/luaentity_sao.cpp | 45 +++-- src/server/luaentity_sao.h | 2 +- src/server/player_sao.cpp | 87 +++++++-- src/server/player_sao.h | 5 +- src/server/unit_sao.cpp | 93 ++++++--- src/server/unit_sao.h | 15 +- src/sound.h | 6 + src/tileanimation.cpp | 16 +- src/tool.cpp | 8 +- 29 files changed, 665 insertions(+), 173 deletions(-) create mode 100644 builtin/game/models/mc_compat_character.b3d diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 20dd18044..b5bce81f0 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -195,8 +195,12 @@ core.register_entity(":__builtin:item", { return -- Don't do anything end - assert(moveresult, - "Collision info missing, this is caused by an out-of-date/buggy mod or game") + -- assert(moveresult, + -- "Collision info missing, this is caused by an out-of-date/buggy mod or game") + if not moveresult then + self.object:set_velocity({x=0, y=0, z=0}) + return + end if not moveresult.collides then -- future TODO: items should probably decelerate in air diff --git a/builtin/game/models/mc_compat_character.b3d b/builtin/game/models/mc_compat_character.b3d new file mode 100644 index 0000000000000000000000000000000000000000..8147edc6d3d557d95b8389b79be7166c66da8475 GIT binary patch literal 173799 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 literal 0 HcmV?d00001 diff --git a/src/activeobject.h b/src/activeobject.h index d97014c96..24576f358 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -44,15 +44,18 @@ enum ActiveObjectType { struct ActiveObjectMessage { - ActiveObjectMessage(u16 id_, bool reliable_=true, const std::string &data_ = "") : + ActiveObjectMessage(u16 id_, bool reliable_ = true, + const std::string &data_ = "", const std::string legacy_ = "") : id(id_), reliable(reliable_), - datastring(data_) + datastring(data_), + legacystring(legacy_) {} u16 id; bool reliable; std::string datastring; + std::string legacystring; }; enum ActiveObjectCommand { diff --git a/src/clientiface.cpp b/src/clientiface.cpp index e08df78f3..adf039232 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -750,8 +750,8 @@ void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacyp } else if (client->net_proto_version != 0) { pkt_to_send = legacypkt; } else { - warningstream << "Client with unhandled version to handle: '" - << client->net_proto_version << "'"; + // This will happen if a client is connecting when sendToAllCompat + // is called, this can safely be ignored. continue; } @@ -762,6 +762,34 @@ void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacyp } } +void ClientInterface::oldSendToAll(NetworkPacket *pkt) +{ + RecursiveMutexAutoLock clientslock(m_clients_mutex); + for (auto &client_it : m_clients) { + RemoteClient *client = client_it.second; + + if (client->net_proto_version != 0 && client->net_proto_version < 37) { + m_con->Send(client->peer_id, + clientCommandFactoryTable[pkt->getCommand()].channel, pkt, + clientCommandFactoryTable[pkt->getCommand()].reliable); + } + } +} + +void ClientInterface::newSendToAll(NetworkPacket *pkt) +{ + RecursiveMutexAutoLock clientslock(m_clients_mutex); + for (auto &client_it : m_clients) { + RemoteClient *client = client_it.second; + + if (client->net_proto_version > 36) { + m_con->Send(client->peer_id, + clientCommandFactoryTable[pkt->getCommand()].channel, pkt, + clientCommandFactoryTable[pkt->getCommand()].reliable); + } + } +} + RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) { RecursiveMutexAutoLock clientslock(m_clients_mutex); diff --git a/src/clientiface.h b/src/clientiface.h index 3b272b26f..09897819b 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -458,6 +458,8 @@ public: /* send to all clients */ void sendToAll(NetworkPacket *pkt); void sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, u16 min_proto_ver); + void oldSendToAll(NetworkPacket *pkt); + void newSendToAll(NetworkPacket *pkt); /* delete a client */ void DeleteClient(session_t peer_id); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 2f0badbe1..638bf0a45 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -126,6 +126,11 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const { // protocol_version >= 37 u8 version = 6; + + // protocol_version < 37 + if (protocol_version < 37) + version = 3; + writeU8(os, version); writeU8(os, type); os << serializeString(name); @@ -153,13 +158,25 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const os << serializeString(node_placement_prediction); - // Version from ContentFeatures::serialize to keep in sync - sound_place.serialize(os, CONTENTFEATURES_VERSION); - sound_place_failed.serialize(os, CONTENTFEATURES_VERSION); + if (version == 3) { + os << serializeString(sound_place.name); + writeF1000(os, sound_place.gain); + writeF1000(os, range); + os << serializeString(sound_place_failed.name); + writeF1000(os, sound_place_failed.gain); + } else { + // Version from ContentFeatures::serialize to keep in sync + sound_place.serialize(os, CONTENTFEATURES_VERSION); + sound_place_failed.serialize(os, CONTENTFEATURES_VERSION); + writeF32(os, range); + } - writeF(os, range, protocol_version); os << serializeString(palette_image); writeARGB8(os, color); + + if (version == 3) + return; + os << serializeString(inventory_overlay); os << serializeString(wield_overlay); } diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 3b6f7a138..f0b6f4e77 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -355,7 +355,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, } } -void MapBlock::serialize(std::ostream &os, u8 version, bool disk) +void MapBlock::serialize(std::ostream &os, u8 version, bool disk, std::string formspec_prepend) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -411,7 +411,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) Node metadata */ std::ostringstream oss(std::ios_base::binary); - m_node_metadata.serialize(oss, version, disk); + m_node_metadata.serialize(oss, version, disk, false, formspec_prepend); compressZlib(oss.str(), os); /* diff --git a/src/mapblock.h b/src/mapblock.h index ea05f85a3..b5272a31e 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -482,7 +482,8 @@ public: // These don't write or read version by itself // Set disk to true for on-disk format, false for over-the-network format // Precondition: version >= SER_FMT_VER_LOWEST_WRITE - void serialize(std::ostream &os, u8 version, bool disk); + void serialize(std::ostream &os, u8 version, bool disk, + std::string formspec_prepend=""); // If disk == true: In addition to doing other things, will add // unknown blocks from id-name mapping to wndef void deSerialize(std::istream &is, u8 version, bool disk); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 19c06113b..3e87809b4 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -44,6 +44,7 @@ public: u16 getCommand() { return m_command; } const u32 getRemainingBytes() const { return m_datasize - m_read_offset; } const char *getRemainingString() { return getString(m_read_offset); } + u16 getProtocolVersion() const { return m_protocol_version; } // Returns a c-string without copying. // A better name for this would be getRawString() diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index f4c39a992..2368e3c7c 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -210,7 +210,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range -#define SERVER_PROTOCOL_VERSION_MIN 37 +#define SERVER_PROTOCOL_VERSION_MIN 25 #define SERVER_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION // Client's supported network protocol range diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 91e9c54c0..06620d6f4 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -89,7 +89,7 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = null_command_handler, // 0x3e null_command_handler, // 0x3f { "TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_RequestMedia }, // 0x40 - null_command_handler, // 0x41 + { "TOSERVER_RECEIVED_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_Deprecated }, // 0x41 not used by the server since protocol version 23 null_command_handler, // 0x42 { "TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, &Server::handleCommand_ClientReady }, // 0x43 null_command_handler, // 0x44 diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 5a58cbd51..06f253958 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -324,7 +324,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt) sendDetachedInventories(peer_id, false); // Send player movement settings - SendMovement(peer_id); + SendMovement(peer_id, protocol_version); // Send time of day u16 time = m_env->getTimeOfDay(); @@ -454,7 +454,7 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, NetworkPacket *pkt) { - if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4 + 4 + 1 + 1) + if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4) return; v3s32 ps, ss; @@ -474,10 +474,14 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, f32 fov = 0; u8 wanted_range = 0; - *pkt >> keyPressed; - *pkt >> f32fov; - fov = (f32)f32fov / 80.0f; - *pkt >> wanted_range; + if (pkt->getRemainingBytes() >= 4) + *pkt >> keyPressed; + if (pkt->getRemainingBytes() >= 1) { + *pkt >> f32fov; + fov = (f32)f32fov / 80.0f; + if (pkt->getRemainingBytes() >= 1) + *pkt >> wanted_range; + } v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f); v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f); @@ -791,9 +795,6 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) void Server::handleCommand_Damage(NetworkPacket* pkt) { - u16 damage; - - *pkt >> damage; session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); @@ -806,6 +807,17 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) return; } + u16 damage; + + // Minetest 0.4 uses 8-bit integers for damage. + if (player->protocol_version >= 37) { + *pkt >> damage; + } else { + u8 raw_damage; + *pkt >> raw_damage; + damage = raw_damage; + } + PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { errorstream << @@ -1670,7 +1682,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (client->chosen_mech != AUTH_MECHANISM_SRP && client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) { actionstream << "Server: got SRP _M packet, while auth" - << "is going on with mech " << client->chosen_mech << " from " + << "is going on with mech " << client->chosen_mech << " from " << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 80a2d5388..4acdc5508 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -74,7 +74,13 @@ void NodeBox::reset() void NodeBox::serialize(std::ostream &os, u16 protocol_version) const { // Protocol >= 36 - const u8 version = 6; + u8 version; + if (protocol_version >= 36) + version = 6; + else if (protocol_version >= 27) + version = 3; + else + version = 2; writeU8(os, version); switch (type) { @@ -84,28 +90,38 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const writeU16(os, fixed.size()); for (const aabb3f &nodebox : fixed) { - writeV3F32(os, nodebox.MinEdge); - writeV3F32(os, nodebox.MaxEdge); + writeV3F(os, nodebox.MinEdge, protocol_version); + writeV3F(os, nodebox.MaxEdge, protocol_version); } break; case NODEBOX_WALLMOUNTED: writeU8(os, type); - writeV3F32(os, wall_top.MinEdge); - writeV3F32(os, wall_top.MaxEdge); - writeV3F32(os, wall_bottom.MinEdge); - writeV3F32(os, wall_bottom.MaxEdge); - writeV3F32(os, wall_side.MinEdge); - writeV3F32(os, wall_side.MaxEdge); + writeV3F(os, wall_top.MinEdge, protocol_version); + writeV3F(os, wall_top.MaxEdge, protocol_version); + writeV3F(os, wall_bottom.MinEdge, protocol_version); + writeV3F(os, wall_bottom.MaxEdge, protocol_version); + writeV3F(os, wall_side.MinEdge, protocol_version); + writeV3F(os, wall_side.MaxEdge, protocol_version); break; case NODEBOX_CONNECTED: + if (version <= 2) { + // send old clients nodes that can't be walked through + // to prevent abuse + writeU8(os, NODEBOX_FIXED); + + writeU16(os, 1); + writeV3F1000(os, v3f(-BS/2, -BS/2, -BS/2)); + writeV3F1000(os, v3f(BS/2, BS/2, BS/2)); + break; + } writeU8(os, type); #define WRITEBOX(box) \ writeU16(os, (box).size()); \ for (const aabb3f &i: (box)) { \ - writeV3F32(os, i.MinEdge); \ - writeV3F32(os, i.MaxEdge); \ + writeV3F(os, i.MinEdge, protocol_version); \ + writeV3F(os, i.MaxEdge, protocol_version); \ }; WRITEBOX(fixed); @@ -115,14 +131,17 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const WRITEBOX(connect_left); WRITEBOX(connect_back); WRITEBOX(connect_right); - WRITEBOX(disconnected_top); - WRITEBOX(disconnected_bottom); - WRITEBOX(disconnected_front); - WRITEBOX(disconnected_left); - WRITEBOX(disconnected_back); - WRITEBOX(disconnected_right); - WRITEBOX(disconnected); - WRITEBOX(disconnected_sides); + + if (version > 5) { + WRITEBOX(disconnected_top); + WRITEBOX(disconnected_bottom); + WRITEBOX(disconnected_front); + WRITEBOX(disconnected_left); + WRITEBOX(disconnected_back); + WRITEBOX(disconnected_right); + WRITEBOX(disconnected); + WRITEBOX(disconnected_sides); + } break; default: writeU8(os, type); @@ -207,12 +226,41 @@ void NodeBox::deSerialize(std::istream &is) void TileDef::serialize(std::ostream &os, u16 protocol_version) const { - // protocol_version >= 36 - u8 version = 6; + u8 version; + if (protocol_version >= 37) + version = 6; + else if (protocol_version >= 30) + version = 4; + else if (protocol_version >= 29) + version = 3; + else if (protocol_version >= 26) + version = 2; + else + version = 1; + writeU8(os, version); os << serializeString(name); animation.serialize(os, version); + + // Compatibility with Minetest 0.4. + if (version < 6) { + writeU8(os, backface_culling); + if (version < 2) + return; + writeU8(os, tileable_horizontal); + writeU8(os, tileable_vertical); + if (version < 3) + return; + writeU8(os, has_color); + if (has_color) { + writeU8(os, color.getRed()); + writeU8(os, color.getGreen()); + writeU8(os, color.getBlue()); + } + return; + } + bool has_scale = scale > 0; u16 flags = 0; if (backface_culling) @@ -424,7 +472,14 @@ void ContentFeatures::reset() void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const { - const u8 version = CONTENTFEATURES_VERSION; + if (protocol_version < 31) { + serializeOld(os, protocol_version); + return; + } + + u8 version = CONTENTFEATURES_VERSION; + if (protocol_version < 37) + version = 10; writeU8(os, version); // general @@ -440,7 +495,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const // visual writeU8(os, drawtype); os << serializeString(mesh); - writeF32(os, visual_scale); + writeF(os, visual_scale, protocol_version); writeU8(os, 6); for (const TileDef &td : tiledef) td.serialize(os, protocol_version); @@ -1125,6 +1180,105 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc } #endif +//// Serialization of old ContentFeatures formats +void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const +{ + u8 compatible_param_type_2 = param_type_2; + if ((protocol_version < 28) + && (compatible_param_type_2 == CPT2_MESHOPTIONS)) + compatible_param_type_2 = CPT2_NONE; + else if (protocol_version < 30) { + if (compatible_param_type_2 == CPT2_COLOR) + compatible_param_type_2 = CPT2_NONE; + else if (compatible_param_type_2 == CPT2_COLORED_FACEDIR) + compatible_param_type_2 = CPT2_FACEDIR; + else if (compatible_param_type_2 == CPT2_COLORED_WALLMOUNTED) + compatible_param_type_2 = CPT2_WALLMOUNTED; + } + + float compatible_visual_scale = visual_scale; + if (protocol_version < 30 && drawtype == NDT_PLANTLIKE) + compatible_visual_scale = sqrt(visual_scale); + + TileDef compatible_tiles[6]; + for (u8 i = 0; i < 6; i++) { + compatible_tiles[i] = tiledef[i]; + if (tiledef_overlay[i].name != "") { + std::stringstream s; + s << "(" << tiledef[i].name << ")^(" << tiledef_overlay[i].name + << ")"; + compatible_tiles[i].name = s.str(); + } + } + + // Protocol >= 24 + if (protocol_version < 31) { + const u8 version = protocol_version < 27 ? 7 : 8; + writeU8(os, 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, compatible_visual_scale); + writeU8(os, 6); + for (u32 i = 0; i < 6; i++) + compatible_tiles[i].serialize(os, protocol_version); + writeU8(os, CF_SPECIAL_COUNT); + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) + tiledef_special[i].serialize(os, protocol_version); + 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, compatible_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, liquid_renewable); + writeU8(os, light_source); + writeU32(os, damage_per_second); + node_box.serialize(os, protocol_version); + selection_box.serialize(os, protocol_version); + writeU8(os, legacy_facedir_simple); + writeU8(os, legacy_wallmounted); + sound_footstep.serialize(os, version); + sound_dig.serialize(os, version); + sound_dug.serialize(os, version); + writeU8(os, rightclickable); + writeU8(os, drowning); + writeU8(os, leveled); + writeU8(os, liquid_range); + writeU8(os, waving); + os << serializeString(mesh); + collision_box.serialize(os, protocol_version); + writeU8(os, floodable); + writeU16(os, connects_to_ids.size()); + for (u16 connects_to_id : connects_to_ids) + writeU16(os, connects_to_id); + writeU8(os, connect_sides); + } else { + throw SerializationError("ContentFeatures::serialize(): " + "Unsupported version requested"); + } +} + /* NodeDefManager */ diff --git a/src/nodedef.h b/src/nodedef.h index 525f6e4d3..711e8443a 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -414,6 +414,7 @@ struct ContentFeatures void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); void deSerializeOld(std::istream &is, int version); + void serializeOld(std::ostream &os, u16 protocol_version) const; /*! * Since vertex alpha is no longer supported, this method * adds opacity directly to the texture pixels. diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 801aa47fa..ef498c5a5 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -40,7 +40,8 @@ NodeMetadata::~NodeMetadata() delete m_inventory; } -void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const +void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk, + std::string formspec_prepend) const { int num_vars = disk ? m_stringvars.size() : countNonPrivate(); writeU32(os, num_vars); @@ -50,7 +51,11 @@ void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const continue; os << serializeString(sv.first); - os << serializeLongString(sv.second); + if (!formspec_prepend.empty() && sv.first == "formspec" && + sv.second.find("no_prepend[]") == std::string::npos) + os << serializeLongString(sv.second + formspec_prepend); + else + os << serializeLongString(sv.second); if (version >= 2) writeU8(os, (priv) ? 1 : 0); } @@ -113,7 +118,7 @@ int NodeMetadata::countNonPrivate() const */ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, - bool absolute_pos) const + bool absolute_pos, std::string formspec_prepend) const { /* Version 0 is a placeholder for "nothing to see here; go away." @@ -146,7 +151,7 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, u16 p16 = (p.Z * MAP_BLOCKSIZE + p.Y) * MAP_BLOCKSIZE + p.X; writeU16(os, p16); } - data->serialize(os, version, disk); + data->serialize(os, version, disk, formspec_prepend); } } diff --git a/src/nodemetadata.h b/src/nodemetadata.h index 17e47013f..04c3cad90 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -40,7 +40,8 @@ public: NodeMetadata(IItemDefManager *item_def_mgr); ~NodeMetadata(); - void serialize(std::ostream &os, u8 version, bool disk=true) const; + void serialize(std::ostream &os, u8 version, bool disk=true, + std::string formspec_prepend="") const; void deSerialize(std::istream &is, u8 version); void clear(); @@ -82,7 +83,7 @@ public: ~NodeMetadataList(); void serialize(std::ostream &os, u8 blockver, bool disk = true, - bool absolute_pos = false) const; + bool absolute_pos = false, std::string formspec_prepend = "") const; void deSerialize(std::istream &is, IItemDefManager *item_def_mgr, bool absolute_pos = false); diff --git a/src/object_properties.cpp b/src/object_properties.cpp index af3c7f26a..2c6a74a6f 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -73,28 +73,62 @@ std::string ObjectProperties::dump() return os.str(); } -void ObjectProperties::serialize(std::ostream &os) const +void ObjectProperties::serialize(std::ostream &os, u16 protocol_version) const { - writeU8(os, 4); // PROTOCOL_VERSION >= 37 + if (protocol_version > 36) + writeU8(os, 4); // PROTOCOL_VERSION >= 37 + else + writeU8(os, 1); writeU16(os, hp_max); writeU8(os, physical); - writeF32(os, 0.f); // Removed property (weight) - writeV3F32(os, collisionbox.MinEdge); - writeV3F32(os, collisionbox.MaxEdge); - writeV3F32(os, selectionbox.MinEdge); - writeV3F32(os, selectionbox.MaxEdge); - writeU8(os, pointable); - os << serializeString(visual); - writeV3F32(os, visual_size); - writeU16(os, textures.size()); - for (const std::string &texture : textures) { - os << serializeString(texture); + writeF(os, 0.f, protocol_version); // Removed property (weight) + if (protocol_version > 36) { + writeV3F32(os, collisionbox.MinEdge); + writeV3F32(os, collisionbox.MaxEdge); + writeV3F32(os, selectionbox.MinEdge); + writeV3F32(os, selectionbox.MaxEdge); + writeU8(os, pointable); + } else if (pointable) { + writeV3F1000(os, selectionbox.MinEdge); + writeV3F1000(os, selectionbox.MaxEdge); + } else { + // A hack to emulate unpointable objects + for (u8 i = 0; i < 6; i++) + writeF1000(os, 0); } + + // The "wielditem" type isn't exactly the same as "item", however this + // is the most similar compatible option + if (visual == "item" && protocol_version < 37) + os << serializeString("wielditem"); + else + os << serializeString(visual); + + if (protocol_version > 36) { + writeV3F32(os, visual_size); + } else { + writeF1000(os, visual_size.X); + writeF1000(os, visual_size.Y); + } + + // MT 0.4.15 and below don't have the wield_item property and expect + // wield_item to be in textures[0]. + if (protocol_version < 37 && (visual == "item" || visual == "wielditem") && + !wield_item.empty()) { + writeU16(os, 1); + os << serializeString(wield_item); + } else { + writeU16(os, textures.size()); + for (const std::string &texture : textures) { + os << serializeString(texture); + } + } + writeV2S16(os, spritediv); writeV2S16(os, initial_sprite_basepos); writeU8(os, is_visible); writeU8(os, makes_footstep_sound); - writeF32(os, automatic_rotate); + writeF(os, automatic_rotate, protocol_version); // Added in protocol version 14 os << serializeString(mesh); writeU16(os, colors.size()); @@ -102,16 +136,21 @@ void ObjectProperties::serialize(std::ostream &os) const writeARGB8(os, color); } writeU8(os, collideWithObjects); - writeF32(os, stepheight); + writeF(os, stepheight, protocol_version); writeU8(os, automatic_face_movement_dir); - writeF32(os, automatic_face_movement_dir_offset); + writeF(os, automatic_face_movement_dir_offset, protocol_version); writeU8(os, backface_culling); os << serializeString(nametag); writeARGB8(os, nametag_color); - writeF32(os, automatic_face_movement_max_rotation_per_sec); + writeF(os, automatic_face_movement_max_rotation_per_sec, protocol_version); os << serializeString(infotext); os << serializeString(wield_item); writeS8(os, glow); + + // Everything after this can use writeF32(). + if (protocol_version < 37) + return; + writeU16(os, breath_max); writeF32(os, eye_height); writeF32(os, zoom_fov); diff --git a/src/object_properties.h b/src/object_properties.h index e68b45e2c..dab826bb4 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -65,6 +65,6 @@ struct ObjectProperties ObjectProperties(); std::string dump(); - void serialize(std::ostream &os) const; + void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); }; diff --git a/src/server.cpp b/src/server.cpp index 0c1012cc4..8a00f65be 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -771,7 +771,11 @@ void Server::AsyncRunStep(bool initial_step) // u16 id // std::string data buffer.append(idbuf, sizeof(idbuf)); - buffer.append(serializeString(aom.datastring)); + if (client->net_proto_version >= 37 || + aom.legacystring.empty()) + buffer.append(serializeString(aom.datastring)); + else + buffer.append(serializeString(aom.legacystring)); } } /* @@ -1147,6 +1151,9 @@ void Server::ProcessData(NetworkPacket *pkt) return; } + RemoteClient *client = getClient(peer_id); + if (client) + pkt->setProtocolVersion(client->net_proto_version); handleCommand(pkt); } catch (SendFailedException &e) { errorstream << "Server::ProcessData(): SendFailedException: " @@ -1154,7 +1161,7 @@ void Server::ProcessData(NetworkPacket *pkt) << std::endl; } catch (PacketError &e) { actionstream << "Server::ProcessData(): PacketError: " - << "what=" << e.what() + << "what=" << e.what() << ", command=" << pkt->getCommand() // TODO: REMOVE COMMAND= << std::endl; } } @@ -1296,11 +1303,11 @@ void Server::Send(session_t peer_id, NetworkPacket *pkt) clientCommandFactoryTable[pkt->getCommand()].reliable); } -void Server::SendMovement(session_t peer_id) +void Server::SendMovement(session_t peer_id, u16 protocol_version) { std::ostringstream os(std::ios_base::binary); - NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id); + NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id, protocol_version); pkt << g_settings->getFloat("movement_acceleration_default"); pkt << g_settings->getFloat("movement_acceleration_air"); @@ -1335,7 +1342,13 @@ void Server::SendPlayerHPOrDie(PlayerSAO *playersao, const PlayerHPChangeReason void Server::SendHP(session_t peer_id, u16 hp) { NetworkPacket pkt(TOCLIENT_HP, 1, peer_id); - pkt << hp; + // Minetest 0.4 uses 8-bit integers for HPP. + if (m_clients.getProtocolVersion(peer_id) >= 37) { + pkt << hp; + } else { + u8 raw_hp = hp & 0xFF; + pkt << raw_hp; + } Send(&pkt); } @@ -1454,6 +1467,9 @@ void Server::SendInventory(PlayerSAO *sao, bool incremental) void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) { + NetworkPacket legacypkt(TOCLIENT_CHAT_MESSAGE_OLD, 0, peer_id); + legacypkt << message.message; + NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id); u8 version = 1; u8 type = message.type; @@ -1464,9 +1480,12 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) if (!player) return; - Send(&pkt); + if (player->protocol_version < 35) + Send(&legacypkt); + else + Send(&pkt); } else { - m_clients.sendToAll(&pkt); + m_clients.sendToAllCompat(&pkt, &legacypkt, 35); } } @@ -1484,7 +1503,12 @@ void Server::SendShowFormspecMessage(session_t peer_id, const std::string &forms pkt.putLongString(""); } else { m_formspec_state_data[peer_id] = formname; - pkt.putLongString(formspec); + RemotePlayer *player = m_env->getPlayer(peer_id); + if (player && player->protocol_version < 37 && + formspec.find("no_prepend[]") == std::string::npos) + pkt.putLongString(formspec + player->formspec_prepend); + else + pkt.putLongString(formspec); } pkt << formname; @@ -1522,7 +1546,7 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version, } assert(protocol_version != 0); - NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id); + NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id, protocol_version); { // NetworkPacket and iostreams are incompatible... @@ -1569,7 +1593,7 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, } assert(protocol_version != 0); - NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id); + NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id, protocol_version); pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime @@ -1604,7 +1628,7 @@ void Server::SendDeleteParticleSpawner(session_t peer_id, u32 id) void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) { - NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id); + NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id, m_clients.getProtocolVersion(peer_id)); pkt << id << (u8) form->type << form->pos << form->name << form->scale << form->text << form->number << form->item << form->dir @@ -1623,7 +1647,7 @@ void Server::SendHUDRemove(session_t peer_id, u32 id) void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value) { - NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id); + NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id, m_clients.getProtocolVersion(peer_id)); pkt << id << (u8) stat; switch (stat) { @@ -1735,7 +1759,8 @@ void Server::SendSetStars(session_t peer_id, const StarParams ¶ms) void Server::SendCloudParams(session_t peer_id, const CloudParams ¶ms) { - NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id); + NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id, + m_clients.getProtocolVersion(peer_id)); pkt << params.density << params.color_bright << params.color_ambient << params.height << params.thickness << params.speed; Send(&pkt); @@ -1754,15 +1779,20 @@ void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { - NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); - pkt << time << time_speed; + if (peer_id != PEER_ID_INEXISTENT) { + NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id, + m_clients.getProtocolVersion(peer_id)); + pkt << time << time_speed; - if (peer_id == PEER_ID_INEXISTENT) { - m_clients.sendToAll(&pkt); - } - else { Send(&pkt); + return; } + + NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id, 37); + NetworkPacket legacypkt(TOCLIENT_TIME_OF_DAY, 0, peer_id, 32); + pkt << time << time_speed; + legacypkt << time << time_speed; + m_clients.sendToAllCompat(&pkt, &legacypkt, 37); } void Server::SendPlayerHP(session_t peer_id) @@ -1792,7 +1822,7 @@ void Server::SendMovePlayer(session_t peer_id) PlayerSAO *sao = player->getPlayerSAO(); assert(sao); - NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); + NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id, player->protocol_version); pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y; { @@ -1821,7 +1851,7 @@ void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames f32 animation_speed) { NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, - peer_id); + peer_id, m_clients.getProtocolVersion(peer_id)); pkt << animation_frames[0] << animation_frames[1] << animation_frames[2] << animation_frames[3] << animation_speed; @@ -1831,7 +1861,7 @@ void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames void Server::SendEyeOffset(session_t peer_id, v3f first, v3f third) { - NetworkPacket pkt(TOCLIENT_EYE_OFFSET, 0, peer_id); + NetworkPacket pkt(TOCLIENT_EYE_OFFSET, 0, peer_id, m_clients.getProtocolVersion(peer_id)); pkt << first << third; Send(&pkt); } @@ -1864,7 +1894,12 @@ void Server::SendPlayerInventoryFormspec(session_t peer_id) return; NetworkPacket pkt(TOCLIENT_INVENTORY_FORMSPEC, 0, peer_id); - pkt.putLongString(player->inventory_formspec); + if (player->protocol_version < 37 && player->inventory_formspec.find( + "no_prepend[]") == std::string::npos) + pkt.putLongString(player->inventory_formspec + + player->formspec_prepend); + else + pkt.putLongString(player->inventory_formspec); Send(&pkt); } @@ -1875,6 +1910,10 @@ void Server::SendPlayerFormspecPrepend(session_t peer_id) assert(player); if (player->getPeerId() == PEER_ID_INEXISTENT) return; + if (player->protocol_version < 37) { + SendPlayerInventoryFormspec(peer_id); + return; + } NetworkPacket pkt(TOCLIENT_FORMSPEC_PREPEND, 0, peer_id); pkt << player->formspec_prepend; @@ -1995,6 +2034,10 @@ void Server::SendActiveObjectMessages(session_t peer_id, const std::string &data void Server::SendCSMRestrictionFlags(session_t peer_id) { + const u16 protocol_version = m_clients.getProtocolVersion(peer_id); + if (protocol_version < 35 && protocol_version != 0) + return; + NetworkPacket pkt(TOCLIENT_CSM_RESTRICTION_FLAGS, sizeof(m_csm_restriction_flags) + sizeof(m_csm_restriction_noderange), peer_id); pkt << m_csm_restriction_flags << m_csm_restriction_noderange; @@ -2086,17 +2129,29 @@ s32 Server::playSound(const SimpleSoundSpec &spec, float gain = params.gain * spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); + NetworkPacket legacypkt(TOCLIENT_PLAY_SOUND, 0, PEER_ID_INEXISTENT, 32); pkt << id << spec.name << gain << (u8) params.type << pos << params.object << params.loop << params.fade << params.pitch << ephemeral; + legacypkt << id << spec.name << gain + << (u8) params.type << pos << params.object + << params.loop << params.fade; bool as_reliable = !ephemeral; + bool play_sound = gain > 0; for (const u16 dst_client : dst_clients) { + const u16 protocol_version = m_clients.getProtocolVersion(dst_client); + if (!play_sound && protocol_version < 32) + continue; if (psound) psound->clients.insert(dst_client); - m_clients.send(dst_client, 0, &pkt, as_reliable); + + if (protocol_version >= 37) + m_clients.send(dst_client, 0, &pkt, as_reliable); + else + m_clients.send(dst_client, 0, &legacypkt, as_reliable); } return id; } @@ -2257,6 +2312,13 @@ void Server::sendMetadataChanged(const std::list &meta_updates, float far if (!client) continue; + if (client->net_proto_version < 37) { + for (const v3s16 &pos : meta_updates) { + client->SetBlockNotSent(getNodeBlockPos(pos)); + } + continue; + } + ServerActiveObject *player = m_env->getActiveObject(i); v3f player_pos = player ? player->getBasePosition() : v3f(); @@ -2303,7 +2365,12 @@ void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, */ std::ostringstream os(std::ios_base::binary); - block->serialize(os, ver, false); + + RemotePlayer *player = m_env->getPlayer(peer_id); + if (player && player->protocol_version < 37) + block->serialize(os, ver, false, player->formspec_prepend); + else + block->serialize(os, ver, false); block->serializeNetworkSpecific(os); std::string s = os.str(); @@ -2483,7 +2550,10 @@ void Server::fillMediaCache() std::vector paths; m_modmgr->getModsMediaPaths(paths); fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); - fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server"); + fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + + DIR_DELIM + "server"); + fs::GetRecursiveDirs(paths, porting::path_share + DIR_DELIM + "builtin" + + DIR_DELIM + "game" + DIR_DELIM + "models"); // Collect media file information from paths into cache for (const std::string &mediapath : paths) { @@ -2645,7 +2715,9 @@ void Server::sendRequestedMedia(session_t peer_id, void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id) { NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id); + NetworkPacket legacy_pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id); pkt << name; + legacy_pkt << name; if (!inventory) { pkt << false; // Remove inventory @@ -2660,12 +2732,25 @@ void Server::sendDetachedInventory(Inventory *inventory, const std::string &name const std::string &os_str = os.str(); pkt << static_cast(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients pkt.putRawString(os_str); + legacy_pkt.putRawString(os_str); } - if (peer_id == PEER_ID_INEXISTENT) - m_clients.sendToAll(&pkt); - else - Send(&pkt); + if (peer_id == PEER_ID_INEXISTENT) { + m_clients.newSendToAll(&pkt); + if (inventory) + m_clients.oldSendToAll(&legacy_pkt); + } else { + RemoteClient *client = getClientNoEx(peer_id, CS_Created); + if (!client) { + warningstream << "Could not get client in sendDetachedInventory!" + << std::endl; + } + + if (!client || client->net_proto_version >= 37) + Send(&pkt); + else if (inventory) + Send(&legacy_pkt); + } } void Server::sendDetachedInventories(session_t peer_id, bool incremental) @@ -2774,7 +2859,7 @@ void Server::acceptAuth(session_t peer_id, bool forSudoMode) if (!forSudoMode) { RemoteClient* client = getClient(peer_id, CS_Invalid); - NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id); + NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id, client->net_proto_version); // Right now, the auth mechs don't change between login and sudo mode. u32 sudo_auth_mechs = client->allowed_auth_mechs; diff --git a/src/server.h b/src/server.h index 52ca1ca3f..40b083272 100644 --- a/src/server.h +++ b/src/server.h @@ -374,7 +374,7 @@ private: void init(); - void SendMovement(session_t peer_id); + void SendMovement(session_t peer_id, u16 protocol_version); void SendHP(session_t peer_id, u16 hp); void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 7b7b7b6c0..e99f56851 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -117,9 +117,10 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) if(!m_properties_sent) { m_properties_sent = true; - std::string str = getPropertyPacket(); + std::string str = getPropertyPacket(37); + std::string legacy_str = getPropertyPacket(32); // create message and add to list - m_messages_out.emplace(getId(), true, str); + m_messages_out.emplace(getId(), true, str, legacy_str); } // If attached, check that our parent is still there. If it isn't, detach. @@ -228,19 +229,26 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) os << serializeString(""); // name writeU8(os, 0); // is_player writeU16(os, getId()); //id - writeV3F32(os, m_base_position); - writeV3F32(os, m_rotation); + writeV3F(os, m_base_position, protocol_version); + if (protocol_version >= 37) + writeV3F32(os, m_rotation); + else + writeF1000(os, m_rotation.Y); writeU16(os, m_hp); std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(getPropertyPacket( + protocol_version)); // message 1 msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + msg_os << serializeLongString(generateUpdateAnimationCommand( + protocol_version)); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeLongString(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, + protocol_version)); // m_bone_position.size } - msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeLongString(generateUpdateAttachmentCommand( + protocol_version)); // 4 int message_count = 4 + m_bone_position.size(); @@ -472,9 +480,9 @@ std::string LuaEntitySAO::getName() return m_init_name; } -std::string LuaEntitySAO::getPropertyPacket() +std::string LuaEntitySAO::getPropertyPacket(const u16 protocol_version) { - return generateSetPropertiesCommand(m_prop); + return generateSetPropertiesCommand(m_prop, protocol_version); } void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) @@ -500,10 +508,23 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) m_rotation, do_interpolate, is_movement_end, - update_interval + update_interval, + 37 ); + + std::string legacy_str = generateUpdatePositionCommand( + m_base_position, + m_velocity, + m_acceleration, + m_rotation, + do_interpolate, + is_movement_end, + update_interval, + 32 + ); + // create message and add to list - m_messages_out.emplace(getId(), false, str); + m_messages_out.emplace(getId(), false, str, legacy_str); } bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 3cae2a636..708639941 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -71,7 +71,7 @@ public: bool collideWithObjects() const; private: - std::string getPropertyPacket(); + std::string getPropertyPacket(const u16 protocol_version); void sendPosition(bool do_interpolate, bool is_movement_end); std::string generateSetTextureModCommand() const; static std::string generateSetSpriteCommand(v2s16 p, u16 num_frames, diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 2f0fa397f..912b4f27e 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -112,20 +112,37 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) os << serializeString(m_player->getName()); // name writeU8(os, 1); // is_player writeS16(os, getId()); // id - writeV3F32(os, m_base_position); - writeV3F32(os, m_rotation); - writeU16(os, getHP()); + if (protocol_version >= 37) { + writeV3F32(os, m_base_position); + writeV3F32(os, m_rotation); + writeU16(os, getHP()); + } else { + writeV3F1000(os, m_base_position + v3f(0, BS, 0)); + writeF1000(os, m_rotation.Y); + + // HP is sent as a signed integer + const u16 hp = getHP(); + if (hp > S16_MAX) + writeS16(os, S16_MAX); + else + writeS16(os, static_cast(hp)); + } std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(getPropertyPacket( + protocol_version)); // message 1 msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + msg_os << serializeLongString(generateUpdateAnimationCommand( + protocol_version)); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeLongString(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, + protocol_version)); // m_bone_position.size } - msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 - msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5 + msg_os << serializeLongString(generateUpdateAttachmentCommand( + protocol_version)); // 4 + msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand( + protocol_version)); // 5 int message_count = 5 + m_bone_position.size(); @@ -221,9 +238,10 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (!m_properties_sent) { m_properties_sent = true; - std::string str = getPropertyPacket(); + std::string str = getPropertyPacket(37); + std::string legacy_str = getPropertyPacket(32); // create message and add to list - m_messages_out.emplace(getId(), true, str); + m_messages_out.emplace(getId(), true, str, legacy_str); m_env->getScriptIface()->player_event(this, "properties_changed"); } @@ -286,30 +304,45 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_rotation, true, false, - update_interval + update_interval, + 37 ); + + std::string legacy_str = generateUpdatePositionCommand( + pos + v3f(0.0f, BS, 0.0f), + v3f(0.0f, 0.0f, 0.0f), + v3f(0.0f, 0.0f, 0.0f), + m_rotation, + true, + false, + update_interval, + 32 + ); + // create message and add to list - m_messages_out.emplace(getId(), false, str); + m_messages_out.emplace(getId(), false, str, legacy_str); } if (!m_physics_override_sent) { m_physics_override_sent = true; // create message and add to list - m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand()); + m_messages_out.emplace(getId(), true, + generateUpdatePhysicsOverrideCommand(37), + generateUpdatePhysicsOverrideCommand(32)); } sendOutdatedData(); } -std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const +std::string PlayerSAO::generateUpdatePhysicsOverrideCommand(const u16 protocol_version) const { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE); // parameters - writeF32(os, m_physics_override_speed); - writeF32(os, m_physics_override_jump); - writeF32(os, m_physics_override_gravity); + writeF(os, m_physics_override_speed, protocol_version); + writeF(os, m_physics_override_jump, protocol_version); + writeF(os, m_physics_override_gravity, protocol_version); // these are sent inverted so we get true when the server sends nothing writeU8(os, !m_physics_override_sneak); writeU8(os, !m_physics_override_sneak_glitch); @@ -537,10 +570,26 @@ void PlayerSAO::unlinkPlayerSessionAndSave() m_env->removePlayer(m_player); } -std::string PlayerSAO::getPropertyPacket() +std::string PlayerSAO::getPropertyPacket(const u16 protocol_version) { m_prop.is_visible = (true); - return generateSetPropertiesCommand(m_prop); + + ObjectProperties prop = m_prop; + if (protocol_version < 37 && (m_prop.mesh == "3d_armor_character.b3d" || + m_prop.mesh == "character.b3d" || + m_prop.mesh == "skinsdb_3d_armor_character_5.b3d")) { + prop.mesh = "mc_compat_character.b3d"; + for (u16 i = prop.textures.size(); i < 5; i++) { + prop.textures.emplace_back("blank.png"); + } + } + + // Remove a one-node offset from a copy of the object properties for MT 0.4 + if (protocol_version < 37) { + prop.selectionbox.MinEdge.Y -= 1.0f; + prop.selectionbox.MaxEdge.Y -= 1.0f; + } + return generateSetPropertiesCommand(prop, protocol_version); } void PlayerSAO::setMaxSpeedOverride(const v3f &vel) diff --git a/src/server/player_sao.h b/src/server/player_sao.h index fae7fd079..2d351fdf1 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -180,9 +180,10 @@ public: inline Metadata &getMeta() { return m_meta; } private: - std::string getPropertyPacket(); + std::string getPropertyPacket(const u16 protocol_version); void unlinkPlayerSessionAndSave(); - std::string generateUpdatePhysicsOverrideCommand() const; + std::string generateUpdatePhysicsOverrideCommand( + const u16 protocol_version) const; RemotePlayer *m_player = nullptr; session_t m_peer_id = 0; diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index 2890448cc..2700896ee 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -99,24 +99,34 @@ void UnitSAO::sendOutdatedData() if (!m_animation_sent) { m_animation_sent = true; m_animation_speed_sent = true; - m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand()); + m_messages_out.emplace(getId(), true, + generateUpdateAnimationCommand(37), + generateUpdateAnimationCommand(32)); } else if (!m_animation_speed_sent) { // Animation speed is also sent when 'm_animation_sent == false' m_animation_speed_sent = true; - m_messages_out.emplace(getId(), true, generateUpdateAnimationSpeedCommand()); + m_messages_out.emplace(getId(), true, + generateUpdateAnimationSpeedCommand(), + // MT 0.4 has no update animation speed command + generateUpdateAnimationCommand(32)); } if (!m_bone_position_sent) { m_bone_position_sent = true; for (const auto &bone_pos : m_bone_position) { - m_messages_out.emplace(getId(), true, generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); + std::string str = generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, 37); + std::string legacy_str = generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, 32); + m_messages_out.emplace(getId(), true, str, legacy_str); } } if (!m_attachment_sent) { m_attachment_sent = true; - m_messages_out.emplace(getId(), true, generateUpdateAttachmentCommand()); + m_messages_out.emplace(getId(), true, + generateUpdateAttachmentCommand(37), + generateUpdateAttachmentCommand(32)); } } // clang-format on @@ -235,7 +245,7 @@ void UnitSAO::notifyObjectPropertiesModified() m_properties_sent = false; } -std::string UnitSAO::generateUpdateAttachmentCommand() const +std::string UnitSAO::generateUpdateAttachmentCommand(const u16 protocol_version) const { std::ostringstream os(std::ios::binary); // command @@ -243,21 +253,37 @@ std::string UnitSAO::generateUpdateAttachmentCommand() const // parameters writeS16(os, m_attachment_parent_id); os << serializeString(m_attachment_bone); - writeV3F32(os, m_attachment_position); - writeV3F32(os, m_attachment_rotation); + + // Add/remove offsets to compensate for MT 0.4 + if (protocol_version >= 37) { + writeV3F32(os, m_attachment_position); + } else { + v3f compat_attachment_position = m_attachment_position; + if (getType() == ACTIVEOBJECT_TYPE_PLAYER) { + compat_attachment_position.Y += BS; + } else { + ServerActiveObject *p = + m_env->getActiveObject(m_attachment_parent_id); + if (p && p->getType() == ACTIVEOBJECT_TYPE_PLAYER) + compat_attachment_position.Y -= BS; + } + writeV3F1000(os, compat_attachment_position); + } + + writeV3F(os, m_attachment_rotation, protocol_version); return os.str(); } -std::string UnitSAO::generateUpdateBonePositionCommand( - const std::string &bone, const v3f &position, const v3f &rotation) +std::string UnitSAO::generateUpdateBonePositionCommand(const std::string &bone, + const v3f &position, const v3f &rotation, const u16 protocol_version) { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_SET_BONE_POSITION); // parameters os << serializeString(bone); - writeV3F32(os, position); - writeV3F32(os, rotation); + writeV3F(os, position, protocol_version); + writeV3F(os, rotation, protocol_version); return os.str(); } @@ -271,15 +297,15 @@ std::string UnitSAO::generateUpdateAnimationSpeedCommand() const return os.str(); } -std::string UnitSAO::generateUpdateAnimationCommand() const +std::string UnitSAO::generateUpdateAnimationCommand(const u16 protocol_version) const { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_SET_ANIMATION); // parameters - writeV2F32(os, m_animation_range); - writeF32(os, m_animation_speed); - writeF32(os, m_animation_blend); + writeV2F(os, m_animation_range, protocol_version); + writeF(os, m_animation_speed, protocol_version); + writeF(os, m_animation_blend, protocol_version); // these are sent inverted so we get true when the server sends nothing writeU8(os, !m_animation_loop); return os.str(); @@ -299,33 +325,38 @@ std::string UnitSAO::generateUpdateArmorGroupsCommand() const std::string UnitSAO::generateUpdatePositionCommand(const v3f &position, const v3f &velocity, const v3f &acceleration, const v3f &rotation, - bool do_interpolate, bool is_movement_end, f32 update_interval) + bool do_interpolate, bool is_movement_end, f32 update_interval, + const u16 protocol_version) { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_UPDATE_POSITION); // pos - writeV3F32(os, position); + writeV3F(os, position, protocol_version); // velocity - writeV3F32(os, velocity); + writeV3F(os, velocity, protocol_version); // acceleration - writeV3F32(os, acceleration); + writeV3F(os, acceleration, protocol_version); // rotation - writeV3F32(os, rotation); + if (protocol_version >= 37) + writeV3F32(os, rotation); + else + writeF1000(os, rotation.Y); // do_interpolate writeU8(os, do_interpolate); // is_end_position (for interpolation) writeU8(os, is_movement_end); // update_interval (for interpolation) - writeF32(os, update_interval); + writeF(os, update_interval, protocol_version); return os.str(); } -std::string UnitSAO::generateSetPropertiesCommand(const ObjectProperties &prop) const +std::string UnitSAO::generateSetPropertiesCommand( + const ObjectProperties &prop, const u16 protocol_version) const { std::ostringstream os(std::ios::binary); writeU8(os, AO_CMD_SET_PROPERTIES); - prop.serialize(os); + prop.serialize(os, protocol_version); return os.str(); } @@ -339,7 +370,19 @@ std::string UnitSAO::generatePunchCommand(u16 result_hp) const return os.str(); } +std::string UnitSAO::generateLegacyPunchCommand(u16 result_hp) const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_PUNCHED); + // result_hp + writeU16(os, result_hp); + return os.str(); +} + void UnitSAO::sendPunchCommand() { - m_messages_out.emplace(getId(), true, generatePunchCommand(getHP())); + const u16 result_hp = getHP(); + m_messages_out.emplace(getId(), true, generatePunchCommand(result_hp), + generateLegacyPunchCommand(result_hp)); } diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h index 76d409ba6..6e1f2138c 100644 --- a/src/server/unit_sao.h +++ b/src/server/unit_sao.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "constants.h" #include "object_properties.h" #include "serveractiveobject.h" @@ -79,16 +80,19 @@ public: void sendOutdatedData(); // Update packets - std::string generateUpdateAttachmentCommand() const; + std::string generateUpdateAttachmentCommand(const u16 protocol_version) const; std::string generateUpdateAnimationSpeedCommand() const; - std::string generateUpdateAnimationCommand() const; + std::string generateUpdateAnimationCommand(const u16 protocol_version) const; std::string generateUpdateArmorGroupsCommand() const; static std::string generateUpdatePositionCommand(const v3f &position, const v3f &velocity, const v3f &acceleration, const v3f &rotation, - bool do_interpolate, bool is_movement_end, f32 update_interval); - std::string generateSetPropertiesCommand(const ObjectProperties &prop) const; + bool do_interpolate, bool is_movement_end, f32 update_interval, + const u16 protocol_version); + std::string generateSetPropertiesCommand( + const ObjectProperties &prop, const u16 protocol_version) const; static std::string generateUpdateBonePositionCommand(const std::string &bone, - const v3f &position, const v3f &rotation); + const v3f &position, const v3f &rotation, + const u16 protocol_version); void sendPunchCommand(); protected: @@ -112,6 +116,7 @@ private: void onDetach(int parent_id); std::string generatePunchCommand(u16 result_hp) const; + std::string generateLegacyPunchCommand(u16 result_hp) const; // Armor groups bool m_armor_groups_sent = false; diff --git a/src/sound.h b/src/sound.h index 090b240c4..bda0780b0 100644 --- a/src/sound.h +++ b/src/sound.h @@ -40,6 +40,12 @@ struct SimpleSoundSpec void serialize(std::ostream &os, u8 cf_version) const { os << serializeString(name); + if (cf_version < 13) { + writeF1000(os, gain); + if (cf_version > 10) + writeF1000(os, pitch); + return; + } writeF32(os, gain); writeF32(os, pitch); writeF32(os, fade); diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index a78cb8cb3..a44d8be35 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -21,15 +21,27 @@ with this program; if not, write to the Free Software Foundation, Inc., void TileAnimationParams::serialize(std::ostream &os, u8 tiledef_version) const { + if (tiledef_version < 3 && type != TAT_VERTICAL_FRAMES) { + writeU8(os, TAT_NONE); + writeU16(os, 1); + writeU16(os, 1); + writeF1000(os, 1.0f); + return; + } + writeU8(os, type); + + // Approximate protocol version + const u16 protocol_version = tiledef_version >= 6 ? 37 : 32; + if (type == TAT_VERTICAL_FRAMES) { writeU16(os, vertical_frames.aspect_w); writeU16(os, vertical_frames.aspect_h); - writeF32(os, vertical_frames.length); + writeF(os, vertical_frames.length, protocol_version); } else if (type == TAT_SHEET_2D) { writeU8(os, sheet_2d.frames_w); writeU8(os, sheet_2d.frames_h); - writeF32(os, sheet_2d.frame_length); + writeF(os, sheet_2d.frame_length, protocol_version); } } diff --git a/src/tool.cpp b/src/tool.cpp index af0434bd5..e956345a5 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -58,9 +58,11 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const { if (protocol_version >= 38) writeU8(os, 5); - else + else if (protocol_version == 37) writeU8(os, 4); // proto == 37 - writeF32(os, full_punch_interval); + else + writeU8(os, 2); // proto >= 18 + writeF(os, full_punch_interval, protocol_version); writeS16(os, max_drop_level); writeU32(os, groupcaps.size()); for (const auto &groupcap : groupcaps) { @@ -72,7 +74,7 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const writeU32(os, cap->times.size()); for (const auto &time : cap->times) { writeS16(os, time.first); - writeF32(os, time.second); + writeF(os, time.second, protocol_version); } }