diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 0d63bd24f..997b79ac5 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -776,17 +776,20 @@ enum ToServerCommand : u16 TOSERVER_INTERACT = 0x39, /* - [0] u16 command - [2] u8 action - [3] u16 item - [5] u32 length of the next item - [9] serialized PointedThing - actions: - 0: start digging (from undersurface) or use - 1: stop digging (all parameters ignored) - 2: digging completed - 3: place block or item (to abovesurface) - 4: use item + u16 command + u8 action (InteractAction) + u16 item + u32 length of the next item + u8[length] serialized PointedThing + v3s32 player position multiplied by 100 and converted to integers + v3s32 player speed multiplied by 100 and converted to integers + s32 player pitch multiplied by 100 and converted to an integer + s32 player yaw multiplied by 100 and converted to an integer + u32 keyPressed + u8 fov + u8 wanted drawing range + optional: + u8 flags; it is 0x01 if the camera is inverted */ TOSERVER_REMOVED_SOUNDS = 0x3a, diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index cf1dcacb1..5c044b792 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -878,15 +878,6 @@ static inline void getWieldedItem(const PlayerSAO *playersao, std::optionalisDead()) { actionstream << "Server: " << player->getName() << " tried to interact while dead; ignoring." << std::endl; - if (pointed.type == POINTEDTHING_NODE) { - // Re-send block to revert change on client-side - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface); - client->SetBlockNotSent(blockpos); - } - // Call callbacks + getClient(peer_id)->respondToInteraction(action, pointed, false); m_script->on_cheat(playersao, "interacted_while_dead"); return; } @@ -969,22 +954,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (!checkPriv(player->getName(), "interact")) { actionstream << player->getName() << " attempted to interact with " << pointed.dump() << " without 'interact' privilege" << std::endl; - - if (pointed.type != POINTEDTHING_NODE) - return; - - // Re-send block to revert change on client-side - RemoteClient *client = getClient(peer_id); - // Digging completed -> under - if (action == INTERACT_DIGGING_COMPLETED) { - v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface); - client->SetBlockNotSent(blockpos); - } - // Placement -> above - else if (action == INTERACT_PLACE) { - v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface); - client->SetBlockNotSent(blockpos); - } + getClient(peer_id)->respondToInteraction(action, pointed, false); return; } @@ -1012,12 +982,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) float d = playersao->getEyePosition().getDistanceFrom(target_pos); if (!checkInteractDistance(player, d, pointed.dump())) { - if (pointed.type == POINTEDTHING_NODE) { - // Re-send block to revert change on client-side - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface); - client->SetBlockNotSent(blockpos); - } + getClient(peer_id)->respondToInteraction(action, pointed, false); return; } } @@ -1170,14 +1135,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (is_valid_dig && n.getContent() != CONTENT_IGNORE) m_script->node_on_dig(p_under, n, playersao); - v3s16 blockpos = getNodeBlockPos(p_under); - RemoteClient *client = getClient(peer_id); - // Send unusual result (that is, node not being removed) - if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) - // Re-send block to revert change on client-side - client->SetBlockNotSent(blockpos); - else - client->ResendBlockIfOnWire(blockpos); + // For whatever reason we assume that the client always predicts that a + // dug node is air irrespective of the node's node_dig_prediction + bool prediction_success = + m_env->getMap().getNode(p_under).getContent() == CONTENT_AIR; + getClient(peer_id)->respondToInteraction(action, pointed, + prediction_success); return; } // action == INTERACT_DIGGING_COMPLETED @@ -1221,24 +1184,11 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) SendInventory(player, true); } - if (pointed.type != POINTEDTHING_NODE) - return; - - // If item has node placement prediction, always send the - // blocks to make sure the client knows what exactly happened - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface); - v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface); - if (had_prediction) { - client->SetBlockNotSent(blockpos); - if (blockpos2 != blockpos) - client->SetBlockNotSent(blockpos2); - } else { - client->ResendBlockIfOnWire(blockpos); - if (blockpos2 != blockpos) - client->ResendBlockIfOnWire(blockpos2); - } - + // Since we do not known if the client has predicted the node at + // pointed.above or pointed.under, + // we assume that a prediction is always wrong. + getClient(peer_id)->respondToInteraction(action, pointed, + !had_prediction); return; } // action == INTERACT_PLACE diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index b114c6c84..16561e5a2 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -65,14 +65,6 @@ RemoteClient::RemoteClient() : { } -void RemoteClient::ResendBlockIfOnWire(v3s16 p) -{ - // if this block is on wire, mark it for sending again as soon as possible - if (m_blocks_sending.find(p) != m_blocks_sending.end()) { - SetBlockNotSent(p); - } -} - static LuaEntitySAO *getAttachedObject(PlayerSAO *sao, ServerEnvironment *env) { ServerActiveObject *ao = sao; @@ -457,6 +449,30 @@ void RemoteClient::SetBlocksNotSent(const std::vector &blocks) } } +void RemoteClient::respondToInteraction(InteractAction action, + const PointedThing &pointed, bool prediction_success) +{ + if ((action != INTERACT_PLACE && action != INTERACT_DIGGING_COMPLETED) + || pointed.type != POINTEDTHING_NODE) + // The client has not predicted not node changes + return; + + // The client may have an outdated mapblock if the placement or dig + // prediction was wrong or if an old mapblock is still being sent to it. + v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface); + if (!prediction_success + || m_blocks_sending.find(blockpos) != m_blocks_sending.end()) { + SetBlockNotSent(blockpos); + } + if (action != INTERACT_PLACE) + return; + v3s16 blockpos2 = getNodeBlockPos(pointed.node_abovesurface); + if (blockpos2 != blockpos && (!prediction_success + || m_blocks_sending.find(blockpos2) != m_blocks_sending.end())) { + SetBlockNotSent(blockpos2); + } +} + void RemoteClient::notifyEvent(ClientStateEvent event) { std::ostringstream myerror; diff --git a/src/server/clientiface.h b/src/server/clientiface.h index 294bcbd26..f2f6ff66f 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -13,6 +13,7 @@ #include "porting.h" #include "threading/mutex_auto_lock.h" #include "clientdynamicinfo.h" +#include "util/pointedthing.h" #include #include @@ -255,12 +256,17 @@ public: void SetBlocksNotSent(const std::vector &blocks); /** - * tell client about this block being modified right now. - * this information is required to requeue the block in case it's "on wire" - * while modification is processed by server - * @param p position of modified block + * Trigger block sending to undo client-side predicted node changes + * + * \param action The interact action to determine which predictions the + * client could have made + * \param pointed The positions where the client has interacted + * \param prediction_success If true, assume that the client has made a + * correct prediction and send the mapblock only if an outdated mapblock + * is currently "on wire", which can erroneously override the prediction */ - void ResendBlockIfOnWire(v3s16 p); + void respondToInteraction(InteractAction action, + const PointedThing &pointed, bool prediction_success); u32 getSendingCount() const { return m_blocks_sending.size(); }